extralite 1.4 → 1.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,11 +1,15 @@
1
1
  #include <stdio.h>
2
2
  #include "ruby.h"
3
+ #include "ruby/thread.h"
3
4
  #include <sqlite3.h>
4
5
 
5
6
  VALUE cError;
6
7
  VALUE cSQLError;
7
8
  VALUE cBusyError;
9
+
10
+ ID ID_KEYS;
8
11
  ID ID_STRIP;
12
+ ID ID_TO_S;
9
13
 
10
14
  typedef struct Database_t {
11
15
  sqlite3 *sqlite3_db;
@@ -47,6 +51,10 @@ static VALUE Database_allocate(VALUE klass) {
47
51
  } \
48
52
  }
49
53
 
54
+ /* call-seq: initialize(path)
55
+ *
56
+ * Initializes a new SQLite database with the given path.
57
+ */
50
58
 
51
59
  VALUE Database_initialize(VALUE self, VALUE path) {
52
60
  int rc;
@@ -68,6 +76,10 @@ VALUE Database_initialize(VALUE self, VALUE path) {
68
76
  return Qnil;
69
77
  }
70
78
 
79
+ /* call-seq: close
80
+ *
81
+ * Closes the database.
82
+ */
71
83
  VALUE Database_close(VALUE self) {
72
84
  int rc;
73
85
  Database_t *db;
@@ -82,6 +94,10 @@ VALUE Database_close(VALUE self) {
82
94
  return self;
83
95
  }
84
96
 
97
+ /* call-seq: closed?
98
+ *
99
+ * Returns true if the database is closed.
100
+ */
85
101
  VALUE Database_closed_p(VALUE self) {
86
102
  Database_t *db;
87
103
  GetDatabase(self, db);
@@ -108,8 +124,35 @@ inline VALUE get_column_value(sqlite3_stmt *stmt, int col, int type) {
108
124
  return Qnil;
109
125
  }
110
126
 
127
+ static void bind_parameter_value(sqlite3_stmt *stmt, int pos, VALUE value);
128
+
129
+ static inline void bind_hash_parameter_values(sqlite3_stmt *stmt, VALUE hash) {
130
+ VALUE keys = rb_funcall(hash, ID_KEYS, 0);
131
+ int len = RARRAY_LEN(keys);
132
+ for (int i = 0; i < len; i++) {
133
+ VALUE k = RARRAY_AREF(keys, i);
134
+ VALUE v = rb_hash_aref(hash, k);
135
+
136
+ switch (TYPE(k)) {
137
+ case T_FIXNUM:
138
+ bind_parameter_value(stmt, NUM2INT(k), v);
139
+ return;
140
+ case T_SYMBOL:
141
+ k = rb_funcall(k, ID_TO_S, 0);
142
+ case T_STRING:
143
+ if(RSTRING_PTR(k)[0] != ':') k = rb_str_plus(rb_str_new2(":"), k);
144
+ int pos = sqlite3_bind_parameter_index(stmt, StringValuePtr(k));
145
+ bind_parameter_value(stmt, pos, v);
146
+ return;
147
+ default:
148
+ rb_raise(cError, "Cannot bind hash key value idx %d", i);
149
+ }
150
+ }
151
+ RB_GC_GUARD(keys);
152
+ }
153
+
111
154
  static inline void bind_parameter_value(sqlite3_stmt *stmt, int pos, VALUE value) {
112
- switch (TYPE(value)) {
155
+ switch (TYPE(value)) {
113
156
  case T_NIL:
114
157
  sqlite3_bind_null(stmt, pos);
115
158
  return;
@@ -128,6 +171,9 @@ static inline void bind_parameter_value(sqlite3_stmt *stmt, int pos, VALUE value
128
171
  case T_STRING:
129
172
  sqlite3_bind_text(stmt, pos, RSTRING_PTR(value), RSTRING_LEN(value), SQLITE_TRANSIENT);
130
173
  return;
174
+ case T_HASH:
175
+ bind_hash_parameter_values(stmt, value);
176
+ return;
131
177
  default:
132
178
  rb_raise(cError, "Cannot bind parameter at position %d", pos);
133
179
  }
@@ -143,7 +189,7 @@ static inline void bind_all_parameters(sqlite3_stmt *stmt, int argc, VALUE *argv
143
189
 
144
190
  static inline VALUE get_column_names(sqlite3_stmt *stmt, int column_count) {
145
191
  VALUE arr = rb_ary_new2(column_count);
146
- for (int i = 0; i < column_count; i++) {
192
+ for (int i = 0; i < column_count; i++) {
147
193
  VALUE name = ID2SYM(rb_intern(sqlite3_column_name(stmt, i)));
148
194
  rb_ary_push(arr, name);
149
195
  }
@@ -168,36 +214,80 @@ static inline VALUE row_to_ary(sqlite3_stmt *stmt, int column_count) {
168
214
  return row;
169
215
  }
170
216
 
171
- inline void prepare_multi_stmt(sqlite3 *db, sqlite3_stmt **stmt, VALUE sql) {
172
- const char *rest = 0;
173
- const char *ptr = RSTRING_PTR(sql);
174
- const char *end = ptr + RSTRING_LEN(sql);
217
+ struct multi_stmt_ctx {
218
+ sqlite3 *db;
219
+ sqlite3_stmt **stmt;
220
+ const char *str;
221
+ int len;
222
+ int rc;
223
+ };
224
+
225
+ void *prepare_multi_stmt_without_gvl(void *ptr) {
226
+ struct multi_stmt_ctx *ctx = (struct multi_stmt_ctx *)ptr;
227
+ const char *rest = NULL;
228
+ const char *str = ctx->str;
229
+ const char *end = ctx->str + ctx->len;
175
230
  while (1) {
176
- int rc = sqlite3_prepare(db, ptr, end - ptr, stmt, &rest);
177
- if (rc) {
178
- sqlite3_finalize(*stmt);
179
- rb_raise(cSQLError, "%s", sqlite3_errmsg(db));
231
+ ctx->rc = sqlite3_prepare_v2(ctx->db, str, end - str, ctx->stmt, &rest);
232
+ if (ctx->rc) {
233
+ sqlite3_finalize(*ctx->stmt);
234
+ return NULL;
180
235
  }
181
236
 
182
- if (rest == end) return;
183
-
237
+ if (rest == end) return NULL;
238
+
184
239
  // perform current query, but discard its results
185
- rc = sqlite3_step(*stmt);
186
- sqlite3_finalize(*stmt);
187
- switch (rc) {
240
+ ctx->rc = sqlite3_step(*ctx->stmt);
241
+ sqlite3_finalize(*ctx->stmt);
242
+ switch (ctx->rc) {
188
243
  case SQLITE_BUSY:
189
- rb_raise(cBusyError, "Database is busy");
190
244
  case SQLITE_ERROR:
191
- rb_raise(cSQLError, "%s", sqlite3_errmsg(db));
245
+ case SQLITE_MISUSE:
246
+ return NULL;
192
247
  }
193
- ptr = rest;
248
+ str = rest;
194
249
  }
250
+ return NULL;
195
251
  }
196
252
 
197
- inline int stmt_iterate(sqlite3_stmt *stmt, sqlite3 *db) {
253
+ /*
254
+ This function prepares a statement from an SQL string containing one or more SQL
255
+ statements. It will release the GVL while the statements are being prepared and
256
+ executed. All statements excluding the last one are executed. The last statement
257
+ is not executed, but instead handed back to the caller for looping over results.
258
+ */
259
+ inline void prepare_multi_stmt(sqlite3 *db, sqlite3_stmt **stmt, VALUE sql) {
260
+ struct multi_stmt_ctx ctx = {db, stmt, RSTRING_PTR(sql), RSTRING_LEN(sql), 0};
261
+ rb_thread_call_without_gvl(prepare_multi_stmt_without_gvl, (void *)&ctx, RUBY_UBF_IO, 0);
262
+ RB_GC_GUARD(sql);
263
+
264
+ switch (ctx.rc) {
265
+ case 0:
266
+ return;
267
+ case SQLITE_BUSY:
268
+ rb_raise(cBusyError, "Database is busy");
269
+ case SQLITE_ERROR:
270
+ rb_raise(cSQLError, "%s", sqlite3_errmsg(db));
271
+ default:
272
+ rb_raise(cError, "Invalid return code for prepare_multi_stmt_without_gvl: %d (please open an issue on https://github.com/digital-fabric/extralite)", ctx.rc);
273
+ }
274
+ }
275
+
276
+ struct step_ctx {
277
+ sqlite3_stmt *stmt;
198
278
  int rc;
199
- rc = sqlite3_step(stmt);
200
- switch (rc) {
279
+ };
280
+
281
+ void *stmt_iterate_without_gvl(void *ptr) {
282
+ struct step_ctx *ctx = (struct step_ctx *)ptr;
283
+ ctx->rc = sqlite3_step(ctx->stmt);
284
+ return NULL;
285
+ }
286
+
287
+ inline int stmt_iterate(sqlite3_stmt *stmt, sqlite3 *db) {
288
+ struct step_ctx ctx = {stmt, 0};
289
+ rb_thread_call_without_gvl(stmt_iterate_without_gvl, (void *)&ctx, RUBY_UBF_IO, 0);
290
+ switch (ctx.rc) {
201
291
  case SQLITE_ROW:
202
292
  return 1;
203
293
  case SQLITE_DONE:
@@ -207,7 +297,7 @@ inline int stmt_iterate(sqlite3_stmt *stmt, sqlite3 *db) {
207
297
  case SQLITE_ERROR:
208
298
  rb_raise(cSQLError, "%s", sqlite3_errmsg(db));
209
299
  default:
210
- rb_raise(cError, "Invalid return code for sqlite3_step: %d", rc);
300
+ rb_raise(cError, "Invalid return code for sqlite3_step: %d (please open an issue on https://github.com/digital-fabric/extralite)", ctx.rc);
211
301
  }
212
302
 
213
303
  return 0;
@@ -264,6 +354,29 @@ VALUE safe_query_hash(VALUE arg) {
264
354
  return result;
265
355
  }
266
356
 
357
+ /* call-seq:
358
+ * query(sql, *parameters, &block)
359
+ * query_hash(sql, *parameters, &block)
360
+ *
361
+ * Runs a query returning rows as hashes (with symbol keys). If a block is
362
+ * given, it will be called for each row. Otherwise, an array containing all
363
+ * rows is returned.
364
+ *
365
+ * Query parameters to be bound to placeholders in the query can be specified as
366
+ * a list of values or as a hash mapping parameter names to values. When
367
+ * parameters are given as a least, the query should specify parameters using
368
+ * `?`:
369
+ *
370
+ * db.query('select * from foo where x = ?', 42)
371
+ *
372
+ * Named placeholders are specified using `:`. The placeholder values are
373
+ * specified using a hash, where keys are either strings are symbols. String
374
+ * keys can include or omit the `:` prefix. The following are equivalent:
375
+ *
376
+ * db.query('select * from foo where x = :bar', bar: 42)
377
+ * db.query('select * from foo where x = :bar', 'bar' => 42)
378
+ * db.query('select * from foo where x = :bar', ':bar' => 42)
379
+ */
267
380
  VALUE Database_query_hash(int argc, VALUE *argv, VALUE self) {
268
381
  query_ctx ctx = { self, argc, argv, 0 };
269
382
  return rb_ensure(safe_query_hash, (VALUE)&ctx, cleanup_stmt, (VALUE)&ctx);
@@ -292,12 +405,32 @@ VALUE safe_query_ary(VALUE arg) {
292
405
  row = row_to_ary(ctx->stmt, column_count);
293
406
  if (yield_to_block) rb_yield(row); else rb_ary_push(result, row);
294
407
  }
295
-
408
+
296
409
  RB_GC_GUARD(row);
297
410
  RB_GC_GUARD(result);
298
411
  return result;
299
412
  }
300
413
 
414
+ /* call-seq: query_ary(sql, *parameters, &block)
415
+ *
416
+ * Runs a query returning rows as arrays. If a block is given, it will be called
417
+ * for each row. Otherwise, an array containing all rows is returned.
418
+ *
419
+ * Query parameters to be bound to placeholders in the query can be specified as
420
+ * a list of values or as a hash mapping parameter names to values. When
421
+ * parameters are given as a least, the query should specify parameters using
422
+ * `?`:
423
+ *
424
+ * db.query_ary('select * from foo where x = ?', 42)
425
+ *
426
+ * Named placeholders are specified using `:`. The placeholder values are
427
+ * specified using a hash, where keys are either strings are symbols. String
428
+ * keys can include or omit the `:` prefix. The following are equivalent:
429
+ *
430
+ * db.query_ary('select * from foo where x = :bar', bar: 42)
431
+ * db.query_ary('select * from foo where x = :bar', 'bar' => 42)
432
+ * db.query_ary('select * from foo where x = :bar', ':bar' => 42)
433
+ */
301
434
  VALUE Database_query_ary(int argc, VALUE *argv, VALUE self) {
302
435
  query_ctx ctx = { self, argc, argv, 0 };
303
436
  return rb_ensure(safe_query_ary, (VALUE)&ctx, cleanup_stmt, (VALUE)&ctx);
@@ -327,6 +460,25 @@ VALUE safe_query_single_row(VALUE arg) {
327
460
  return row;
328
461
  }
329
462
 
463
+ /* call-seq: query_single_row(sql, *parameters)
464
+ *
465
+ * Runs a query returning a single row as a hash.
466
+ *
467
+ * Query parameters to be bound to placeholders in the query can be specified as
468
+ * a list of values or as a hash mapping parameter names to values. When
469
+ * parameters are given as a least, the query should specify parameters using
470
+ * `?`:
471
+ *
472
+ * db.query_single_row('select * from foo where x = ?', 42)
473
+ *
474
+ * Named placeholders are specified using `:`. The placeholder values are
475
+ * specified using a hash, where keys are either strings are symbols. String
476
+ * keys can include or omit the `:` prefix. The following are equivalent:
477
+ *
478
+ * db.query_single_row('select * from foo where x = :bar', bar: 42)
479
+ * db.query_single_row('select * from foo where x = :bar', 'bar' => 42)
480
+ * db.query_single_row('select * from foo where x = :bar', ':bar' => 42)
481
+ */
330
482
  VALUE Database_query_single_row(int argc, VALUE *argv, VALUE self) {
331
483
  query_ctx ctx = { self, argc, argv, 0 };
332
484
  return rb_ensure(safe_query_single_row, (VALUE)&ctx, cleanup_stmt, (VALUE)&ctx);
@@ -364,6 +516,26 @@ VALUE safe_query_single_column(VALUE arg) {
364
516
  return result;
365
517
  }
366
518
 
519
+ /* call-seq: query_single_column(sql, *parameters, &block)
520
+ *
521
+ * Runs a query returning single column values. If a block is given, it will be called
522
+ * for each value. Otherwise, an array containing all values is returned.
523
+ *
524
+ * Query parameters to be bound to placeholders in the query can be specified as
525
+ * a list of values or as a hash mapping parameter names to values. When
526
+ * parameters are given as a least, the query should specify parameters using
527
+ * `?`:
528
+ *
529
+ * db.query_single_column('select x from foo where x = ?', 42)
530
+ *
531
+ * Named placeholders are specified using `:`. The placeholder values are
532
+ * specified using a hash, where keys are either strings are symbols. String
533
+ * keys can include or omit the `:` prefix. The following are equivalent:
534
+ *
535
+ * db.query_single_column('select x from foo where x = :bar', bar: 42)
536
+ * db.query_single_column('select x from foo where x = :bar', 'bar' => 42)
537
+ * db.query_single_column('select x from foo where x = :bar', ':bar' => 42)
538
+ */
367
539
  VALUE Database_query_single_column(int argc, VALUE *argv, VALUE self) {
368
540
  query_ctx ctx = { self, argc, argv, 0 };
369
541
  return rb_ensure(safe_query_single_column, (VALUE)&ctx, cleanup_stmt, (VALUE)&ctx);
@@ -392,11 +564,34 @@ VALUE safe_query_single_value(VALUE arg) {
392
564
  return value;
393
565
  }
394
566
 
567
+ /* call-seq: query_single_value(sql, *parameters)
568
+ *
569
+ * Runs a query returning a single value from the first row.
570
+ *
571
+ * Query parameters to be bound to placeholders in the query can be specified as
572
+ * a list of values or as a hash mapping parameter names to values. When
573
+ * parameters are given as a least, the query should specify parameters using
574
+ * `?`:
575
+ *
576
+ * db.query_single_value('select x from foo where x = ?', 42)
577
+ *
578
+ * Named placeholders are specified using `:`. The placeholder values are
579
+ * specified using a hash, where keys are either strings are symbols. String
580
+ * keys can include or omit the `:` prefix. The following are equivalent:
581
+ *
582
+ * db.query_single_value('select x from foo where x = :bar', bar: 42)
583
+ * db.query_single_value('select x from foo where x = :bar', 'bar' => 42)
584
+ * db.query_single_value('select x from foo where x = :bar', ':bar' => 42)
585
+ */
395
586
  VALUE Database_query_single_value(int argc, VALUE *argv, VALUE self) {
396
587
  query_ctx ctx = { self, argc, argv, 0 };
397
588
  return rb_ensure(safe_query_single_value, (VALUE)&ctx, cleanup_stmt, (VALUE)&ctx);
398
589
  }
399
590
 
591
+ /* call-seq: last_insert_rowid
592
+ *
593
+ * Returns the rowid of the last inserted row.
594
+ */
400
595
  VALUE Database_last_insert_rowid(VALUE self) {
401
596
  Database_t *db;
402
597
  GetOpenDatabase(self, db);
@@ -404,6 +599,10 @@ VALUE Database_last_insert_rowid(VALUE self) {
404
599
  return INT2NUM(sqlite3_last_insert_rowid(db->sqlite3_db));
405
600
  }
406
601
 
602
+ /* call-seq: changes
603
+ *
604
+ * Returns the number of changes made to the database by the last operation.
605
+ */
407
606
  VALUE Database_changes(VALUE self) {
408
607
  Database_t *db;
409
608
  GetOpenDatabase(self, db);
@@ -411,6 +610,10 @@ VALUE Database_changes(VALUE self) {
411
610
  return INT2NUM(sqlite3_changes(db->sqlite3_db));
412
611
  }
413
612
 
613
+ /* call-seq: filename
614
+ *
615
+ * Returns the database filename.
616
+ */
414
617
  VALUE Database_filename(int argc, VALUE *argv, VALUE self) {
415
618
  const char *db_name;
416
619
  const char *filename;
@@ -423,6 +626,10 @@ VALUE Database_filename(int argc, VALUE *argv, VALUE self) {
423
626
  return filename ? rb_str_new_cstr(filename) : Qnil;
424
627
  }
425
628
 
629
+ /* call-seq: transaction_active?
630
+ *
631
+ * Returns true if a transaction is currently in progress.
632
+ */
426
633
  VALUE Database_transaction_active_p(VALUE self) {
427
634
  Database_t *db;
428
635
  GetOpenDatabase(self, db);
@@ -430,6 +637,10 @@ VALUE Database_transaction_active_p(VALUE self) {
430
637
  return sqlite3_get_autocommit(db->sqlite3_db) ? Qfalse : Qtrue;
431
638
  }
432
639
 
640
+ /* call-seq: load_extension(path)
641
+ *
642
+ * Loads an extension with the given path.
643
+ */
433
644
  VALUE Database_load_extension(VALUE self, VALUE path) {
434
645
  Database_t *db;
435
646
  GetOpenDatabase(self, db);
@@ -453,14 +664,14 @@ void Init_Extralite() {
453
664
  rb_define_method(cDatabase, "initialize", Database_initialize, 1);
454
665
  rb_define_method(cDatabase, "close", Database_close, 0);
455
666
  rb_define_method(cDatabase, "closed?", Database_closed_p, 0);
456
-
667
+
457
668
  rb_define_method(cDatabase, "query", Database_query_hash, -1);
458
669
  rb_define_method(cDatabase, "query_hash", Database_query_hash, -1);
459
670
  rb_define_method(cDatabase, "query_ary", Database_query_ary, -1);
460
671
  rb_define_method(cDatabase, "query_single_row", Database_query_single_row, -1);
461
672
  rb_define_method(cDatabase, "query_single_column", Database_query_single_column, -1);
462
673
  rb_define_method(cDatabase, "query_single_value", Database_query_single_value, -1);
463
-
674
+
464
675
  rb_define_method(cDatabase, "last_insert_rowid", Database_last_insert_rowid, 0);
465
676
  rb_define_method(cDatabase, "changes", Database_changes, 0);
466
677
  rb_define_method(cDatabase, "filename", Database_filename, -1);
@@ -474,5 +685,7 @@ void Init_Extralite() {
474
685
  rb_gc_register_mark_object(cSQLError);
475
686
  rb_gc_register_mark_object(cBusyError);
476
687
 
477
- ID_STRIP = rb_intern("strip");
688
+ ID_KEYS = rb_intern("keys");
689
+ ID_STRIP = rb_intern("strip");
690
+ ID_TO_S = rb_intern("to_s");
478
691
  }
data/extralite.gemspec CHANGED
@@ -11,7 +11,6 @@ Gem::Specification.new do |s|
11
11
  s.homepage = 'https://github.com/digital-fabric/extralite'
12
12
  s.metadata = {
13
13
  "source_code_uri" => "https://github.com/digital-fabric/extralite",
14
- "documentation_uri" => "https://github.com/digital-fabric/extralite",
15
14
  "homepage_uri" => "https://github.com/digital-fabric/extralite",
16
15
  "changelog_uri" => "https://github.com/digital-fabric/extralite/blob/master/CHANGELOG.md"
17
16
  }
@@ -21,9 +20,10 @@ Gem::Specification.new do |s|
21
20
  s.require_paths = ["lib"]
22
21
  s.required_ruby_version = '>= 2.6'
23
22
 
24
- s.add_development_dependency 'rake-compiler', '1.1.1'
25
- s.add_development_dependency 'minitest', '5.14.4'
23
+ s.add_development_dependency 'rake-compiler', '1.1.6'
24
+ s.add_development_dependency 'minitest', '5.15.0'
26
25
  s.add_development_dependency 'simplecov', '0.17.1'
27
- s.add_development_dependency 'rubocop', '0.85.1'
28
- s.add_development_dependency 'pry', '0.13.1'
26
+ s.add_development_dependency 'yard', '0.9.27'
27
+
28
+ s.add_development_dependency 'sequel', '5.51.0'
29
29
  end
@@ -1,3 +1,3 @@
1
1
  module Extralite
2
- VERSION = '1.4'
2
+ VERSION = '1.8.1'
3
3
  end
data/lib/extralite.rb CHANGED
@@ -1 +1,21 @@
1
1
  require_relative './extralite_ext'
2
+
3
+ # Extralite is a Ruby gem for working with SQLite databases
4
+ module Extralite
5
+ # A base class for Extralite exceptions
6
+ class Error < RuntimeError
7
+ end
8
+
9
+ # An exception representing an SQL error emitted by SQLite
10
+ class SQLError < Error
11
+ end
12
+
13
+ # An exception raised when an SQLite database is busy (locked by another
14
+ # thread or process)
15
+ class BusyError < Error
16
+ end
17
+
18
+ # An SQLite database
19
+ class Database
20
+ end
21
+ end