extralite 1.12 → 1.13

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: 5d4d7fe119c7197de4a8cf2a7c54a98845a6bf3cbb5e5140c6b272e9b5d1e458
4
+ data.tar.gz: bfd3010a039dd6445cb0a0fe0c5dca12a1a7aa19d7c44ec3f89c5b8768f464f7
5
5
  SHA512:
6
- metadata.gz: 9baa147f13fa4498264e6e942e9c154bcdcd458f503944be9385f5ea61b82a8e6d381f77a75ad7a4c7643aa124cc2a4d5aa8ffb58400efa641dcb62fad810437
7
- data.tar.gz: 789d203582c86f54a8f234b67d7b54b03150b900d25a5c76e81a57d779d1735fa7a8dc9822d121df7d08af4513481bf4c0a1a6d2fb1d6dfc54d80022b2b6e559
6
+ metadata.gz: bc80083972aafcca792665edaeaed05dedcc8d22f3f1c277d7211d99a807344ba6ec203098aa6b4038f77318fc40aa44511d46c6d586e7ed8c75862596c7a092
7
+ data.tar.gz: 0a4f2d8779ba19732e1ec2f5a2367b39d0889de83fc6cf03a90cd49fae2f378ffcf7238842ca17aff4e8826c66d98158a497e200361cff6caac0ff70cde59ad9
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- extralite (1.12)
4
+ extralite (1.13)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
data/README.md CHANGED
@@ -23,22 +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
 
