extralite 1.17 → 1.19
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/README.md +5 -0
- data/ext/extralite/common.c +31 -3
- data/ext/extralite/database.c +36 -0
- data/ext/extralite/extconf.rb +1 -1
- data/ext/extralite/extralite.h +4 -0
- data/ext/extralite/prepared_statement.c +27 -1
- data/lib/extralite/version.rb +1 -1
- data/test/test_database.rb +36 -0
- data/test/test_prepared_statement.rb +19 -0
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f2fbd9028327105949e5f4fcc225ccb4ec89cdb2f324d949c17c4690f8b4653f
|
4
|
+
data.tar.gz: 226c07bfa8e8848dc322399ee386e8f15e6bd9e41c6912850709b1cc8fd24ad3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ca3c24e4be2cedee6d20101ce56018ed9f037cbe29d4e5c6fb63116a77c988390992b770586a1973acc0c42a521389369ed79cf1553464e5f775fe420c9bf421
|
7
|
+
data.tar.gz: f9ffb7dbe2a5d00ea2c15cca1407462897590f299acecf88de6a040f463f4f28715ae98ae34a6a0539f97b1cf2f12902803217517014661208b6238618013287
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,12 @@
|
|
1
|
+
## 1.19 2022-12-01
|
2
|
+
|
3
|
+
- Add `Database#execute_multi`
|
4
|
+
- Add `PreparedStatement#execute_multi`
|
5
|
+
|
6
|
+
## 1.18 2022-12-01
|
7
|
+
|
8
|
+
- Fix usage with system sqlite3 lib where `load_extension` is disabled
|
9
|
+
|
1
10
|
## 1.17 2022-10-31
|
2
11
|
|
3
12
|
- Fix `Database#tables` to work on older version of sqlite (pre 3.33.0)
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -51,6 +51,7 @@ latest features and enhancements.
|
|
51
51
|
queries (handy for creating/modifying schemas).
|
52
52
|
- Get last insert rowid.
|
53
53
|
- Get number of rows changed by last query.
|
54
|
+
- Execute the same query with multiple parameter lists (useful for inserting records).
|
54
55
|
- Load extensions (loading of extensions is autmatically enabled. You can find
|
55
56
|
some useful extensions here: https://github.com/nalgeon/sqlean.)
|
56
57
|
- Includes a [Sequel adapter](#usage-with-sequel).
|
@@ -123,6 +124,10 @@ db.query('select * from foo where bar = :bar', bar: 42)
|
|
123
124
|
db.query('select * from foo where bar = :bar', 'bar' => 42)
|
124
125
|
db.query('select * from foo where bar = :bar', ':bar' => 42)
|
125
126
|
|
127
|
+
# insert multiple rows
|
128
|
+
db.execute_multi('insert into foo values (?)', ['bar', 'baz'])
|
129
|
+
db.execute_multi('insert into foo values (?, ?)', [[1, 2], [3, 4]])
|
130
|
+
|
126
131
|
# prepared statements
|
127
132
|
stmt = db.prepare('select ? as foo, ? as bar') #=> Extralite::PreparedStatement
|
128
133
|
stmt.query(1, 2) #=> [{ :foo => 1, :bar => 2 }]
|
data/ext/extralite/common.c
CHANGED
@@ -81,6 +81,16 @@ void bind_all_parameters(sqlite3_stmt *stmt, int argc, VALUE *argv) {
|
|
81
81
|
}
|
82
82
|
}
|
83
83
|
|
84
|
+
void bind_all_parameters_from_object(sqlite3_stmt *stmt, VALUE obj) {
|
85
|
+
if (TYPE(obj) == T_ARRAY) {
|
86
|
+
int count = RARRAY_LEN(obj);
|
87
|
+
for (int i = 0; i < count; i++)
|
88
|
+
bind_parameter_value(stmt, i + 1, RARRAY_AREF(obj, i));
|
89
|
+
}
|
90
|
+
else
|
91
|
+
bind_parameter_value(stmt, 1, obj);
|
92
|
+
}
|
93
|
+
|
84
94
|
static inline VALUE get_column_names(sqlite3_stmt *stmt, int column_count) {
|
85
95
|
VALUE arr = rb_ary_new2(column_count);
|
86
96
|
for (int i = 0; i < column_count; i++) {
|
@@ -223,7 +233,7 @@ void *stmt_iterate_without_gvl(void *ptr) {
|
|
223
233
|
return NULL;
|
224
234
|
}
|
225
235
|
|
226
|
-
|
236
|
+
int stmt_iterate(sqlite3_stmt *stmt, sqlite3 *db) {
|
227
237
|
struct step_ctx ctx = {stmt, 0};
|
228
238
|
rb_thread_call_without_gvl(stmt_iterate_without_gvl, (void *)&ctx, RUBY_UBF_IO, 0);
|
229
239
|
switch (ctx.rc) {
|
@@ -262,7 +272,8 @@ VALUE safe_query_hash(query_ctx *ctx) {
|
|
262
272
|
|
263
273
|
while (stmt_iterate(ctx->stmt, ctx->sqlite3_db)) {
|
264
274
|
row = row_to_hash(ctx->stmt, column_count, column_names);
|
265
|
-
if (yield_to_block) rb_yield(row);
|
275
|
+
if (yield_to_block) rb_yield(row);
|
276
|
+
else rb_ary_push(result, row);
|
266
277
|
}
|
267
278
|
|
268
279
|
RB_GC_GUARD(column_names);
|
@@ -284,7 +295,8 @@ VALUE safe_query_ary(query_ctx *ctx) {
|
|
284
295
|
|
285
296
|
while (stmt_iterate(ctx->stmt, ctx->sqlite3_db)) {
|
286
297
|
row = row_to_ary(ctx->stmt, column_count);
|
287
|
-
if (yield_to_block) rb_yield(row);
|
298
|
+
if (yield_to_block) rb_yield(row);
|
299
|
+
else rb_ary_push(result, row);
|
288
300
|
}
|
289
301
|
|
290
302
|
RB_GC_GUARD(row);
|
@@ -346,6 +358,22 @@ VALUE safe_query_single_value(query_ctx *ctx) {
|
|
346
358
|
return value;
|
347
359
|
}
|
348
360
|
|
361
|
+
VALUE safe_execute_multi(query_ctx *ctx) {
|
362
|
+
int count = RARRAY_LEN(ctx->params);
|
363
|
+
int changes = 0;
|
364
|
+
|
365
|
+
for (int i = 0; i < count; i++) {
|
366
|
+
sqlite3_reset(ctx->stmt);
|
367
|
+
sqlite3_clear_bindings(ctx->stmt);
|
368
|
+
bind_all_parameters_from_object(ctx->stmt, RARRAY_AREF(ctx->params, i));
|
369
|
+
|
370
|
+
while (stmt_iterate(ctx->stmt, ctx->sqlite3_db));
|
371
|
+
changes += sqlite3_changes(ctx->sqlite3_db);
|
372
|
+
}
|
373
|
+
|
374
|
+
return INT2FIX(changes);
|
375
|
+
}
|
376
|
+
|
349
377
|
VALUE safe_query_columns(query_ctx *ctx) {
|
350
378
|
return get_column_names(ctx->stmt, sqlite3_column_count(ctx->stmt));
|
351
379
|
}
|
data/ext/extralite/database.c
CHANGED
@@ -77,11 +77,13 @@ VALUE Database_initialize(VALUE self, VALUE path) {
|
|
77
77
|
rb_raise(cError, "%s", sqlite3_errmsg(db->sqlite3_db));
|
78
78
|
}
|
79
79
|
|
80
|
+
#ifdef HAVE_SQLITE3_ENABLE_LOAD_EXTENSION
|
80
81
|
rc = sqlite3_enable_load_extension(db->sqlite3_db, 1);
|
81
82
|
if (rc) {
|
82
83
|
sqlite3_close(db->sqlite3_db);
|
83
84
|
rb_raise(cError, "%s", sqlite3_errmsg(db->sqlite3_db));
|
84
85
|
}
|
86
|
+
#endif
|
85
87
|
|
86
88
|
return Qnil;
|
87
89
|
}
|
@@ -263,6 +265,34 @@ VALUE Database_query_single_value(int argc, VALUE *argv, VALUE self) {
|
|
263
265
|
return Database_perform_query(argc, argv, self, safe_query_single_value);
|
264
266
|
}
|
265
267
|
|
268
|
+
/* call-seq:
|
269
|
+
* db.execute_multi(sql, params_array) -> changes
|
270
|
+
*
|
271
|
+
* Executes the given query for each list of parameters in params_array. Returns
|
272
|
+
* the number of changes effected. This method is designed for inserting
|
273
|
+
* multiple records.
|
274
|
+
*
|
275
|
+
* records = [
|
276
|
+
* [1, 2, 3],
|
277
|
+
* [4, 5, 6]
|
278
|
+
* ]
|
279
|
+
* db.execute_multi_query('insert into foo values (?, ?, ?)', records)
|
280
|
+
*
|
281
|
+
*/
|
282
|
+
VALUE Database_execute_multi(VALUE self, VALUE sql, VALUE params_array) {
|
283
|
+
Database_t *db;
|
284
|
+
sqlite3_stmt *stmt;
|
285
|
+
|
286
|
+
if (RSTRING_LEN(sql) == 0) return Qnil;
|
287
|
+
|
288
|
+
// prepare query ctx
|
289
|
+
GetOpenDatabase(self, db);
|
290
|
+
prepare_single_stmt(db->sqlite3_db, &stmt, sql);
|
291
|
+
query_ctx ctx = { self, db->sqlite3_db, stmt, params_array };
|
292
|
+
|
293
|
+
return rb_ensure(SAFE(safe_execute_multi), (VALUE)&ctx, SAFE(cleanup_stmt), (VALUE)&ctx);
|
294
|
+
}
|
295
|
+
|
266
296
|
/* call-seq:
|
267
297
|
* db.columns(sql) -> columns
|
268
298
|
*
|
@@ -325,6 +355,7 @@ VALUE Database_transaction_active_p(VALUE self) {
|
|
325
355
|
return sqlite3_get_autocommit(db->sqlite3_db) ? Qfalse : Qtrue;
|
326
356
|
}
|
327
357
|
|
358
|
+
#ifdef HAVE_SQLITE3_LOAD_EXTENSION
|
328
359
|
/* call-seq:
|
329
360
|
* db.load_extension(path) -> db
|
330
361
|
*
|
@@ -344,6 +375,7 @@ VALUE Database_load_extension(VALUE self, VALUE path) {
|
|
344
375
|
|
345
376
|
return self;
|
346
377
|
}
|
378
|
+
#endif
|
347
379
|
|
348
380
|
/* call-seq:
|
349
381
|
* db.prepare(sql) -> Extralite::PreparedStatement
|
@@ -371,13 +403,17 @@ void Init_ExtraliteDatabase() {
|
|
371
403
|
rb_define_method(cDatabase, "query_single_row", Database_query_single_row, -1);
|
372
404
|
rb_define_method(cDatabase, "query_single_column", Database_query_single_column, -1);
|
373
405
|
rb_define_method(cDatabase, "query_single_value", Database_query_single_value, -1);
|
406
|
+
rb_define_method(cDatabase, "execute_multi", Database_execute_multi, 2);
|
374
407
|
rb_define_method(cDatabase, "columns", Database_columns, 1);
|
375
408
|
|
376
409
|
rb_define_method(cDatabase, "last_insert_rowid", Database_last_insert_rowid, 0);
|
377
410
|
rb_define_method(cDatabase, "changes", Database_changes, 0);
|
378
411
|
rb_define_method(cDatabase, "filename", Database_filename, -1);
|
379
412
|
rb_define_method(cDatabase, "transaction_active?", Database_transaction_active_p, 0);
|
413
|
+
|
414
|
+
#ifdef HAVE_SQLITE3_LOAD_EXTENSION
|
380
415
|
rb_define_method(cDatabase, "load_extension", Database_load_extension, 1);
|
416
|
+
#endif
|
381
417
|
|
382
418
|
rb_define_method(cDatabase, "prepare", Database_prepare, 1);
|
383
419
|
|
data/ext/extralite/extconf.rb
CHANGED
@@ -102,7 +102,7 @@ have_func('sqlite3_enable_load_extension')
|
|
102
102
|
have_func('sqlite3_load_extension')
|
103
103
|
|
104
104
|
unless have_func('sqlite3_open_v2')
|
105
|
-
abort
|
105
|
+
abort 'Please use a newer version of SQLite3'
|
106
106
|
end
|
107
107
|
|
108
108
|
have_func('sqlite3_prepare_v2')
|
data/ext/extralite/extralite.h
CHANGED
@@ -46,6 +46,7 @@ typedef struct {
|
|
46
46
|
VALUE self;
|
47
47
|
sqlite3 *sqlite3_db;
|
48
48
|
sqlite3_stmt *stmt;
|
49
|
+
VALUE params;
|
49
50
|
} query_ctx;
|
50
51
|
|
51
52
|
VALUE safe_query_ary(query_ctx *ctx);
|
@@ -53,11 +54,14 @@ VALUE safe_query_hash(query_ctx *ctx);
|
|
53
54
|
VALUE safe_query_single_column(query_ctx *ctx);
|
54
55
|
VALUE safe_query_single_row(query_ctx *ctx);
|
55
56
|
VALUE safe_query_single_value(query_ctx *ctx);
|
57
|
+
VALUE safe_execute_multi(query_ctx *ctx);
|
56
58
|
VALUE safe_query_columns(query_ctx *ctx);
|
57
59
|
|
58
60
|
void prepare_single_stmt(sqlite3 *db, sqlite3_stmt **stmt, VALUE sql);
|
59
61
|
void prepare_multi_stmt(sqlite3 *db, sqlite3_stmt **stmt, VALUE sql);
|
60
62
|
void bind_all_parameters(sqlite3_stmt *stmt, int argc, VALUE *argv);
|
63
|
+
void bind_all_parameters_from_object(sqlite3_stmt *stmt, VALUE obj);
|
64
|
+
int stmt_iterate(sqlite3_stmt *stmt, sqlite3 *db);
|
61
65
|
VALUE cleanup_stmt(query_ctx *ctx);
|
62
66
|
|
63
67
|
sqlite3 *Database_sqlite3_db(VALUE self);
|
@@ -53,7 +53,6 @@ VALUE PreparedStatement_initialize(VALUE self, VALUE db, VALUE sql) {
|
|
53
53
|
stmt->sqlite3_db = Database_sqlite3_db(db);
|
54
54
|
stmt->sql = sql;
|
55
55
|
|
56
|
-
// TODO: setup stmt
|
57
56
|
prepare_single_stmt(stmt->sqlite3_db, &stmt->stmt, sql);
|
58
57
|
|
59
58
|
return Qnil;
|
@@ -198,6 +197,32 @@ VALUE PreparedStatement_query_single_value(int argc, VALUE *argv, VALUE self) {
|
|
198
197
|
return PreparedStatement_perform_query(argc, argv, self, safe_query_single_value);
|
199
198
|
}
|
200
199
|
|
200
|
+
/* call-seq:
|
201
|
+
* stmt.execute_multi(params_array) -> changes
|
202
|
+
*
|
203
|
+
* Executes the prepared statment for each list of parameters in params_array.
|
204
|
+
* Returns the number of changes effected. This method is designed for inserting
|
205
|
+
* multiple records.
|
206
|
+
*
|
207
|
+
* stmt = db.prepare('insert into foo values (?, ?, ?)')
|
208
|
+
* records = [
|
209
|
+
* [1, 2, 3],
|
210
|
+
* [4, 5, 6]
|
211
|
+
* ]
|
212
|
+
* stmt.execute_multi_query(records)
|
213
|
+
*
|
214
|
+
*/
|
215
|
+
VALUE PreparedStatement_execute_multi(VALUE self, VALUE params_array) {
|
216
|
+
PreparedStatement_t *stmt;
|
217
|
+
GetPreparedStatement(self, stmt);
|
218
|
+
|
219
|
+
if (!stmt->stmt)
|
220
|
+
rb_raise(cError, "Prepared statement is closed");
|
221
|
+
|
222
|
+
query_ctx ctx = { self, stmt->sqlite3_db, stmt->stmt, params_array };
|
223
|
+
return safe_execute_multi(&ctx);
|
224
|
+
}
|
225
|
+
|
201
226
|
/* call-seq:
|
202
227
|
* stmt.database -> database
|
203
228
|
* stmt.db -> database
|
@@ -275,6 +300,7 @@ void Init_ExtralitePreparedStatement() {
|
|
275
300
|
rb_define_method(cPreparedStatement, "query_single_row", PreparedStatement_query_single_row, -1);
|
276
301
|
rb_define_method(cPreparedStatement, "query_single_column", PreparedStatement_query_single_column, -1);
|
277
302
|
rb_define_method(cPreparedStatement, "query_single_value", PreparedStatement_query_single_value, -1);
|
303
|
+
rb_define_method(cPreparedStatement, "execute_multi", PreparedStatement_execute_multi, 1);
|
278
304
|
|
279
305
|
rb_define_method(cPreparedStatement, "columns", PreparedStatement_columns, 0);
|
280
306
|
|
data/lib/extralite/version.rb
CHANGED
data/test/test_database.rb
CHANGED
@@ -207,6 +207,42 @@ end
|
|
207
207
|
assert_equal [{schema_version: 33}], @db.pragma(:schema_version)
|
208
208
|
assert_equal [{recursive_triggers: 1}], @db.pragma(:recursive_triggers)
|
209
209
|
end
|
210
|
+
|
211
|
+
def test_execute_multi
|
212
|
+
@db.query('create table foo (a, b, c)')
|
213
|
+
assert_equal [], @db.query('select * from foo')
|
214
|
+
|
215
|
+
records = [
|
216
|
+
[1, '2', 3],
|
217
|
+
['4', 5, 6]
|
218
|
+
]
|
219
|
+
|
220
|
+
changes = @db.execute_multi('insert into foo values (?, ?, ?)', records)
|
221
|
+
|
222
|
+
assert_equal 2, changes
|
223
|
+
assert_equal [
|
224
|
+
{ a: 1, b: '2', c: 3 },
|
225
|
+
{ a: '4', b: 5, c: 6 }
|
226
|
+
], @db.query('select * from foo')
|
227
|
+
end
|
228
|
+
|
229
|
+
def test_execute_multi_single_values
|
230
|
+
@db.query('create table foo (bar)')
|
231
|
+
assert_equal [], @db.query('select * from foo')
|
232
|
+
|
233
|
+
records = [
|
234
|
+
'hi',
|
235
|
+
'bye'
|
236
|
+
]
|
237
|
+
|
238
|
+
changes = @db.execute_multi('insert into foo values (?)', records)
|
239
|
+
|
240
|
+
assert_equal 2, changes
|
241
|
+
assert_equal [
|
242
|
+
{ bar: 'hi' },
|
243
|
+
{ bar: 'bye' }
|
244
|
+
], @db.query('select * from foo')
|
245
|
+
end
|
210
246
|
end
|
211
247
|
|
212
248
|
class ScenarioTest < MiniTest::Test
|
@@ -181,4 +181,23 @@ end
|
|
181
181
|
|
182
182
|
assert_raises { p.query_single_value }
|
183
183
|
end
|
184
|
+
|
185
|
+
def test_prepared_statement_execute_multi
|
186
|
+
@db.query('create table foo (a, b, c)')
|
187
|
+
assert_equal [], @db.query('select * from foo')
|
188
|
+
|
189
|
+
records = [
|
190
|
+
[1, '2', 3],
|
191
|
+
['4', 5, 6]
|
192
|
+
]
|
193
|
+
|
194
|
+
p = @db.prepare('insert into foo values (?, ?, ?)')
|
195
|
+
changes = p.execute_multi(records)
|
196
|
+
|
197
|
+
assert_equal 2, changes
|
198
|
+
assert_equal [
|
199
|
+
{ a: 1, b: '2', c: 3 },
|
200
|
+
{ a: '4', b: 5, c: 6 }
|
201
|
+
], @db.query('select * from foo')
|
202
|
+
end
|
184
203
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: extralite
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: '1.
|
4
|
+
version: '1.19'
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Sharon Rosner
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-
|
11
|
+
date: 2022-12-01 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rake-compiler
|