extralite 1.8.1 → 1.11
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/FUNDING.yml +1 -0
- data/.github/workflows/test.yml +1 -1
- data/CHANGELOG.md +13 -1
- data/Gemfile.lock +1 -1
- data/README.md +49 -22
- data/ext/extralite/extralite.c +10 -10
- data/extralite.gemspec +1 -0
- data/lib/extralite/version.rb +1 -1
- data/test/perf_ary.rb +51 -0
- data/test/{perf.rb → perf_hash.rb} +0 -0
- data/test/test_database.rb +25 -0
- metadata +6 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d88bd81eca07e148be1f8d5b07d24c0aab49b32a9dc9aeb4a9ea5a74cf769491
|
4
|
+
data.tar.gz: 9f5d55a8cd4a58e2026ca3e850d48c28d86878e191e91fb92e59c76335a64484
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: cca614a4e3ffde8ff56ef3e8394f85f4a57d37f8615f2f144a9a39f84d50e51dd015348da7f93340326b40ff4b16202ec21bc03f23edacf678c03f57d0955974
|
7
|
+
data.tar.gz: 2e9f9c9091ac99fa96c3562fdb1e49642b2961ee682c30d98f8babd95b8366d9c96019df7185c5ef493b330f6c32066be6868398dcdb123c4607e9ab4875462b
|
data/.github/FUNDING.yml
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
github: ciconia
|
data/.github/workflows/test.yml
CHANGED
data/CHANGELOG.md
CHANGED
@@ -1,4 +1,16 @@
|
|
1
|
-
## 1.
|
1
|
+
## 1.11 2021-12-17
|
2
|
+
|
3
|
+
- Fix compilation on MacOS (#3)
|
4
|
+
|
5
|
+
## 1.10 2021-12-15
|
6
|
+
|
7
|
+
- Fix mutliple parameter binding with hash
|
8
|
+
|
9
|
+
## 1.9 2021-12-15
|
10
|
+
|
11
|
+
- Add support for reading BLOBs
|
12
|
+
|
13
|
+
## 1.8.2 2021-12-15
|
2
14
|
|
3
15
|
- Add documentation
|
4
16
|
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -1,14 +1,31 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
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
|
10
|
-
wrapper for Ruby. It provides a single class with a minimal set of methods
|
11
|
-
|
26
|
+
Extralite is a fast, extra-lightweight (less than 460 lines of C-code) SQLite3
|
27
|
+
wrapper for Ruby. It provides a single class with a minimal set of methods for
|
28
|
+
interacting with an SQLite3 database.
|
12
29
|
|
13
30
|
## Features
|
14
31
|
|
@@ -127,7 +144,7 @@ Here's a table summarizing the differences between the two gems:
|
|
127
144
|
|custom collations|yes|no|
|
128
145
|
|custom aggregate functions|yes|no|
|
129
146
|
|Multithread friendly|no|[yes](#what-about-concurrency)|
|
130
|
-
|Code size|~2650LoC|~
|
147
|
+
|Code size|~2650LoC|~530LoC|
|
131
148
|
|Performance|1x|1.5x to 12.5x (see [below](#performance))|
|
132
149
|
|
133
150
|
## What about concurrency?
|
@@ -140,23 +157,33 @@ performance:
|
|
140
157
|
|
141
158
|
## Performance
|
142
159
|
|
143
|
-
A benchmark script is
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
160
|
+
A benchmark script is included, creating a table of various row counts, then
|
161
|
+
fetching the entire table using either `sqlite3` or `extralite`. This benchmark
|
162
|
+
shows Extralite to be up to 12.5 times faster than `sqlite3` when fetching a
|
163
|
+
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):
|
164
|
+
|
165
|
+
|Row count|sqlite3-ruby|Extralite|Advantage|
|
166
|
+
|-:|-:|-:|-:|
|
167
|
+
|10|57620 rows/s|95340 rows/s|__1.65x__|
|
168
|
+
|1K|286.8K rows/s|2106.4 rows/s|__7.35x__|
|
169
|
+
|100K|181K rows/s|2275.3K rows/s|__12.53x__|
|
149
170
|
|
150
|
-
|
151
|
-
|
152
|
-
|
|
153
|
-
|
154
|
-
|
|
171
|
+
When [fetching rows as arrays](https://github.com/digital-fabric/extralite/blob/main/test/perf_ary.rb) Extralite also significantly outperforms sqlite3-ruby:
|
172
|
+
|
173
|
+
|Row count|sqlite3-ruby|Extralite|Advantage|
|
174
|
+
|-:|-:|-:|-:|
|
175
|
+
|10|64365 rows/s|94031 rows/s|__1.46x__|
|
176
|
+
|1K|498.9K rows/s|2478.2K rows/s|__4.97x__|
|
177
|
+
|100K|441.1K rows/s|3023.4K rows/s|__6.85x__|
|
155
178
|
|
156
179
|
(If you're interested in checking this yourself, just run the script and let me
|
157
|
-
know if your results are
|
180
|
+
know if your results are better/worse.)
|
181
|
+
|
182
|
+
As those benchmarks show, Extralite is capabale of reading more than 3M
|
183
|
+
rows/second (when fetching rows as arrays), and more than 2.2M rows/second (when
|
184
|
+
fetching rows as hashes.)
|
158
185
|
|
159
186
|
## Contributing
|
160
187
|
|
161
188
|
Contributions in the form of issues, PRs or comments will be greatly
|
162
|
-
appreciated!
|
189
|
+
appreciated!
|
data/ext/extralite/extralite.c
CHANGED
@@ -105,7 +105,7 @@ VALUE Database_closed_p(VALUE self) {
|
|
105
105
|
return db->sqlite3_db ? Qfalse : Qtrue;
|
106
106
|
}
|
107
107
|
|
108
|
-
inline VALUE get_column_value(sqlite3_stmt *stmt, int col, int type) {
|
108
|
+
static inline VALUE get_column_value(sqlite3_stmt *stmt, int col, int type) {
|
109
109
|
switch (type) {
|
110
110
|
case SQLITE_NULL:
|
111
111
|
return Qnil;
|
@@ -116,7 +116,7 @@ inline VALUE get_column_value(sqlite3_stmt *stmt, int col, int type) {
|
|
116
116
|
case SQLITE_TEXT:
|
117
117
|
return rb_str_new_cstr((char *)sqlite3_column_text(stmt, col));
|
118
118
|
case SQLITE_BLOB:
|
119
|
-
|
119
|
+
return rb_str_new((const char *)sqlite3_column_blob(stmt, col), (long)sqlite3_column_bytes(stmt, col));
|
120
120
|
default:
|
121
121
|
rb_raise(cError, "Unknown column type: %d", type);
|
122
122
|
}
|
@@ -128,24 +128,24 @@ static void bind_parameter_value(sqlite3_stmt *stmt, int pos, VALUE value);
|
|
128
128
|
|
129
129
|
static inline void bind_hash_parameter_values(sqlite3_stmt *stmt, VALUE hash) {
|
130
130
|
VALUE keys = rb_funcall(hash, ID_KEYS, 0);
|
131
|
-
|
132
|
-
for (
|
131
|
+
long len = RARRAY_LEN(keys);
|
132
|
+
for (long i = 0; i < len; i++) {
|
133
133
|
VALUE k = RARRAY_AREF(keys, i);
|
134
134
|
VALUE v = rb_hash_aref(hash, k);
|
135
135
|
|
136
136
|
switch (TYPE(k)) {
|
137
137
|
case T_FIXNUM:
|
138
138
|
bind_parameter_value(stmt, NUM2INT(k), v);
|
139
|
-
|
139
|
+
break;
|
140
140
|
case T_SYMBOL:
|
141
141
|
k = rb_funcall(k, ID_TO_S, 0);
|
142
142
|
case T_STRING:
|
143
143
|
if(RSTRING_PTR(k)[0] != ':') k = rb_str_plus(rb_str_new2(":"), k);
|
144
144
|
int pos = sqlite3_bind_parameter_index(stmt, StringValuePtr(k));
|
145
145
|
bind_parameter_value(stmt, pos, v);
|
146
|
-
|
146
|
+
break;
|
147
147
|
default:
|
148
|
-
|
148
|
+
rb_raise(cError, "Cannot bind hash key value idx %ld", i);
|
149
149
|
}
|
150
150
|
}
|
151
151
|
RB_GC_GUARD(keys);
|
@@ -218,7 +218,7 @@ struct multi_stmt_ctx {
|
|
218
218
|
sqlite3 *db;
|
219
219
|
sqlite3_stmt **stmt;
|
220
220
|
const char *str;
|
221
|
-
|
221
|
+
long len;
|
222
222
|
int rc;
|
223
223
|
};
|
224
224
|
|
@@ -256,7 +256,7 @@ statements. It will release the GVL while the statements are being prepared and
|
|
256
256
|
executed. All statements excluding the last one are executed. The last statement
|
257
257
|
is not executed, but instead handed back to the caller for looping over results.
|
258
258
|
*/
|
259
|
-
inline void prepare_multi_stmt(sqlite3 *db, sqlite3_stmt **stmt, VALUE sql) {
|
259
|
+
static inline void prepare_multi_stmt(sqlite3 *db, sqlite3_stmt **stmt, VALUE sql) {
|
260
260
|
struct multi_stmt_ctx ctx = {db, stmt, RSTRING_PTR(sql), RSTRING_LEN(sql), 0};
|
261
261
|
rb_thread_call_without_gvl(prepare_multi_stmt_without_gvl, (void *)&ctx, RUBY_UBF_IO, 0);
|
262
262
|
RB_GC_GUARD(sql);
|
@@ -284,7 +284,7 @@ void *stmt_iterate_without_gvl(void *ptr) {
|
|
284
284
|
return NULL;
|
285
285
|
}
|
286
286
|
|
287
|
-
inline int stmt_iterate(sqlite3_stmt *stmt, sqlite3 *db) {
|
287
|
+
static inline int stmt_iterate(sqlite3_stmt *stmt, sqlite3 *db) {
|
288
288
|
struct step_ctx ctx = {stmt, 0};
|
289
289
|
rb_thread_call_without_gvl(stmt_iterate_without_gvl, (void *)&ctx, RUBY_UBF_IO, 0);
|
290
290
|
switch (ctx.rc) {
|
data/extralite.gemspec
CHANGED
@@ -11,6 +11,7 @@ Gem::Specification.new do |s|
|
|
11
11
|
s.homepage = 'https://github.com/digital-fabric/extralite'
|
12
12
|
s.metadata = {
|
13
13
|
"source_code_uri" => "https://github.com/digital-fabric/extralite",
|
14
|
+
"documentation_uri" => "https://www.rubydoc.info/gems/extralite",
|
14
15
|
"homepage_uri" => "https://github.com/digital-fabric/extralite",
|
15
16
|
"changelog_uri" => "https://github.com/digital-fabric/extralite/blob/master/CHANGELOG.md"
|
16
17
|
}
|
data/lib/extralite/version.rb
CHANGED
data/test/perf_ary.rb
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'bundler/inline'
|
4
|
+
|
5
|
+
gemfile do
|
6
|
+
source 'https://rubygems.org'
|
7
|
+
gem 'sqlite3'
|
8
|
+
gem 'extralite', path: '..'
|
9
|
+
gem 'benchmark-ips'
|
10
|
+
end
|
11
|
+
|
12
|
+
require 'benchmark/ips'
|
13
|
+
require 'fileutils'
|
14
|
+
|
15
|
+
DB_PATH = '/tmp/extralite_sqlite3_perf.db'
|
16
|
+
|
17
|
+
def prepare_database(count)
|
18
|
+
FileUtils.rm(DB_PATH) rescue nil
|
19
|
+
db = Extralite::Database.new(DB_PATH)
|
20
|
+
db.query('create table foo ( a integer primary key, b text )')
|
21
|
+
db.query('begin')
|
22
|
+
count.times { db.query('insert into foo (b) values (?)', "hello#{rand(1000)}" )}
|
23
|
+
db.query('commit')
|
24
|
+
end
|
25
|
+
|
26
|
+
def sqlite3_run(count)
|
27
|
+
db = SQLite3::Database.new(DB_PATH)
|
28
|
+
results = db.execute('select * from foo')
|
29
|
+
raise unless results.size == count
|
30
|
+
end
|
31
|
+
|
32
|
+
def extralite_run(count)
|
33
|
+
db = Extralite::Database.new(DB_PATH)
|
34
|
+
results = db.query_ary('select * from foo')
|
35
|
+
raise unless results.size == count
|
36
|
+
end
|
37
|
+
|
38
|
+
[10, 1000, 100000].each do |c|
|
39
|
+
puts; puts; puts "Record count: #{c}"
|
40
|
+
|
41
|
+
prepare_database(c)
|
42
|
+
|
43
|
+
Benchmark.ips do |x|
|
44
|
+
x.config(:time => 3, :warmup => 1)
|
45
|
+
|
46
|
+
x.report("sqlite3") { sqlite3_run(c) }
|
47
|
+
x.report("extralite") { extralite_run(c) }
|
48
|
+
|
49
|
+
x.compare!
|
50
|
+
end
|
51
|
+
end
|
File without changes
|
data/test/test_database.rb
CHANGED
@@ -138,6 +138,31 @@ end
|
|
138
138
|
r = @db.query('select x, y, z from t where z = :bazzz', ':bazzz' => 6)
|
139
139
|
assert_equal [{ x: 4, y: 5, z: 6 }], r
|
140
140
|
end
|
141
|
+
|
142
|
+
def test_parameter_binding_with_index_key
|
143
|
+
r = @db.query('select x, y, z from t where z = ?', 1 => 3)
|
144
|
+
assert_equal [{ x: 1, y: 2, z: 3 }], r
|
145
|
+
|
146
|
+
r = @db.query('select x, y, z from t where x = ?2', 1 => 42, 2 => 4)
|
147
|
+
assert_equal [{ x: 4, y: 5, z: 6 }], r
|
148
|
+
end
|
149
|
+
|
150
|
+
def test_value_casting
|
151
|
+
r = @db.query_single_value("select 'abc'")
|
152
|
+
assert_equal 'abc', r
|
153
|
+
|
154
|
+
r = @db.query_single_value('select 123')
|
155
|
+
assert_equal 123, r
|
156
|
+
|
157
|
+
r = @db.query_single_value('select 12.34')
|
158
|
+
assert_equal 12.34, r
|
159
|
+
|
160
|
+
r = @db.query_single_value('select zeroblob(4)')
|
161
|
+
assert_equal "\x00\x00\x00\x00", r
|
162
|
+
|
163
|
+
r = @db.query_single_value('select null')
|
164
|
+
assert_nil r
|
165
|
+
end
|
141
166
|
end
|
142
167
|
|
143
168
|
class ScenarioTest < MiniTest::Test
|
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.11'
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Sharon Rosner
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-12-
|
11
|
+
date: 2021-12-17 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rake-compiler
|
@@ -88,6 +88,7 @@ extensions:
|
|
88
88
|
extra_rdoc_files:
|
89
89
|
- README.md
|
90
90
|
files:
|
91
|
+
- ".github/FUNDING.yml"
|
91
92
|
- ".github/workflows/test.yml"
|
92
93
|
- ".gitignore"
|
93
94
|
- CHANGELOG.md
|
@@ -105,7 +106,8 @@ files:
|
|
105
106
|
- lib/extralite/version.rb
|
106
107
|
- lib/sequel/adapters/extralite.rb
|
107
108
|
- test/helper.rb
|
108
|
-
- test/
|
109
|
+
- test/perf_ary.rb
|
110
|
+
- test/perf_hash.rb
|
109
111
|
- test/run.rb
|
110
112
|
- test/test_database.rb
|
111
113
|
- test/test_sequel.rb
|
@@ -114,6 +116,7 @@ licenses:
|
|
114
116
|
- MIT
|
115
117
|
metadata:
|
116
118
|
source_code_uri: https://github.com/digital-fabric/extralite
|
119
|
+
documentation_uri: https://www.rubydoc.info/gems/extralite
|
117
120
|
homepage_uri: https://github.com/digital-fabric/extralite
|
118
121
|
changelog_uri: https://github.com/digital-fabric/extralite/blob/master/CHANGELOG.md
|
119
122
|
post_install_message:
|