32
- - Zero dependencies: Extralite bundles SQLite3 version 3.37.2 - no need to
33
- install any `libsqlite3` packages.
33
+ - Zero dependencies: Extralite bundles SQLite3 version
34
+ [3.38.0](https://sqlite.org/releaselog/3_38_0.html) - no need to install any
35
+ `libsqlite3` packages.
34
36
  - A variety of methods for different data access patterns: rows as hashes, rows
35
37
  as arrays, single row, single column, single value.
38
+ - Prepared statements.
36
39
  - Super fast - [up to 12.5x faster](#performance) than the
37
40
  [sqlite3](https://github.com/sparklemotion/sqlite3-ruby) gem (see also
38
41
  [comparison](#why-not-just-use-the-sqlite3-gem).)
39
- - Improved [concurrency](#what-about-concurrency) for multithreaded apps: the
40
- Ruby GVL is released while preparing SQL statements and while iterating over
41
- results.
42
+ - Improved [concurrency](#concurrency) for multithreaded apps: the Ruby GVL is
43
+ released while preparing SQL statements and while iterating over results.
42
44
  - Iterate over records with a block, or collect records into an array.
43
45
  - Parameter binding.
44
46
  - Automatically execute SQL strings containing multiple semicolon-separated
@@ -49,13 +51,27 @@ interacting with an SQLite3 database.
49
51
  some useful extensions here: https://github.com/nalgeon/sqlean.)
50
52
  - Includes a [Sequel adapter](#usage-with-sequel).
51
53
 
54
+ ## Installation
55
+
56
+ To use Extralite in your Ruby app, add the following to your `Gemfile`:
57
+
58
+ ```ruby
59
+ gem 'extralite'
60
+ ```
61
+
62
+ You can also run `gem install extralite` if you just want to check it out.
63
+
64
+ > **Important note**: Extralite will take a while to install (on my modest
65
+ > machine it takes about a minute). This is owing to the fact that Extralite
66
+ > bundles the sqlite3 code, which is compiled upon installation.
67
+
52
68
  ## Usage
53
69
 
54
70
  ```ruby
55
71
  require 'extralite'
56
72
 
57
73
  # get sqlite3 version
58
- Extralite.sqlite3_version #=> "3.37.2"
74
+ Extralite.sqlite3_version #=> "3.38.0"
59
75
 
60
76
  # open a database
61
77
  db = Extralite::Database.new('/tmp/my.db')
@@ -94,6 +110,12 @@ db.query('select * from foo where bar = :bar', bar: 42)
94
110
  db.query('select * from foo where bar = :bar', 'bar' => 42)
95
111
  db.query('select * from foo where bar = :bar', ':bar' => 42)
96
112
 
113
+ # prepared statements
114
+ stmt = db.prepare('select ? as foo, ? as bar') #=> Extralite::PreparedStatement
115
+ stmt.query(1, 2) #=> [{ :foo => 1, :bar => 2 }]
116
+ # PreparedStatement offers the same data access methods as the Database class,
117
+ # but without the sql parameter.
118
+
97
119
  # get last insert rowid
98
120
  rowid = db.last_insert_rowid
99
121
 
@@ -134,9 +156,9 @@ simpler API that gives me query results in a variety of ways. Thus extralite was
134
156
  born.
135
157
 
136
158
  Extralite is quite a bit [faster](#performance) than sqlite3-ruby and is also
137
- [thread-friendly](#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.
159
+ [thread-friendly](#concurrency). On the other hand, Extralite does not have
160
+ support for defining custom functions, aggregates and collations. If you're
161
+ using any of those features, you'll have to stick to sqlite3-ruby.
140
162
 
141
163
  Here's a table summarizing the differences between the two gems:
142
164
 
@@ -145,15 +167,16 @@ Here's a table summarizing the differences between the two gems:
145
167
  |SQLite3 dependency|depends on OS-installed libsqlite3|bundles latest version of SQLite3|
146
168
  |API design|multiple classes|single class|
147
169
  |Query results|row as hash, row as array, single row, single value|row as hash, row as array, __single column__, single row, single value|
148
- |execute multiple statements|separate API (#execute_batch)|integrated|
170
+ |Execute multiple statements|separate API (#execute_batch)|integrated|
171
+ |Prepared statements|yes|yes|
149
172
  |custom functions in Ruby|yes|no|
150
173
  |custom collations|yes|no|
151
174
  |custom aggregate functions|yes|no|
152
- |Multithread friendly|no|[yes](#what-about-concurrency)|
153
- |Code size|~2650LoC|~530LoC|
175
+ |Multithread friendly|no|[yes](#concurrency)|
176
+ |Code size|~2650LoC|~600LoC|
154
177
  |Performance|1x|1.5x to 12.5x (see [below](#performance))|
155
178
 
156
- ## What about concurrency?
179
+ ## Concurrency
157
180
 
158
181
  Extralite releases the GVL while making blocking calls to the sqlite3 library,
159
182
  that is while preparing SQL statements and fetching rows. Releasing the GVL
@@ -165,29 +188,42 @@ performance:
165
188
 
166
189
  A benchmark script is included, creating a table of various row counts, then
167
190
  fetching the entire table using either `sqlite3` or `extralite`. This benchmark
168
- shows Extralite to be up to 12.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):
191
+ shows Extralite to be up to ~12 times faster than `sqlite3` when fetching a
192
+ large number of rows.
193
+
194
+ ### Rows as hashes
195
+
196
+ [Benchmark source code](https://github.com/digital-fabric/extralite/blob/main/test/perf_hash.rb)
170
197
 
171
198
  |Row count|sqlite3-ruby|Extralite|Advantage|
172
199
  |-:|-:|-:|-:|
173
- |10|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__|
200
+ |10|75.3K rows/s|134.2K rows/s|__1.78x__|
201
+ |1K|286.8K rows/s|2106.4K rows/s|__7.35x__|
202
+ |100K|181.0K rows/s|2275.3K rows/s|__12.53x__|
176
203
 
177
- When [fetching rows as arrays](https://github.com/digital-fabric/extralite/blob/main/test/perf_ary.rb) Extralite also significantly outperforms sqlite3-ruby:
204
+ ### Rows as arrays
205
+
206
+ [Benchmark source code](https://github.com/digital-fabric/extralite/blob/main/test/perf_ary.rb)
178
207
 
179
208
  |Row count|sqlite3-ruby|Extralite|Advantage|
180
209
  |-:|-:|-:|-:|
181
- |10|64365 rows/s|94031 rows/s|__1.46x__|
210
+ |10|64.3K rows/s|94.0K rows/s|__1.46x__|
182
211
  |1K|498.9K rows/s|2478.2K rows/s|__4.97x__|
183
212
  |100K|441.1K rows/s|3023.4K rows/s|__6.85x__|
184
213
 
185
- (If you're interested in checking this yourself, just run the script and let me
186
- 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__|
187
223
 
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.)
224
+ As those benchmarks show, Extralite is capabale of reading up to 3M rows/second
225
+ when fetching rows as arrays, and up to 2.2M rows/second when fetching
226
+ rows as hashes.
191
227
 
192
228
  ## Contributing
193
229
 
data/Rakefile CHANGED
@@ -16,7 +16,7 @@ task :test do
16
16
  exec 'ruby test/run.rb'
17
17
  end
18
18
 
19
- CLEAN.include '**/*.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
+ 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
+ }