extralite 1.10 → 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: e1dbf79f19c8e1a1b31da57db9e1b20eb32efed2a510dfd1a19df048ecdf6532
4
- data.tar.gz: 633afbfd7042dedb38a5275d525bf9876a8cd194e6b6f95db183d78019c53816
3
+ metadata.gz: 5d4d7fe119c7197de4a8cf2a7c54a98845a6bf3cbb5e5140c6b272e9b5d1e458
4
+ data.tar.gz: bfd3010a039dd6445cb0a0fe0c5dca12a1a7aa19d7c44ec3f89c5b8768f464f7
5
5
  SHA512:
6
- metadata.gz: 9987a2e1b14e9213a289d04d79a0914e6d3a843c5af94e4d57d6839f193b8294b57b280141dd4784419d9f920d8b1ee32f9a21efee56a85ff524128bc4262aed
7
- data.tar.gz: 2b53f9dd796f37e7c901f88b5bfe2b28463c0465ff5aaf0ade160b1ecf95f0beb7b0cd56496e3c76bd4f754ef7649bf8a704ff7eb94053a7b7f1b1099cf31a5b
6
+ metadata.gz: bc80083972aafcca792665edaeaed05dedcc8d22f3f1c277d7211d99a807344ba6ec203098aa6b4038f77318fc40aa44511d46c6d586e7ed8c75862596c7a092
7
+ data.tar.gz: 0a4f2d8779ba19732e1ec2f5a2367b39d0889de83fc6cf03a90cd49fae2f378ffcf7238842ca17aff4e8826c66d98158a497e200361cff6caac0ff70cde59ad9
@@ -7,8 +7,8 @@ jobs:
7
7
  strategy:
8
8
  fail-fast: false
9
9
  matrix:
