extralite 2.3 → 2.5
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.
- checksums.yaml +4 -4
- data/.editorconfig +6 -0
- data/.github/workflows/test-bundle.yml +30 -0
- data/.github/workflows/test.yml +7 -3
- data/.gitignore +3 -0
- data/CHANGELOG.md +44 -3
- data/Gemfile-bundle +5 -0
- data/Gemfile.lock +3 -3
- data/README.md +106 -13
- data/TODO.md +0 -3
- data/bin/update_sqlite_source +10 -3
- data/ext/extralite/common.c +288 -37
- data/ext/extralite/database.c +256 -39
- data/ext/extralite/extralite.h +20 -9
- data/ext/extralite/extralite_ext.c +4 -0
- data/ext/extralite/iterator.c +5 -5
- data/ext/extralite/query.c +250 -41
- data/gemspec.rb +1 -1
- data/lib/extralite/version.rb +1 -1
- data/lib/extralite.rb +41 -6
- data/test/fixtures/image.png +0 -0
- data/test/helper.rb +3 -0
- data/test/issue-38.rb +80 -0
- data/test/issue-54.rb +21 -0
- data/test/issue-59.rb +70 -0
- data/test/perf_ary.rb +6 -3
- data/test/perf_hash.rb +7 -4
- data/test/test_database.rb +726 -15
- data/test/test_iterator.rb +2 -1
- data/test/test_query.rb +402 -6
- metadata +10 -3
data/ext/extralite/query.c
CHANGED
@@ -20,8 +20,14 @@ static size_t Query_size(const void *ptr) {
|
|
20
20
|
|
21
21
|
static void Query_mark(void *ptr) {
|
22
22
|
Query_t *query = ptr;
|
23
|
-
|
24
|
-
|
23
|
+
rb_gc_mark_movable(query->db);
|
24
|
+
rb_gc_mark_movable(query->sql);
|
25
|
+
}
|
26
|
+
|
27
|
+
static void Query_compact(void *ptr) {
|
28
|
+
Query_t *query = ptr;
|
29
|
+
query->db = rb_gc_location(query->db);
|
30
|
+
query->sql = rb_gc_location(query->sql);
|
25
31
|
}
|
26
32
|
|
27
33
|
static void Query_free(void *ptr) {
|
@@ -32,8 +38,8 @@ static void Query_free(void *ptr) {
|
|
32
38
|
|
33
39
|
static const rb_data_type_t Query_type = {
|
34
40
|
"Query",
|
35
|
-
{Query_mark, Query_free, Query_size,},
|
36
|
-
0, 0, RUBY_TYPED_FREE_IMMEDIATELY
|
41
|
+
{Query_mark, Query_free, Query_size, Query_compact},
|
42
|
+
0, 0, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED
|
37
43
|
};
|
38
44
|
|
39
45
|
static VALUE Query_allocate(VALUE klass) {
|
@@ -67,10 +73,12 @@ VALUE Query_initialize(VALUE self, VALUE db, VALUE sql) {
|
|
67
73
|
if (!RSTRING_LEN(sql))
|
68
74
|
rb_raise(cError, "Cannot prepare an empty SQL query");
|
69
75
|
|
76
|
+
RB_OBJ_WRITE(self, &query->db, db);
|
77
|
+
RB_OBJ_WRITE(self, &query->sql, sql);
|
78
|
+
|
70
79
|
query->db = db;
|
71
80
|
query->db_struct = self_to_database(db);
|
72
81
|
query->sqlite3_db = Database_sqlite3_db(db);
|
73
|
-
query->sql = sql;
|
74
82
|
query->stmt = NULL;
|
75
83
|
query->closed = 0;
|
76
84
|
query->eof = 0;
|
@@ -134,14 +142,14 @@ VALUE Query_reset(VALUE self) {
|
|
134
142
|
* Bound parameters can be specified as a list of values or as a hash mapping
|
135
143
|
* parameter names to values. When parameters are given as a splatted array, the
|
136
144
|
* query should specify parameters using `?`:
|
137
|
-
*
|
145
|
+
*
|
138
146
|
* query = db.prepare('select * from foo where x = ?')
|
139
147
|
* query.bind(42)
|
140
148
|
*
|
141
149
|
* Named placeholders are specified using `:`. The placeholder values are
|
142
150
|
* specified using a hash, where keys are either strings are symbols. String
|
143
151
|
* keys can include or omit the `:` prefix. The following are equivalent:
|
144
|
-
*
|
152
|
+
*
|
145
153
|
* query = db.prepare('select * from foo where x = :bar')
|
146
154
|
* query.bind(bar: 42)
|
147
155
|
*
|
@@ -171,19 +179,18 @@ VALUE Query_eof_p(VALUE self) {
|
|
171
179
|
static inline VALUE Query_perform_next(VALUE self, int max_rows, VALUE (*call)(query_ctx *)) {
|
172
180
|
Query_t *query = self_to_query(self);
|
173
181
|
if (query->closed) rb_raise(cError, "Query is closed");
|
174
|
-
|
182
|
+
|
175
183
|
if (!query->stmt) query_reset(query);
|
176
184
|
if (query->eof) return rb_block_given_p() ? self : Qnil;
|
177
185
|
|
178
|
-
query_ctx ctx =
|
186
|
+
query_ctx ctx = QUERY_CTX(
|
179
187
|
self,
|
180
|
-
query->
|
188
|
+
query->db_struct,
|
181
189
|
query->stmt,
|
182
190
|
Qnil,
|
183
191
|
QUERY_MODE(max_rows == SINGLE_ROW ? QUERY_SINGLE_ROW : QUERY_MULTI_ROW),
|
184
|
-
MAX_ROWS(max_rows)
|
185
|
-
|
186
|
-
};
|
192
|
+
MAX_ROWS(max_rows)
|
193
|
+
);
|
187
194
|
VALUE result = call(&ctx);
|
188
195
|
query->eof = ctx.eof;
|
189
196
|
return (ctx.mode == QUERY_YIELD) ? self : result;
|
@@ -193,12 +200,12 @@ static inline VALUE Query_perform_next(VALUE self, int max_rows, VALUE (*call)(q
|
|
193
200
|
|
194
201
|
/* Returns the next 1 or more rows from the associated query's result set as a
|
195
202
|
* hash.
|
196
|
-
*
|
203
|
+
*
|
197
204
|
* If no row count is given, a single row is returned. If a row count is given,
|
198
205
|
* an array containing up to the `row_count` rows is returned. If `row_count` is
|
199
206
|
* -1, all rows are returned. If the end of the result set has been reached,
|
200
207
|
* `nil` is returned.
|
201
|
-
*
|
208
|
+
*
|
202
209
|
* If a block is given, rows are passed to the block and self is returned.
|
203
210
|
*
|
204
211
|
* @overload next()
|
@@ -219,12 +226,12 @@ VALUE Query_next_hash(int argc, VALUE *argv, VALUE self) {
|
|
219
226
|
|
220
227
|
/* Returns the next 1 or more rows from the associated query's result set as an
|
221
228
|
* array.
|
222
|
-
*
|
229
|
+
*
|
223
230
|
* If no row count is given, a single row is returned. If a row count is given,
|
224
231
|
* an array containing up to the `row_count` rows is returned. If `row_count` is
|
225
232
|
* -1, all rows are returned. If the end of the result set has been reached,
|
226
233
|
* `nil` is returned.
|
227
|
-
*
|
234
|
+
*
|
228
235
|
* If a block is given, rows are passed to the block and self is returned.
|
229
236
|
*
|
230
237
|
* @overload next_ary()
|
@@ -241,12 +248,12 @@ VALUE Query_next_ary(int argc, VALUE *argv, VALUE self) {
|
|
241
248
|
/* Returns the next 1 or more rows from the associated query's result set as an
|
242
249
|
* single values. If the result set contains more than one column an error is
|
243
250
|
* raised.
|
244
|
-
*
|
251
|
+
*
|
245
252
|
* If no row count is given, a single row is returned. If a row count is given,
|
246
253
|
* an array containing up to the `row_count` rows is returned. If `row_count` is
|
247
254
|
* -1, all rows are returned. If the end of the result set has been reached,
|
248
255
|
* `nil` is returned.
|
249
|
-
*
|
256
|
+
*
|
250
257
|
* If a block is given, rows are passed to the block and self is returned.
|
251
258
|
*
|
252
259
|
* @overload next_ary()
|
@@ -261,7 +268,7 @@ VALUE Query_next_single_column(int argc, VALUE *argv, VALUE self) {
|
|
261
268
|
}
|
262
269
|
|
263
270
|
/* Returns all rows in the associated query's result set as hashes.
|
264
|
-
*
|
271
|
+
*
|
265
272
|
* @overload to_a()
|
266
273
|
* @return [Array<Hash>] all rows
|
267
274
|
* @overload to_a_hash
|
@@ -274,7 +281,7 @@ VALUE Query_to_a_hash(VALUE self) {
|
|
274
281
|
}
|
275
282
|
|
276
283
|
/* Returns all rows in the associated query's result set as arrays.
|
277
|
-
*
|
284
|
+
*
|
278
285
|
* @return [Array<Array>] all rows
|
279
286
|
*/
|
280
287
|
VALUE Query_to_a_ary(VALUE self) {
|
@@ -285,7 +292,7 @@ VALUE Query_to_a_ary(VALUE self) {
|
|
285
292
|
|
286
293
|
/* Returns all rows in the associated query's result set as single values. If
|
287
294
|
* the result set contains more than one column an error is raised.
|
288
|
-
*
|
295
|
+
*
|
289
296
|
* @return [Array<Object>] all rows
|
290
297
|
*/
|
291
298
|
VALUE Query_to_a_single_column(VALUE self) {
|
@@ -297,7 +304,7 @@ VALUE Query_to_a_single_column(VALUE self) {
|
|
297
304
|
/* Iterates through the result set, passing each row to the given block as a
|
298
305
|
* hash. If no block is given, returns a `Extralite::Iterator` instance in hash
|
299
306
|
* mode.
|
300
|
-
*
|
307
|
+
*
|
301
308
|
* @return [Extralite::Query, Extralite::Iterator] self or an iterator if no block is given
|
302
309
|
*/
|
303
310
|
VALUE Query_each_hash(VALUE self) {
|
@@ -311,7 +318,7 @@ VALUE Query_each_hash(VALUE self) {
|
|
311
318
|
/* Iterates through the result set, passing each row to the given block as an
|
312
319
|
* array. If no block is given, returns a `Extralite::Iterator` instance in
|
313
320
|
* array mode.
|
314
|
-
*
|
321
|
+
*
|
315
322
|
* @return [Extralite::Query, Extralite::Iterator] self or an iterator if no block is given
|
316
323
|
*/
|
317
324
|
VALUE Query_each_ary(VALUE self) {
|
@@ -326,7 +333,7 @@ VALUE Query_each_ary(VALUE self) {
|
|
326
333
|
* single value. If the result set contains more than one column an error is
|
327
334
|
* raised. If no block is given, returns a `Extralite::Iterator` instance in
|
328
335
|
* single column mode.
|
329
|
-
*
|
336
|
+
*
|
330
337
|
* @return [Extralite::Query, Extralite::Iterator] self or an iterator if no block is given
|
331
338
|
*/
|
332
339
|
VALUE Query_each_single_column(VALUE self) {
|
@@ -366,30 +373,213 @@ VALUE Query_execute(int argc, VALUE *argv, VALUE self) {
|
|
366
373
|
return Query_perform_next(self, ALL_ROWS, safe_query_changes);
|
367
374
|
}
|
368
375
|
|
369
|
-
/*
|
370
|
-
*
|
371
|
-
*
|
372
|
-
*
|
376
|
+
/* call-seq:
|
377
|
+
* query << [...] -> query
|
378
|
+
* query << { ... } -> query
|
379
|
+
*
|
380
|
+
* Runs the with the given parameters, returning the total changes effected.
|
381
|
+
* This method should be used for data- or schema-manipulation queries.
|
382
|
+
*
|
383
|
+
* Query parameters to be bound to placeholders in the query can be specified as
|
384
|
+
* a list of values or as a hash mapping parameter names to values. When
|
385
|
+
* parameters are given as an array, the query should specify parameters using
|
386
|
+
* `?`:
|
387
|
+
*
|
388
|
+
* query = db.prepare('update foo set x = ? where y = ?')
|
389
|
+
* query << [42, 43]
|
390
|
+
*
|
391
|
+
* Named placeholders are specified using `:`. The placeholder values are
|
392
|
+
* specified using a hash, where keys are either strings are symbols. String
|
393
|
+
* keys can include or omit the `:` prefix. The following are equivalent:
|
394
|
+
*
|
395
|
+
* query = db.prepare('update foo set x = :bar')
|
396
|
+
* query << { bar: 42 }
|
397
|
+
* query << { 'bar' => 42 }
|
398
|
+
* query << { ':bar' => 42 }
|
399
|
+
*/
|
400
|
+
VALUE Query_execute_chevrons(VALUE self, VALUE params) {
|
401
|
+
Query_execute(1, ¶ms, self);
|
402
|
+
return self;
|
403
|
+
}
|
404
|
+
|
405
|
+
/* call-seq:
|
406
|
+
* query.batch_execute(params_array) -> changes
|
407
|
+
* query.batch_execute(enumerable) -> changes
|
408
|
+
* query.batch_execute(callable) -> changes
|
409
|
+
*
|
410
|
+
* Executes the query for each set of parameters in the paramter source. If an
|
411
|
+
* enumerable is given, it is iterated and each of its values is used as the
|
412
|
+
* parameters for running the query. If a callable is given, it is called
|
413
|
+
* repeatedly and each of its return values is used as the parameters, until nil
|
414
|
+
* is returned.
|
415
|
+
*
|
416
|
+
* Returns the number of changes effected. This method is designed for inserting
|
417
|
+
* multiple records.
|
373
418
|
*
|
374
419
|
* query = db.prepare('insert into foo values (?, ?, ?)')
|
375
420
|
* records = [
|
376
421
|
* [1, 2, 3],
|
377
422
|
* [4, 5, 6]
|
378
423
|
* ]
|
379
|
-
* query.
|
424
|
+
* query.batch_execute(records)
|
425
|
+
*
|
426
|
+
* source = [
|
427
|
+
* [1, 2, 3],
|
428
|
+
* [4, 5, 6]
|
429
|
+
* ]
|
430
|
+
* query.batch_execute { records.shift }
|
380
431
|
*
|
381
|
-
* @param parameters [Array<Array, Hash
|
432
|
+
* @param parameters [Array<Array, Hash>, Enumerable, Enumerator, Callable] array of parameters to run query with
|
382
433
|
* @return [Integer] number of changes effected
|
383
434
|
*/
|
384
|
-
VALUE
|
435
|
+
VALUE Query_batch_execute(VALUE self, VALUE parameters) {
|
385
436
|
Query_t *query = self_to_query(self);
|
386
437
|
if (query->closed) rb_raise(cError, "Query is closed");
|
387
438
|
|
388
439
|
if (!query->stmt)
|
389
440
|
prepare_single_stmt(query->sqlite3_db, &query->stmt, query->sql);
|
390
441
|
|
391
|
-
query_ctx ctx =
|
392
|
-
|
442
|
+
query_ctx ctx = QUERY_CTX(
|
443
|
+
self,
|
444
|
+
query->db_struct,
|
445
|
+
query->stmt,
|
446
|
+
parameters,
|
447
|
+
QUERY_MODE(QUERY_MULTI_ROW),
|
448
|
+
ALL_ROWS
|
449
|
+
);
|
450
|
+
return safe_batch_execute(&ctx);
|
451
|
+
}
|
452
|
+
|
453
|
+
/* call-seq:
|
454
|
+
* query.batch_query(sql, params_array) -> rows
|
455
|
+
* query.batch_query(sql, enumerable) -> rows
|
456
|
+
* query.batch_query(sql, callable) -> rows
|
457
|
+
* query.batch_query(sql, params_array) { |rows| ... } -> changes
|
458
|
+
* query.batch_query(sql, enumerable) { |rows| ... } -> changes
|
459
|
+
* query.batch_query(sql, callable) { |rows| ... } -> changes
|
460
|
+
*
|
461
|
+
* Executes the prepared query for each list of parameters in the given paramter
|
462
|
+
* source. If a block is given, it is called with the resulting rows for each
|
463
|
+
* invocation of the query, and the total number of changes is returned.
|
464
|
+
* Otherwise, an array containing the resulting rows for each invocation is
|
465
|
+
* returned.
|
466
|
+
*
|
467
|
+
* q = db.prepare('insert into foo values (?, ?) returning bar, baz')
|
468
|
+
* records = [
|
469
|
+
* [1, 2],
|
470
|
+
* [3, 4]
|
471
|
+
* ]
|
472
|
+
* q.batch_query(records)
|
473
|
+
* #=> [{ bar: 1, baz: 2 }, { bar: 3, baz: 4}]
|
474
|
+
* *
|
475
|
+
* @param sql [String] query SQL
|
476
|
+
* @param parameters [Array<Array, Hash>, Enumerable, Enumerator, Callable] parameters to run query with
|
477
|
+
* @return [Array<Hash>, Integer] Total number of changes effected
|
478
|
+
*/
|
479
|
+
VALUE Query_batch_query(VALUE self, VALUE parameters) {
|
480
|
+
Query_t *query = self_to_query(self);
|
481
|
+
if (query->closed) rb_raise(cError, "Query is closed");
|
482
|
+
|
483
|
+
if (!query->stmt)
|
484
|
+
prepare_single_stmt(query->sqlite3_db, &query->stmt, query->sql);
|
485
|
+
|
486
|
+
query_ctx ctx = QUERY_CTX(
|
487
|
+
self,
|
488
|
+
query->db_struct,
|
489
|
+
query->stmt,
|
490
|
+
parameters,
|
491
|
+
QUERY_MODE(QUERY_MULTI_ROW),
|
492
|
+
ALL_ROWS
|
493
|
+
);
|
494
|
+
return safe_batch_query(&ctx);
|
495
|
+
}
|
496
|
+
|
497
|
+
/* call-seq:
|
498
|
+
* query.batch_query_ary(sql, params_array) -> rows
|
499
|
+
* query.batch_query_ary(sql, enumerable) -> rows
|
500
|
+
* query.batch_query_ary(sql, callable) -> rows
|
501
|
+
* query.batch_query_ary(sql, params_array) { |rows| ... } -> changes
|
502
|
+
* query.batch_query_ary(sql, enumerable) { |rows| ... } -> changes
|
503
|
+
* query.batch_query_ary(sql, callable) { |rows| ... } -> changes
|
504
|
+
*
|
505
|
+
* Executes the prepared query for each list of parameters in the given paramter
|
506
|
+
* source. If a block is given, it is called with the resulting rows for each
|
507
|
+
* invocation of the query, and the total number of changes is returned.
|
508
|
+
* Otherwise, an array containing the resulting rows for each invocation is
|
509
|
+
* returned. Rows are represented as arrays.
|
510
|
+
*
|
511
|
+
* q = db.prepare('insert into foo values (?, ?) returning bar, baz')
|
512
|
+
* records = [
|
513
|
+
* [1, 2],
|
514
|
+
* [3, 4]
|
515
|
+
* ]
|
516
|
+
* q.batch_query_ary(records)
|
517
|
+
* #=> [{ bar: 1, baz: 2 }, { bar: 3, baz: 4}]
|
518
|
+
* *
|
519
|
+
* @param sql [String] query SQL
|
520
|
+
* @param parameters [Array<Array, Hash>, Enumerable, Enumerator, Callable] parameters to run query with
|
521
|
+
* @return [Array<Hash>, Integer] Total number of changes effected
|
522
|
+
*/
|
523
|
+
VALUE Query_batch_query_ary(VALUE self, VALUE parameters) {
|
524
|
+
Query_t *query = self_to_query(self);
|
525
|
+
if (query->closed) rb_raise(cError, "Query is closed");
|
526
|
+
|
527
|
+
if (!query->stmt)
|
528
|
+
prepare_single_stmt(query->sqlite3_db, &query->stmt, query->sql);
|
529
|
+
|
530
|
+
query_ctx ctx = QUERY_CTX(
|
531
|
+
self,
|
532
|
+
query->db_struct,
|
533
|
+
query->stmt,
|
534
|
+
parameters,
|
535
|
+
QUERY_MODE(QUERY_MULTI_ROW),
|
536
|
+
ALL_ROWS
|
537
|
+
);
|
538
|
+
return safe_batch_query_ary(&ctx);
|
539
|
+
}
|
540
|
+
|
541
|
+
/* call-seq:
|
542
|
+
* query.batch_query_single_column(sql, params_array) -> rows
|
543
|
+
* query.batch_query_single_column(sql, enumerable) -> rows
|
544
|
+
* query.batch_query_single_column(sql, callable) -> rows
|
545
|
+
* query.batch_query_single_column(sql, params_array) { |rows| ... } -> changes
|
546
|
+
* query.batch_query_single_column(sql, enumerable) { |rows| ... } -> changes
|
547
|
+
* query.batch_query_single_column(sql, callable) { |rows| ... } -> changes
|
548
|
+
*
|
549
|
+
* Executes the prepared query for each list of parameters in the given paramter
|
550
|
+
* source. If a block is given, it is called with the resulting rows for each
|
551
|
+
* invocation of the query, and the total number of changes is returned.
|
552
|
+
* Otherwise, an array containing the resulting rows for each invocation is
|
553
|
+
* returned. Rows are represented as single values.
|
554
|
+
*
|
555
|
+
* q = db.prepare('insert into foo values (?, ?) returning bar, baz')
|
556
|
+
* records = [
|
557
|
+
* [1, 2],
|
558
|
+
* [3, 4]
|
559
|
+
* ]
|
560
|
+
* q.batch_query_single_column(records)
|
561
|
+
* #=> [{ bar: 1, baz: 2 }, { bar: 3, baz: 4}]
|
562
|
+
* *
|
563
|
+
* @param sql [String] query SQL
|
564
|
+
* @param parameters [Array<Array, Hash>, Enumerable, Enumerator, Callable] parameters to run query with
|
565
|
+
* @return [Array<Hash>, Integer] Total number of changes effected
|
566
|
+
*/
|
567
|
+
VALUE Query_batch_query_single_column(VALUE self, VALUE parameters) {
|
568
|
+
Query_t *query = self_to_query(self);
|
569
|
+
if (query->closed) rb_raise(cError, "Query is closed");
|
570
|
+
|
571
|
+
if (!query->stmt)
|
572
|
+
prepare_single_stmt(query->sqlite3_db, &query->stmt, query->sql);
|
573
|
+
|
574
|
+
query_ctx ctx = QUERY_CTX(
|
575
|
+
self,
|
576
|
+
query->db_struct,
|
577
|
+
query->stmt,
|
578
|
+
parameters,
|
579
|
+
QUERY_MODE(QUERY_MULTI_ROW),
|
580
|
+
ALL_ROWS
|
581
|
+
);
|
582
|
+
return safe_batch_query_single_column(&ctx);
|
393
583
|
}
|
394
584
|
|
395
585
|
/* Returns the database associated with the query.
|
@@ -405,7 +595,7 @@ VALUE Query_database(VALUE self) {
|
|
405
595
|
}
|
406
596
|
|
407
597
|
/* Returns the SQL string for the query.
|
408
|
-
*
|
598
|
+
*
|
409
599
|
* @return [String] SQL string
|
410
600
|
*/
|
411
601
|
VALUE Query_sql(VALUE self) {
|
@@ -414,7 +604,7 @@ VALUE Query_sql(VALUE self) {
|
|
414
604
|
}
|
415
605
|
|
416
606
|
/* Returns the column names for the query without running it.
|
417
|
-
*
|
607
|
+
*
|
418
608
|
* @return [Array<Symbol>] column names
|
419
609
|
*/
|
420
610
|
VALUE Query_columns(VALUE self) {
|
@@ -423,8 +613,21 @@ VALUE Query_columns(VALUE self) {
|
|
423
613
|
return Query_perform_next(self, ALL_ROWS, safe_query_columns);
|
424
614
|
}
|
425
615
|
|
616
|
+
/* call-seq:
|
617
|
+
* query.clone -> copy
|
618
|
+
* query.dup -> copy
|
619
|
+
*
|
620
|
+
* Returns a new query instance for the same SQL as the original query.
|
621
|
+
*
|
622
|
+
* @return [Extralite::Query] copy of query
|
623
|
+
*/
|
624
|
+
VALUE Query_clone(VALUE self) {
|
625
|
+
Query_t *query = self_to_query(self);
|
626
|
+
return rb_funcall(cQuery, ID_new, 2, query->db, query->sql);
|
627
|
+
}
|
628
|
+
|
426
629
|
/* Closes the query. Attempting to run a closed query will raise an error.
|
427
|
-
*
|
630
|
+
*
|
428
631
|
* @return [Extralite::Query] self
|
429
632
|
*/
|
430
633
|
VALUE Query_close(VALUE self) {
|
@@ -438,7 +641,7 @@ VALUE Query_close(VALUE self) {
|
|
438
641
|
}
|
439
642
|
|
440
643
|
/* Returns true if the query is closed.
|
441
|
-
*
|
644
|
+
*
|
442
645
|
* @return [boolean] true if query is closed
|
443
646
|
*/
|
444
647
|
VALUE Query_closed_p(VALUE self) {
|
@@ -449,7 +652,7 @@ VALUE Query_closed_p(VALUE self) {
|
|
449
652
|
/* Returns the current [status
|
450
653
|
* value](https://sqlite.org/c3ref/c_stmtstatus_counter.html) for the given op.
|
451
654
|
* To reset the value, pass true as reset.
|
452
|
-
*
|
655
|
+
*
|
453
656
|
* @overload status(op)
|
454
657
|
* @param op [Integer] status op
|
455
658
|
* @return [Integer] current status value for the given op
|
@@ -486,7 +689,7 @@ VALUE Query_inspect(VALUE self) {
|
|
486
689
|
rb_str_cat2(sql, "...");
|
487
690
|
}
|
488
691
|
sql = rb_funcall(sql, ID_inspect, 0);
|
489
|
-
|
692
|
+
|
490
693
|
RB_GC_GUARD(sql);
|
491
694
|
return rb_sprintf("#<%"PRIsVALUE":%p %"PRIsVALUE">", cname, (void*)self, sql);
|
492
695
|
}
|
@@ -501,8 +704,10 @@ void Init_ExtraliteQuery(void) {
|
|
501
704
|
rb_define_method(cQuery, "close", Query_close, 0);
|
502
705
|
rb_define_method(cQuery, "closed?", Query_closed_p, 0);
|
503
706
|
rb_define_method(cQuery, "columns", Query_columns, 0);
|
707
|
+
rb_define_method(cQuery, "clone", Query_clone, 0);
|
504
708
|
rb_define_method(cQuery, "database", Query_database, 0);
|
505
709
|
rb_define_method(cQuery, "db", Query_database, 0);
|
710
|
+
rb_define_method(cQuery, "dup", Query_clone, 0);
|
506
711
|
|
507
712
|
rb_define_method(cQuery, "each", Query_each_hash, 0);
|
508
713
|
rb_define_method(cQuery, "each_ary", Query_each_ary, 0);
|
@@ -511,7 +716,11 @@ void Init_ExtraliteQuery(void) {
|
|
511
716
|
|
512
717
|
rb_define_method(cQuery, "eof?", Query_eof_p, 0);
|
513
718
|
rb_define_method(cQuery, "execute", Query_execute, -1);
|
514
|
-
rb_define_method(cQuery, "
|
719
|
+
rb_define_method(cQuery, "<<", Query_execute_chevrons, 1);
|
720
|
+
rb_define_method(cQuery, "batch_execute", Query_batch_execute, 1);
|
721
|
+
rb_define_method(cQuery, "batch_query", Query_batch_query, 1);
|
722
|
+
rb_define_method(cQuery, "batch_query_ary", Query_batch_query_ary, 1);
|
723
|
+
rb_define_method(cQuery, "batch_query_single_column", Query_batch_query_single_column, 1);
|
515
724
|
rb_define_method(cQuery, "initialize", Query_initialize, 2);
|
516
725
|
rb_define_method(cQuery, "inspect", Query_inspect, 0);
|
517
726
|
|
data/gemspec.rb
CHANGED
@@ -16,7 +16,7 @@ def common_spec(s)
|
|
16
16
|
s.rdoc_options = ["--title", "extralite", "--main", "README.md"]
|
17
17
|
s.extra_rdoc_files = ["README.md"]
|
18
18
|
s.require_paths = ["lib"]
|
19
|
-
s.required_ruby_version = '>=
|
19
|
+
s.required_ruby_version = '>= 3.0'
|
20
20
|
|
21
21
|
s.add_development_dependency 'rake-compiler', '1.1.6'
|
22
22
|
s.add_development_dependency 'minitest', '5.15.0'
|
data/lib/extralite/version.rb
CHANGED
data/lib/extralite.rb
CHANGED
@@ -25,20 +25,28 @@ module Extralite
|
|
25
25
|
class InterruptError < Error
|
26
26
|
end
|
27
27
|
|
28
|
+
# An exception raised when an Extralite doesn't know how to bind a parameter to a query
|
29
|
+
class ParameterError < Error
|
30
|
+
end
|
31
|
+
|
28
32
|
# An SQLite database
|
29
33
|
class Database
|
30
34
|
# @!visibility private
|
31
35
|
TABLES_SQL = <<~SQL
|
32
|
-
SELECT name FROM sqlite_master
|
36
|
+
SELECT name FROM %<db>s.sqlite_master
|
33
37
|
WHERE type ='table'
|
34
|
-
AND name NOT LIKE 'sqlite_
|
38
|
+
AND name NOT LIKE 'sqlite_%%';
|
35
39
|
SQL
|
36
40
|
|
37
|
-
|
41
|
+
alias_method :execute_multi, :batch_execute
|
42
|
+
|
43
|
+
# Returns the list of currently defined tables. If a database name is given,
|
44
|
+
# returns the list of tables for the relevant attached database.
|
38
45
|
#
|
46
|
+
# @param db [String] name of attached database
|
39
47
|
# @return [Array] list of tables
|
40
|
-
def tables
|
41
|
-
query_single_column(TABLES_SQL)
|
48
|
+
def tables(db = 'main')
|
49
|
+
query_single_column(format(TABLES_SQL, db: db))
|
42
50
|
end
|
43
51
|
|
44
52
|
# Gets or sets one or more pragmas:
|
@@ -52,6 +60,29 @@ module Extralite
|
|
52
60
|
value.is_a?(Hash) ? pragma_set(value) : pragma_get(value)
|
53
61
|
end
|
54
62
|
|
63
|
+
# Starts a transaction and runs the given block. If an exception is raised
|
64
|
+
# in the block, the transaction is rolled back. Otherwise, the transaction
|
65
|
+
# is commited after running the block.
|
66
|
+
#
|
67
|
+
# db.transaction do
|
68
|
+
# db.execute('insert into foo values (1, 2, 3)')
|
69
|
+
# raise if db.query_single_value('select x from bar') > 42
|
70
|
+
# end
|
71
|
+
#
|
72
|
+
# @param mode [Symbol, String] transaction mode (deferred, immediate or exclusive). Defaults to immediate.
|
73
|
+
# @return [Any] the given block's return value
|
74
|
+
def transaction(mode = :immediate)
|
75
|
+
execute "begin #{mode} transaction"
|
76
|
+
|
77
|
+
abort = false
|
78
|
+
yield self
|
79
|
+
rescue
|
80
|
+
abort = true
|
81
|
+
raise
|
82
|
+
ensure
|
83
|
+
execute(abort ? 'rollback' : 'commit')
|
84
|
+
end
|
85
|
+
|
55
86
|
private
|
56
87
|
|
57
88
|
def pragma_set(values)
|
@@ -60,7 +91,11 @@ module Extralite
|
|
60
91
|
end
|
61
92
|
|
62
93
|
def pragma_get(key)
|
63
|
-
|
94
|
+
query_single_value("pragma #{key}")
|
64
95
|
end
|
65
96
|
end
|
97
|
+
|
98
|
+
class Query
|
99
|
+
alias_method :execute_multi, :batch_execute
|
100
|
+
end
|
66
101
|
end
|
Binary file
|
data/test/helper.rb
CHANGED
data/test/issue-38.rb
ADDED
@@ -0,0 +1,80 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "sqlite3"
|
4
|
+
require "./lib/extralite"
|
5
|
+
require "benchmark"
|
6
|
+
|
7
|
+
# Setup
|
8
|
+
|
9
|
+
File.delete("benchmark.sqlite3") if File.exist?("benchmark.sqlite3")
|
10
|
+
|
11
|
+
POOL_SIZE = 10
|
12
|
+
|
13
|
+
EXTRALITE_CONNECTIONS = POOL_SIZE.times.map do
|
14
|
+
db = Extralite::Database.new("benchmark.sqlite3")
|
15
|
+
db.execute("PRAGMA journal_mode = WAL")
|
16
|
+
db.execute("PRAGMA synchronous = NORMAL")
|
17
|
+
db.execute("PRAGMA journal_size_limit = 64000000")
|
18
|
+
db.execute("PRAGMA mmap_size = 128000000")
|
19
|
+
db.execute("PRAGMA cache_size = 2000")
|
20
|
+
db.execute("PRAGMA busy_timeout = 5000")
|
21
|
+
db
|
22
|
+
end
|
23
|
+
|
24
|
+
SQLITE3_CONNECTIONS = POOL_SIZE.times.map do
|
25
|
+
db = SQLite3::Database.new("benchmark.sqlite3")
|
26
|
+
db.execute("PRAGMA journal_mode = WAL")
|
27
|
+
db.execute("PRAGMA synchronous = NORMAL")
|
28
|
+
db.execute("PRAGMA journal_size_limit = 64000000")
|
29
|
+
db.execute("PRAGMA mmap_size = 128000000")
|
30
|
+
db.execute("PRAGMA cache_size = 2000")
|
31
|
+
db.execute("PRAGMA busy_timeout = 5000")
|
32
|
+
db
|
33
|
+
end
|
34
|
+
|
35
|
+
EXTRALITE_CONNECTIONS[0].execute("CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT, created_at TEXT, updated_at TEXT) STRICT")
|
36
|
+
insert_statement = EXTRALITE_CONNECTIONS[0].prepare("INSERT INTO users (name, created_at, updated_at) VALUES (?, ?, ?)")
|
37
|
+
1000.times do
|
38
|
+
insert_statement.execute("John Doe", Time.now.iso8601, Time.now.iso8601)
|
39
|
+
end
|
40
|
+
|
41
|
+
# Benchmark variations
|
42
|
+
|
43
|
+
THREAD_COUNTS = [1, 2, 4, 8]
|
44
|
+
LIMITS = [10, 100, 1000]
|
45
|
+
CLIENTS = %w[extralite sqlite3]
|
46
|
+
|
47
|
+
# Benchmark
|
48
|
+
|
49
|
+
GC.disable
|
50
|
+
Benchmark.bm do |x|
|
51
|
+
LIMITS.each do |limit|
|
52
|
+
THREAD_COUNTS.each do |thread_count|
|
53
|
+
CLIENTS.each do |client|
|
54
|
+
GC.start
|
55
|
+
|
56
|
+
x.report("#{client.rjust('extralite'.length)} - limit: #{limit}, threads: #{thread_count}") do
|
57
|
+
threads = thread_count.times.map do |thread_number|
|
58
|
+
Thread.new do
|
59
|
+
start = Time.now
|
60
|
+
if client == "extralite"
|
61
|
+
1_000.times do
|
62
|
+
records = EXTRALITE_CONNECTIONS[thread_number].query_ary("SELECT * FROM users LIMIT #{limit}")
|
63
|
+
raise "Expected #{limit} but got #{length}" unless records.length == limit
|
64
|
+
end
|
65
|
+
else
|
66
|
+
1_000.times do
|
67
|
+
records = SQLITE3_CONNECTIONS[thread_number].query("SELECT * FROM users LIMIT #{limit}").entries
|
68
|
+
raise "Expected #{limit} but got #{length}" unless records.length == limit
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
threads.each(&:join)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
puts
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
GC.enable
|
data/test/issue-54.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "./lib/extralite"
|
4
|
+
|
5
|
+
puts 'Connecting to database...'
|
6
|
+
|
7
|
+
connection_1 = Extralite::Database.new("test.sqlite3")
|
8
|
+
puts "#{connection_1} connected"
|
9
|
+
connection_2 = Extralite::Database.new("test.sqlite3")
|
10
|
+
connection_2.busy_timeout = 0
|
11
|
+
puts "#{connection_2} connected"
|
12
|
+
|
13
|
+
[connection_1, connection_2].each do |connection|
|
14
|
+
puts "#{connection} beginning transaction..."
|
15
|
+
connection.execute "begin immediate transaction"
|
16
|
+
end
|
17
|
+
|
18
|
+
[connection_1, connection_2].each do |connection|
|
19
|
+
puts "#{connection} rolling back transaction..."
|
20
|
+
connection.execute "rollback transaction"
|
21
|
+
end
|