extralite 0.2.1 → 1.0

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