extralite-bundle 1.26 → 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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +14 -0
- data/Gemfile.lock +2 -2
- data/LICENSE +1 -1
- data/README.md +41 -15
- data/TODO.md +5 -0
- data/ext/extralite/common.c +75 -58
- data/ext/extralite/database.c +21 -12
- data/ext/extralite/extconf.rb +16 -16
- data/ext/extralite/extralite.h +61 -16
- data/ext/extralite/extralite_ext.c +4 -2
- data/ext/extralite/iterator.c +180 -0
- data/ext/extralite/query.c +479 -0
- data/ext/sqlite3/sqlite3.c +7506 -3493
- data/ext/sqlite3/sqlite3.h +304 -130
- data/lib/extralite/sqlite3_constants.rb +1 -1
- data/lib/extralite/version.rb +1 -1
- data/lib/sequel/adapters/extralite.rb +84 -86
- data/test/perf_prepared.rb +2 -2
- data/test/test_database.rb +9 -9
- data/test/test_extralite.rb +1 -1
- data/test/test_iterator.rb +99 -0
- data/test/test_query.rb +502 -0
- data/test/test_sequel.rb +46 -5
- metadata +6 -4
- data/ext/extralite/prepared_statement.c +0 -333
- data/test/test_prepared_statement.rb +0 -225
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: '065380a4b010e7a2e34c7f79ac1e1b2aae0a999e73f8a1263f47332de7257190'
|
4
|
+
data.tar.gz: 8dcad5df68d791e4411e347ae55b88a61a4fef7f93e33200cdd144716f17e087
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6e190b0418ad9a59b08039948ae69985b3264d177990eb4e1eb80b82514502e36cf33cddb60cecd5a780d5d47fd8db47d04f9f91c0a735fbc4032e2d4cdf3c77
|
7
|
+
data.tar.gz: 435ab81c615f2fcd3f7a6ed49d3ba28390ae8ed9f6c25f9ddeca51c26c32e5ccbf7ab7276a8ff9f8812d8f6c27d951bb47add5f4edbd5948b05bf68ebecb6663
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,17 @@
|
|
1
|
+
# 2.0 2023-07-08
|
2
|
+
|
3
|
+
- Fix Sequel migrations (#8)
|
4
|
+
- Redesign prepared statement functionality (#24)
|
5
|
+
- Rewrite `Extralite::PreparedStatement` into `Extralite::Query` with breaking API changes
|
6
|
+
- Add `Extralite::Iterator` class for external iteration
|
7
|
+
- Add `Query#each_xxx`, `Query#to_a_xxx` method
|
8
|
+
- Add `Query#eof?` method
|
9
|
+
|
10
|
+
# 1.27 2023-06-12
|
11
|
+
|
12
|
+
- Fix execution of prepared statements in Sequel adapter (#23 @gschlager)
|
13
|
+
- Update bundled sqlite code to version 3.42.0 (#22 @gschlager)
|
14
|
+
|
1
15
|
# 1.26 2023-05-17
|
2
16
|
|
3
17
|
- Improve documentation
|
data/Gemfile.lock
CHANGED
data/LICENSE
CHANGED
data/README.md
CHANGED
@@ -9,12 +9,12 @@
|
|
9
9
|
|
10
10
|
Extralite is a super fast, extra-lightweight (about 1300 lines of C-code)
|
11
11
|
SQLite3 wrapper for Ruby. It provides a minimal set of methods for interacting
|
12
|
-
with an SQLite3 database, as well as prepared statements.
|
12
|
+
with an SQLite3 database, as well as prepared queries (prepared statements).
|
13
13
|
|
14
14
|
Extralite comes in two flavors: the `extralite` gem which uses the
|
15
15
|
system-installed sqlite3 library, and the `extralite-bundle` gem which bundles
|
16
16
|
the latest version of SQLite
|
17
|
-
([3.
|
17
|
+
([3.42.0](https://sqlite.org/releaselog/3_42_0.html)), offering access to the
|
18
18
|
latest features and enhancements.
|
19
19
|
|
20
20
|
## Features
|
@@ -26,11 +26,11 @@ latest features and enhancements.
|
|
26
26
|
as arrays, single row, single column, single value.
|
27
27
|
- Prepared statements.
|
28
28
|
- Parameter binding.
|
29
|
+
- External iteration - get single records or batches of records.
|
29
30
|
- Use system-installed sqlite3, or the [bundled latest version of
|
30
31
|
SQLite3](#installing-the-extralite-sqlite3-bundle).
|
31
32
|
- Improved [concurrency](#concurrency) for multithreaded apps: the Ruby GVL is
|
32
33
|
released while preparing SQL statements and while iterating over results.
|
33
|
-
- Iterate over records with a block, or collect records into an array.
|
34
34
|
- Automatically execute SQL strings containing multiple semicolon-separated
|
35
35
|
queries (handy for creating/modifying schemas).
|
36
36
|
- Execute the same query with multiple parameter lists (useful for inserting records).
|
@@ -111,11 +111,37 @@ db.query('select * from foo where bar = :bar', ':bar' => 42)
|
|
111
111
|
db.execute_multi('insert into foo values (?)', ['bar', 'baz'])
|
112
112
|
db.execute_multi('insert into foo values (?, ?)', [[1, 2], [3, 4]])
|
113
113
|
|
114
|
-
# prepared
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
114
|
+
# prepared queries
|
115
|
+
query = db.prepare('select ? as foo, ? as bar') #=> Extralite::Query
|
116
|
+
query.bind(1, 2) #=> [{ :foo => 1, :bar => 2 }]
|
117
|
+
|
118
|
+
query.next #=> next row in result_set (as hash)
|
119
|
+
query.next_hash #=> next row in result_set (as hash)
|
120
|
+
query.next_ary #=> next row in result_set (as array)
|
121
|
+
query.next_single_column #=> next row in result_set (as single value)
|
122
|
+
|
123
|
+
query.next(10) #=> next 10 rows in result_set (as hash)
|
124
|
+
query.next_hash(10) #=> next 10 rows in result_set (as hash)
|
125
|
+
query.next_ary(10) #=> next 10 rows in result_set (as array)
|
126
|
+
query.next_single_column(10) #=> next 10 rows in result_set (as single value)
|
127
|
+
|
128
|
+
query.to_a #=> all rows as array of hashes
|
129
|
+
query.to_a_hash #=> all rows as array of hashes
|
130
|
+
query.to_a_ary #=> all rows as array of arrays
|
131
|
+
query.to_a_single_column #=> all rows as array of single values
|
132
|
+
|
133
|
+
query.each { |r| ... } #=> iterate over all rows as hashes
|
134
|
+
query.each_hash { |r| ... } #=> iterate over all rows as hashes
|
135
|
+
query.each_ary { |r| ... } #=> iterate over all rows as arrays
|
136
|
+
query.each_single_column { |r| ... } #=> iterate over all rows as single columns
|
137
|
+
|
138
|
+
iterator = query.each #=> create enumerable iterator
|
139
|
+
iterator.next #=> next row
|
140
|
+
iterator.each { |r| ... } #=> iterate over all rows
|
141
|
+
values = iterator.map { |r| r[:foo] * 10 } #=> map all rows
|
142
|
+
|
143
|
+
iterator = query.each_ary #=> create enumerable iterator with rows as arrays
|
144
|
+
iterator = query.each_single_column #=> create enumerable iterator with single values
|
119
145
|
|
120
146
|
# get last insert rowid
|
121
147
|
rowid = db.last_insert_rowid
|
@@ -195,10 +221,10 @@ end
|
|
195
221
|
|
196
222
|
Extralite provides methods for retrieving status information about the sqlite
|
197
223
|
runtime, database-specific status and prepared statement-specific status,
|
198
|
-
`Extralite.runtime_status`, `Database#status` and `
|
199
|
-
|
200
|
-
|
201
|
-
|
224
|
+
`Extralite.runtime_status`, `Database#status` and `Query#status` respectively.
|
225
|
+
You can also reset the high water mark for the specific status code by providing
|
226
|
+
true as the reset argument. The status codes mirror those defined by the SQLite
|
227
|
+
API. Some examples:
|
202
228
|
|
203
229
|
```ruby
|
204
230
|
# The Extralite.runtime_status returns a tuple consisting of the current value
|
@@ -212,9 +238,9 @@ current, high_watermark = Extralite.runtime_status(Extralite::SQLITE_STATUS_MEMO
|
|
212
238
|
# argument in order to reset the high watermark):
|
213
239
|
current, high_watermark = db.status(Extralite::SQLITE_DBSTATUS_CACHE_USED)
|
214
240
|
|
215
|
-
# The
|
241
|
+
# The Query#status method returns a single value (pass true as a
|
216
242
|
# second argument in order to reset the high watermark):
|
217
|
-
value =
|
243
|
+
value = query.status(Extralite::SQLITE_STMTSTATUS_RUN)
|
218
244
|
```
|
219
245
|
|
220
246
|
### Working with Database Limits
|
@@ -302,7 +328,7 @@ large number of rows.
|
|
302
328
|
|1K|502.1K rows/s|2.065M rows/s|__4.11x__|
|
303
329
|
|100K|455.7K rows/s|2.511M rows/s|__5.51x__|
|
304
330
|
|
305
|
-
### Prepared Statements
|
331
|
+
### Prepared Queries (Prepared Statements)
|
306
332
|
|
307
333
|
[Benchmark source code](https://github.com/digital-fabric/extralite/blob/main/test/perf_prepared.rb)
|
308
334
|
|
data/TODO.md
CHANGED
data/ext/extralite/common.c
CHANGED
@@ -75,13 +75,11 @@ inline void bind_parameter_value(sqlite3_stmt *stmt, int pos, VALUE value) {
|
|
75
75
|
}
|
76
76
|
}
|
77
77
|
|
78
|
-
void bind_all_parameters(sqlite3_stmt *stmt, int argc, VALUE *argv) {
|
79
|
-
for (int i = 0; i < argc; i++)
|
80
|
-
bind_parameter_value(stmt, i + 1, argv[i]);
|
81
|
-
}
|
78
|
+
inline void bind_all_parameters(sqlite3_stmt *stmt, int argc, VALUE *argv) {
|
79
|
+
for (int i = 0; i < argc; i++) bind_parameter_value(stmt, i + 1, argv[i]);
|
82
80
|
}
|
83
81
|
|
84
|
-
void bind_all_parameters_from_object(sqlite3_stmt *stmt, VALUE obj) {
|
82
|
+
inline void bind_all_parameters_from_object(sqlite3_stmt *stmt, VALUE obj) {
|
85
83
|
if (TYPE(obj) == T_ARRAY) {
|
86
84
|
int count = RARRAY_LEN(obj);
|
87
85
|
for (int i = 0; i < count; i++)
|
@@ -233,22 +231,23 @@ void *stmt_iterate_without_gvl(void *ptr) {
|
|
233
231
|
return NULL;
|
234
232
|
}
|
235
233
|
|
236
|
-
int stmt_iterate(
|
237
|
-
struct step_ctx
|
238
|
-
rb_thread_call_without_gvl(stmt_iterate_without_gvl, (void *)&
|
239
|
-
switch (
|
234
|
+
inline int stmt_iterate(query_ctx *ctx) {
|
235
|
+
struct step_ctx step_ctx = {ctx->stmt, 0};
|
236
|
+
rb_thread_call_without_gvl(stmt_iterate_without_gvl, (void *)&step_ctx, RUBY_UBF_IO, 0);
|
237
|
+
switch (step_ctx.rc) {
|
240
238
|
case SQLITE_ROW:
|
241
239
|
return 1;
|
242
240
|
case SQLITE_DONE:
|
241
|
+
ctx->eof = 1;
|
243
242
|
return 0;
|
244
243
|
case SQLITE_BUSY:
|
245
244
|
rb_raise(cBusyError, "Database is busy");
|
246
245
|
case SQLITE_INTERRUPT:
|
247
246
|
rb_raise(cInterruptError, "Query was interrupted");
|
248
247
|
case SQLITE_ERROR:
|
249
|
-
rb_raise(cSQLError, "%s", sqlite3_errmsg(
|
248
|
+
rb_raise(cSQLError, "%s", sqlite3_errmsg(ctx->sqlite3_db));
|
250
249
|
default:
|
251
|
-
rb_raise(cError, "%s", sqlite3_errmsg(
|
250
|
+
rb_raise(cError, "%s", sqlite3_errmsg(ctx->sqlite3_db));
|
252
251
|
}
|
253
252
|
|
254
253
|
return 0;
|
@@ -260,50 +259,61 @@ VALUE cleanup_stmt(query_ctx *ctx) {
|
|
260
259
|
}
|
261
260
|
|
262
261
|
VALUE safe_query_hash(query_ctx *ctx) {
|
263
|
-
VALUE
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
column_count = sqlite3_column_count(ctx->stmt);
|
270
|
-
column_names = get_column_names(ctx->stmt, column_count);
|
271
|
-
|
272
|
-
// block not given, so prepare the array of records to be returned
|
273
|
-
if (!yield_to_block) result = rb_ary_new();
|
262
|
+
VALUE array = MULTI_ROW_P(ctx->mode) ? rb_ary_new() : Qnil;
|
263
|
+
VALUE row = Qnil;
|
264
|
+
int column_count = sqlite3_column_count(ctx->stmt);
|
265
|
+
VALUE column_names = get_column_names(ctx->stmt, column_count);
|
266
|
+
int row_count = 0;
|
274
267
|
|
275
|
-
while (stmt_iterate(ctx
|
268
|
+
while (stmt_iterate(ctx)) {
|
276
269
|
row = row_to_hash(ctx->stmt, column_count, column_names);
|
277
|
-
|
278
|
-
|
270
|
+
row_count++;
|
271
|
+
switch (ctx->mode) {
|
272
|
+
case QUERY_YIELD:
|
273
|
+
rb_yield(row);
|
274
|
+
break;
|
275
|
+
case QUERY_MULTI_ROW:
|
276
|
+
rb_ary_push(array, row);
|
277
|
+
break;
|
278
|
+
case QUERY_SINGLE_ROW:
|
279
|
+
return row;
|
280
|
+
}
|
281
|
+
if (ctx->max_rows != ALL_ROWS && row_count >= ctx->max_rows)
|
282
|
+
return MULTI_ROW_P(ctx->mode) ? array : ctx->self;
|
279
283
|
}
|
280
284
|
|
281
285
|
RB_GC_GUARD(column_names);
|
282
286
|
RB_GC_GUARD(row);
|
283
|
-
RB_GC_GUARD(
|
284
|
-
return
|
287
|
+
RB_GC_GUARD(array);
|
288
|
+
return MULTI_ROW_P(ctx->mode) ? array : Qnil;
|
285
289
|
}
|
286
290
|
|
287
291
|
VALUE safe_query_ary(query_ctx *ctx) {
|
288
|
-
|
289
|
-
VALUE
|
290
|
-
int
|
291
|
-
|
292
|
-
|
293
|
-
column_count = sqlite3_column_count(ctx->stmt);
|
294
|
-
|
295
|
-
// block not given, so prepare the array of records to be returned
|
296
|
-
if (!yield_to_block) result = rb_ary_new();
|
292
|
+
VALUE array = MULTI_ROW_P(ctx->mode) ? rb_ary_new() : Qnil;
|
293
|
+
VALUE row = Qnil;
|
294
|
+
int column_count = sqlite3_column_count(ctx->stmt);
|
295
|
+
int row_count = 0;
|
297
296
|
|
298
|
-
while (stmt_iterate(ctx
|
297
|
+
while (stmt_iterate(ctx)) {
|
299
298
|
row = row_to_ary(ctx->stmt, column_count);
|
300
|
-
|
301
|
-
|
299
|
+
row_count++;
|
300
|
+
switch (ctx->mode) {
|
301
|
+
case QUERY_YIELD:
|
302
|
+
rb_yield(row);
|
303
|
+
break;
|
304
|
+
case QUERY_MULTI_ROW:
|
305
|
+
rb_ary_push(array, row);
|
306
|
+
break;
|
307
|
+
case QUERY_SINGLE_ROW:
|
308
|
+
return row;
|
309
|
+
}
|
310
|
+
if (ctx->max_rows != ALL_ROWS && row_count >= ctx->max_rows)
|
311
|
+
return MULTI_ROW_P(ctx->mode) ? array : ctx->self;
|
302
312
|
}
|
303
313
|
|
304
314
|
RB_GC_GUARD(row);
|
305
|
-
RB_GC_GUARD(
|
306
|
-
return
|
315
|
+
RB_GC_GUARD(array);
|
316
|
+
return MULTI_ROW_P(ctx->mode) ? array : Qnil;
|
307
317
|
}
|
308
318
|
|
309
319
|
VALUE safe_query_single_row(query_ctx *ctx) {
|
@@ -314,7 +324,7 @@ VALUE safe_query_single_row(query_ctx *ctx) {
|
|
314
324
|
column_count = sqlite3_column_count(ctx->stmt);
|
315
325
|
column_names = get_column_names(ctx->stmt, column_count);
|
316
326
|
|
317
|
-
if (stmt_iterate(ctx
|
327
|
+
if (stmt_iterate(ctx))
|
318
328
|
row = row_to_hash(ctx->stmt, column_count, column_names);
|
319
329
|
|
320
330
|
RB_GC_GUARD(row);
|
@@ -323,26 +333,33 @@ VALUE safe_query_single_row(query_ctx *ctx) {
|
|
323
333
|
}
|
324
334
|
|
325
335
|
VALUE safe_query_single_column(query_ctx *ctx) {
|
326
|
-
|
327
|
-
VALUE
|
328
|
-
int
|
329
|
-
|
330
|
-
|
331
|
-
column_count = sqlite3_column_count(ctx->stmt);
|
332
|
-
if (column_count != 1)
|
333
|
-
rb_raise(cError, "Expected query result to have 1 column");
|
336
|
+
VALUE array = MULTI_ROW_P(ctx->mode) ? rb_ary_new() : Qnil;
|
337
|
+
VALUE value = Qnil;
|
338
|
+
int column_count = sqlite3_column_count(ctx->stmt);
|
339
|
+
int row_count = 0;
|
334
340
|
|
335
|
-
|
336
|
-
if (!yield_to_block) result = rb_ary_new();
|
341
|
+
if (column_count != 1) rb_raise(cError, "Expected query result to have 1 column");
|
337
342
|
|
338
|
-
while (stmt_iterate(ctx
|
343
|
+
while (stmt_iterate(ctx)) {
|
339
344
|
value = get_column_value(ctx->stmt, 0, sqlite3_column_type(ctx->stmt, 0));
|
340
|
-
|
345
|
+
row_count++;
|
346
|
+
switch (ctx->mode) {
|
347
|
+
case QUERY_YIELD:
|
348
|
+
rb_yield(value);
|
349
|
+
break;
|
350
|
+
case QUERY_MULTI_ROW:
|
351
|
+
rb_ary_push(array, value);
|
352
|
+
break;
|
353
|
+
case QUERY_SINGLE_ROW:
|
354
|
+
return value;
|
355
|
+
}
|
356
|
+
if (ctx->max_rows != ALL_ROWS && row_count >= ctx->max_rows)
|
357
|
+
return MULTI_ROW_P(ctx->mode) ? array : ctx->self;
|
341
358
|
}
|
342
359
|
|
343
360
|
RB_GC_GUARD(value);
|
344
|
-
RB_GC_GUARD(
|
345
|
-
return
|
361
|
+
RB_GC_GUARD(array);
|
362
|
+
return MULTI_ROW_P(ctx->mode) ? array : Qnil;
|
346
363
|
}
|
347
364
|
|
348
365
|
VALUE safe_query_single_value(query_ctx *ctx) {
|
@@ -353,7 +370,7 @@ VALUE safe_query_single_value(query_ctx *ctx) {
|
|
353
370
|
if (column_count != 1)
|
354
371
|
rb_raise(cError, "Expected query result to have 1 column");
|
355
372
|
|
356
|
-
if (stmt_iterate(ctx
|
373
|
+
if (stmt_iterate(ctx))
|
357
374
|
value = get_column_value(ctx->stmt, 0, sqlite3_column_type(ctx->stmt, 0));
|
358
375
|
|
359
376
|
RB_GC_GUARD(value);
|
@@ -369,7 +386,7 @@ VALUE safe_execute_multi(query_ctx *ctx) {
|
|
369
386
|
sqlite3_clear_bindings(ctx->stmt);
|
370
387
|
bind_all_parameters_from_object(ctx->stmt, RARRAY_AREF(ctx->params, i));
|
371
388
|
|
372
|
-
while (stmt_iterate(ctx
|
389
|
+
while (stmt_iterate(ctx));
|
373
390
|
changes += sqlite3_changes(ctx->sqlite3_db);
|
374
391
|
}
|
375
392
|
|
data/ext/extralite/database.c
CHANGED
@@ -7,6 +7,7 @@ VALUE cSQLError;
|
|
7
7
|
VALUE cBusyError;
|
8
8
|
VALUE cInterruptError;
|
9
9
|
|
10
|
+
ID ID_bind;
|
10
11
|
ID ID_call;
|
11
12
|
ID ID_keys;
|
12
13
|
ID ID_new;
|
@@ -155,7 +156,7 @@ static inline VALUE Database_perform_query(int argc, VALUE *argv, VALUE self, VA
|
|
155
156
|
RB_GC_GUARD(sql);
|
156
157
|
|
157
158
|
bind_all_parameters(stmt, argc - 1, argv + 1);
|
158
|
-
query_ctx ctx = { self, db->sqlite3_db, stmt };
|
159
|
+
query_ctx ctx = { self, db->sqlite3_db, stmt, Qnil, QUERY_MODE(QUERY_MULTI_ROW), ALL_ROWS };
|
159
160
|
|
160
161
|
return rb_ensure(SAFE(call), (VALUE)&ctx, SAFE(cleanup_stmt), (VALUE)&ctx);
|
161
162
|
}
|
@@ -170,7 +171,7 @@ static inline VALUE Database_perform_query(int argc, VALUE *argv, VALUE self, VA
|
|
170
171
|
*
|
171
172
|
* Query parameters to be bound to placeholders in the query can be specified as
|
172
173
|
* a list of values or as a hash mapping parameter names to values. When
|
173
|
-
* parameters are given as
|
174
|
+
* parameters are given as an array, the query should specify parameters using
|
174
175
|
* `?`:
|
175
176
|
*
|
176
177
|
* db.query('select * from foo where x = ?', 42)
|
@@ -195,7 +196,7 @@ VALUE Database_query_hash(int argc, VALUE *argv, VALUE self) {
|
|
195
196
|
*
|
196
197
|
* Query parameters to be bound to placeholders in the query can be specified as
|
197
198
|
* a list of values or as a hash mapping parameter names to values. When
|
198
|
-
* parameters are given as
|
199
|
+
* parameters are given as an array, the query should specify parameters using
|
199
200
|
* `?`:
|
200
201
|
*
|
201
202
|
* db.query_ary('select * from foo where x = ?', 42)
|
@@ -219,7 +220,7 @@ VALUE Database_query_ary(int argc, VALUE *argv, VALUE self) {
|
|
219
220
|
*
|
220
221
|
* Query parameters to be bound to placeholders in the query can be specified as
|
221
222
|
* a list of values or as a hash mapping parameter names to values. When
|
222
|
-
* parameters are given as
|
223
|
+
* parameters are given as an array, the query should specify parameters using
|
223
224
|
* `?`:
|
224
225
|
*
|
225
226
|
* db.query_single_row('select * from foo where x = ?', 42)
|
@@ -244,7 +245,7 @@ VALUE Database_query_single_row(int argc, VALUE *argv, VALUE self) {
|
|
244
245
|
*
|
245
246
|
* Query parameters to be bound to placeholders in the query can be specified as
|
246
247
|
* a list of values or as a hash mapping parameter names to values. When
|
247
|
-
* parameters are given as
|
248
|
+
* parameters are given as an array, the query should specify parameters using
|
248
249
|
* `?`:
|
249
250
|
*
|
250
251
|
* db.query_single_column('select x from foo where x = ?', 42)
|
@@ -268,7 +269,7 @@ VALUE Database_query_single_column(int argc, VALUE *argv, VALUE self) {
|
|
268
269
|
*
|
269
270
|
* Query parameters to be bound to placeholders in the query can be specified as
|
270
271
|
* a list of values or as a hash mapping parameter names to values. When
|
271
|
-
* parameters are given as
|
272
|
+
* parameters are given as an array, the query should specify parameters using
|
272
273
|
* `?`:
|
273
274
|
*
|
274
275
|
* db.query_single_value('select x from foo where x = ?', 42)
|
@@ -296,7 +297,7 @@ VALUE Database_query_single_value(int argc, VALUE *argv, VALUE self) {
|
|
296
297
|
* [1, 2, 3],
|
297
298
|
* [4, 5, 6]
|
298
299
|
* ]
|
299
|
-
* db.
|
300
|
+
* db.execute_multi('insert into foo values (?, ?, ?)', records)
|
300
301
|
*
|
301
302
|
*/
|
302
303
|
VALUE Database_execute_multi(VALUE self, VALUE sql, VALUE params_array) {
|
@@ -308,7 +309,7 @@ VALUE Database_execute_multi(VALUE self, VALUE sql, VALUE params_array) {
|
|
308
309
|
// prepare query ctx
|
309
310
|
GetOpenDatabase(self, db);
|
310
311
|
prepare_single_stmt(db->sqlite3_db, &stmt, sql);
|
311
|
-
query_ctx ctx = { self, db->sqlite3_db, stmt, params_array };
|
312
|
+
query_ctx ctx = { self, db->sqlite3_db, stmt, params_array, QUERY_MODE(QUERY_MULTI_ROW), ALL_ROWS };
|
312
313
|
|
313
314
|
return rb_ensure(SAFE(safe_execute_multi), (VALUE)&ctx, SAFE(cleanup_stmt), (VALUE)&ctx);
|
314
315
|
}
|
@@ -398,12 +399,16 @@ VALUE Database_load_extension(VALUE self, VALUE path) {
|
|
398
399
|
#endif
|
399
400
|
|
400
401
|
/* call-seq:
|
401
|
-
* db.prepare(sql) -> Extralite::
|
402
|
+
* db.prepare(sql) -> Extralite::Query
|
402
403
|
*
|
403
404
|
* Creates a prepared statement with the given SQL query.
|
404
405
|
*/
|
405
|
-
VALUE Database_prepare(VALUE
|
406
|
-
|
406
|
+
VALUE Database_prepare(int argc, VALUE *argv, VALUE self) {
|
407
|
+
rb_check_arity(argc, 1, UNLIMITED_ARGUMENTS);
|
408
|
+
VALUE query = rb_funcall(cQuery, ID_new, 2, self, argv[0]);
|
409
|
+
if (argc > 1) rb_funcallv(query, ID_bind, argc - 1, argv + 1);
|
410
|
+
RB_GC_GUARD(query);
|
411
|
+
return query;
|
407
412
|
}
|
408
413
|
|
409
414
|
/* call-seq:
|
@@ -540,6 +545,9 @@ VALUE Database_backup(int argc, VALUE *argv, VALUE self) {
|
|
540
545
|
backup_ctx ctx = { dst_db, dst_is_fn, backup, rb_block_given_p(), 0 };
|
541
546
|
rb_ensure(SAFE(backup_safe_iterate), (VALUE)&ctx, SAFE(backup_cleanup), (VALUE)&ctx);
|
542
547
|
|
548
|
+
RB_GC_GUARD(src_name);
|
549
|
+
RB_GC_GUARD(dst_name);
|
550
|
+
|
543
551
|
return self;
|
544
552
|
}
|
545
553
|
|
@@ -717,7 +725,7 @@ void Init_ExtraliteDatabase(void) {
|
|
717
725
|
rb_define_method(cDatabase, "interrupt", Database_interrupt, 0);
|
718
726
|
rb_define_method(cDatabase, "last_insert_rowid", Database_last_insert_rowid, 0);
|
719
727
|
rb_define_method(cDatabase, "limit", Database_limit, -1);
|
720
|
-
rb_define_method(cDatabase, "prepare", Database_prepare, 1);
|
728
|
+
rb_define_method(cDatabase, "prepare", Database_prepare, -1);
|
721
729
|
rb_define_method(cDatabase, "query", Database_query_hash, -1);
|
722
730
|
rb_define_method(cDatabase, "query_ary", Database_query_ary, -1);
|
723
731
|
rb_define_method(cDatabase, "query_hash", Database_query_hash, -1);
|
@@ -742,6 +750,7 @@ void Init_ExtraliteDatabase(void) {
|
|
742
750
|
rb_gc_register_mark_object(cBusyError);
|
743
751
|
rb_gc_register_mark_object(cInterruptError);
|
744
752
|
|
753
|
+
ID_bind = rb_intern("bind");
|
745
754
|
ID_call = rb_intern("call");
|
746
755
|
ID_keys = rb_intern("keys");
|
747
756
|
ID_new = rb_intern("new");
|
data/ext/extralite/extconf.rb
CHANGED
@@ -4,13 +4,13 @@ if ENV['EXTRALITE_BUNDLE']
|
|
4
4
|
require_relative('extconf-bundle')
|
5
5
|
else
|
6
6
|
ENV['RC_ARCHS'] = '' if RUBY_PLATFORM =~ /darwin/
|
7
|
-
|
7
|
+
|
8
8
|
require 'mkmf'
|
9
|
-
|
9
|
+
|
10
10
|
# :stopdoc:
|
11
|
-
|
11
|
+
|
12
12
|
RbConfig::MAKEFILE_CONFIG['CC'] = ENV['CC'] if ENV['CC']
|
13
|
-
|
13
|
+
|
14
14
|
ldflags = cppflags = nil
|
15
15
|
if RbConfig::CONFIG["host_os"] =~ /darwin/
|
16
16
|
begin
|
@@ -25,7 +25,7 @@ else
|
|
25
25
|
cppflags = "#{brew_prefix}/include"
|
26
26
|
pkg_conf = "#{brew_prefix}/lib/pkgconfig"
|
27
27
|
end
|
28
|
-
|
28
|
+
|
29
29
|
# pkg_config should be less error prone than parsing compiler
|
30
30
|
# commandline options, but we need to set default ldflags and cpp flags
|
31
31
|
# in case the user doesn't have pkg-config installed
|
@@ -33,13 +33,13 @@ else
|
|
33
33
|
rescue
|
34
34
|
end
|
35
35
|
end
|
36
|
-
|
36
|
+
|
37
37
|
if with_config('sqlcipher')
|
38
38
|
pkg_config("sqlcipher")
|
39
39
|
else
|
40
40
|
pkg_config("sqlite3")
|
41
41
|
end
|
42
|
-
|
42
|
+
|
43
43
|
# --with-sqlite3-{dir,include,lib}
|
44
44
|
if with_config('sqlcipher')
|
45
45
|
$CFLAGS << ' -DUSING_SQLCIPHER'
|
@@ -47,15 +47,15 @@ else
|
|
47
47
|
else
|
48
48
|
dir_config("sqlite3", cppflags, ldflags)
|
49
49
|
end
|
50
|
-
|
50
|
+
|
51
51
|
if RbConfig::CONFIG["host_os"] =~ /mswin/
|
52
52
|
$CFLAGS << ' -W3'
|
53
53
|
end
|
54
|
-
|
54
|
+
|
55
55
|
if RUBY_VERSION < '2.7'
|
56
56
|
$CFLAGS << ' -DTAINTING_SUPPORT'
|
57
57
|
end
|
58
|
-
|
58
|
+
|
59
59
|
# @!visibility private
|
60
60
|
def asplode missing
|
61
61
|
if RUBY_PLATFORM =~ /mingw|mswin/
|
@@ -70,25 +70,25 @@ else
|
|
70
70
|
error
|
71
71
|
end
|
72
72
|
end
|
73
|
-
|
73
|
+
|
74
74
|
asplode('sqlite3.h') unless find_header 'sqlite3.h'
|
75
75
|
find_library 'pthread', 'pthread_create' # 1.8 support. *shrug*
|
76
|
-
|
76
|
+
|
77
77
|
have_library 'dl' # for static builds
|
78
|
-
|
78
|
+
|
79
79
|
if with_config('sqlcipher')
|
80
80
|
asplode('sqlcipher') unless find_library 'sqlcipher', 'sqlite3_libversion_number'
|
81
81
|
else
|
82
82
|
asplode('sqlite3') unless find_library 'sqlite3', 'sqlite3_libversion_number'
|
83
83
|
end
|
84
|
-
|
84
|
+
|
85
85
|
have_func('sqlite3_enable_load_extension')
|
86
86
|
have_func('sqlite3_load_extension')
|
87
87
|
have_func('sqlite3_prepare_v2')
|
88
88
|
have_func('sqlite3_error_offset')
|
89
|
-
|
89
|
+
|
90
90
|
$defs << "-DEXTRALITE_NO_BUNDLE"
|
91
|
-
|
91
|
+
|
92
92
|
dir_config('extralite_ext')
|
93
93
|
create_makefile('extralite_ext')
|
94
94
|
end
|