extralite 2.4 → 2.6
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/.github/workflows/test-bundle.yml +30 -0
- data/.github/workflows/test.yml +2 -12
- data/CHANGELOG.md +49 -10
- data/Gemfile.lock +1 -1
- data/LICENSE +1 -1
- data/README.md +876 -217
- data/TODO.md +2 -3
- data/ext/extralite/changeset.c +463 -0
- data/ext/extralite/common.c +226 -19
- data/ext/extralite/database.c +339 -23
- data/ext/extralite/extconf-bundle.rb +10 -4
- data/ext/extralite/extconf.rb +31 -27
- data/ext/extralite/extralite.h +25 -5
- data/ext/extralite/extralite_ext.c +10 -0
- data/ext/extralite/iterator.c +8 -3
- data/ext/extralite/query.c +222 -22
- data/gemspec.rb +1 -1
- data/lib/extralite/version.rb +1 -1
- data/lib/extralite.rb +64 -8
- data/test/helper.rb +8 -0
- data/test/issue-54.rb +21 -0
- data/test/issue-59.rb +70 -0
- data/test/perf_ary.rb +14 -12
- data/test/perf_hash.rb +17 -15
- data/test/perf_hash_prepared.rb +58 -0
- data/test/test_changeset.rb +161 -0
- data/test/test_database.rb +672 -13
- data/test/test_query.rb +367 -2
- metadata +10 -5
- data/test/perf_prepared.rb +0 -64
data/ext/extralite/common.c
CHANGED
@@ -31,7 +31,7 @@ static inline VALUE get_column_value(sqlite3_stmt *stmt, int col, int type) {
|
|
31
31
|
return Qnil;
|
32
32
|
}
|
33
33
|
|
34
|
-
|
34
|
+
int bind_parameter_value(sqlite3_stmt *stmt, int pos, VALUE value);
|
35
35
|
|
36
36
|
static inline void bind_key_value(sqlite3_stmt *stmt, VALUE k, VALUE v) {
|
37
37
|
switch (TYPE(k)) {
|
@@ -72,24 +72,24 @@ void bind_struct_parameter_values(sqlite3_stmt *stmt, VALUE struct_obj) {
|
|
72
72
|
RB_GC_GUARD(members);
|
73
73
|
}
|
74
74
|
|
75
|
-
inline
|
75
|
+
inline int bind_parameter_value(sqlite3_stmt *stmt, int pos, VALUE value) {
|
76
76
|
switch (TYPE(value)) {
|
77
77
|
case T_NIL:
|
78
78
|
sqlite3_bind_null(stmt, pos);
|
79
|
-
return;
|
79
|
+
return 1;
|
80
80
|
case T_FIXNUM:
|
81
81
|
case T_BIGNUM:
|
82
82
|
sqlite3_bind_int64(stmt, pos, NUM2LL(value));
|
83
|
-
return;
|
83
|
+
return 1;
|
84
84
|
case T_FLOAT:
|
85
85
|
sqlite3_bind_double(stmt, pos, NUM2DBL(value));
|
86
|
-
return;
|
86
|
+
return 1;
|
87
87
|
case T_TRUE:
|
88
88
|
sqlite3_bind_int(stmt, pos, 1);
|
89
|
-
return;
|
89
|
+
return 1;
|
90
90
|
case T_FALSE:
|
91
91
|
sqlite3_bind_int(stmt, pos, 0);
|
92
|
-
return;
|
92
|
+
return 1;
|
93
93
|
case T_SYMBOL:
|
94
94
|
value = rb_sym2str(value);
|
95
95
|
case T_STRING:
|
@@ -97,13 +97,20 @@ inline void bind_parameter_value(sqlite3_stmt *stmt, int pos, VALUE value) {
|
|
97
97
|
sqlite3_bind_blob(stmt, pos, RSTRING_PTR(value), RSTRING_LEN(value), SQLITE_TRANSIENT);
|
98
98
|
else
|
99
99
|
sqlite3_bind_text(stmt, pos, RSTRING_PTR(value), RSTRING_LEN(value), SQLITE_TRANSIENT);
|
100
|
-
return;
|
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
|
+
}
|
101
108
|
case T_HASH:
|
102
109
|
bind_hash_parameter_values(stmt, value);
|
103
|
-
return;
|
110
|
+
return 0;
|
104
111
|
case T_STRUCT:
|
105
112
|
bind_struct_parameter_values(stmt, value);
|
106
|
-
return;
|
113
|
+
return 0;
|
107
114
|
default:
|
108
115
|
rb_raise(cParameterError, "Cannot bind parameter at position %d of type %"PRIsVALUE"",
|
109
116
|
pos, rb_class_name(rb_obj_class(value)));
|
@@ -111,14 +118,18 @@ inline void bind_parameter_value(sqlite3_stmt *stmt, int pos, VALUE value) {
|
|
111
118
|
}
|
112
119
|
|
113
120
|
inline void bind_all_parameters(sqlite3_stmt *stmt, int argc, VALUE *argv) {
|
114
|
-
|
121
|
+
int pos = 1;
|
122
|
+
for (int i = 0; i < argc; i++) {
|
123
|
+
pos += bind_parameter_value(stmt, pos, argv[i]);
|
124
|
+
}
|
115
125
|
}
|
116
126
|
|
117
127
|
inline void bind_all_parameters_from_object(sqlite3_stmt *stmt, VALUE obj) {
|
118
128
|
if (TYPE(obj) == T_ARRAY) {
|
129
|
+
int pos = 1;
|
119
130
|
int count = RARRAY_LEN(obj);
|
120
131
|
for (int i = 0; i < count; i++)
|
121
|
-
bind_parameter_value(stmt,
|
132
|
+
pos += bind_parameter_value(stmt, pos, RARRAY_AREF(obj, i));
|
122
133
|
}
|
123
134
|
else
|
124
135
|
bind_parameter_value(stmt, 1, obj);
|
@@ -194,9 +205,9 @@ statements. It will release the GVL while the statements are being prepared and
|
|
194
205
|
executed. All statements excluding the last one are executed. The last statement
|
195
206
|
is not executed, but instead handed back to the caller for looping over results.
|
196
207
|
*/
|
197
|
-
void prepare_multi_stmt(sqlite3 *db, sqlite3_stmt **stmt, VALUE sql) {
|
208
|
+
void prepare_multi_stmt(enum gvl_mode mode, sqlite3 *db, sqlite3_stmt **stmt, VALUE sql) {
|
198
209
|
prepare_stmt_ctx ctx = {db, stmt, RSTRING_PTR(sql), RSTRING_LEN(sql), 0};
|
199
|
-
gvl_call(
|
210
|
+
gvl_call(mode, prepare_multi_stmt_impl, (void *)&ctx);
|
200
211
|
RB_GC_GUARD(sql);
|
201
212
|
|
202
213
|
switch (ctx.rc) {
|
@@ -236,9 +247,9 @@ end:
|
|
236
247
|
return NULL;
|
237
248
|
}
|
238
249
|
|
239
|
-
void prepare_single_stmt(sqlite3 *db, sqlite3_stmt **stmt, VALUE sql) {
|
250
|
+
void prepare_single_stmt(enum gvl_mode mode, sqlite3 *db, sqlite3_stmt **stmt, VALUE sql) {
|
240
251
|
prepare_stmt_ctx ctx = {db, stmt, RSTRING_PTR(sql), RSTRING_LEN(sql), 0};
|
241
|
-
gvl_call(
|
252
|
+
gvl_call(mode, prepare_single_stmt_impl, (void *)&ctx);
|
242
253
|
RB_GC_GUARD(sql);
|
243
254
|
|
244
255
|
switch (ctx.rc) {
|
@@ -423,8 +434,80 @@ VALUE safe_query_single_value(query_ctx *ctx) {
|
|
423
434
|
return value;
|
424
435
|
}
|
425
436
|
|
426
|
-
|
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) {
|
427
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;
|
428
511
|
int changes = 0;
|
429
512
|
|
430
513
|
for (int i = 0; i < count; i++) {
|
@@ -432,11 +515,135 @@ VALUE safe_execute_multi(query_ctx *ctx) {
|
|
432
515
|
sqlite3_clear_bindings(ctx->stmt);
|
433
516
|
bind_all_parameters_from_object(ctx->stmt, RARRAY_AREF(ctx->params, i));
|
434
517
|
|
435
|
-
|
518
|
+
batch_iterate(ctx, mode, &rows);
|
436
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
|
+
}
|
527
|
+
}
|
528
|
+
|
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
|
+
}
|
437
608
|
}
|
438
609
|
|
439
|
-
|
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);
|
440
647
|
}
|
441
648
|
|
442
649
|
VALUE safe_query_columns(query_ctx *ctx) {
|