extralite 1.8.1 → 1.11
Sign up to get free protection for your applications and to get access to all the features.
- 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:
|