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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 566a896b52bee42cadd627b119dc198f7ee8b958acb7c2d73f8f5e77c4dc19e9
4
- data.tar.gz: 7e95e8d41d675aea693e5f79442f1d8d484ea8b2fde52d1e8105bfd0241b9428
3
+ metadata.gz: '065380a4b010e7a2e34c7f79ac1e1b2aae0a999e73f8a1263f47332de7257190'
4
+ data.tar.gz: 8dcad5df68d791e4411e347ae55b88a61a4fef7f93e33200cdd144716f17e087
5
5
  SHA512:
6
- metadata.gz: c4c392e07f2375665e2be172b3b0fafda12832bb49fd0e8bafa2aa99501640695847290aef6a13cdbd11077a69bba1708a5fb9532707510e00ca48cbcbefce6b
7
- data.tar.gz: bb306af730a61fdb406881ad2f090b7b60223b81830e54ff7d682573162a1d6d9745608da0ba8fcb6b4aa17c8a547c884ace734981c058b79f68e9cd23bd028b
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
@@ -1,13 +1,13 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- extralite (1.26)
4
+ extralite (2.0)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
8
8
  specs:
9
9
  docile (1.4.0)
10
- json (2.6.1)
10
+ json (2.6.3)
11
11
  minitest (5.15.0)
12
12
  rake (13.0.6)
13
13
  rake-compiler (1.1.6)
data/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2022 Sharon Rosner
3
+ Copyright (c) 2023 Sharon Rosner
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
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.40.1](https://sqlite.org/releaselog/3_40_1.html)), offering access to the
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 statements
115
- stmt = db.prepare('select ? as foo, ? as bar') #=> Extralite::PreparedStatement
116
- stmt.query(1, 2) #=> [{ :foo => 1, :bar => 2 }]
117
- # PreparedStatement offers the same data access methods as the Database class,
118
- # but without the sql parameter.
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 `PreparedStatement#status`
199
- respectively. You can also reset the high water mark for the specific status
200
- code by providing true as the reset argument. The status codes mirror those
201
- defined by the SQLite API. Some examples:
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 PreparedStatement#status method returns a single value (pass true as a
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 = stmt.status(Extralite::SQLITE_STMTSTATUS_RUN)
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
@@ -0,0 +1,5 @@
1
+ - Add option to open database in readonly mode, use sqlite3_open_v2
2
+ https://sqlite.org/c3ref/open.html
3
+
4
+ - Improve tracing
5
+ - Add `#inspect` method for `Database`, `Query`, `Iterator` classes
@@ -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(sqlite3_stmt *stmt, sqlite3 *db) {
237
- struct step_ctx ctx = {stmt, 0};
238
- rb_thread_call_without_gvl(stmt_iterate_without_gvl, (void *)&ctx, RUBY_UBF_IO, 0);
239
- switch (ctx.rc) {
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(db));
248
+ rb_raise(cSQLError, "%s", sqlite3_errmsg(ctx->sqlite3_db));
250
249
  default:
251
- rb_raise(cError, "%s", sqlite3_errmsg(db));
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 result = ctx->self;
264
- int yield_to_block = rb_block_given_p();
265
- VALUE row;
266
- int column_count;
267
- VALUE column_names;
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->stmt, ctx->sqlite3_db)) {
268
+ while (stmt_iterate(ctx)) {
276
269
  row = row_to_hash(ctx->stmt, column_count, column_names);
277
- if (yield_to_block) rb_yield(row);
278
- else rb_ary_push(result, row);
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(result);
284
- return result;
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
- int column_count;
289
- VALUE result = ctx->self;
290
- int yield_to_block = rb_block_given_p();
291
- VALUE row;
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->stmt, ctx->sqlite3_db)) {
297
+ while (stmt_iterate(ctx)) {
299
298
  row = row_to_ary(ctx->stmt, column_count);
300
- if (yield_to_block) rb_yield(row);
301
- else rb_ary_push(result, row);
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(result);
306
- return result;
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->stmt, ctx->sqlite3_db))
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
- int column_count;
327
- VALUE result = ctx->self;
328
- int yield_to_block = rb_block_given_p();
329
- VALUE value;
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
- // block not given, so prepare the array of records to be returned
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->stmt, ctx->sqlite3_db)) {
343
+ while (stmt_iterate(ctx)) {
339
344
  value = get_column_value(ctx->stmt, 0, sqlite3_column_type(ctx->stmt, 0));
340
- if (yield_to_block) rb_yield(value); else rb_ary_push(result, value);
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(result);
345
- return result;
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->stmt, ctx->sqlite3_db))
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->stmt, ctx->sqlite3_db));
389
+ while (stmt_iterate(ctx));
373
390
  changes += sqlite3_changes(ctx->sqlite3_db);
374
391
  }
375
392
 
@@ -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 a least, the query should specify parameters using
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 a least, the query should specify parameters using
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 a least, the query should specify parameters using
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 a least, the query should specify parameters using
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 a least, the query should specify parameters using
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.execute_multi_query('insert into foo values (?, ?, ?)', records)
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::PreparedStatement
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 self, VALUE sql) {
406
- return rb_funcall(cPreparedStatement, ID_new, 2, self, sql);
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");
@@ -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