extralite 0.6 → 1.3
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 +4 -4
- data/CHANGELOG.md +18 -0
- data/Gemfile.lock +1 -1
- data/README.md +50 -12
- data/ext/extralite/extralite.c +167 -159
- data/lib/extralite/version.rb +1 -1
- data/test/test_database.rb +11 -0
- metadata +3 -4
- data/ext/sqlite3/sqlite3.h +0 -12264
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b9cad52b4d9e4807384a8e1b3f9b9ab521c539cbc705bcf475fb24f8e3aa462c
|
4
|
+
data.tar.gz: '04792f9ce5e23c21509a7de369152501972e4a20167988f12211aa6d23b901a3'
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1e8fac21c746aa4fe8ebd8b09f12bf8c4fb71f2f888af948f90b83ae26fdebbaec67c9e1d5d4c7fda93e22152631579e6aa6cf37d8d6352492dd6ff9e5a1d255
|
7
|
+
data.tar.gz: 9a9df25229c0939df53f047426bd7df56359e81e187b0ba0ffd98ff1e8c5fd676ac828ebba01aae66524391166968b12b46ebea625775b1414f018ff5f161b08
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,21 @@
|
|
1
|
+
## 1.3 2021-08-17
|
2
|
+
|
3
|
+
- Pin error classes (for better compatibility with `GC.compact`)
|
4
|
+
|
5
|
+
## 1.2 2021-06-06
|
6
|
+
|
7
|
+
- Add support for big integers
|
8
|
+
|
9
|
+
## 1.1 2021-06-02
|
10
|
+
|
11
|
+
- Add `#close`, `#closed?` methods
|
12
|
+
|
13
|
+
## 1.0 2021-05-27
|
14
|
+
|
15
|
+
- Refactor C code
|
16
|
+
- Use `rb_ensure` to finalize stmt
|
17
|
+
- Remove bundled `sqlite3.h`, use system-wide header file instead
|
18
|
+
|
1
19
|
## 0.6 2021-05-25
|
2
20
|
|
3
21
|
- Add more specific errors: `SQLError`, `BusyError`
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -1,25 +1,35 @@
|
|
1
|
-
|
1
|
+
# Extralite - a Ruby gem for working with SQLite3 databases
|
2
2
|
|
3
|
-
|
4
|
-
|
3
|
+
[](http://rubygems.org/gems/extralite)
|
4
|
+
[](https://github.com/digital-fabric/extralite/actions?query=workflow%3ATests)
|
5
|
+
[](https://github.com/digital-fabric/extralite/blob/master/LICENSE)
|
5
6
|
|
6
|
-
|
7
|
+
## What is Extralite?
|
7
8
|
|
8
|
-
|
9
|
-
|
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.
|
14
|
-
- Load extensions.
|
23
|
+
- Load extensions (loading of extensions is autmatically enabled. You can find
|
24
|
+
some useful extensions here: https://github.com/nalgeon/sqlean.)
|
15
25
|
|
16
|
-
|
26
|
+
## Usage
|
17
27
|
|
18
28
|
```ruby
|
19
29
|
require 'extralite'
|
20
30
|
|
21
31
|
# open a database
|
22
|
-
db = Extralite::Database.new('
|
32
|
+
db = Extralite::Database.new('/tmp/my.db')
|
23
33
|
|
24
34
|
# get query results as array of hashes
|
25
35
|
db.query('select 1 as foo') #=> [{ :foo => 1 }]
|
@@ -53,9 +63,37 @@ db.query_hash('select ? as foo, ? as bar', 1, 2) #=> [{ :foo => 1, :bar => 2 }]
|
|
53
63
|
# get last insert rowid
|
54
64
|
rowid = db.last_insert_id
|
55
65
|
|
56
|
-
# get rows changed in last query
|
57
|
-
|
66
|
+
# get number of rows changed in last query
|
67
|
+
number_of_rows_affected = db.changes
|
58
68
|
|
59
69
|
# get db filename
|
60
|
-
|
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
|
61
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.
|
data/ext/extralite/extralite.c
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
#include <stdio.h>
|
2
2
|
#include "ruby.h"
|
3
|
-
#include
|
3
|
+
#include <sqlite3.h>
|
4
4
|
|
5
5
|
VALUE cError;
|
6
6
|
VALUE cSQLError;
|
@@ -39,6 +39,14 @@ static VALUE Database_allocate(VALUE klass) {
|
|
39
39
|
#define GetDatabase(obj, database) \
|
40
40
|
TypedData_Get_Struct((obj), Database_t, &Database_type, (database))
|
41
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
|
+
|
42
50
|
|
43
51
|
VALUE Database_initialize(VALUE self, VALUE path) {
|
44
52
|
int rc;
|
@@ -60,12 +68,33 @@ VALUE Database_initialize(VALUE self, VALUE path) {
|
|
60
68
|
return Qnil;
|
61
69
|
}
|
62
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
|
+
|
63
92
|
inline VALUE get_column_value(sqlite3_stmt *stmt, int col, int type) {
|
64
93
|
switch (type) {
|
65
94
|
case SQLITE_NULL:
|
66
95
|
return Qnil;
|
67
96
|
case SQLITE_INTEGER:
|
68
|
-
return
|
97
|
+
return LL2NUM(sqlite3_column_int64(stmt, col));
|
69
98
|
case SQLITE_FLOAT:
|
70
99
|
return DBL2NUM(sqlite3_column_double(stmt, col));
|
71
100
|
case SQLITE_TEXT:
|
@@ -85,7 +114,7 @@ static inline void bind_parameter_value(sqlite3_stmt *stmt, int pos, VALUE value
|
|
85
114
|
sqlite3_bind_null(stmt, pos);
|
86
115
|
return;
|
87
116
|
case T_FIXNUM:
|
88
|
-
|
117
|
+
sqlite3_bind_int64(stmt, pos, NUM2LL(value));
|
89
118
|
return;
|
90
119
|
case T_FLOAT:
|
91
120
|
sqlite3_bind_double(stmt, pos, NUM2DBL(value));
|
@@ -165,245 +194,219 @@ inline void prepare_multi_stmt(sqlite3 *db, sqlite3_stmt **stmt, VALUE sql) {
|
|
165
194
|
}
|
166
195
|
}
|
167
196
|
|
168
|
-
|
197
|
+
inline int stmt_iterate(sqlite3_stmt *stmt, sqlite3 *db) {
|
169
198
|
int rc;
|
170
|
-
sqlite3_stmt* stmt;
|
171
|
-
int column_count;
|
172
|
-
Database_t *db;
|
173
|
-
VALUE result = self;
|
174
|
-
int yield_to_block = rb_block_given_p();
|
175
|
-
VALUE row;
|
176
|
-
VALUE column_names;
|
177
|
-
VALUE sql;
|
178
|
-
|
179
|
-
rb_check_arity(argc, 1, UNLIMITED_ARGUMENTS);
|
180
|
-
sql = rb_funcall(argv[0], ID_STRIP, 0);
|
181
|
-
if (RSTRING_LEN(sql) == 0) return Qnil;
|
182
|
-
|
183
|
-
GetDatabase(self, db);
|
184
|
-
|
185
|
-
prepare_multi_stmt(db->sqlite3_db, &stmt, sql);
|
186
|
-
bind_all_parameters(stmt, argc, argv);
|
187
|
-
column_count = sqlite3_column_count(stmt);
|
188
|
-
column_names = get_column_names(stmt, column_count);
|
189
|
-
|
190
|
-
// block not given, so prepare the array of records to be returned
|
191
|
-
if (!yield_to_block) result = rb_ary_new();
|
192
|
-
|
193
|
-
step:
|
194
199
|
rc = sqlite3_step(stmt);
|
195
200
|
switch (rc) {
|
196
201
|
case SQLITE_ROW:
|
197
|
-
|
198
|
-
if (yield_to_block) rb_yield(row); else rb_ary_push(result, row);
|
199
|
-
goto step;
|
202
|
+
return 1;
|
200
203
|
case SQLITE_DONE:
|
201
|
-
|
204
|
+
return 0;
|
202
205
|
case SQLITE_BUSY:
|
203
|
-
sqlite3_finalize(stmt);
|
204
206
|
rb_raise(cBusyError, "Database is busy");
|
205
207
|
case SQLITE_ERROR:
|
206
|
-
|
207
|
-
rb_raise(cSQLError, "%s", sqlite3_errmsg(db->sqlite3_db));
|
208
|
+
rb_raise(cSQLError, "%s", sqlite3_errmsg(db));
|
208
209
|
default:
|
209
|
-
sqlite3_finalize(stmt);
|
210
210
|
rb_raise(cError, "Invalid return code for sqlite3_step: %d", rc);
|
211
211
|
}
|
212
|
-
|
213
|
-
|
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
|
+
|
214
261
|
RB_GC_GUARD(column_names);
|
215
262
|
RB_GC_GUARD(row);
|
216
263
|
RB_GC_GUARD(result);
|
217
264
|
return result;
|
218
265
|
}
|
219
266
|
|
220
|
-
VALUE
|
221
|
-
|
222
|
-
|
223
|
-
|
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);
|
270
|
+
}
|
271
|
+
|
272
|
+
VALUE safe_query_ary(VALUE arg) {
|
273
|
+
query_ctx *ctx = (query_ctx *)arg;
|
224
274
|
Database_t *db;
|
225
|
-
|
275
|
+
int column_count;
|
276
|
+
VALUE result = ctx->self;
|
226
277
|
int yield_to_block = rb_block_given_p();
|
227
278
|
VALUE row;
|
228
279
|
VALUE sql;
|
229
280
|
|
230
|
-
|
231
|
-
|
232
|
-
if (RSTRING_LEN(sql) == 0) return Qnil;
|
233
|
-
GetDatabase(self, db);
|
281
|
+
check_arity_and_prepare_sql(ctx->argc, ctx->argv, sql);
|
282
|
+
GetOpenDatabase(ctx->self, db);
|
234
283
|
|
235
|
-
prepare_multi_stmt(db->sqlite3_db, &stmt, sql);
|
236
|
-
bind_all_parameters(stmt, argc, argv);
|
237
|
-
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);
|
238
287
|
|
239
288
|
// block not given, so prepare the array of records to be returned
|
240
289
|
if (!yield_to_block) result = rb_ary_new();
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
row = row_to_ary(stmt, column_count);
|
246
|
-
if (yield_to_block) rb_yield(row); else rb_ary_push(result, row);
|
247
|
-
goto step;
|
248
|
-
case SQLITE_DONE:
|
249
|
-
break;
|
250
|
-
case SQLITE_BUSY:
|
251
|
-
sqlite3_finalize(stmt);
|
252
|
-
rb_raise(cBusyError, "Database is busy");
|
253
|
-
case SQLITE_ERROR:
|
254
|
-
sqlite3_finalize(stmt);
|
255
|
-
rb_raise(cSQLError, "%s", sqlite3_errmsg(db->sqlite3_db));
|
256
|
-
default:
|
257
|
-
sqlite3_finalize(stmt);
|
258
|
-
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);
|
259
294
|
}
|
260
|
-
|
295
|
+
|
261
296
|
RB_GC_GUARD(row);
|
262
297
|
RB_GC_GUARD(result);
|
263
298
|
return result;
|
264
299
|
}
|
265
300
|
|
266
|
-
VALUE
|
267
|
-
|
268
|
-
|
269
|
-
|
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;
|
270
308
|
Database_t *db;
|
309
|
+
int column_count;
|
271
310
|
VALUE sql;
|
272
311
|
VALUE row = Qnil;
|
273
312
|
VALUE column_names;
|
274
313
|
|
275
|
-
|
276
|
-
|
277
|
-
if (RSTRING_LEN(sql) == 0) return Qnil;
|
314
|
+
check_arity_and_prepare_sql(ctx->argc, ctx->argv, sql);
|
315
|
+
GetOpenDatabase(ctx->self, db);
|
278
316
|
|
279
|
-
|
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);
|
280
321
|
|
281
|
-
|
282
|
-
|
283
|
-
column_count = sqlite3_column_count(stmt);
|
284
|
-
column_names = get_column_names(stmt, column_count);
|
322
|
+
if (stmt_iterate(ctx->stmt, db->sqlite3_db))
|
323
|
+
row = row_to_hash(ctx->stmt, column_count, column_names);
|
285
324
|
|
286
|
-
rc = sqlite3_step(stmt);
|
287
|
-
switch (rc) {
|
288
|
-
case SQLITE_ROW:
|
289
|
-
row = row_to_hash(stmt, column_count, column_names);
|
290
|
-
break;
|
291
|
-
case SQLITE_DONE:
|
292
|
-
break;
|
293
|
-
case SQLITE_BUSY:
|
294
|
-
rb_raise(cBusyError, "Database is busy");
|
295
|
-
case SQLITE_ERROR:
|
296
|
-
rb_raise(cSQLError, "%s", sqlite3_errmsg(db->sqlite3_db));
|
297
|
-
default:
|
298
|
-
rb_raise(cError, "Invalid return code for sqlite3_step: %d", rc);
|
299
|
-
}
|
300
|
-
|
301
|
-
sqlite3_finalize(stmt);
|
302
325
|
RB_GC_GUARD(row);
|
303
326
|
RB_GC_GUARD(column_names);
|
304
327
|
return row;
|
305
328
|
}
|
306
329
|
|
307
|
-
VALUE
|
308
|
-
|
309
|
-
|
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
|
+
|
310
338
|
int column_count;
|
311
339
|
Database_t *db;
|
312
|
-
VALUE result = self;
|
340
|
+
VALUE result = ctx->self;
|
313
341
|
int yield_to_block = rb_block_given_p();
|
314
342
|
VALUE sql;
|
315
343
|
VALUE value;
|
316
344
|
|
317
|
-
|
318
|
-
|
319
|
-
if (RSTRING_LEN(sql) == 0) return Qnil;
|
320
|
-
|
321
|
-
GetDatabase(self, db);
|
345
|
+
check_arity_and_prepare_sql(ctx->argc, ctx->argv, sql);
|
346
|
+
GetOpenDatabase(ctx->self, db);
|
322
347
|
|
323
|
-
prepare_multi_stmt(db->sqlite3_db, &stmt, sql);
|
324
|
-
bind_all_parameters(stmt, argc, argv);
|
325
|
-
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);
|
326
351
|
if (column_count != 1)
|
327
352
|
rb_raise(cError, "Expected query result to have 1 column");
|
328
353
|
|
329
354
|
// block not given, so prepare the array of records to be returned
|
330
355
|
if (!yield_to_block) result = rb_ary_new();
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
value = get_column_value(stmt, 0, sqlite3_column_type(stmt, 0));
|
336
|
-
if (yield_to_block) rb_yield(value); else rb_ary_push(result, value);
|
337
|
-
goto step;
|
338
|
-
case SQLITE_DONE:
|
339
|
-
break;
|
340
|
-
case SQLITE_BUSY:
|
341
|
-
sqlite3_finalize(stmt);
|
342
|
-
rb_raise(cBusyError, "Database is busy");
|
343
|
-
case SQLITE_ERROR:
|
344
|
-
sqlite3_finalize(stmt);
|
345
|
-
rb_raise(cSQLError, "%s", sqlite3_errmsg(db->sqlite3_db));
|
346
|
-
default:
|
347
|
-
sqlite3_finalize(stmt);
|
348
|
-
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);
|
349
360
|
}
|
350
361
|
|
351
|
-
sqlite3_finalize(stmt);
|
352
362
|
RB_GC_GUARD(value);
|
353
363
|
RB_GC_GUARD(result);
|
354
364
|
return result;
|
355
365
|
}
|
356
366
|
|
357
|
-
VALUE
|
358
|
-
|
359
|
-
|
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;
|
360
374
|
int column_count;
|
361
375
|
Database_t *db;
|
362
376
|
VALUE sql;
|
363
377
|
VALUE value = Qnil;
|
364
378
|
|
365
|
-
|
366
|
-
|
367
|
-
if (RSTRING_LEN(sql) == 0) return Qnil;
|
368
|
-
|
369
|
-
GetDatabase(self, db);
|
379
|
+
check_arity_and_prepare_sql(ctx->argc, ctx->argv, sql);
|
380
|
+
GetOpenDatabase(ctx->self, db);
|
370
381
|
|
371
|
-
prepare_multi_stmt(db->sqlite3_db, &stmt, sql);
|
372
|
-
bind_all_parameters(stmt, argc, argv);
|
373
|
-
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);
|
374
385
|
if (column_count != 1)
|
375
386
|
rb_raise(cError, "Expected query result to have 1 column");
|
376
387
|
|
377
|
-
|
378
|
-
|
379
|
-
case SQLITE_ROW:
|
380
|
-
value = get_column_value(stmt, 0, sqlite3_column_type(stmt, 0));
|
381
|
-
break;
|
382
|
-
case SQLITE_DONE:
|
383
|
-
break;
|
384
|
-
case SQLITE_BUSY:
|
385
|
-
rb_raise(cBusyError, "Database is busy");
|
386
|
-
case SQLITE_ERROR:
|
387
|
-
rb_raise(cSQLError, "%s", sqlite3_errmsg(db->sqlite3_db));
|
388
|
-
default:
|
389
|
-
rb_raise(cError, "Invalid return code for sqlite3_step: %d", rc);
|
390
|
-
}
|
388
|
+
if (stmt_iterate(ctx->stmt, db->sqlite3_db))
|
389
|
+
value = get_column_value(ctx->stmt, 0, sqlite3_column_type(ctx->stmt, 0));
|
391
390
|
|
392
|
-
sqlite3_finalize(stmt);
|
393
391
|
RB_GC_GUARD(value);
|
394
392
|
return value;
|
395
393
|
}
|
396
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
|
+
|
397
400
|
VALUE Database_last_insert_rowid(VALUE self) {
|
398
401
|
Database_t *db;
|
399
|
-
|
402
|
+
GetOpenDatabase(self, db);
|
400
403
|
|
401
404
|
return INT2NUM(sqlite3_last_insert_rowid(db->sqlite3_db));
|
402
405
|
}
|
403
406
|
|
404
407
|
VALUE Database_changes(VALUE self) {
|
405
408
|
Database_t *db;
|
406
|
-
|
409
|
+
GetOpenDatabase(self, db);
|
407
410
|
|
408
411
|
return INT2NUM(sqlite3_changes(db->sqlite3_db));
|
409
412
|
}
|
@@ -412,7 +415,7 @@ VALUE Database_filename(int argc, VALUE *argv, VALUE self) {
|
|
412
415
|
const char *db_name;
|
413
416
|
const char *filename;
|
414
417
|
Database_t *db;
|
415
|
-
|
418
|
+
GetOpenDatabase(self, db);
|
416
419
|
|
417
420
|
rb_check_arity(argc, 0, 1);
|
418
421
|
db_name = (argc == 1) ? StringValueCStr(argv[0]) : "main";
|
@@ -422,14 +425,14 @@ VALUE Database_filename(int argc, VALUE *argv, VALUE self) {
|
|
422
425
|
|
423
426
|
VALUE Database_transaction_active_p(VALUE self) {
|
424
427
|
Database_t *db;
|
425
|
-
|
428
|
+
GetOpenDatabase(self, db);
|
426
429
|
|
427
430
|
return sqlite3_get_autocommit(db->sqlite3_db) ? Qfalse : Qtrue;
|
428
431
|
}
|
429
432
|
|
430
433
|
VALUE Database_load_extension(VALUE self, VALUE path) {
|
431
434
|
Database_t *db;
|
432
|
-
|
435
|
+
GetOpenDatabase(self, db);
|
433
436
|
char *err_msg;
|
434
437
|
|
435
438
|
int rc = sqlite3_load_extension(db->sqlite3_db, RSTRING_PTR(path), 0, &err_msg);
|
@@ -448,6 +451,8 @@ void Init_Extralite() {
|
|
448
451
|
rb_define_alloc_func(cDatabase, Database_allocate);
|
449
452
|
|
450
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);
|
451
456
|
|
452
457
|
rb_define_method(cDatabase, "query", Database_query_hash, -1);
|
453
458
|
rb_define_method(cDatabase, "query_hash", Database_query_hash, -1);
|
@@ -465,6 +470,9 @@ void Init_Extralite() {
|
|
465
470
|
cError = rb_define_class_under(mExtralite, "Error", rb_eRuntimeError);
|
466
471
|
cSQLError = rb_define_class_under(mExtralite, "SQLError", cError);
|
467
472
|
cBusyError = rb_define_class_under(mExtralite, "BusyError", cError);
|
473
|
+
rb_gc_register_mark_object(cError);
|
474
|
+
rb_gc_register_mark_object(cSQLError);
|
475
|
+
rb_gc_register_mark_object(cBusyError);
|
468
476
|
|
469
477
|
ID_STRIP = rb_intern("strip");
|
470
478
|
}
|