extralite 1.27 → 2.0

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