extralite 1.27 → 2.1

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.
@@ -0,0 +1,534 @@
1
+ #include <stdio.h>
2
+ #include "extralite.h"
3
+
4
+ /*
5
+ * Document-class: Extralite::Query
6
+ *
7
+ * This class represents a prepared query that can be reused with different
8
+ * parameters. It encapsulates [SQLite prepared
9
+ * statements](https://sqlite.org/c3ref/stmt.html).
10
+ */
11
+
12
+ VALUE cQuery;
13
+
14
+ ID ID_inspect;
15
+ ID ID_slice;
16
+
17
+ static size_t Query_size(const void *ptr) {
18
+ return sizeof(Query_t);
19
+ }
20
+
21
+ static void Query_mark(void *ptr) {
22
+ Query_t *query = ptr;
23
+ rb_gc_mark(query->db);
24
+ rb_gc_mark(query->sql);
25
+ }
26
+
27
+ static void Query_free(void *ptr) {
28
+ Query_t *query = ptr;
29
+ if (query->stmt) sqlite3_finalize(query->stmt);
30
+ free(ptr);
31
+ }
32
+
33
+ static const rb_data_type_t Query_type = {
34
+ "Query",
35
+ {Query_mark, Query_free, Query_size,},
36
+ 0, 0, RUBY_TYPED_FREE_IMMEDIATELY
37
+ };
38
+
39
+ static VALUE Query_allocate(VALUE klass) {
40
+ Query_t *query = ALLOC(Query_t);
41
+ query->db = Qnil;
42
+ query->sql = Qnil;
43
+ query->sqlite3_db = NULL;
44
+ query->stmt = NULL;
45
+ return TypedData_Wrap_Struct(klass, &Query_type, query);
46
+ }
47
+
48
+ static inline Query_t *self_to_query(VALUE obj) {
49
+ Query_t *query;
50
+ TypedData_Get_Struct((obj), Query_t, &Query_type, (query));
51
+ return query;
52
+ }
53
+
54
+ /* Initializes a new prepared query with the given database and SQL string. A
55
+ * `Query` is normally instantiated by calling `Database#prepare`:
56
+ *
57
+ * query = @db.prepare('select * from foo')
58
+ *
59
+ * @param db [Extralite::Database] associated database
60
+ * @param sql [String] SQL string
61
+ * @return [void]
62
+ */
63
+ VALUE Query_initialize(VALUE self, VALUE db, VALUE sql) {
64
+ Query_t *query = self_to_query(self);
65
+
66
+ sql = rb_funcall(sql, ID_strip, 0);
67
+ if (!RSTRING_LEN(sql))
68
+ rb_raise(cError, "Cannot prepare an empty SQL query");
69
+
70
+ query->db = db;
71
+ query->db_struct = self_to_database(db);
72
+ query->sqlite3_db = Database_sqlite3_db(db);
73
+ query->sql = sql;
74
+ query->stmt = NULL;
75
+ query->closed = 0;
76
+ query->eof = 0;
77
+
78
+ return Qnil;
79
+ }
80
+
81
+ static inline void query_reset(Query_t *query) {
82
+ if (!query->stmt)
83
+ prepare_single_stmt(query->sqlite3_db, &query->stmt, query->sql);
84
+ if (query->db_struct->trace_block != Qnil)
85
+ rb_funcall(query->db_struct->trace_block, ID_call, 1, query->sql);
86
+ sqlite3_reset(query->stmt);
87
+ query->eof = 0;
88
+ }
89
+
90
+ static inline void query_reset_and_bind(Query_t *query, int argc, VALUE * argv) {
91
+ if (!query->stmt)
92
+ prepare_single_stmt(query->sqlite3_db, &query->stmt, query->sql);
93
+
94
+ if (query->db_struct->trace_block != Qnil)
95
+ rb_funcall(query->db_struct->trace_block, ID_call, 1, query->sql);
96
+
97
+ sqlite3_reset(query->stmt);
98
+ query->eof = 0;
99
+ if (argc > 0) {
100
+ sqlite3_clear_bindings(query->stmt);
101
+ bind_all_parameters(query->stmt, argc, argv);
102
+ }
103
+ }
104
+
105
+ /* Resets the underlying prepared statement. After calling this method the
106
+ * underlying prepared statement is reset to its initial state, and any call to
107
+ * one of the `#next_xxx` methods will return the first row in the query's
108
+ * result set.
109
+ *
110
+ * query = @db.prepare('select * from foo where bar = ?')
111
+ * first = query.next
112
+ * second = query.next
113
+ * query.reset
114
+ * query.next #=> returns the first row again
115
+ *
116
+ * @return [Extralite::Query] self
117
+ */
118
+ VALUE Query_reset(VALUE self) {
119
+ Query_t *query = self_to_query(self);
120
+ if (query->closed) rb_raise(cError, "Query is closed");
121
+
122
+ query_reset(query);
123
+ if (query->db_struct->trace_block != Qnil)
124
+ rb_funcall(query->db_struct->trace_block, ID_call, 1, query->sql);
125
+
126
+ return self;
127
+ }
128
+
129
+ /* Resets the underlying prepared statement and rebinds parameters if any are
130
+ * given. After calling this method the underlying prepared statement is reset
131
+ * to its initial state, and any call to one of the `#next_xxx` methods will
132
+ * return the first row in the query's result set.
133
+ *
134
+ * Bound parameters can be specified as a list of values or as a hash mapping
135
+ * parameter names to values. When parameters are given as a splatted array, the
136
+ * query should specify parameters using `?`:
137
+ *
138
+ * query = db.prepare('select * from foo where x = ?')
139
+ * query.bind(42)
140
+ *
141
+ * Named placeholders are specified using `:`. The placeholder values are
142
+ * specified using a hash, where keys are either strings are symbols. String
143
+ * keys can include or omit the `:` prefix. The following are equivalent:
144
+ *
145
+ * query = db.prepare('select * from foo where x = :bar')
146
+ * query.bind(bar: 42)
147
+ *
148
+ * @return [Extralite::Query] self
149
+ */
150
+ VALUE Query_bind(int argc, VALUE *argv, VALUE self) {
151
+ Query_t *query = self_to_query(self);
152
+ if (query->closed) rb_raise(cError, "Query is closed");
153
+
154
+ query_reset_and_bind(query, argc, argv);
155
+ return self;
156
+ }
157
+
158
+ /* Returns true if iteration has reached the end of the result set.
159
+ *
160
+ * @return [boolean] true if iteration has reached the end of the result set
161
+ */
162
+ VALUE Query_eof_p(VALUE self) {
163
+ Query_t *query = self_to_query(self);
164
+ if (query->closed) rb_raise(cError, "Query is closed");
165
+
166
+ return query->eof ? Qtrue : Qfalse;
167
+ }
168
+
169
+ #define MAX_ROWS(max_rows) (max_rows == SINGLE_ROW ? 1 : max_rows)
170
+
171
+ static inline VALUE Query_perform_next(VALUE self, int max_rows, VALUE (*call)(query_ctx *)) {
172
+ Query_t *query = self_to_query(self);
173
+ if (query->closed) rb_raise(cError, "Query is closed");
174
+
175
+ if (!query->stmt) query_reset(query);
176
+ if (query->eof) return rb_block_given_p() ? self : Qnil;
177
+
178
+ query_ctx ctx = {
179
+ self,
180
+ query->sqlite3_db,
181
+ query->stmt,
182
+ Qnil,
183
+ QUERY_MODE(max_rows == SINGLE_ROW ? QUERY_SINGLE_ROW : QUERY_MULTI_ROW),
184
+ MAX_ROWS(max_rows),
185
+ 0
186
+ };
187
+ VALUE result = call(&ctx);
188
+ query->eof = ctx.eof;
189
+ return (ctx.mode == QUERY_YIELD) ? self : result;
190
+ }
191
+
192
+ #define MAX_ROWS_FROM_ARGV(argc, argv) (argc == 1 ? FIX2INT(argv[0]) : SINGLE_ROW)
193
+
194
+ /* Returns the next 1 or more rows from the associated query's result set as a
195
+ * hash.
196
+ *
197
+ * If no row count is given, a single row is returned. If a row count is given,
198
+ * an array containing up to the `row_count` rows is returned. If `row_count` is
199
+ * -1, all rows are returned. If the end of the result set has been reached,
200
+ * `nil` is returned.
201
+ *
202
+ * If a block is given, rows are passed to the block and self is returned.
203
+ *
204
+ * @overload next()
205
+ * @return [Hash, Extralite::Query] next row or self if block is given
206
+ * @overload next_hash()
207
+ * @return [Hash, Extralite::Query] next row or self if block is given
208
+ * @overload next(row_count)
209
+ * @param row_count [Integer] maximum row count or -1 for all rows
210
+ * @return [Array<Hash>, Extralite::Query] next rows or self if block is given
211
+ * @overload next_hash(row_count)
212
+ * @param row_count [Integer] maximum row count or -1 for all rows
213
+ * @return [Array<Hash>, Extralite::Query] next rows or self if block is given
214
+ */
215
+ VALUE Query_next_hash(int argc, VALUE *argv, VALUE self) {
216
+ rb_check_arity(argc, 0, 1);
217
+ return Query_perform_next(self, MAX_ROWS_FROM_ARGV(argc, argv), safe_query_hash);
218
+ }
219
+
220
+ /* Returns the next 1 or more rows from the associated query's result set as an
221
+ * array.
222
+ *
223
+ * If no row count is given, a single row is returned. If a row count is given,
224
+ * an array containing up to the `row_count` rows is returned. If `row_count` is
225
+ * -1, all rows are returned. If the end of the result set has been reached,
226
+ * `nil` is returned.
227
+ *
228
+ * If a block is given, rows are passed to the block and self is returned.
229
+ *
230
+ * @overload next_ary()
231
+ * @return [Array, Extralite::Query] next row or self if block is given
232
+ * @overload next_ary(row_count)
233
+ * @param row_count [Integer] maximum row count or -1 for all rows
234
+ * @return [Array<Array>, Extralite::Query] next rows or self if block is given
235
+ */
236
+ VALUE Query_next_ary(int argc, VALUE *argv, VALUE self) {
237
+ rb_check_arity(argc, 0, 1);
238
+ return Query_perform_next(self, MAX_ROWS_FROM_ARGV(argc, argv), safe_query_ary);
239
+ }
240
+
241
+ /* Returns the next 1 or more rows from the associated query's result set as an
242
+ * single values. If the result set contains more than one column an error is
243
+ * raised.
244
+ *
245
+ * If no row count is given, a single row is returned. If a row count is given,
246
+ * an array containing up to the `row_count` rows is returned. If `row_count` is
247
+ * -1, all rows are returned. If the end of the result set has been reached,
248
+ * `nil` is returned.
249
+ *
250
+ * If a block is given, rows are passed to the block and self is returned.
251
+ *
252
+ * @overload next_ary()
253
+ * @return [Object, Extralite::Query] next row or self if block is given
254
+ * @overload next_ary(row_count)
255
+ * @param row_count [Integer] maximum row count or -1 for all rows
256
+ * @return [Array<Object>, Extralite::Query] next rows or self if block is given
257
+ */
258
+ VALUE Query_next_single_column(int argc, VALUE *argv, VALUE self) {
259
+ rb_check_arity(argc, 0, 1);
260
+ return Query_perform_next(self, MAX_ROWS_FROM_ARGV(argc, argv), safe_query_single_column);
261
+ }
262
+
263
+ /* Returns all rows in the associated query's result set as hashes.
264
+ *
265
+ * @overload to_a()
266
+ * @return [Array<Hash>] all rows
267
+ * @overload to_a_hash
268
+ * @return [Array<Hash>] all rows
269
+ */
270
+ VALUE Query_to_a_hash(VALUE self) {
271
+ Query_t *query = self_to_query(self);
272
+ query_reset(query);
273
+ return Query_perform_next(self, ALL_ROWS, safe_query_hash);
274
+ }
275
+
276
+ /* Returns all rows in the associated query's result set as arrays.
277
+ *
278
+ * @return [Array<Array>] all rows
279
+ */
280
+ VALUE Query_to_a_ary(VALUE self) {
281
+ Query_t *query = self_to_query(self);
282
+ query_reset(query);
283
+ return Query_perform_next(self, ALL_ROWS, safe_query_ary);
284
+ }
285
+
286
+ /* Returns all rows in the associated query's result set as single values. If
287
+ * the result set contains more than one column an error is raised.
288
+ *
289
+ * @return [Array<Object>] all rows
290
+ */
291
+ VALUE Query_to_a_single_column(VALUE self) {
292
+ Query_t *query = self_to_query(self);
293
+ query_reset(query);
294
+ return Query_perform_next(self, ALL_ROWS, safe_query_single_column);
295
+ }
296
+
297
+ /* Iterates through the result set, passing each row to the given block as a
298
+ * hash. If no block is given, returns a `Extralite::Iterator` instance in hash
299
+ * mode.
300
+ *
301
+ * @return [Extralite::Query, Extralite::Iterator] self or an iterator if no block is given
302
+ */
303
+ VALUE Query_each_hash(VALUE self) {
304
+ if (!rb_block_given_p()) return rb_funcall(cIterator, ID_new, 2, self, SYM_hash);
305
+
306
+ Query_t *query = self_to_query(self);
307
+ query_reset(query);
308
+ return Query_perform_next(self, ALL_ROWS, safe_query_hash);
309
+ }
310
+
311
+ /* Iterates through the result set, passing each row to the given block as an
312
+ * array. If no block is given, returns a `Extralite::Iterator` instance in
313
+ * array mode.
314
+ *
315
+ * @return [Extralite::Query, Extralite::Iterator] self or an iterator if no block is given
316
+ */
317
+ VALUE Query_each_ary(VALUE self) {
318
+ if (!rb_block_given_p()) return rb_funcall(cIterator, ID_new, 2, self, SYM_ary);
319
+
320
+ Query_t *query = self_to_query(self);
321
+ query_reset(query);
322
+ return Query_perform_next(self, ALL_ROWS, safe_query_ary);
323
+ }
324
+
325
+ /* Iterates through the result set, passing each row to the given block as a
326
+ * single value. If the result set contains more than one column an error is
327
+ * raised. If no block is given, returns a `Extralite::Iterator` instance in
328
+ * single column mode.
329
+ *
330
+ * @return [Extralite::Query, Extralite::Iterator] self or an iterator if no block is given
331
+ */
332
+ VALUE Query_each_single_column(VALUE self) {
333
+ if (!rb_block_given_p()) return rb_funcall(cIterator, ID_new, 2, self, SYM_single_column);
334
+
335
+ Query_t *query = self_to_query(self);
336
+ query_reset(query);
337
+ return Query_perform_next(self, ALL_ROWS, safe_query_single_column);
338
+ }
339
+
340
+ /* call-seq:
341
+ * query.execute(*parameters) -> changes
342
+ *
343
+ * Runs a query returning the total changes effected. This method should be used
344
+ * for data- or schema-manipulation queries.
345
+ *
346
+ * Query parameters to be bound to placeholders in the query can be specified as
347
+ * a list of values or as a hash mapping parameter names to values. When
348
+ * parameters are given as an array, the query should specify parameters using
349
+ * `?`:
350
+ *
351
+ * query = db.prepare('update foo set x = ? where y = ?')
352
+ * query.execute(42, 43)
353
+ *
354
+ * Named placeholders are specified using `:`. The placeholder values are
355
+ * specified using a hash, where keys are either strings are symbols. String
356
+ * keys can include or omit the `:` prefix. The following are equivalent:
357
+ *
358
+ * query = db.prepare('update foo set x = :bar')
359
+ * query.execute(bar: 42)
360
+ * query.execute('bar' => 42)
361
+ * query.execute(':bar' => 42)
362
+ */
363
+ VALUE Query_execute(int argc, VALUE *argv, VALUE self) {
364
+ Query_t *query = self_to_query(self);
365
+ query_reset_and_bind(query, argc, argv);
366
+ return Query_perform_next(self, ALL_ROWS, safe_query_changes);
367
+ }
368
+
369
+ /* Executes the query for each set of parameters in the given array. Parameters
370
+ * can be specified as either an array (for unnamed parameters) or a hash (for
371
+ * named parameters). Returns the number of changes effected. This method is
372
+ * designed for inserting multiple records.
373
+ *
374
+ * query = db.prepare('insert into foo values (?, ?, ?)')
375
+ * records = [
376
+ * [1, 2, 3],
377
+ * [4, 5, 6]
378
+ * ]
379
+ * query.execute_multi(records)
380
+ *
381
+ * @param parameters [Array<Array, Hash>] array of parameters to run query with
382
+ * @return [Integer] number of changes effected
383
+ */
384
+ VALUE Query_execute_multi(VALUE self, VALUE parameters) {
385
+ Query_t *query = self_to_query(self);
386
+ if (query->closed) rb_raise(cError, "Query is closed");
387
+
388
+ if (!query->stmt)
389
+ prepare_single_stmt(query->sqlite3_db, &query->stmt, query->sql);
390
+
391
+ query_ctx ctx = { self, query->sqlite3_db, query->stmt, parameters, QUERY_MODE(QUERY_MULTI_ROW), ALL_ROWS };
392
+ return safe_execute_multi(&ctx);
393
+ }
394
+
395
+ /* Returns the database associated with the query.
396
+ *
397
+ * @overload database()
398
+ * @return [Extralite::Database] associated database
399
+ * @overload db()
400
+ * @return [Extralite::Database] associated database
401
+ */
402
+ VALUE Query_database(VALUE self) {
403
+ Query_t *query = self_to_query(self);
404
+ return query->db;
405
+ }
406
+
407
+ /* Returns the SQL string for the query.
408
+ *
409
+ * @return [String] SQL string
410
+ */
411
+ VALUE Query_sql(VALUE self) {
412
+ Query_t *query = self_to_query(self);
413
+ return query->sql;
414
+ }
415
+
416
+ /* Returns the column names for the query without running it.
417
+ *
418
+ * @return [Array<Symbol>] column names
419
+ */
420
+ VALUE Query_columns(VALUE self) {
421
+ Query_t *query = self_to_query(self);
422
+ query_reset(query);
423
+ return Query_perform_next(self, ALL_ROWS, safe_query_columns);
424
+ }
425
+
426
+ /* Closes the query. Attempting to run a closed query will raise an error.
427
+ *
428
+ * @return [Extralite::Query] self
429
+ */
430
+ VALUE Query_close(VALUE self) {
431
+ Query_t *query = self_to_query(self);
432
+ if (query->stmt) {
433
+ sqlite3_finalize(query->stmt);
434
+ query->stmt = NULL;
435
+ }
436
+ query->closed = 1;
437
+ return self;
438
+ }
439
+
440
+ /* Returns true if the query is closed.
441
+ *
442
+ * @return [boolean] true if query is closed
443
+ */
444
+ VALUE Query_closed_p(VALUE self) {
445
+ Query_t *query = self_to_query(self);
446
+ return query->closed ? Qtrue : Qfalse;
447
+ }
448
+
449
+ /* Returns the current [status
450
+ * value](https://sqlite.org/c3ref/c_stmtstatus_counter.html) for the given op.
451
+ * To reset the value, pass true as reset.
452
+ *
453
+ * @overload status(op)
454
+ * @param op [Integer] status op
455
+ * @return [Integer] current status value for the given op
456
+ * @overload status(op, reset)
457
+ * @param op [Integer] status op
458
+ * @param reset [true] reset flag
459
+ * @return [Integer] current status value for the given op (before reset)
460
+ */
461
+ VALUE Query_status(int argc, VALUE* argv, VALUE self) {
462
+ VALUE op, reset;
463
+
464
+ rb_scan_args(argc, argv, "11", &op, &reset);
465
+
466
+ Query_t *query = self_to_query(self);
467
+ if (query->closed) rb_raise(cError, "Query is closed");
468
+
469
+ if (!query->stmt)
470
+ prepare_single_stmt(query->sqlite3_db, &query->stmt, query->sql);
471
+
472
+ int value = sqlite3_stmt_status(query->stmt, NUM2INT(op), RTEST(reset) ? 1 : 0);
473
+ return INT2NUM(value);
474
+ }
475
+
476
+ /* Returns a short string representation of the query instance, including the
477
+ * SQL string.
478
+ *
479
+ * @return [String] string representation
480
+ */
481
+ VALUE Query_inspect(VALUE self) {
482
+ VALUE cname = rb_class_name(CLASS_OF(self));
483
+ VALUE sql = self_to_query(self)->sql;
484
+ if (RSTRING_LEN(sql) > 48) {
485
+ sql = rb_funcall(sql, ID_slice, 2, INT2FIX(0), INT2FIX(45));
486
+ rb_str_cat2(sql, "...");
487
+ }
488
+ sql = rb_funcall(sql, ID_inspect, 0);
489
+
490
+ RB_GC_GUARD(sql);
491
+ return rb_sprintf("#<%"PRIsVALUE":%p %"PRIsVALUE">", cname, (void*)self, sql);
492
+ }
493
+
494
+ void Init_ExtraliteQuery(void) {
495
+ VALUE mExtralite = rb_define_module("Extralite");
496
+
497
+ cQuery = rb_define_class_under(mExtralite, "Query", rb_cObject);
498
+ rb_define_alloc_func(cQuery, Query_allocate);
499
+
500
+ rb_define_method(cQuery, "bind", Query_bind, -1);
501
+ rb_define_method(cQuery, "close", Query_close, 0);
502
+ rb_define_method(cQuery, "closed?", Query_closed_p, 0);
503
+ rb_define_method(cQuery, "columns", Query_columns, 0);
504
+ rb_define_method(cQuery, "database", Query_database, 0);
505
+ rb_define_method(cQuery, "db", Query_database, 0);
506
+
507
+ rb_define_method(cQuery, "each", Query_each_hash, 0);
508
+ rb_define_method(cQuery, "each_ary", Query_each_ary, 0);
509
+ rb_define_method(cQuery, "each_hash", Query_each_hash, 0);
510
+ rb_define_method(cQuery, "each_single_column", Query_each_single_column, 0);
511
+
512
+ rb_define_method(cQuery, "eof?", Query_eof_p, 0);
513
+ rb_define_method(cQuery, "execute", Query_execute, -1);
514
+ rb_define_method(cQuery, "execute_multi", Query_execute_multi, 1);
515
+ rb_define_method(cQuery, "initialize", Query_initialize, 2);
516
+ rb_define_method(cQuery, "inspect", Query_inspect, 0);
517
+
518
+ rb_define_method(cQuery, "next", Query_next_hash, -1);
519
+ rb_define_method(cQuery, "next_ary", Query_next_ary, -1);
520
+ rb_define_method(cQuery, "next_hash", Query_next_hash, -1);
521
+ rb_define_method(cQuery, "next_single_column", Query_next_single_column, -1);
522
+
523
+ rb_define_method(cQuery, "reset", Query_reset, 0);
524
+ rb_define_method(cQuery, "sql", Query_sql, 0);
525
+ rb_define_method(cQuery, "status", Query_status, -1);
526
+
527
+ rb_define_method(cQuery, "to_a", Query_to_a_hash, 0);
528
+ rb_define_method(cQuery, "to_a_ary", Query_to_a_ary, 0);
529
+ rb_define_method(cQuery, "to_a_hash", Query_to_a_hash, 0);
530
+ rb_define_method(cQuery, "to_a_single_column", Query_to_a_single_column, 0);
531
+
532
+ ID_inspect = rb_intern("inspect");
533
+ ID_slice = rb_intern("slice");
534
+ }
@@ -46,7 +46,7 @@ module Extralite
46
46
  SQLITE_LIMIT_VARIABLE_NUMBER = 9
47
47
  SQLITE_LIMIT_TRIGGER_DEPTH = 10
48
48
  SQLITE_LIMIT_WORKER_THREADS = 11
49
-
49
+
50
50
  SQLITE_OK = 0
51
51
  SQLITE_ERROR = 1
52
52
  SQLITE_INTERNAL = 2
@@ -1,4 +1,4 @@
1
1
  module Extralite
2
2
  # Extralite version
3
- VERSION = '1.27'
3
+ VERSION = '2.1'
4
4
  end
data/lib/extralite.rb CHANGED
@@ -27,8 +27,6 @@ module Extralite
27
27
 
28
28
  # An SQLite database
29
29
  class Database
30
- alias_method :execute, :query
31
-
32
30
  # @!visibility private
33
31
  TABLES_SQL = <<~SQL
34
32
  SELECT name FROM sqlite_master