extralite 0.6 → 1.3

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: b9cad52b4d9e4807384a8e1b3f9b9ab521c539cbc705bcf475fb24f8e3aa462c
4
+ data.tar.gz: '04792f9ce5e23c21509a7de369152501972e4a20167988f12211aa6d23b901a3'
5
5
  SHA512:
6
- metadata.gz: 6c29b92468cdf3a2fe9f65088c579a901ff981b11c68d8dc32c99508c4ab7fae5a8d9a348e2310dcf18b0980edac9f3503889de76e097c6d27c6c3fb20480245
7
- data.tar.gz: 49c703000e9285cff90dadd0d64f40690d96a6f6c59305021dca0561d41aff6fedcc64a4918ce16337acbb7537fd91c73f2b1d65953cdff8be591773844c3465
6
+ metadata.gz: 1e8fac21c746aa4fe8ebd8b09f12bf8c4fb71f2f888af948f90b83ae26fdebbaec67c9e1d5d4c7fda93e22152631579e6aa6cf37d8d6352492dd6ff9e5a1d255
7
+ data.tar.gz: 9a9df25229c0939df53f047426bd7df56359e81e187b0ba0ffd98ff1e8c5fd676ac828ebba01aae66524391166968b12b46ebea625775b1414f018ff5f161b08
data/CHANGELOG.md CHANGED
@@ -1,3 +1,21 @@
1
+ ## 1.3 2021-08-17
2
+
3
+ - Pin error classes (for better compatibility with `GC.compact`)
4
+
5
+ ## 1.2 2021-06-06
6
+
7
+ - Add support for big integers
8
+
9
+ ## 1.1 2021-06-02
10
+
11
+ - Add `#close`, `#closed?` methods
12
+
13
+ ## 1.0 2021-05-27
14
+
15
+ - Refactor C code
16
+ - Use `rb_ensure` to finalize stmt
17
+ - Remove bundled `sqlite3.h`, use system-wide header file instead
18
+
1
19
  ## 0.6 2021-05-25
2
20
 
3
21
  - 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.3)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
