extralite 0.6 → 1.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: 89b99ba7c6d26fc887574bf943f8e07f4c27f0c058a99723be73d7b64a90b3d5
4
- data.tar.gz: 41c9bc62b91d78d0507d58b6a3e150608aae813b7b929410f23b0389a8b8c98f
3
+ metadata.gz: 9c69948d51f689ead3e4ccf008bc7b0670085d0c987d39359bb38172b9993b58
4
+ data.tar.gz: 776ed4e4f95cdb405b9b3be277d0aaa5a27fcd0e84aff8f6ebb3aeece531b1cf
5
5
  SHA512:
6
- metadata.gz: 6c29b92468cdf3a2fe9f65088c579a901ff981b11c68d8dc32c99508c4ab7fae5a8d9a348e2310dcf18b0980edac9f3503889de76e097c6d27c6c3fb20480245
7
- data.tar.gz: 49c703000e9285cff90dadd0d64f40690d96a6f6c59305021dca0561d41aff6fedcc64a4918ce16337acbb7537fd91c73f2b1d65953cdff8be591773844c3465
6
+ metadata.gz: 472db7ae94e195e647712ce04213538b229bcfebc3f9ec22b3f8e69dceb0a5cbba9000e14147a27ed2e26231aa7c73b43585a45be369f94d8cd2507643dd88b9
7
+ data.tar.gz: 6475f99ebe990b9b073f77379f58ddcd4fa0f678fb32e64b39021e3aa7b6d6f9343c8f02d69cce1826a9d156c8b0918985536732af5a11be2bda28524524e8c2
data/CHANGELOG.md CHANGED
@@ -1,3 +1,9 @@
1
+ ## 1.0 2021-05-27
2
+
3
+ - Refactor C code
4
+ - Use `rb_ensure` to finalize stmt
5
+ - Remove bundled `sqlite3.h`, use system-wide header file instead
6
+
1
7
  ## 0.6 2021-05-25
2
8
 
3
9
  - Add more specific errors: `SQLError`, `BusyError`
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- extralite (0.6)
4
+ extralite (1.0)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
data/README.md CHANGED
@@ -1,17 +1,21 @@
1
1
  ## Extralite
2
2
 
3
- Extralite is an extra-lightweight SQLite3 wrapper for Ruby. It provides a single
4
- class with a minimal set of methods to interact with an SQLite3 database.
3
+ Extralite is an extra-lightweight (~365 lines of C-code) SQLite3 wrapper for
4
+ Ruby. It provides a single class with a minimal set of methods to interact with
5
+ an SQLite3 database.
5
6
 
6
7
  ### Features
7
8
 
8
- - A variety of methods for different data access patterns: row as hash, row as array, single
9
- single row, single column, single value.
9
+ - A variety of methods for different data access patterns: row as hash, row as
10
+ array, single single row, single column, single value.
10
11
  - Iterate over records with a block, or collect records into an array.
11
12
  - Parameter binding.
13
+ - Correctly execute strings with multiple semicolon-separated queries (handy for
14
+ creating/modifying schemas).
12
15
  - Get last insert rowid.
13
16
  - Get number of rows changed by last query.
