extralite 0.3 → 1.1
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 +22 -0
- data/Gemfile.lock +1 -1
- data/README.md +49 -11
- data/ext/extralite/extralite.c +216 -134
- data/lib/extralite/version.rb +1 -1
- data/test/test_database.rb +47 -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: bcad0681ac1ee598acef1989d7320f4215242dcfb3dad8857e791e313b2e673b
|
4
|
+
data.tar.gz: c26b4ed323da3ccd965a026b7d1c738723293412d226bda3c7c6795881b73239
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
data/README.md
CHANGED
@@ -1,24 +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 (~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
|
-
|
26
|
+
## Usage
|
16
27
|
|
17
28
|
```ruby
|
18
29
|
require 'extralite'
|
19
30
|
|
20
31
|
# open a database
|
21
|
-
db = Extralite::Database.new('
|
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
|
-
|
66
|
+
# get number of rows changed in last query
|
67
|
+
number_of_rows_affected = db.changes
|
54
68
|
|
55
69
|
# get db filename
|
56
|
-
|
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.
|
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;
|
@@ -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
|
-
|
50
|
-
|
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(
|
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(
|
189
|
+
rb_raise(cBusyError, "Database is busy");
|
145
190
|
case SQLITE_ERROR:
|
146
|
-
rb_raise(
|
191
|
+
rb_raise(cSQLError, "%s", sqlite3_errmsg(db));
|
147
192
|
}
|
148
193
|
ptr = rest;
|
149
194
|
}
|
150
195
|
}
|
151
196
|
|
152
|
-
|
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
|
-
|
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
|
-
|
204
|
+
return 0;
|
184
205
|
case SQLITE_BUSY:
|
185
|
-
|
186
|
-
rb_raise(cError, "Database is busy");
|
206
|
+
rb_raise(cBusyError, "Database is busy");
|
187
207
|
case SQLITE_ERROR:
|
188
|
-
|
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
|
-
|
195
|
-
|
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
|
-
|
203
|
-
|
204
|
-
|
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
|
212
|
-
|
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
|
-
|
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
|
-
|
222
|
-
|
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
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
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
|
-
|
295
|
+
|
251
296
|
RB_GC_GUARD(row);
|
252
297
|
RB_GC_GUARD(result);
|
253
298
|
return result;
|
254
299
|
}
|
255
300
|
|
256
|
-
VALUE
|
257
|
-
|
258
|
-
|
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
|
-
|
267
|
-
|
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
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
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
|
306
|
-
|
307
|
-
|
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
|
-
|
314
|
-
|
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
|
-
|
324
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
}
|