extralite 1.12 → 1.14

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: ed6cb05e12c36bbbc92701af98639a9a92b91d9ba0fca0362b35fb12f3af676e
4
- data.tar.gz: 6d7976fc5b39aa414a5a3431a9c5a1a6c517166d4e82de621b22da3be50fe38b
3
+ metadata.gz: 32105cda1ba5871c89609e42828a095d76d4cd2da29bd6d47beefc6a3ae93bd6
4
+ data.tar.gz: 4ff38c0e6144b22792e2a57792f295fc85a6b3d4b9eb09820e4f3b3e4c78bec1
5
5
  SHA512:
6
- metadata.gz: 9baa147f13fa4498264e6e942e9c154bcdcd458f503944be9385f5ea61b82a8e6d381f77a75ad7a4c7643aa124cc2a4d5aa8ffb58400efa641dcb62fad810437
7
- data.tar.gz: 789d203582c86f54a8f234b67d7b54b03150b900d25a5c76e81a57d779d1735fa7a8dc9822d121df7d08af4513481bf4c0a1a6d2fb1d6dfc54d80022b2b6e559
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
@@ -1,3 +1,3 @@
1
1
  source 'https://rubygems.org'
2
2
 
3
- gemspec
3
+ gemspec name: 'extralite'
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- extralite (1.12)
4
+ extralite (1.14)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
data/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2021 Sharon Rosner
3
+ Copyright (c) 2022 Sharon Rosner
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
data/README.md CHANGED
@@ -23,22 +23,26 @@
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.
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](#what-about-concurrency) for multithreaded apps: the
40
- Ruby GVL is released while preparing SQL statements and while iterating over
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.37.2"
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](#what-about-concurrency). On the other hand, Extralite does
138
- not have support for defining custom functions, aggregates and collations. If
139
- you're using those features, you'll need to stick with sqlite3-ruby.
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
- |execute multiple statements|separate API (#execute_batch)|integrated|
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](#what-about-concurrency)|
153
- |Code size|~2650LoC|~530LoC|
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
- ## What about concurrency?
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.5 times faster than `sqlite3` when fetching a
169
- 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):
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|57620 rows/s|95340 rows/s|__1.65x__|
174
- |1K|286.8K rows/s|2106.4 rows/s|__7.35x__|
175
- |100K|181K rows/s|2275.3K rows/s|__12.53x__|
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
- When [fetching rows as arrays](https://github.com/digital-fabric/extralite/blob/main/test/perf_ary.rb) Extralite also significantly outperforms sqlite3-ruby:
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|64365 rows/s|94031 rows/s|__1.46x__|
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
- (If you're interested in checking this yourself, just run the script and let me
186
- know if your results are better/worse.)
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
- As those benchmarks show, Extralite is capabale of reading more than 3M
189
- rows/second (when fetching rows as arrays), and more than 2.2M rows/second (when
190
- fetching rows as hashes.)
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 '**/*.o', '**/*.so', '**/*.so.*', '**/*.a', '**/*.bundle', '**/*.jar', 'pkg', 'tmp'
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
+ }