extralite 2.3 → 2.5
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/.editorconfig +6 -0
- data/.github/workflows/test-bundle.yml +30 -0
- data/.github/workflows/test.yml +7 -3
- data/.gitignore +3 -0
- data/CHANGELOG.md +44 -3
- data/Gemfile-bundle +5 -0
- data/Gemfile.lock +3 -3
- data/README.md +106 -13
- data/TODO.md +0 -3
- data/bin/update_sqlite_source +10 -3
- data/ext/extralite/common.c +288 -37
- data/ext/extralite/database.c +256 -39
- data/ext/extralite/extralite.h +20 -9
- data/ext/extralite/extralite_ext.c +4 -0
- data/ext/extralite/iterator.c +5 -5
- data/ext/extralite/query.c +250 -41
- data/gemspec.rb +1 -1
- data/lib/extralite/version.rb +1 -1
- data/lib/extralite.rb +41 -6
- data/test/fixtures/image.png +0 -0
- data/test/helper.rb +3 -0
- data/test/issue-38.rb +80 -0
- data/test/issue-54.rb +21 -0
- data/test/issue-59.rb +70 -0
- data/test/perf_ary.rb +6 -3
- data/test/perf_hash.rb +7 -4
- data/test/test_database.rb +726 -15
- data/test/test_iterator.rb +2 -1
- data/test/test_query.rb +402 -6
- metadata +10 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: dd2ac51ceb97cc6476f169da43fd11d6dad00ac39d5e9fd5eb0d4842ce670f0e
|
4
|
+
data.tar.gz: f58478324015a1f1827c55ca8b2170b177b270cad0ab8102be225a414da34148
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3051d9b5d2a2543be9bdafd5b46aab5574acefa8ca946eaf34f5b1a8097154748f90ab6ba1b7fd76d5bb2b92d97599b35748be0d815258b4b3a499c6c5cefef7
|
7
|
+
data.tar.gz: 148d29ccfff66b0f1531000d0773b8de98b98169594c08ed032fccd7e92ffe1333357aa3da2e168d2b629d4c7b41b14af373e705601bf384b9d4a4f39af9c4f6
|
data/.editorconfig
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
name: Tests (extralite-bundle)
|
2
|
+
|
3
|
+
on: [push, pull_request]
|
4
|
+
|
5
|
+
concurrency:
|
6
|
+
group: tests-${{ format('{0}-{1}', github.head_ref || github.run_number, github.job) }}
|
7
|
+
cancel-in-progress: true
|
8
|
+
|
9
|
+
jobs:
|
10
|
+
build:
|
11
|
+
strategy:
|
12
|
+
fail-fast: false
|
13
|
+
matrix:
|
14
|
+
os: [ubuntu-latest, macos-latest]
|
15
|
+
ruby: ['3.0', '3.1', '3.2', '3.3']
|
16
|
+
|
17
|
+
name: >-
|
18
|
+
${{matrix.os}}, ${{matrix.ruby}}
|
19
|
+
|
20
|
+
runs-on: ${{matrix.os}}
|
21
|
+
steps:
|
22
|
+
- uses: actions/checkout@v4
|
23
|
+
- uses: ruby/setup-ruby@v1
|
24
|
+
with:
|
25
|
+
ruby-version: ${{matrix.ruby}}
|
26
|
+
bundler-cache: true # 'bundle install' and cache
|
27
|
+
- name: Compile C-extension
|
28
|
+
run: EXTRALITE_BUNDLE=1 bundle exec rake compile
|
29
|
+
- name: Run tests
|
30
|
+
run: bundle exec rake test
|
data/.github/workflows/test.yml
CHANGED
@@ -1,21 +1,25 @@
|
|
1
|
-
name: Tests
|
1
|
+
name: Tests (extralite)
|
2
2
|
|
3
3
|
on: [push, pull_request]
|
4
4
|
|
5
|
+
concurrency:
|
6
|
+
group: tests-${{ format('{0}-{1}', github.head_ref || github.run_number, github.job) }}
|
7
|
+
cancel-in-progress: true
|
8
|
+
|
5
9
|
jobs:
|
6
10
|
build:
|
7
11
|
strategy:
|
8
12
|
fail-fast: false
|
9
13
|
matrix:
|
10
14
|
os: [ubuntu-latest, macos-latest]
|
11
|
-
ruby: ['
|
15
|
+
ruby: ['3.0', '3.1', '3.2', '3.3']
|
12
16
|
|
13
17
|
name: >-
|
14
18
|
${{matrix.os}}, ${{matrix.ruby}}
|
15
19
|
|
16
20
|
runs-on: ${{matrix.os}}
|
17
21
|
steps:
|
18
|
-
- uses: actions/checkout@
|
22
|
+
- uses: actions/checkout@v4
|
19
23
|
- uses: ruby/setup-ruby@v1
|
20
24
|
with:
|
21
25
|
ruby-version: ${{matrix.ruby}}
|
data/.gitignore
CHANGED
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,43 @@
|
|
1
|
+
# 2.5 2024-01-16
|
2
|
+
|
3
|
+
- Update bundled sqlite to version 3.45.0
|
4
|
+
- Implement `Database#batch_query` and related methods
|
5
|
+
[53](https://github.com/digital-fabric/extralite/issues/53)
|
6
|
+
- Accept more options in `Database#initialize`
|
7
|
+
[48](https://github.com/digital-fabric/extralite/issues/48)
|
8
|
+
- Fix `Database#pragma` to return single value when reading pragma value
|
9
|
+
- Accept database name in `Database#tables` method
|
10
|
+
- Improve `Database#batch_execute` - now accepts Enumerable and Callable
|
11
|
+
parameters [52](https://github.com/digital-fabric/extralite/issues/52)
|
12
|
+
- Rename `Database#execute_multi` to `Database#batch_execute`
|
13
|
+
- Implement Query#clone
|
14
|
+
[51](https://github.com/digital-fabric/extralite/issues/51)
|
15
|
+
- Add support for GC compaction
|
16
|
+
- Remove support for Ruby 2.7
|
17
|
+
- Implement Query#<< [49](https://github.com/digital-fabric/extralite/issues/49)
|
18
|
+
- Allow passing parameters in array
|
19
|
+
- Add support for ractors
|
20
|
+
[#50](https://github.com/digital-fabric/extralite/issues/50)
|
21
|
+
|
22
|
+
# 2.4 2023-12-24
|
23
|
+
|
24
|
+
- Implement GVL release threshold.
|
25
|
+
[#46](https://github.com/digital-fabric/extralite/pull/46)
|
26
|
+
- Implement write barriers for better GC performance.
|
27
|
+
- Add support for binding large numbers and symbols.
|
28
|
+
[#43](https://github.com/digital-fabric/extralite/pull/43)
|
29
|
+
- Implement Database#transaction.
|
30
|
+
[#42](https://github.com/digital-fabric/extralite/pull/42)
|
31
|
+
- Add support for binding BLOBs.
|
32
|
+
[#40](https://github.com/digital-fabric/extralite/pull/40)
|
33
|
+
- Minor fixes and cleanup of C-code.
|
34
|
+
- Fix `Database#inspect` for a closed database instance
|
35
|
+
[#37](https://github.com/digital-fabric/extralite/issues/37)
|
36
|
+
- Add support for binding named parameters from Struct and Data classes
|
37
|
+
[#30](https://github.com/digital-fabric/extralite/pull/30)
|
38
|
+
- Update bundled SQLite code to version 3.44.2
|
39
|
+
[#32](https://github.com/digital-fabric/extralite/pull/32)
|
40
|
+
|
1
41
|
# 2.3 2023-11-12
|
2
42
|
|
3
43
|
- Update bundled SQLite to version 3.44.0 (#29)
|
@@ -16,7 +56,8 @@
|
|
16
56
|
|
17
57
|
- Fix Sequel migrations (#8)
|
18
58
|
- Redesign prepared statement functionality (#24)
|
19
|
-
- Rewrite `Extralite::PreparedStatement` into `Extralite::Query` with breaking
|
59
|
+
- Rewrite `Extralite::PreparedStatement` into `Extralite::Query` with breaking
|
60
|
+
API changes
|
20
61
|
- Add `Extralite::Iterator` class for external iteration
|
21
62
|
- Add `Query#each_xxx`, `Query#to_a_xxx` method
|
22
63
|
- Add `Query#eof?` method
|
@@ -65,7 +106,8 @@
|
|
65
106
|
# 1.20 2023-01-21
|
66
107
|
|
67
108
|
- Fix compilation error (#15 @sitano)
|
68
|
-
- Add status methods `Extralite.runtime_status`, `Database#status`,
|
109
|
+
- Add status methods `Extralite.runtime_status`, `Database#status`,
|
110
|
+
`PreparedStatement#status` (#14 @sitano)
|
69
111
|
- Add `Database#interrupt` (#13 @sitano)
|
70
112
|
- Add `Database#backup` (#11 @sitano)
|
71
113
|
- Derive `Extralite::Error` from `StandardError` (#10 @sitano)
|
@@ -187,4 +229,3 @@
|
|
187
229
|
## 0.1 2021-05-21
|
188
230
|
|
189
231
|
- First release
|
190
|
-
|
data/Gemfile-bundle
ADDED
data/Gemfile.lock
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
extralite (2.
|
4
|
+
extralite (2.5)
|
5
5
|
|
6
6
|
GEM
|
7
7
|
remote: https://rubygems.org/
|
8
8
|
specs:
|
9
9
|
docile (1.4.0)
|
10
|
-
json (2.
|
10
|
+
json (2.7.1)
|
11
11
|
minitest (5.15.0)
|
12
12
|
rake (13.1.0)
|
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.
|
37
|
+
2.4.22
|
data/README.md
CHANGED
@@ -14,14 +14,13 @@ with an SQLite3 database, as well as prepared queries (prepared statements).
|
|
14
14
|
Extralite comes in two flavors: the `extralite` gem which uses the
|
15
15
|
system-installed sqlite3 library, and the `extralite-bundle` gem which bundles
|
16
16
|
the latest version of SQLite
|
17
|
-
([3.44.
|
17
|
+
([3.44.2](https://sqlite.org/releaselog/3_44_2.html)), offering access to the
|
18
18
|
latest features and enhancements.
|
19
19
|
|
20
20
|
## Features
|
21
21
|
|
22
22
|
- Super fast - [up to 11x faster](#performance) than the
|
23
|
-
[sqlite3](https://github.com/sparklemotion/sqlite3-ruby) gem
|
24
|
-
[comparison](#why-not-just-use-the-sqlite3-gem).)
|
23
|
+
[sqlite3](https://github.com/sparklemotion/sqlite3-ruby) gem.
|
25
24
|
- A variety of methods for different data access patterns: rows as hashes, rows
|
26
25
|
as arrays, single row, single column, single value.
|
27
26
|
- Prepared statements.
|
@@ -30,10 +29,12 @@ latest features and enhancements.
|
|
30
29
|
- Use system-installed sqlite3, or the [bundled latest version of
|
31
30
|
SQLite3](#installing-the-extralite-sqlite3-bundle).
|
32
31
|
- Improved [concurrency](#concurrency) for multithreaded apps: the Ruby GVL is
|
33
|
-
released while preparing SQL statements and while iterating over
|
32
|
+
released peridically while preparing SQL statements and while iterating over
|
33
|
+
results.
|
34
34
|
- Automatically execute SQL strings containing multiple semicolon-separated
|
35
35
|
queries (handy for creating/modifying schemas).
|
36
|
-
- Execute the same query with multiple parameter lists (useful for inserting
|
36
|
+
- Execute the same query with multiple parameter lists (useful for inserting
|
37
|
+
records).
|
37
38
|
- Load extensions (loading of extensions is autmatically enabled. You can find
|
38
39
|
some useful extensions here: https://github.com/nalgeon/sqlean.)
|
39
40
|
- Includes a [Sequel adapter](#usage-with-sequel).
|
@@ -48,6 +49,8 @@ gem 'extralite'
|
|
48
49
|
|
49
50
|
You can also run `gem install extralite` if you just want to check it out.
|
50
51
|
|
52
|
+
__Note__: Extralite supports Ruby 3.0 and higher.
|
53
|
+
|
51
54
|
### Installing the Extralite-SQLite3 Bundle
|
52
55
|
|
53
56
|
If you don't have sqlite3 installed on your system, do not want to use the
|
@@ -101,15 +104,37 @@ db.query_single_value("select 'foo'") #=> "foo"
|
|
101
104
|
|
102
105
|
# parameter binding (works for all query_xxx methods)
|
103
106
|
db.query_hash('select ? as foo, ? as bar', 1, 2) #=> [{ :foo => 1, :bar => 2 }]
|
107
|
+
db.query_hash('select ? as foo, ? as bar', [1, 2]) #=> [{ :foo => 1, :bar => 2 }]
|
108
|
+
|
109
|
+
# explicit parameter indexes
|
110
|
+
db.query_hash('select ?2 as foo, ?1 as bar, ?1 * ?2 as baz', 6, 7) #=> [{ :foo => 7, :bar => 6, :baz => 42 }
|
104
111
|
|
105
|
-
#
|
112
|
+
# named parameters
|
106
113
|
db.query('select * from foo where bar = :bar', bar: 42)
|
107
114
|
db.query('select * from foo where bar = :bar', 'bar' => 42)
|
108
115
|
db.query('select * from foo where bar = :bar', ':bar' => 42)
|
109
116
|
|
117
|
+
# parameter binding of named parameters from Struct and Data
|
118
|
+
SomeStruct = Struct.new(:foo, :bar)
|
119
|
+
db.query_single_column('select :bar', SomeStruct.new(41, 42)) #=> [42]
|
120
|
+
SomeData = Data.define(:foo, :bar)
|
121
|
+
db.query_single_column('select :bar', SomeData.new(foo: 41, bar: 42)) #=> [42]
|
122
|
+
|
123
|
+
# parameter binding for binary data (BLOBs)
|
124
|
+
db.execute('insert into foo values (?)', File.binread('/path/to/file'))
|
125
|
+
db.execute('insert into foo values (?)', Extralite::Blob.new('Hello, 世界!'))
|
126
|
+
db.execute('insert into foo values (?)', 'Hello, 世界!'.force_encoding(Encoding::ASCII_8BIT))
|
127
|
+
|
110
128
|
# insert multiple rows
|
111
|
-
db.
|
112
|
-
db.
|
129
|
+
db.batch_execute('insert into foo values (?)', ['bar', 'baz'])
|
130
|
+
db.batch_execute('insert into foo values (?, ?)', [[1, 2], [3, 4]])
|
131
|
+
|
132
|
+
# batch execute from enumerable
|
133
|
+
db.batch_execute('insert into foo values (?)', 1..10)
|
134
|
+
|
135
|
+
# batch execute from block
|
136
|
+
source = [[1, 2], [2, 3], [3, 4]]
|
137
|
+
db.batch_execute('insert into foo values (?, ?)') { source.shift }
|
113
138
|
|
114
139
|
# prepared queries
|
115
140
|
query = db.prepare('select ? as foo, ? as bar') #=> Extralite::Query
|
@@ -166,6 +191,13 @@ db.pragma(:journal_mode) #=> 'wal'
|
|
166
191
|
# load an extension
|
167
192
|
db.load_extension('/path/to/extension.so')
|
168
193
|
|
194
|
+
# run queries in a transaction
|
195
|
+
db.transaction do
|
196
|
+
db.execute('insert into foo values (?)', 1)
|
197
|
+
db.execute('insert into foo values (?)', 2)
|
198
|
+
db.execute('insert into foo values (?)', 3)
|
199
|
+
end
|
200
|
+
|
169
201
|
# close database
|
170
202
|
db.close
|
171
203
|
db.closed? #=> true
|
@@ -194,6 +226,23 @@ ensure
|
|
194
226
|
end
|
195
227
|
```
|
196
228
|
|
229
|
+
### Running transactions
|
230
|
+
|
231
|
+
In order to run multiple queries in a single transaction, use
|
232
|
+
`Database#transaction`, passing a block that runs the queries. You can specify
|
233
|
+
the [transaction
|
234
|
+
mode](https://www.sqlite.org/lang_transaction.html#deferred_immediate_and_exclusive_transactions).
|
235
|
+
The default mode is `:immediate`:
|
236
|
+
|
237
|
+
```ruby
|
238
|
+
db.transaction { ... } # Run an immediate transaction
|
239
|
+
db.transaction(:deferred) { ... } # Run a deferred transaction
|
240
|
+
db.transaction(:exclusive) { ... } # Run an exclusive transaction
|
241
|
+
```
|
242
|
+
|
243
|
+
If an exception is raised in the given block, the transaction will be rolled
|
244
|
+
back. Otherwise, it is committed.
|
245
|
+
|
197
246
|
### Creating Backups
|
198
247
|
|
199
248
|
You can use `Database#backup` to create backup copies of a database. The
|
@@ -295,11 +344,55 @@ p articles.to_a
|
|
295
344
|
|
296
345
|
## Concurrency
|
297
346
|
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
347
|
+
### The Ruby GVL
|
348
|
+
|
349
|
+
Extralite releases the [Ruby
|
350
|
+
GVL](https://www.speedshop.co/2020/05/11/the-ruby-gvl-and-scaling.html) while
|
351
|
+
making calls to the sqlite3 library that might block, such as when backing up a
|
352
|
+
database, or when preparing a query. This allows other threads to run while the
|
353
|
+
underlying sqlite3 library is busy preparing queries, fetching records and
|
354
|
+
backing up databases.
|
355
|
+
|
356
|
+
Extralite also releases the GVL periodically when iterating over records. By
|
357
|
+
default, the GVL is released every 1000 records iterated. The GVL release
|
358
|
+
threshold can be set separately for each database:
|
359
|
+
|
360
|
+
```ruby
|
361
|
+
db.gvl_release_threshold = 10 # release GVL every 10 records
|
362
|
+
|
363
|
+
db.gvl_release_threshold = nil # use default value (currently 1000)
|
364
|
+
```
|
365
|
+
|
366
|
+
For most applications, there's no need to tune the GVL threshold value, as it
|
367
|
+
provides [excellent](#performance) performance characteristics for both
|
368
|
+
single-threaded and multi-threaded applications.
|
369
|
+
|
370
|
+
In a heavily multi-threaded application, releasing the GVL more often (lower
|
371
|
+
threshold value) will lead to less latency (for threads not running a query),
|
372
|
+
but will also hurt the throughput (for the thread running the query). Releasing
|
373
|
+
the GVL less often (higher threshold value) will lead to better throughput for
|
374
|
+
queries, while increasing latency for threads not running a query. The following
|
375
|
+
diagram demonstrates the relationship between the GVL release threshold value,
|
376
|
+
latency and throughput:
|
377
|
+
|
378
|
+
```
|
379
|
+
less latency & throughput <<< GVL release threshold >>> more latency & throughput
|
380
|
+
```
|
381
|
+
|
382
|
+
### Thread Safety
|
383
|
+
|
384
|
+
A single database instance can be safely used in multiple threads simultaneously
|
385
|
+
as long as the following conditions are met:
|
386
|
+
|
387
|
+
- No explicit transactions are used.
|
388
|
+
- Each thread issues queries by calling `Database#query_xxx`, or uses a separate
|
389
|
+
`Query` instance.
|
390
|
+
- The GVL release threshold is not `0` (i.e. the GVL is released periodically
|
391
|
+
while running queries.)
|
392
|
+
|
393
|
+
### Use with Ractors
|
394
|
+
|
395
|
+
Extralite databases can be used inside ractors
|
303
396
|
|
304
397
|
## Performance
|
305
398
|
|
data/TODO.md
CHANGED
@@ -1,7 +1,5 @@
|
|
1
|
-
- Improve tracing
|
2
1
|
- Transactions and savepoints:
|
3
2
|
|
4
|
-
- `DB#transaction {}` - does a `BEGIN..COMMIT` - non-reentrant!
|
5
3
|
- `DB#savepoint(name)` - creates a savepoint
|
6
4
|
- `DB#release(name)` - releases a savepoint
|
7
5
|
- `DB#rollback` - raises `Extralite::Rollback`, which is rescued by `DB#transaction`
|
@@ -10,7 +8,6 @@
|
|
10
8
|
- More database methods:
|
11
9
|
|
12
10
|
- `Database#quote`
|
13
|
-
- `Database#busy_timeout=` https://sqlite.org/c3ref/busy_timeout.html
|
14
11
|
- `Database#cache_flush` https://sqlite.org/c3ref/db_cacheflush.html
|
15
12
|
- `Database#release_memory` https://sqlite.org/c3ref/db_release_memory.html
|
16
13
|
|
data/bin/update_sqlite_source
CHANGED
@@ -9,10 +9,10 @@ require 'date'
|
|
9
9
|
|
10
10
|
FileUtils.cd '/tmp'
|
11
11
|
|
12
|
-
version_id = version.
|
12
|
+
version_id = version.split('.').each_with_index.map { |v, i| i == 0 ? v : v.rjust(2, '0') }.join
|
13
13
|
version_id += '0' * (7 - version_id.length)
|
14
14
|
url = "https://sqlite.org/#{Date.today.year}/sqlite-amalgamation-#{version_id}.zip"
|
15
|
-
dest = File.expand_path('../ext/
|
15
|
+
dest = File.expand_path('../ext/sqlite3', __dir__)
|
16
16
|
|
17
17
|
puts "Downloading from #{url}..."
|
18
18
|
`curl #{url} > #{version_id}.zip`
|
@@ -23,4 +23,11 @@ puts "Unzipping zip file..."
|
|
23
23
|
puts "Copying source files"
|
24
24
|
`cp sqlite-amalgamation-#{version_id}/sqlite3.* #{dest}/`
|
25
25
|
|
26
|
-
puts
|
26
|
+
puts "Updating README"
|
27
|
+
readme_path = File.expand_path('../README.md', __dir__)
|
28
|
+
readme = File.read(readme_path)
|
29
|
+
readme.gsub!(/\[\d+\.\d+\.\d+\]/, "[#{version}]")
|
30
|
+
readme.gsub!(/\d+_\d+_\d+\.html/, "#{version.gsub('.', '_')}.html")
|
31
|
+
File.write(readme_path, readme)
|
32
|
+
|
33
|
+
puts 'Done updating source files'
|