extralite 0.4 → 1.2

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: 8cdf5b562230dd1dc2e3dcc4083836c6d61ac4bfd51fdc12d7129ff57dc36148
4
- data.tar.gz: 1db604b9d586ed4f3ca45516ad25ed73c4d668407616c5657372074a7d0d13a8
3
+ metadata.gz: a6bb308b421895a192cdbc83d022e105ec1fbd4060d8f8898a71f9f13741bd18
4
+ data.tar.gz: b044f3d91692b19f7c0cc5912896cec1458c3101ebb5f058fe09c147a3cf00ea
5
5
  SHA512:
6
- metadata.gz: bca80a7a86e23651cf7c672a74bbc423b696b080155a1932378af4028d282260b079754d99c0e6e52e1ab7bd2ca9417c67fe285af8bcd7e090c9b4d764f0f6a5
7
- data.tar.gz: 74f35b4542eda30a6944403b1ea46d329a64deacf75a023bdae1ff718f7b0cce01278ffbc1e67f2a7a865d8fca44788454329d2aaedad69b402692c059ca8e90
6
+ metadata.gz: 4bb40f58b77290b083466563465232cc3aadfa8cb68d305d08edc7fd4b1344d256af45f84f7efe8765fe00b604010a071b5966fd15abb63896b7d843337ce683
7
+ data.tar.gz: b29ee3ff34f307d9f45515971ecd0b147fe236f6c2d95e4b48cdd1a91b3d9847d477fee2c667836f83db5c68f559a0f4f2129d12b8d5d1fe5f075ca2cdd6d05c
data/CHANGELOG.md CHANGED
@@ -1,3 +1,25 @@
1
+ ## 1.2 2021-06-06
2
+
3
+ - Add support for big integers
4
+
5
+ ## 1.1 2021-06-02
6
+
7
+ - Add `#close`, `#closed?` methods
8
+
9
+ ## 1.0 2021-05-27
10
+
11
+ - Refactor C code
12
+ - Use `rb_ensure` to finalize stmt
13
+ - Remove bundled `sqlite3.h`, use system-wide header file instead
14
+
15
+ ## 0.6 2021-05-25
16
+
17
+ - Add more specific errors: `SQLError`, `BusyError`
18
+
19
+ ## 0.5 2021-05-25
20
+
21
+ - Implement `Database#query_single_row`
22
+
1
23
  ## 0.4 2021-05-24
2
24
 
