extralite 0.3 → 1.1

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: 8063960fd539fc0384882cf017572baefe79b9e29591d03c790951c6fad4fac0
4
- data.tar.gz: 9f1c5be233721502dd0b97ffef2d195912ec029eccee977b16ca67b567299f04
3
+ metadata.gz: bcad0681ac1ee598acef1989d7320f4215242dcfb3dad8857e791e313b2e673b
4
+ data.tar.gz: c26b4ed323da3ccd965a026b7d1c738723293412d226bda3c7c6795881b73239
5
5
  SHA512:
6
- metadata.gz: b674b89217ff2231d7c48f7d93917c29e84bbcb8b240216854a87b85d0c4ca5588fe821d0e6db406df04adc5c0b29aca4e24335349084e45a7d98449119a981f
7
- data.tar.gz: 3f33b7ef708052e51033e2ca38f2323c8b78f95eecda2c328fba506762831e260ae03a66ff5944e4b961b22906aa42740ebdeaad1ff233f1141daded682a6d47
6
+ metadata.gz: f1d5ab7ba821785f24a75cfe2e0d9d9fac0dbf6f2504321e24a89a89fc783fc3a30bc10f9ee4a97340202db04a8351e730c701d11e53643b065dee5fc009014b
7
+ data.tar.gz: 31a6ad671bb88f74ae2ba02d0da1e8c23275816ce41f630e82bcb7003b7e3f06a5cd9a53abeeb215bbedee54aeac763445809d4add0906cfbce087ce2acbc5b9
data/CHANGELOG.md CHANGED
@@ -1,3 +1,25 @@
1
+ ## 1.1 2021-06-02
2
+
3
+ - Add `#close`, `#closed?` methods
4
+
5
+ ## 1.0 2021-05-27
6
+
7
+ - Refactor C code
8
+ - Use `rb_ensure` to finalize stmt
9
+ - Remove bundled `sqlite3.h`, use system-wide header file instead
10
+
11
+ ## 0.6 2021-05-25
12
+
13
+ - Add more specific errors: `SQLError`, `BusyError`
14
+
15
+ ## 0.5 2021-05-25
16
+
17
+ - Implement `Database#query_single_row`
18
+
19
+ ## 0.4 2021-05-24
20
+
21
+ - Add support for loading extensions
22
+
1
23
  ## 0.3 2021-05-24
2
24
 
