extralite 0.3 → 1.1

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