extralite 1.18 → 1.19

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a40752d79cd5d28d8c9367c973f86b7e6a80d05e3878b5c70a6a98e0a70f3838
4
- data.tar.gz: d72e040c85a2e34a07ea513fd7b28cc558a0b8c71a8b4f211b21aa3a9f492387
3
+ metadata.gz: f2fbd9028327105949e5f4fcc225ccb4ec89cdb2f324d949c17c4690f8b4653f
4
+ data.tar.gz: 226c07bfa8e8848dc322399ee386e8f15e6bd9e41c6912850709b1cc8fd24ad3
5
5
  SHA512:
6
- metadata.gz: 4bc536fb4a5590fbae92abb1f99189f57812f5440addb49964908fcb6c45f1bfea575fc2421bfd4ddddcaaf7d00e9285ea3db4db9162afe4df71c27349dd1342
7
- data.tar.gz: a1a158d144c3f97f3bd281ba050419d42c95566408cb168f35f3a0fe8d328705f662fbc1c9ac22e788a44f1120ff90db7e237e8ad1f2505b29a643c05589b331
6
+ metadata.gz: ca3c24e4be2cedee6d20101ce56018ed9f037cbe29d4e5c6fb63116a77c988390992b770586a1973acc0c42a521389369ed79cf1553464e5f775fe420c9bf421
7
+ data.tar.gz: f9ffb7dbe2a5d00ea2c15cca1407462897590f299acecf88de6a040f463f4f28715ae98ae34a6a0539f97b1cf2f12902803217517014661208b6238618013287
data/CHANGELOG.md CHANGED
@@ -1,3 +1,8 @@
1
+ ## 1.19 2022-12-01
2
+
3
+ - Add `Database#execute_multi`
4
+ - Add `PreparedStatement#execute_multi`
5
+
1
6
  ## 1.18 2022-12-01
2
7
 
3
8
  - Fix usage with system sqlite3 lib where `load_extension` is disabled
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- extralite (1.18)
4
+ extralite (1.19)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
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 }]
@@ -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
- static inline int stmt_iterate(sqlite3_stmt *stmt, sqlite3 *db) {
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); else rb_ary_push(result, 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); else rb_ary_push(result, 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
  }
@@ -265,6 +265,34 @@ VALUE Database_query_single_value(int argc, VALUE *argv, VALUE self) {
265
265
  return Database_perform_query(argc, argv, self, safe_query_single_value);
266
266
  }
267
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
+
268
296
  /* call-seq:
269
297
  * db.columns(sql) -> columns
270
298
  *
@@ -375,6 +403,7 @@ void Init_ExtraliteDatabase() {
375
403
  rb_define_method(cDatabase, "query_single_row", Database_query_single_row, -1);
376
404
  rb_define_method(cDatabase, "query_single_column", Database_query_single_column, -1);
377
405
  rb_define_method(cDatabase, "query_single_value", Database_query_single_value, -1);
406
+ rb_define_method(cDatabase, "execute_multi", Database_execute_multi, 2);
378
407
  rb_define_method(cDatabase, "columns", Database_columns, 1);
379
408
 
380
409
  rb_define_method(cDatabase, "last_insert_rowid", Database_last_insert_rowid, 0);
@@ -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);
@@ -197,6 +197,32 @@ VALUE PreparedStatement_query_single_value(int argc, VALUE *argv, VALUE self) {
197
197
  return PreparedStatement_perform_query(argc, argv, self, safe_query_single_value);
198
198
  }
199
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
+
200
226
  /* call-seq:
201
227
  * stmt.database -> database
202
228
  * stmt.db -> database
@@ -274,6 +300,7 @@ void Init_ExtralitePreparedStatement() {
274
300
  rb_define_method(cPreparedStatement, "query_single_row", PreparedStatement_query_single_row, -1);
275
301
  rb_define_method(cPreparedStatement, "query_single_column", PreparedStatement_query_single_column, -1);
276
302
  rb_define_method(cPreparedStatement, "query_single_value", PreparedStatement_query_single_value, -1);
303
+ rb_define_method(cPreparedStatement, "execute_multi", PreparedStatement_execute_multi, 1);
277
304
 
278
305
  rb_define_method(cPreparedStatement, "columns", PreparedStatement_columns, 0);
279
306
 
@@ -1,3 +1,3 @@
1
1
  module Extralite
2
- VERSION = '1.18'
2
+ VERSION = '1.19'
3
3
  end
@@ -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,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: extralite
3
3
  version: !ruby/object:Gem::Version
4
- version: '1.18'
4
+ version: '1.19'
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sharon Rosner