extralite 1.4 → 1.8.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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