extralite 1.12 → 1.14
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 +14 -0
- data/Gemfile +1 -1
- data/Gemfile.lock +1 -1
- data/LICENSE +1 -1
- data/README.md +81 -28
- data/Rakefile +1 -2
- data/bin/update_sqlite_source +26 -0
- data/ext/extralite/common.c +347 -0
- data/ext/extralite/database.c +385 -0
- data/ext/extralite/extconf-bundle.rb +9 -0
- data/ext/extralite/extconf.rb +108 -2
- data/ext/extralite/extralite.h +64 -0
- data/ext/extralite/extralite_ext.c +4 -2
- data/ext/extralite/extralite_sqlite3.c +3 -0
- data/ext/extralite/prepared_statement.c +238 -0
- data/ext/{extralite → sqlite3}/sqlite3.c +5568 -3801
- data/ext/{extralite → sqlite3}/sqlite3.h +341 -31
- data/extralite-bundle.gemspec +8 -0
- data/extralite.gemspec +3 -25
- data/gemspec.rb +26 -0
- data/lib/extralite/version.rb +1 -1
- data/test/helper.rb +1 -1
- data/test/perf_hash.rb +1 -1
- data/test/perf_prepared.rb +64 -0
- data/test/test_extralite.rb +5 -6
- data/test/test_prepared_statement.rb +165 -0
- metadata +15 -5
- 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: 32105cda1ba5871c89609e42828a095d76d4cd2da29bd6d47beefc6a3ae93bd6
|
|
4
|
+
data.tar.gz: 4ff38c0e6144b22792e2a57792f295fc85a6b3d4b9eb09820e4f3b3e4c78bec1
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: c0e94553b7ca02732fe93729844ef16ce58bce7582be1e9c1684f215999b7a4a84414265c6a6932fbc379887940bd70d8ff913c782b0ce6b1ee0eebd9f4438a0
|
|
7
|
+
data.tar.gz: 14db1538d2aafb781375d636cdcb9a42e92a74ce11304a953930f7456c76895051f8a37e61cf5dfff7b8dc3c4e02694187fd0ad6a8204ca0d948ec1fe3b26479
|
data/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,17 @@
|
|
|
1
|
+
## 1.14 2022-02-28
|
|
2
|
+
|
|
3
|
+
- Introduce `extralite-bundle` gem for bundling SQLite, use system lib by
|
|
4
|
+
default.
|
|
5
|
+
|
|
6
|
+
## 1.13.1 2022-02-27
|
|
7
|
+
|
|
8
|
+
- Fix compilation on TruffleRuby
|
|
9
|
+
|
|
10
|
+
## 1.13 2022-02-27
|
|
11
|
+
|
|
12
|
+
- Implement prepared statements (#7)
|
|
13
|
+
- Update SQLite to 3.38.0 (#6)
|
|
14
|
+
|
|
1
15
|
## 1.12 2022-02-15
|
|
2
16
|
|
|
3
17
|
- Add `Extralite.sqlite3_version` method
|
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
data/LICENSE
CHANGED
data/README.md
CHANGED
|
@@ -23,22 +23,26 @@
|
|
|
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.
|
|
29
|
+
|
|
30
|
+
Extralite comes in two flavors: the `extralite` gem which uses the
|
|
31
|
+
system-installed sqlite3 library, and the `extralite-bundle` gem which bundles
|
|
32
|
+
the latest version of SQLite
|
|
33
|
+
([3.38.0](https://sqlite.org/releaselog/3_38_0.html)), offering access to the
|
|
34
|
+
latest features and enhancements.
|
|
29
35
|
|
|
30
36
|
## Features
|
|
31
37
|
|
|
32
|
-
- Zero dependencies: Extralite bundles SQLite3 version 3.37.2 - no need to
|
|
33
|
-
install any `libsqlite3` packages.
|
|
34
38
|
- A variety of methods for different data access patterns: rows as hashes, rows
|
|
35
39
|
as arrays, single row, single column, single value.
|
|
40
|
+
- Prepared statements.
|
|
36
41
|
- Super fast - [up to 12.5x faster](#performance) than the
|
|
37
42
|
[sqlite3](https://github.com/sparklemotion/sqlite3-ruby) gem (see also
|
|
38
43
|
[comparison](#why-not-just-use-the-sqlite3-gem).)
|
|
39
|
-
- Improved [concurrency](#
|
|
40
|
-
|
|
41
|
-
results.
|
|
44
|
+
- Improved [concurrency](#concurrency) for multithreaded apps: the Ruby GVL is
|
|
45
|
+
released while preparing SQL statements and while iterating over results.
|
|
42
46
|
- Iterate over records with a block, or collect records into an array.
|
|
43
47
|
- Parameter binding.
|
|
44
48
|
- Automatically execute SQL strings containing multiple semicolon-separated
|
|
@@ -49,13 +53,36 @@ interacting with an SQLite3 database.
|
|
|
49
53
|
some useful extensions here: https://github.com/nalgeon/sqlean.)
|
|
50
54
|
- Includes a [Sequel adapter](#usage-with-sequel).
|
|
51
55
|
|
|
56
|
+
## Installation
|
|
57
|
+
|
|
58
|
+
To use Extralite in your Ruby app, add the following to your `Gemfile`:
|
|
59
|
+
|
|
60
|
+
```ruby
|
|
61
|
+
gem 'extralite'
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
You can also run `gem install extralite` if you just want to check it out.
|
|
65
|
+
|
|
66
|
+
## Installing the Extralite-SQLite3 bundle
|
|
67
|
+
|
|
68
|
+
If you don't have sqlite3 installed on your system, do not want to use the
|
|
69
|
+
system-installed version of SQLite3, or would like to use the latest version of
|
|
70
|
+
SQLite3, you can install the `extralite-bundle` gem, which integrates the
|
|
71
|
+
SQLite3 source code.
|
|
72
|
+
|
|
73
|
+
> **Important note**: The `extralite-bundle` will take a while to install (on my
|
|
74
|
+
> modest machine it takes about a minute), due to the size of the sqlite3 code.
|
|
75
|
+
|
|
76
|
+
Usage of the `extralite-bundle` gem is identical to the usage of the normal
|
|
77
|
+
`extralite` gem.
|
|
78
|
+
|
|
52
79
|
## Usage
|
|
53
80
|
|
|
54
81
|
```ruby
|
|
55
82
|
require 'extralite'
|
|
56
83
|
|
|
57
84
|
# get sqlite3 version
|
|
58
|
-
Extralite.sqlite3_version #=> "3.
|
|
85
|
+
Extralite.sqlite3_version #=> "3.35.2"
|
|
59
86
|
|
|
60
87
|
# open a database
|
|
61
88
|
db = Extralite::Database.new('/tmp/my.db')
|
|
@@ -94,6 +121,12 @@ db.query('select * from foo where bar = :bar', bar: 42)
|
|
|
94
121
|
db.query('select * from foo where bar = :bar', 'bar' => 42)
|
|
95
122
|
db.query('select * from foo where bar = :bar', ':bar' => 42)
|
|
96
123
|
|
|
124
|
+
# prepared statements
|
|
125
|
+
stmt = db.prepare('select ? as foo, ? as bar') #=> Extralite::PreparedStatement
|
|
126
|
+
stmt.query(1, 2) #=> [{ :foo => 1, :bar => 2 }]
|
|
127
|
+
# PreparedStatement offers the same data access methods as the Database class,
|
|
128
|
+
# but without the sql parameter.
|
|
129
|
+
|
|
97
130
|
# get last insert rowid
|
|
98
131
|
rowid = db.last_insert_rowid
|
|
99
132
|
|
|
@@ -134,9 +167,9 @@ simpler API that gives me query results in a variety of ways. Thus extralite was
|
|
|
134
167
|
born.
|
|
135
168
|
|
|
136
169
|
Extralite is quite a bit [faster](#performance) than sqlite3-ruby and is also
|
|
137
|
-
[thread-friendly](#
|
|
138
|
-
|
|
139
|
-
|
|
170
|
+
[thread-friendly](#concurrency). On the other hand, Extralite does not have
|
|
171
|
+
support for defining custom functions, aggregates and collations. If you're
|
|
172
|
+
using any of those features, you'll have to stick to sqlite3-ruby.
|
|
140
173
|
|
|
141
174
|
Here's a table summarizing the differences between the two gems:
|
|
142
175
|
|
|
@@ -145,15 +178,16 @@ Here's a table summarizing the differences between the two gems:
|
|
|
145
178
|
|SQLite3 dependency|depends on OS-installed libsqlite3|bundles latest version of SQLite3|
|
|
146
179
|
|API design|multiple classes|single class|
|
|
147
180
|
|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
|
-
|
|
|
181
|
+
|Execute multiple statements|separate API (#execute_batch)|integrated|
|
|
182
|
+
|Prepared statements|yes|yes|
|
|
149
183
|
|custom functions in Ruby|yes|no|
|
|
150
184
|
|custom collations|yes|no|
|
|
151
185
|
|custom aggregate functions|yes|no|
|
|
152
|
-
|Multithread friendly|no|[yes](#
|
|
153
|
-
|Code size|~2650LoC|~
|
|
186
|
+
|Multithread friendly|no|[yes](#concurrency)|
|
|
187
|
+
|Code size|~2650LoC|~600LoC|
|
|
154
188
|
|Performance|1x|1.5x to 12.5x (see [below](#performance))|
|
|
155
189
|
|
|
156
|
-
##
|
|
190
|
+
## Concurrency
|
|
157
191
|
|
|
158
192
|
Extralite releases the GVL while making blocking calls to the sqlite3 library,
|
|
159
193
|
that is while preparing SQL statements and fetching rows. Releasing the GVL
|
|
@@ -165,29 +199,48 @@ performance:
|
|
|
165
199
|
|
|
166
200
|
A benchmark script is included, creating a table of various row counts, then
|
|
167
201
|
fetching the entire table using either `sqlite3` or `extralite`. This benchmark
|
|
168
|
-
shows Extralite to be up to 12
|
|
169
|
-
large number of rows.
|
|
202
|
+
shows Extralite to be up to ~12 times faster than `sqlite3` when fetching a
|
|
203
|
+
large number of rows.
|
|
204
|
+
|
|
205
|
+
### Rows as hashes
|
|
206
|
+
|
|
207
|
+
[Benchmark source code](https://github.com/digital-fabric/extralite/blob/main/test/perf_hash.rb)
|
|
170
208
|
|
|
171
209
|
|Row count|sqlite3-ruby|Extralite|Advantage|
|
|
172
210
|
|-:|-:|-:|-:|
|
|
173
|
-
|10|
|
|
174
|
-
|1K|286.8K rows/s|2106.
|
|
175
|
-
|100K|
|
|
211
|
+
|10|75.3K rows/s|134.2K rows/s|__1.78x__|
|
|
212
|
+
|1K|286.8K rows/s|2106.4K rows/s|__7.35x__|
|
|
213
|
+
|100K|181.0K rows/s|2275.3K rows/s|__12.53x__|
|
|
176
214
|
|
|
177
|
-
|
|
215
|
+
### Rows as arrays
|
|
216
|
+
|
|
217
|
+
[Benchmark source code](https://github.com/digital-fabric/extralite/blob/main/test/perf_ary.rb)
|
|
178
218
|
|
|
179
219
|
|Row count|sqlite3-ruby|Extralite|Advantage|
|
|
180
220
|
|-:|-:|-:|-:|
|
|
181
|
-
|10|
|
|
221
|
+
|10|64.3K rows/s|94.0K rows/s|__1.46x__|
|
|
182
222
|
|1K|498.9K rows/s|2478.2K rows/s|__4.97x__|
|
|
183
223
|
|100K|441.1K rows/s|3023.4K rows/s|__6.85x__|
|
|
184
224
|
|
|
185
|
-
|
|
186
|
-
|
|
225
|
+
### Prepared statements
|
|
226
|
+
|
|
227
|
+
[Benchmark source code](https://github.com/digital-fabric/extralite/blob/main/test/perf_prepared.rb)
|
|
228
|
+
|
|
229
|
+
|Row count|sqlite3-ruby|Extralite|Advantage|
|
|
230
|
+
|-:|-:|-:|-:|
|
|
231
|
+
|10|241.8K rows/s|888K rows/s|__3.67x__|
|
|
232
|
+
|1K|298.6K rows/s|2606K rows/s|__8.73x__|
|
|
233
|
+
|100K|201.6K rows/s|1934K rows/s|__9.6x__|
|
|
234
|
+
|
|
235
|
+
As those benchmarks show, Extralite is capabale of reading up to 3M rows/second
|
|
236
|
+
when fetching rows as arrays, and up to 2.6M rows/second when fetching
|
|
237
|
+
rows as hashes.
|
|
238
|
+
|
|
239
|
+
## License
|
|
187
240
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
241
|
+
The source code for Extralite is published under the [MIT license](LICENSE). The
|
|
242
|
+
source code for SQLite is in the [public
|
|
243
|
+
domain](https://sqlite.org/copyright.html).
|
|
191
244
|
|
|
192
245
|
## Contributing
|
|
193
246
|
|
data/Rakefile
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require 'bundler/gem_tasks'
|
|
4
3
|
require 'rake/clean'
|
|
5
4
|
|
|
6
5
|
require 'rake/extensiontask'
|
|
@@ -16,7 +15,7 @@ task :test do
|
|
|
16
15
|
exec 'ruby test/run.rb'
|
|
17
16
|
end
|
|
18
17
|
|
|
19
|
-
CLEAN.include '
|
|
18
|
+
CLEAN.include 'lib/*.o', 'lib/*.so', 'lib/*.so.*', 'lib/*.a', 'lib/*.bundle', 'lib/*.jar', 'pkg', 'tmp'
|
|
20
19
|
|
|
21
20
|
require 'yard'
|
|
22
21
|
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
|
+
static 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
|
+
}
|