extralite-bundle 2.11 → 2.12
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/CHANGELOG.md +4 -0
- data/README.md +25 -17
- data/ext/extralite/common.c +26 -12
- data/ext/extralite/database.c +39 -9
- data/ext/extralite/extralite.h +6 -1
- data/ext/extralite/query.c +49 -28
- data/lib/extralite/version.rb +1 -1
- data/test/perf_array.rb +1 -1
- data/test/perf_hash.rb +1 -1
- data/test/perf_hash_prepared.rb +1 -1
- data/test/perf_polyphony.rb +1 -1
- data/test/perf_splat.rb +1 -1
- data/test/test_database.rb +3 -13
- data/test/test_trace.rb +189 -0
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: aa2c324060fa5f9827cf202ff7f6d744d33646a020ca40db1fddb0ff57e490c7
|
4
|
+
data.tar.gz: 8d20c4480b8c5c51c5934869a6057fd71b1eb16ec5f8f7f8f690689f8a372302
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 34f2012945d4e860e50e8153c76638064949f8c91fe55ec2eed655f84999f365ef15b52bbe0bfbd4eb3b6fd8e28e672f1115b2776a96b1ef54298861861f19f6
|
7
|
+
data.tar.gz: 94065b331479701f1910d4880d5fe126620e9e6e3711be27d42bc4d89a471f4b9fe9885d42de0649a10393a92e20a6ea8acb9bfec6259762dc7c15bf6b6b7429
|
data/CHANGELOG.md
CHANGED
data/README.md
CHANGED
@@ -37,7 +37,7 @@ latest features and enhancements.
|
|
37
37
|
|
38
38
|
## Features
|
39
39
|
|
40
|
-
- Best-in-class [performance](#performance) (up to
|
40
|
+
- Best-in-class [performance](#performance) (up to 4.5X the performance of the
|
41
41
|
[sqlite3](https://github.com/sparklemotion/sqlite3-ruby) gem).
|
42
42
|
- Support for [concurrency](#concurrency) out of the box for multi-threaded
|
43
43
|
and multi-fibered apps.
|
@@ -1216,12 +1216,20 @@ To trace all SQL statements executed on the database, pass a block to
|
|
1216
1216
|
|
1217
1217
|
```ruby
|
1218
1218
|
# enable tracing
|
1219
|
-
db.trace { |sql|
|
1219
|
+
db.trace { |sql| p sql: sql }
|
1220
1220
|
|
1221
1221
|
# disable tracing
|
1222
1222
|
db.trace
|
1223
1223
|
```
|
1224
1224
|
|
1225
|
+
Any bound parameters will also be passed to the trace block:
|
1226
|
+
|
1227
|
+
```ruby
|
1228
|
+
db.trace { |sql, *args| p sql: sql, args: args }
|
1229
|
+
db.query('select ?, ?', 1, 2)
|
1230
|
+
# { sql: "select ?, ?", args: [1, 2] }
|
1231
|
+
```
|
1232
|
+
|
1225
1233
|
## Usage with Sequel
|
1226
1234
|
|
1227
1235
|
Extralite includes an adapter for
|
@@ -1240,7 +1248,7 @@ p articles.to_a
|
|
1240
1248
|
|
1241
1249
|
A benchmark script is included, creating a table of various row counts, then
|
1242
1250
|
fetching the entire table using either `sqlite3` or `extralite`. This benchmark
|
1243
|
-
shows Extralite to be up to ~
|
1251
|
+
shows Extralite to be up to ~4.5 times faster than `sqlite3` when fetching a
|
1244
1252
|
large number of rows.
|
1245
1253
|
|
1246
1254
|
### Rows as Hashes
|
@@ -1248,36 +1256,36 @@ large number of rows.
|
|
1248
1256
|
[Benchmark source
|
1249
1257
|
code](https://github.com/digital-fabric/extralite/blob/main/test/perf_hash.rb)
|
1250
1258
|
|
1251
|
-
|Row count|sqlite3
|
1259
|
+
|Row count|sqlite3 2.6.0|Extralite 2.12|Advantage|
|
1252
1260
|
|-:|-:|-:|-:|
|
1253
|
-
|10|
|
1254
|
-
|1K|
|
1255
|
-
|100K|
|
1261
|
+
|10|629.0K rows/s|950.4K rows/s|__1.51x__|
|
1262
|
+
|1K|1770.5K rows/s|4321.5K rows/s|__2.44x__|
|
1263
|
+
|100K|1028.8K rows/s|4088.7K rows/s|__3.97x__|
|
1256
1264
|
|
1257
1265
|
### Rows as Arrays
|
1258
1266
|
|
1259
1267
|
[Benchmark source
|
1260
1268
|
code](https://github.com/digital-fabric/extralite/blob/main/test/perf_array.rb)
|
1261
1269
|
|
1262
|
-
|Row count|sqlite3
|
1270
|
+
|Row count|sqlite3 2.6.0|Extralite 2.12|Advantage|
|
1263
1271
|
|-:|-:|-:|-:|
|
1264
|
-
|10|
|
1265
|
-
|1K|
|
1266
|
-
|100K|
|
1272
|
+
|10|889.4K rows/s|1000.1K rows/s|__1.13x__|
|
1273
|
+
|1K|4518.1K rows/s|5381.5K rows/s|__1.19x__|
|
1274
|
+
|100K|4454.0K rows/s|5083.8K rows/s|__1.14x__|
|
1267
1275
|
|
1268
1276
|
### Prepared Queries (Prepared Statements)
|
1269
1277
|
|
1270
1278
|
[Benchmark source
|
1271
1279
|
code](https://github.com/digital-fabric/extralite/blob/main/test/perf_hash_prepared.rb)
|
1272
1280
|
|
1273
|
-
|Row count|sqlite3
|
1281
|
+
|Row count|sqlite3 2.6.0|Extralite 2.12|Advantage|
|
1274
1282
|
|-:|-:|-:|-:|
|
1275
|
-
|10|
|
1276
|
-
|1K|
|
1277
|
-
|100K|
|
1283
|
+
|10|783.1K rows/s|1115.1K rows/s|__1.42x__|
|
1284
|
+
|1K|1782.5K rows/s|4635.5K rows/s|__2.60x__|
|
1285
|
+
|100K|1018.1K rows/s|4599.4K rows/s|__4.52x__|
|
1278
1286
|
|
1279
|
-
As those benchmarks show, Extralite is capabale of reading up to
|
1280
|
-
second, and can be more than
|
1287
|
+
As those benchmarks show, Extralite is capabale of reading up to 4.5M rows per
|
1288
|
+
second, and can be more than 4 times faster than the `sqlite3` gem.
|
1281
1289
|
|
1282
1290
|
Note that the benchmarks above were performed on synthetic data, in a
|
1283
1291
|
single-threaded environment, with the GVL release threshold set to -1, which
|
data/ext/extralite/common.c
CHANGED
@@ -379,7 +379,7 @@ VALUE safe_query_hash(query_ctx *ctx) {
|
|
379
379
|
while (stmt_iterate(ctx)) {
|
380
380
|
row = row_to_hash(ctx->stmt, column_count, &names);
|
381
381
|
if (do_transform)
|
382
|
-
row =
|
382
|
+
row = INVOKE_PROC(ctx->transform_proc, 1, &row);
|
383
383
|
row_count++;
|
384
384
|
switch (ctx->row_mode) {
|
385
385
|
case ROW_YIELD:
|
@@ -416,7 +416,7 @@ VALUE safe_query_hash(query_ctx *ctx) {
|
|
416
416
|
#define ARGV_GET_ROW(ctx, column_count, argv_values, row, do_transform, return_rows) \
|
417
417
|
row_to_splat_values(ctx->stmt, column_count, argv_values); \
|
418
418
|
if (do_transform) \
|
419
|
-
row =
|
419
|
+
row = INVOKE_PROC(ctx->transform_proc, column_count, argv_values); \
|
420
420
|
else if (return_rows) \
|
421
421
|
row = column_count == 1 ? argv_values[0] : rb_ary_new_from_values(column_count, argv_values);
|
422
422
|
|
@@ -468,7 +468,7 @@ VALUE safe_query_array(query_ctx *ctx) {
|
|
468
468
|
while (stmt_iterate(ctx)) {
|
469
469
|
row = row_to_array(ctx->stmt, column_count);
|
470
470
|
if (do_transform)
|
471
|
-
row =
|
471
|
+
row = INVOKE_PROC(ctx->transform_proc, 1, &row);
|
472
472
|
row_count++;
|
473
473
|
switch (ctx->row_mode) {
|
474
474
|
case ROW_YIELD:
|
@@ -497,7 +497,7 @@ VALUE safe_query_single_row_hash(query_ctx *ctx) {
|
|
497
497
|
if (stmt_iterate(ctx)) {
|
498
498
|
row = row_to_hash(ctx->stmt, column_count, &names);
|
499
499
|
if (!NIL_P(ctx->transform_proc))
|
500
|
-
row =
|
500
|
+
row = INVOKE_PROC(ctx->transform_proc, 1, &row);
|
501
501
|
}
|
502
502
|
|
503
503
|
RB_GC_GUARD(row);
|
@@ -530,7 +530,7 @@ VALUE safe_query_single_row_array(query_ctx *ctx) {
|
|
530
530
|
if (stmt_iterate(ctx)) {
|
531
531
|
row = row_to_array(ctx->stmt, column_count);
|
532
532
|
if (do_transform)
|
533
|
-
row =
|
533
|
+
row = INVOKE_PROC(ctx->transform_proc, 1, &row);
|
534
534
|
}
|
535
535
|
|
536
536
|
RB_GC_GUARD(row);
|
@@ -554,7 +554,7 @@ static inline VALUE batch_iterate_hash(query_ctx *ctx) {
|
|
554
554
|
while (stmt_iterate(ctx)) {
|
555
555
|
row = row_to_hash(ctx->stmt, column_count, &names);
|
556
556
|
if (do_transform)
|
557
|
-
row =
|
557
|
+
row = INVOKE_PROC(ctx->transform_proc, 1, &row);
|
558
558
|
rb_ary_push(rows, row);
|
559
559
|
}
|
560
560
|
|
@@ -573,7 +573,7 @@ static inline VALUE batch_iterate_array(query_ctx *ctx) {
|
|
573
573
|
while (stmt_iterate(ctx)) {
|
574
574
|
row = row_to_array(ctx->stmt, column_count);
|
575
575
|
if (do_transform)
|
576
|
-
row =
|
576
|
+
row = INVOKE_PROC(ctx->transform_proc, 1, &row);
|
577
577
|
rb_ary_push(rows, row);
|
578
578
|
}
|
579
579
|
|
@@ -619,6 +619,19 @@ static inline void batch_iterate(query_ctx *ctx, enum batch_mode mode, VALUE *ro
|
|
619
619
|
}
|
620
620
|
}
|
621
621
|
|
622
|
+
static inline void invoke_pre_query_hook(query_ctx *ctx, VALUE params) {
|
623
|
+
int argc = 1;
|
624
|
+
switch (TYPE(params)) {
|
625
|
+
case T_ARRAY:
|
626
|
+
argc = -1;
|
627
|
+
break;
|
628
|
+
case T_NIL:
|
629
|
+
argc = 0;
|
630
|
+
}
|
631
|
+
|
632
|
+
Database_pre_query_hook(ctx->db, ctx->stmt, ctx->sql, argc, ¶ms);
|
633
|
+
}
|
634
|
+
|
622
635
|
static inline VALUE batch_run_array(query_ctx *ctx, enum batch_mode batch_mode) {
|
623
636
|
int count = RARRAY_LEN(ctx->params);
|
624
637
|
int block_given = rb_block_given_p();
|
@@ -629,8 +642,9 @@ static inline VALUE batch_run_array(query_ctx *ctx, enum batch_mode batch_mode)
|
|
629
642
|
for (int i = 0; i < count; i++) {
|
630
643
|
sqlite3_reset(ctx->stmt);
|
631
644
|
sqlite3_clear_bindings(ctx->stmt);
|
632
|
-
|
633
|
-
bind_all_parameters_from_object(ctx->stmt,
|
645
|
+
VALUE params = RARRAY_AREF(ctx->params, i);
|
646
|
+
bind_all_parameters_from_object(ctx->stmt, params);
|
647
|
+
invoke_pre_query_hook(ctx, params);
|
634
648
|
|
635
649
|
batch_iterate(ctx, batch_mode, &rows);
|
636
650
|
changes += sqlite3_changes(ctx->sqlite3_db);
|
@@ -666,8 +680,8 @@ static VALUE batch_run_each_iter(RB_BLOCK_CALL_FUNC_ARGLIST(yield_value, vctx))
|
|
666
680
|
|
667
681
|
sqlite3_reset(each_ctx->ctx->stmt);
|
668
682
|
sqlite3_clear_bindings(each_ctx->ctx->stmt);
|
669
|
-
Database_issue_query(each_ctx->ctx->db, each_ctx->ctx->stmt);
|
670
683
|
bind_all_parameters_from_object(each_ctx->ctx->stmt, yield_value);
|
684
|
+
invoke_pre_query_hook(each_ctx->ctx, yield_value);
|
671
685
|
|
672
686
|
batch_iterate(each_ctx->ctx, each_ctx->batch_mode, &rows);
|
673
687
|
each_ctx->changes += sqlite3_changes(each_ctx->ctx->sqlite3_db);
|
@@ -707,13 +721,13 @@ static inline VALUE batch_run_proc(query_ctx *ctx, enum batch_mode batch_mode) {
|
|
707
721
|
int changes = 0;
|
708
722
|
|
709
723
|
while (1) {
|
710
|
-
params =
|
724
|
+
params = INVOKE_PROC(ctx->params, 0, NULL);
|
711
725
|
if (NIL_P(params)) break;
|
712
726
|
|
713
727
|
sqlite3_reset(ctx->stmt);
|
714
728
|
sqlite3_clear_bindings(ctx->stmt);
|
715
|
-
Database_issue_query(ctx->db, ctx->stmt);
|
716
729
|
bind_all_parameters_from_object(ctx->stmt, params);
|
730
|
+
invoke_pre_query_hook(ctx, params);
|
717
731
|
|
718
732
|
batch_iterate(ctx, batch_mode, &rows);
|
719
733
|
changes += sqlite3_changes(ctx->sqlite3_db);
|
data/ext/extralite/database.c
CHANGED
@@ -150,14 +150,15 @@ int Database_progress_handler(void *ptr) {
|
|
150
150
|
|
151
151
|
db->progress_handler.tick_count -= db->progress_handler.period;
|
152
152
|
db->progress_handler.call_count += 1;
|
153
|
-
|
153
|
+
INVOKE_PROC(db->progress_handler.proc, 0, NULL);
|
154
154
|
done:
|
155
155
|
return 0;
|
156
156
|
}
|
157
157
|
|
158
158
|
int Database_busy_handler(void *ptr, int v) {
|
159
159
|
Database_t *db = (Database_t *)ptr;
|
160
|
-
|
160
|
+
VALUE arg = Qtrue;
|
161
|
+
INVOKE_PROC(db->progress_handler.proc, 1, &arg);
|
161
162
|
return 1;
|
162
163
|
}
|
163
164
|
|
@@ -297,7 +298,7 @@ static inline VALUE Database_perform_query(int argc, VALUE *argv, VALUE self, VA
|
|
297
298
|
if (stmt == NULL) return Qnil;
|
298
299
|
|
299
300
|
bind_all_parameters(stmt, argc - 1, argv + 1);
|
300
|
-
|
301
|
+
Database_pre_query_hook(db, stmt, sql, argc - 1, argv + 1);
|
301
302
|
|
302
303
|
query_ctx ctx = QUERY_CTX(
|
303
304
|
self, sql, db, stmt, Qnil, transform,
|
@@ -1104,16 +1105,45 @@ static inline enum progress_handler_mode symbol_to_progress_mode(VALUE mode) {
|
|
1104
1105
|
rb_raise(eArgumentError, "Invalid progress handler mode");
|
1105
1106
|
}
|
1106
1107
|
|
1107
|
-
|
1108
|
-
|
1109
|
-
|
1110
|
-
|
1111
|
-
|
1108
|
+
#define MAX_TRACE_ARGS 16
|
1109
|
+
|
1110
|
+
static inline void Database_trace_invoke(Database_t *db, VALUE sql, int argc, VALUE *argv) {
|
1111
|
+
if (argc == 0)
|
1112
|
+
INVOKE_PROC(db->trace_proc, 1, &sql);
|
1113
|
+
else if (argc == -1) {
|
1114
|
+
VALUE args = argv[0];
|
1115
|
+
if (TYPE(args) == T_ARRAY) {
|
1116
|
+
int args_len = RARRAY_LEN(args);
|
1117
|
+
VALUE params = rb_ary_new_capa(args_len + 1);
|
1118
|
+
rb_ary_push(params, sql);
|
1119
|
+
for (int i = 0; i < args_len; i++)
|
1120
|
+
rb_ary_push(params, RARRAY_AREF(args, i));
|
1121
|
+
rb_proc_call_kw(db->trace_proc, params, RB_NO_KEYWORDS);
|
1122
|
+
RB_GC_GUARD(params);
|
1123
|
+
}
|
1124
|
+
else
|
1125
|
+
INVOKE_PROC(db->trace_proc, 1, &sql);
|
1126
|
+
}
|
1127
|
+
else if (argc > MAX_TRACE_ARGS) {
|
1128
|
+
rb_raise(cError, "Too many arguments provided to trace proc");
|
1129
|
+
}
|
1130
|
+
else {
|
1131
|
+
VALUE trace_argv[MAX_TRACE_ARGS];
|
1132
|
+
trace_argv[0] = sql;
|
1133
|
+
for (int i = 0; i < argc; i++)
|
1134
|
+
trace_argv[i + 1] = argv[i];
|
1135
|
+
INVOKE_PROC(db->trace_proc, argc + 1, trace_argv);
|
1112
1136
|
}
|
1137
|
+
}
|
1138
|
+
|
1139
|
+
inline void Database_pre_query_hook(Database_t *db, sqlite3_stmt *stmt, VALUE sql, int argc, VALUE *argv) {
|
1140
|
+
if (db->trace_proc != Qnil)
|
1141
|
+
Database_trace_invoke(db, sql, argc, argv);
|
1142
|
+
|
1113
1143
|
switch (db->progress_handler.mode) {
|
1114
1144
|
case PROGRESS_AT_LEAST_ONCE:
|
1115
1145
|
case PROGRESS_ONCE:
|
1116
|
-
|
1146
|
+
INVOKE_PROC(db->progress_handler.proc, 0, NULL);
|
1117
1147
|
default:
|
1118
1148
|
; // do nothing
|
1119
1149
|
|
data/ext/extralite/extralite.h
CHANGED
@@ -22,6 +22,8 @@
|
|
22
22
|
|
23
23
|
#define SAFE(f) (VALUE (*)(VALUE))(f)
|
24
24
|
|
25
|
+
#define INVOKE_PROC(proc, argc, argv) rb_proc_call_with_block(proc, argc, argv, Qnil)
|
26
|
+
|
25
27
|
extern VALUE cDatabase;
|
26
28
|
extern VALUE cQuery;
|
27
29
|
extern VALUE cIterator;
|
@@ -76,14 +78,17 @@ enum query_mode {
|
|
76
78
|
};
|
77
79
|
|
78
80
|
typedef struct {
|
81
|
+
VALUE self;
|
79
82
|
VALUE db;
|
80
83
|
VALUE sql;
|
81
84
|
VALUE transform_proc;
|
85
|
+
VALUE bound_params;
|
82
86
|
Database_t *db_struct;
|
83
87
|
sqlite3 *sqlite3_db;
|
84
88
|
sqlite3_stmt *stmt;
|
85
89
|
int eof;
|
86
90
|
int closed;
|
91
|
+
int should_reset;
|
87
92
|
enum query_mode query_mode;
|
88
93
|
} Query_t;
|
89
94
|
|
@@ -180,7 +185,7 @@ void bind_all_parameters_from_object(sqlite3_stmt *stmt, VALUE obj);
|
|
180
185
|
int stmt_iterate(query_ctx *ctx);
|
181
186
|
VALUE cleanup_stmt(query_ctx *ctx);
|
182
187
|
|
183
|
-
void
|
188
|
+
void Database_pre_query_hook(Database_t *db, sqlite3_stmt *stmt, VALUE sql, int argc, VALUE *argv);
|
184
189
|
sqlite3 *Database_sqlite3_db(VALUE self);
|
185
190
|
enum gvl_mode Database_prepare_gvl_mode(Database_t *db);
|
186
191
|
Database_t *self_to_database(VALUE self);
|
data/ext/extralite/query.c
CHANGED
@@ -29,6 +29,7 @@ static void Query_mark(void *ptr) {
|
|
29
29
|
rb_gc_mark_movable(query->db);
|
30
30
|
rb_gc_mark_movable(query->sql);
|
31
31
|
rb_gc_mark_movable(query->transform_proc);
|
32
|
+
rb_gc_mark_movable(query->bound_params);
|
32
33
|
}
|
33
34
|
|
34
35
|
static void Query_compact(void *ptr) {
|
@@ -36,6 +37,7 @@ static void Query_compact(void *ptr) {
|
|
36
37
|
query->db = rb_gc_location(query->db);
|
37
38
|
query->sql = rb_gc_location(query->sql);
|
38
39
|
query->transform_proc = rb_gc_location(query->transform_proc);
|
40
|
+
query->bound_params = rb_gc_location(query->bound_params);
|
39
41
|
}
|
40
42
|
|
41
43
|
static void Query_free(void *ptr) {
|
@@ -55,6 +57,7 @@ static VALUE Query_allocate(VALUE klass) {
|
|
55
57
|
query->db = Qnil;
|
56
58
|
query->sql = Qnil;
|
57
59
|
query->transform_proc = Qnil;
|
60
|
+
query->bound_params = Qnil;
|
58
61
|
query->sqlite3_db = NULL;
|
59
62
|
query->stmt = NULL;
|
60
63
|
return TypedData_Wrap_Struct(klass, &Query_type, query);
|
@@ -66,6 +69,14 @@ static inline Query_t *self_to_query(VALUE obj) {
|
|
66
69
|
return query;
|
67
70
|
}
|
68
71
|
|
72
|
+
// verifies that the query is not closed
|
73
|
+
static inline Query_t *self_to_query_verify(VALUE obj) {
|
74
|
+
Query_t *query = self_to_query(obj);
|
75
|
+
if (query->closed) rb_raise(cError, "Query is closed");
|
76
|
+
return query;
|
77
|
+
}
|
78
|
+
|
79
|
+
|
69
80
|
static inline enum query_mode symbol_to_query_mode(VALUE sym) {
|
70
81
|
if (sym == SYM_hash) return QUERY_HASH;
|
71
82
|
if (sym == SYM_splat) return QUERY_SPLAT;
|
@@ -109,12 +120,14 @@ VALUE Query_initialize(VALUE self, VALUE db, VALUE sql, VALUE mode) {
|
|
109
120
|
if (rb_block_given_p())
|
110
121
|
RB_OBJ_WRITE(self, &query->transform_proc, rb_block_proc());
|
111
122
|
|
123
|
+
query->self = self;
|
112
124
|
query->db = db;
|
113
125
|
query->db_struct = self_to_database(db);
|
114
126
|
query->sqlite3_db = Database_sqlite3_db(db);
|
115
127
|
query->stmt = NULL;
|
116
128
|
query->closed = 0;
|
117
129
|
query->eof = 0;
|
130
|
+
query->should_reset = 0;
|
118
131
|
query->query_mode = symbol_to_query_mode(mode);
|
119
132
|
|
120
133
|
return Qnil;
|
@@ -123,20 +136,31 @@ VALUE Query_initialize(VALUE self, VALUE db, VALUE sql, VALUE mode) {
|
|
123
136
|
static inline void query_reset(Query_t *query) {
|
124
137
|
if (!query->stmt)
|
125
138
|
prepare_single_stmt(DB_GVL_MODE(query), query->sqlite3_db, &query->stmt, query->sql);
|
126
|
-
|
127
|
-
|
139
|
+
else
|
140
|
+
sqlite3_reset(query->stmt);
|
141
|
+
|
142
|
+
Database_pre_query_hook(query->db_struct, query->stmt, query->sql, query->bound_params == Qnil ? 0 : -1, &query->bound_params);
|
143
|
+
|
128
144
|
query->eof = 0;
|
145
|
+
query->should_reset = 0;
|
129
146
|
}
|
130
147
|
|
131
|
-
static inline void
|
148
|
+
static inline void query_bind(Query_t *query, int argc, VALUE * argv) {
|
132
149
|
if (!query->stmt)
|
133
150
|
prepare_single_stmt(DB_GVL_MODE(query), query->sqlite3_db, &query->stmt, query->sql);
|
134
|
-
|
135
|
-
|
136
|
-
|
151
|
+
else
|
152
|
+
// we call sqlite3_reset because that's what the SQLite API expects before
|
153
|
+
// changing the binding. See note at bottom of
|
154
|
+
// https://www.sqlite.org/c3ref/bind_blob.html
|
155
|
+
sqlite3_reset(query->stmt);
|
156
|
+
|
157
|
+
sqlite3_clear_bindings(query->stmt);
|
137
158
|
if (argc > 0) {
|
138
|
-
sqlite3_clear_bindings(query->stmt);
|
139
159
|
bind_all_parameters(query->stmt, argc, argv);
|
160
|
+
RB_OBJ_WRITE(query->self, &query->bound_params, rb_ary_new_from_values(argc, argv));
|
161
|
+
}
|
162
|
+
else {
|
163
|
+
RB_OBJ_WRITE(query->self, &query->bound_params, Qnil);
|
140
164
|
}
|
141
165
|
}
|
142
166
|
|
@@ -154,8 +178,7 @@ static inline void query_reset_and_bind(Query_t *query, int argc, VALUE * argv)
|
|
154
178
|
* @return [Extralite::Query] self
|
155
179
|
*/
|
156
180
|
VALUE Query_reset(VALUE self) {
|
157
|
-
Query_t *query =
|
158
|
-
if (query->closed) rb_raise(cError, "Query is closed");
|
181
|
+
Query_t *query = self_to_query_verify(self);
|
159
182
|
|
160
183
|
query_reset(query);
|
161
184
|
return self;
|
@@ -183,10 +206,10 @@ VALUE Query_reset(VALUE self) {
|
|
183
206
|
* @return [Extralite::Query] self
|
184
207
|
*/
|
185
208
|
VALUE Query_bind(int argc, VALUE *argv, VALUE self) {
|
186
|
-
Query_t *query =
|
187
|
-
if (query->closed) rb_raise(cError, "Query is closed");
|
209
|
+
Query_t *query = self_to_query_verify(self);
|
188
210
|
|
189
|
-
|
211
|
+
query_bind(query, argc, argv);
|
212
|
+
query->should_reset = 1;
|
190
213
|
return self;
|
191
214
|
}
|
192
215
|
|
@@ -195,8 +218,7 @@ VALUE Query_bind(int argc, VALUE *argv, VALUE self) {
|
|
195
218
|
* @return [boolean] true if iteration has reached the end of the result set
|
196
219
|
*/
|
197
220
|
VALUE Query_eof_p(VALUE self) {
|
198
|
-
Query_t *query =
|
199
|
-
if (query->closed) rb_raise(cError, "Query is closed");
|
221
|
+
Query_t *query = self_to_query_verify(self);
|
200
222
|
|
201
223
|
return query->eof ? Qtrue : Qfalse;
|
202
224
|
}
|
@@ -204,10 +226,9 @@ VALUE Query_eof_p(VALUE self) {
|
|
204
226
|
#define MAX_ROWS(max_rows) (max_rows == SINGLE_ROW ? 1 : max_rows)
|
205
227
|
|
206
228
|
static inline VALUE Query_perform_next(VALUE self, int max_rows, safe_query_impl call) {
|
207
|
-
Query_t *query =
|
208
|
-
if (query->closed) rb_raise(cError, "Query is closed");
|
229
|
+
Query_t *query = self_to_query_verify(self);
|
209
230
|
|
210
|
-
if (!query->stmt) query_reset(query);
|
231
|
+
if (!query->stmt || query->should_reset) query_reset(query);
|
211
232
|
if (query->eof) return rb_block_given_p() ? self : Qnil;
|
212
233
|
|
213
234
|
query_ctx ctx = QUERY_CTX(
|
@@ -258,7 +279,7 @@ inline safe_query_impl query_impl(enum query_mode query_mode) {
|
|
258
279
|
* @return [Array<any>, Extralite::Query] next rows or self if block is given
|
259
280
|
*/
|
260
281
|
VALUE Query_next(int argc, VALUE *argv, VALUE self) {
|
261
|
-
Query_t *query =
|
282
|
+
Query_t *query = self_to_query_verify(self);
|
262
283
|
rb_check_arity(argc, 0, 1);
|
263
284
|
return Query_perform_next(self, MAX_ROWS_FROM_ARGV(argc, argv), query_impl(query->query_mode));
|
264
285
|
}
|
@@ -269,7 +290,7 @@ VALUE Query_next(int argc, VALUE *argv, VALUE self) {
|
|
269
290
|
* @return [Array<any>] all rows
|
270
291
|
*/
|
271
292
|
VALUE Query_to_a(VALUE self) {
|
272
|
-
Query_t *query =
|
293
|
+
Query_t *query = self_to_query_verify(self);
|
273
294
|
query_reset(query);
|
274
295
|
return Query_perform_next(self, ALL_ROWS, query_impl(query->query_mode));
|
275
296
|
}
|
@@ -283,7 +304,7 @@ VALUE Query_to_a(VALUE self) {
|
|
283
304
|
VALUE Query_each(VALUE self) {
|
284
305
|
if (!rb_block_given_p()) return rb_funcall(cIterator, ID_new, 1, self);
|
285
306
|
|
286
|
-
Query_t *query =
|
307
|
+
Query_t *query = self_to_query_verify(self);
|
287
308
|
query_reset(query);
|
288
309
|
return Query_perform_next(self, ALL_ROWS, query_impl(query->query_mode));
|
289
310
|
}
|
@@ -312,8 +333,9 @@ VALUE Query_each(VALUE self) {
|
|
312
333
|
* query.execute(':bar' => 42)
|
313
334
|
*/
|
314
335
|
VALUE Query_execute(int argc, VALUE *argv, VALUE self) {
|
315
|
-
Query_t *query =
|
316
|
-
|
336
|
+
Query_t *query = self_to_query_verify(self);
|
337
|
+
query_bind(query, argc, argv);
|
338
|
+
query_reset(query);
|
317
339
|
return Query_perform_next(self, ALL_ROWS, safe_query_changes);
|
318
340
|
}
|
319
341
|
|
@@ -377,7 +399,7 @@ VALUE Query_execute_chevrons(VALUE self, VALUE params) {
|
|
377
399
|
* @return [Integer] number of changes effected
|
378
400
|
*/
|
379
401
|
VALUE Query_batch_execute(VALUE self, VALUE parameters) {
|
380
|
-
Query_t *query =
|
402
|
+
Query_t *query = self_to_query_verify(self);
|
381
403
|
if (query->closed) rb_raise(cError, "Query is closed");
|
382
404
|
|
383
405
|
if (!query->stmt)
|
@@ -426,7 +448,7 @@ VALUE Query_batch_execute(VALUE self, VALUE parameters) {
|
|
426
448
|
* @return [Array<Array>, Integer] Returned rows or total number of changes effected
|
427
449
|
*/
|
428
450
|
VALUE Query_batch_query(VALUE self, VALUE parameters) {
|
429
|
-
Query_t *query =
|
451
|
+
Query_t *query = self_to_query_verify(self);
|
430
452
|
if (query->closed) rb_raise(cError, "Query is closed");
|
431
453
|
|
432
454
|
if (!query->stmt)
|
@@ -472,7 +494,7 @@ VALUE Query_sql(VALUE self) {
|
|
472
494
|
* @return [Array<Symbol>] column names
|
473
495
|
*/
|
474
496
|
VALUE Query_columns(VALUE self) {
|
475
|
-
Query_t *query =
|
497
|
+
Query_t *query = self_to_query_verify(self);
|
476
498
|
query_reset(query);
|
477
499
|
return Query_perform_next(self, ALL_ROWS, safe_query_columns);
|
478
500
|
}
|
@@ -535,8 +557,7 @@ VALUE Query_status(int argc, VALUE* argv, VALUE self) {
|
|
535
557
|
|
536
558
|
rb_scan_args(argc, argv, "11", &op, &reset);
|
537
559
|
|
538
|
-
Query_t *query =
|
539
|
-
if (query->closed) rb_raise(cError, "Query is closed");
|
560
|
+
Query_t *query = self_to_query_verify(self);
|
540
561
|
|
541
562
|
if (!query->stmt)
|
542
563
|
prepare_single_stmt(DB_GVL_MODE(query), query->sqlite3_db, &query->stmt, query->sql);
|
@@ -560,7 +581,7 @@ VALUE Query_status(int argc, VALUE* argv, VALUE self) {
|
|
560
581
|
* @return [Extralite::Query] query
|
561
582
|
*/
|
562
583
|
VALUE Query_transform(VALUE self) {
|
563
|
-
Query_t *query =
|
584
|
+
Query_t *query = self_to_query_verify(self);
|
564
585
|
|
565
586
|
RB_OBJ_WRITE(self, &query->transform_proc, rb_block_given_p() ? rb_block_proc() : Qnil);
|
566
587
|
return self;
|
data/lib/extralite/version.rb
CHANGED
data/test/perf_array.rb
CHANGED
data/test/perf_hash.rb
CHANGED
data/test/perf_hash_prepared.rb
CHANGED
data/test/perf_polyphony.rb
CHANGED
@@ -20,7 +20,7 @@ $db2 = Extralite::Database.new(DB_PATH, gvl_release_threshold: 0)
|
|
20
20
|
$db3 = Extralite::Database.new(DB_PATH)
|
21
21
|
|
22
22
|
$snooze_count = 0
|
23
|
-
$db3.on_progress(
|
23
|
+
$db3.on_progress(tick: 50, period: 100, mode: :at_least_once) { $snooze_count += 1; snooze }
|
24
24
|
|
25
25
|
def prepare_database(count)
|
26
26
|
$db1.execute('create table if not exists foo ( a integer primary key, b text )')
|
data/test/perf_splat.rb
CHANGED
data/test/test_database.rb
CHANGED
@@ -1221,6 +1221,8 @@ class ScenarioTest < Minitest::Test
|
|
1221
1221
|
t2&.kill
|
1222
1222
|
end
|
1223
1223
|
|
1224
|
+
# Note: more comprehensive testing of the trace functionality is done in
|
1225
|
+
# test_trace.rb
|
1224
1226
|
def test_database_trace
|
1225
1227
|
sqls = []
|
1226
1228
|
@db.trace { |sql| sqls << sql }
|
@@ -1245,18 +1247,6 @@ class ScenarioTest < Minitest::Test
|
|
1245
1247
|
@db.query('select 4')
|
1246
1248
|
assert_equal ['select 1', 'select 2', 'select 3'], sqls
|
1247
1249
|
end
|
1248
|
-
|
1249
|
-
def test_database_trace_expanded_sql
|
1250
|
-
sqls = []
|
1251
|
-
@db.trace { |sql| sqls << sql }
|
1252
|
-
|
1253
|
-
@db.query('select ?, ?, ?', 1, '2', 3)
|
1254
|
-
assert_equal ["select 1, '2', 3"], sqls
|
1255
|
-
|
1256
|
-
sqls = []
|
1257
|
-
@db.query('select :x, :y, :z', x: 42, y: '43')
|
1258
|
-
assert_equal ["select 42, '43', NULL"], sqls
|
1259
|
-
end
|
1260
1250
|
end
|
1261
1251
|
|
1262
1252
|
class BackupTest < Minitest::Test
|
@@ -1337,7 +1327,7 @@ class ConcurrencyTest < Minitest::Test
|
|
1337
1327
|
t2.join
|
1338
1328
|
t1.join
|
1339
1329
|
|
1340
|
-
|
1330
|
+
assert_in_range 4.., delays.size
|
1341
1331
|
assert_equal 0, delays.select { |d| d > 0.15 }.size
|
1342
1332
|
ensure
|
1343
1333
|
t1&.kill
|
data/test/test_trace.rb
ADDED
@@ -0,0 +1,189 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'helper'
|
4
|
+
require 'date'
|
5
|
+
require 'json'
|
6
|
+
|
7
|
+
class DatabaseTraceTest < Minitest::Test
|
8
|
+
def setup
|
9
|
+
@db = Extralite::Database.new(':memory:')
|
10
|
+
|
11
|
+
@trace_buf = []
|
12
|
+
@db.trace { |sql, *args| @trace_buf << { sql: sql, args: args } }
|
13
|
+
end
|
14
|
+
|
15
|
+
def test_trace_database_query
|
16
|
+
@db.query('select 1')
|
17
|
+
@db.query('select ?', 2)
|
18
|
+
@db.query('select ?, ?, ?', 3, 4, 5)
|
19
|
+
@db.query('select :x, :y, :z', x: 6, y: 7)
|
20
|
+
|
21
|
+
assert_equal [
|
22
|
+
{ sql: 'select 1', args: [] },
|
23
|
+
{ sql: 'select ?', args: [2] },
|
24
|
+
{ sql: 'select ?, ?, ?', args: [3, 4, 5] },
|
25
|
+
{ sql: 'select :x, :y, :z', args: [{ x: 6, y: 7 }] }
|
26
|
+
], @trace_buf
|
27
|
+
end
|
28
|
+
|
29
|
+
def test_trace_database_execute
|
30
|
+
@db.execute('select 1')
|
31
|
+
@db.execute('select ?', 2)
|
32
|
+
@db.execute('select ?, ?, ?', 3, 4, 5)
|
33
|
+
@db.execute('select :x, :y, :z', x: 6, y: 7)
|
34
|
+
|
35
|
+
assert_equal [
|
36
|
+
{ sql: 'select 1', args: [] },
|
37
|
+
{ sql: 'select ?', args: [2] },
|
38
|
+
{ sql: 'select ?, ?, ?', args: [3, 4, 5] },
|
39
|
+
{ sql: 'select :x, :y, :z', args: [{ x: 6, y: 7 }] }
|
40
|
+
], @trace_buf
|
41
|
+
end
|
42
|
+
|
43
|
+
def test_trace_database_query_xxx
|
44
|
+
@db.query_single('select 1')
|
45
|
+
@db.query_array('select ?', 2)
|
46
|
+
@db.query_splat('select ?, ?, ?', 3, 4, 5)
|
47
|
+
@db.query_single_array('select :x, :y, :z', x: 6, y: 7)
|
48
|
+
|
49
|
+
assert_equal [
|
50
|
+
{ sql: 'select 1', args: [] },
|
51
|
+
{ sql: 'select ?', args: [2] },
|
52
|
+
{ sql: 'select ?, ?, ?', args: [3, 4, 5] },
|
53
|
+
{ sql: 'select :x, :y, :z', args: [{ x: 6, y: 7 }] }
|
54
|
+
], @trace_buf
|
55
|
+
end
|
56
|
+
|
57
|
+
def test_trace_database_batch_execute
|
58
|
+
@db.batch_execute('select ?', [1, 2, 3])
|
59
|
+
@db.batch_execute('select ?, ?', [[44, 55], [66, 77]])
|
60
|
+
@db.batch_execute('select :x, :y', [{ x: 5, y: 6 }, { x: 7, y: 8 }])
|
61
|
+
|
62
|
+
assert_equal [
|
63
|
+
{ sql: 'select ?', args: [1] },
|
64
|
+
{ sql: 'select ?', args: [2] },
|
65
|
+
{ sql: 'select ?', args: [3] },
|
66
|
+
{ sql: 'select ?, ?', args: [44, 55] },
|
67
|
+
{ sql: 'select ?, ?', args: [66, 77] },
|
68
|
+
{ sql: 'select :x, :y', args: [{ x: 5, y: 6 }] },
|
69
|
+
{ sql: 'select :x, :y', args: [{ x: 7, y: 8 }] }
|
70
|
+
], @trace_buf
|
71
|
+
end
|
72
|
+
|
73
|
+
def test_trace_database_batch_query
|
74
|
+
@db.batch_query('select ?', [1, 2, 3])
|
75
|
+
@db.batch_query('select ?, ?', [[44, 55], [66, 77]])
|
76
|
+
@db.batch_query('select :x, :y', [{ x: 5, y: 6 }, { x: 7, y: 8 }])
|
77
|
+
|
78
|
+
assert_equal [
|
79
|
+
{ sql: 'select ?', args: [1] },
|
80
|
+
{ sql: 'select ?', args: [2] },
|
81
|
+
{ sql: 'select ?', args: [3] },
|
82
|
+
{ sql: 'select ?, ?', args: [44, 55] },
|
83
|
+
{ sql: 'select ?, ?', args: [66, 77] },
|
84
|
+
{ sql: 'select :x, :y', args: [{ x: 5, y: 6 }] },
|
85
|
+
{ sql: 'select :x, :y', args: [{ x: 7, y: 8 }] }
|
86
|
+
], @trace_buf
|
87
|
+
end
|
88
|
+
|
89
|
+
def test_trace_database_batch_query_with_enumerable
|
90
|
+
@db.batch_query('select ?', 1..3)
|
91
|
+
|
92
|
+
assert_equal [
|
93
|
+
{ sql: 'select ?', args: [1] },
|
94
|
+
{ sql: 'select ?', args: [2] },
|
95
|
+
{ sql: 'select ?', args: [3] }
|
96
|
+
], @trace_buf
|
97
|
+
end
|
98
|
+
|
99
|
+
def test_trace_database_batch_query_with_proc
|
100
|
+
values = [4, 5, 6]
|
101
|
+
|
102
|
+
@db.batch_query('select ?', -> { values.shift })
|
103
|
+
|
104
|
+
assert_equal [
|
105
|
+
{ sql: 'select ?', args: [4] },
|
106
|
+
{ sql: 'select ?', args: [5] },
|
107
|
+
{ sql: 'select ?', args: [6] }
|
108
|
+
], @trace_buf
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
class QueryTraceTest < Minitest::Test
|
113
|
+
def setup
|
114
|
+
@db = Extralite::Database.new(':memory:')
|
115
|
+
|
116
|
+
@query = @db.prepare('select ?')
|
117
|
+
|
118
|
+
@trace_buf = []
|
119
|
+
@db.trace { |sql, *args| @trace_buf << { sql: sql, args: args } }
|
120
|
+
end
|
121
|
+
|
122
|
+
def test_trace_query_iteration
|
123
|
+
assert_equal [], @trace_buf
|
124
|
+
|
125
|
+
@query.bind(42)
|
126
|
+
assert_equal [], @trace_buf
|
127
|
+
|
128
|
+
@query.to_a
|
129
|
+
assert_equal [
|
130
|
+
{ sql: 'select ?', args: [42] },
|
131
|
+
], @trace_buf
|
132
|
+
|
133
|
+
@query.to_a
|
134
|
+
assert_equal [
|
135
|
+
{ sql: 'select ?', args: [42] },
|
136
|
+
{ sql: 'select ?', args: [42] }
|
137
|
+
], @trace_buf
|
138
|
+
|
139
|
+
@query.bind(44)
|
140
|
+
assert_equal [
|
141
|
+
{ sql: 'select ?', args: [42] },
|
142
|
+
{ sql: 'select ?', args: [42] }
|
143
|
+
], @trace_buf
|
144
|
+
|
145
|
+
@query.reset
|
146
|
+
assert_equal [
|
147
|
+
{ sql: 'select ?', args: [42] },
|
148
|
+
{ sql: 'select ?', args: [42] },
|
149
|
+
{ sql: 'select ?', args: [44] }
|
150
|
+
], @trace_buf
|
151
|
+
|
152
|
+
@trace_buf = []
|
153
|
+
@query.bind(46)
|
154
|
+
assert_equal [], @trace_buf
|
155
|
+
|
156
|
+
@query.each { }
|
157
|
+
assert_equal [
|
158
|
+
{ sql: 'select ?', args: [46] }
|
159
|
+
], @trace_buf
|
160
|
+
|
161
|
+
@trace_buf = []
|
162
|
+
@query.bind(48)
|
163
|
+
assert_equal [], @trace_buf
|
164
|
+
|
165
|
+
e = @query.each
|
166
|
+
e.to_a
|
167
|
+
assert_equal [
|
168
|
+
{ sql: 'select ?', args: [48] }
|
169
|
+
], @trace_buf
|
170
|
+
|
171
|
+
e.map {}
|
172
|
+
assert_equal [
|
173
|
+
{ sql: 'select ?', args: [48] },
|
174
|
+
{ sql: 'select ?', args: [48] }
|
175
|
+
], @trace_buf
|
176
|
+
end
|
177
|
+
|
178
|
+
def test_trace_query_batch_execute
|
179
|
+
assert_equal [], @trace_buf
|
180
|
+
|
181
|
+
@query.batch_execute([42, 43, 44])
|
182
|
+
assert_equal [
|
183
|
+
{ sql: 'select ?', args: [42] },
|
184
|
+
{ sql: 'select ?', args: [43] },
|
185
|
+
{ sql: 'select ?', args: [44] },
|
186
|
+
], @trace_buf
|
187
|
+
|
188
|
+
end
|
189
|
+
end
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: extralite-bundle
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: '2.
|
4
|
+
version: '2.12'
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Sharon Rosner
|
8
8
|
bindir: bin
|
9
9
|
cert_chain: []
|
10
|
-
date: 2025-03-
|
10
|
+
date: 2025-03-26 00:00:00.000000000 Z
|
11
11
|
dependencies:
|
12
12
|
- !ruby/object:Gem::Dependency
|
13
13
|
name: rake-compiler
|
@@ -146,6 +146,7 @@ files:
|
|
146
146
|
- test/test_iterator.rb
|
147
147
|
- test/test_query.rb
|
148
148
|
- test/test_sequel.rb
|
149
|
+
- test/test_trace.rb
|
149
150
|
homepage: https://github.com/digital-fabric/extralite
|
150
151
|
licenses:
|
151
152
|
- MIT
|