extralite 1.27 → 2.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a027328ec4af0d01bc81706f4f8e1ea08770a1341d8a36953882244b2d20ed88
4
- data.tar.gz: 916fabb4a5328e93bcdcb8121b54aa3e958b87915e9b1c9027cccb44941837b9
3
+ metadata.gz: bd8cf56eff701ddf57586d6f7510d5959cdf81ff15e6a64c6ed8ff698f93b43b
4
+ data.tar.gz: 42000f8c83849577fd1672996969961357c5bbadb82d89d2168c74be7acfa303
5
5
  SHA512:
6
- metadata.gz: fc4cd60e38d6e229d5d28ab6e28db3a16f7ddf1ffde53e3f5a97cacebe607158604421016bab34fa5bf96659ab4d0074fffb0f51712e4429d5529f59bbcdec0d
7
- data.tar.gz: db9905c1c839a4135fe4c1156b3991f8cad5ac438b5c70091be34e8afee7563371e19afe45cd835cec2f83dd26fc92e5855bf7c53c6ee473728b80caabb3e5e1
6
+ metadata.gz: 1159c965d8ce9bfade2c81de97b6ef311dcfdb84ce65eb85238aa6a23b29b786d31de2bb8f30f87a24a800b88d01475a7cb400b97245401d587bef365b140087
7
+ data.tar.gz: 74f975529a9efb7291524eaa4c285fadc6976377a607cd28e72cbd3010cbc9fd061264aabb9a5df1ce864f097ca330c9acd9dded62b8de7cc63c1217be88412a
data/CHANGELOG.md CHANGED
@@ -1,3 +1,17 @@
1
+ # 2.1 2023-07-11
2
+
3
+ - Implement `Database#execute`, `Query#execute` for data-manipulation queries
4
+ - Add option for opening databases for read only access
5
+
6
+ # 2.0 2023-07-08
7
+
8
+ - Fix Sequel migrations (#8)
9
+ - Redesign prepared statement functionality (#24)
10
+ - Rewrite `Extralite::PreparedStatement` into `Extralite::Query` with breaking API changes
11
+ - Add `Extralite::Iterator` class for external iteration
12
+ - Add `Query#each_xxx`, `Query#to_a_xxx` method
13
+ - Add `Query#eof?` method
14
+
1
15
  # 1.27 2023-06-12
2
16
 
3
17
  - Fix execution of prepared statements in Sequel adapter (#23 @gschlager)
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- extralite (1.27)
4
+ extralite (2.1)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
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,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 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,21 @@
1
+ - Improve tracing
2
+ - Transactions and savepoints:
3
+
4
+ - `DB#transaction {}` - does a `BEGIN..COMMIT` - non-reentrant!
5
+ - `DB#savepoint(name)` - creates a savepoint
6
+ - `DB#release(name)` - releases a savepoint
7
+ - `DB#rollback` - raises `Extralite::Rollback`, which is rescued by `DB#transaction`
8
+ - `DB#rollback_to(name)` - rolls back to a savepoint
9
+
10
+ - More database methods:
11
+
12
+ - `Database#quote`
13
+ - `Database#busy_timeout=` https://sqlite.org/c3ref/busy_timeout.html
14
+ - `Database#cache_flush` https://sqlite.org/c3ref/db_cacheflush.html
15
+ - `Database#release_memory` https://sqlite.org/c3ref/db_release_memory.html
16
+
17
+ - Security
18
+
19
+ - Enable extension loading by using
20
+ [SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION](https://www.sqlite.org/c3ref/c_dbconfig_defensive.html#sqlitedbconfigenableloadextension)
21
+ in order to prevent usage of `load_extension()` SQL function.
@@ -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
 
@@ -379,3 +396,8 @@ VALUE safe_execute_multi(query_ctx *ctx) {
379
396
  VALUE safe_query_columns(query_ctx *ctx) {
380
397
  return get_column_names(ctx->stmt, sqlite3_column_count(ctx->stmt));
381
398
  }
399
+
400
+ VALUE safe_query_changes(query_ctx *ctx) {
401
+ while (stmt_iterate(ctx));
402
+ return INT2FIX(sqlite3_changes(ctx->sqlite3_db));
403
+ }