extralite 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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 99b4c44cc4b1661709179ecba90e49a459b76167a7aba1bea6eccb41e58305af
4
- data.tar.gz: 56e8c49fe7f3f65285a1a89e1f435a1884e9d5a2af84e7caec5a4b635e70e345
3
+ metadata.gz: 6c7b8e712ebddb0de0f3ba623102fafb596f6396c79ed7d7aff5bb86a6781e60
4
+ data.tar.gz: e0e204499dd8fad21554f115ba8afeb53995ebf5175dc67eb420a6a1120ac030
5
5
  SHA512:
6
- metadata.gz: b635937a6454102656f00fcae58c843263c39df38d445026888ec170984e483b08810f4a7418756c629cfff861d3fe35c77032f9dde67ed7f1d8ea4fb95df54b
7
- data.tar.gz: b317e0a21b540558c595a129a5086862d3d9729349b27d337b2cfab086395299aec1217eb1a5099cdb65d8653600c4d023b4205e50ea2c4c3fb55f9ca2c02cd6
6
+ metadata.gz: dc879428575151f1194208b3929f9f7aeea79e843a024eb1688a149b9f298992d3004da24eaa32bfc16f2785ebc8153ef1f7159a0016560c3e27e128dd25ca00
7
+ data.tar.gz: a3965148daaf04792c5584aba4fd51b7d2e19bd138c677ab91691390182c8d1b0f7f24c2691c3316ba5a765e243a9258684cc4add69acc71a392355ffa1090fa
data/CHANGELOG.md CHANGED
@@ -1,3 +1,7 @@
1
+ ## 2.12 2025-03-25
2
+
3
+ - Reimplement trace, provide trace proc with bound parameters [#80](https://github.com/digital-fabric/extralite/issues/80)
4
+
1
5
  ## 2.11 2025-03-14
2
6
 
3
7
  - Remove support for Ruby versions older than 3.2.
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 14X the performance of the
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| puts sql: 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 ~11 times faster than `sqlite3` when fetching a
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 1.7.0|Extralite 2.5|Advantage|
1259
+ |Row count|sqlite3 2.6.0|Extralite 2.12|Advantage|
1252
1260
  |-:|-:|-:|-:|
1253
- |10|184.9K rows/s|473.2K rows/s|__2.56x__|
1254
- |1K|290.5K rows/s|2320.7K rows/s|__7.98x__|
1255
- |100K|143.0K rows/s|2061.3K rows/s|__14.41x__|
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 1.7.0|Extralite 2.5|Advantage|
1270
+ |Row count|sqlite3 2.6.0|Extralite 2.12|Advantage|
1263
1271
  |-:|-:|-:|-:|
1264
- |10|278.0K rows/s|493.6K rows/s|__1.78x__|
1265
- |1K|608.6K rows/s|2692.2K rows/s|__4.42x__|
1266
- |100K|502.9K rows/s|2564.0K rows/s|__5.10x__|
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 1.7.0|Extralite 2.5|Advantage|
1281
+ |Row count|sqlite3 2.6.0|Extralite 2.12|Advantage|
1274
1282
  |-:|-:|-:|-:|
1275
- |10|228.5K rows/s|707.9K rows/s|__3.10x__|
1276
- |1K|296.5K rows/s|2396.2K rows/s|__8.08x__|
1277
- |100K|145.9K rows/s|2107.3K rows/s|__14.45x__|
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 2.7M rows per
1280
- second, and can be more than 14 times faster than the `sqlite3` gem.
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
@@ -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 = rb_funcall(ctx->transform_proc, ID_call, 1, 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 = rb_funcall2(ctx->transform_proc, ID_call, column_count, argv_values); \
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 = rb_funcall(ctx->transform_proc, ID_call, 1, 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 = rb_funcall(ctx->transform_proc, ID_call, 1, 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 = rb_funcall(ctx->transform_proc, ID_call, 1, 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 = rb_funcall(ctx->transform_proc, ID_call, 1, 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 = rb_funcall(ctx->transform_proc, ID_call, 1, 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, &params);
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
- Database_issue_query(ctx->db, ctx->stmt);
633
- bind_all_parameters_from_object(ctx->stmt, RARRAY_AREF(ctx->params, i));
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 = rb_funcall(ctx->params, ID_call, 0);
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);
@@ -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
- rb_funcall(db->progress_handler.proc, ID_call, 0);
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
- rb_funcall(db->progress_handler.proc, ID_call, 1, Qtrue);
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
- Database_issue_query(db, stmt);
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
- inline void Database_issue_query(Database_t *db, sqlite3_stmt *stmt) {
1108
- if (db->trace_proc != Qnil) {
1109
- VALUE sql = rb_str_new_cstr(sqlite3_expanded_sql(stmt));
1110
- rb_funcall(db->trace_proc, ID_call, 1, sql);
1111
- RB_GC_GUARD(sql);
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
- rb_funcall(db->progress_handler.proc, ID_call, 0);
1146
+ INVOKE_PROC(db->progress_handler.proc, 0, NULL);
1117
1147
  default:
1118
1148
  ; // do nothing
1119
1149
 
@@ -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 Database_issue_query(Database_t *db, sqlite3_stmt *stmt);
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);
@@ -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
- Database_issue_query(query->db_struct, query->stmt);
127
- sqlite3_reset(query->stmt);
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 query_reset_and_bind(Query_t *query, int argc, VALUE * argv) {
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
- Database_issue_query(query->db_struct, query->stmt);
135
- sqlite3_reset(query->stmt);
136
- query->eof = 0;
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 = self_to_query(self);
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 = self_to_query(self);
187
- if (query->closed) rb_raise(cError, "Query is closed");
209
+ Query_t *query = self_to_query_verify(self);
188
210
 
189
- query_reset_and_bind(query, argc, argv);
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 = self_to_query(self);
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 = self_to_query(self);
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 = self_to_query(self);
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 = self_to_query(self);
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 = self_to_query(self);
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 = self_to_query(self);
316
- query_reset_and_bind(query, argc, argv);
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 = self_to_query(self);
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 = self_to_query(self);
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 = self_to_query(self);
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 = self_to_query(self);
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 = self_to_query(self);
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;
@@ -1,4 +1,4 @@
1
1
  module Extralite
2
2
  # Extralite version
3
- VERSION = '2.11'
3
+ VERSION = '2.12'
4
4
  end
data/test/perf_array.rb CHANGED
@@ -4,7 +4,7 @@ require 'bundler/inline'
4
4
 
5
5
  gemfile do
6
6
  source 'https://rubygems.org'
7
- gem 'sqlite3'
7
+ gem 'sqlite3', '2.6.0'
8
8
  gem 'extralite', path: '..'
9
9
  gem 'benchmark-ips'
10
10
  end
data/test/perf_hash.rb CHANGED
@@ -7,7 +7,7 @@ require 'bundler/inline'
7
7
  gemfile do
8
8
  source 'https://rubygems.org'
9
9
  gem 'extralite', path: '..'
10
- gem 'sqlite3'
10
+ gem 'sqlite3', '2.6.0'
11
11
  gem 'benchmark-ips'
12
12
  end
13
13
 
@@ -5,7 +5,7 @@ require 'bundler/inline'
5
5
  gemfile do
6
6
  source 'https://rubygems.org'
7
7
  gem 'extralite', path: '..'
8
- gem 'sqlite3'
8
+ gem 'sqlite3', '2.6.0'
9
9
  gem 'benchmark-ips'
10
10
  end
11
11
 
@@ -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(25) { $snooze_count += 1; snooze }
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
@@ -4,7 +4,7 @@ require 'bundler/inline'
4
4
 
5
5
  gemfile do
6
6
  source 'https://rubygems.org'
7
- gem 'sqlite3'
7
+ gem 'sqlite3', '2.6.0'
8
8
  gem 'extralite', path: '..'
9
9
  gem 'benchmark-ips'
10
10
  end
@@ -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
- assert delays.size > 4
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
@@ -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
3
3
  version: !ruby/object:Gem::Version
4
- version: '2.11'
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-14 00:00:00.000000000 Z
10
+ date: 2025-03-26 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: rake-compiler
@@ -144,6 +144,7 @@ files:
144
144
  - test/test_iterator.rb
145
145
  - test/test_query.rb
146
146
  - test/test_sequel.rb
147
+ - test/test_trace.rb
147
148
  homepage: https://github.com/digital-fabric/extralite
148
149
  licenses:
149
150
  - MIT