extralite 3.0.1 → 3.1.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +9 -0
- data/README.md +10 -1
- data/Rakefile +2 -2
- data/TODO.md +2 -2
- data/examples/pubsub_store_polyphony.rb +1 -1
- data/ext/extralite/common.c +180 -79
- data/ext/extralite/database.c +81 -30
- data/ext/extralite/extralite.h +39 -17
- data/ext/extralite/iterator.c +1 -1
- data/ext/extralite/query.c +11 -11
- data/lib/extralite/version.rb +3 -2
- data/lib/extralite.rb +2 -1
- data/lib/sequel/adapters/extralite.rb +22 -22
- data/test/test_database.rb +239 -15
- data/test/test_query.rb +10 -9
- data/test/test_sequel.rb +3 -3
- data/test/test_trace.rb +3 -3
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: d0aae9850a753861706c0f8d5dc7323f5e682524767dc2a170c984ab56a52fc3
|
|
4
|
+
data.tar.gz: 475a2af8fce7e4d2c020ae1c0501bf54917c2ca6002ea69b702f1b48f9b076ad
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 40241318fd77231731516f2a033ae2df6e08c97c264977e067c0e5e8d536cba3ba373a78df087b20bcefd244c78d45198f752808ba289aeb2ea8a40b280291f4
|
|
7
|
+
data.tar.gz: a4108bc234202f71971a7c0bb3360c44bdf16241cadd6b4411bc522e63742e2f86f9d876deac71648ddd787ff161b8d746b63c2107d1e3e492edca2508ee7aa5
|
data/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,12 @@
|
|
|
1
|
+
# 3.1.0 2026-09-22
|
|
2
|
+
|
|
3
|
+
- Update bundled SQLite to 3.53.4
|
|
4
|
+
- Add `stmt_cache` option to `Database#initialize`
|
|
5
|
+
- Implement automatic stmt caching for parametric queries
|
|
6
|
+
- Change handling of multi stmt query and execute invocations.
|
|
7
|
+
- For #execute, accept multiple statements but raise error if parameters are given
|
|
8
|
+
- For #query, raise error on multiple statements
|
|
9
|
+
|
|
1
10
|
## 3.0.1 2026-07-21
|
|
2
11
|
|
|
3
12
|
- Fix passing nil transform to `DB#prepare`
|
data/README.md
CHANGED
|
@@ -32,7 +32,7 @@ databases.
|
|
|
32
32
|
Extralite comes in two flavors: the `extralite` gem which uses the
|
|
33
33
|
system-installed sqlite3 library, and the `extralite-bundle` gem which bundles
|
|
34
34
|
the latest version of SQLite
|
|
35
|
-
([3.53.
|
|
35
|
+
([3.53.4](https://sqlite.org/releaselog/3_53_4.html)), offering access to the
|
|
36
36
|
latest features and enhancements.
|
|
37
37
|
|
|
38
38
|
## Features
|
|
@@ -49,6 +49,7 @@ latest features and enhancements.
|
|
|
49
49
|
allowing iterating through single records or batches of records.
|
|
50
50
|
- [Prepared queries](#prepared-queries).
|
|
51
51
|
- [Parameter binding](#parameter-binding).
|
|
52
|
+
- [Automatic query caching] for queries with parameters.
|
|
52
53
|
- [Batch execution](#batch-execution-of-queries) of queries.
|
|
53
54
|
- [transactions and savepoints](#transactions-and-savepoints).
|
|
54
55
|
- Advanced features: load [SQLite extensions](#loading-extensions), create
|
|
@@ -69,6 +70,7 @@ latest features and enhancements.
|
|
|
69
70
|
- [Data Types](#data-types)
|
|
70
71
|
- [Prepared Queries](#prepared-queries)
|
|
71
72
|
- [Batch Execution of Queries](#batch-execution-of-queries)
|
|
73
|
+
- [Automatic query caching](#automatic-query-caching)
|
|
72
74
|
- [Transactions and Savepoints](#transactions-and-savepoints)
|
|
73
75
|
- [Database Information](#database-information)
|
|
74
76
|
- [Error Handling](#error-handling)
|
|
@@ -653,6 +655,13 @@ query.batch_query([[42, 3], [43, 6]])
|
|
|
653
655
|
#=> [{ x: 42, y: 2, z: 3 }, { x: 43, y: 5, z: 6 }]
|
|
654
656
|
```
|
|
655
657
|
|
|
658
|
+
## Automatic query caching
|
|
659
|
+
|
|
660
|
+
Extralite implements automatic caching of parametric queries. This means that
|
|
661
|
+
any time you run one of the `execute` or `query_xxx` methods with parameters,
|
|
662
|
+
Extralite will only prepare the query once, and the underlying `sqlite_stmt`
|
|
663
|
+
object will be cached for reuse.
|
|
664
|
+
|
|
656
665
|
## Transactions and Savepoints
|
|
657
666
|
|
|
658
667
|
All reads and writes to SQLite databases occur within a
|
data/Rakefile
CHANGED
|
@@ -28,7 +28,7 @@ end
|
|
|
28
28
|
task :release do
|
|
29
29
|
require_relative './lib/extralite/version'
|
|
30
30
|
version = Extralite::VERSION
|
|
31
|
-
|
|
31
|
+
|
|
32
32
|
puts 'Building extralite...'
|
|
33
33
|
`gem build extralite.gemspec`
|
|
34
34
|
|
|
@@ -66,4 +66,4 @@ begin
|
|
|
66
66
|
end
|
|
67
67
|
rescue LoadError => e
|
|
68
68
|
warn("NOTE: ruby_memcheck is not available in this environment: #{e}")
|
|
69
|
-
end
|
|
69
|
+
end
|
data/TODO.md
CHANGED
|
@@ -28,7 +28,7 @@ end
|
|
|
28
28
|
]
|
|
29
29
|
```
|
|
30
30
|
|
|
31
|
-
-
|
|
31
|
+
- Use single value instead of hash
|
|
32
32
|
|
|
33
33
|
```ruby
|
|
34
34
|
Extralite::Transform.new do
|
|
@@ -38,7 +38,7 @@ Extralite::Transform.new do
|
|
|
38
38
|
content: text,
|
|
39
39
|
_tag_id: skip,
|
|
40
40
|
tags: [
|
|
41
|
-
name: text
|
|
41
|
+
{ name: text.unbox }
|
|
42
42
|
]
|
|
43
43
|
}
|
|
44
44
|
end
|
data/ext/extralite/common.c
CHANGED
|
@@ -157,11 +157,6 @@ static inline void column_names_set(struct column_names *names, int idx, VALUE v
|
|
|
157
157
|
rb_ary_push(names->array, value);
|
|
158
158
|
}
|
|
159
159
|
|
|
160
|
-
static inline VALUE column_names_get(struct column_names *names, int idx) {
|
|
161
|
-
return (names->count <= MAX_EMBEDDED_COLUMN_NAMES) ?
|
|
162
|
-
names->names[idx] : RARRAY_AREF(names->array, idx);
|
|
163
|
-
}
|
|
164
|
-
|
|
165
160
|
static inline struct column_names get_column_names(sqlite3_stmt *stmt, int column_count) {
|
|
166
161
|
struct column_names names;
|
|
167
162
|
column_names_setup(&names, column_count);
|
|
@@ -213,108 +208,202 @@ static inline void row_to_splat_values(sqlite3_stmt *stmt, int column_count, VAL
|
|
|
213
208
|
}
|
|
214
209
|
}
|
|
215
210
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
sqlite3_stmt
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
}
|
|
211
|
+
static inline void lookup_cache_entry(stmt_ctx *ctx) {
|
|
212
|
+
VALUE cached = rb_hash_aref(ctx->stmt_cache, ctx->sql);
|
|
213
|
+
*(ctx->stmtptr) = NIL_P(cached) ? NULL : (sqlite3_stmt *)NUM2ULONG(cached);
|
|
214
|
+
if (*(ctx->stmtptr)) {
|
|
215
|
+
sqlite3_reset(*(ctx->stmtptr));
|
|
216
|
+
ctx->flags |= STMT_CTX_F_CACHE_HIT;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
static inline void finalize_stmt_ctx(stmt_ctx *ctx) {
|
|
221
|
+
if (!*(ctx->stmtptr)) return;
|
|
222
|
+
|
|
223
|
+
if (!(ctx->flags & STMT_CTX_F_USE_CACHE)) {
|
|
224
|
+
sqlite3_finalize(*(ctx->stmtptr));
|
|
225
|
+
*(ctx->stmtptr) = NULL;
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
if (!(ctx->flags & STMT_CTX_F_CACHE_HIT))
|
|
230
|
+
rb_hash_aset(ctx->stmt_cache, ctx->sql, ULONG2NUM((uint64_t)*(ctx->stmtptr)));
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
void make_stmt_ctx(
|
|
234
|
+
stmt_ctx *ctx, Database_t *db, sqlite3_stmt **stmt, VALUE sql, int argc, VALUE *argv
|
|
235
|
+
) {
|
|
236
|
+
ctx->stmt_cache = db->stmt_cache;
|
|
237
|
+
ctx->sql = sql;
|
|
238
|
+
|
|
239
|
+
ctx->db = db->sqlite3_db;
|
|
240
|
+
ctx->stmtptr = stmt;
|
|
241
|
+
|
|
242
|
+
int use_cache = (argc > 0) && (db->flags & DB_F_STMT_CACHE);
|
|
243
|
+
ctx->flags = use_cache ? STMT_CTX_F_USE_CACHE : 0;
|
|
244
|
+
if (use_cache) {
|
|
245
|
+
lookup_cache_entry(ctx);
|
|
246
|
+
if (ctx->flags & STMT_CTX_F_CACHE_HIT)
|
|
247
|
+
sqlite3_clear_bindings(*(ctx->stmtptr));
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
if (!use_cache || !(*(ctx->stmtptr))) {
|
|
251
|
+
ctx->str = RSTRING_PTR(sql);
|
|
252
|
+
ctx->len = RSTRING_LEN(sql);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
ctx->gvl_mode = db->gvl_release_threshold < 0 ? GVL_HOLD : GVL_RELEASE;
|
|
256
|
+
ctx->rc = 0;
|
|
257
|
+
ctx->total_changes = 0;
|
|
258
|
+
ctx->argc = argc;
|
|
259
|
+
ctx->argv = argv;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
static inline int exec_stmt_iterate(sqlite3_stmt *stmt) {
|
|
263
|
+
while (true) {
|
|
264
|
+
int rc = sqlite3_step(stmt);
|
|
265
|
+
switch (rc) {
|
|
266
|
+
case SQLITE_ROW: continue;
|
|
267
|
+
case SQLITE_DONE: return 0;
|
|
268
|
+
default: return rc;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
static inline void finalize_stmt(sqlite3_stmt **stmt) {
|
|
274
|
+
if (*stmt) {
|
|
275
|
+
sqlite3_finalize(*stmt);
|
|
276
|
+
*stmt = NULL;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
static inline void *exec_bind_parameters(void *ptr) {
|
|
281
|
+
stmt_ctx *ctx = (stmt_ctx *)ptr;
|
|
282
|
+
bind_all_parameters(*(ctx->stmtptr), ctx->argc, ctx->argv);
|
|
283
|
+
return NULL;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
void *exec_multi_stmt_impl(void *ptr) {
|
|
287
|
+
stmt_ctx *ctx = (stmt_ctx *)ptr;
|
|
288
|
+
|
|
289
|
+
if (ctx->flags & STMT_CTX_F_CACHE_HIT) {
|
|
290
|
+
rb_thread_call_with_gvl(exec_bind_parameters, ctx);
|
|
291
|
+
ctx->rc = exec_stmt_iterate(*(ctx->stmtptr));
|
|
292
|
+
if (ctx->rc == SQLITE_OK)
|
|
293
|
+
ctx->total_changes += sqlite3_changes(ctx->db);
|
|
294
|
+
else
|
|
295
|
+
ctx->total_changes = 0;
|
|
296
|
+
finalize_stmt_ctx(ctx);
|
|
297
|
+
return NULL;
|
|
298
|
+
}
|
|
223
299
|
|
|
224
|
-
void *prepare_multi_stmt_impl(void *ptr) {
|
|
225
|
-
prepare_stmt_ctx *ctx = (prepare_stmt_ctx *)ptr;
|
|
226
300
|
const char *rest = NULL;
|
|
227
301
|
const char *str = ctx->str;
|
|
228
302
|
const char *end = ctx->str + ctx->len;
|
|
303
|
+
sqlite3_stmt *next_stmt = NULL;
|
|
304
|
+
ctx->total_changes = 0;
|
|
229
305
|
while (1) {
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
306
|
+
if (next_stmt) {
|
|
307
|
+
*(ctx->stmtptr) = next_stmt;
|
|
308
|
+
next_stmt = NULL;
|
|
309
|
+
ctx->rc = SQLITE_OK;
|
|
310
|
+
}
|
|
311
|
+
else
|
|
312
|
+
ctx->rc = sqlite3_prepare_v2(ctx->db, str, end - str, ctx->stmtptr, &rest);
|
|
313
|
+
|
|
314
|
+
if ((ctx->rc != SQLITE_OK) || !(*(ctx->stmtptr))) goto done;
|
|
315
|
+
if (ctx->argc) {
|
|
316
|
+
// parameters were provided - check if str contains multiple statements
|
|
317
|
+
if (rest != end) {
|
|
318
|
+
int res = sqlite3_prepare_v2(ctx->db, rest, end-rest, &next_stmt, NULL);
|
|
319
|
+
if (next_stmt) res = SQLITE_MISUSE;
|
|
320
|
+
if (res != SQLITE_OK) {
|
|
321
|
+
ctx->rc = res;
|
|
322
|
+
goto done;
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
rb_thread_call_with_gvl(exec_bind_parameters, ctx);
|
|
235
326
|
}
|
|
236
327
|
|
|
237
|
-
|
|
328
|
+
ctx->rc = exec_stmt_iterate(*(ctx->stmtptr));
|
|
329
|
+
if (ctx->rc != SQLITE_OK) goto done;
|
|
238
330
|
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
case SQLITE_BUSY:
|
|
244
|
-
case SQLITE_ERROR:
|
|
245
|
-
case SQLITE_MISUSE:
|
|
246
|
-
return NULL;
|
|
247
|
-
}
|
|
331
|
+
ctx->total_changes += sqlite3_changes(ctx->db);
|
|
332
|
+
finalize_stmt_ctx(ctx);
|
|
333
|
+
|
|
334
|
+
if (rest == end) return NULL;
|
|
248
335
|
str = rest;
|
|
249
336
|
}
|
|
337
|
+
done:
|
|
338
|
+
finalize_stmt(&next_stmt);
|
|
339
|
+
finalize_stmt_ctx(ctx);
|
|
250
340
|
return NULL;
|
|
251
341
|
}
|
|
252
342
|
|
|
343
|
+
inline int raise_error(stmt_ctx *ctx) {
|
|
344
|
+
switch (ctx->rc) {
|
|
345
|
+
case SQLITE_BUSY:
|
|
346
|
+
rb_raise(cBusyError, "Database is busy");
|
|
347
|
+
case SQLITE_ERROR:
|
|
348
|
+
rb_raise(cSQLError, "%s", sqlite3_errmsg(ctx->db));
|
|
349
|
+
case SQLITE_MISUSE:
|
|
350
|
+
rb_raise(cError, "Multiple statements cannot take parameters");
|
|
351
|
+
default:
|
|
352
|
+
rb_raise(cError, "%s", sqlite3_errmsg(ctx->db));
|
|
353
|
+
}
|
|
354
|
+
return 0;
|
|
355
|
+
}
|
|
356
|
+
|
|
253
357
|
/*
|
|
254
358
|
This function prepares a statement from an SQL string containing one or more SQL
|
|
255
359
|
statements. It will release the GVL while the statements are being prepared and
|
|
256
360
|
executed. All statements excluding the last one are executed. The last statement
|
|
257
361
|
is not executed, but instead handed back to the caller for looping over results.
|
|
362
|
+
|
|
363
|
+
@return [int] total changes
|
|
258
364
|
*/
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
RB_GC_GUARD(sql);
|
|
365
|
+
int exec_multi_stmt(stmt_ctx *ctx) {
|
|
366
|
+
gvl_call(ctx->gvl_mode, exec_multi_stmt_impl, (void *)ctx);
|
|
367
|
+
if (ctx->rc == SQLITE_OK) return ctx->total_changes;
|
|
263
368
|
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
return;
|
|
267
|
-
case SQLITE_BUSY:
|
|
268
|
-
rb_raise(cBusyError, "Database is busy");
|
|
269
|
-
case SQLITE_ERROR:
|
|
270
|
-
rb_raise(cSQLError, "%s", sqlite3_errmsg(db));
|
|
271
|
-
default:
|
|
272
|
-
rb_raise(cError, "%s", sqlite3_errmsg(db));
|
|
273
|
-
}
|
|
369
|
+
if (*(ctx->stmtptr)) sqlite3_finalize(*(ctx->stmtptr));
|
|
370
|
+
return raise_error(ctx);
|
|
274
371
|
}
|
|
275
372
|
|
|
276
|
-
|
|
373
|
+
////////////////////////////////////////////////////////////////////////////////
|
|
374
|
+
|
|
375
|
+
void *prep_single_stmt_impl(void *ptr) {
|
|
376
|
+
stmt_ctx *ctx = (stmt_ctx *)ptr;
|
|
377
|
+
|
|
378
|
+
if (ctx->flags & STMT_CTX_F_CACHE_HIT) return NULL;
|
|
277
379
|
|
|
278
|
-
void *prepare_single_stmt_impl(void *ptr) {
|
|
279
|
-
prepare_stmt_ctx *ctx = (prepare_stmt_ctx *)ptr;
|
|
280
380
|
const char *rest = NULL;
|
|
281
381
|
const char *str = ctx->str;
|
|
282
382
|
const char *end = ctx->str + ctx->len;
|
|
283
383
|
|
|
284
|
-
ctx->rc = sqlite3_prepare_v2(ctx->db, str, end - str, ctx->
|
|
285
|
-
if (ctx->rc)
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
ctx->rc = SQLITE_MULTI_STMT;
|
|
289
|
-
goto discard_stmt;
|
|
384
|
+
ctx->rc = sqlite3_prepare_v2(ctx->db, str, end - str, ctx->stmtptr, &rest);
|
|
385
|
+
if (ctx->rc != SQLITE_OK) {
|
|
386
|
+
finalize_stmt(ctx->stmtptr);
|
|
387
|
+
return NULL;
|
|
290
388
|
}
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
389
|
+
if (rest != end) {
|
|
390
|
+
sqlite3_stmt *next = NULL;
|
|
391
|
+
ctx->rc = sqlite3_prepare_v2(ctx->db, rest, end - rest, &next, NULL);
|
|
392
|
+
if (next) {
|
|
393
|
+
sqlite3_finalize(next);
|
|
394
|
+
ctx->rc = SQLITE_MISUSE;
|
|
395
|
+
}
|
|
396
|
+
if (ctx->rc != SQLITE_OK) finalize_stmt(ctx->stmtptr);
|
|
296
397
|
}
|
|
297
|
-
end:
|
|
298
398
|
return NULL;
|
|
299
399
|
}
|
|
300
400
|
|
|
301
|
-
void
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
RB_GC_GUARD(sql);
|
|
401
|
+
void prep_single_stmt(stmt_ctx *ctx) {
|
|
402
|
+
gvl_call(ctx->gvl_mode, prep_single_stmt_impl, (void *)ctx);
|
|
403
|
+
if (ctx->rc == SQLITE_OK) return;
|
|
305
404
|
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
return;
|
|
309
|
-
case SQLITE_BUSY:
|
|
310
|
-
rb_raise(cBusyError, "Database is busy");
|
|
311
|
-
case SQLITE_ERROR:
|
|
312
|
-
rb_raise(cSQLError, "%s", sqlite3_errmsg(db));
|
|
313
|
-
case SQLITE_MULTI_STMT:
|
|
314
|
-
rb_raise(cError, "A prepared statement does not accept SQL strings with multiple queries");
|
|
315
|
-
default:
|
|
316
|
-
rb_raise(cError, "%s", sqlite3_errmsg(db));
|
|
317
|
-
}
|
|
405
|
+
if (*(ctx->stmtptr)) sqlite3_finalize(*(ctx->stmtptr));
|
|
406
|
+
raise_error(ctx);
|
|
318
407
|
}
|
|
319
408
|
|
|
320
409
|
struct step_ctx {
|
|
@@ -362,7 +451,15 @@ inline int stmt_iterate(query_ctx *ctx) {
|
|
|
362
451
|
}
|
|
363
452
|
|
|
364
453
|
VALUE cleanup_stmt(query_ctx *ctx) {
|
|
365
|
-
if (ctx->stmt)
|
|
454
|
+
if (!ctx->stmt) goto done;
|
|
455
|
+
|
|
456
|
+
if (ctx->flags & STMT_CTX_F_USE_CACHE) {
|
|
457
|
+
if (!(ctx->flags & STMT_CTX_F_CACHE_HIT))
|
|
458
|
+
rb_hash_aset(ctx->db->stmt_cache, ctx->sql, ULONG2NUM((uint64_t)(ctx->stmt)));
|
|
459
|
+
}
|
|
460
|
+
else
|
|
461
|
+
sqlite3_finalize(ctx->stmt);
|
|
462
|
+
done:
|
|
366
463
|
return Qnil;
|
|
367
464
|
}
|
|
368
465
|
|
|
@@ -517,7 +614,7 @@ VALUE safe_query_transform(query_ctx *ctx) {
|
|
|
517
614
|
VALUE array = rb_ary_new();
|
|
518
615
|
VALUE identity_storage = rb_hash_new();
|
|
519
616
|
VALUE row = Qnil;
|
|
520
|
-
// int column_count = sqlite3_column_count(ctx->
|
|
617
|
+
// int column_count = sqlite3_column_count(ctx->stmtptr);
|
|
521
618
|
struct transform_node *transform_root = get_transform_root(ctx->transform);
|
|
522
619
|
|
|
523
620
|
int row_count = 0;
|
|
@@ -542,17 +639,17 @@ done:
|
|
|
542
639
|
return rb_ary_entry(array, 0);
|
|
543
640
|
}
|
|
544
641
|
|
|
545
|
-
return Qnil;
|
|
546
642
|
RB_GC_GUARD(identity_storage);
|
|
547
643
|
RB_GC_GUARD(row);
|
|
548
644
|
RB_GC_GUARD(array);
|
|
645
|
+
return Qnil;
|
|
549
646
|
}
|
|
550
647
|
|
|
551
648
|
VALUE safe_query_single_row_transform(query_ctx *ctx) {
|
|
552
649
|
VALUE array = rb_ary_new();
|
|
553
650
|
VALUE identity_storage = rb_hash_new();
|
|
554
651
|
VALUE row = Qnil;
|
|
555
|
-
// int column_count = sqlite3_column_count(ctx->
|
|
652
|
+
// int column_count = sqlite3_column_count(ctx->stmtptr);
|
|
556
653
|
struct transform_node *transform_root = get_transform_root(ctx->transform);
|
|
557
654
|
|
|
558
655
|
int row_count = 0;
|
|
@@ -572,10 +669,10 @@ VALUE safe_query_single_row_transform(query_ctx *ctx) {
|
|
|
572
669
|
return row;
|
|
573
670
|
}
|
|
574
671
|
|
|
575
|
-
return Qnil;
|
|
576
672
|
RB_GC_GUARD(identity_storage);
|
|
577
673
|
RB_GC_GUARD(row);
|
|
578
674
|
RB_GC_GUARD(array);
|
|
675
|
+
return Qnil;
|
|
579
676
|
}
|
|
580
677
|
|
|
581
678
|
VALUE safe_query_splat(query_ctx *ctx);
|
|
@@ -1008,3 +1105,7 @@ VALUE safe_query_changes(query_ctx *ctx) {
|
|
|
1008
1105
|
while (stmt_iterate(ctx));
|
|
1009
1106
|
return INT2FIX(sqlite3_changes(ctx->sqlite3_db));
|
|
1010
1107
|
}
|
|
1108
|
+
|
|
1109
|
+
VALUE safe_total_changes(query_ctx *ctx) {
|
|
1110
|
+
return INT2FIX(ctx->total_changes);
|
|
1111
|
+
}
|