extralite-bundle 2.5 → 2.7

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.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/CHANGELOG.md +34 -13
  4. data/Gemfile +4 -0
  5. data/Gemfile-bundle +1 -1
  6. data/LICENSE +1 -1
  7. data/README.md +1059 -247
  8. data/Rakefile +18 -0
  9. data/TODO.md +0 -7
  10. data/examples/kv_store.rb +49 -0
  11. data/examples/multi_fiber.rb +16 -0
  12. data/examples/on_progress.rb +9 -0
  13. data/examples/pubsub_store_polyphony.rb +194 -0
  14. data/examples/pubsub_store_threads.rb +204 -0
  15. data/ext/extralite/changeset.c +463 -0
  16. data/ext/extralite/common.c +177 -91
  17. data/ext/extralite/database.c +745 -276
  18. data/ext/extralite/extconf-bundle.rb +10 -4
  19. data/ext/extralite/extconf.rb +34 -34
  20. data/ext/extralite/extralite.h +104 -47
  21. data/ext/extralite/extralite_ext.c +6 -0
  22. data/ext/extralite/iterator.c +14 -86
  23. data/ext/extralite/query.c +171 -264
  24. data/extralite-bundle.gemspec +1 -1
  25. data/extralite.gemspec +1 -1
  26. data/gemspec.rb +10 -11
  27. data/lib/extralite/version.rb +1 -1
  28. data/lib/extralite.rb +69 -10
  29. data/lib/sequel/adapters/extralite.rb +1 -1
  30. data/test/helper.rb +9 -1
  31. data/test/perf_argv_transform.rb +74 -0
  32. data/test/perf_ary.rb +14 -12
  33. data/test/perf_hash.rb +17 -15
  34. data/test/perf_hash_prepared.rb +58 -0
  35. data/test/perf_hash_transform.rb +66 -0
  36. data/test/perf_polyphony.rb +74 -0
  37. data/test/test_changeset.rb +161 -0
  38. data/test/test_database.rb +720 -104
  39. data/test/test_extralite.rb +2 -2
  40. data/test/test_iterator.rb +28 -13
  41. data/test/test_query.rb +352 -110
  42. data/test/test_sequel.rb +4 -4
  43. metadata +24 -16
  44. data/Gemfile.lock +0 -37
  45. data/test/perf_prepared.rb +0 -64
@@ -14,6 +14,12 @@ VALUE cQuery;
14
14
  ID ID_inspect;
15
15
  ID ID_slice;
16
16
 
17
+ VALUE SYM_hash;
18
+ VALUE SYM_argv;
19
+ VALUE SYM_ary;
20
+
21
+ #define DB_GVL_MODE(query) Database_prepare_gvl_mode(query->db_struct)
22
+
17
23
  static size_t Query_size(const void *ptr) {
18
24
  return sizeof(Query_t);
19
25
  }
@@ -22,12 +28,14 @@ static void Query_mark(void *ptr) {
22
28
  Query_t *query = ptr;
23
29
  rb_gc_mark_movable(query->db);
24
30
  rb_gc_mark_movable(query->sql);
31
+ rb_gc_mark_movable(query->transform_proc);
25
32
  }
26
33
 
27
34
  static void Query_compact(void *ptr) {
28
35
  Query_t *query = ptr;
29
36
  query->db = rb_gc_location(query->db);
30
37
  query->sql = rb_gc_location(query->sql);
38
+ query->transform_proc = rb_gc_location(query->transform_proc);
31
39
  }
32
40
 
