extralite 0.6 → 1.0

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: 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);