extralite 0.2.1 → 1.0

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: edc158d606131bab091a3a9053a00a540728e53b12aa6165ec69f3611d5c449a
4
- data.tar.gz: 826223da4bcd18ff70bc5d6f81c08c2ae8aa486387cd2277d0275268a1917853
3
+ metadata.gz: 9c69948d51f689ead3e4ccf008bc7b0670085d0c987d39359bb38172b9993b58
4
+ data.tar.gz: 776ed4e4f95cdb405b9b3be277d0aaa5a27fcd0e84aff8f6ebb3aeece531b1cf
5
5
  SHA512:
6
- metadata.gz: c5fa113e40bf4ed2f4b2d89267386cd901e7346462ae080a8ba9771c0b8da2d95ce0473e584031210c02b4b3e9785d607afb8886da1c8d75fcadb9bb5022181d
7
- data.tar.gz: df5a8b1025cd8278b5a929415bd62581d79b9769cd57f0353b3163fdb5d3f81a2c9ccd23901a4dade805e58add9436671e728aaead945ffed8239cb4dc619e17
6
+ metadata.gz: 472db7ae94e195e647712ce04213538b229bcfebc3f9ec22b3f8e69dceb0a5cbba9000e14147a27ed2e26231aa7c73b43585a45be369f94d8cd2507643dd88b9
7
+ data.tar.gz: 6475f99ebe990b9b073f77379f58ddcd4fa0f678fb32e64b39021e3aa7b6d6f9343c8f02d69cce1826a9d156c8b0918985536732af5a11be2bda28524524e8c2
data/CHANGELOG.md CHANGED
@@ -1,4 +1,26 @@
1
- ## 0.2 2021-05-24
1
+ ## 1.0 2021-05-27
2
+
3
+ - Refactor C code
4
+ - Use `rb_ensure` to finalize stmt
5
+ - Remove bundled `sqlite3.h`, use system-wide header file instead
6
+
7
+ ## 0.6 2021-05-25
8
+
9
+ - Add more specific errors: `SQLError`, `BusyError`
10
+
11
+ ## 0.5 2021-05-25
12
+
13
+ - Implement `Database#query_single_row`
14
+
15
+ ## 0.4 2021-05-24
16
+
17
+ - Add support for loading extensions
18
+
19
+ ## 0.3 2021-05-24
20
+
21
+ - Add support for running multiple statements
22
+
23
+ ## 0.2 2021-05-23
2
24
 
3
25
  - Implement `Database#transaction_active?`
4
26
  - Add tests
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- extralite (0.2.1)
4
+ extralite (1.0)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
data/README.md CHANGED
@@ -1,16 +1,21 @@
1
1
  ## Extralite
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
+ Extralite is an extra-lightweight (~365 lines of C-code) SQLite3 wrapper for
4
+ Ruby. It provides a single class with a minimal set of methods to interact with
5
+ an SQLite3 database.
5
6
 
6
7
  ### Features
7
8
 
8
- - A variety of ways to get back query results: row as hash, row as array, single
9
- column, single value.
9
+ - A variety of methods for different data access patterns: row as hash, row as
10
+ array, single single row, single column, single value.
10
11
  - Iterate over records with a block, or collect records into an array.
11
12
  - Parameter binding.
13
+ - Correctly execute strings with multiple semicolon-separated queries (handy for
14
+ creating/modifying schemas).
12
15
  - Get last insert rowid.
13
16
  - Get number of rows changed by last query.
