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/common.c
CHANGED
@@ -3,6 +3,15 @@
|
|
3
3
|
|
4
4
|
rb_encoding *UTF8_ENCODING;
|
5
5
|
|
6
|
+
inline void *gvl_call(enum gvl_mode mode, void *(*fn)(void *), void *data) {
|
7
|
+
switch (mode) {
|
8
|
+
case GVL_RELEASE:
|
9
|
+
return rb_thread_call_without_gvl(fn, data, RUBY_UBF_IO, 0);
|
10
|
+
default:
|
11
|
+
return fn(data);
|
12
|
+
}
|
13
|
+
}
|
14
|
+
|
6
15
|
static inline VALUE get_column_value(sqlite3_stmt *stmt, int col, int type) {
|
7
16
|
switch (type) {
|
8
17
|
case SQLITE_NULL:
|
@@ -22,7 +31,25 @@ static inline VALUE get_column_value(sqlite3_stmt *stmt, int col, int type) {
|
|
22
31
|
return Qnil;
|
23
32
|
}
|
24
33
|
|
25
|
-
|
34
|
+
int bind_parameter_value(sqlite3_stmt *stmt, int pos, VALUE value);
|
35
|
+
|
36
|
+
static inline void bind_key_value(sqlite3_stmt *stmt, VALUE k, VALUE v) {
|
37
|
+
switch (TYPE(k)) {
|
38
|
+
case T_FIXNUM:
|
39
|
+
bind_parameter_value(stmt, FIX2INT(k), v);
|
40
|
+
break;
|
41
|
+
case T_SYMBOL:
|
42
|
+
k = rb_sym2str(k);
|
43
|
+
case T_STRING:
|
44
|
+
if (RSTRING_PTR(k)[0] != ':') k = rb_str_plus(rb_str_new2(":"), k);
|
45
|
+
int pos = sqlite3_bind_parameter_index(stmt, StringValuePtr(k));
|
46
|
+
bind_parameter_value(stmt, pos, v);
|
47
|
+
break;
|
48
|
+
default:
|
49
|
+
rb_raise(cParameterError, "Cannot bind parameter with a key of type %"PRIsVALUE"",
|
50
|
+
rb_class_name(rb_obj_class(k)));
|
51
|
+
}
|
52
|
+
}
|
26
53
|
|
27
54
|
void bind_hash_parameter_values(sqlite3_stmt *stmt, VALUE hash) {
|
28
55
|
VALUE keys = rb_funcall(hash, ID_keys, 0);
|
@@ -30,62 +57,79 @@ void bind_hash_parameter_values(sqlite3_stmt *stmt, VALUE hash) {
|
|
30
57
|
for (long i = 0; i < len; i++) {
|
31
58
|
VALUE k = RARRAY_AREF(keys, i);
|
32
59
|
VALUE v = rb_hash_aref(hash, k);
|
33
|
-
|
34
|
-
switch (TYPE(k)) {
|
35
|
-
case T_FIXNUM:
|
36
|
-
bind_parameter_value(stmt, FIX2INT(k), v);
|
37
|
-
break;
|
38
|
-
case T_SYMBOL:
|
39
|
-
k = rb_funcall(k, ID_to_s, 0);
|
40
|
-
case T_STRING:
|
41
|
-
if(RSTRING_PTR(k)[0] != ':') k = rb_str_plus(rb_str_new2(":"), k);
|
42
|
-
int pos = sqlite3_bind_parameter_index(stmt, StringValuePtr(k));
|
43
|
-
bind_parameter_value(stmt, pos, v);
|
44
|
-
break;
|
45
|
-
default:
|
46
|
-
rb_raise(cError, "Cannot bind hash key value idx %ld", i);
|
47
|
-
}
|
60
|
+
bind_key_value(stmt, k, v);
|
48
61
|
}
|
49
62
|
RB_GC_GUARD(keys);
|
50
63
|
}
|
51
64
|
|
52
|
-
|
65
|
+
void bind_struct_parameter_values(sqlite3_stmt *stmt, VALUE struct_obj) {
|
66
|
+
VALUE members = rb_struct_members(struct_obj);
|
67
|
+
for (long i = 0; i < RSTRUCT_LEN(struct_obj); i++) {
|
68
|
+
VALUE k = rb_ary_entry(members, i);
|
69
|
+
VALUE v = RSTRUCT_GET(struct_obj, i);
|
70
|
+
bind_key_value(stmt, k, v);
|
71
|
+
}
|
72
|
+
RB_GC_GUARD(members);
|
73
|
+
}
|
74
|
+
|
75
|
+
inline int bind_parameter_value(sqlite3_stmt *stmt, int pos, VALUE value) {
|
53
76
|
switch (TYPE(value)) {
|
54
77
|
case T_NIL:
|
55
78
|
sqlite3_bind_null(stmt, pos);
|
56
|
-
return;
|
79
|
+
return 1;
|
57
80
|
case T_FIXNUM:
|
81
|
+
case T_BIGNUM:
|
58
82
|
sqlite3_bind_int64(stmt, pos, NUM2LL(value));
|
59
|
-
return;
|
83
|
+
return 1;
|
60
84
|
case T_FLOAT:
|
61
85
|
sqlite3_bind_double(stmt, pos, NUM2DBL(value));
|
62
|
-
return;
|
86
|
+
return 1;
|
63
87
|
case T_TRUE:
|
64
88
|
sqlite3_bind_int(stmt, pos, 1);
|
65
|
-
return;
|
89
|
+
return 1;
|
66
90
|
case T_FALSE:
|
67
91
|
sqlite3_bind_int(stmt, pos, 0);
|
68
|
-
return;
|
92
|
+
return 1;
|
93
|
+
case T_SYMBOL:
|
94
|
+
value = rb_sym2str(value);
|
69
95
|
case T_STRING:
|
70
|
-
|
71
|
-
|
96
|
+
if (rb_enc_get_index(value) == rb_ascii8bit_encindex() || CLASS_OF(value) == cBlob)
|
97
|
+
sqlite3_bind_blob(stmt, pos, RSTRING_PTR(value), RSTRING_LEN(value), SQLITE_TRANSIENT);
|
98
|
+
else
|
99
|
+
sqlite3_bind_text(stmt, pos, RSTRING_PTR(value), RSTRING_LEN(value), SQLITE_TRANSIENT);
|
100
|
+
return 1;
|
101
|
+
case T_ARRAY:
|
102
|
+
{
|
103
|
+
int count = RARRAY_LEN(value);
|
104
|
+
for (int i = 0; i < count; i++)
|
105
|
+
bind_parameter_value(stmt, pos + i, RARRAY_AREF(value, i));
|
106
|
+
return count;
|
107
|
+
}
|
72
108
|
case T_HASH:
|
73
109
|
bind_hash_parameter_values(stmt, value);
|
74
|
-
return;
|
110
|
+
return 0;
|
111
|
+
case T_STRUCT:
|
112
|
+
bind_struct_parameter_values(stmt, value);
|
113
|
+
return 0;
|
75
114
|
default:
|
76
|
-
rb_raise(
|
115
|
+
rb_raise(cParameterError, "Cannot bind parameter at position %d of type %"PRIsVALUE"",
|
116
|
+
pos, rb_class_name(rb_obj_class(value)));
|
77
117
|
}
|
78
118
|
}
|
79
119
|
|
80
120
|
inline void bind_all_parameters(sqlite3_stmt *stmt, int argc, VALUE *argv) {
|
81
|
-
|
121
|
+
int pos = 1;
|
122
|
+
for (int i = 0; i < argc; i++) {
|
123
|
+
pos += bind_parameter_value(stmt, pos, argv[i]);
|
124
|
+
}
|
82
125
|
}
|
83
126
|
|
84
127
|
inline void bind_all_parameters_from_object(sqlite3_stmt *stmt, VALUE obj) {
|
85
128
|
if (TYPE(obj) == T_ARRAY) {
|
129
|
+
int pos = 1;
|
86
130
|
int count = RARRAY_LEN(obj);
|
87
131
|
for (int i = 0; i < count; i++)
|
88
|
-
bind_parameter_value(stmt,
|
132
|
+
pos += bind_parameter_value(stmt, pos, RARRAY_AREF(obj, i));
|
89
133
|
}
|
90
134
|
else
|
91
135
|
bind_parameter_value(stmt, 1, obj);
|
@@ -126,7 +170,7 @@ typedef struct {
|
|
126
170
|
int rc;
|
127
171
|
} prepare_stmt_ctx;
|
128
172
|
|
129
|
-
void *
|
173
|
+
void *prepare_multi_stmt_impl(void *ptr) {
|
130
174
|
prepare_stmt_ctx *ctx = (prepare_stmt_ctx *)ptr;
|
131
175
|
const char *rest = NULL;
|
132
176
|
const char *str = ctx->str;
|
@@ -163,7 +207,7 @@ is not executed, but instead handed back to the caller for looping over results.
|
|
163
207
|
*/
|
164
208
|
void prepare_multi_stmt(sqlite3 *db, sqlite3_stmt **stmt, VALUE sql) {
|
165
209
|
prepare_stmt_ctx ctx = {db, stmt, RSTRING_PTR(sql), RSTRING_LEN(sql), 0};
|
166
|
-
|
210
|
+
gvl_call(GVL_RELEASE, prepare_multi_stmt_impl, (void *)&ctx);
|
167
211
|
RB_GC_GUARD(sql);
|
168
212
|
|
169
213
|
switch (ctx.rc) {
|
@@ -180,7 +224,7 @@ void prepare_multi_stmt(sqlite3 *db, sqlite3_stmt **stmt, VALUE sql) {
|
|
180
224
|
|
181
225
|
#define SQLITE_MULTI_STMT -1
|
182
226
|
|
183
|
-
void *
|
227
|
+
void *prepare_single_stmt_impl(void *ptr) {
|
184
228
|
prepare_stmt_ctx *ctx = (prepare_stmt_ctx *)ptr;
|
185
229
|
const char *rest = NULL;
|
186
230
|
const char *str = ctx->str;
|
@@ -205,7 +249,7 @@ end:
|
|
205
249
|
|
206
250
|
void prepare_single_stmt(sqlite3 *db, sqlite3_stmt **stmt, VALUE sql) {
|
207
251
|
prepare_stmt_ctx ctx = {db, stmt, RSTRING_PTR(sql), RSTRING_LEN(sql), 0};
|
208
|
-
|
252
|
+
gvl_call(GVL_RELEASE, prepare_single_stmt_impl, (void *)&ctx);
|
209
253
|
RB_GC_GUARD(sql);
|
210
254
|
|
211
255
|
switch (ctx.rc) {
|
@@ -227,15 +271,26 @@ struct step_ctx {
|
|
227
271
|
int rc;
|
228
272
|
};
|
229
273
|
|
230
|
-
void *
|
274
|
+
void *stmt_iterate_step(void *ptr) {
|
231
275
|
struct step_ctx *ctx = (struct step_ctx *)ptr;
|
232
276
|
ctx->rc = sqlite3_step(ctx->stmt);
|
233
277
|
return NULL;
|
234
278
|
}
|
235
279
|
|
280
|
+
inline enum gvl_mode stepwise_gvl_mode(query_ctx *ctx) {
|
281
|
+
// a negative or zero threshold means the GVL is always held during iteration.
|
282
|
+
if (ctx->gvl_release_threshold <= 0) return GVL_HOLD;
|
283
|
+
|
284
|
+
if (!sqlite3_stmt_busy(ctx->stmt)) return GVL_RELEASE;
|
285
|
+
|
286
|
+
// if positive, the GVL is normally held, and release every <threshold> steps.
|
287
|
+
return (ctx->step_count % ctx->gvl_release_threshold) ? GVL_HOLD : GVL_RELEASE;
|
288
|
+
}
|
289
|
+
|
236
290
|
inline int stmt_iterate(query_ctx *ctx) {
|
237
291
|
struct step_ctx step_ctx = {ctx->stmt, 0};
|
238
|
-
|
292
|
+
ctx->step_count += 1;
|
293
|
+
gvl_call(stepwise_gvl_mode(ctx), stmt_iterate_step, (void *)&step_ctx);
|
239
294
|
switch (step_ctx.rc) {
|
240
295
|
case SQLITE_ROW:
|
241
296
|
return 1;
|
@@ -379,8 +434,80 @@ VALUE safe_query_single_value(query_ctx *ctx) {
|
|
379
434
|
return value;
|
380
435
|
}
|
381
436
|
|
382
|
-
|
437
|
+
enum batch_mode {
|
438
|
+
BATCH_EXECUTE,
|
439
|
+
BATCH_QUERY_ARY,
|
440
|
+
BATCH_QUERY_HASH,
|
441
|
+
BATCH_QUERY_SINGLE_COLUMN
|
442
|
+
};
|
443
|
+
|
444
|
+
static inline VALUE batch_iterate_hash(query_ctx *ctx) {
|
445
|
+
VALUE rows = rb_ary_new();
|
446
|
+
VALUE row = Qnil;
|
447
|
+
int column_count = sqlite3_column_count(ctx->stmt);
|
448
|
+
VALUE column_names = get_column_names(ctx->stmt, column_count);
|
449
|
+
|
450
|
+
while (stmt_iterate(ctx)) {
|
451
|
+
row = row_to_hash(ctx->stmt, column_count, column_names);
|
452
|
+
rb_ary_push(rows, row);
|
453
|
+
}
|
454
|
+
|
455
|
+
RB_GC_GUARD(column_names);
|
456
|
+
RB_GC_GUARD(rows);
|
457
|
+
return rows;
|
458
|
+
}
|
459
|
+
|
460
|
+
static inline VALUE batch_iterate_ary(query_ctx *ctx) {
|
461
|
+
VALUE rows = rb_ary_new();
|
462
|
+
VALUE row = Qnil;
|
463
|
+
int column_count = sqlite3_column_count(ctx->stmt);
|
464
|
+
|
465
|
+
while (stmt_iterate(ctx)) {
|
466
|
+
row = row_to_ary(ctx->stmt, column_count);
|
467
|
+
rb_ary_push(rows, row);
|
468
|
+
}
|
469
|
+
|
470
|
+
RB_GC_GUARD(rows);
|
471
|
+
return rows;
|
472
|
+
}
|
473
|
+
|
474
|
+
static inline VALUE batch_iterate_single_column(query_ctx *ctx) {
|
475
|
+
VALUE rows = rb_ary_new();
|
476
|
+
VALUE value = Qnil;
|
477
|
+
int column_count = sqlite3_column_count(ctx->stmt);
|
478
|
+
if (column_count != 1) rb_raise(cError, "Expected query result to have 1 column");
|
479
|
+
|
480
|
+
while (stmt_iterate(ctx)) {
|
481
|
+
value = get_column_value(ctx->stmt, 0, sqlite3_column_type(ctx->stmt, 0));
|
482
|
+
rb_ary_push(rows, value);
|
483
|
+
}
|
484
|
+
|
485
|
+
RB_GC_GUARD(rows);
|
486
|
+
return rows;
|
487
|
+
}
|
488
|
+
|
489
|
+
static inline void batch_iterate(query_ctx *ctx, enum batch_mode mode, VALUE *rows) {
|
490
|
+
switch (mode) {
|
491
|
+
case BATCH_EXECUTE:
|
492
|
+
while (stmt_iterate(ctx));
|
493
|
+
break;
|
494
|
+
case BATCH_QUERY_ARY:
|
495
|
+
*rows = batch_iterate_ary(ctx);
|
496
|
+
break;
|
497
|
+
case BATCH_QUERY_HASH:
|
498
|
+
*rows = batch_iterate_hash(ctx);
|
499
|
+
break;
|
500
|
+
case BATCH_QUERY_SINGLE_COLUMN:
|
501
|
+
*rows = batch_iterate_single_column(ctx);
|
502
|
+
break;
|
503
|
+
}
|
504
|
+
}
|
505
|
+
|
506
|
+
static inline VALUE batch_run_array(query_ctx *ctx, enum batch_mode mode) {
|
383
507
|
int count = RARRAY_LEN(ctx->params);
|
508
|
+
int block_given = rb_block_given_p();
|
509
|
+
VALUE results = (mode != BATCH_EXECUTE) && !block_given ? rb_ary_new() : Qnil;
|
510
|
+
VALUE rows = Qnil;
|
384
511
|
int changes = 0;
|
385
512
|
|
386
513
|
for (int i = 0; i < count; i++) {
|
@@ -388,11 +515,135 @@ VALUE safe_execute_multi(query_ctx *ctx) {
|
|
388
515
|
sqlite3_clear_bindings(ctx->stmt);
|
389
516
|
bind_all_parameters_from_object(ctx->stmt, RARRAY_AREF(ctx->params, i));
|
390
517
|
|
391
|
-
|
518
|
+
batch_iterate(ctx, mode, &rows);
|
392
519
|
changes += sqlite3_changes(ctx->sqlite3_db);
|
520
|
+
|
521
|
+
if (mode != BATCH_EXECUTE) {
|
522
|
+
if (block_given)
|
523
|
+
rb_yield(rows);
|
524
|
+
else
|
525
|
+
rb_ary_push(results, rows);
|
526
|
+
}
|
393
527
|
}
|
394
528
|
|
395
|
-
|
529
|
+
RB_GC_GUARD(rows);
|
530
|
+
RB_GC_GUARD(results);
|
531
|
+
|
532
|
+
if (mode == BATCH_EXECUTE || block_given)
|
533
|
+
return INT2FIX(changes);
|
534
|
+
else
|
535
|
+
return results;
|
536
|
+
}
|
537
|
+
|
538
|
+
struct batch_execute_each_ctx {
|
539
|
+
query_ctx *ctx;
|
540
|
+
enum batch_mode mode;
|
541
|
+
int block_given;
|
542
|
+
VALUE results;
|
543
|
+
int changes;
|
544
|
+
};
|
545
|
+
|
546
|
+
static VALUE batch_run_each_iter(RB_BLOCK_CALL_FUNC_ARGLIST(yield_value, vctx)) {
|
547
|
+
struct batch_execute_each_ctx *each_ctx = (struct batch_execute_each_ctx*)vctx;
|
548
|
+
VALUE rows = Qnil;
|
549
|
+
|
550
|
+
sqlite3_reset(each_ctx->ctx->stmt);
|
551
|
+
sqlite3_clear_bindings(each_ctx->ctx->stmt);
|
552
|
+
bind_all_parameters_from_object(each_ctx->ctx->stmt, yield_value);
|
553
|
+
|
554
|
+
batch_iterate(each_ctx->ctx, each_ctx->mode, &rows);
|
555
|
+
each_ctx->changes += sqlite3_changes(each_ctx->ctx->sqlite3_db);
|
556
|
+
|
557
|
+
if (each_ctx->mode != BATCH_EXECUTE) {
|
558
|
+
if (each_ctx->block_given)
|
559
|
+
rb_yield(rows);
|
560
|
+
else
|
561
|
+
rb_ary_push(each_ctx->results, rows);
|
562
|
+
}
|
563
|
+
RB_GC_GUARD(rows);
|
564
|
+
|
565
|
+
return Qnil;
|
566
|
+
}
|
567
|
+
|
568
|
+
static inline VALUE batch_run_each(query_ctx *ctx, enum batch_mode mode) {
|
569
|
+
struct batch_execute_each_ctx each_ctx = {
|
570
|
+
.ctx = ctx,
|
571
|
+
.mode = mode,
|
572
|
+
.block_given = rb_block_given_p(),
|
573
|
+
.results = ((mode != BATCH_EXECUTE) && !rb_block_given_p() ? rb_ary_new() : Qnil),
|
574
|
+
.changes = 0
|
575
|
+
};
|
576
|
+
rb_block_call(ctx->params, ID_each, 0, 0, batch_run_each_iter, (VALUE)&each_ctx);
|
577
|
+
|
578
|
+
if (mode == BATCH_EXECUTE || each_ctx.block_given)
|
579
|
+
return INT2FIX(each_ctx.changes);
|
580
|
+
else
|
581
|
+
return each_ctx.results;
|
582
|
+
}
|
583
|
+
|
584
|
+
static inline VALUE batch_run_proc(query_ctx *ctx, enum batch_mode mode) {
|
585
|
+
VALUE params = Qnil;
|
586
|
+
int block_given = rb_block_given_p();
|
587
|
+
VALUE results = (mode != BATCH_EXECUTE) && !block_given ? rb_ary_new() : Qnil;
|
588
|
+
VALUE rows = Qnil;
|
589
|
+
int changes = 0;
|
590
|
+
|
591
|
+
while (1) {
|
592
|
+
params = rb_funcall(ctx->params, ID_call, 0);
|
593
|
+
if (NIL_P(params)) break;
|
594
|
+
|
595
|
+
sqlite3_reset(ctx->stmt);
|
596
|
+
sqlite3_clear_bindings(ctx->stmt);
|
597
|
+
bind_all_parameters_from_object(ctx->stmt, params);
|
598
|
+
|
599
|
+
batch_iterate(ctx, mode, &rows);
|
600
|
+
changes += sqlite3_changes(ctx->sqlite3_db);
|
601
|
+
|
602
|
+
if (mode != BATCH_EXECUTE) {
|
603
|
+
if (block_given)
|
604
|
+
rb_yield(rows);
|
605
|
+
else
|
606
|
+
rb_ary_push(results, rows);
|
607
|
+
}
|
608
|
+
}
|
609
|
+
|
610
|
+
RB_GC_GUARD(rows);
|
611
|
+
RB_GC_GUARD(results);
|
612
|
+
RB_GC_GUARD(params);
|
613
|
+
|
614
|
+
if (mode == BATCH_EXECUTE || block_given)
|
615
|
+
return INT2FIX(changes);
|
616
|
+
else
|
617
|
+
return results;
|
618
|
+
}
|
619
|
+
|
620
|
+
static inline VALUE batch_run(query_ctx *ctx, enum batch_mode mode) {
|
621
|
+
if (TYPE(ctx->params) == T_ARRAY)
|
622
|
+
return batch_run_array(ctx, mode);
|
623
|
+
|
624
|
+
if (rb_respond_to(ctx->params, ID_each))
|
625
|
+
return batch_run_each(ctx, mode);
|
626
|
+
|
627
|
+
if (rb_respond_to(ctx->params, ID_call))
|
628
|
+
return batch_run_proc(ctx, mode);
|
629
|
+
|
630
|
+
rb_raise(cParameterError, "Invalid parameter source supplied to #batch_execute");
|
631
|
+
}
|
632
|
+
|
633
|
+
VALUE safe_batch_execute(query_ctx *ctx) {
|
634
|
+
return batch_run(ctx, BATCH_EXECUTE);
|
635
|
+
}
|
636
|
+
|
637
|
+
VALUE safe_batch_query(query_ctx *ctx) {
|
638
|
+
return batch_run(ctx, BATCH_QUERY_HASH);
|
639
|
+
}
|
640
|
+
|
641
|
+
VALUE safe_batch_query_ary(query_ctx *ctx) {
|
642
|
+
return batch_run(ctx, BATCH_QUERY_ARY);
|
643
|
+
}
|
644
|
+
|
645
|
+
VALUE safe_batch_query_single_column(query_ctx *ctx) {
|
646
|
+
return batch_run(ctx, BATCH_QUERY_SINGLE_COLUMN);
|
396
647
|
}
|
397
648
|
|
398
649
|
VALUE safe_query_columns(query_ctx *ctx) {
|