data/README.md CHANGED
@@ -1,25 +1,35 @@
1
- ## Extralite
1
+ # Extralite - a Ruby gem for working with SQLite3 databases
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
+ [![Gem Version](https://badge.fury.io/rb/extralite.svg)](http://rubygems.org/gems/extralite)
4
+ [![Modulation Test](https://github.com/digital-fabric/extralite/workflows/Tests/badge.svg)](https://github.com/digital-fabric/extralite/actions?query=workflow%3ATests)
5
+ [![MIT licensed](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/digital-fabric/extralite/blob/master/LICENSE)
5
6
 
6
- ### Features
7
+ ## What is Extralite?
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
+ Extralite is an extra-lightweight (less than 400 lines of C-code) SQLite3 wrapper for
10
+ Ruby. It provides a single class with a minimal set of methods to interact with
11
+ an SQLite3 database.
12
+
13
+ ## Features
14
+
15
+ - A variety of methods for different data access patterns: row as hash, row as
16
+ array, single single row, single column, single value.
10
17
  - Iterate over records with a block, or collect records into an array.
11
18
  - Parameter binding.
19
+ - Correctly execute strings with multiple semicolon-separated queries (handy for
20
+ creating/modifying schemas).
12
21
  - Get last insert rowid.
13
22
  - Get number of rows changed by last query.
14
- - Load extensions.
23
+ - Load extensions (loading of extensions is autmatically enabled. You can find
24
+ some useful extensions here: https://github.com/nalgeon/sqlean.)
15
25
 
16
- ### Usage
26
+ ## Usage
17
27
 
18
28
  ```ruby
19
29
  require 'extralite'
20
30
 
21
31
  # open a database
22
- db = Extralite::Database.new('mydb')
32
+ db = Extralite::Database.new('/tmp/my.db')
23
33
 
24
34
  # get query results as array of hashes
25
35
  db.query('select 1 as foo') #=> [{ :foo => 1 }]
@@ -53,9 +63,37 @@ db.query_hash('select ? as foo, ? as bar', 1, 2) #=> [{ :foo => 1, :bar => 2 }]
53
63
  # get last insert rowid
54
64
  rowid = db.last_insert_id
55
65
 
56
- # get rows changed in last query
57
- rows_changed = db.changes
66
+ # get number of rows changed in last query
67
+ number_of_rows_affected = db.changes
58
68
 
59
69
  # get db filename
60
- Extralite::Database.new('/tmp/my.db').filename #=> "/tmp/my.db"
70
+ db.filename #=> "/tmp/my.db"
71
+
72
+ # load an extension
73
+ db.load_extension('/path/to/extension.so')
74
+
75
+ # close database
76
+ db.close
77
+ db.closed? #=> true
61
78
  ```
79
+
80
+ ## Why not just use the sqlite3 gem?
81
+
82
+ The sqlite3-ruby gem is a popular, solid, well-maintained project, used by
83
+ thousands of developers. I've been doing a lot of work with SQLite3 lately, and
84
+ wanted to have a simpler API that gives me query results in a variety of ways.
85
+ Thus extralite was born.
86
+
87
+ ## What about concurrency?
88
+
89
+ Extralite currently does not release the GVL. This means that even if queries
90
+ are executed on a separate thread, no other Ruby threads will be scheduled while
91
+ SQLite3 is busy fetching the next record.
92
+
93
+ In the future Extralite might be changed to release the GVL each time
94
+ `sqlite3_step` is called.
95
+
96
+ ## Can I use it with an ORM like ActiveRecord or Sequel?
97
+
98
+ Not yet, but you are welcome to contribute adapters for those projects. I will
99
+ 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;
@@ -39,6 +39,14 @@ static VALUE Database_allocate(VALUE klass) {
39
39
  #define GetDatabase(obj, database) \
40
40
  TypedData_Get_Struct((obj), Database_t, &Database_type, (database))
41
41
 
42
+ // make sure the database is open
43
+ #define GetOpenDatabase(obj, database) { \
44
+ TypedData_Get_Struct((obj), Database_t, &Database_type, (database)); \
45
+ if (!(database)->sqlite3_db) { \
46
+ rb_raise(cError, "Database is closed"); \
47
+ } \
48
+ }
49
+
42
50
 
43
51
  VALUE Database_initialize(VALUE self, VALUE path) {
44
52
  int rc;
@@ -60,12 +68,33 @@ VALUE Database_initialize(VALUE self, VALUE path) {
60
68
  return Qnil;
61
69
  }
62
70
 
71
+ VALUE Database_close(VALUE self) {
72
+ int rc;
73
+ Database_t *db;
74
+ GetDatabase(self, db);
75
+
76
+ rc = sqlite3_close(db->sqlite3_db);
77
+ if (rc) {
78
+ rb_raise(cError, "%s", sqlite3_errmsg(db->sqlite3_db));
79
+ }
80
+
81
+ db->sqlite3_db = 0;
82
+ return self;
83
+ }
84
+
85
+ VALUE Database_closed_p(VALUE self) {
86
+ Database_t *db;
87
+ GetDatabase(self, db);
88
+
89
+ return db->sqlite3_db ? Qfalse : Qtrue;
90
+ }
91
+
63
92
  inline VALUE get_column_value(sqlite3_stmt *stmt, int col, int type) {
64
93
  switch (type) {
65
94
  case SQLITE_NULL:
66
95
  return Qnil;
67
96
  case SQLITE_INTEGER:
68
- return INT2NUM(sqlite3_column_int(stmt, col));
97
+ return LL2NUM(sqlite3_column_int64(stmt, col));
69
98
  case SQLITE_FLOAT:
70
99
  return DBL2NUM(sqlite3_column_double(stmt, col));
71
100
  case SQLITE_TEXT:
@@ -85,7 +114,7 @@ static inline void bind_parameter_value(sqlite3_stmt *stmt, int pos, VALUE value
85
114
  sqlite3_bind_null(stmt, pos);
86
115
  return;
87
116
  case T_FIXNUM:
88
- sqlite3_bind_int(stmt, pos, NUM2INT(value));
117
+ sqlite3_bind_int64(stmt, pos, NUM2LL(value));
89
118
  return;
90
119
  case T_FLOAT:
91
120
  sqlite3_bind_double(stmt, pos, NUM2DBL(value));
@@ -165,245 +194,219 @@ inline void prepare_multi_stmt(sqlite3 *db, sqlite3_stmt **stmt, VALUE sql) {
165
194
  }
166
195
  }
167
196
 
168
- VALUE Database_query_hash(int argc, VALUE *argv, VALUE self) {
197
+ inline int stmt_iterate(sqlite3_stmt *stmt, sqlite3 *db) {
169
198
  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
199
  rc = sqlite3_step(stmt);
195
200
  switch (rc) {
196
201
  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;
202
+ return 1;
200
203
  case SQLITE_DONE:
201
- break;
204
+ return 0;
202
205
  case SQLITE_BUSY:
203
- sqlite3_finalize(stmt);
204
206
  rb_raise(cBusyError, "Database is busy");
205
207
  case SQLITE_ERROR:
206
- sqlite3_finalize(stmt);
207
- rb_raise(cSQLError, "%s", sqlite3_errmsg(db->sqlite3_db));
208
+ rb_raise(cSQLError, "%s", sqlite3_errmsg(db));
208
209
  default:
209
- sqlite3_finalize(stmt);
210
210
  rb_raise(cError, "Invalid return code for sqlite3_step: %d", rc);
211
211
  }
212
- // TODO, use ensure to finalize statement
213
- sqlite3_finalize(stmt);
212
+
213
+ return 0;
214
+ }
215
+
216
+ typedef struct query_ctx {
217
+ VALUE self;
218
+ int argc;
219
+ VALUE *argv;
220
+ sqlite3_stmt *stmt;
221
+ } query_ctx;
222
+
223
+ VALUE cleanup_stmt(VALUE arg) {
224
+ query_ctx *ctx = (query_ctx *)arg;
225
+ sqlite3_finalize(ctx->stmt);
226
+ return Qnil;
227
+ }
228
+
229
+ #define check_arity_and_prepare_sql(argc, argv, sql) { \
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
+ }
234
+
235
+ VALUE safe_query_hash(VALUE arg) {
236
+ query_ctx *ctx = (query_ctx *)arg;
237
+ Database_t *db;
238
+ VALUE result = ctx->self;
239
+ int yield_to_block = rb_block_given_p();
240
+ VALUE row;
241
+ VALUE sql;
242
+ int column_count;
243
+ VALUE column_names;
244
+
245
+ check_arity_and_prepare_sql(ctx->argc, ctx->argv, sql);
246
+ GetOpenDatabase(ctx->self, db);
247
+
248
+ prepare_multi_stmt(db->sqlite3_db, &ctx->stmt, sql);
249
+ bind_all_parameters(ctx->stmt, ctx->argc, ctx->argv);
250
+ column_count = sqlite3_column_count(ctx->stmt);
251
+ column_names = get_column_names(ctx->stmt, column_count);
252
+
253
+ // block not given, so prepare the array of records to be returned
254
+ if (!yield_to_block) result = rb_ary_new();
255
+
256
+ while (stmt_iterate(ctx->stmt, db->sqlite3_db)) {
257
+ row = row_to_hash(ctx->stmt, column_count, column_names);
258
+ if (yield_to_block) rb_yield(row); else rb_ary_push(result, row);
259
+ }
260
+
214
261
  RB_GC_GUARD(column_names);
215
262
  RB_GC_GUARD(row);
216
263
  RB_GC_GUARD(result);
217
264
  return result;
218
265
  }
219
266
 
220
- VALUE Database_query_ary(int argc, VALUE *argv, VALUE self) {
221
- int rc;
222
- sqlite3_stmt* stmt;
223
- int column_count;
267
+ VALUE Database_query_hash(int argc, VALUE *argv, VALUE self) {
268
+ query_ctx ctx = { self, argc, argv, 0 };
269
+ return rb_ensure(safe_query_hash, (VALUE)&ctx, cleanup_stmt, (VALUE)&ctx);
270
+ }
271
+
272
+ VALUE safe_query_ary(VALUE arg) {
273
+ query_ctx *ctx = (query_ctx *)arg;
224
274
  Database_t *db;
225
- VALUE result = self;
275
+ int column_count;
276
+ VALUE result = ctx->self;
226
277
  int yield_to_block = rb_block_given_p();
227
278
  VALUE row;
228
279
  VALUE sql;
229
280
 
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);
281
+ check_arity_and_prepare_sql(ctx->argc, ctx->argv, sql);
282
+ GetOpenDatabase(ctx->self, db);
234
283
 
235
- prepare_multi_stmt(db->sqlite3_db, &stmt, sql);
236
- bind_all_parameters(stmt, argc, argv);
237
- column_count = sqlite3_column_count(stmt);
284
+ prepare_multi_stmt(db->sqlite3_db, &ctx->stmt, sql);
285
+ bind_all_parameters(ctx->stmt, ctx->argc, ctx->argv);
286
+ column_count = sqlite3_column_count(ctx->stmt);
238
287
 
239
288
  // block not given, so prepare the array of records to be returned
240
289
  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);
290
+
291
+ while (stmt_iterate(ctx->stmt, db->sqlite3_db)) {
292
+ row = row_to_ary(ctx->stmt, column_count);
293
+ if (yield_to_block) rb_yield(row); else rb_ary_push(result, row);
259
294
  }
260
- sqlite3_finalize(stmt);
295
+
261
296
  RB_GC_GUARD(row);
262
297
  RB_GC_GUARD(result);
263
298
  return result;
264
299
  }
265
300
 
266
- VALUE Database_query_single_row(int argc, VALUE *argv, VALUE self) {
267
- int rc;
268
- sqlite3_stmt* stmt;
269
- int column_count;
301
+ VALUE Database_query_ary(int argc, VALUE *argv, VALUE self) {
302
+ query_ctx ctx = { self, argc, argv, 0 };
303
+ return rb_ensure(safe_query_ary, (VALUE)&ctx, cleanup_stmt, (VALUE)&ctx);
304
+ }
305
+
306
+ VALUE safe_query_single_row(VALUE arg) {
307
+ query_ctx *ctx = (query_ctx *)arg;
270
308
  Database_t *db;
309
+ int column_count;
271
310
  VALUE sql;
272
311
  VALUE row = Qnil;
273
312
  VALUE column_names;
274
313
 
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;
314
+ check_arity_and_prepare_sql(ctx->argc, ctx->argv, sql);
315
+ GetOpenDatabase(ctx->self, db);
278
316
 
279
- GetDatabase(self, db);
317
+ prepare_multi_stmt(db->sqlite3_db, &ctx->stmt, sql);
318
+ bind_all_parameters(ctx->stmt, ctx->argc, ctx->argv);
319
+ column_count = sqlite3_column_count(ctx->stmt);
320
+ column_names = get_column_names(ctx->stmt, column_count);
280
321
 
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);
322
+ if (stmt_iterate(ctx->stmt, db->sqlite3_db))
323
+ row = row_to_hash(ctx->stmt, column_count, column_names);
285
324
 
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
325
  RB_GC_GUARD(row);
303
326
  RB_GC_GUARD(column_names);
304
327
  return row;
305
328
  }
306
329
 
307
- VALUE Database_query_single_column(int argc, VALUE *argv, VALUE self) {
308
- int rc;
309
- sqlite3_stmt* stmt;
330
+ VALUE Database_query_single_row(int argc, VALUE *argv, VALUE self) {
331
+ query_ctx ctx = { self, argc, argv, 0 };
332
+ return rb_ensure(safe_query_single_row, (VALUE)&ctx, cleanup_stmt, (VALUE)&ctx);
333
+ }
334
+
335
+ VALUE safe_query_single_column(VALUE arg) {
336
+ query_ctx *ctx = (query_ctx *)arg;
337
+
310
338
  int column_count;
311
339
  Database_t *db;
312
- VALUE result = self;
340
+ VALUE result = ctx->self;
313
341
  int yield_to_block = rb_block_given_p();
314
342
  VALUE sql;
315
343
  VALUE value;
316
344
 
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;
320
-
321
- GetDatabase(self, db);
345
+ check_arity_and_prepare_sql(ctx->argc, ctx->argv, sql);
346
+ GetOpenDatabase(ctx->self, db);
322
347
 
323
- prepare_multi_stmt(db->sqlite3_db, &stmt, sql);
324
- bind_all_parameters(stmt, argc, argv);
325
- column_count = sqlite3_column_count(stmt);
348
+ prepare_multi_stmt(db->sqlite3_db, &ctx->stmt, sql);
349
+ bind_all_parameters(ctx->stmt, ctx->argc, ctx->argv);
350
+ column_count = sqlite3_column_count(ctx->stmt);
326
351
  if (column_count != 1)
327
352
  rb_raise(cError, "Expected query result to have 1 column");
328
353
 
329
354
  // block not given, so prepare the array of records to be returned
330
355
  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);
356
+
357
+ while (stmt_iterate(ctx->stmt, db->sqlite3_db)) {
358
+ value = get_column_value(ctx->stmt, 0, sqlite3_column_type(ctx->stmt, 0));
359
+ if (yield_to_block) rb_yield(value); else rb_ary_push(result, value);
349
360
  }
350
361
 
351
- sqlite3_finalize(stmt);
352
362
  RB_GC_GUARD(value);
353
363
  RB_GC_GUARD(result);
354
364
  return result;
355
365
  }
356
366
 
357
- VALUE Database_query_single_value(int argc, VALUE *argv, VALUE self) {
358
- int rc;
359
- sqlite3_stmt* stmt;
367
+ VALUE Database_query_single_column(int argc, VALUE *argv, VALUE self) {
368
+ query_ctx ctx = { self, argc, argv, 0 };
369
+ return rb_ensure(safe_query_single_column, (VALUE)&ctx, cleanup_stmt, (VALUE)&ctx);
370
+ }
371
+
372
+ VALUE safe_query_single_value(VALUE arg) {
373
+ query_ctx *ctx = (query_ctx *)arg;
360
374
  int column_count;
361
375
  Database_t *db;
362
376
  VALUE sql;
363
377
  VALUE value = Qnil;
364
378
 
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);
379
+ check_arity_and_prepare_sql(ctx->argc, ctx->argv, sql);
380
+ GetOpenDatabase(ctx->self, db);
370
381
 
371
- prepare_multi_stmt(db->sqlite3_db, &stmt, sql);
372
- bind_all_parameters(stmt, argc, argv);
373
- column_count = sqlite3_column_count(stmt);
382
+ prepare_multi_stmt(db->sqlite3_db, &ctx->stmt, sql);
383
+ bind_all_parameters(ctx->stmt, ctx->argc, ctx->argv);
384
+ column_count = sqlite3_column_count(ctx->stmt);
374
385
  if (column_count != 1)
375
386
  rb_raise(cError, "Expected query result to have 1 column");
376
387
 
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
- }
388
+ if (stmt_iterate(ctx->stmt, db->sqlite3_db))
389
+ value = get_column_value(ctx->stmt, 0, sqlite3_column_type(ctx->stmt, 0));
391
390
 
392
- sqlite3_finalize(stmt);
393
391
  RB_GC_GUARD(value);
394
392
  return value;
395
393
  }
396
394
 
395
+ VALUE Database_query_single_value(int argc, VALUE *argv, VALUE self) {
396
+ query_ctx ctx = { self, argc, argv, 0 };
397
+ return rb_ensure(safe_query_single_value, (VALUE)&ctx, cleanup_stmt, (VALUE)&ctx);
398
+ }
399
+
397
400
  VALUE Database_last_insert_rowid(VALUE self) {
398
401
  Database_t *db;
399
- GetDatabase(self, db);
402
+ GetOpenDatabase(self, db);
400
403
 
401
404
  return INT2NUM(sqlite3_last_insert_rowid(db->sqlite3_db));
402
405
  }
403
406
 
404
407
  VALUE Database_changes(VALUE self) {
405
408
  Database_t *db;
406
- GetDatabase(self, db);
409
+ GetOpenDatabase(self, db);
407
410
 
408
411
  return INT2NUM(sqlite3_changes(db->sqlite3_db));
409
412
  }
@@ -412,7 +415,7 @@ VALUE Database_filename(int argc, VALUE *argv, VALUE self) {
412
415
  const char *db_name;
413
416
  const char *filename;
414
417
  Database_t *db;
415
- GetDatabase(self, db);
418
+ GetOpenDatabase(self, db);
416
419
 
417
420
  rb_check_arity(argc, 0, 1);
418
421
  db_name = (argc == 1) ? StringValueCStr(argv[0]) : "main";
@@ -422,14 +425,14 @@ VALUE Database_filename(int argc, VALUE *argv, VALUE self) {
422
425
 
423
426
  VALUE Database_transaction_active_p(VALUE self) {
424
427
  Database_t *db;
425
- GetDatabase(self, db);
428
+ GetOpenDatabase(self, db);
426
429
 
427
430
  return sqlite3_get_autocommit(db->sqlite3_db) ? Qfalse : Qtrue;
428
431
  }
429
432
 
430
433
  VALUE Database_load_extension(VALUE self, VALUE path) {
431
434
  Database_t *db;
432
- GetDatabase(self, db);
435
+ GetOpenDatabase(self, db);
433
436
  char *err_msg;
434
437
 
435
438
  int rc = sqlite3_load_extension(db->sqlite3_db, RSTRING_PTR(path), 0, &err_msg);
@@ -448,6 +451,8 @@ void Init_Extralite() {
448
451
  rb_define_alloc_func(cDatabase, Database_allocate);
449
452
 
450
453
  rb_define_method(cDatabase, "initialize", Database_initialize, 1);
454
+ rb_define_method(cDatabase, "close", Database_close, 0);
455
+ rb_define_method(cDatabase, "closed?", Database_closed_p, 0);
451
456
 
452
457
  rb_define_method(cDatabase, "query", Database_query_hash, -1);
453
458
  rb_define_method(cDatabase, "query_hash", Database_query_hash, -1);
@@ -465,6 +470,9 @@ void Init_Extralite() {
465
470
  cError = rb_define_class_under(mExtralite, "Error", rb_eRuntimeError);
466
471
  cSQLError = rb_define_class_under(mExtralite, "SQLError", cError);
467
472
  cBusyError = rb_define_class_under(mExtralite, "BusyError", cError);
473
+ rb_gc_register_mark_object(cError);
474
+ rb_gc_register_mark_object(cSQLError);
475
+ rb_gc_register_mark_object(cBusyError);
468
476
 
469
477
  ID_STRIP = rb_intern("strip");
470
478
  }