3
25
  - Add support for running multiple statements
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- extralite (0.3)
4
+ extralite (1.1)
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 (~365 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,33 @@ 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')
57
74
  ```
75
+
76
+ ## Why not just use the sqlite3 gem?
77
+
78
+ The sqlite3-ruby gem is a popular, solid, well-maintained project, used by
79
+ thousands of developers. I've been doing a lot of work with SQLite3 lately, and
80
+ wanted to have a simpler API that gives me query results in a variety of ways.
81
+ Thus extralite was born.
82
+
83
+ ## What about concurrency?
84
+
85
+ Extralite currently does not release the GVL. This means that even if queries
86
+ are executed on a separate thread, no other Ruby threads will be scheduled while
87
+ SQLite3 is busy fetching the next record.
88
+
89
+ In the future Extralite might be changed to release the GVL each time
90
+ `sqlite3_step` is called.
91
+
92
+ ## Can I use it with an ORM like ActiveRecord or Sequel?
93
+
94
+ Not yet, but you are welcome to contribute adapters for those projects. I will
95
+ 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;
@@ -44,15 +55,40 @@ VALUE Database_initialize(VALUE self, VALUE path) {
44
55
 
45
56
  rc = sqlite3_open(StringValueCStr(path), &db->sqlite3_db);
46
57
  if (rc) {
47
- fprintf(stderr, "Can't open database: %s\n", sqlite3_errmsg(db->sqlite3_db));
48
58
  sqlite3_close(db->sqlite3_db);
49
- // TODO: raise error
50
- return Qfalse;
59
+ rb_raise(cError, "%s", sqlite3_errmsg(db->sqlite3_db));
60
+ }
61
+
62
+ rc = sqlite3_enable_load_extension(db->sqlite3_db, 1);
63
+ if (rc) {
64
+ sqlite3_close(db->sqlite3_db);
65
+ rb_raise(cError, "%s", sqlite3_errmsg(db->sqlite3_db));
51
66
  }
52
67
 
53
68
  return Qnil;
54
69
  }
55
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
+
56
92
  inline VALUE get_column_value(sqlite3_stmt *stmt, int col, int type) {
57
93
  switch (type) {
58
94
  case SQLITE_NULL:
@@ -123,6 +159,15 @@ static inline VALUE row_to_hash(sqlite3_stmt *stmt, int column_count, VALUE colu
123
159
  return row;
124
160
  }
125
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
+
126
171
  inline void prepare_multi_stmt(sqlite3 *db, sqlite3_stmt **stmt, VALUE sql) {
127
172
  const char *rest = 0;
128
173
  const char *ptr = RSTRING_PTR(sql);
@@ -131,7 +176,7 @@ inline void prepare_multi_stmt(sqlite3 *db, sqlite3_stmt **stmt, VALUE sql) {
131
176
  int rc = sqlite3_prepare(db, ptr, end - ptr, stmt, &rest);
132
177
  if (rc) {
133
178
  sqlite3_finalize(*stmt);
134
- rb_raise(cError, "%s", sqlite3_errmsg(db));
179
+ rb_raise(cSQLError, "%s", sqlite3_errmsg(db));
135
180
  }
136
181
 
137
182
  if (rest == end) return;
@@ -141,213 +186,227 @@ inline void prepare_multi_stmt(sqlite3 *db, sqlite3_stmt **stmt, VALUE sql) {
141
186
  sqlite3_finalize(*stmt);
142
187
  switch (rc) {
143
188
  case SQLITE_BUSY:
144
- rb_raise(cError, "Database is busy");
189
+ rb_raise(cBusyError, "Database is busy");
145
190
  case SQLITE_ERROR:
146
- rb_raise(cError, "%s", sqlite3_errmsg(db));
191
+ rb_raise(cSQLError, "%s", sqlite3_errmsg(db));
147
192
  }
148
193
  ptr = rest;
149
194
  }
150
195
  }
151
196
 
152
- VALUE Database_query_hash(int argc, VALUE *argv, VALUE self) {
197
+ inline int stmt_iterate(sqlite3_stmt *stmt, sqlite3 *db) {
153
198
  int rc;
154
- sqlite3_stmt* stmt;
155
- int column_count;
156
- Database_t *db;
157
- VALUE result = self;
158
- int yield_to_block = rb_block_given_p();
159
- VALUE row;
160
- VALUE column_names;
161
- VALUE sql;
162
-
163
- rb_check_arity(argc, 1, UNLIMITED_ARGUMENTS);
164
- sql = argv[0];
165
- GetDatabase(self, db);
166
-
167
- prepare_multi_stmt(db->sqlite3_db, &stmt, sql);
168
- bind_all_parameters(stmt, argc, argv);
169
- column_count = sqlite3_column_count(stmt);
170
- column_names = get_column_names(stmt, column_count);
171
-
172
- // block not given, so prepare the array of records to be returned
173
- if (!yield_to_block) result = rb_ary_new();
174
-
175
- step:
176
199
  rc = sqlite3_step(stmt);
177
200
  switch (rc) {
178
201
  case SQLITE_ROW:
179
- row = row_to_hash(stmt, column_count, column_names);
180
- if (yield_to_block) rb_yield(row); else rb_ary_push(result, row);
181
- goto step;
202
+ return 1;
182
203
  case SQLITE_DONE:
183
- break;
204
+ return 0;
184
205
  case SQLITE_BUSY:
185
- sqlite3_finalize(stmt);
186
- rb_raise(cError, "Database is busy");
206
+ rb_raise(cBusyError, "Database is busy");
187
207
  case SQLITE_ERROR:
188
- sqlite3_finalize(stmt);
189
- rb_raise(cError, "%s", sqlite3_errmsg(db->sqlite3_db));
208
+ rb_raise(cSQLError, "%s", sqlite3_errmsg(db));
190
209
  default:
191
- sqlite3_finalize(stmt);
192
210
  rb_raise(cError, "Invalid return code for sqlite3_step: %d", rc);
193
211
  }
194
- // TODO, use ensure to finalize statement
195
- 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
+
196
261
  RB_GC_GUARD(column_names);
197
262
  RB_GC_GUARD(row);
198
263
  RB_GC_GUARD(result);
199
264
  return result;
200
265
  }
201
266
 
202
- static inline VALUE row_to_ary(sqlite3_stmt *stmt, int column_count) {
203
- VALUE row = rb_ary_new2(column_count);
204
- for (int i = 0; i < column_count; i++) {
205
- VALUE value = get_column_value(stmt, i, sqlite3_column_type(stmt, i));
206
- rb_ary_push(row, value);
207
- }
208
- 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);
209
270
  }
210
271
 
211
- VALUE Database_query_ary(int argc, VALUE *argv, VALUE self) {
212
- int rc;
213
- sqlite3_stmt* stmt;
214
- int column_count;
272
+ VALUE safe_query_ary(VALUE arg) {
273
+ query_ctx *ctx = (query_ctx *)arg;
215
274
  Database_t *db;
216
- VALUE result = self;
275
+ int column_count;
276
+ VALUE result = ctx->self;
217
277
  int yield_to_block = rb_block_given_p();
218
278
  VALUE row;
219
279
  VALUE sql;
220
280
 
221
- rb_check_arity(argc, 1, UNLIMITED_ARGUMENTS);
222
- sql = argv[0];
223
- GetDatabase(self, db);
281
+ check_arity_and_prepare_sql(ctx->argc, ctx->argv, sql);
282
+ GetOpenDatabase(ctx->self, db);
224
283
 
225
- prepare_multi_stmt(db->sqlite3_db, &stmt, sql);
226
- bind_all_parameters(stmt, argc, argv);
227
- 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);
228
287
 
229
288
  // block not given, so prepare the array of records to be returned
230
289
  if (!yield_to_block) result = rb_ary_new();
231
- step:
232
- rc = sqlite3_step(stmt);
233
- switch (rc) {
234
- case SQLITE_ROW:
235
- row = row_to_ary(stmt, column_count);
236
- if (yield_to_block) rb_yield(row); else rb_ary_push(result, row);
237
- goto step;
238
- case SQLITE_DONE:
239
- break;
240
- case SQLITE_BUSY:
241
- sqlite3_finalize(stmt);
242
- rb_raise(cError, "Database is busy");
243
- case SQLITE_ERROR:
244
- sqlite3_finalize(stmt);
245
- rb_raise(cError, "%s", sqlite3_errmsg(db->sqlite3_db));
246
- default:
247
- sqlite3_finalize(stmt);
248
- 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);
249
294
  }
250
- sqlite3_finalize(stmt);
295
+
251
296
  RB_GC_GUARD(row);
252
297
  RB_GC_GUARD(result);
253
298
  return result;
254
299
  }
255
300
 
256
- VALUE Database_query_single_column(int argc, VALUE *argv, VALUE self) {
257
- int rc;
258
- 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
+
259
338
  int column_count;
260
339
  Database_t *db;
261
- VALUE result = self;
340
+ VALUE result = ctx->self;
262
341
  int yield_to_block = rb_block_given_p();
263
342
  VALUE sql;
264
343
  VALUE value;
265
344
 
266
- rb_check_arity(argc, 1, UNLIMITED_ARGUMENTS);
267
- sql = argv[0];
268
- GetDatabase(self, db);
345
+ check_arity_and_prepare_sql(ctx->argc, ctx->argv, sql);
346
+ GetOpenDatabase(ctx->self, db);
269
347
 
270
- prepare_multi_stmt(db->sqlite3_db, &stmt, sql);
271
- bind_all_parameters(stmt, argc, argv);
272
- 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);
273
351
  if (column_count != 1)
274
352
  rb_raise(cError, "Expected query result to have 1 column");
275
353
 
276
354
  // block not given, so prepare the array of records to be returned
277
355
  if (!yield_to_block) result = rb_ary_new();
278
- step:
279
- rc = sqlite3_step(stmt);
280
- printf("rc=%d\n", rc);
281
- switch (rc) {
282
- case SQLITE_ROW:
283
- value = get_column_value(stmt, 0, sqlite3_column_type(stmt, 0));
284
- if (yield_to_block) rb_yield(value); else rb_ary_push(result, value);
285
- goto step;
286
- case SQLITE_DONE:
287
- break;
288
- case SQLITE_BUSY:
289
- sqlite3_finalize(stmt);
290
- rb_raise(cError, "Database is busy");
291
- case SQLITE_ERROR:
292
- sqlite3_finalize(stmt);
293
- rb_raise(cError, "%s", sqlite3_errmsg(db->sqlite3_db));
294
- default:
295
- sqlite3_finalize(stmt);
296
- 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);
297
360
  }
298
361
 
299
- sqlite3_finalize(stmt);
300
362
  RB_GC_GUARD(value);
301
363
  RB_GC_GUARD(result);
302
364
  return result;
303
365
  }
304
366
 
305
- VALUE Database_query_single_value(int argc, VALUE *argv, VALUE self) {
306
- int rc;
307
- 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;
308
374
  int column_count;
309
375
  Database_t *db;
310
376
  VALUE sql;
311
377
  VALUE value = Qnil;
312
378
 
313
- rb_check_arity(argc, 1, UNLIMITED_ARGUMENTS);
314
- sql = argv[0];
315
- GetDatabase(self, db);
379
+ check_arity_and_prepare_sql(ctx->argc, ctx->argv, sql);
380
+ GetOpenDatabase(ctx->self, db);
316
381
 
317
- prepare_multi_stmt(db->sqlite3_db, &stmt, sql);
318
- bind_all_parameters(stmt, argc, argv);
319
- 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);
320
385
  if (column_count != 1)
321
386
  rb_raise(cError, "Expected query result to have 1 column");
322
387
 
323
- rc = sqlite3_step(stmt);
324
- switch (rc) {
325
- case SQLITE_ROW:
326
- value = get_column_value(stmt, 0, sqlite3_column_type(stmt, 0));
327
- break;
328
- case SQLITE_BUSY:
329
- rb_raise(cError, "Database is busy");
330
- case SQLITE_ERROR:
331
- rb_raise(cError, "%s", sqlite3_errmsg(db->sqlite3_db));
332
- default:
333
- rb_raise(cError, "Invalid return code for sqlite3_step: %d", rc);
334
- }
388
+ if (stmt_iterate(ctx->stmt, db->sqlite3_db))
389
+ value = get_column_value(ctx->stmt, 0, sqlite3_column_type(ctx->stmt, 0));
335
390
 
336
- sqlite3_finalize(stmt);
337
391
  RB_GC_GUARD(value);
338
392
  return value;
339
393
  }
340
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
+
341
400
  VALUE Database_last_insert_rowid(VALUE self) {
342
401
  Database_t *db;
343
- GetDatabase(self, db);
402
+ GetOpenDatabase(self, db);
344
403
 
345
404
  return INT2NUM(sqlite3_last_insert_rowid(db->sqlite3_db));
346
405
  }
347
406
 
348
407
  VALUE Database_changes(VALUE self) {
349
408
  Database_t *db;
350
- GetDatabase(self, db);
409
+ GetOpenDatabase(self, db);
351
410
 
352
411
  return INT2NUM(sqlite3_changes(db->sqlite3_db));
353
412
  }
@@ -356,7 +415,7 @@ VALUE Database_filename(int argc, VALUE *argv, VALUE self) {
356
415
  const char *db_name;
357
416
  const char *filename;
358
417
  Database_t *db;
359
- GetDatabase(self, db);
418
+ GetOpenDatabase(self, db);
360
419
 
361
420
  rb_check_arity(argc, 0, 1);
362
421
  db_name = (argc == 1) ? StringValueCStr(argv[0]) : "main";
@@ -366,21 +425,39 @@ VALUE Database_filename(int argc, VALUE *argv, VALUE self) {
366
425
 
367
426
  VALUE Database_transaction_active_p(VALUE self) {
368
427
  Database_t *db;
369
- GetDatabase(self, db);
428
+ GetOpenDatabase(self, db);
370
429
 
371
430
  return sqlite3_get_autocommit(db->sqlite3_db) ? Qfalse : Qtrue;
372
431
  }
373
432
 
433
+ VALUE Database_load_extension(VALUE self, VALUE path) {
434
+ Database_t *db;
435
+ GetOpenDatabase(self, db);
436
+ char *err_msg;
437
+
438
+ int rc = sqlite3_load_extension(db->sqlite3_db, RSTRING_PTR(path), 0, &err_msg);
439
+ if (rc != SQLITE_OK) {
440
+ VALUE error = rb_exc_new2(cError, err_msg);
441
+ sqlite3_free(err_msg);
442
+ rb_exc_raise(error);
443
+ }
444
+
445
+ return self;
446
+ }
447
+
374
448
  void Init_Extralite() {
375
449
  VALUE mExtralite = rb_define_module("Extralite");
376
450
  VALUE cDatabase = rb_define_class_under(mExtralite, "Database", rb_cObject);
377
451
  rb_define_alloc_func(cDatabase, Database_allocate);
378
452
 
379
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);
380
456
 
381
457
  rb_define_method(cDatabase, "query", Database_query_hash, -1);
382
458
  rb_define_method(cDatabase, "query_hash", Database_query_hash, -1);
383
459
  rb_define_method(cDatabase, "query_ary", Database_query_ary, -1);
460
+ rb_define_method(cDatabase, "query_single_row", Database_query_single_row, -1);
384
461
  rb_define_method(cDatabase, "query_single_column", Database_query_single_column, -1);
385
462
  rb_define_method(cDatabase, "query_single_value", Database_query_single_value, -1);
386
463
 
@@ -388,6 +465,11 @@ void Init_Extralite() {
388
465
  rb_define_method(cDatabase, "changes", Database_changes, 0);
389
466
  rb_define_method(cDatabase, "filename", Database_filename, -1);
390
467
  rb_define_method(cDatabase, "transaction_active?", Database_transaction_active_p, 0);
468
+ rb_define_method(cDatabase, "load_extension", Database_load_extension, 1);
391
469
 
392
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");
393
475
  }