extralite 1.11 → 1.13.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/.github/workflows/test.yml +1 -1
- data/CHANGELOG.md +14 -0
- data/Gemfile.lock +3 -3
- data/README.md +69 -27
- 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/extconf.rb +3 -109
- 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 +239246 -0
- data/ext/extralite/sqlite3.h +12802 -0
- data/extralite.gemspec +1 -1
- data/lib/extralite/version.rb +1 -1
- data/test/extensions/text.dylib +0 -0
- data/test/extensions/text.so +0 -0
- data/test/helper.rb +2 -0
- data/test/perf_hash.rb +1 -1
- data/test/perf_prepared.rb +64 -0
- data/test/test_database.rb +14 -0
- data/test/test_extralite.rb +14 -0
- data/test/test_prepared_statement.rb +165 -0
- metadata +20 -9
- data/ext/extralite/extralite.c +0 -691
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 887537f02a38e762c189e2c322074efc59ef28a662021f59fbfad6cc0dafc13c
|
|
4
|
+
data.tar.gz: e519f080b81120044cd25113145fb571e5655b377e992643b5fc711a7ab6abfa
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 50fef96363c87e0f2461165bed0c608b0bf7a24d1d3d9b90fc1b741446a2685d94bd54d392bae1714984b4c95fbf925c1a839c2b70bfb62262fc3e1112c321a8
|
|
7
|
+
data.tar.gz: ca51f38183ebc65b01b18ad4ce58885eba4481ab8804de3e70ad140e1470bfeb2c99177db6e0817fb8aa5532c532d6a848e0b20814da93f29b84f7f4fd9bdd6d
|
data/.github/workflows/test.yml
CHANGED
data/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,17 @@
|
|
|
1
|
+
## 1.13.1 2022-02-27
|
|
2
|
+
|
|
3
|
+
- Fix compilation on TruffleRuby
|
|
4
|
+
|
|
5
|
+
## 1.13 2022-02-27
|
|
6
|
+
|
|
7
|
+
- Implement prepared statements (#7)
|
|
8
|
+
- Update SQLite to 3.38.0 (#6)
|
|
9
|
+
|
|
10
|
+
## 1.12 2022-02-15
|
|
11
|
+
|
|
12
|
+
- Add `Extralite.sqlite3_version` method
|
|
13
|
+
- Bundle sqlite3 in gem
|
|
14
|
+
|
|
1
15
|
## 1.11 2021-12-17
|
|
2
16
|
|
|
3
17
|
- Fix compilation on MacOS (#3)
|
data/Gemfile.lock
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: .
|
|
3
3
|
specs:
|
|
4
|
-
extralite (1.
|
|
4
|
+
extralite (1.13.1)
|
|
5
5
|
|
|
6
6
|
GEM
|
|
7
7
|
remote: https://rubygems.org/
|
|
8
8
|
specs:
|
|
9
9
|
docile (1.4.0)
|
|
10
|
-
json (2.
|
|
10
|
+
json (2.6.1)
|
|
11
11
|
minitest (5.15.0)
|
|
12
12
|
rake (13.0.6)
|
|
13
13
|
rake-compiler (1.1.6)
|
|
@@ -34,4 +34,4 @@ DEPENDENCIES
|
|
|
34
34
|
yard (= 0.9.27)
|
|
35
35
|
|
|
36
36
|
BUNDLED WITH
|
|
37
|
-
2.
|
|
37
|
+
2.3.3
|
data/README.md
CHANGED
|
@@ -23,20 +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
|
|
|
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.
|
|
32
36
|
- A variety of methods for different data access patterns: rows as hashes, rows
|
|
33
37
|
as arrays, single row, single column, single value.
|
|
38
|
+
- Prepared statements.
|
|
34
39
|
- Super fast - [up to 12.5x faster](#performance) than the
|
|
35
40
|
[sqlite3](https://github.com/sparklemotion/sqlite3-ruby) gem (see also
|
|
36
41
|
[comparison](#why-not-just-use-the-sqlite3-gem).)
|
|
37
|
-
- Improved [concurrency](#
|
|
38
|
-
|
|
39
|
-
results.
|
|
42
|
+
- Improved [concurrency](#concurrency) for multithreaded apps: the Ruby GVL is
|
|
43
|
+
released while preparing SQL statements and while iterating over results.
|
|
40
44
|
- Iterate over records with a block, or collect records into an array.
|
|
41
45
|
- Parameter binding.
|
|
42
46
|
- Automatically execute SQL strings containing multiple semicolon-separated
|
|
@@ -45,13 +49,30 @@ interacting with an SQLite3 database.
|
|
|
45
49
|
- Get number of rows changed by last query.
|
|
46
50
|
- Load extensions (loading of extensions is autmatically enabled. You can find
|
|
47
51
|
some useful extensions here: https://github.com/nalgeon/sqlean.)
|
|
48
|
-
- Includes a [Sequel adapter](#usage-with-sequel)
|
|
52
|
+
- Includes a [Sequel adapter](#usage-with-sequel).
|
|
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.
|
|
49
67
|
|
|
50
68
|
## Usage
|
|
51
69
|
|
|
52
70
|
```ruby
|
|
53
71
|
require 'extralite'
|
|
54
72
|
|
|
73
|
+
# get sqlite3 version
|
|
74
|
+
Extralite.sqlite3_version #=> "3.38.0"
|
|
75
|
+
|
|
55
76
|
# open a database
|
|
56
77
|
db = Extralite::Database.new('/tmp/my.db')
|
|
57
78
|
|
|
@@ -89,6 +110,12 @@ db.query('select * from foo where bar = :bar', bar: 42)
|
|
|
89
110
|
db.query('select * from foo where bar = :bar', 'bar' => 42)
|
|
90
111
|
db.query('select * from foo where bar = :bar', ':bar' => 42)
|
|
91
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
|
+
|
|
92
119
|
# get last insert rowid
|
|
93
120
|
rowid = db.last_insert_rowid
|
|
94
121
|
|
|
@@ -113,7 +140,7 @@ Extralite includes an adapter for
|
|
|
113
140
|
just use the `extralite` scheme instead of `sqlite`:
|
|
114
141
|
|
|
115
142
|
```ruby
|
|
116
|
-
DB = Sequel.connect('extralite
|
|
143
|
+
DB = Sequel.connect('extralite://blog.db')
|
|
117
144
|
articles = DB[:articles]
|
|
118
145
|
p articles.to_a
|
|
119
146
|
```
|
|
@@ -129,25 +156,27 @@ simpler API that gives me query results in a variety of ways. Thus extralite was
|
|
|
129
156
|
born.
|
|
130
157
|
|
|
131
158
|
Extralite is quite a bit [faster](#performance) than sqlite3-ruby and is also
|
|
132
|
-
[thread-friendly](#
|
|
133
|
-
|
|
134
|
-
|
|
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.
|
|
135
162
|
|
|
136
163
|
Here's a table summarizing the differences between the two gems:
|
|
137
164
|
|
|
138
165
|
| |sqlite3-ruby|Extralite|
|
|
139
166
|
|-|-|-|
|
|
167
|
+
|SQLite3 dependency|depends on OS-installed libsqlite3|bundles latest version of SQLite3|
|
|
140
168
|
|API design|multiple classes|single class|
|
|
141
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|
|
|
142
|
-
|
|
|
170
|
+
|Execute multiple statements|separate API (#execute_batch)|integrated|
|
|
171
|
+
|Prepared statements|yes|yes|
|
|
143
172
|
|custom functions in Ruby|yes|no|
|
|
144
173
|
|custom collations|yes|no|
|
|
145
174
|
|custom aggregate functions|yes|no|
|
|
146
|
-
|Multithread friendly|no|[yes](#
|
|
147
|
-
|Code size|~2650LoC|~
|
|
175
|
+
|Multithread friendly|no|[yes](#concurrency)|
|
|
176
|
+
|Code size|~2650LoC|~600LoC|
|
|
148
177
|
|Performance|1x|1.5x to 12.5x (see [below](#performance))|
|
|
149
178
|
|
|
150
|
-
##
|
|
179
|
+
## Concurrency
|
|
151
180
|
|
|
152
181
|
Extralite releases the GVL while making blocking calls to the sqlite3 library,
|
|
153
182
|
that is while preparing SQL statements and fetching rows. Releasing the GVL
|
|
@@ -159,29 +188,42 @@ performance:
|
|
|
159
188
|
|
|
160
189
|
A benchmark script is included, creating a table of various row counts, then
|
|
161
190
|
fetching the entire table using either `sqlite3` or `extralite`. This benchmark
|
|
162
|
-
shows Extralite to be up to 12
|
|
163
|
-
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)
|
|
164
197
|
|
|
165
198
|
|Row count|sqlite3-ruby|Extralite|Advantage|
|
|
166
199
|
|-:|-:|-:|-:|
|
|
167
|
-
|10|
|
|
168
|
-
|1K|286.8K rows/s|2106.
|
|
169
|
-
|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__|
|
|
203
|
+
|
|
204
|
+
### Rows as arrays
|
|
170
205
|
|
|
171
|
-
|
|
206
|
+
[Benchmark source code](https://github.com/digital-fabric/extralite/blob/main/test/perf_ary.rb)
|
|
172
207
|
|
|
173
208
|
|Row count|sqlite3-ruby|Extralite|Advantage|
|
|
174
209
|
|-:|-:|-:|-:|
|
|
175
|
-
|10|
|
|
210
|
+
|10|64.3K rows/s|94.0K rows/s|__1.46x__|
|
|
176
211
|
|1K|498.9K rows/s|2478.2K rows/s|__4.97x__|
|
|
177
212
|
|100K|441.1K rows/s|3023.4K rows/s|__6.85x__|
|
|
178
213
|
|
|
179
|
-
|
|
180
|
-
|
|
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__|
|
|
181
223
|
|
|
182
|
-
As those benchmarks show, Extralite is capabale of reading
|
|
183
|
-
|
|
184
|
-
|
|
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.6M rows/second when fetching
|
|
226
|
+
rows as hashes.
|
|
185
227
|
|
|
186
228
|
## Contributing
|
|
187
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
|
+
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
|
+
}
|