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 +4 -4
- data/CHANGELOG.md +23 -1
- data/Gemfile.lock +1 -1
- data/README.md +40 -8
- data/ext/extralite/extralite.c +201 -144
- data/lib/extralite/version.rb +1 -1
- data/test/test_database.rb +42 -1
- metadata +2 -3
- 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: 9c69948d51f689ead3e4ccf008bc7b0670085d0c987d39359bb38172b9993b58
|
|
4
|
+
data.tar.gz: 776ed4e4f95cdb405b9b3be277d0aaa5a27fcd0e84aff8f6ebb3aeece531b1cf
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 472db7ae94e195e647712ce04213538b229bcfebc3f9ec22b3f8e69dceb0a5cbba9000e14147a27ed2e26231aa7c73b43585a45be369f94d8cd2507643dd88b9
|
|
7
|
+
data.tar.gz: 6475f99ebe990b9b073f77379f58ddcd4fa0f678fb32e64b39021e3aa7b6d6f9343c8f02d69cce1826a9d156c8b0918985536732af5a11be2bda28524524e8c2
|
data/CHANGELOG.md
CHANGED
|
@@ -1,4 +1,26 @@
|
|
|
1
|
-
## 0
|
|
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
data/README.md
CHANGED
|
@@ -1,16 +1,21 @@
|
|
|
1
1
|
## Extralite
|
|
2
2
|
|
|
3
|
-
Extralite is an extra-lightweight
|
|
4
|
-
class with a minimal set of methods to interact with
|
|
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
|
|
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('
|
|
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
|
-
|
|
60
|
+
# get number of rows changed in last query
|
|
61
|
+
number_of_rows_affected = db.changes
|
|
54
62
|
|
|
55
63
|
# get db filename
|
|
56
|
-
|
|
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.
|
data/ext/extralite/extralite.c
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
#include <stdio.h>
|
|
2
2
|
#include "ruby.h"
|
|
3
|
-
#include
|
|
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
|
-
|
|
50
|
-
|
|
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
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
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
|
-
|
|
149
|
-
|
|
150
|
-
|
|
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
|
-
|
|
153
|
-
if (!yield_to_block) result = rb_ary_new();
|
|
153
|
+
if (rest == end) return;
|
|
154
154
|
|
|
155
|
-
|
|
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
|
-
|
|
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
|
-
|
|
175
|
+
return 0;
|
|
164
176
|
case SQLITE_BUSY:
|
|
165
|
-
|
|
166
|
-
rb_raise(cError, "Database is busy");
|
|
177
|
+
rb_raise(cBusyError, "Database is busy");
|
|
167
178
|
case SQLITE_ERROR:
|
|
168
|
-
|
|
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
|
-
|
|
175
|
-
|
|
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
|
-
|
|
183
|
-
|
|
184
|
-
|
|
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
|
|
192
|
-
|
|
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
|
-
|
|
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
|
-
|
|
202
|
-
|
|
203
|
-
GetDatabase(self, db);
|
|
252
|
+
check_arity_and_prepare_sql(ctx->argc, ctx->argv, sql);
|
|
253
|
+
GetDatabase(ctx->self, db);
|
|
204
254
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
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
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
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
|
-
|
|
266
|
+
|
|
235
267
|
RB_GC_GUARD(row);
|
|
236
268
|
RB_GC_GUARD(result);
|
|
237
269
|
return result;
|
|
238
270
|
}
|
|
239
271
|
|
|
240
|
-
VALUE
|
|
241
|
-
|
|
242
|
-
|
|
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
|
-
|
|
251
|
-
|
|
252
|
-
GetDatabase(self, db);
|
|
316
|
+
check_arity_and_prepare_sql(ctx->argc, ctx->argv, sql);
|
|
317
|
+
GetDatabase(ctx->self, db);
|
|
253
318
|
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
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
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
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
|
|
293
|
-
|
|
294
|
-
|
|
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
|
-
|
|
301
|
-
|
|
302
|
-
GetDatabase(self, db);
|
|
350
|
+
check_arity_and_prepare_sql(ctx->argc, ctx->argv, sql);
|
|
351
|
+
GetDatabase(ctx->self, db);
|
|
303
352
|
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
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
|
-
|
|
318
|
-
|
|
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
|
}
|