extralite 0.6 → 1.3

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: 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
  }