extralite 1.21 → 1.23
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/workflows/test.yml +1 -1
- data/CHANGELOG.md +12 -0
- data/Gemfile.lock +1 -1
- data/README.md +151 -90
- data/ext/extralite/common.c +4 -4
- data/ext/extralite/database.c +98 -3
- data/ext/extralite/extralite.h +4 -0
- data/ext/extralite/prepared_statement.c +3 -0
- data/lib/extralite/sqlite3_constants.rb +50 -0
- data/lib/extralite/version.rb +1 -1
- data/lib/extralite.rb +15 -52
- data/test/test_database.rb +86 -0
- data/test/test_prepared_statement.rb +5 -0
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7466fb655d5f2cc65862d604190d52208773a9cc8fb611b184bc46a20ccb8d1f
|
4
|
+
data.tar.gz: bbc9b59c5c2297df34d716a19b53ccc95008bea3be4d38d3cdd91340089962b5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 227a7fb703cca6eed8b25f0adb32cf8421f3c503149a715006c381d5cca475e692048650acc561ccfcb5ff1203c489fa6fe429922aecb2c0486cd0ec83c85360
|
7
|
+
data.tar.gz: 131aa35e939c4623a306be6e7a756d40cfe77a5b199ffc90cb1937194e7066ba6fa4de3d4cc8618b8123b68dcfe9e92a60b3e821c49b51145497deb005467f24
|
data/.github/workflows/test.yml
CHANGED
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,15 @@
|
|
1
|
+
# 1.23 2023-01-26
|
2
|
+
|
3
|
+
- Add `Database#trace` (#21)
|
4
|
+
- Add `Database#total_changes` (#20)
|
5
|
+
- Add `Database#busy_timeout=` (#19)
|
6
|
+
- Add `Database#limit` (#16)
|
7
|
+
- Improve error handling
|
8
|
+
|
9
|
+
# 1.22 2023-01-23
|
10
|
+
|
11
|
+
- Improve documentation (#17)
|
12
|
+
|
1
13
|
# 1.21 2023-01-23
|
2
14
|
|
3
15
|
- Update bundled sqlite to version 3.40.1 (#18)
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -1,64 +1,42 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
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>
|
1
|
+
# Extralite - a Super Fast Ruby Gem for Working with SQLite3 Databases
|
2
|
+
|
3
|
+
* Source code: https://github.com/digital-fabric/extralite
|
4
|
+
* Documentation: http://www.rubydoc.info/gems/extralite
|
5
|
+
|
6
|
+
[](https://rubygems.org/gems/extralite) [](https://github.com/digital-fabric/extralite/actions?query=workflow%3ATests) [](https://github.com/digital-fabric/extralite/blob/master/LICENSE)
|
23
7
|
|
24
8
|
## What is Extralite?
|
25
9
|
|
26
|
-
Extralite is a fast, extra-lightweight (about
|
27
|
-
wrapper for Ruby. It provides a minimal set of methods for interacting
|
28
|
-
SQLite3 database, as well as prepared statements.
|
10
|
+
Extralite is a super fast, extra-lightweight (about 1300 lines of C-code)
|
11
|
+
SQLite3 wrapper for Ruby. It provides a minimal set of methods for interacting
|
12
|
+
with an SQLite3 database, as well as prepared statements.
|
29
13
|
|
30
14
|
Extralite comes in two flavors: the `extralite` gem which uses the
|
31
15
|
system-installed sqlite3 library, and the `extralite-bundle` gem which bundles
|
32
16
|
the latest version of SQLite
|
33
|
-
([3.
|
17
|
+
([3.40.1](https://sqlite.org/releaselog/3_40_1.html)), offering access to the
|
34
18
|
latest features and enhancements.
|
35
19
|
|
36
20
|
## Features
|
37
21
|
|
22
|
+
- Super fast - [up to 11x faster](#performance) than the
|
23
|
+
[sqlite3](https://github.com/sparklemotion/sqlite3-ruby) gem (see also
|
24
|
+
[comparison](#why-not-just-use-the-sqlite3-gem).)
|
38
25
|
- A variety of methods for different data access patterns: rows as hashes, rows
|
39
26
|
as arrays, single row, single column, single value.
|
40
27
|
- Prepared statements.
|
28
|
+
- Parameter binding.
|
41
29
|
- Use system-installed sqlite3, or the [bundled latest version of
|
42
30
|
SQLite3](#installing-the-extralite-sqlite3-bundle).
|
43
|
-
- Super fast - [up to 12.5x faster](#performance) than the
|
44
|
-
[sqlite3](https://github.com/sparklemotion/sqlite3-ruby) gem (see also
|
45
|
-
[comparison](#why-not-just-use-the-sqlite3-gem).)
|
46
31
|
- Improved [concurrency](#concurrency) for multithreaded apps: the Ruby GVL is
|
47
32
|
released while preparing SQL statements and while iterating over results.
|
48
33
|
- Iterate over records with a block, or collect records into an array.
|
49
|
-
- Parameter binding.
|
50
34
|
- Automatically execute SQL strings containing multiple semicolon-separated
|
51
35
|
queries (handy for creating/modifying schemas).
|
52
|
-
- Get last insert rowid.
|
53
|
-
- Get number of rows changed by last query.
|
54
36
|
- Execute the same query with multiple parameter lists (useful for inserting records).
|
55
37
|
- Load extensions (loading of extensions is autmatically enabled. You can find
|
56
38
|
some useful extensions here: https://github.com/nalgeon/sqlean.)
|
57
39
|
- Includes a [Sequel adapter](#usage-with-sequel).
|
58
|
-
- Other features:
|
59
|
-
- Backup databases
|
60
|
-
- Interrupt long-running queries (from another thread)
|
61
|
-
- Get runtime status, database status and prepared statement status values.
|
62
40
|
|
63
41
|
## Installation
|
64
42
|
|
@@ -70,20 +48,21 @@ gem 'extralite'
|
|
70
48
|
|
71
49
|
You can also run `gem install extralite` if you just want to check it out.
|
72
50
|
|
73
|
-
### Installing the Extralite-SQLite3
|
51
|
+
### Installing the Extralite-SQLite3 Bundle
|
74
52
|
|
75
53
|
If you don't have sqlite3 installed on your system, do not want to use the
|
76
54
|
system-installed version of SQLite3, or would like to use the latest version of
|
77
55
|
SQLite3, you can install the `extralite-bundle` gem, which integrates the
|
78
56
|
SQLite3 source code.
|
79
57
|
|
80
|
-
> **Important note**: The `extralite-bundle` will take a while to install
|
81
|
-
> modest machine it takes about a minute), due to the size of the sqlite3
|
58
|
+
> **Important note**: The `extralite-bundle` gem will take a while to install
|
59
|
+
> (on my modest machine it takes about a minute), due to the size of the sqlite3
|
60
|
+
> code.
|
82
61
|
|
83
62
|
Usage of the `extralite-bundle` gem is identical to the usage of the normal
|
84
|
-
`extralite` gem.
|
63
|
+
`extralite` gem, using `require 'extralite'` to load the gem.
|
85
64
|
|
86
|
-
##
|
65
|
+
## Synopsis
|
87
66
|
|
88
67
|
```ruby
|
89
68
|
require 'extralite'
|
@@ -144,6 +123,9 @@ rowid = db.last_insert_rowid
|
|
144
123
|
# get number of rows changed in last query
|
145
124
|
number_of_rows_affected = db.changes
|
146
125
|
|
126
|
+
# get column names for the given sql
|
127
|
+
db.columns('select a, b, c from foo') => [:a, :b, :c]
|
128
|
+
|
147
129
|
# get db filename
|
148
130
|
db.filename #=> "/tmp/my.db"
|
149
131
|
|
@@ -163,6 +145,114 @@ db.close
|
|
163
145
|
db.closed? #=> true
|
164
146
|
```
|
165
147
|
|
148
|
+
## More Features
|
149
|
+
|
150
|
+
### Interrupting Long-running Queries
|
151
|
+
|
152
|
+
When running long-running queries, you can use `Database#interrupt` to interrupt
|
153
|
+
the query:
|
154
|
+
|
155
|
+
```ruby
|
156
|
+
timeout_thread = Thread.new do
|
157
|
+
sleep 10
|
158
|
+
db.interrupt
|
159
|
+
end
|
160
|
+
|
161
|
+
result = begin
|
162
|
+
db.query(super_slow_sql)
|
163
|
+
rescue Extralite::InterruptError
|
164
|
+
nil
|
165
|
+
ensure
|
166
|
+
timeout_thread.kill
|
167
|
+
timeout_thread.join
|
168
|
+
end
|
169
|
+
```
|
170
|
+
|
171
|
+
### Creating Backups
|
172
|
+
|
173
|
+
You can use `Database#backup` to create backup copies of a database. The
|
174
|
+
`#backup` method takes either a filename or a database instance:
|
175
|
+
|
176
|
+
```ruby
|
177
|
+
# with a filename
|
178
|
+
db.backup('backup.db')
|
179
|
+
|
180
|
+
# with an instance
|
181
|
+
target = Extralite::Database.new('backup.db')
|
182
|
+
db.backup(target)
|
183
|
+
```
|
184
|
+
|
185
|
+
For big databases, you can also track the backup progress by providing a block
|
186
|
+
that takes two arguments - the number of remaining pages, and the total number pages:
|
187
|
+
|
188
|
+
```ruby
|
189
|
+
db.backup('backup.db') do |remaining, total|
|
190
|
+
puts "backup progress: #{(remaining.to_f/total * 100).round}%"
|
191
|
+
end
|
192
|
+
```
|
193
|
+
|
194
|
+
### Retrieving Status Information
|
195
|
+
|
196
|
+
Extralite provides methods for retrieving status information about the sqlite
|
197
|
+
runtime, database-specific status and prepared statement-specific status,
|
198
|
+
`Extralite.runtime_status`, `Database#status` and `PreparedStatement#status`
|
199
|
+
respectively. You can also reset the high water mark for the specific status
|
200
|
+
code by providing true as the reset argument. The status codes mirror those
|
201
|
+
defined by the SQLite API. Some examples:
|
202
|
+
|
203
|
+
```ruby
|
204
|
+
# The Extralite.runtime_status returns a tuple consisting of the current value
|
205
|
+
# and the high water mark value.
|
206
|
+
current, high_watermark = Extralite.runtime_status(Extralite::SQLITE_STATUS_MEMORY_USED)
|
207
|
+
|
208
|
+
# To reset the high water mark, pass true as a second argument:
|
209
|
+
current, high_watermark = Extralite.runtime_status(Extralite::SQLITE_STATUS_MEMORY_USED, true)
|
210
|
+
|
211
|
+
# Similarly, you can interrogate a database's status (pass true as a second
|
212
|
+
# argument in order to reset the high watermark):
|
213
|
+
current, high_watermark = db.status(Extralite::SQLITE_DBSTATUS_CACHE_USED)
|
214
|
+
|
215
|
+
# The PreparedStatement#status method returns a single value (pass true as a
|
216
|
+
# second argument in order to reset the high watermark):
|
217
|
+
value = stmt.status(Extralite::SQLITE_STMTSTATUS_RUN)
|
218
|
+
```
|
219
|
+
|
220
|
+
### Working with Database Limits
|
221
|
+
|
222
|
+
The `Database#limit` can be used to get and set various database limits, as
|
223
|
+
[discussed in the SQLite docs](https://www.sqlite.org/limits.html):
|
224
|
+
|
225
|
+
```ruby
|
226
|
+
# get limit
|
227
|
+
value = db.limit(Extralite::SQLITE_LIMIT_ATTACHED)
|
228
|
+
|
229
|
+
# set limit
|
230
|
+
db.limit(Extralite::SQLITE_LIMIT_ATTACHED, new_value)
|
231
|
+
```
|
232
|
+
|
233
|
+
### Setting the Busy Timeout
|
234
|
+
|
235
|
+
When accessing a database concurrently it can be handy to set a busy timeout, in
|
236
|
+
order to not have to deal with rescuing `Extralite::BusyError` exceptions. The
|
237
|
+
timeout is given in seconds:
|
238
|
+
|
239
|
+
```ruby
|
240
|
+
db.busy_timeout = 5
|
241
|
+
```
|
242
|
+
|
243
|
+
### Tracing SQL Statements
|
244
|
+
|
245
|
+
To trace all SQL statements executed on the database, pass a block to
|
246
|
+
`Database#trace`. To disable tracing, call `Database#trace` without a block:
|
247
|
+
|
248
|
+
```ruby
|
249
|
+
# enable tracing
|
250
|
+
db.trace { |sql| puts sql: sql }
|
251
|
+
|
252
|
+
# disable tracing
|
253
|
+
db.trace
|
254
|
+
```
|
255
|
+
|
166
256
|
## Usage with Sequel
|
167
257
|
|
168
258
|
Extralite includes an adapter for
|
@@ -177,82 +267,53 @@ p articles.to_a
|
|
177
267
|
|
178
268
|
(Make sure you include `extralite` as a dependency in your `Gemfile`.)
|
179
269
|
|
180
|
-
## Why not just use the sqlite3 gem?
|
181
|
-
|
182
|
-
The [sqlite3-ruby](https://github.com/sparklemotion/sqlite3-ruby) gem is a
|
183
|
-
popular, solid, well-maintained project, used by thousands of developers. I've
|
184
|
-
been doing a lot of work with SQLite3 databases lately, and wanted to have a
|
185
|
-
simpler API that gives me query results in a variety of ways. Thus extralite was
|
186
|
-
born.
|
187
|
-
|
188
|
-
Extralite is quite a bit [faster](#performance) than sqlite3-ruby and is also
|
189
|
-
[thread-friendly](#concurrency). On the other hand, Extralite does not have
|
190
|
-
support for defining custom functions, aggregates and collations. If you're
|
191
|
-
using any of those features, you'll have to stick to sqlite3-ruby.
|
192
|
-
|
193
|
-
Here's a table summarizing the differences between the two gems:
|
194
|
-
|
195
|
-
| |sqlite3-ruby|Extralite|
|
196
|
-
|-|-|-|
|
197
|
-
|SQLite3 dependency|depends on OS-installed libsqlite3|Use either system sqlite3 or [bundled latest version of SQLite3](#installing-the-extralite-sqlite3-bundle)|
|
198
|
-
|API design|multiple classes|single class|
|
199
|
-
|Query results|row as hash, row as array, single row, single value|row as hash, row as array, __single column__, single row, single value|
|
200
|
-
|Execute multiple statements|separate API (#execute_batch)|integrated|
|
201
|
-
|Prepared statements|yes|yes|
|
202
|
-
|custom functions in Ruby|yes|no|
|
203
|
-
|custom collations|yes|no|
|
204
|
-
|custom aggregate functions|yes|no|
|
205
|
-
|Multithread friendly|no|[yes](#concurrency)|
|
206
|
-
|Code size|~2650LoC|~600LoC|
|
207
|
-
|Performance|1x|1.5x to 12.5x (see [below](#performance))|
|
208
|
-
|
209
270
|
## Concurrency
|
210
271
|
|
211
272
|
Extralite releases the GVL while making blocking calls to the sqlite3 library,
|
212
273
|
that is while preparing SQL statements and fetching rows. Releasing the GVL
|
213
274
|
allows other threads to run while the sqlite3 library is busy compiling SQL into
|
214
|
-
bytecode, or fetching the next row. This does not
|
215
|
-
performance:
|
275
|
+
bytecode, or fetching the next row. This *does not* hurt Extralite's
|
276
|
+
performance, as you can see:
|
216
277
|
|
217
278
|
## Performance
|
218
279
|
|
219
280
|
A benchmark script is included, creating a table of various row counts, then
|
220
281
|
fetching the entire table using either `sqlite3` or `extralite`. This benchmark
|
221
|
-
shows Extralite to be up to ~
|
282
|
+
shows Extralite to be up to ~11 times faster than `sqlite3` when fetching a
|
222
283
|
large number of rows.
|
223
284
|
|
224
|
-
### Rows as
|
285
|
+
### Rows as Hashes
|
225
286
|
|
226
287
|
[Benchmark source code](https://github.com/digital-fabric/extralite/blob/main/test/perf_hash.rb)
|
227
288
|
|
228
|
-
|Row count|sqlite3
|
289
|
+
|Row count|sqlite3 1.6.0|Extralite 1.21|Advantage|
|
229
290
|
|-:|-:|-:|-:|
|
230
|
-
|10|
|
231
|
-
|1K|
|
232
|
-
|100K|
|
291
|
+
|10|63.7K rows/s|94.0K rows/s|__1.48x__|
|
292
|
+
|1K|299.2K rows/s|1.983M rows/s|__6.63x__|
|
293
|
+
|100K|185.4K rows/s|2.033M rows/s|__10.97x__|
|
233
294
|
|
234
|
-
### Rows as
|
295
|
+
### Rows as Arrays
|
235
296
|
|
236
297
|
[Benchmark source code](https://github.com/digital-fabric/extralite/blob/main/test/perf_ary.rb)
|
237
298
|
|
238
|
-
|Row count|sqlite3
|
299
|
+
|Row count|sqlite3 1.6.0|Extralite 1.21|Advantage|
|
239
300
|
|-:|-:|-:|-:|
|
240
|
-
|10|
|
241
|
-
|1K|
|
242
|
-
|100K|
|
301
|
+
|10|71.2K rows/s|92.1K rows/s|__1.29x__|
|
302
|
+
|1K|502.1K rows/s|2.065M rows/s|__4.11x__|
|
303
|
+
|100K|455.7K rows/s|2.511M rows/s|__5.51x__|
|
243
304
|
|
244
|
-
### Prepared
|
305
|
+
### Prepared Statements
|
245
306
|
|
246
307
|
[Benchmark source code](https://github.com/digital-fabric/extralite/blob/main/test/perf_prepared.rb)
|
247
308
|
|
248
|
-
|Row count|sqlite3
|
309
|
+
|Row count|sqlite3 1.6.0|Extralite 1.21|Advantage|
|
249
310
|
|-:|-:|-:|-:|
|
250
|
-
|10|
|
251
|
-
|1K|
|
252
|
-
|100K|
|
311
|
+
|10|232.2K rows/s|741.6K rows/s|__3.19x__|
|
312
|
+
|1K|299.8K rows/s|2386.0M rows/s|__7.96x__|
|
313
|
+
|100K|183.1K rows/s|1.893M rows/s|__10.34x__|
|
253
314
|
|
254
|
-
As those benchmarks show, Extralite is capabale of reading up to
|
255
|
-
when fetching rows as arrays, and up to
|
315
|
+
As those benchmarks show, Extralite is capabale of reading up to 2.5M
|
316
|
+
rows/second when fetching rows as arrays, and up to 2M rows/second when fetching
|
256
317
|
rows as hashes.
|
257
318
|
|
258
319
|
## License
|
data/ext/extralite/common.c
CHANGED
@@ -174,7 +174,7 @@ void prepare_multi_stmt(sqlite3 *db, sqlite3_stmt **stmt, VALUE sql) {
|
|
174
174
|
case SQLITE_ERROR:
|
175
175
|
rb_raise(cSQLError, "%s", sqlite3_errmsg(db));
|
176
176
|
default:
|
177
|
-
rb_raise(cError, "
|
177
|
+
rb_raise(cError, "%s", sqlite3_errmsg(db));
|
178
178
|
}
|
179
179
|
}
|
180
180
|
|
@@ -216,9 +216,9 @@ void prepare_single_stmt(sqlite3 *db, sqlite3_stmt **stmt, VALUE sql) {
|
|
216
216
|
case SQLITE_ERROR:
|
217
217
|
rb_raise(cSQLError, "%s", sqlite3_errmsg(db));
|
218
218
|
case SQLITE_MULTI_STMT:
|
219
|
-
rb_raise(
|
219
|
+
rb_raise(cError, "A prepared statement does not accept SQL strings with multiple queries");
|
220
220
|
default:
|
221
|
-
rb_raise(cError, "
|
221
|
+
rb_raise(cError, "%s", sqlite3_errmsg(db));
|
222
222
|
}
|
223
223
|
}
|
224
224
|
|
@@ -248,7 +248,7 @@ int stmt_iterate(sqlite3_stmt *stmt, sqlite3 *db) {
|
|
248
248
|
case SQLITE_ERROR:
|
249
249
|
rb_raise(cSQLError, "%s", sqlite3_errmsg(db));
|
250
250
|
default:
|
251
|
-
rb_raise(cError, "
|
251
|
+
rb_raise(cError, "%s", sqlite3_errmsg(db));
|
252
252
|
}
|
253
253
|
|
254
254
|
return 0;
|
data/ext/extralite/database.c
CHANGED
@@ -7,6 +7,7 @@ VALUE cSQLError;
|
|
7
7
|
VALUE cBusyError;
|
8
8
|
VALUE cInterruptError;
|
9
9
|
|
10
|
+
ID ID_CALL;
|
10
11
|
ID ID_KEYS;
|
11
12
|
ID ID_NEW;
|
12
13
|
ID ID_STRIP;
|
@@ -45,6 +46,12 @@ static VALUE Database_allocate(VALUE klass) {
|
|
45
46
|
} \
|
46
47
|
}
|
47
48
|
|
49
|
+
Database_t *Database_struct(VALUE self) {
|
50
|
+
Database_t *db;
|
51
|
+
GetDatabase(self, db);
|
52
|
+
return db;
|
53
|
+
}
|
54
|
+
|
48
55
|
sqlite3 *Database_sqlite3_db(VALUE self) {
|
49
56
|
Database_t *db;
|
50
57
|
GetDatabase(self, db);
|
@@ -86,6 +93,8 @@ VALUE Database_initialize(VALUE self, VALUE path) {
|
|
86
93
|
}
|
87
94
|
#endif
|
88
95
|
|
96
|
+
db->trace_block = Qnil;
|
97
|
+
|
89
98
|
return Qnil;
|
90
99
|
}
|
91
100
|
|
@@ -134,6 +143,7 @@ static inline VALUE Database_perform_query(int argc, VALUE *argv, VALUE self, VA
|
|
134
143
|
|
135
144
|
// prepare query ctx
|
136
145
|
GetOpenDatabase(self, db);
|
146
|
+
if (db->trace_block != Qnil) rb_funcall(db->trace_block, ID_CALL, 1, sql);
|
137
147
|
prepare_multi_stmt(db->sqlite3_db, &stmt, sql);
|
138
148
|
bind_all_parameters(stmt, argc - 1, argv + 1);
|
139
149
|
query_ctx ctx = { self, db->sqlite3_db, stmt };
|
@@ -470,6 +480,17 @@ VALUE backup_cleanup(VALUE ptr) {
|
|
470
480
|
return Qnil;
|
471
481
|
}
|
472
482
|
|
483
|
+
/* call-seq:
|
484
|
+
* db.backup(dest) -> db
|
485
|
+
* db.backup(dest) { |remaining, total| } -> db
|
486
|
+
*
|
487
|
+
* Creates a backup of the database to the given destination, which can be
|
488
|
+
* either a filename or a database instance. In order to monitor the backup
|
489
|
+
* progress you can pass a block that will be called periodically by the backup
|
490
|
+
* method with two arguments: the remaining page count, and the total page
|
491
|
+
* count, which can be used to display the progress to the user or to collect
|
492
|
+
* statistics.
|
493
|
+
*/
|
473
494
|
VALUE Database_backup(int argc, VALUE *argv, VALUE self) {
|
474
495
|
VALUE dst;
|
475
496
|
VALUE src_name;
|
@@ -513,8 +534,8 @@ VALUE Database_backup(int argc, VALUE *argv, VALUE self) {
|
|
513
534
|
return self;
|
514
535
|
}
|
515
536
|
|
516
|
-
/*
|
517
|
-
*
|
537
|
+
/* call-seq:
|
538
|
+
* Extralite.runtime_status(op[, reset]) -> [value, highwatermark]
|
518
539
|
*
|
519
540
|
* Returns runtime status values for the given op as an array containing the
|
520
541
|
* current value and the high water mark value. To reset the high water mark,
|
@@ -539,7 +560,7 @@ VALUE Extralite_runtime_status(int argc, VALUE* argv, VALUE self) {
|
|
539
560
|
* current value and the high water mark value. To reset the high water mark,
|
540
561
|
* pass true as reset.
|
541
562
|
*/
|
542
|
-
VALUE Database_status(int argc, VALUE*
|
563
|
+
VALUE Database_status(int argc, VALUE *argv, VALUE self) {
|
543
564
|
VALUE op, reset;
|
544
565
|
int cur, hwm;
|
545
566
|
|
@@ -554,6 +575,75 @@ VALUE Database_status(int argc, VALUE* argv, VALUE self) {
|
|
554
575
|
return rb_ary_new3(2, INT2NUM(cur), INT2NUM(hwm));
|
555
576
|
}
|
556
577
|
|
578
|
+
/* call-seq:
|
579
|
+
* db.limit(category) -> value
|
580
|
+
* db.limit(category, new_value) -> prev_value
|
581
|
+
*
|
582
|
+
* Returns the current limit for the given category. If a new value is given,
|
583
|
+
* sets the limit to the new value and returns the previous value.
|
584
|
+
*/
|
585
|
+
VALUE Database_limit(int argc, VALUE *argv, VALUE self) {
|
586
|
+
VALUE category, new_value;
|
587
|
+
|
588
|
+
rb_scan_args(argc, argv, "11", &category, &new_value);
|
589
|
+
|
590
|
+
Database_t *db;
|
591
|
+
GetOpenDatabase(self, db);
|
592
|
+
|
593
|
+
int value = sqlite3_limit(db->sqlite3_db, NUM2INT(category), RTEST(new_value) ? NUM2INT(new_value) : -1);
|
594
|
+
|
595
|
+
if (value == -1) rb_raise(cError, "Invalid limit category");
|
596
|
+
|
597
|
+
return INT2NUM(value);
|
598
|
+
}
|
599
|
+
|
600
|
+
/* call-seq:
|
601
|
+
* db.busy_timeout=(sec) -> db
|
602
|
+
* db.busy_timeout=nil -> db
|
603
|
+
*
|
604
|
+
* Sets the busy timeout for the database, in seconds or fractions thereof. To
|
605
|
+
* disable the busy timeout, set it to 0 or nil.
|
606
|
+
*/
|
607
|
+
VALUE Database_busy_timeout_set(VALUE self, VALUE sec) {
|
608
|
+
Database_t *db;
|
609
|
+
GetOpenDatabase(self, db);
|
610
|
+
|
611
|
+
int ms = (sec == Qnil) ? 0 : (int)(NUM2DBL(sec) * 1000);
|
612
|
+
|
613
|
+
int rc = sqlite3_busy_timeout(db->sqlite3_db, ms);
|
614
|
+
if (rc != SQLITE_OK) rb_raise(cError, "Failed to set busy timeout");
|
615
|
+
|
616
|
+
return self;
|
617
|
+
}
|
618
|
+
|
619
|
+
/* call-seq:
|
620
|
+
* db.total_changes -> value
|
621
|
+
*
|
622
|
+
* Returns the total number of changes made to the database since opening it.
|
623
|
+
*/
|
624
|
+
VALUE Database_total_changes(VALUE self) {
|
625
|
+
Database_t *db;
|
626
|
+
GetOpenDatabase(self, db);
|
627
|
+
|
628
|
+
int value = sqlite3_total_changes(db->sqlite3_db);
|
629
|
+
return INT2NUM(value);
|
630
|
+
}
|
631
|
+
|
632
|
+
/* call-seq:
|
633
|
+
* db.trace { |sql| } -> db
|
634
|
+
* db.trace -> db
|
635
|
+
*
|
636
|
+
* Installs or removes a block that will be invoked for every SQL statement
|
637
|
+
* executed.
|
638
|
+
*/
|
639
|
+
VALUE Database_trace(VALUE self) {
|
640
|
+
Database_t *db;
|
641
|
+
GetOpenDatabase(self, db);
|
642
|
+
|
643
|
+
db->trace_block = rb_block_given_p() ? rb_block_proc() : Qnil;
|
644
|
+
return self;
|
645
|
+
}
|
646
|
+
|
557
647
|
void Init_ExtraliteDatabase(void) {
|
558
648
|
VALUE mExtralite = rb_define_module("Extralite");
|
559
649
|
rb_define_singleton_method(mExtralite, "runtime_status", Extralite_runtime_status, -1);
|
@@ -563,6 +653,7 @@ void Init_ExtraliteDatabase(void) {
|
|
563
653
|
rb_define_alloc_func(cDatabase, Database_allocate);
|
564
654
|
|
565
655
|
rb_define_method(cDatabase, "backup", Database_backup, -1);
|
656
|
+
rb_define_method(cDatabase, "busy_timeout=", Database_busy_timeout_set, 1);
|
566
657
|
rb_define_method(cDatabase, "changes", Database_changes, 0);
|
567
658
|
rb_define_method(cDatabase, "close", Database_close, 0);
|
568
659
|
rb_define_method(cDatabase, "closed?", Database_closed_p, 0);
|
@@ -572,6 +663,7 @@ void Init_ExtraliteDatabase(void) {
|
|
572
663
|
rb_define_method(cDatabase, "initialize", Database_initialize, 1);
|
573
664
|
rb_define_method(cDatabase, "interrupt", Database_interrupt, 0);
|
574
665
|
rb_define_method(cDatabase, "last_insert_rowid", Database_last_insert_rowid, 0);
|
666
|
+
rb_define_method(cDatabase, "limit", Database_limit, -1);
|
575
667
|
rb_define_method(cDatabase, "prepare", Database_prepare, 1);
|
576
668
|
rb_define_method(cDatabase, "query", Database_query_hash, -1);
|
577
669
|
rb_define_method(cDatabase, "query_ary", Database_query_ary, -1);
|
@@ -580,6 +672,8 @@ void Init_ExtraliteDatabase(void) {
|
|
580
672
|
rb_define_method(cDatabase, "query_single_row", Database_query_single_row, -1);
|
581
673
|
rb_define_method(cDatabase, "query_single_value", Database_query_single_value, -1);
|
582
674
|
rb_define_method(cDatabase, "status", Database_status, -1);
|
675
|
+
rb_define_method(cDatabase, "total_changes", Database_total_changes, 0);
|
676
|
+
rb_define_method(cDatabase, "trace", Database_trace, 0);
|
583
677
|
rb_define_method(cDatabase, "transaction_active?", Database_transaction_active_p, 0);
|
584
678
|
|
585
679
|
#ifdef HAVE_SQLITE3_LOAD_EXTENSION
|
@@ -595,6 +689,7 @@ void Init_ExtraliteDatabase(void) {
|
|
595
689
|
rb_gc_register_mark_object(cBusyError);
|
596
690
|
rb_gc_register_mark_object(cInterruptError);
|
597
691
|
|
692
|
+
ID_CALL = rb_intern("call");
|
598
693
|
ID_KEYS = rb_intern("keys");
|
599
694
|
ID_NEW = rb_intern("new");
|
600
695
|
ID_STRIP = rb_intern("strip");
|
data/ext/extralite/extralite.h
CHANGED
@@ -27,6 +27,7 @@ extern VALUE cSQLError;
|
|
27
27
|
extern VALUE cBusyError;
|
28
28
|
extern VALUE cInterruptError;
|
29
29
|
|
30
|
+
extern ID ID_CALL;
|
30
31
|
extern ID ID_KEYS;
|
31
32
|
extern ID ID_NEW;
|
32
33
|
extern ID ID_STRIP;
|
@@ -34,11 +35,13 @@ extern ID ID_TO_S;
|
|
34
35
|
|
35
36
|
typedef struct {
|
36
37
|
sqlite3 *sqlite3_db;
|
38
|
+
VALUE trace_block;
|
37
39
|
} Database_t;
|
38
40
|
|
39
41
|
typedef struct {
|
40
42
|
VALUE db;
|
41
43
|
VALUE sql;
|
44
|
+
Database_t *db_struct;
|
42
45
|
sqlite3 *sqlite3_db;
|
43
46
|
sqlite3_stmt *stmt;
|
44
47
|
} PreparedStatement_t;
|
@@ -72,5 +75,6 @@ int stmt_iterate(sqlite3_stmt *stmt, sqlite3 *db);
|
|
72
75
|
VALUE cleanup_stmt(query_ctx *ctx);
|
73
76
|
|
74
77
|
sqlite3 *Database_sqlite3_db(VALUE self);
|
78
|
+
Database_t *Database_struct(VALUE self);
|
75
79
|
|
76
80
|
#endif /* EXTRALITE_H */
|
@@ -50,6 +50,7 @@ VALUE PreparedStatement_initialize(VALUE self, VALUE db, VALUE sql) {
|
|
50
50
|
rb_raise(cError, "Cannot prepare an empty SQL query");
|
51
51
|
|
52
52
|
stmt->db = db;
|
53
|
+
stmt->db_struct = Database_struct(db);
|
53
54
|
stmt->sqlite3_db = Database_sqlite3_db(db);
|
54
55
|
stmt->sql = sql;
|
55
56
|
|
@@ -65,6 +66,8 @@ static inline VALUE PreparedStatement_perform_query(int argc, VALUE *argv, VALUE
|
|
65
66
|
if (!stmt->stmt)
|
66
67
|
rb_raise(cError, "Prepared statement is closed");
|
67
68
|
|
69
|
+
if (stmt->db_struct->trace_block != Qnil) rb_funcall(stmt->db_struct->trace_block, ID_CALL, 1, stmt->sql);
|
70
|
+
|
68
71
|
sqlite3_reset(stmt->stmt);
|
69
72
|
sqlite3_clear_bindings(stmt->stmt);
|
70
73
|
bind_all_parameters(stmt->stmt, argc, argv);
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module Extralite
|
2
|
+
|
3
|
+
SQLITE_STATUS_MEMORY_USED = 0
|
4
|
+
SQLITE_STATUS_PAGECACHE_USED = 1
|
5
|
+
SQLITE_STATUS_PAGECACHE_OVERFLOW = 2
|
6
|
+
SQLITE_STATUS_SCRATCH_USED = 3 # NOT USED
|
7
|
+
SQLITE_STATUS_SCRATCH_OVERFLOW = 4 # NOT USED
|
8
|
+
SQLITE_STATUS_MALLOC_SIZE = 5
|
9
|
+
SQLITE_STATUS_PARSER_STACK = 6
|
10
|
+
SQLITE_STATUS_PAGECACHE_SIZE = 7
|
11
|
+
SQLITE_STATUS_SCRATCH_SIZE = 8 # NOT USED
|
12
|
+
SQLITE_STATUS_MALLOC_COUNT = 9
|
13
|
+
|
14
|
+
SQLITE_DBSTATUS_LOOKASIDE_USED = 0
|
15
|
+
SQLITE_DBSTATUS_CACHE_USED = 1
|
16
|
+
SQLITE_DBSTATUS_SCHEMA_USED = 2
|
17
|
+
SQLITE_DBSTATUS_STMT_USED = 3
|
18
|
+
SQLITE_DBSTATUS_LOOKASIDE_HIT = 4
|
19
|
+
SQLITE_DBSTATUS_LOOKASIDE_MISS_SIZE = 5
|
20
|
+
SQLITE_DBSTATUS_LOOKASIDE_MISS_FULL = 6
|
21
|
+
SQLITE_DBSTATUS_CACHE_HIT = 7
|
22
|
+
SQLITE_DBSTATUS_CACHE_MISS = 8
|
23
|
+
SQLITE_DBSTATUS_CACHE_WRITE = 9
|
24
|
+
SQLITE_DBSTATUS_DEFERRED_FKS = 10
|
25
|
+
SQLITE_DBSTATUS_CACHE_USED_SHARED = 11
|
26
|
+
SQLITE_DBSTATUS_CACHE_SPILL = 12
|
27
|
+
|
28
|
+
SQLITE_STMTSTATUS_FULLSCAN_STEP = 1
|
29
|
+
SQLITE_STMTSTATUS_SORT = 2
|
30
|
+
SQLITE_STMTSTATUS_AUTOINDEX = 3
|
31
|
+
SQLITE_STMTSTATUS_VM_STEP = 4
|
32
|
+
SQLITE_STMTSTATUS_REPREPARE = 5
|
33
|
+
SQLITE_STMTSTATUS_RUN = 6
|
34
|
+
SQLITE_STMTSTATUS_FILTER_MISS = 7
|
35
|
+
SQLITE_STMTSTATUS_FILTER_HIT = 8
|
36
|
+
SQLITE_STMTSTATUS_MEMUSED = 99
|
37
|
+
|
38
|
+
SQLITE_LIMIT_LENGTH = 0
|
39
|
+
SQLITE_LIMIT_SQL_LENGTH = 1
|
40
|
+
SQLITE_LIMIT_COLUMN = 2
|
41
|
+
SQLITE_LIMIT_EXPR_DEPTH = 3
|
42
|
+
SQLITE_LIMIT_COMPOUND_SELECT = 4
|
43
|
+
SQLITE_LIMIT_VDBE_OP = 5
|
44
|
+
SQLITE_LIMIT_FUNCTION_ARG = 6
|
45
|
+
SQLITE_LIMIT_ATTACHED = 7
|
46
|
+
SQLITE_LIMIT_LIKE_PATTERN_LENGTH = 8
|
47
|
+
SQLITE_LIMIT_VARIABLE_NUMBER = 9
|
48
|
+
SQLITE_LIMIT_TRIGGER_DEPTH = 10
|
49
|
+
SQLITE_LIMIT_WORKER_THREADS = 11
|
50
|
+
end
|
data/lib/extralite/version.rb
CHANGED
data/lib/extralite.rb
CHANGED
@@ -1,46 +1,11 @@
|
|
1
1
|
require_relative './extralite_ext'
|
2
|
+
require_relative './extralite/sqlite3_constants'
|
2
3
|
|
3
4
|
# Extralite is a Ruby gem for working with SQLite databases
|
4
5
|
module Extralite
|
5
|
-
|
6
|
-
SQLITE_STATUS_MEMORY_USED = 0
|
7
|
-
SQLITE_STATUS_PAGECACHE_USED = 1
|
8
|
-
SQLITE_STATUS_PAGECACHE_OVERFLOW = 2
|
9
|
-
SQLITE_STATUS_SCRATCH_USED = 3 # NOT USED
|
10
|
-
SQLITE_STATUS_SCRATCH_OVERFLOW = 4 # NOT USED
|
11
|
-
SQLITE_STATUS_MALLOC_SIZE = 5
|
12
|
-
SQLITE_STATUS_PARSER_STACK = 6
|
13
|
-
SQLITE_STATUS_PAGECACHE_SIZE = 7
|
14
|
-
SQLITE_STATUS_SCRATCH_SIZE = 8 # NOT USED
|
15
|
-
SQLITE_STATUS_MALLOC_COUNT = 9
|
16
6
|
|
17
|
-
|
18
|
-
|
19
|
-
SQLITE_DBSTATUS_SCHEMA_USED = 2
|
20
|
-
SQLITE_DBSTATUS_STMT_USED = 3
|
21
|
-
SQLITE_DBSTATUS_LOOKASIDE_HIT = 4
|
22
|
-
SQLITE_DBSTATUS_LOOKASIDE_MISS_SIZE = 5
|
23
|
-
SQLITE_DBSTATUS_LOOKASIDE_MISS_FULL = 6
|
24
|
-
SQLITE_DBSTATUS_CACHE_HIT = 7
|
25
|
-
SQLITE_DBSTATUS_CACHE_MISS = 8
|
26
|
-
SQLITE_DBSTATUS_CACHE_WRITE = 9
|
27
|
-
SQLITE_DBSTATUS_DEFERRED_FKS = 10
|
28
|
-
SQLITE_DBSTATUS_CACHE_USED_SHARED = 11
|
29
|
-
SQLITE_DBSTATUS_CACHE_SPILL = 12
|
30
|
-
|
31
|
-
SQLITE_STMTSTATUS_FULLSCAN_STEP = 1
|
32
|
-
SQLITE_STMTSTATUS_SORT = 2
|
33
|
-
SQLITE_STMTSTATUS_AUTOINDEX = 3
|
34
|
-
SQLITE_STMTSTATUS_VM_STEP = 4
|
35
|
-
SQLITE_STMTSTATUS_REPREPARE = 5
|
36
|
-
SQLITE_STMTSTATUS_RUN = 6
|
37
|
-
SQLITE_STMTSTATUS_FILTER_MISS = 7
|
38
|
-
SQLITE_STMTSTATUS_FILTER_HIT = 8
|
39
|
-
SQLITE_STMTSTATUS_MEMUSED = 99
|
40
|
-
|
41
|
-
# The following class definitions are not really needed, as they're already
|
42
|
-
# defined in the C extension. We put them here for the sake of generating
|
43
|
-
# docs.
|
7
|
+
# The following error classes are already defined in the C extension. We put
|
8
|
+
# them here for the sake of generating docs.
|
44
9
|
|
45
10
|
# A base class for Extralite exceptions
|
46
11
|
class Error < ::StandardError
|
@@ -70,14 +35,26 @@ module Extralite
|
|
70
35
|
AND name NOT LIKE 'sqlite_%';
|
71
36
|
SQL
|
72
37
|
|
38
|
+
# Returns the list of currently defined tables.
|
39
|
+
#
|
40
|
+
# @return [Array] list of tables
|
73
41
|
def tables
|
74
42
|
query_single_column(TABLES_SQL)
|
75
43
|
end
|
76
44
|
|
45
|
+
# Gets or sets one or more pragmas:
|
46
|
+
#
|
47
|
+
# db.pragma(:cache_size) # get
|
48
|
+
# db.pragma(cache_size: -2000) # set
|
49
|
+
#
|
50
|
+
# @param value [Symbol, String, Hash] pragma name or hash mapping names to values
|
51
|
+
# @return [Hash] query result
|
77
52
|
def pragma(value)
|
78
53
|
value.is_a?(Hash) ? pragma_set(value) : pragma_get(value)
|
79
54
|
end
|
80
55
|
|
56
|
+
private
|
57
|
+
|
81
58
|
def pragma_set(values)
|
82
59
|
sql = values.inject(+'') { |s, (k, v)| s += "pragma #{k}=#{v}; " }
|
83
60
|
query(sql)
|
@@ -87,18 +64,4 @@ module Extralite
|
|
87
64
|
query("pragma #{key}")
|
88
65
|
end
|
89
66
|
end
|
90
|
-
|
91
|
-
# An SQLite backup
|
92
|
-
class Backup
|
93
|
-
# def initialize(dst, dst_name, src, src_name); end
|
94
|
-
|
95
|
-
# def dst; end
|
96
|
-
# def src; end
|
97
|
-
|
98
|
-
# def step(pages); end
|
99
|
-
# def finish; end
|
100
|
-
|
101
|
-
# def pagecount; end
|
102
|
-
# def remaining; end
|
103
|
-
end
|
104
67
|
end
|
data/test/test_database.rb
CHANGED
@@ -269,6 +269,68 @@ end
|
|
269
269
|
def test_database_status
|
270
270
|
assert_operator 0, :<, @db.status(Extralite::SQLITE_DBSTATUS_SCHEMA_USED).first
|
271
271
|
end
|
272
|
+
|
273
|
+
def test_database_limit
|
274
|
+
result = @db.limit(Extralite::SQLITE_LIMIT_ATTACHED)
|
275
|
+
assert_equal 10, result
|
276
|
+
|
277
|
+
result = @db.limit(Extralite::SQLITE_LIMIT_ATTACHED, 5)
|
278
|
+
assert_equal 10, result
|
279
|
+
|
280
|
+
result = @db.limit(Extralite::SQLITE_LIMIT_ATTACHED)
|
281
|
+
assert_equal 5, result
|
282
|
+
|
283
|
+
assert_raises(Extralite::Error) { @db.limit(-999) }
|
284
|
+
end
|
285
|
+
|
286
|
+
def test_database_busy_timeout
|
287
|
+
fn = "/tmp/extralite-#{rand(10000)}.db"
|
288
|
+
db1 = Extralite::Database.new(fn)
|
289
|
+
db2 = Extralite::Database.new(fn)
|
290
|
+
|
291
|
+
db1.query('begin exclusive')
|
292
|
+
assert_raises(Extralite::BusyError) { db2.query('begin exclusive') }
|
293
|
+
|
294
|
+
db2.busy_timeout = 0.3
|
295
|
+
t0 = Time.now
|
296
|
+
t = Thread.new { sleep 0.1; db1.query('rollback') }
|
297
|
+
result = db2.query('begin exclusive')
|
298
|
+
t1 = Time.now
|
299
|
+
|
300
|
+
assert_equal [], result
|
301
|
+
assert t1 - t0 >= 0.1
|
302
|
+
db2.query('rollback')
|
303
|
+
|
304
|
+
# try to provoke a timeout
|
305
|
+
db1.query('begin exclusive')
|
306
|
+
db2.busy_timeout = 0.1
|
307
|
+
t0 = Time.now
|
308
|
+
t = Thread.new do
|
309
|
+
sleep 0.5
|
310
|
+
ensure
|
311
|
+
db1.query('rollback')
|
312
|
+
end
|
313
|
+
assert_raises(Extralite::BusyError) { db2.query('begin exclusive') }
|
314
|
+
t1 = Time.now
|
315
|
+
assert t1 - t0 >= 0.1
|
316
|
+
t.kill
|
317
|
+
t.join
|
318
|
+
|
319
|
+
db1.query('begin exclusive')
|
320
|
+
db2.busy_timeout = 0
|
321
|
+
assert_raises(Extralite::BusyError) { db2.query('begin exclusive') }
|
322
|
+
|
323
|
+
db2.busy_timeout = nil
|
324
|
+
assert_raises(Extralite::BusyError) { db2.query('begin exclusive') }
|
325
|
+
end
|
326
|
+
|
327
|
+
def test_database_total_changes
|
328
|
+
assert_equal 2, @db.total_changes
|
329
|
+
|
330
|
+
@db.query('insert into t values (7, 8, 9)')
|
331
|
+
|
332
|
+
assert_equal 3, @db.total_changes
|
333
|
+
end
|
272
334
|
end
|
273
335
|
|
274
336
|
class ScenarioTest < MiniTest::Test
|
@@ -336,6 +398,30 @@ class ScenarioTest < MiniTest::Test
|
|
336
398
|
result = @db.query_single_column('select x from t')
|
337
399
|
assert_equal [1, 4, 7], result
|
338
400
|
end
|
401
|
+
|
402
|
+
def test_database_trace
|
403
|
+
sqls = []
|
404
|
+
@db.trace { |sql| sqls << sql }
|
405
|
+
|
406
|
+
@db.query('select 1')
|
407
|
+
assert_equal ['select 1'], sqls
|
408
|
+
|
409
|
+
@db.query('select 2')
|
410
|
+
assert_equal ['select 1', 'select 2'], sqls
|
411
|
+
|
412
|
+
stmt = @db.prepare('select 3')
|
413
|
+
|
414
|
+
stmt.query
|
415
|
+
assert_equal ['select 1', 'select 2', 'select 3'], sqls
|
416
|
+
|
417
|
+
# turn off
|
418
|
+
@db.trace
|
419
|
+
|
420
|
+
stmt.query
|
421
|
+
|
422
|
+
@db.query('select 4')
|
423
|
+
assert_equal ['select 1', 'select 2', 'select 3'], sqls
|
424
|
+
end
|
339
425
|
end
|
340
426
|
|
341
427
|
class BackupTest < MiniTest::Test
|
@@ -37,6 +37,11 @@ class PreparedStatementTest < MiniTest::Test
|
|
37
37
|
assert_raises(Extralite::SQLError) { @db.prepare('blah') }
|
38
38
|
end
|
39
39
|
|
40
|
+
def test_prepared_statement_with_multiple_queries
|
41
|
+
error = begin; @db.prepare('select 1; select 2'); rescue => e; error = e; end
|
42
|
+
assert_equal Extralite::Error, error.class
|
43
|
+
end
|
44
|
+
|
40
45
|
def test_prepared_statement_query_hash
|
41
46
|
r = @stmt.query_hash(4)
|
42
47
|
assert_equal [{x: 4, y: 5, z: 6}], r
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: extralite
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: '1.
|
4
|
+
version: '1.23'
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Sharon Rosner
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-01-
|
11
|
+
date: 2023-01-26 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rake-compiler
|
@@ -113,6 +113,7 @@ files:
|
|
113
113
|
- extralite.gemspec
|
114
114
|
- gemspec.rb
|
115
115
|
- lib/extralite.rb
|
116
|
+
- lib/extralite/sqlite3_constants.rb
|
116
117
|
- lib/extralite/version.rb
|
117
118
|
- lib/sequel/adapters/extralite.rb
|
118
119
|
- test/extensions/text.dylib
|