extralite 0.4 → 1.2

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