extralite 1.27 → 2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +9 -0
- data/Gemfile.lock +1 -1
- data/LICENSE +1 -1
- data/README.md +40 -14
- 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/lib/extralite/sqlite3_constants.rb +1 -1
- data/lib/extralite/version.rb +1 -1
- data/lib/sequel/adapters/extralite.rb +104 -106
- 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 +23 -4
- 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: d399062a5652d11c0e969dfb74f6c998015980e8831e221d2aece7ddb6cfc83d
|
4
|
+
data.tar.gz: '0759cd16b15124934c52d5e2a3f36620b165f5a205aaea707c3d957503038173'
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 62ad641bd7661d326c3ba83eff0e7ecf5befdd12d7267608e748d6a7cf586d96e9f870cd179989cabb230b822e9d245ff9d1ed8877632ca33c7577858a639ded
|
7
|
+
data.tar.gz: 2984632a7bb790b354713396ac00856f0646c79aa22bb3a9bcd26995a127eb8cb86a5ca64efcbd91b1fcc8dd5a41f95e0bacefed7b5bacd477e9b689d79186a6
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,12 @@
|
|
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
|
+
|
1
10
|
# 1.27 2023-06-12
|
2
11
|
|
3
12
|
- Fix execution of prepared statements in Sequel adapter (#23 @gschlager)
|
data/Gemfile.lock
CHANGED
data/LICENSE
CHANGED
data/README.md
CHANGED
@@ -9,7 +9,7 @@
|
|
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
|
@@ -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
|