extralite-bundle 2.3 → 2.5
Sign up to get free protection for your applications and to get access to all the features.
- 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/ext/sqlite3/sqlite3.c +5556 -2531
- data/ext/sqlite3/sqlite3.h +125 -27
- 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: 4b3eeea4f7340013a751b39ce75005f85c17004d092aa49b1bb345c30e0db0e2
|
4
|
+
data.tar.gz: 7179f952c683aed063ad4f4f7613009a12cdefc02af77514fd0995dc21bba5fa
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: eefd07e68fc4e2744a66d2941a48a85f603b667d9dd48504556f120d3077d4fbc95cc0913f85047b1fe917c11b0f0d8b60cfc1b41cbecc8e8b8d83f62cce4fb5
|
7
|
+
data.tar.gz: 1269b6eaed626d299b89ed634cc77d38b72e74913864ca18d046fd6ecae030dc6071d23152f7213ed15b1eef40effae88fe72508f4b2d6e1c46dc0a0580c783c
|
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'
|