extralite 1.12 → 1.13
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/Gemfile.lock +1 -1
- data/README.md +64 -28
- data/Rakefile +1 -1
- data/bin/update_sqlite_source +26 -0
- data/ext/extralite/common.c +347 -0
- data/ext/extralite/database.c +385 -0
- data/ext/extralite/extralite.h +59 -0
- data/ext/extralite/extralite_ext.c +4 -2
- data/ext/extralite/prepared_statement.c +238 -0
- data/ext/extralite/sqlite3.c +5568 -3801
- data/ext/extralite/sqlite3.h +341 -31
- data/lib/extralite/version.rb +1 -1
- data/test/perf_hash.rb +1 -1
- data/test/perf_prepared.rb +64 -0
- data/test/test_prepared_statement.rb +165 -0
- metadata +9 -3
- data/ext/extralite/extralite.c +0 -702
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5d4d7fe119c7197de4a8cf2a7c54a98845a6bf3cbb5e5140c6b272e9b5d1e458
|
4
|
+
data.tar.gz: bfd3010a039dd6445cb0a0fe0c5dca12a1a7aa19d7c44ec3f89c5b8768f464f7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: bc80083972aafcca792665edaeaed05dedcc8d22f3f1c277d7211d99a807344ba6ec203098aa6b4038f77318fc40aa44511d46c6d586e7ed8c75862596c7a092
|
7
|
+
data.tar.gz: 0a4f2d8779ba19732e1ec2f5a2367b39d0889de83fc6cf03a90cd49fae2f378ffcf7238842ca17aff4e8826c66d98158a497e200361cff6caac0ff70cde59ad9
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -23,22 +23,24 @@
|
|
23
23
|
|
24
24
|
## What is Extralite?
|
25
25
|
|
26
|
-
Extralite is a fast, extra-lightweight (
|
27
|
-
wrapper for Ruby. It provides a
|
28
|
-
|
26
|
+
Extralite is a fast, extra-lightweight (about 600 lines of C-code) SQLite3
|
27
|
+
wrapper for Ruby. It provides a minimal set of methods for interacting with an
|
28
|
+
SQLite3 database, as well as prepared statements. Extralite bundles the latest
|
29
|
+
version of SQLite, offering access to the latest features and enhancements.
|
29
30
|
|
30
31
|
## Features
|
31
32
|
|
32
|
-
- Zero dependencies: Extralite bundles SQLite3 version
|
33
|
-
install any
|
33
|
+
- Zero dependencies: Extralite bundles SQLite3 version
|
34
|
+
[3.38.0](https://sqlite.org/releaselog/3_38_0.html) - no need to install any
|
35
|
+
`libsqlite3` packages.
|
34
36
|
- A variety of methods for different data access patterns: rows as hashes, rows
|
35
37
|
as arrays, single row, single column, single value.
|
38
|
+
- Prepared statements.
|
36
39
|
- Super fast - [up to 12.5x faster](#performance) than the
|
37
40
|
[sqlite3](https://github.com/sparklemotion/sqlite3-ruby) gem (see also
|
38
41
|
[comparison](#why-not-just-use-the-sqlite3-gem).)
|
39
|
-
- Improved [concurrency](#
|
40
|
-
|
41
|
-
results.
|
42
|
+
- Improved [concurrency](#concurrency) for multithreaded apps: the Ruby GVL is
|
43
|
+
released while preparing SQL statements and while iterating over results.
|
42
44
|
- Iterate over records with a block, or collect records into an array.
|
43
45
|
- Parameter binding.
|
44
46
|
- Automatically execute SQL strings containing multiple semicolon-separated
|
@@ -49,13 +51,27 @@ interacting with an SQLite3 database.
|
|
49
51
|
some useful extensions here: https://github.com/nalgeon/sqlean.)
|
50
52
|
- Includes a [Sequel adapter](#usage-with-sequel).
|
51
53
|
|
54
|
+
## Installation
|
55
|
+
|
56
|
+
To use Extralite in your Ruby app, add the following to your `Gemfile`:
|
57
|
+
|
58
|
+
```ruby
|
59
|
+
gem 'extralite'
|
60
|
+
```
|
61
|
+
|
62
|
+
You can also run `gem install extralite` if you just want to check it out.
|
63
|
+
|
64
|
+
> **Important note**: Extralite will take a while to install (on my modest
|
65
|
+
> machine it takes about a minute). This is owing to the fact that Extralite
|
66
|
+
> bundles the sqlite3 code, which is compiled upon installation.
|
67
|
+
|
52
68
|
## Usage
|
53
69
|
|
54
70
|
```ruby
|
55
71
|
require 'extralite'
|
56
72
|
|
57
73
|
# get sqlite3 version
|
58
|
-
Extralite.sqlite3_version #=> "3.
|
74
|
+
Extralite.sqlite3_version #=> "3.38.0"
|
59
75
|
|
60
76
|
# open a database
|
61
77
|
db = Extralite::Database.new('/tmp/my.db')
|
@@ -94,6 +110,12 @@ db.query('select * from foo where bar = :bar', bar: 42)
|
|
94
110
|
db.query('select * from foo where bar = :bar', 'bar' => 42)
|
95
111
|
db.query('select * from foo where bar = :bar', ':bar' => 42)
|
96
112
|
|
113
|
+
# prepared statements
|
114
|
+
stmt = db.prepare('select ? as foo, ? as bar') #=> Extralite::PreparedStatement
|
115
|
+
stmt.query(1, 2) #=> [{ :foo => 1, :bar => 2 }]
|
116
|
+
# PreparedStatement offers the same data access methods as the Database class,
|
117
|
+
# but without the sql parameter.
|
118
|
+
|
97
119
|
# get last insert rowid
|
98
120
|
rowid = db.last_insert_rowid
|
99
121
|
|
@@ -134,9 +156,9 @@ simpler API that gives me query results in a variety of ways. Thus extralite was
|
|
134
156
|
born.
|
135
157
|
|
136
158
|
Extralite is quite a bit [faster](#performance) than sqlite3-ruby and is also
|
137
|
-
[thread-friendly](#
|
138
|
-
|
139
|
-
|
159
|
+
[thread-friendly](#concurrency). On the other hand, Extralite does not have
|
160
|
+
support for defining custom functions, aggregates and collations. If you're
|
161
|
+
using any of those features, you'll have to stick to sqlite3-ruby.
|
140
162
|
|
141
163
|
Here's a table summarizing the differences between the two gems:
|
142
164
|
|
@@ -145,15 +167,16 @@ Here's a table summarizing the differences between the two gems:
|
|
145
167
|
|SQLite3 dependency|depends on OS-installed libsqlite3|bundles latest version of SQLite3|
|
146
168
|
|API design|multiple classes|single class|
|
147
169
|
|Query results|row as hash, row as array, single row, single value|row as hash, row as array, __single column__, single row, single value|
|
148
|
-
|
|
170
|
+
|Execute multiple statements|separate API (#execute_batch)|integrated|
|
171
|
+
|Prepared statements|yes|yes|
|
149
172
|
|custom functions in Ruby|yes|no|
|
150
173
|
|custom collations|yes|no|
|
151
174
|
|custom aggregate functions|yes|no|
|
152
|
-
|Multithread friendly|no|[yes](#
|
153
|
-
|Code size|~2650LoC|~
|
175
|
+
|Multithread friendly|no|[yes](#concurrency)|
|
176
|
+
|Code size|~2650LoC|~600LoC|
|
154
177
|
|Performance|1x|1.5x to 12.5x (see [below](#performance))|
|
155
178
|
|
156
|
-
##
|
179
|
+
## Concurrency
|
157
180
|
|
158
181
|
Extralite releases the GVL while making blocking calls to the sqlite3 library,
|
159
182
|
that is while preparing SQL statements and fetching rows. Releasing the GVL
|
@@ -165,29 +188,42 @@ performance:
|
|
165
188
|
|
166
189
|
A benchmark script is included, creating a table of various row counts, then
|
167
190
|
fetching the entire table using either `sqlite3` or `extralite`. This benchmark
|
168
|
-
shows Extralite to be up to 12
|
169
|
-
large number of rows.
|
191
|
+
shows Extralite to be up to ~12 times faster than `sqlite3` when fetching a
|
192
|
+
large number of rows.
|
193
|
+
|
194
|
+
### Rows as hashes
|
195
|
+
|
196
|
+
[Benchmark source code](https://github.com/digital-fabric/extralite/blob/main/test/perf_hash.rb)
|
170
197
|
|
171
198
|
|Row count|sqlite3-ruby|Extralite|Advantage|
|
172
199
|
|-:|-:|-:|-:|
|
173
|
-
|10|
|
174
|
-
|1K|286.8K rows/s|2106.
|
175
|
-
|100K|
|
200
|
+
|10|75.3K rows/s|134.2K rows/s|__1.78x__|
|
201
|
+
|1K|286.8K rows/s|2106.4K rows/s|__7.35x__|
|
202
|
+
|100K|181.0K rows/s|2275.3K rows/s|__12.53x__|
|
176
203
|
|
177
|
-
|
204
|
+
### Rows as arrays
|
205
|
+
|
206
|
+
[Benchmark source code](https://github.com/digital-fabric/extralite/blob/main/test/perf_ary.rb)
|
178
207
|
|
179
208
|
|Row count|sqlite3-ruby|Extralite|Advantage|
|
180
209
|
|-:|-:|-:|-:|
|
181
|
-
|10|
|
210
|
+
|10|64.3K rows/s|94.0K rows/s|__1.46x__|
|
182
211
|
|1K|498.9K rows/s|2478.2K rows/s|__4.97x__|
|
183
212
|
|100K|441.1K rows/s|3023.4K rows/s|__6.85x__|
|
184
213
|
|
185
|
-
|
186
|
-
|
214
|
+
### Prepared statements
|
215
|
+
|
216
|
+
[Benchmark source code](https://github.com/digital-fabric/extralite/blob/main/test/perf_prepared.rb)
|
217
|
+
|
218
|
+
|Row count|sqlite3-ruby|Extralite|Advantage|
|
219
|
+
|-:|-:|-:|-:|
|
220
|
+
|10|241.8K rows/s|888K rows/s|__3.67x__|
|
221
|
+
|1K|298.6K rows/s|2606K rows/s|__8.73x__|
|
222
|
+
|100K|201.6K rows/s|1934K rows/s|__9.6x__|
|
187
223
|
|
188
|
-
As those benchmarks show, Extralite is capabale of reading
|
189
|
-
|
190
|
-
|
224
|
+
As those benchmarks show, Extralite is capabale of reading up to 3M rows/second
|
225
|
+
when fetching rows as arrays, and up to 2.2M rows/second when fetching
|
226
|
+
rows as hashes.
|
191
227
|
|
192
228
|
## Contributing
|
193
229
|
|
data/Rakefile
CHANGED
@@ -16,7 +16,7 @@ task :test do
|
|
16
16
|
exec 'ruby test/run.rb'
|
17
17
|
end
|
18
18
|
|
19
|
-
CLEAN.include '
|
19
|
+
CLEAN.include 'lib/*.o', 'lib/*.so', 'lib/*.so.*', 'lib/*.a', 'lib/*.bundle', 'lib/*.jar', 'pkg', 'tmp'
|
20
20
|
|
21
21
|
require 'yard'
|
22
22
|
YARD_FILES = FileList['ext/extralite/extralite.c', 'lib/extralite.rb', 'lib/sequel/adapters/extralite.rb']
|
@@ -0,0 +1,26 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
version = ARGV[0]
|
5
|
+
raise "Please specify version" unless version
|
6
|
+
|
7
|
+
require 'fileutils'
|
8
|
+
require 'date'
|
9
|
+
|
10
|
+
FileUtils.cd '/tmp'
|
11
|
+
|
12
|
+
version_id = version.gsub('.', '')
|
13
|
+
version_id += '0' * (7 - version_id.length)
|
14
|
+
url = "https://sqlite.org/#{Date.today.year}/sqlite-amalgamation-#{version_id}.zip"
|
15
|
+
dest = File.expand_path('../ext/extralite', __dir__)
|
16
|
+
|
17
|
+
puts "Downloading from #{url}..."
|
18
|
+
`curl #{url} > #{version_id}.zip`
|
19
|
+
|
20
|
+
puts "Unzipping zip file..."
|
21
|
+
`unzip -o #{version_id}.zip`
|
22
|
+
|
23
|
+
puts "Copying source files"
|
24
|
+
`cp sqlite-amalgamation-#{version_id}/sqlite3.* #{dest}/`
|
25
|
+
|
26
|
+
puts 'Done updating source files'
|
@@ -0,0 +1,347 @@
|
|
1
|
+
#include <stdio.h>
|
2
|
+
#include "extralite.h"
|
3
|
+
|
4
|
+
static inline VALUE get_column_value(sqlite3_stmt *stmt, int col, int type) {
|
5
|
+
switch (type) {
|
6
|
+
case SQLITE_NULL:
|
7
|
+
return Qnil;
|
8
|
+
case SQLITE_INTEGER:
|
9
|
+
return LL2NUM(sqlite3_column_int64(stmt, col));
|
10
|
+
case SQLITE_FLOAT:
|
11
|
+
return DBL2NUM(sqlite3_column_double(stmt, col));
|
12
|
+
case SQLITE_TEXT:
|
13
|
+
return rb_str_new_cstr((char *)sqlite3_column_text(stmt, col));
|
14
|
+
case SQLITE_BLOB:
|
15
|
+
return rb_str_new((const char *)sqlite3_column_blob(stmt, col), (long)sqlite3_column_bytes(stmt, col));
|
16
|
+
default:
|
17
|
+
rb_raise(cError, "Unknown column type: %d", type);
|
18
|
+
}
|
19
|
+
|
20
|
+
return Qnil;
|
21
|
+
}
|
22
|
+
|
23
|
+
void bind_parameter_value(sqlite3_stmt *stmt, int pos, VALUE value);
|
24
|
+
|
25
|
+
void bind_hash_parameter_values(sqlite3_stmt *stmt, VALUE hash) {
|
26
|
+
VALUE keys = rb_funcall(hash, ID_KEYS, 0);
|
27
|
+
long len = RARRAY_LEN(keys);
|
28
|
+
for (long i = 0; i < len; i++) {
|
29
|
+
VALUE k = RARRAY_AREF(keys, i);
|
30
|
+
VALUE v = rb_hash_aref(hash, k);
|
31
|
+
|
32
|
+
switch (TYPE(k)) {
|
33
|
+
case T_FIXNUM:
|
34
|
+
bind_parameter_value(stmt, NUM2INT(k), v);
|
35
|
+
break;
|
36
|
+
case T_SYMBOL:
|
37
|
+
k = rb_funcall(k, ID_TO_S, 0);
|
38
|
+
case T_STRING:
|
39
|
+
if(RSTRING_PTR(k)[0] != ':') k = rb_str_plus(rb_str_new2(":"), k);
|
40
|
+
int pos = sqlite3_bind_parameter_index(stmt, StringValuePtr(k));
|
41
|
+
bind_parameter_value(stmt, pos, v);
|
42
|
+
break;
|
43
|
+
default:
|
44
|
+
rb_raise(cError, "Cannot bind hash key value idx %ld", i);
|
45
|
+
}
|
46
|
+
}
|
47
|
+
RB_GC_GUARD(keys);
|
48
|
+
}
|
49
|
+
|
50
|
+
inline void bind_parameter_value(sqlite3_stmt *stmt, int pos, VALUE value) {
|
51
|
+
switch (TYPE(value)) {
|
52
|
+
case T_NIL:
|
53
|
+
sqlite3_bind_null(stmt, pos);
|
54
|
+
return;
|
55
|
+
case T_FIXNUM:
|
56
|
+
sqlite3_bind_int64(stmt, pos, NUM2LL(value));
|
57
|
+
return;
|
58
|
+
case T_FLOAT:
|
59
|
+
sqlite3_bind_double(stmt, pos, NUM2DBL(value));
|
60
|
+
return;
|
61
|
+
case T_TRUE:
|
62
|
+
sqlite3_bind_int(stmt, pos, 1);
|
63
|
+
return;
|
64
|
+
case T_FALSE:
|
65
|
+
sqlite3_bind_int(stmt, pos, 0);
|
66
|
+
return;
|
67
|
+
case T_STRING:
|
68
|
+
sqlite3_bind_text(stmt, pos, RSTRING_PTR(value), RSTRING_LEN(value), SQLITE_TRANSIENT);
|
69
|
+
return;
|
70
|
+
case T_HASH:
|
71
|
+
bind_hash_parameter_values(stmt, value);
|
72
|
+
return;
|
73
|
+
default:
|
74
|
+
rb_raise(cError, "Cannot bind parameter at position %d", pos);
|
75
|
+
}
|
76
|
+
}
|
77
|
+
|
78
|
+
void bind_all_parameters(sqlite3_stmt *stmt, int argc, VALUE *argv) {
|
79
|
+
for (int i = 0; i < argc; i++) {
|
80
|
+
bind_parameter_value(stmt, i + 1, argv[i]);
|
81
|
+
}
|
82
|
+
}
|
83
|
+
|
84
|
+
static inline VALUE get_column_names(sqlite3_stmt *stmt, int column_count) {
|
85
|
+
VALUE arr = rb_ary_new2(column_count);
|
86
|
+
for (int i = 0; i < column_count; i++) {
|
87
|
+
VALUE name = ID2SYM(rb_intern(sqlite3_column_name(stmt, i)));
|
88
|
+
rb_ary_push(arr, name);
|
89
|
+
}
|
90
|
+
return arr;
|
91
|
+
}
|
92
|
+
|
93
|
+
static inline VALUE row_to_hash(sqlite3_stmt *stmt, int column_count, VALUE column_names) {
|
94
|
+
VALUE row = rb_hash_new();
|
95
|
+
for (int i = 0; i < column_count; i++) {
|
96
|
+
VALUE value = get_column_value(stmt, i, sqlite3_column_type(stmt, i));
|
97
|
+
rb_hash_aset(row, RARRAY_AREF(column_names, i), value);
|
98
|
+
}
|
99
|
+
return row;
|
100
|
+
}
|
101
|
+
|
102
|
+
static inline VALUE row_to_ary(sqlite3_stmt *stmt, int column_count) {
|
103
|
+
VALUE row = rb_ary_new2(column_count);
|
104
|
+
for (int i = 0; i < column_count; i++) {
|
105
|
+
VALUE value = get_column_value(stmt, i, sqlite3_column_type(stmt, i));
|
106
|
+
rb_ary_push(row, value);
|
107
|
+
}
|
108
|
+
return row;
|
109
|
+
}
|
110
|
+
|
111
|
+
typedef struct {
|
112
|
+
sqlite3 *db;
|
113
|
+
sqlite3_stmt **stmt;
|
114
|
+
const char *str;
|
115
|
+
long len;
|
116
|
+
int rc;
|
117
|
+
} prepare_stmt_ctx;
|
118
|
+
|
119
|
+
void *prepare_multi_stmt_without_gvl(void *ptr) {
|
120
|
+
prepare_stmt_ctx *ctx = (prepare_stmt_ctx *)ptr;
|
121
|
+
const char *rest = NULL;
|
122
|
+
const char *str = ctx->str;
|
123
|
+
const char *end = ctx->str + ctx->len;
|
124
|
+
while (1) {
|
125
|
+
ctx->rc = sqlite3_prepare_v2(ctx->db, str, end - str, ctx->stmt, &rest);
|
126
|
+
if (ctx->rc) {
|
127
|
+
// error
|
128
|
+
sqlite3_finalize(*ctx->stmt);
|
129
|
+
return NULL;
|
130
|
+
}
|
131
|
+
|
132
|
+
if (rest == end) return NULL;
|
133
|
+
|
134
|
+
// perform current query, but discard its results
|
135
|
+
ctx->rc = sqlite3_step(*ctx->stmt);
|
136
|
+
sqlite3_finalize(*ctx->stmt);
|
137
|
+
switch (ctx->rc) {
|
138
|
+
case SQLITE_BUSY:
|
139
|
+
case SQLITE_ERROR:
|
140
|
+
case SQLITE_MISUSE:
|
141
|
+
return NULL;
|
142
|
+
}
|
143
|
+
str = rest;
|
144
|
+
}
|
145
|
+
return NULL;
|
146
|
+
}
|
147
|
+
|
148
|
+
/*
|
149
|
+
This function prepares a statement from an SQL string containing one or more SQL
|
150
|
+
statements. It will release the GVL while the statements are being prepared and
|
151
|
+
executed. All statements excluding the last one are executed. The last statement
|
152
|
+
is not executed, but instead handed back to the caller for looping over results.
|
153
|
+
*/
|
154
|
+
void prepare_multi_stmt(sqlite3 *db, sqlite3_stmt **stmt, VALUE sql) {
|
155
|
+
prepare_stmt_ctx ctx = {db, stmt, RSTRING_PTR(sql), RSTRING_LEN(sql), 0};
|
156
|
+
rb_thread_call_without_gvl(prepare_multi_stmt_without_gvl, (void *)&ctx, RUBY_UBF_IO, 0);
|
157
|
+
RB_GC_GUARD(sql);
|
158
|
+
|
159
|
+
switch (ctx.rc) {
|
160
|
+
case 0:
|
161
|
+
return;
|
162
|
+
case SQLITE_BUSY:
|
163
|
+
rb_raise(cBusyError, "Database is busy");
|
164
|
+
case SQLITE_ERROR:
|
165
|
+
rb_raise(cSQLError, "%s", sqlite3_errmsg(db));
|
166
|
+
default:
|
167
|
+
rb_raise(cError, "Invalid return code for prepare_multi_stmt_without_gvl: %d (please open an issue on https://github.com/digital-fabric/extralite)", ctx.rc);
|
168
|
+
}
|
169
|
+
}
|
170
|
+
|
171
|
+
#define SQLITE_MULTI_STMT -1
|
172
|
+
|
173
|
+
void *prepare_single_stmt_without_gvl(void *ptr) {
|
174
|
+
prepare_stmt_ctx *ctx = (prepare_stmt_ctx *)ptr;
|
175
|
+
const char *rest = NULL;
|
176
|
+
const char *str = ctx->str;
|
177
|
+
const char *end = ctx->str + ctx->len;
|
178
|
+
|
179
|
+
ctx->rc = sqlite3_prepare_v2(ctx->db, str, end - str, ctx->stmt, &rest);
|
180
|
+
if (ctx->rc)
|
181
|
+
goto discard_stmt;
|
182
|
+
else if (rest != end) {
|
183
|
+
ctx->rc = SQLITE_MULTI_STMT;
|
184
|
+
goto discard_stmt;
|
185
|
+
}
|
186
|
+
goto end;
|
187
|
+
discard_stmt:
|
188
|
+
if (*ctx->stmt) {
|
189
|
+
sqlite3_finalize(*ctx->stmt);
|
190
|
+
*ctx->stmt = NULL;
|
191
|
+
}
|
192
|
+
end:
|
193
|
+
return NULL;
|
194
|
+
}
|
195
|
+
|
196
|
+
void prepare_single_stmt(sqlite3 *db, sqlite3_stmt **stmt, VALUE sql) {
|
197
|
+
prepare_stmt_ctx ctx = {db, stmt, RSTRING_PTR(sql), RSTRING_LEN(sql), 0};
|
198
|
+
rb_thread_call_without_gvl(prepare_single_stmt_without_gvl, (void *)&ctx, RUBY_UBF_IO, 0);
|
199
|
+
RB_GC_GUARD(sql);
|
200
|
+
|
201
|
+
switch (ctx.rc) {
|
202
|
+
case 0:
|
203
|
+
return;
|
204
|
+
case SQLITE_BUSY:
|
205
|
+
rb_raise(cBusyError, "Database is busy");
|
206
|
+
case SQLITE_ERROR:
|
207
|
+
rb_raise(cSQLError, "%s", sqlite3_errmsg(db));
|
208
|
+
case SQLITE_MULTI_STMT:
|
209
|
+
rb_raise(cSQLError, "A prepared statement does not accept SQL strings with multiple queries");
|
210
|
+
default:
|
211
|
+
rb_raise(cError, "Invalid return code for prepare_multi_stmt_without_gvl: %d (please open an issue on https://github.com/digital-fabric/extralite)", ctx.rc);
|
212
|
+
}
|
213
|
+
}
|
214
|
+
|
215
|
+
struct step_ctx {
|
216
|
+
sqlite3_stmt *stmt;
|
217
|
+
int rc;
|
218
|
+
};
|
219
|
+
|
220
|
+
void *stmt_iterate_without_gvl(void *ptr) {
|
221
|
+
struct step_ctx *ctx = (struct step_ctx *)ptr;
|
222
|
+
ctx->rc = sqlite3_step(ctx->stmt);
|
223
|
+
return NULL;
|
224
|
+
}
|
225
|
+
|
226
|
+
inline int stmt_iterate(sqlite3_stmt *stmt, sqlite3 *db) {
|
227
|
+
struct step_ctx ctx = {stmt, 0};
|
228
|
+
rb_thread_call_without_gvl(stmt_iterate_without_gvl, (void *)&ctx, RUBY_UBF_IO, 0);
|
229
|
+
switch (ctx.rc) {
|
230
|
+
case SQLITE_ROW:
|
231
|
+
return 1;
|
232
|
+
case SQLITE_DONE:
|
233
|
+
return 0;
|
234
|
+
case SQLITE_BUSY:
|
235
|
+
rb_raise(cBusyError, "Database is busy");
|
236
|
+
case SQLITE_ERROR:
|
237
|
+
rb_raise(cSQLError, "%s", sqlite3_errmsg(db));
|
238
|
+
default:
|
239
|
+
rb_raise(cError, "Invalid return code for sqlite3_step: %d (please open an issue on https://github.com/digital-fabric/extralite)", ctx.rc);
|
240
|
+
}
|
241
|
+
|
242
|
+
return 0;
|
243
|
+
}
|
244
|
+
|
245
|
+
VALUE cleanup_stmt(query_ctx *ctx) {
|
246
|
+
if (ctx->stmt) sqlite3_finalize(ctx->stmt);
|
247
|
+
return Qnil;
|
248
|
+
}
|
249
|
+
|
250
|
+
VALUE safe_query_hash(query_ctx *ctx) {
|
251
|
+
VALUE result = ctx->self;
|
252
|
+
int yield_to_block = rb_block_given_p();
|
253
|
+
VALUE row;
|
254
|
+
int column_count;
|
255
|
+
VALUE column_names;
|
256
|
+
|
257
|
+
column_count = sqlite3_column_count(ctx->stmt);
|
258
|
+
column_names = get_column_names(ctx->stmt, column_count);
|
259
|
+
|
260
|
+
// block not given, so prepare the array of records to be returned
|
261
|
+
if (!yield_to_block) result = rb_ary_new();
|
262
|
+
|
263
|
+
while (stmt_iterate(ctx->stmt, ctx->sqlite3_db)) {
|
264
|
+
row = row_to_hash(ctx->stmt, column_count, column_names);
|
265
|
+
if (yield_to_block) rb_yield(row); else rb_ary_push(result, row);
|
266
|
+
}
|
267
|
+
|
268
|
+
RB_GC_GUARD(column_names);
|
269
|
+
RB_GC_GUARD(row);
|
270
|
+
RB_GC_GUARD(result);
|
271
|
+
return result;
|
272
|
+
}
|
273
|
+
|
274
|
+
VALUE safe_query_ary(query_ctx *ctx) {
|
275
|
+
int column_count;
|
276
|
+
VALUE result = ctx->self;
|
277
|
+
int yield_to_block = rb_block_given_p();
|
278
|
+
VALUE row;
|
279
|
+
|
280
|
+
column_count = sqlite3_column_count(ctx->stmt);
|
281
|
+
|
282
|
+
// block not given, so prepare the array of records to be returned
|
283
|
+
if (!yield_to_block) result = rb_ary_new();
|
284
|
+
|
285
|
+
while (stmt_iterate(ctx->stmt, ctx->sqlite3_db)) {
|
286
|
+
row = row_to_ary(ctx->stmt, column_count);
|
287
|
+
if (yield_to_block) rb_yield(row); else rb_ary_push(result, row);
|
288
|
+
}
|
289
|
+
|
290
|
+
RB_GC_GUARD(row);
|
291
|
+
RB_GC_GUARD(result);
|
292
|
+
return result;
|
293
|
+
}
|
294
|
+
|
295
|
+
VALUE safe_query_single_row(query_ctx *ctx) {
|
296
|
+
int column_count;
|
297
|
+
VALUE row = Qnil;
|
298
|
+
VALUE column_names;
|
299
|
+
|
300
|
+
column_count = sqlite3_column_count(ctx->stmt);
|
301
|
+
column_names = get_column_names(ctx->stmt, column_count);
|
302
|
+
|
303
|
+
if (stmt_iterate(ctx->stmt, ctx->sqlite3_db))
|
304
|
+
row = row_to_hash(ctx->stmt, column_count, column_names);
|
305
|
+
|
306
|
+
RB_GC_GUARD(row);
|
307
|
+
RB_GC_GUARD(column_names);
|
308
|
+
return row;
|
309
|
+
}
|
310
|
+
|
311
|
+
VALUE safe_query_single_column(query_ctx *ctx) {
|
312
|
+
int column_count;
|
313
|
+
VALUE result = ctx->self;
|
314
|
+
int yield_to_block = rb_block_given_p();
|
315
|
+
VALUE value;
|
316
|
+
|
317
|
+
column_count = sqlite3_column_count(ctx->stmt);
|
318
|
+
if (column_count != 1)
|
319
|
+
rb_raise(cError, "Expected query result to have 1 column");
|
320
|
+
|
321
|
+
// block not given, so prepare the array of records to be returned
|
322
|
+
if (!yield_to_block) result = rb_ary_new();
|
323
|
+
|
324
|
+
while (stmt_iterate(ctx->stmt, ctx->sqlite3_db)) {
|
325
|
+
value = get_column_value(ctx->stmt, 0, sqlite3_column_type(ctx->stmt, 0));
|
326
|
+
if (yield_to_block) rb_yield(value); else rb_ary_push(result, value);
|
327
|
+
}
|
328
|
+
|
329
|
+
RB_GC_GUARD(value);
|
330
|
+
RB_GC_GUARD(result);
|
331
|
+
return result;
|
332
|
+
}
|
333
|
+
|
334
|
+
VALUE safe_query_single_value(query_ctx *ctx) {
|
335
|
+
int column_count;
|
336
|
+
VALUE value = Qnil;
|
337
|
+
|
338
|
+
column_count = sqlite3_column_count(ctx->stmt);
|
339
|
+
if (column_count != 1)
|
340
|
+
rb_raise(cError, "Expected query result to have 1 column");
|
341
|
+
|
342
|
+
if (stmt_iterate(ctx->stmt, ctx->sqlite3_db))
|
343
|
+
value = get_column_value(ctx->stmt, 0, sqlite3_column_type(ctx->stmt, 0));
|
344
|
+
|
345
|
+
RB_GC_GUARD(value);
|
346
|
+
return value;
|
347
|
+
}
|