extralite 1.11 → 1.13.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d88bd81eca07e148be1f8d5b07d24c0aab49b32a9dc9aeb4a9ea5a74cf769491
4
- data.tar.gz: 9f5d55a8cd4a58e2026ca3e850d48c28d86878e191e91fb92e59c76335a64484
3
+ metadata.gz: 887537f02a38e762c189e2c322074efc59ef28a662021f59fbfad6cc0dafc13c
4
+ data.tar.gz: e519f080b81120044cd25113145fb571e5655b377e992643b5fc711a7ab6abfa
5
5
  SHA512:
6
- metadata.gz: cca614a4e3ffde8ff56ef3e8394f85f4a57d37f8615f2f144a9a39f84d50e51dd015348da7f93340326b40ff4b16202ec21bc03f23edacf678c03f57d0955974
7
- data.tar.gz: 2e9f9c9091ac99fa96c3562fdb1e49642b2961ee682c30d98f8babd95b8366d9c96019df7185c5ef493b330f6c32066be6868398dcdb123c4607e9ab4875462b
6
+ metadata.gz: 50fef96363c87e0f2461165bed0c608b0bf7a24d1d3d9b90fc1b741446a2685d94bd54d392bae1714984b4c95fbf925c1a839c2b70bfb62262fc3e1112c321a8
7
+ data.tar.gz: ca51f38183ebc65b01b18ad4ce58885eba4481ab8804de3e70ad140e1470bfeb2c99177db6e0817fb8aa5532c532d6a848e0b20814da93f29b84f7f4fd9bdd6d
@@ -8,7 +8,7 @@ jobs:
8
8
  fail-fast: false
9
9
  matrix:
10
10
  os: [ubuntu-latest, macos-10.15]
11
- ruby: [2.6, 2.7, '3.0']
11
+ ruby: [2.7, 3.0, 3.1, truffleruby]
12
12
 
13
13
  name: >-
14
14
  ${{matrix.os}}, ${{matrix.ruby}}
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.11)
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.5.1)
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.1.4
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 (less than 460 lines of C-code) SQLite3
27
- wrapper for Ruby. It provides a single class with a minimal set of methods for
28
- interacting with an SQLite3 database.
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](#what-about-concurrency) for multithreaded apps: the
38
- Ruby GVL is released while preparing SQL statements and while iterating over
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) (an ActiveRecord)
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:blog.db')
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](#what-about-concurrency). On the other hand, Extralite does
133
- not have support for defining custom functions, aggregates and collations. If
134
- you're using those features, you'll need to stick with sqlite3-ruby.
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
- |execute multiple statements|separate API (#execute_batch)|integrated|
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](#what-about-concurrency)|
147
- |Code size|~2650LoC|~530LoC|
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
- ## What about concurrency?
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.5 times faster than `sqlite3` when fetching a
163
- large number of rows. Here are the [results for fetching rows as hashes](https://github.com/digital-fabric/extralite/blob/main/test/perf_hash.rb):
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|57620 rows/s|95340 rows/s|__1.65x__|
168
- |1K|286.8K rows/s|2106.4 rows/s|__7.35x__|
169
- |100K|181K rows/s|2275.3K rows/s|__12.53x__|
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
- When [fetching rows as arrays](https://github.com/digital-fabric/extralite/blob/main/test/perf_ary.rb) Extralite also significantly outperforms sqlite3-ruby:
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|64365 rows/s|94031 rows/s|__1.46x__|
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
- (If you're interested in checking this yourself, just run the script and let me
180
- know if your results are better/worse.)
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 more than 3M
183
- rows/second (when fetching rows as arrays), and more than 2.2M rows/second (when
184
- fetching rows as hashes.)
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 '**/*.o', '**/*.so', '**/*.so.*', '**/*.a', '**/*.bundle', '**/*.jar', 'pkg', 'tmp'
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
+ }