3
25
  - Add support for loading extensions
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- extralite (0.4)
4
+ extralite (1.2)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
data/README.md CHANGED
@@ -1,24 +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 ways to get back query results: row as hash, row as array, single
9
- 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.
23
+ - Load extensions (loading of extensions is autmatically enabled. You can find
24
+ some useful extensions here: https://github.com/nalgeon/sqlean.)
14
25
 
15
- ### Usage
26
+ ## Usage
16
27
 
17
28
  ```ruby
18
29
  require 'extralite'
19
30
 
20
31
  # open a database
21
- db = Extralite::Database.new('mydb')
32
+ db = Extralite::Database.new('/tmp/my.db')
22
33
 
23
34
  # get query results as array of hashes
24
35
  db.query('select 1 as foo') #=> [{ :foo => 1 }]
@@ -34,6 +45,9 @@ db.query_ary('select 1, 2, 3') #=> [[1, 2, 3]]
34
45
  db.query_ary('select 1, 2, 3') { |r| p r }
35
46
  # [1, 2, 3]
36
47
 
48
+ # get a single row as a hash
49
+ db.query_single_row("select 1 as foo") #=> { :foo => 1 }
50
+
37
51
  # get single column query results as array of values
38
52
  db.query_single_column('select 42') #=> [42]
39
53
  # or iterate over results
@@ -49,9 +63,37 @@ db.query_hash('select ? as foo, ? as bar', 1, 2) #=> [{ :foo => 1, :bar => 2 }]
49
63
  # get last insert rowid
50
64
  rowid = db.last_insert_id
51
65
 
52
- # get rows changed in last query
53
- rows_changed = db.changes
66
+ # get number of rows changed in last query
67
+ number_of_rows_affected = db.changes
54
68
 
55
69
  # get db filename
56
- 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
57
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,8 +1,11 @@
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
+ VALUE cSQLError;
7
+ VALUE cBusyError;
8
+ ID ID_STRIP;
6
9
 
7
10
  typedef struct Database_t {
8
11
  sqlite3 *sqlite3_db;
@@ -36,6 +39,14 @@ static VALUE Database_allocate(VALUE klass) {
36
39
  #define GetDatabase(obj, database) \
37
40
  TypedData_Get_Struct((obj), Database_t, &Database_type, (database))
38
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
+
39
50
 
40
51
  VALUE Database_initialize(VALUE self, VALUE path) {
41
52
  int rc;
@@ -57,12 +68,33 @@ VALUE Database_initialize(VALUE self, VALUE path) {
57
68
  return Qnil;
58
69
  }
59
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
+
60
92
  inline VALUE get_column_value(sqlite3_stmt *stmt, int col, int type) {
61
93
  switch (type) {
62
94
  case SQLITE_NULL:
63
95
  return Qnil;
64
96
  case SQLITE_INTEGER:
65
- return INT2NUM(sqlite3_column_int(stmt, col));
97
+ return LL2NUM(sqlite3_column_int64(stmt, col));
66
98
  case SQLITE_FLOAT:
67
99
  return DBL2NUM(sqlite3_column_double(stmt, col));
68
100
  case SQLITE_TEXT:
@@ -82,7 +114,7 @@ static inline void bind_parameter_value(sqlite3_stmt *stmt, int pos, VALUE value
82
114
  sqlite3_bind_null(stmt, pos);
83
115
  return;
84
116
  case T_FIXNUM:
85
- sqlite3_bind_int(stmt, pos, NUM2INT(value));
117
+ sqlite3_bind_int64(stmt, pos, NUM2LL(value));
86
118
  return;
87
119
  case T_FLOAT:
88
120
  sqlite3_bind_double(stmt, pos, NUM2DBL(value));
@@ -127,6 +159,15 @@ static inline VALUE row_to_hash(sqlite3_stmt *stmt, int column_count, VALUE colu
127
159
  return row;
128
160
  }
129
161
 
162
+ static inline VALUE row_to_ary(sqlite3_stmt *stmt, int column_count) {
163
+ VALUE row = rb_ary_new2(column_count);
164
+ for (int i = 0; i < column_count; i++) {
165
+ VALUE value = get_column_value(stmt, i, sqlite3_column_type(stmt, i));
166
+ rb_ary_push(row, value);
167
+ }
168
+ return row;
169
+ }
170
+
130
171
  inline void prepare_multi_stmt(sqlite3 *db, sqlite3_stmt **stmt, VALUE sql) {
131
172
  const char *rest = 0;
132
173
  const char *ptr = RSTRING_PTR(sql);
@@ -135,7 +176,7 @@ inline void prepare_multi_stmt(sqlite3 *db, sqlite3_stmt **stmt, VALUE sql) {
135
176
  int rc = sqlite3_prepare(db, ptr, end - ptr, stmt, &rest);
136
177
  if (rc) {
137
178
  sqlite3_finalize(*stmt);
138
- rb_raise(cError, "%s", sqlite3_errmsg(db));
179
+ rb_raise(cSQLError, "%s", sqlite3_errmsg(db));
139
180
  }
140
181
 
141
182
  if (rest == end) return;
@@ -145,213 +186,227 @@ inline void prepare_multi_stmt(sqlite3 *db, sqlite3_stmt **stmt, VALUE sql) {
145
186
  sqlite3_finalize(*stmt);
146
187
  switch (rc) {
147
188
  case SQLITE_BUSY:
148
- rb_raise(cError, "Database is busy");
189
+ rb_raise(cBusyError, "Database is busy");
149
190
  case SQLITE_ERROR:
150
- rb_raise(cError, "%s", sqlite3_errmsg(db));
191
+ rb_raise(cSQLError, "%s", sqlite3_errmsg(db));
151
192
  }
152
193
  ptr = rest;
153
194
  }
154
195
  }
155
196
 
156
- VALUE Database_query_hash(int argc, VALUE *argv, VALUE self) {
197
+ inline int stmt_iterate(sqlite3_stmt *stmt, sqlite3 *db) {
157
198
  int rc;
158
- sqlite3_stmt* stmt;
159
- int column_count;
160
- Database_t *db;
161
- VALUE result = self;
162
- int yield_to_block = rb_block_given_p();
163
- VALUE row;
164
- VALUE column_names;
165
- VALUE sql;
166
-
167
- rb_check_arity(argc, 1, UNLIMITED_ARGUMENTS);
168
- sql = argv[0];
169
- GetDatabase(self, db);
170
-
171
- prepare_multi_stmt(db->sqlite3_db, &stmt, sql);
172
- bind_all_parameters(stmt, argc, argv);
173
- column_count = sqlite3_column_count(stmt);
174
- column_names = get_column_names(stmt, column_count);
175
-
176
- // block not given, so prepare the array of records to be returned
177
- if (!yield_to_block) result = rb_ary_new();
178
-
179
- step:
180
199
  rc = sqlite3_step(stmt);
181
200
  switch (rc) {
182
201
  case SQLITE_ROW:
183
- row = row_to_hash(stmt, column_count, column_names);
184
- if (yield_to_block) rb_yield(row); else rb_ary_push(result, row);
185
- goto step;
202
+ return 1;
186
203
  case SQLITE_DONE:
187
- break;
204
+ return 0;
188
205
  case SQLITE_BUSY:
189
- sqlite3_finalize(stmt);
190
- rb_raise(cError, "Database is busy");
206
+ rb_raise(cBusyError, "Database is busy");
191
207
  case SQLITE_ERROR:
192
- sqlite3_finalize(stmt);
193
- rb_raise(cError, "%s", sqlite3_errmsg(db->sqlite3_db));
208
+ rb_raise(cSQLError, "%s", sqlite3_errmsg(db));
194
209
  default:
195
- sqlite3_finalize(stmt);
196
210
  rb_raise(cError, "Invalid return code for sqlite3_step: %d", rc);
197
211
  }
198
- // TODO, use ensure to finalize statement
199
- 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
+
200
261
  RB_GC_GUARD(column_names);
201
262
  RB_GC_GUARD(row);
202
263
  RB_GC_GUARD(result);
203
264
  return result;
204
265
  }
205
266
 
206
- static inline VALUE row_to_ary(sqlite3_stmt *stmt, int column_count) {
207
- VALUE row = rb_ary_new2(column_count);
208
- for (int i = 0; i < column_count; i++) {
209
- VALUE value = get_column_value(stmt, i, sqlite3_column_type(stmt, i));
210
- rb_ary_push(row, value);
211
- }
212
- return row;
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);
213
270
  }
214
271
 
215
- VALUE Database_query_ary(int argc, VALUE *argv, VALUE self) {
216
- int rc;
217
- sqlite3_stmt* stmt;
218
- int column_count;
272
+ VALUE safe_query_ary(VALUE arg) {
273
+ query_ctx *ctx = (query_ctx *)arg;
219
274
  Database_t *db;
220
- VALUE result = self;
275
+ int column_count;
276
+ VALUE result = ctx->self;
221
277
  int yield_to_block = rb_block_given_p();
222
278
  VALUE row;
223
279
  VALUE sql;
224
280
 
225
- rb_check_arity(argc, 1, UNLIMITED_ARGUMENTS);
226
- sql = argv[0];
227
- GetDatabase(self, db);
281
+ check_arity_and_prepare_sql(ctx->argc, ctx->argv, sql);
282
+ GetOpenDatabase(ctx->self, db);
228
283
 
229
- prepare_multi_stmt(db->sqlite3_db, &stmt, sql);
230
- bind_all_parameters(stmt, argc, argv);
231
- 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);
232
287
 
233
288
  // block not given, so prepare the array of records to be returned
234
289
  if (!yield_to_block) result = rb_ary_new();
235
- step:
236
- rc = sqlite3_step(stmt);
237
- switch (rc) {
238
- case SQLITE_ROW:
239
- row = row_to_ary(stmt, column_count);
240
- if (yield_to_block) rb_yield(row); else rb_ary_push(result, row);
241
- goto step;
242
- case SQLITE_DONE:
243
- break;
244
- case SQLITE_BUSY:
245
- sqlite3_finalize(stmt);
246
- rb_raise(cError, "Database is busy");
247
- case SQLITE_ERROR:
248
- sqlite3_finalize(stmt);
249
- rb_raise(cError, "%s", sqlite3_errmsg(db->sqlite3_db));
250
- default:
251
- sqlite3_finalize(stmt);
252
- 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);
253
294
  }
254
- sqlite3_finalize(stmt);
295
+
255
296
  RB_GC_GUARD(row);
256
297
  RB_GC_GUARD(result);
257
298
  return result;
258
299
  }
259
300
 
260
- VALUE Database_query_single_column(int argc, VALUE *argv, VALUE self) {
261
- int rc;
262
- sqlite3_stmt* stmt;
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;
308
+ Database_t *db;
309
+ int column_count;
310
+ VALUE sql;
311
+ VALUE row = Qnil;
312
+ VALUE column_names;
313
+
314
+ check_arity_and_prepare_sql(ctx->argc, ctx->argv, sql);
315
+ GetOpenDatabase(ctx->self, db);
316
+
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);
321
+
322
+ if (stmt_iterate(ctx->stmt, db->sqlite3_db))
323
+ row = row_to_hash(ctx->stmt, column_count, column_names);
324
+
325
+ RB_GC_GUARD(row);
326
+ RB_GC_GUARD(column_names);
327
+ return row;
328
+ }
329
+
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
+
263
338
  int column_count;
264
339
  Database_t *db;
265
- VALUE result = self;
340
+ VALUE result = ctx->self;
266
341
  int yield_to_block = rb_block_given_p();
267
342
  VALUE sql;
268
343
  VALUE value;
269
344
 
270
- rb_check_arity(argc, 1, UNLIMITED_ARGUMENTS);
271
- sql = argv[0];
272
- GetDatabase(self, db);
345
+ check_arity_and_prepare_sql(ctx->argc, ctx->argv, sql);
346
+ GetOpenDatabase(ctx->self, db);
273
347
 
274
- prepare_multi_stmt(db->sqlite3_db, &stmt, sql);
275
- bind_all_parameters(stmt, argc, argv);
276
- 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);
277
351
  if (column_count != 1)
278
352
  rb_raise(cError, "Expected query result to have 1 column");
279
353
 
280
354
  // block not given, so prepare the array of records to be returned
281
355
  if (!yield_to_block) result = rb_ary_new();
282
- step:
283
- rc = sqlite3_step(stmt);
284
- printf("rc=%d\n", rc);
285
- switch (rc) {
286
- case SQLITE_ROW:
287
- value = get_column_value(stmt, 0, sqlite3_column_type(stmt, 0));
288
- if (yield_to_block) rb_yield(value); else rb_ary_push(result, value);
289
- goto step;
290
- case SQLITE_DONE:
291
- break;
292
- case SQLITE_BUSY:
293
- sqlite3_finalize(stmt);
294
- rb_raise(cError, "Database is busy");
295
- case SQLITE_ERROR:
296
- sqlite3_finalize(stmt);
297
- rb_raise(cError, "%s", sqlite3_errmsg(db->sqlite3_db));
298
- default:
299
- sqlite3_finalize(stmt);
300
- 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);
301
360
  }
302
361
 
303
- sqlite3_finalize(stmt);
304
362
  RB_GC_GUARD(value);
305
363
  RB_GC_GUARD(result);
306
364
  return result;
307
365
  }
308
366
 
309
- VALUE Database_query_single_value(int argc, VALUE *argv, VALUE self) {
310
- int rc;
311
- 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;
312
374
  int column_count;
313
375
  Database_t *db;
314
376
  VALUE sql;
315
377
  VALUE value = Qnil;
316
378
 
317
- rb_check_arity(argc, 1, UNLIMITED_ARGUMENTS);
318
- sql = argv[0];
319
- GetDatabase(self, db);
379
+ check_arity_and_prepare_sql(ctx->argc, ctx->argv, sql);
380
+ GetOpenDatabase(ctx->self, db);
320
381
 
321
- prepare_multi_stmt(db->sqlite3_db, &stmt, sql);
322
- bind_all_parameters(stmt, argc, argv);
323
- 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);
324
385
  if (column_count != 1)
325
386
  rb_raise(cError, "Expected query result to have 1 column");
326
387
 
327
- rc = sqlite3_step(stmt);
328
- switch (rc) {
329
- case SQLITE_ROW:
330
- value = get_column_value(stmt, 0, sqlite3_column_type(stmt, 0));
331
- break;
332
- case SQLITE_BUSY:
333
- rb_raise(cError, "Database is busy");
334
- case SQLITE_ERROR:
335
- rb_raise(cError, "%s", sqlite3_errmsg(db->sqlite3_db));
336
- default:
337
- rb_raise(cError, "Invalid return code for sqlite3_step: %d", rc);
338
- }
388
+ if (stmt_iterate(ctx->stmt, db->sqlite3_db))
389
+ value = get_column_value(ctx->stmt, 0, sqlite3_column_type(ctx->stmt, 0));
339
390
 
340
- sqlite3_finalize(stmt);
341
391
  RB_GC_GUARD(value);
342
392
  return value;
343
393
  }
344
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
+
345
400
  VALUE Database_last_insert_rowid(VALUE self) {
346
401
  Database_t *db;
347
- GetDatabase(self, db);
402
+ GetOpenDatabase(self, db);
348
403
 
349
404
  return INT2NUM(sqlite3_last_insert_rowid(db->sqlite3_db));
350
405
  }
351
406
 
352
407
  VALUE Database_changes(VALUE self) {
353
408
  Database_t *db;
354
- GetDatabase(self, db);
409
+ GetOpenDatabase(self, db);
355
410
 
356
411
  return INT2NUM(sqlite3_changes(db->sqlite3_db));
357
412
  }
@@ -360,7 +415,7 @@ VALUE Database_filename(int argc, VALUE *argv, VALUE self) {
360
415
  const char *db_name;
361
416
  const char *filename;
362
417
  Database_t *db;
363
- GetDatabase(self, db);
418
+ GetOpenDatabase(self, db);
364
419
 
365
420
  rb_check_arity(argc, 0, 1);
366
421
  db_name = (argc == 1) ? StringValueCStr(argv[0]) : "main";
@@ -370,14 +425,14 @@ VALUE Database_filename(int argc, VALUE *argv, VALUE self) {
370
425
 
371
426
  VALUE Database_transaction_active_p(VALUE self) {
372
427
  Database_t *db;
373
- GetDatabase(self, db);
428
+ GetOpenDatabase(self, db);
374
429
 
375
430
  return sqlite3_get_autocommit(db->sqlite3_db) ? Qfalse : Qtrue;
376
431
  }
377
432
 
378
433
  VALUE Database_load_extension(VALUE self, VALUE path) {
379
434
  Database_t *db;
380
- GetDatabase(self, db);
435
+ GetOpenDatabase(self, db);
381
436
  char *err_msg;
382
437
 
383
438
  int rc = sqlite3_load_extension(db->sqlite3_db, RSTRING_PTR(path), 0, &err_msg);
@@ -396,10 +451,13 @@ void Init_Extralite() {
396
451
  rb_define_alloc_func(cDatabase, Database_allocate);
397
452
 
398
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);
399
456
 
400
457
  rb_define_method(cDatabase, "query", Database_query_hash, -1);
401
458
  rb_define_method(cDatabase, "query_hash", Database_query_hash, -1);
402
459
  rb_define_method(cDatabase, "query_ary", Database_query_ary, -1);
460
+ rb_define_method(cDatabase, "query_single_row", Database_query_single_row, -1);
403
461
  rb_define_method(cDatabase, "query_single_column", Database_query_single_column, -1);
404
462
  rb_define_method(cDatabase, "query_single_value", Database_query_single_value, -1);
405
463
 
@@ -410,4 +468,8 @@ void Init_Extralite() {
410
468
  rb_define_method(cDatabase, "load_extension", Database_load_extension, 1);
411
469
 
412
470
  cError = rb_define_class_under(mExtralite, "Error", rb_eRuntimeError);
471
+ cSQLError = rb_define_class_under(mExtralite, "SQLError", cError);
472
+ cBusyError = rb_define_class_under(mExtralite, "BusyError", cError);
473
+
474
+ ID_STRIP = rb_intern("strip");
413
475
  }