17
+ - Load extensions (loading of extensions is autmatically enabled. You can find
18
+ some useful extensions here: https://github.com/nalgeon/sqlean.)
14
19
 
15
20
  ### Usage
16
21
 
@@ -18,7 +23,7 @@ class with a minimal set of methods to interact with an SQLite3 database.
18
23
  require 'extralite'
19
24
 
20
25
  # open a database
21
- db = Extralite::Database.new('mydb')
26
+ db = Extralite::Database.new('/tmp/my.db')
22
27
 
23
28
  # get query results as array of hashes
24
29
  db.query('select 1 as foo') #=> [{ :foo => 1 }]
@@ -34,6 +39,9 @@ db.query_ary('select 1, 2, 3') #=> [[1, 2, 3]]
34
39
  db.query_ary('select 1, 2, 3') { |r| p r }
35
40
  # [1, 2, 3]
36
41
 
42
+ # get a single row as a hash
43
+ db.query_single_row("select 1 as foo") #=> { :foo => 1 }
44
+
37
45
  # get single column query results as array of values
38
46
  db.query_single_column('select 42') #=> [42]
39
47
  # or iterate over results
@@ -49,9 +57,33 @@ db.query_hash('select ? as foo, ? as bar', 1, 2) #=> [{ :foo => 1, :bar => 2 }]
49
57
  # get last insert rowid
50
58
  rowid = db.last_insert_id
51
59
 
52
- # get rows changed in last query
53
- rows_changed = db.changes
60
+ # get number of rows changed in last query
61
+ number_of_rows_affected = db.changes
54
62
 
55
63
  # get db filename
56
- Extralite::Database.new('/tmp/my.db').filename #=> "/tmp/my.db"
64
+ db.filename #=> "/tmp/my.db"
65
+
66
+ # load an extension
67
+ db.load_extension('/path/to/extension.so')
57
68
  ```
69
+
70
+ ### Why not just use the sqlite3 gem?
71
+
72
+ The sqlite3-ruby gem is a popular, solid, well-maintained project, used by
73
+ thousands of developers. I've been doing a lot of work with SQLite3 lately, and
74
+ wanted to have a simpler API that gives me query results in a variety of ways.
75
+ Thus extralite was born.
76
+
77
+ ### What about concurrency?
78
+
79
+ Extralite currently does not release the GVL. This means that even if queries
80
+ are executed on a separate thread, no other Ruby threads will be scheduled while
81
+ SQLite3 is busy fetching the next record.
82
+
83
+ In the future Extralite might be changed to release the GVL each time
84
+ `sqlite3_step` is called.
85
+
86
+ ### Can I use it with an ORM like ActiveRecord or Sequel?
87
+
88
+ Not yet, but you are welcome to contribute adapters for those projects. I will
89
+ 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;
@@ -44,10 +47,14 @@ VALUE Database_initialize(VALUE self, VALUE path) {
44
47
 
45
48
  rc = sqlite3_open(StringValueCStr(path), &db->sqlite3_db);
46
49
  if (rc) {
47
- fprintf(stderr, "Can't open database: %s\n", sqlite3_errmsg(db->sqlite3_db));
48
50
  sqlite3_close(db->sqlite3_db);
49
- // TODO: raise error
50
- return Qfalse;
51
+ rb_raise(cError, "%s", sqlite3_errmsg(db->sqlite3_db));
52
+ }
53
+
54
+ rc = sqlite3_enable_load_extension(db->sqlite3_db, 1);
55
+ if (rc) {
56
+ sqlite3_close(db->sqlite3_db);
57
+ rb_raise(cError, "%s", sqlite3_errmsg(db->sqlite3_db));
51
58
  }
52
59
 
53
60
  return Qnil;
@@ -123,215 +130,244 @@ static inline VALUE row_to_hash(sqlite3_stmt *stmt, int column_count, VALUE colu
123
130
  return row;
124
131
  }
125
132
 
126
- VALUE Database_query_hash(int argc, VALUE *argv, VALUE self) {
127
- int rc;
128
- sqlite3_stmt* stmt;
129
- int column_count;
130
- Database_t *db;
131
- VALUE result = self;
132
- int yield_to_block = rb_block_given_p();
133
- VALUE row;
134
- VALUE column_names;
135
- VALUE sql;
136
-
137
- rb_check_arity(argc, 1, UNLIMITED_ARGUMENTS);
138
- sql = argv[0];
139
- GetDatabase(self, db);
140
-
141
- rc = sqlite3_prepare(db->sqlite3_db, RSTRING_PTR(sql), RSTRING_LEN(sql), &stmt, 0);
142
- if (rc) {
143
- sqlite3_finalize(stmt);
144
- rb_raise(cError, "%s", sqlite3_errmsg(db->sqlite3_db));
145
- return Qnil;
133
+ static inline VALUE row_to_ary(sqlite3_stmt *stmt, int column_count) {
134
+ VALUE row = rb_ary_new2(column_count);
135
+ for (int i = 0; i < column_count; i++) {
136
+ VALUE value = get_column_value(stmt, i, sqlite3_column_type(stmt, i));
137
+ rb_ary_push(row, value);
146
138
  }
139
+ return row;
140
+ }
147
141
 
148
- bind_all_parameters(stmt, argc, argv);
149
- column_count = sqlite3_column_count(stmt);
150
- column_names = get_column_names(stmt, column_count);
142
+ inline void prepare_multi_stmt(sqlite3 *db, sqlite3_stmt **stmt, VALUE sql) {
143
+ const char *rest = 0;
144
+ const char *ptr = RSTRING_PTR(sql);
145
+ const char *end = ptr + RSTRING_LEN(sql);
146
+ while (1) {
147
+ int rc = sqlite3_prepare(db, ptr, end - ptr, stmt, &rest);
148
+ if (rc) {
149
+ sqlite3_finalize(*stmt);
150
+ rb_raise(cSQLError, "%s", sqlite3_errmsg(db));
151
+ }
151
152
 
152
- // block not given, so prepare the array of records to be returned
153
- if (!yield_to_block) result = rb_ary_new();
153
+ if (rest == end) return;
154
154
 
155
- step:
155
+ // perform current query, but discard its results
156
+ rc = sqlite3_step(*stmt);
157
+ sqlite3_finalize(*stmt);
158
+ switch (rc) {
159
+ case SQLITE_BUSY:
160
+ rb_raise(cBusyError, "Database is busy");
161
+ case SQLITE_ERROR:
162
+ rb_raise(cSQLError, "%s", sqlite3_errmsg(db));
163
+ }
164
+ ptr = rest;
165
+ }
166
+ }
167
+
168
+ inline int stmt_iterate(sqlite3_stmt *stmt, sqlite3 *db) {
169
+ int rc;
156
170
  rc = sqlite3_step(stmt);
157
171
  switch (rc) {
158
172
  case SQLITE_ROW:
159
- row = row_to_hash(stmt, column_count, column_names);
160
- if (yield_to_block) rb_yield(row); else rb_ary_push(result, row);
161
- goto step;
173
+ return 1;
162
174
  case SQLITE_DONE:
163
- break;
175
+ return 0;
164
176
  case SQLITE_BUSY:
165
- sqlite3_finalize(stmt);
166
- rb_raise(cError, "Database is busy");
177
+ rb_raise(cBusyError, "Database is busy");
167
178
  case SQLITE_ERROR:
168
- sqlite3_finalize(stmt);
169
- rb_raise(cError, "%s", sqlite3_errmsg(db->sqlite3_db));
179
+ rb_raise(cSQLError, "%s", sqlite3_errmsg(db));
170
180
  default:
171
- sqlite3_finalize(stmt);
172
181
  rb_raise(cError, "Invalid return code for sqlite3_step: %d", rc);
173
182
  }
174
- // TODO, use ensure to finalize statement
175
- sqlite3_finalize(stmt);
183
+
184
+ return 0;
185
+ }
186
+
187
+ typedef struct query_ctx {
188
+ VALUE self;
189
+ int argc;
190
+ VALUE *argv;
191
+ sqlite3_stmt *stmt;
192
+ } query_ctx;
193
+
194
+ VALUE cleanup_stmt(VALUE arg) {
195
+ query_ctx *ctx = (query_ctx *)arg;
196
+ sqlite3_finalize(ctx->stmt);
197
+ return Qnil;
198
+ }
199
+
200
+ #define check_arity_and_prepare_sql(argc, argv, sql) { \
201
+ rb_check_arity(argc, 1, UNLIMITED_ARGUMENTS); \
202
+ sql = rb_funcall(argv[0], ID_STRIP, 0); \
203
+ if (RSTRING_LEN(sql) == 0) return Qnil; \
204
+ }
205
+
206
+ VALUE safe_query_hash(VALUE arg) {
207
+ query_ctx *ctx = (query_ctx *)arg;
208
+ Database_t *db;
209
+ VALUE result = ctx->self;
210
+ int yield_to_block = rb_block_given_p();
211
+ VALUE row;
212
+ VALUE sql;
213
+ int column_count;
214
+ VALUE column_names;
215
+
216
+ check_arity_and_prepare_sql(ctx->argc, ctx->argv, sql);
217
+ GetDatabase(ctx->self, db);
218
+
219
+ prepare_multi_stmt(db->sqlite3_db, &ctx->stmt, sql);
220
+ bind_all_parameters(ctx->stmt, ctx->argc, ctx->argv);
221
+ column_count = sqlite3_column_count(ctx->stmt);
222
+ column_names = get_column_names(ctx->stmt, column_count);
223
+
224
+ // block not given, so prepare the array of records to be returned
225
+ if (!yield_to_block) result = rb_ary_new();
226
+
227
+ while (stmt_iterate(ctx->stmt, db->sqlite3_db)) {
228
+ row = row_to_hash(ctx->stmt, column_count, column_names);
229
+ if (yield_to_block) rb_yield(row); else rb_ary_push(result, row);
230
+ }
231
+
176
232
  RB_GC_GUARD(column_names);
177
233
  RB_GC_GUARD(row);
178
234
  RB_GC_GUARD(result);
179
235
  return result;
180
236
  }
181
237
 
182
- static inline VALUE row_to_ary(sqlite3_stmt *stmt, int column_count) {
183
- VALUE row = rb_ary_new2(column_count);
184
- for (int i = 0; i < column_count; i++) {
185
- VALUE value = get_column_value(stmt, i, sqlite3_column_type(stmt, i));
186
- rb_ary_push(row, value);
187
- }
188
- return row;
238
+ VALUE Database_query_hash(int argc, VALUE *argv, VALUE self) {
239
+ query_ctx ctx = { self, argc, argv, 0 };
240
+ return rb_ensure(safe_query_hash, (VALUE)&ctx, cleanup_stmt, (VALUE)&ctx);
189
241
  }
190
242
 
191
- VALUE Database_query_ary(int argc, VALUE *argv, VALUE self) {
192
- int rc;
193
- sqlite3_stmt* stmt;
194
- int column_count;
243
+ VALUE safe_query_ary(VALUE arg) {
244
+ query_ctx *ctx = (query_ctx *)arg;
195
245
  Database_t *db;
196
- VALUE result = self;
246
+ int column_count;
247
+ VALUE result = ctx->self;
197
248
  int yield_to_block = rb_block_given_p();
198
249
  VALUE row;
199
250
  VALUE sql;
200
251
 
201
- rb_check_arity(argc, 1, UNLIMITED_ARGUMENTS);
202
- sql = argv[0];
203
- GetDatabase(self, db);
252
+ check_arity_and_prepare_sql(ctx->argc, ctx->argv, sql);
253
+ GetDatabase(ctx->self, db);
204
254
 
205
- rc = sqlite3_prepare(db->sqlite3_db, RSTRING_PTR(sql), RSTRING_LEN(sql), &stmt, 0);
206
- if (rc) {
207
- fprintf(stderr, "Failed to prepare statement: %s\n", sqlite3_errmsg(db->sqlite3_db));
208
- sqlite3_finalize(stmt);
209
- // TODO: raise error
210
- return Qfalse;
211
- }
212
-
213
- bind_all_parameters(stmt, argc, argv);
214
- column_count = sqlite3_column_count(stmt);
255
+ prepare_multi_stmt(db->sqlite3_db, &ctx->stmt, sql);
256
+ bind_all_parameters(ctx->stmt, ctx->argc, ctx->argv);
257
+ column_count = sqlite3_column_count(ctx->stmt);
215
258
 
216
259
  // block not given, so prepare the array of records to be returned
217
260
  if (!yield_to_block) result = rb_ary_new();
218
- step:
219
- rc = sqlite3_step(stmt);
220
- switch (rc) {
221
- case SQLITE_ROW:
222
- row = row_to_ary(stmt, column_count);
223
- if (yield_to_block) rb_yield(row); else rb_ary_push(result, row);
224
- goto step;
225
- case SQLITE_DONE:
226
- break;
227
- case SQLITE_BUSY:
228
- rb_raise(cError, "Database is busy");
229
- case SQLITE_ERROR:
230
- rb_raise(cError, "%s", sqlite3_errmsg(db->sqlite3_db));
231
- default:
232
- rb_raise(cError, "Invalid return code for sqlite3_step: %d", rc);
261
+
262
+ while (stmt_iterate(ctx->stmt, db->sqlite3_db)) {
263
+ row = row_to_ary(ctx->stmt, column_count);
264
+ if (yield_to_block) rb_yield(row); else rb_ary_push(result, row);
233
265
  }
234
- sqlite3_finalize(stmt);
266
+
235
267
  RB_GC_GUARD(row);
236
268
  RB_GC_GUARD(result);
237
269
  return result;
238
270
  }
239
271
 
240
- VALUE Database_query_single_column(int argc, VALUE *argv, VALUE self) {
241
- int rc;
242
- sqlite3_stmt* stmt;
272
+ VALUE Database_query_ary(int argc, VALUE *argv, VALUE self) {
273
+ query_ctx ctx = { self, argc, argv, 0 };
274
+ return rb_ensure(safe_query_ary, (VALUE)&ctx, cleanup_stmt, (VALUE)&ctx);
275
+ }
276
+
277
+ VALUE safe_query_single_row(VALUE arg) {
278
+ query_ctx *ctx = (query_ctx *)arg;
279
+ Database_t *db;
280
+ int column_count;
281
+ VALUE sql;
282
+ VALUE row = Qnil;
283
+ VALUE column_names;
284
+
285
+ check_arity_and_prepare_sql(ctx->argc, ctx->argv, sql);
286
+ GetDatabase(ctx->self, db);
287
+
288
+ prepare_multi_stmt(db->sqlite3_db, &ctx->stmt, sql);
289
+ bind_all_parameters(ctx->stmt, ctx->argc, ctx->argv);
290
+ column_count = sqlite3_column_count(ctx->stmt);
291
+ column_names = get_column_names(ctx->stmt, column_count);
292
+
293
+ if (stmt_iterate(ctx->stmt, db->sqlite3_db))
294
+ row = row_to_hash(ctx->stmt, column_count, column_names);
295
+
296
+ RB_GC_GUARD(row);
297
+ RB_GC_GUARD(column_names);
298
+ return row;
299
+ }
300
+
301
+ VALUE Database_query_single_row(int argc, VALUE *argv, VALUE self) {
302
+ query_ctx ctx = { self, argc, argv, 0 };
303
+ return rb_ensure(safe_query_single_row, (VALUE)&ctx, cleanup_stmt, (VALUE)&ctx);
304
+ }
305
+
306
+ VALUE safe_query_single_column(VALUE arg) {
307
+ query_ctx *ctx = (query_ctx *)arg;
308
+
243
309
  int column_count;
244
310
  Database_t *db;
245
- VALUE result = self;
311
+ VALUE result = ctx->self;
246
312
  int yield_to_block = rb_block_given_p();
247
313
  VALUE sql;
248
314
  VALUE value;
249
315
 
250
- rb_check_arity(argc, 1, UNLIMITED_ARGUMENTS);
251
- sql = argv[0];
252
- GetDatabase(self, db);
316
+ check_arity_and_prepare_sql(ctx->argc, ctx->argv, sql);
317
+ GetDatabase(ctx->self, db);
253
318
 
254
- rc = sqlite3_prepare(db->sqlite3_db, RSTRING_PTR(sql), RSTRING_LEN(sql), &stmt, 0);
255
- if (rc) {
256
- fprintf(stderr, "Failed to prepare statement: %s\n", sqlite3_errmsg(db->sqlite3_db));
257
- sqlite3_finalize(stmt);
258
- // TODO: raise error
259
- return Qfalse;
260
- }
261
-
262
- bind_all_parameters(stmt, argc, argv);
263
- column_count = sqlite3_column_count(stmt);
319
+ prepare_multi_stmt(db->sqlite3_db, &ctx->stmt, sql);
320
+ bind_all_parameters(ctx->stmt, ctx->argc, ctx->argv);
321
+ column_count = sqlite3_column_count(ctx->stmt);
264
322
  if (column_count != 1)
265
323
  rb_raise(cError, "Expected query result to have 1 column");
266
324
 
267
325
  // block not given, so prepare the array of records to be returned
268
326
  if (!yield_to_block) result = rb_ary_new();
269
- step:
270
- rc = sqlite3_step(stmt);
271
- switch (rc) {
272
- case SQLITE_ROW:
273
- value = get_column_value(stmt, 0, sqlite3_column_type(stmt, 0));
274
- if (yield_to_block) rb_yield(value); else rb_ary_push(result, value);
275
- goto step;
276
- case SQLITE_DONE:
277
- break;
278
- case SQLITE_BUSY:
279
- rb_raise(cError, "Database is busy");
280
- case SQLITE_ERROR:
281
- rb_raise(cError, "%s", sqlite3_errmsg(db->sqlite3_db));
282
- default:
283
- rb_raise(cError, "Invalid return code for sqlite3_step: %d", rc);
327
+
328
+ while (stmt_iterate(ctx->stmt, db->sqlite3_db)) {
329
+ value = get_column_value(ctx->stmt, 0, sqlite3_column_type(ctx->stmt, 0));
330
+ if (yield_to_block) rb_yield(value); else rb_ary_push(result, value);
284
331
  }
285
332
 
286
- sqlite3_finalize(stmt);
287
333
  RB_GC_GUARD(value);
288
334
  RB_GC_GUARD(result);
289
335
  return result;
290
336
  }
291
337
 
292
- VALUE Database_query_single_value(int argc, VALUE *argv, VALUE self) {
293
- int rc;
294
- sqlite3_stmt* stmt;
338
+ VALUE Database_query_single_column(int argc, VALUE *argv, VALUE self) {
339
+ query_ctx ctx = { self, argc, argv, 0 };
340
+ return rb_ensure(safe_query_single_column, (VALUE)&ctx, cleanup_stmt, (VALUE)&ctx);
341
+ }
342
+
343
+ VALUE safe_query_single_value(VALUE arg) {
344
+ query_ctx *ctx = (query_ctx *)arg;
295
345
  int column_count;
296
346
  Database_t *db;
297
347
  VALUE sql;
298
348
  VALUE value = Qnil;
299
349
 
300
- rb_check_arity(argc, 1, UNLIMITED_ARGUMENTS);
301
- sql = argv[0];
302
- GetDatabase(self, db);
350
+ check_arity_and_prepare_sql(ctx->argc, ctx->argv, sql);
351
+ GetDatabase(ctx->self, db);
303
352
 
304
- rc = sqlite3_prepare(db->sqlite3_db, RSTRING_PTR(sql), RSTRING_LEN(sql), &stmt, 0);
305
- if (rc) {
306
- fprintf(stderr, "Failed to prepare statement: %s\n", sqlite3_errmsg(db->sqlite3_db));
307
- sqlite3_finalize(stmt);
308
- // TODO: raise error
309
- return Qfalse;
310
- }
311
-
312
- bind_all_parameters(stmt, argc, argv);
313
- column_count = sqlite3_column_count(stmt);
353
+ prepare_multi_stmt(db->sqlite3_db, &ctx->stmt, sql);
354
+ bind_all_parameters(ctx->stmt, ctx->argc, ctx->argv);
355
+ column_count = sqlite3_column_count(ctx->stmt);
314
356
  if (column_count != 1)
315
357
  rb_raise(cError, "Expected query result to have 1 column");
316
358
 
317
- rc = sqlite3_step(stmt);
318
- switch (rc) {
319
- case SQLITE_ROW:
320
- value = get_column_value(stmt, 0, sqlite3_column_type(stmt, 0));
321
- break;
322
- case SQLITE_BUSY:
323
- rb_raise(cError, "Database is busy");
324
- case SQLITE_ERROR:
325
- rb_raise(cError, "%s", sqlite3_errmsg(db->sqlite3_db));
326
- default:
327
- rb_raise(cError, "Invalid return code for sqlite3_step: %d", rc);
328
- }
359
+ if (stmt_iterate(ctx->stmt, db->sqlite3_db))
360
+ value = get_column_value(ctx->stmt, 0, sqlite3_column_type(ctx->stmt, 0));
329
361
 
330
- sqlite3_finalize(stmt);
331
362
  RB_GC_GUARD(value);
332
363
  return value;
333
364
  }
334
365
 
366
+ VALUE Database_query_single_value(int argc, VALUE *argv, VALUE self) {
367
+ query_ctx ctx = { self, argc, argv, 0 };
368
+ return rb_ensure(safe_query_single_value, (VALUE)&ctx, cleanup_stmt, (VALUE)&ctx);
369
+ }
370
+
335
371
  VALUE Database_last_insert_rowid(VALUE self) {
336
372
  Database_t *db;
337
373
  GetDatabase(self, db);
@@ -365,6 +401,21 @@ VALUE Database_transaction_active_p(VALUE self) {
365
401
  return sqlite3_get_autocommit(db->sqlite3_db) ? Qfalse : Qtrue;
366
402
  }
367
403
 
404
+ VALUE Database_load_extension(VALUE self, VALUE path) {
405
+ Database_t *db;
406
+ GetDatabase(self, db);
407
+ char *err_msg;
408
+
409
+ int rc = sqlite3_load_extension(db->sqlite3_db, RSTRING_PTR(path), 0, &err_msg);
410
+ if (rc != SQLITE_OK) {
411
+ VALUE error = rb_exc_new2(cError, err_msg);
412
+ sqlite3_free(err_msg);
413
+ rb_exc_raise(error);
414
+ }
415
+
416
+ return self;
417
+ }
418
+
368
419
  void Init_Extralite() {
369
420
  VALUE mExtralite = rb_define_module("Extralite");
370
421
  VALUE cDatabase = rb_define_class_under(mExtralite, "Database", rb_cObject);
@@ -375,6 +426,7 @@ void Init_Extralite() {
375
426
  rb_define_method(cDatabase, "query", Database_query_hash, -1);
376
427
  rb_define_method(cDatabase, "query_hash", Database_query_hash, -1);
377
428
  rb_define_method(cDatabase, "query_ary", Database_query_ary, -1);
429
+ rb_define_method(cDatabase, "query_single_row", Database_query_single_row, -1);
378
430
  rb_define_method(cDatabase, "query_single_column", Database_query_single_column, -1);
379
431
  rb_define_method(cDatabase, "query_single_value", Database_query_single_value, -1);
380
432
 
@@ -382,6 +434,11 @@ void Init_Extralite() {
382
434
  rb_define_method(cDatabase, "changes", Database_changes, 0);
383
435
  rb_define_method(cDatabase, "filename", Database_filename, -1);
384
436
  rb_define_method(cDatabase, "transaction_active?", Database_transaction_active_p, 0);
437
+ rb_define_method(cDatabase, "load_extension", Database_load_extension, 1);
385
438
 
386
439
  cError = rb_define_class_under(mExtralite, "Error", rb_eRuntimeError);
440
+ cSQLError = rb_define_class_under(mExtralite, "SQLError", cError);
441
+ cBusyError = rb_define_class_under(mExtralite, "BusyError", cError);
442
+
443
+ ID_STRIP = rb_intern("strip");
387
444
  }