14
- - Load extensions.
17
+ - Load extensions (loading of extensions is autmatically enabled. You can find
18
+ some useful extensions here: https://github.com/nalgeon/sqlean.)
15
19
 
16
20
  ### Usage
17
21
 
@@ -19,7 +23,7 @@ class with a minimal set of methods to interact with an SQLite3 database.
19
23
  require 'extralite'
20
24
 
21
25
  # open a database
22
- db = Extralite::Database.new('mydb')
26
+ db = Extralite::Database.new('/tmp/my.db')
23
27
 
24
28
  # get query results as array of hashes
25
29
  db.query('select 1 as foo') #=> [{ :foo => 1 }]
@@ -53,9 +57,33 @@ db.query_hash('select ? as foo, ? as bar', 1, 2) #=> [{ :foo => 1, :bar => 2 }]
53
57
  # get last insert rowid
54
58
  rowid = db.last_insert_id
55
59
 
56
- # get rows changed in last query
57
- rows_changed = db.changes
60
+ # get number of rows changed in last query
61
+ number_of_rows_affected = db.changes
58
62
 
59
63
  # get db filename
60
- Extralite::Database.new('/tmp/my.db').filename #=> "/tmp/my.db"
64
+ db.filename #=> "/tmp/my.db"
65
+
66
+ # load an extension
67
+ db.load_extension('/path/to/extension.so')
61
68
  ```
69
+
70
+ ### Why not just use the sqlite3 gem?
71
+
72
+ The sqlite3-ruby gem is a popular, solid, well-maintained project, used by
73
+ thousands of developers. I've been doing a lot of work with SQLite3 lately, and
74
+ wanted to have a simpler API that gives me query results in a variety of ways.
75
+ Thus extralite was born.
76
+
77
+ ### What about concurrency?
78
+
79
+ Extralite currently does not release the GVL. This means that even if queries
80
+ are executed on a separate thread, no other Ruby threads will be scheduled while
81
+ SQLite3 is busy fetching the next record.
82
+
83
+ In the future Extralite might be changed to release the GVL each time
84
+ `sqlite3_step` is called.
85
+
86
+ ### Can I use it with an ORM like ActiveRecord or Sequel?
87
+
88
+ Not yet, but you are welcome to contribute adapters for those projects. I will
89
+ be releasing my own not-an-ORM tool in the near future.
@@ -1,6 +1,6 @@
1
1
  #include <stdio.h>
2
2
  #include "ruby.h"
3
- #include "../sqlite3/sqlite3.h"
3
+ #include <sqlite3.h>
4
4
 
5
5
  VALUE cError;
6
6
  VALUE cSQLError;
@@ -165,235 +165,209 @@ inline void prepare_multi_stmt(sqlite3 *db, sqlite3_stmt **stmt, VALUE sql) {
165
165
  }
166
166
  }
167
167
 
168
- VALUE Database_query_hash(int argc, VALUE *argv, VALUE self) {
168
+ inline int stmt_iterate(sqlite3_stmt *stmt, sqlite3 *db) {
169
169
  int rc;
170
- sqlite3_stmt* stmt;
171
- int column_count;
172
- Database_t *db;
173
- VALUE result = self;
174
- int yield_to_block = rb_block_given_p();
175
- VALUE row;
176
- VALUE column_names;
177
- VALUE sql;
178
-
179
- rb_check_arity(argc, 1, UNLIMITED_ARGUMENTS);
180
- sql = rb_funcall(argv[0], ID_STRIP, 0);
181
- if (RSTRING_LEN(sql) == 0) return Qnil;
182
-
183
- GetDatabase(self, db);
184
-
185
- prepare_multi_stmt(db->sqlite3_db, &stmt, sql);
186
- bind_all_parameters(stmt, argc, argv);
187
- column_count = sqlite3_column_count(stmt);
188
- column_names = get_column_names(stmt, column_count);
189
-
190
- // block not given, so prepare the array of records to be returned
191
- if (!yield_to_block) result = rb_ary_new();
192
-
193
- step:
194
170
  rc = sqlite3_step(stmt);
195
171
  switch (rc) {
196
172
  case SQLITE_ROW:
197
- row = row_to_hash(stmt, column_count, column_names);
198
- if (yield_to_block) rb_yield(row); else rb_ary_push(result, row);
199
- goto step;
173
+ return 1;
200
174
  case SQLITE_DONE:
201
- break;
175
+ return 0;
202
176
  case SQLITE_BUSY:
203
- sqlite3_finalize(stmt);
204
177
  rb_raise(cBusyError, "Database is busy");
205
178
  case SQLITE_ERROR:
206
- sqlite3_finalize(stmt);
207
- rb_raise(cSQLError, "%s", sqlite3_errmsg(db->sqlite3_db));
179
+ rb_raise(cSQLError, "%s", sqlite3_errmsg(db));
208
180
  default:
209
- sqlite3_finalize(stmt);
210
181
  rb_raise(cError, "Invalid return code for sqlite3_step: %d", rc);
211
182
  }
212
- // TODO, use ensure to finalize statement
213
- sqlite3_finalize(stmt);
183
+
184
+ return 0;
185
+ }
186
+
187
+ typedef struct query_ctx {
188
+ VALUE self;
189
+ int argc;
190
+ VALUE *argv;
191
+ sqlite3_stmt *stmt;
192
+ } query_ctx;
193
+
194
+ VALUE cleanup_stmt(VALUE arg) {
195
+ query_ctx *ctx = (query_ctx *)arg;
196
+ sqlite3_finalize(ctx->stmt);
197
+ return Qnil;
198
+ }
199
+
200
+ #define check_arity_and_prepare_sql(argc, argv, sql) { \
201
+ rb_check_arity(argc, 1, UNLIMITED_ARGUMENTS); \
202
+ sql = rb_funcall(argv[0], ID_STRIP, 0); \
203
+ if (RSTRING_LEN(sql) == 0) return Qnil; \
204
+ }
205
+
206
+ VALUE safe_query_hash(VALUE arg) {
207
+ query_ctx *ctx = (query_ctx *)arg;
208
+ Database_t *db;
209
+ VALUE result = ctx->self;
210
+ int yield_to_block = rb_block_given_p();
211
+ VALUE row;
212
+ VALUE sql;
213
+ int column_count;
214
+ VALUE column_names;
215
+
216
+ check_arity_and_prepare_sql(ctx->argc, ctx->argv, sql);
217
+ GetDatabase(ctx->self, db);
218
+
219
+ prepare_multi_stmt(db->sqlite3_db, &ctx->stmt, sql);
220
+ bind_all_parameters(ctx->stmt, ctx->argc, ctx->argv);
221
+ column_count = sqlite3_column_count(ctx->stmt);
222
+ column_names = get_column_names(ctx->stmt, column_count);
223
+
224
+ // block not given, so prepare the array of records to be returned
225
+ if (!yield_to_block) result = rb_ary_new();
226
+
227
+ while (stmt_iterate(ctx->stmt, db->sqlite3_db)) {
228
+ row = row_to_hash(ctx->stmt, column_count, column_names);
229
+ if (yield_to_block) rb_yield(row); else rb_ary_push(result, row);
230
+ }
231
+
214
232
  RB_GC_GUARD(column_names);
215
233
  RB_GC_GUARD(row);
216
234
  RB_GC_GUARD(result);
217
235
  return result;
218
236
  }
219
237
 
220
- VALUE Database_query_ary(int argc, VALUE *argv, VALUE self) {
221
- int rc;
222
- sqlite3_stmt* stmt;
223
- int column_count;
238
+ VALUE Database_query_hash(int argc, VALUE *argv, VALUE self) {
239
+ query_ctx ctx = { self, argc, argv, 0 };
240
+ return rb_ensure(safe_query_hash, (VALUE)&ctx, cleanup_stmt, (VALUE)&ctx);
241
+ }
242
+
243
+ VALUE safe_query_ary(VALUE arg) {
244
+ query_ctx *ctx = (query_ctx *)arg;
224
245
  Database_t *db;
225
- VALUE result = self;
246
+ int column_count;
247
+ VALUE result = ctx->self;
226
248
  int yield_to_block = rb_block_given_p();
227
249
  VALUE row;
228
250
  VALUE sql;
229
251
 
230
- rb_check_arity(argc, 1, UNLIMITED_ARGUMENTS);
231
- sql = rb_funcall(argv[0], ID_STRIP, 0);
232
- if (RSTRING_LEN(sql) == 0) return Qnil;
233
- GetDatabase(self, db);
252
+ check_arity_and_prepare_sql(ctx->argc, ctx->argv, sql);
253
+ GetDatabase(ctx->self, db);
234
254
 
235
- prepare_multi_stmt(db->sqlite3_db, &stmt, sql);
236
- bind_all_parameters(stmt, argc, argv);
237
- column_count = sqlite3_column_count(stmt);
255
+ prepare_multi_stmt(db->sqlite3_db, &ctx->stmt, sql);
256
+ bind_all_parameters(ctx->stmt, ctx->argc, ctx->argv);
257
+ column_count = sqlite3_column_count(ctx->stmt);
238
258
 
239
259
  // block not given, so prepare the array of records to be returned
240
260
  if (!yield_to_block) result = rb_ary_new();
241
- step:
242
- rc = sqlite3_step(stmt);
243
- switch (rc) {
244
- case SQLITE_ROW:
245
- row = row_to_ary(stmt, column_count);
246
- if (yield_to_block) rb_yield(row); else rb_ary_push(result, row);
247
- goto step;
248
- case SQLITE_DONE:
249
- break;
250
- case SQLITE_BUSY:
251
- sqlite3_finalize(stmt);
252
- rb_raise(cBusyError, "Database is busy");
253
- case SQLITE_ERROR:
254
- sqlite3_finalize(stmt);
255
- rb_raise(cSQLError, "%s", sqlite3_errmsg(db->sqlite3_db));
256
- default:
257
- sqlite3_finalize(stmt);
258
- rb_raise(cError, "Invalid return code for sqlite3_step: %d", rc);
261
+
262
+ while (stmt_iterate(ctx->stmt, db->sqlite3_db)) {
263
+ row = row_to_ary(ctx->stmt, column_count);
264
+ if (yield_to_block) rb_yield(row); else rb_ary_push(result, row);
259
265
  }
260
- sqlite3_finalize(stmt);
266
+
261
267
  RB_GC_GUARD(row);
262
268
  RB_GC_GUARD(result);
263
269
  return result;
264
270
  }
265
271
 
266
- VALUE Database_query_single_row(int argc, VALUE *argv, VALUE self) {
267
- int rc;
268
- sqlite3_stmt* stmt;
269
- int column_count;
272
+ VALUE Database_query_ary(int argc, VALUE *argv, VALUE self) {
273
+ query_ctx ctx = { self, argc, argv, 0 };
274
+ return rb_ensure(safe_query_ary, (VALUE)&ctx, cleanup_stmt, (VALUE)&ctx);
275
+ }
276
+
277
+ VALUE safe_query_single_row(VALUE arg) {
278
+ query_ctx *ctx = (query_ctx *)arg;
270
279
  Database_t *db;
280
+ int column_count;
271
281
  VALUE sql;
272
282
  VALUE row = Qnil;
273
283
  VALUE column_names;
274
284
 
275
- rb_check_arity(argc, 1, UNLIMITED_ARGUMENTS);
276
- sql = rb_funcall(argv[0], ID_STRIP, 0);
277
- if (RSTRING_LEN(sql) == 0) return Qnil;
285
+ check_arity_and_prepare_sql(ctx->argc, ctx->argv, sql);
286
+ GetDatabase(ctx->self, db);
278
287
 
279
- GetDatabase(self, db);
288
+ prepare_multi_stmt(db->sqlite3_db, &ctx->stmt, sql);
289
+ bind_all_parameters(ctx->stmt, ctx->argc, ctx->argv);
290
+ column_count = sqlite3_column_count(ctx->stmt);
291
+ column_names = get_column_names(ctx->stmt, column_count);
280
292
 
281
- prepare_multi_stmt(db->sqlite3_db, &stmt, sql);
282
- bind_all_parameters(stmt, argc, argv);
283
- column_count = sqlite3_column_count(stmt);
284
- column_names = get_column_names(stmt, column_count);
293
+ if (stmt_iterate(ctx->stmt, db->sqlite3_db))
294
+ row = row_to_hash(ctx->stmt, column_count, column_names);
285
295
 
286
- rc = sqlite3_step(stmt);
287
- switch (rc) {
288
- case SQLITE_ROW:
289
- row = row_to_hash(stmt, column_count, column_names);
290
- break;
291
- case SQLITE_DONE:
292
- break;
293
- case SQLITE_BUSY:
294
- rb_raise(cBusyError, "Database is busy");
295
- case SQLITE_ERROR:
296
- rb_raise(cSQLError, "%s", sqlite3_errmsg(db->sqlite3_db));
297
- default:
298
- rb_raise(cError, "Invalid return code for sqlite3_step: %d", rc);
299
- }
300
-
301
- sqlite3_finalize(stmt);
302
296
  RB_GC_GUARD(row);
303
297
  RB_GC_GUARD(column_names);
304
298
  return row;
305
299
  }
306
300
 
307
- VALUE Database_query_single_column(int argc, VALUE *argv, VALUE self) {
308
- int rc;
309
- sqlite3_stmt* stmt;
301
+ VALUE Database_query_single_row(int argc, VALUE *argv, VALUE self) {
302
+ query_ctx ctx = { self, argc, argv, 0 };
303
+ return rb_ensure(safe_query_single_row, (VALUE)&ctx, cleanup_stmt, (VALUE)&ctx);
304
+ }
305
+
306
+ VALUE safe_query_single_column(VALUE arg) {
307
+ query_ctx *ctx = (query_ctx *)arg;
308
+
310
309
  int column_count;
311
310
  Database_t *db;
312
- VALUE result = self;
311
+ VALUE result = ctx->self;
313
312
  int yield_to_block = rb_block_given_p();
314
313
  VALUE sql;
315
314
  VALUE value;
316
315
 
317
- rb_check_arity(argc, 1, UNLIMITED_ARGUMENTS);
318
- sql = rb_funcall(argv[0], ID_STRIP, 0);
319
- if (RSTRING_LEN(sql) == 0) return Qnil;
316
+ check_arity_and_prepare_sql(ctx->argc, ctx->argv, sql);
317
+ GetDatabase(ctx->self, db);
320
318
 
321
- GetDatabase(self, db);
322
-
323
- prepare_multi_stmt(db->sqlite3_db, &stmt, sql);
324
- bind_all_parameters(stmt, argc, argv);
325
- column_count = sqlite3_column_count(stmt);
319
+ prepare_multi_stmt(db->sqlite3_db, &ctx->stmt, sql);
320
+ bind_all_parameters(ctx->stmt, ctx->argc, ctx->argv);
321
+ column_count = sqlite3_column_count(ctx->stmt);
326
322
  if (column_count != 1)
327
323
  rb_raise(cError, "Expected query result to have 1 column");
328
324
 
329
325
  // block not given, so prepare the array of records to be returned
330
326
  if (!yield_to_block) result = rb_ary_new();
331
- step:
332
- rc = sqlite3_step(stmt);
333
- switch (rc) {
334
- case SQLITE_ROW:
335
- value = get_column_value(stmt, 0, sqlite3_column_type(stmt, 0));
336
- if (yield_to_block) rb_yield(value); else rb_ary_push(result, value);
337
- goto step;
338
- case SQLITE_DONE:
339
- break;
340
- case SQLITE_BUSY:
341
- sqlite3_finalize(stmt);
342
- rb_raise(cBusyError, "Database is busy");
343
- case SQLITE_ERROR:
344
- sqlite3_finalize(stmt);
345
- rb_raise(cSQLError, "%s", sqlite3_errmsg(db->sqlite3_db));
346
- default:
347
- sqlite3_finalize(stmt);
348
- rb_raise(cError, "Invalid return code for sqlite3_step: %d", rc);
327
+
328
+ while (stmt_iterate(ctx->stmt, db->sqlite3_db)) {
329
+ value = get_column_value(ctx->stmt, 0, sqlite3_column_type(ctx->stmt, 0));
330
+ if (yield_to_block) rb_yield(value); else rb_ary_push(result, value);
349
331
  }
350
332
 
351
- sqlite3_finalize(stmt);
352
333
  RB_GC_GUARD(value);
353
334
  RB_GC_GUARD(result);
354
335
  return result;
355
336
  }
356
337
 
357
- VALUE Database_query_single_value(int argc, VALUE *argv, VALUE self) {
358
- int rc;
359
- sqlite3_stmt* stmt;
338
+ VALUE Database_query_single_column(int argc, VALUE *argv, VALUE self) {
339
+ query_ctx ctx = { self, argc, argv, 0 };
340
+ return rb_ensure(safe_query_single_column, (VALUE)&ctx, cleanup_stmt, (VALUE)&ctx);
341
+ }
342
+
343
+ VALUE safe_query_single_value(VALUE arg) {
344
+ query_ctx *ctx = (query_ctx *)arg;
360
345
  int column_count;
361
346
  Database_t *db;
362
347
  VALUE sql;
363
348
  VALUE value = Qnil;
364
349
 
365
- rb_check_arity(argc, 1, UNLIMITED_ARGUMENTS);
366
- sql = rb_funcall(argv[0], ID_STRIP, 0);
367
- if (RSTRING_LEN(sql) == 0) return Qnil;
368
-
369
- GetDatabase(self, db);
350
+ check_arity_and_prepare_sql(ctx->argc, ctx->argv, sql);
351
+ GetDatabase(ctx->self, db);
370
352
 
371
- prepare_multi_stmt(db->sqlite3_db, &stmt, sql);
372
- bind_all_parameters(stmt, argc, argv);
373
- column_count = sqlite3_column_count(stmt);
353
+ prepare_multi_stmt(db->sqlite3_db, &ctx->stmt, sql);
354
+ bind_all_parameters(ctx->stmt, ctx->argc, ctx->argv);
355
+ column_count = sqlite3_column_count(ctx->stmt);
374
356
  if (column_count != 1)
375
357
  rb_raise(cError, "Expected query result to have 1 column");
376
358
 
377
- rc = sqlite3_step(stmt);
378
- switch (rc) {
379
- case SQLITE_ROW:
380
- value = get_column_value(stmt, 0, sqlite3_column_type(stmt, 0));
381
- break;
382
- case SQLITE_DONE:
383
- break;
384
- case SQLITE_BUSY:
385
- rb_raise(cBusyError, "Database is busy");
386
- case SQLITE_ERROR:
387
- rb_raise(cSQLError, "%s", sqlite3_errmsg(db->sqlite3_db));
388
- default:
389
- rb_raise(cError, "Invalid return code for sqlite3_step: %d", rc);
390
- }
359
+ if (stmt_iterate(ctx->stmt, db->sqlite3_db))
360
+ value = get_column_value(ctx->stmt, 0, sqlite3_column_type(ctx->stmt, 0));
391
361
 
392
- sqlite3_finalize(stmt);
393
362
  RB_GC_GUARD(value);
394
363
  return value;
395
364
  }
396
365
 
366
+ VALUE Database_query_single_value(int argc, VALUE *argv, VALUE self) {
367
+ query_ctx ctx = { self, argc, argv, 0 };
368
+ return rb_ensure(safe_query_single_value, (VALUE)&ctx, cleanup_stmt, (VALUE)&ctx);
369
+ }
370
+
397
371
  VALUE Database_last_insert_rowid(VALUE self) {
398
372
  Database_t *db;
399
373
  GetDatabase(self, db);