extralite 2.5 → 2.7

Sign up to get free protection for your applications and to get access to all the features.
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