extralite 0.4 → 1.2
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 +53 -11
- data/ext/extralite/extralite.c +196 -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: a6bb308b421895a192cdbc83d022e105ec1fbd4060d8f8898a71f9f13741bd18
|
4
|
+
data.tar.gz: b044f3d91692b19f7c0cc5912896cec1458c3101ebb5f058fe09c147a3cf00ea
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4bb40f58b77290b083466563465232cc3aadfa8cb68d305d08edc7fd4b1344d256af45f84f7efe8765fe00b604010a071b5966fd15abb63896b7d843337ce683
|
7
|
+
data.tar.gz: b29ee3ff34f307d9f45515971ecd0b147fe236f6c2d95e4b48cdd1a91b3d9847d477fee2c667836f83db5c68f559a0f4f2129d12b8d5d1fe5f075ca2cdd6d05c
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,25 @@
|
|
1
|
+
## 1.2 2021-06-06
|
2
|
+
|
3
|
+
- Add support for big integers
|
4
|
+
|
5
|
+
## 1.1 2021-06-02
|
6
|
+
|
7
|
+
- Add `#close`, `#closed?` methods
|
8
|
+
|
9
|
+
## 1.0 2021-05-27
|
10
|
+
|
11
|
+
- Refactor C code
|
12
|
+
- Use `rb_ensure` to finalize stmt
|
13
|
+
- Remove bundled `sqlite3.h`, use system-wide header file instead
|
14
|
+
|
15
|
+
## 0.6 2021-05-25
|
16
|
+
|
17
|
+
- Add more specific errors: `SQLError`, `BusyError`
|
18
|
+
|
19
|
+
## 0.5 2021-05-25
|
20
|
+
|
21
|
+
- Implement `Database#query_single_row`
|
22
|
+
|
1
23
|
## 0.4 2021-05-24
|
2
24
|
|
3
25
|
- Add support for loading extensions
|
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 (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.
|
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,37 @@ 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')
|
74
|
+
|
75
|
+
# close database
|
76
|
+
db.close
|
77
|
+
db.closed? #=> true
|
57
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,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;
|
@@ -57,12 +68,33 @@ VALUE Database_initialize(VALUE self, VALUE path) {
|
|
57
68
|
return Qnil;
|
58
69
|
}
|
59
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
|
+
|
60
92
|
inline VALUE get_column_value(sqlite3_stmt *stmt, int col, int type) {
|
61
93
|
switch (type) {
|
62
94
|
case SQLITE_NULL:
|
63
95
|
return Qnil;
|
64
96
|
case SQLITE_INTEGER:
|
65
|
-
return
|
97
|
+
return LL2NUM(sqlite3_column_int64(stmt, col));
|
66
98
|
case SQLITE_FLOAT:
|
67
99
|
return DBL2NUM(sqlite3_column_double(stmt, col));
|
68
100
|
case SQLITE_TEXT:
|
@@ -82,7 +114,7 @@ static inline void bind_parameter_value(sqlite3_stmt *stmt, int pos, VALUE value
|
|
82
114
|
sqlite3_bind_null(stmt, pos);
|
83
115
|
return;
|
84
116
|
case T_FIXNUM:
|
85
|
-
|
117
|
+
sqlite3_bind_int64(stmt, pos, NUM2LL(value));
|
86
118
|
return;
|
87
119
|
case T_FLOAT:
|
88
120
|
sqlite3_bind_double(stmt, pos, NUM2DBL(value));
|
@@ -127,6 +159,15 @@ static inline VALUE row_to_hash(sqlite3_stmt *stmt, int column_count, VALUE colu
|
|
127
159
|
return row;
|
128
160
|
}
|
129
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
|
+
|
130
171
|
inline void prepare_multi_stmt(sqlite3 *db, sqlite3_stmt **stmt, VALUE sql) {
|
131
172
|
const char *rest = 0;
|
132
173
|
const char *ptr = RSTRING_PTR(sql);
|
@@ -135,7 +176,7 @@ inline void prepare_multi_stmt(sqlite3 *db, sqlite3_stmt **stmt, VALUE sql) {
|
|
135
176
|
int rc = sqlite3_prepare(db, ptr, end - ptr, stmt, &rest);
|
136
177
|
if (rc) {
|
137
178
|
sqlite3_finalize(*stmt);
|
138
|
-
rb_raise(
|
179
|
+
rb_raise(cSQLError, "%s", sqlite3_errmsg(db));
|
139
180
|
}
|
140
181
|
|
141
182
|
if (rest == end) return;
|
@@ -145,213 +186,227 @@ inline void prepare_multi_stmt(sqlite3 *db, sqlite3_stmt **stmt, VALUE sql) {
|
|
145
186
|
sqlite3_finalize(*stmt);
|
146
187
|
switch (rc) {
|
147
188
|
case SQLITE_BUSY:
|
148
|
-
rb_raise(
|
189
|
+
rb_raise(cBusyError, "Database is busy");
|
149
190
|
case SQLITE_ERROR:
|
150
|
-
rb_raise(
|
191
|
+
rb_raise(cSQLError, "%s", sqlite3_errmsg(db));
|
151
192
|
}
|
152
193
|
ptr = rest;
|
153
194
|
}
|
154
195
|
}
|
155
196
|
|
156
|
-
|
197
|
+
inline int stmt_iterate(sqlite3_stmt *stmt, sqlite3 *db) {
|
157
198
|
int rc;
|
158
|
-
sqlite3_stmt* stmt;
|
159
|
-
int column_count;
|
160
|
-
Database_t *db;
|
161
|
-
VALUE result = self;
|
162
|
-
int yield_to_block = rb_block_given_p();
|
163
|
-
VALUE row;
|
164
|
-
VALUE column_names;
|
165
|
-
VALUE sql;
|
166
|
-
|
167
|
-
rb_check_arity(argc, 1, UNLIMITED_ARGUMENTS);
|
168
|
-
sql = argv[0];
|
169
|
-
GetDatabase(self, db);
|
170
|
-
|
171
|
-
prepare_multi_stmt(db->sqlite3_db, &stmt, sql);
|
172
|
-
bind_all_parameters(stmt, argc, argv);
|
173
|
-
column_count = sqlite3_column_count(stmt);
|
174
|
-
column_names = get_column_names(stmt, column_count);
|
175
|
-
|
176
|
-
// block not given, so prepare the array of records to be returned
|
177
|
-
if (!yield_to_block) result = rb_ary_new();
|
178
|
-
|
179
|
-
step:
|
180
199
|
rc = sqlite3_step(stmt);
|
181
200
|
switch (rc) {
|
182
201
|
case SQLITE_ROW:
|
183
|
-
|
184
|
-
if (yield_to_block) rb_yield(row); else rb_ary_push(result, row);
|
185
|
-
goto step;
|
202
|
+
return 1;
|
186
203
|
case SQLITE_DONE:
|
187
|
-
|
204
|
+
return 0;
|
188
205
|
case SQLITE_BUSY:
|
189
|
-
|
190
|
-
rb_raise(cError, "Database is busy");
|
206
|
+
rb_raise(cBusyError, "Database is busy");
|
191
207
|
case SQLITE_ERROR:
|
192
|
-
|
193
|
-
rb_raise(cError, "%s", sqlite3_errmsg(db->sqlite3_db));
|
208
|
+
rb_raise(cSQLError, "%s", sqlite3_errmsg(db));
|
194
209
|
default:
|
195
|
-
sqlite3_finalize(stmt);
|
196
210
|
rb_raise(cError, "Invalid return code for sqlite3_step: %d", rc);
|
197
211
|
}
|
198
|
-
|
199
|
-
|
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
|
+
|
200
261
|
RB_GC_GUARD(column_names);
|
201
262
|
RB_GC_GUARD(row);
|
202
263
|
RB_GC_GUARD(result);
|
203
264
|
return result;
|
204
265
|
}
|
205
266
|
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
VALUE value = get_column_value(stmt, i, sqlite3_column_type(stmt, i));
|
210
|
-
rb_ary_push(row, value);
|
211
|
-
}
|
212
|
-
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);
|
213
270
|
}
|
214
271
|
|
215
|
-
VALUE
|
216
|
-
|
217
|
-
sqlite3_stmt* stmt;
|
218
|
-
int column_count;
|
272
|
+
VALUE safe_query_ary(VALUE arg) {
|
273
|
+
query_ctx *ctx = (query_ctx *)arg;
|
219
274
|
Database_t *db;
|
220
|
-
|
275
|
+
int column_count;
|
276
|
+
VALUE result = ctx->self;
|
221
277
|
int yield_to_block = rb_block_given_p();
|
222
278
|
VALUE row;
|
223
279
|
VALUE sql;
|
224
280
|
|
225
|
-
|
226
|
-
|
227
|
-
GetDatabase(self, db);
|
281
|
+
check_arity_and_prepare_sql(ctx->argc, ctx->argv, sql);
|
282
|
+
GetOpenDatabase(ctx->self, db);
|
228
283
|
|
229
|
-
prepare_multi_stmt(db->sqlite3_db, &stmt, sql);
|
230
|
-
bind_all_parameters(stmt, argc, argv);
|
231
|
-
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);
|
232
287
|
|
233
288
|
// block not given, so prepare the array of records to be returned
|
234
289
|
if (!yield_to_block) result = rb_ary_new();
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
row = row_to_ary(stmt, column_count);
|
240
|
-
if (yield_to_block) rb_yield(row); else rb_ary_push(result, row);
|
241
|
-
goto step;
|
242
|
-
case SQLITE_DONE:
|
243
|
-
break;
|
244
|
-
case SQLITE_BUSY:
|
245
|
-
sqlite3_finalize(stmt);
|
246
|
-
rb_raise(cError, "Database is busy");
|
247
|
-
case SQLITE_ERROR:
|
248
|
-
sqlite3_finalize(stmt);
|
249
|
-
rb_raise(cError, "%s", sqlite3_errmsg(db->sqlite3_db));
|
250
|
-
default:
|
251
|
-
sqlite3_finalize(stmt);
|
252
|
-
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);
|
253
294
|
}
|
254
|
-
|
295
|
+
|
255
296
|
RB_GC_GUARD(row);
|
256
297
|
RB_GC_GUARD(result);
|
257
298
|
return result;
|
258
299
|
}
|
259
300
|
|
260
|
-
VALUE
|
261
|
-
|
262
|
-
|
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
|
+
|
263
338
|
int column_count;
|
264
339
|
Database_t *db;
|
265
|
-
VALUE result = self;
|
340
|
+
VALUE result = ctx->self;
|
266
341
|
int yield_to_block = rb_block_given_p();
|
267
342
|
VALUE sql;
|
268
343
|
VALUE value;
|
269
344
|
|
270
|
-
|
271
|
-
|
272
|
-
GetDatabase(self, db);
|
345
|
+
check_arity_and_prepare_sql(ctx->argc, ctx->argv, sql);
|
346
|
+
GetOpenDatabase(ctx->self, db);
|
273
347
|
|
274
|
-
prepare_multi_stmt(db->sqlite3_db, &stmt, sql);
|
275
|
-
bind_all_parameters(stmt, argc, argv);
|
276
|
-
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);
|
277
351
|
if (column_count != 1)
|
278
352
|
rb_raise(cError, "Expected query result to have 1 column");
|
279
353
|
|
280
354
|
// block not given, so prepare the array of records to be returned
|
281
355
|
if (!yield_to_block) result = rb_ary_new();
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
case SQLITE_ROW:
|
287
|
-
value = get_column_value(stmt, 0, sqlite3_column_type(stmt, 0));
|
288
|
-
if (yield_to_block) rb_yield(value); else rb_ary_push(result, value);
|
289
|
-
goto step;
|
290
|
-
case SQLITE_DONE:
|
291
|
-
break;
|
292
|
-
case SQLITE_BUSY:
|
293
|
-
sqlite3_finalize(stmt);
|
294
|
-
rb_raise(cError, "Database is busy");
|
295
|
-
case SQLITE_ERROR:
|
296
|
-
sqlite3_finalize(stmt);
|
297
|
-
rb_raise(cError, "%s", sqlite3_errmsg(db->sqlite3_db));
|
298
|
-
default:
|
299
|
-
sqlite3_finalize(stmt);
|
300
|
-
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);
|
301
360
|
}
|
302
361
|
|
303
|
-
sqlite3_finalize(stmt);
|
304
362
|
RB_GC_GUARD(value);
|
305
363
|
RB_GC_GUARD(result);
|
306
364
|
return result;
|
307
365
|
}
|
308
366
|
|
309
|
-
VALUE
|
310
|
-
|
311
|
-
|
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;
|
312
374
|
int column_count;
|
313
375
|
Database_t *db;
|
314
376
|
VALUE sql;
|
315
377
|
VALUE value = Qnil;
|
316
378
|
|
317
|
-
|
318
|
-
|
319
|
-
GetDatabase(self, db);
|
379
|
+
check_arity_and_prepare_sql(ctx->argc, ctx->argv, sql);
|
380
|
+
GetOpenDatabase(ctx->self, db);
|
320
381
|
|
321
|
-
prepare_multi_stmt(db->sqlite3_db, &stmt, sql);
|
322
|
-
bind_all_parameters(stmt, argc, argv);
|
323
|
-
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);
|
324
385
|
if (column_count != 1)
|
325
386
|
rb_raise(cError, "Expected query result to have 1 column");
|
326
387
|
|
327
|
-
|
328
|
-
|
329
|
-
case SQLITE_ROW:
|
330
|
-
value = get_column_value(stmt, 0, sqlite3_column_type(stmt, 0));
|
331
|
-
break;
|
332
|
-
case SQLITE_BUSY:
|
333
|
-
rb_raise(cError, "Database is busy");
|
334
|
-
case SQLITE_ERROR:
|
335
|
-
rb_raise(cError, "%s", sqlite3_errmsg(db->sqlite3_db));
|
336
|
-
default:
|
337
|
-
rb_raise(cError, "Invalid return code for sqlite3_step: %d", rc);
|
338
|
-
}
|
388
|
+
if (stmt_iterate(ctx->stmt, db->sqlite3_db))
|
389
|
+
value = get_column_value(ctx->stmt, 0, sqlite3_column_type(ctx->stmt, 0));
|
339
390
|
|
340
|
-
sqlite3_finalize(stmt);
|
341
391
|
RB_GC_GUARD(value);
|
342
392
|
return value;
|
343
393
|
}
|
344
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
|
+
|
345
400
|
VALUE Database_last_insert_rowid(VALUE self) {
|
346
401
|
Database_t *db;
|
347
|
-
|
402
|
+
GetOpenDatabase(self, db);
|
348
403
|
|
349
404
|
return INT2NUM(sqlite3_last_insert_rowid(db->sqlite3_db));
|
350
405
|
}
|
351
406
|
|
352
407
|
VALUE Database_changes(VALUE self) {
|
353
408
|
Database_t *db;
|
354
|
-
|
409
|
+
GetOpenDatabase(self, db);
|
355
410
|
|
356
411
|
return INT2NUM(sqlite3_changes(db->sqlite3_db));
|
357
412
|
}
|
@@ -360,7 +415,7 @@ VALUE Database_filename(int argc, VALUE *argv, VALUE self) {
|
|
360
415
|
const char *db_name;
|
361
416
|
const char *filename;
|
362
417
|
Database_t *db;
|
363
|
-
|
418
|
+
GetOpenDatabase(self, db);
|
364
419
|
|
365
420
|
rb_check_arity(argc, 0, 1);
|
366
421
|
db_name = (argc == 1) ? StringValueCStr(argv[0]) : "main";
|
@@ -370,14 +425,14 @@ VALUE Database_filename(int argc, VALUE *argv, VALUE self) {
|
|
370
425
|
|
371
426
|
VALUE Database_transaction_active_p(VALUE self) {
|
372
427
|
Database_t *db;
|
373
|
-
|
428
|
+
GetOpenDatabase(self, db);
|
374
429
|
|
375
430
|
return sqlite3_get_autocommit(db->sqlite3_db) ? Qfalse : Qtrue;
|
376
431
|
}
|
377
432
|
|
378
433
|
VALUE Database_load_extension(VALUE self, VALUE path) {
|
379
434
|
Database_t *db;
|
380
|
-
|
435
|
+
GetOpenDatabase(self, db);
|
381
436
|
char *err_msg;
|
382
437
|
|
383
438
|
int rc = sqlite3_load_extension(db->sqlite3_db, RSTRING_PTR(path), 0, &err_msg);
|
@@ -396,10 +451,13 @@ void Init_Extralite() {
|
|
396
451
|
rb_define_alloc_func(cDatabase, Database_allocate);
|
397
452
|
|
398
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);
|
399
456
|
|
400
457
|
rb_define_method(cDatabase, "query", Database_query_hash, -1);
|
401
458
|
rb_define_method(cDatabase, "query_hash", Database_query_hash, -1);
|
402
459
|
rb_define_method(cDatabase, "query_ary", Database_query_ary, -1);
|
460
|
+
rb_define_method(cDatabase, "query_single_row", Database_query_single_row, -1);
|
403
461
|
rb_define_method(cDatabase, "query_single_column", Database_query_single_column, -1);
|
404
462
|
rb_define_method(cDatabase, "query_single_value", Database_query_single_value, -1);
|
405
463
|
|
@@ -410,4 +468,8 @@ void Init_Extralite() {
|
|
410
468
|
rb_define_method(cDatabase, "load_extension", Database_load_extension, 1);
|
411
469
|
|
412
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");
|
413
475
|
}
|