33
41
  static void Query_free(void *ptr) {
@@ -46,6 +54,7 @@ static VALUE Query_allocate(VALUE klass) {
46
54
  Query_t *query = ALLOC(Query_t);
47
55
  query->db = Qnil;
48
56
  query->sql = Qnil;
57
+ query->transform_proc = Qnil;
49
58
  query->sqlite3_db = NULL;
50
59
  query->stmt = NULL;
51
60
  return TypedData_Wrap_Struct(klass, &Query_type, query);
@@ -57,6 +66,27 @@ static inline Query_t *self_to_query(VALUE obj) {
57
66
  return query;
58
67
  }
59
68
 
69
+ static inline enum query_mode symbol_to_query_mode(VALUE sym) {
70
+ if (sym == SYM_hash) return QUERY_HASH;
71
+ if (sym == SYM_argv) return QUERY_ARGV;
72
+ if (sym == SYM_ary) return QUERY_ARY;
73
+
74
+ rb_raise(cError, "Invalid query mode");
75
+ }
76
+
77
+ static inline VALUE query_mode_to_symbol(enum query_mode query_mode) {
78
+ switch (query_mode) {
79
+ case QUERY_HASH:
80
+ return SYM_hash;
81
+ case QUERY_ARGV:
82
+ return SYM_argv;
83
+ case QUERY_ARY:
84
+ return SYM_ary;
85
+ default:
86
+ rb_raise(cError, "Invalid mode");
87
+ }
88
+ }
89
+
60
90
  /* Initializes a new prepared query with the given database and SQL string. A
61
91
  * `Query` is normally instantiated by calling `Database#prepare`:
62
92
  *
@@ -64,9 +94,10 @@ static inline Query_t *self_to_query(VALUE obj) {
64
94
  *
65
95
  * @param db [Extralite::Database] associated database
66
96
  * @param sql [String] SQL string
97
+ * @param mode [Symbol] query mode
67
98
  * @return [void]
68
99
  */
69
- VALUE Query_initialize(VALUE self, VALUE db, VALUE sql) {
100
+ VALUE Query_initialize(VALUE self, VALUE db, VALUE sql, VALUE mode) {
70
101
  Query_t *query = self_to_query(self);
71
102
 
72
103
  sql = rb_funcall(sql, ID_strip, 0);
@@ -75,6 +106,8 @@ VALUE Query_initialize(VALUE self, VALUE db, VALUE sql) {
75
106
 
76
107
  RB_OBJ_WRITE(self, &query->db, db);
77
108
  RB_OBJ_WRITE(self, &query->sql, sql);
109
+ if (rb_block_given_p())
110
+ RB_OBJ_WRITE(self, &query->transform_proc, rb_block_proc());
78
111
 
79
112
  query->db = db;
80
113
  query->db_struct = self_to_database(db);
@@ -82,26 +115,23 @@ VALUE Query_initialize(VALUE self, VALUE db, VALUE sql) {
82
115
  query->stmt = NULL;
83
116
  query->closed = 0;
84
117
  query->eof = 0;
118
+ query->query_mode = symbol_to_query_mode(mode);
85
119
 
86
120
  return Qnil;
87
121
  }
88
122
 
89
123
  static inline void query_reset(Query_t *query) {
90
124
  if (!query->stmt)
91
- prepare_single_stmt(query->sqlite3_db, &query->stmt, query->sql);
92
- if (query->db_struct->trace_block != Qnil)
93
- rb_funcall(query->db_struct->trace_block, ID_call, 1, query->sql);
125
+ prepare_single_stmt(DB_GVL_MODE(query), query->sqlite3_db, &query->stmt, query->sql);
126
+ Database_issue_query(query->db_struct, query->sql);
94
127
  sqlite3_reset(query->stmt);
95
128
  query->eof = 0;
96
129
  }
97
130
 
98
131
  static inline void query_reset_and_bind(Query_t *query, int argc, VALUE * argv) {
99
132
  if (!query->stmt)
100
- prepare_single_stmt(query->sqlite3_db, &query->stmt, query->sql);
101
-
102
- if (query->db_struct->trace_block != Qnil)
103
- rb_funcall(query->db_struct->trace_block, ID_call, 1, query->sql);
104
-
133
+ prepare_single_stmt(DB_GVL_MODE(query), query->sqlite3_db, &query->stmt, query->sql);
134
+ Database_issue_query(query->db_struct, query->sql);
105
135
  sqlite3_reset(query->stmt);
106
136
  query->eof = 0;
107
137
  if (argc > 0) {
@@ -128,9 +158,6 @@ VALUE Query_reset(VALUE self) {
128
158
  if (query->closed) rb_raise(cError, "Query is closed");
129
159
 
130
160
  query_reset(query);
131
- if (query->db_struct->trace_block != Qnil)
132
- rb_funcall(query->db_struct->trace_block, ID_call, 1, query->sql);
133
-
134
161
  return self;
135
162
  }
136
163
 
@@ -176,7 +203,7 @@ VALUE Query_eof_p(VALUE self) {
176
203
 
177
204
  #define MAX_ROWS(max_rows) (max_rows == SINGLE_ROW ? 1 : max_rows)
178
205
 
179
- static inline VALUE Query_perform_next(VALUE self, int max_rows, VALUE (*call)(query_ctx *)) {
206
+ static inline VALUE Query_perform_next(VALUE self, int max_rows, safe_query_impl call) {
180
207
  Query_t *query = self_to_query(self);
181
208
  if (query->closed) rb_raise(cError, "Query is closed");
182
209
 
@@ -185,69 +212,37 @@ static inline VALUE Query_perform_next(VALUE self, int max_rows, VALUE (*call)(q
185
212
 
186
213
  query_ctx ctx = QUERY_CTX(
187
214
  self,
215
+ query->sql,
188
216
  query->db_struct,
189
217
  query->stmt,
190
218
  Qnil,
191
- QUERY_MODE(max_rows == SINGLE_ROW ? QUERY_SINGLE_ROW : QUERY_MULTI_ROW),
219
+ query->transform_proc,
220
+ query->query_mode,
221
+ ROW_YIELD_OR_MODE(max_rows == SINGLE_ROW ? ROW_SINGLE : ROW_MULTI),
192
222
  MAX_ROWS(max_rows)
193
223
  );
194
224
  VALUE result = call(&ctx);
195
225
  query->eof = ctx.eof;
196
- return (ctx.mode == QUERY_YIELD) ? self : result;
226
+ return (ctx.row_mode == ROW_YIELD) ? self : result;
197
227
  }
198
228
 
199
229
  #define MAX_ROWS_FROM_ARGV(argc, argv) (argc == 1 ? FIX2INT(argv[0]) : SINGLE_ROW)
200
230
 
201
- /* Returns the next 1 or more rows from the associated query's result set as a
202
- * hash.
203
- *
204
- * If no row count is given, a single row is returned. If a row count is given,
205
- * an array containing up to the `row_count` rows is returned. If `row_count` is
206
- * -1, all rows are returned. If the end of the result set has been reached,
207
- * `nil` is returned.
208
- *
209
- * If a block is given, rows are passed to the block and self is returned.
210
- *
211
- * @overload next()
212
- * @return [Hash, Extralite::Query] next row or self if block is given
213
- * @overload next_hash()
214
- * @return [Hash, Extralite::Query] next row or self if block is given
215
- * @overload next(row_count)
216
- * @param row_count [Integer] maximum row count or -1 for all rows
217
- * @return [Array<Hash>, Extralite::Query] next rows or self if block is given
218
- * @overload next_hash(row_count)
219
- * @param row_count [Integer] maximum row count or -1 for all rows
220
- * @return [Array<Hash>, Extralite::Query] next rows or self if block is given
221
- */
222
- VALUE Query_next_hash(int argc, VALUE *argv, VALUE self) {
223
- rb_check_arity(argc, 0, 1);
224
- return Query_perform_next(self, MAX_ROWS_FROM_ARGV(argc, argv), safe_query_hash);
225
- }
226
-
227
- /* Returns the next 1 or more rows from the associated query's result set as an
228
- * array.
229
- *
230
- * If no row count is given, a single row is returned. If a row count is given,
231
- * an array containing up to the `row_count` rows is returned. If `row_count` is
232
- * -1, all rows are returned. If the end of the result set has been reached,
233
- * `nil` is returned.
234
- *
235
- * If a block is given, rows are passed to the block and self is returned.
236
- *
237
- * @overload next_ary()
238
- * @return [Array, Extralite::Query] next row or self if block is given
239
- * @overload next_ary(row_count)
240
- * @param row_count [Integer] maximum row count or -1 for all rows
241
- * @return [Array<Array>, Extralite::Query] next rows or self if block is given
242
- */
243
- VALUE Query_next_ary(int argc, VALUE *argv, VALUE self) {
244
- rb_check_arity(argc, 0, 1);
245
- return Query_perform_next(self, MAX_ROWS_FROM_ARGV(argc, argv), safe_query_ary);
231
+ inline safe_query_impl query_impl(enum query_mode query_mode) {
232
+ switch (query_mode) {
233
+ case QUERY_HASH:
234
+ return safe_query_hash;
235
+ case QUERY_ARGV:
236
+ return safe_query_argv;
237
+ case QUERY_ARY:
238
+ return safe_query_ary;
239
+ default:
240
+ rb_raise(cError, "Invalid query mode (query_impl)");
241
+ }
246
242
  }
247
243
 
248
- /* Returns the next 1 or more rows from the associated query's result set as an
249
- * single values. If the result set contains more than one column an error is
250
- * raised.
244
+ /* Returns the next 1 or more rows from the associated query's result set. The
245
+ * row value is returned according to the query mode and the query transform.
251
246
  *
252
247
  * If no row count is given, a single row is returned. If a row count is given,
253
248
  * an array containing up to the `row_count` rows is returned. If `row_count` is
@@ -256,92 +251,41 @@ VALUE Query_next_ary(int argc, VALUE *argv, VALUE self) {
256
251
  *
257
252
  * If a block is given, rows are passed to the block and self is returned.
258
253
  *
259
- * @overload next_ary()
260
- * @return [Object, Extralite::Query] next row or self if block is given
261
- * @overload next_ary(row_count)
254
+ * @overload next()
255
+ * @return [any, Extralite::Query] next row or self if block is given
256
+ * @overload next(row_count)
262
257
  * @param row_count [Integer] maximum row count or -1 for all rows
263
- * @return [Array<Object>, Extralite::Query] next rows or self if block is given
264
- */
265
- VALUE Query_next_single_column(int argc, VALUE *argv, VALUE self) {
266
- rb_check_arity(argc, 0, 1);
267
- return Query_perform_next(self, MAX_ROWS_FROM_ARGV(argc, argv), safe_query_single_column);
268
- }
269
-
270
- /* Returns all rows in the associated query's result set as hashes.
271
- *
272
- * @overload to_a()
273
- * @return [Array<Hash>] all rows
274
- * @overload to_a_hash
275
- * @return [Array<Hash>] all rows
276
- */
277
- VALUE Query_to_a_hash(VALUE self) {
278
- Query_t *query = self_to_query(self);
279
- query_reset(query);
280
- return Query_perform_next(self, ALL_ROWS, safe_query_hash);
281
- }
282
-
283
- /* Returns all rows in the associated query's result set as arrays.
284
- *
285
- * @return [Array<Array>] all rows
286
- */
287
- VALUE Query_to_a_ary(VALUE self) {
288
- Query_t *query = self_to_query(self);
289
- query_reset(query);
290
- return Query_perform_next(self, ALL_ROWS, safe_query_ary);
291
- }
292
-
293
- /* Returns all rows in the associated query's result set as single values. If
294
- * the result set contains more than one column an error is raised.
295
- *
296
- * @return [Array<Object>] all rows
258
+ * @return [Array<any>, Extralite::Query] next rows or self if block is given
297
259
  */
298
- VALUE Query_to_a_single_column(VALUE self) {
260
+ VALUE Query_next(int argc, VALUE *argv, VALUE self) {
299
261
  Query_t *query = self_to_query(self);
300
- query_reset(query);
301
- return Query_perform_next(self, ALL_ROWS, safe_query_single_column);
262
+ rb_check_arity(argc, 0, 1);
263
+ return Query_perform_next(self, MAX_ROWS_FROM_ARGV(argc, argv), query_impl(query->query_mode));
302
264
  }
303
265
 
304
- /* Iterates through the result set, passing each row to the given block as a
305
- * hash. If no block is given, returns a `Extralite::Iterator` instance in hash
306
- * mode.
266
+ /* Returns all rows in the associated query's result set. Rows are returned
267
+ * according to the query mode and the query transform.
307
268
  *
308
- * @return [Extralite::Query, Extralite::Iterator] self or an iterator if no block is given
269
+ * @return [Array<any>] all rows
309
270
  */
310
- VALUE Query_each_hash(VALUE self) {
311
- if (!rb_block_given_p()) return rb_funcall(cIterator, ID_new, 2, self, SYM_hash);
312
-
271
+ VALUE Query_to_a(VALUE self) {
313
272
  Query_t *query = self_to_query(self);
314
273
  query_reset(query);
315
- return Query_perform_next(self, ALL_ROWS, safe_query_hash);
274
+ return Query_perform_next(self, ALL_ROWS, query_impl(query->query_mode));
316
275
  }
317
276
 
318
- /* Iterates through the result set, passing each row to the given block as an
319
- * array. If no block is given, returns a `Extralite::Iterator` instance in
320
- * array mode.
277
+ /* Iterates through the result set, passing each row to the given block. If no
278
+ * block is given, returns a `Extralite::Iterator` instance. Rows are given to
279
+ * the block according to the query mode and the query transform.
321
280
  *
322
281
  * @return [Extralite::Query, Extralite::Iterator] self or an iterator if no block is given
323
282
  */
324
- VALUE Query_each_ary(VALUE self) {
325
- if (!rb_block_given_p()) return rb_funcall(cIterator, ID_new, 2, self, SYM_ary);
283
+ VALUE Query_each(VALUE self) {
284
+ if (!rb_block_given_p()) return rb_funcall(cIterator, ID_new, 1, self);
326
285
 
327
286
  Query_t *query = self_to_query(self);
328
287
  query_reset(query);
329
- return Query_perform_next(self, ALL_ROWS, safe_query_ary);
330
- }
331
-
332
- /* Iterates through the result set, passing each row to the given block as a
333
- * single value. If the result set contains more than one column an error is
334
- * raised. If no block is given, returns a `Extralite::Iterator` instance in
335
- * single column mode.
336
- *
337
- * @return [Extralite::Query, Extralite::Iterator] self or an iterator if no block is given
338
- */
339
- VALUE Query_each_single_column(VALUE self) {
340
- if (!rb_block_given_p()) return rb_funcall(cIterator, ID_new, 2, self, SYM_single_column);
341
-
342
- Query_t *query = self_to_query(self);
343
- query_reset(query);
344
- return Query_perform_next(self, ALL_ROWS, safe_query_single_column);
288
+ return Query_perform_next(self, ALL_ROWS, query_impl(query->query_mode));
345
289
  }
346
290
 
347
291
  /* call-seq:
@@ -437,14 +381,17 @@ VALUE Query_batch_execute(VALUE self, VALUE parameters) {
437
381
  if (query->closed) rb_raise(cError, "Query is closed");
438
382
 
439
383
  if (!query->stmt)
440
- prepare_single_stmt(query->sqlite3_db, &query->stmt, query->sql);
384
+ prepare_single_stmt(DB_GVL_MODE(query), query->sqlite3_db, &query->stmt, query->sql);
441
385
 
442
386
  query_ctx ctx = QUERY_CTX(
443
387
  self,
388
+ query->sql,
444
389
  query->db_struct,
445
390
  query->stmt,
446
391
  parameters,
447
- QUERY_MODE(QUERY_MULTI_ROW),
392
+ Qnil,
393
+ QUERY_HASH,
394
+ ROW_YIELD_OR_MODE(ROW_MULTI),
448
395
  ALL_ROWS
449
396
  );
450
397
  return safe_batch_execute(&ctx);
@@ -463,6 +410,8 @@ VALUE Query_batch_execute(VALUE self, VALUE parameters) {
463
410
  * invocation of the query, and the total number of changes is returned.
464
411
  * Otherwise, an array containing the resulting rows for each invocation is
465
412
  * returned.
413
+ *
414
+ * Rows are returned according to the query mode and transform.
466
415
  *
467
416
  * q = db.prepare('insert into foo values (?, ?) returning bar, baz')
468
417
  * records = [
@@ -474,114 +423,29 @@ VALUE Query_batch_execute(VALUE self, VALUE parameters) {
474
423
  * *
475
424
  * @param sql [String] query SQL
476
425
  * @param parameters [Array<Array, Hash>, Enumerable, Enumerator, Callable] parameters to run query with
477
- * @return [Array<Hash>, Integer] Total number of changes effected
426
+ * @return [Array<Array>, Integer] Returned rows or total number of changes effected
478
427
  */
479
428
  VALUE Query_batch_query(VALUE self, VALUE parameters) {
480
429
  Query_t *query = self_to_query(self);
481
430
  if (query->closed) rb_raise(cError, "Query is closed");
482
431
 
483
432
  if (!query->stmt)
484
- prepare_single_stmt(query->sqlite3_db, &query->stmt, query->sql);
433
+ prepare_single_stmt(DB_GVL_MODE(query), query->sqlite3_db, &query->stmt, query->sql);
485
434
 
486
435
  query_ctx ctx = QUERY_CTX(
487
436
  self,
437
+ query->sql,
488
438
  query->db_struct,
489
439
  query->stmt,
490
440
  parameters,
491
- QUERY_MODE(QUERY_MULTI_ROW),
441
+ query->transform_proc,
442
+ query->query_mode,
443
+ ROW_YIELD_OR_MODE(ROW_MULTI),
492
444
  ALL_ROWS
493
445
  );
494
446
  return safe_batch_query(&ctx);
495
447
  }
496
448
 
497
- /* call-seq:
498
- * query.batch_query_ary(sql, params_array) -> rows
499
- * query.batch_query_ary(sql, enumerable) -> rows
500
- * query.batch_query_ary(sql, callable) -> rows
501
- * query.batch_query_ary(sql, params_array) { |rows| ... } -> changes
502
- * query.batch_query_ary(sql, enumerable) { |rows| ... } -> changes
503
- * query.batch_query_ary(sql, callable) { |rows| ... } -> changes
504
- *
505
- * Executes the prepared query for each list of parameters in the given paramter
506
- * source. If a block is given, it is called with the resulting rows for each
507
- * invocation of the query, and the total number of changes is returned.
508
- * Otherwise, an array containing the resulting rows for each invocation is
509
- * returned. Rows are represented as arrays.
510
- *
511
- * q = db.prepare('insert into foo values (?, ?) returning bar, baz')
512
- * records = [
513
- * [1, 2],
514
- * [3, 4]
515
- * ]
516
- * q.batch_query_ary(records)
517
- * #=> [{ bar: 1, baz: 2 }, { bar: 3, baz: 4}]
518
- * *
519
- * @param sql [String] query SQL
520
- * @param parameters [Array<Array, Hash>, Enumerable, Enumerator, Callable] parameters to run query with
521
- * @return [Array<Hash>, Integer] Total number of changes effected
522
- */
523
- VALUE Query_batch_query_ary(VALUE self, VALUE parameters) {
524
- Query_t *query = self_to_query(self);
525
- if (query->closed) rb_raise(cError, "Query is closed");
526
-
527
- if (!query->stmt)
528
- prepare_single_stmt(query->sqlite3_db, &query->stmt, query->sql);
529
-
530
- query_ctx ctx = QUERY_CTX(
531
- self,
532
- query->db_struct,
533
- query->stmt,
534
- parameters,
535
- QUERY_MODE(QUERY_MULTI_ROW),
536
- ALL_ROWS
537
- );
538
- return safe_batch_query_ary(&ctx);
539
- }
540
-
541
- /* call-seq:
542
- * query.batch_query_single_column(sql, params_array) -> rows
543
- * query.batch_query_single_column(sql, enumerable) -> rows
544
- * query.batch_query_single_column(sql, callable) -> rows
545
- * query.batch_query_single_column(sql, params_array) { |rows| ... } -> changes
546
- * query.batch_query_single_column(sql, enumerable) { |rows| ... } -> changes
547
- * query.batch_query_single_column(sql, callable) { |rows| ... } -> changes
548
- *
549
- * Executes the prepared query for each list of parameters in the given paramter
550
- * source. If a block is given, it is called with the resulting rows for each
551
- * invocation of the query, and the total number of changes is returned.
552
- * Otherwise, an array containing the resulting rows for each invocation is
553
- * returned. Rows are represented as single values.
554
- *
555
- * q = db.prepare('insert into foo values (?, ?) returning bar, baz')
556
- * records = [
557
- * [1, 2],
558
- * [3, 4]
559
- * ]
560
- * q.batch_query_single_column(records)
561
- * #=> [{ bar: 1, baz: 2 }, { bar: 3, baz: 4}]
562
- * *
563
- * @param sql [String] query SQL
564
- * @param parameters [Array<Array, Hash>, Enumerable, Enumerator, Callable] parameters to run query with
565
- * @return [Array<Hash>, Integer] Total number of changes effected
566
- */
567
- VALUE Query_batch_query_single_column(VALUE self, VALUE parameters) {
568
- Query_t *query = self_to_query(self);
569
- if (query->closed) rb_raise(cError, "Query is closed");
570
-
571
- if (!query->stmt)
572
- prepare_single_stmt(query->sqlite3_db, &query->stmt, query->sql);
573
-
574
- query_ctx ctx = QUERY_CTX(
575
- self,
576
- query->db_struct,
577
- query->stmt,
578
- parameters,
579
- QUERY_MODE(QUERY_MULTI_ROW),
580
- ALL_ROWS
581
- );
582
- return safe_batch_query_single_column(&ctx);
583
- }
584
-
585
449
  /* Returns the database associated with the query.
586
450
  *
587
451
  * @overload database()
@@ -623,7 +487,12 @@ VALUE Query_columns(VALUE self) {
623
487
  */
624
488
  VALUE Query_clone(VALUE self) {
625
489
  Query_t *query = self_to_query(self);
626
- return rb_funcall(cQuery, ID_new, 2, query->db, query->sql);
490
+ VALUE args[] = {
491
+ query->db,
492
+ query->sql,
493
+ query_mode_to_symbol(query->query_mode)
494
+ };
495
+ return rb_funcall_with_block(cQuery, ID_new, 3, args, query->transform_proc);
627
496
  }
628
497
 
629
498
  /* Closes the query. Attempting to run a closed query will raise an error.
@@ -670,12 +539,33 @@ VALUE Query_status(int argc, VALUE* argv, VALUE self) {
670
539
  if (query->closed) rb_raise(cError, "Query is closed");
671
540
 
672
541
  if (!query->stmt)
673
- prepare_single_stmt(query->sqlite3_db, &query->stmt, query->sql);
542
+ prepare_single_stmt(DB_GVL_MODE(query), query->sqlite3_db, &query->stmt, query->sql);
674
543
 
675
544
  int value = sqlite3_stmt_status(query->stmt, NUM2INT(op), RTEST(reset) ? 1 : 0);
676
545
  return INT2NUM(value);
677
546
  }
678
547
 
548
+ /* Sets the transform block to the given block. If a transform block is set,
549
+ * calls to #to_a, #next, #each and #batch_query will transform values fetched
550
+ * from the database using the transform block before passing them to the
551
+ * application code. To remove the transform block, call `#transform`
552
+ * without a block. The transform for each row is done by passing the row hash
553
+ * to the block.
554
+ *
555
+ * # fetch column c as an ORM object
556
+ * q = db.prepare('select * from foo order by a').transform do |h|
557
+ * MyModel.new(h)
558
+ * end
559
+ *
560
+ * @return [Extralite::Query] query
561
+ */
562
+ VALUE Query_transform(VALUE self) {
563
+ Query_t *query = self_to_query(self);
564
+
565
+ RB_OBJ_WRITE(self, &query->transform_proc, rb_block_given_p() ? rb_block_proc() : Qnil);
566
+ return self;
567
+ }
568
+
679
569
  /* Returns a short string representation of the query instance, including the
680
570
  * SQL string.
681
571
  *
@@ -694,50 +584,67 @@ VALUE Query_inspect(VALUE self) {
694
584
  return rb_sprintf("#<%"PRIsVALUE":%p %"PRIsVALUE">", cname, (void*)self, sql);
695
585
  }
696
586
 
587
+ /* Returns the query mode.
588
+ *
589
+ * @return [Symbol] query mode
590
+ */
591
+ VALUE Query_mode_get(VALUE self) {
592
+ Query_t *query = self_to_query(self);
593
+ return query_mode_to_symbol(query->query_mode);
594
+ }
595
+
596
+ /* Sets the query mode. This can be one of `:hash`, `:argv`, `:ary`.
597
+ *
598
+ * @param [Symbol] query mode
599
+ * @return [Symbol] query mode
600
+ */
601
+ VALUE Query_mode_set(VALUE self, VALUE mode) {
602
+ Query_t *query = self_to_query(self);
603
+ query->query_mode = symbol_to_query_mode(mode);
604
+ return mode;
605
+ }
606
+
697
607
  void Init_ExtraliteQuery(void) {
698
608
  VALUE mExtralite = rb_define_module("Extralite");
699
609
 
700
610
  cQuery = rb_define_class_under(mExtralite, "Query", rb_cObject);
701
611
  rb_define_alloc_func(cQuery, Query_allocate);
702
612
 
703
- rb_define_method(cQuery, "bind", Query_bind, -1);
704
- rb_define_method(cQuery, "close", Query_close, 0);
705
- rb_define_method(cQuery, "closed?", Query_closed_p, 0);
706
- rb_define_method(cQuery, "columns", Query_columns, 0);
707
- rb_define_method(cQuery, "clone", Query_clone, 0);
708
- rb_define_method(cQuery, "database", Query_database, 0);
709
- rb_define_method(cQuery, "db", Query_database, 0);
710
- rb_define_method(cQuery, "dup", Query_clone, 0);
711
-
712
- rb_define_method(cQuery, "each", Query_each_hash, 0);
713
- rb_define_method(cQuery, "each_ary", Query_each_ary, 0);
714
- rb_define_method(cQuery, "each_hash", Query_each_hash, 0);
715
- rb_define_method(cQuery, "each_single_column", Query_each_single_column, 0);
716
-
717
- rb_define_method(cQuery, "eof?", Query_eof_p, 0);
718
- rb_define_method(cQuery, "execute", Query_execute, -1);
719
- rb_define_method(cQuery, "<<", Query_execute_chevrons, 1);
720
- rb_define_method(cQuery, "batch_execute", Query_batch_execute, 1);
721
- rb_define_method(cQuery, "batch_query", Query_batch_query, 1);
722
- rb_define_method(cQuery, "batch_query_ary", Query_batch_query_ary, 1);
723
- rb_define_method(cQuery, "batch_query_single_column", Query_batch_query_single_column, 1);
724
- rb_define_method(cQuery, "initialize", Query_initialize, 2);
725
- rb_define_method(cQuery, "inspect", Query_inspect, 0);
726
-
727
- rb_define_method(cQuery, "next", Query_next_hash, -1);
728
- rb_define_method(cQuery, "next_ary", Query_next_ary, -1);
729
- rb_define_method(cQuery, "next_hash", Query_next_hash, -1);
730
- rb_define_method(cQuery, "next_single_column", Query_next_single_column, -1);
731
-
732
- rb_define_method(cQuery, "reset", Query_reset, 0);
733
- rb_define_method(cQuery, "sql", Query_sql, 0);
734
- rb_define_method(cQuery, "status", Query_status, -1);
735
-
736
- rb_define_method(cQuery, "to_a", Query_to_a_hash, 0);
737
- rb_define_method(cQuery, "to_a_ary", Query_to_a_ary, 0);
738
- rb_define_method(cQuery, "to_a_hash", Query_to_a_hash, 0);
739
- rb_define_method(cQuery, "to_a_single_column", Query_to_a_single_column, 0);
613
+ #define DEF(s, f, a) rb_define_method(cQuery, s, f, a)
614
+
615
+ DEF("bind", Query_bind, -1);
616
+ DEF("close", Query_close, 0);
617
+ DEF("closed?", Query_closed_p, 0);
618
+ DEF("columns", Query_columns, 0);
619
+ DEF("clone", Query_clone, 0);
620
+ DEF("database", Query_database, 0);
621
+ DEF("db", Query_database, 0);
622
+ DEF("dup", Query_clone, 0);
623
+ DEF("each", Query_each, 0);
624
+ DEF("eof?", Query_eof_p, 0);
625
+ DEF("execute", Query_execute, -1);
626
+ DEF("<<", Query_execute_chevrons, 1);
627
+ DEF("batch_execute", Query_batch_execute, 1);
628
+ DEF("batch_query", Query_batch_query, 1);
629
+ DEF("initialize", Query_initialize, 3);
630
+ DEF("inspect", Query_inspect, 0);
631
+ DEF("mode", Query_mode_get, 0);
632
+ DEF("mode=", Query_mode_set, 1);
633
+ DEF("next", Query_next, -1);
634
+ DEF("reset", Query_reset, 0);
635
+ DEF("sql", Query_sql, 0);
636
+ DEF("status", Query_status, -1);
637
+ DEF("to_a", Query_to_a, 0);
638
+ DEF("transform", Query_transform, 0);
740
639
 
741
640
  ID_inspect = rb_intern("inspect");
742
641
  ID_slice = rb_intern("slice");
642
+
643
+ SYM_hash = ID2SYM(rb_intern("hash"));
644
+ SYM_argv = ID2SYM(rb_intern("argv"));
645
+ SYM_ary = ID2SYM(rb_intern("ary"));
646
+
647
+ rb_gc_register_mark_object(SYM_hash);
648
+ rb_gc_register_mark_object(SYM_argv);
649
+ rb_gc_register_mark_object(SYM_ary);
743
650
  }
@@ -5,5 +5,5 @@ Gem::Specification.new do |s|
5
5
 
6
6
  s.name = 'extralite-bundle'
7
7
  s.summary = 'Extra-lightweight SQLite3 wrapper for Ruby with bundled SQLite3'
8
- s.extensions = ["ext/extralite/extconf-bundle.rb"]
8
+ s.extensions = ['ext/extralite/extconf-bundle.rb']
9
9
  end
data/extralite.gemspec CHANGED
@@ -6,5 +6,5 @@ Gem::Specification.new do |s|
6
6
  s.files.reject! { |fn| fn =~ /^ext\/sqlite3/ }
7
7
  s.name = 'extralite'
8
8
  s.summary = 'Extra-lightweight SQLite3 wrapper for Ruby'
9
- s.extensions = ["ext/extralite/extconf.rb"]
9
+ s.extensions = ['ext/extralite/extconf.rb']
10
10
  end