10
- os: [ubuntu-latest]
11
- ruby: [2.6, 2.7, '3.0']
10
+ os: [ubuntu-latest, macos-10.15]
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,12 @@
1
+ ## 1.12 2022-02-15
2
+
3
+ - Add `Extralite.sqlite3_version` method
4
+ - Bundle sqlite3 in gem
5
+
6
+ ## 1.11 2021-12-17
7
+
8
+ - Fix compilation on MacOS (#3)
9
+
1
10
  ## 1.10 2021-12-15
2
11
 
3
12
  - Fix mutliple parameter binding with hash
data/Gemfile.lock CHANGED
@@ -1,13 +1,13 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- extralite (1.10)
4
+ extralite (1.13)
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
@@ -1,25 +1,46 @@
1
- # Extralite - a fast Ruby gem for working with SQLite3 databases
2
-
3
- [![Gem Version](https://badge.fury.io/rb/extralite.svg)](http://rubygems.org/gems/extralite)
4
- [![Modulation Test](https://github.com/digital-fabric/extralite/workflows/Tests/badge.svg)](https://github.com/digital-fabric/extralite/actions?query=workflow%3ATests)
5
- [![MIT licensed](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/digital-fabric/extralite/blob/master/LICENSE)
1
+ <h1 align="center">
2
+ Extralite
3
+ </h1>
4
+
5
+ <h4 align="center">A fast Ruby gem for working with SQLite3 databases</h4>
6
+
7
+ <p align="center">
8
+ <a href="http://rubygems.org/gems/extralite">
9
+ <img src="https://badge.fury.io/rb/extralite.svg" alt="Ruby gem">
10
+ </a>
11
+ <a href="https://github.com/digital-fabric/extralite/actions?query=workflow%3ATests">
12
+ <img src="https://github.com/digital-fabric/extralite/workflows/Tests/badge.svg" alt="Tests">
13
+ </a>
14
+ <a href="https://github.com/digital-fabric/extralite/blob/master/LICENSE">
15
+ <img src="https://img.shields.io/badge/license-MIT-blue.svg" alt="MIT License">
16
+ </a>
17
+ </p>
18
+
19
+ <p align="center">
20
+ <a href="https://www.rubydoc.info/gems/extralite">DOCS</a> |
21
+ <a href="https://noteflakes.com/articles/2021-12-15-extralite">BLOG POST</a>
22
+ </p>
6
23
 
7
24
  ## What is Extralite?
8
25
 
9
- Extralite is a fast, extra-lightweight (less than 460 lines of C-code) SQLite3
10
- wrapper for Ruby. It provides a single class with a minimal set of methods for
11
- 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.
12
30
 
13
31
  ## Features
14
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.
15
36
  - A variety of methods for different data access patterns: rows as hashes, rows
16
37
  as arrays, single row, single column, single value.
38
+ - Prepared statements.
17
39
  - Super fast - [up to 12.5x faster](#performance) than the
18
40
  [sqlite3](https://github.com/sparklemotion/sqlite3-ruby) gem (see also
19
41
  [comparison](#why-not-just-use-the-sqlite3-gem).)
20
- - Improved [concurrency](#what-about-concurrency) for multithreaded apps: the
21
- Ruby GVL is released while preparing SQL statements and while iterating over
22
- results.
42
+ - Improved [concurrency](#concurrency) for multithreaded apps: the Ruby GVL is
43
+ released while preparing SQL statements and while iterating over results.
23
44
  - Iterate over records with a block, or collect records into an array.
24
45
  - Parameter binding.
25
46
  - Automatically execute SQL strings containing multiple semicolon-separated
@@ -28,13 +49,30 @@ interacting with an SQLite3 database.
28
49
  - Get number of rows changed by last query.
29
50
  - Load extensions (loading of extensions is autmatically enabled. You can find
30
51
  some useful extensions here: https://github.com/nalgeon/sqlean.)
31
- - 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.
32
67
 
33
68
  ## Usage
34
69
 
35
70
  ```ruby
36
71
  require 'extralite'
37
72
 
73
+ # get sqlite3 version
74
+ Extralite.sqlite3_version #=> "3.38.0"
75
+
38
76
  # open a database
39
77
  db = Extralite::Database.new('/tmp/my.db')
40
78
 
@@ -72,6 +110,12 @@ db.query('select * from foo where bar = :bar', bar: 42)
72
110
  db.query('select * from foo where bar = :bar', 'bar' => 42)
73
111
  db.query('select * from foo where bar = :bar', ':bar' => 42)
74
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
+
75
119
  # get last insert rowid
76
120
  rowid = db.last_insert_rowid
77
121
 
@@ -96,7 +140,7 @@ Extralite includes an adapter for
96
140
  just use the `extralite` scheme instead of `sqlite`:
97
141
 
98
142
  ```ruby
99
- DB = Sequel.connect('extralite:blog.db')
143
+ DB = Sequel.connect('extralite://blog.db')
100
144
  articles = DB[:articles]
101
145
  p articles.to_a
102
146
  ```
@@ -112,25 +156,27 @@ simpler API that gives me query results in a variety of ways. Thus extralite was
112
156
  born.
113
157
 
114
158
  Extralite is quite a bit [faster](#performance) than sqlite3-ruby and is also
115
- [thread-friendly](#what-about-concurrency). On the other hand, Extralite does
116
- not have support for defining custom functions, aggregates and collations. If
117
- 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.
118
162
 
119
163
  Here's a table summarizing the differences between the two gems:
120
164
 
121
165
  | |sqlite3-ruby|Extralite|
122
166
  |-|-|-|
167
+ |SQLite3 dependency|depends on OS-installed libsqlite3|bundles latest version of SQLite3|
123
168
  |API design|multiple classes|single class|
124
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|
125
- |execute multiple statements|separate API (#execute_batch)|integrated|
170
+ |Execute multiple statements|separate API (#execute_batch)|integrated|
171
+ |Prepared statements|yes|yes|
126
172
  |custom functions in Ruby|yes|no|
127
173
  |custom collations|yes|no|
128
174
  |custom aggregate functions|yes|no|
129
- |Multithread friendly|no|[yes](#what-about-concurrency)|
130
- |Code size|~2650LoC|~530LoC|
175
+ |Multithread friendly|no|[yes](#concurrency)|
176
+ |Code size|~2650LoC|~600LoC|
131
177
  |Performance|1x|1.5x to 12.5x (see [below](#performance))|
132
178
 
133
- ## What about concurrency?
179
+ ## Concurrency
134
180
 
135
181
  Extralite releases the GVL while making blocking calls to the sqlite3 library,
136
182
  that is while preparing SQL statements and fetching rows. Releasing the GVL
@@ -142,29 +188,42 @@ performance:
142
188
 
143
189
  A benchmark script is included, creating a table of various row counts, then
144
190
  fetching the entire table using either `sqlite3` or `extralite`. This benchmark
145
- shows Extralite to be up to 12.5 times faster than `sqlite3` when fetching a
146
- 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)
147
197
 
148
198
  |Row count|sqlite3-ruby|Extralite|Advantage|
149
199
  |-:|-:|-:|-:|
150
- |10|57620 rows/s|95340 rows/s|__1.65x__|
151
- |1K|286.8K rows/s|2106.4 rows/s|__7.35x__|
152
- |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
153
205
 
154
- 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)
155
207
 
156
208
  |Row count|sqlite3-ruby|Extralite|Advantage|
157
209
  |-:|-:|-:|-:|
158
- |10|64365 rows/s|94031 rows/s|__1.46x__|
210
+ |10|64.3K rows/s|94.0K rows/s|__1.46x__|
159
211
  |1K|498.9K rows/s|2478.2K rows/s|__4.97x__|
160
212
  |100K|441.1K rows/s|3023.4K rows/s|__6.85x__|
161
213
 
162
- (If you're interested in checking this yourself, just run the script and let me
163
- 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__|
164
223
 
165
- As those benchmarks show, Extralite is capabale of reading more than 3M
166
- rows/second (when fetching rows as arrays), and more than 2.2M rows/second (when
167
- 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.
168
227
 
169
228
  ## Contributing
170
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
+ }