extralite 1.8.2 → 1.12
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 +2 -2
- data/CHANGELOG.md +17 -0
- data/Gemfile.lock +3 -3
- data/README.md +57 -24
- data/ext/extralite/extconf.rb +3 -109
- data/ext/extralite/extralite.c +21 -10
- data/ext/extralite/sqlite3.c +237479 -0
- data/ext/extralite/sqlite3.h +12492 -0
- data/extralite.gemspec +1 -1
- data/lib/extralite/version.rb +1 -1
- data/test/extensions/text.dylib +0 -0
- data/test/extensions/text.so +0 -0
- data/test/helper.rb +2 -0
- data/test/perf_ary.rb +51 -0
- data/test/{perf.rb → perf_hash.rb} +0 -0
- data/test/test_database.rb +39 -0
- data/test/test_extralite.rb +14 -0
- metadata +16 -9
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ed6cb05e12c36bbbc92701af98639a9a92b91d9ba0fca0362b35fb12f3af676e
|
4
|
+
data.tar.gz: 6d7976fc5b39aa414a5a3431a9c5a1a6c517166d4e82de621b22da3be50fe38b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9baa147f13fa4498264e6e942e9c154bcdcd458f503944be9385f5ea61b82a8e6d381f77a75ad7a4c7643aa124cc2a4d5aa8ffb58400efa641dcb62fad810437
|
7
|
+
data.tar.gz: 789d203582c86f54a8f234b67d7b54b03150b900d25a5c76e81a57d779d1735fa7a8dc9822d121df7d08af4513481bf4c0a1a6d2fb1d6dfc54d80022b2b6e559
|
data/.github/FUNDING.yml
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
github: ciconia
|
data/.github/workflows/test.yml
CHANGED
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,20 @@
|
|
1
|
+
## 1.12 2022-02-15
|
2
|
+
|
3
|
+
- Add `Extralite.sqlite3_version` method
|
4
|
+
- Bundle sqlite3 in gem
|
5
|
+
|
6
|
+
## 1.11 2021-12-17
|
7
|
+
|
8
|
+
- Fix compilation on MacOS (#3)
|
9
|
+
|
10
|
+
## 1.10 2021-12-15
|
11
|
+
|
12
|
+
- Fix mutliple parameter binding with hash
|
13
|
+
|
14
|
+
## 1.9 2021-12-15
|
15
|
+
|
16
|
+
- Add support for reading BLOBs
|
17
|
+
|
1
18
|
## 1.8.2 2021-12-15
|
2
19
|
|
3
20
|
- Add documentation
|
data/Gemfile.lock
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
extralite (1.
|
4
|
+
extralite (1.12)
|
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.6.1)
|
11
11
|
minitest (5.15.0)
|
12
12
|
rake (13.0.6)
|
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.3.3
|
data/README.md
CHANGED
@@ -1,17 +1,36 @@
|
|
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
|
|
32
|
+
- Zero dependencies: Extralite bundles SQLite3 version 3.37.2 - no need to
|
33
|
+
install any `libsqlite3` packages.
|
15
34
|
- A variety of methods for different data access patterns: rows as hashes, rows
|
16
35
|
as arrays, single row, single column, single value.
|
17
36
|
- Super fast - [up to 12.5x faster](#performance) than the
|
@@ -28,13 +47,16 @@ interact with an SQLite3 database.
|
|
28
47
|
- Get number of rows changed by last query.
|
29
48
|
- Load extensions (loading of extensions is autmatically enabled. You can find
|
30
49
|
some useful extensions here: https://github.com/nalgeon/sqlean.)
|
31
|
-
- Includes a [Sequel adapter](#usage-with-sequel)
|
50
|
+
- Includes a [Sequel adapter](#usage-with-sequel).
|
32
51
|
|
33
52
|
## Usage
|
34
53
|
|
35
54
|
```ruby
|
36
55
|
require 'extralite'
|
37
56
|
|
57
|
+
# get sqlite3 version
|
58
|
+
Extralite.sqlite3_version #=> "3.37.2"
|
59
|
+
|
38
60
|
# open a database
|
39
61
|
db = Extralite::Database.new('/tmp/my.db')
|
40
62
|
|
@@ -96,7 +118,7 @@ Extralite includes an adapter for
|
|
96
118
|
just use the `extralite` scheme instead of `sqlite`:
|
97
119
|
|
98
120
|
```ruby
|
99
|
-
DB = Sequel.connect('extralite
|
121
|
+
DB = Sequel.connect('extralite://blog.db')
|
100
122
|
articles = DB[:articles]
|
101
123
|
p articles.to_a
|
102
124
|
```
|
@@ -120,6 +142,7 @@ Here's a table summarizing the differences between the two gems:
|
|
120
142
|
|
121
143
|
| |sqlite3-ruby|Extralite|
|
122
144
|
|-|-|-|
|
145
|
+
|SQLite3 dependency|depends on OS-installed libsqlite3|bundles latest version of SQLite3|
|
123
146
|
|API design|multiple classes|single class|
|
124
147
|
|Query results|row as hash, row as array, single row, single value|row as hash, row as array, __single column__, single row, single value|
|
125
148
|
|execute multiple statements|separate API (#execute_batch)|integrated|
|
@@ -127,7 +150,7 @@ Here's a table summarizing the differences between the two gems:
|
|
127
150
|
|custom collations|yes|no|
|
128
151
|
|custom aggregate functions|yes|no|
|
129
152
|
|Multithread friendly|no|[yes](#what-about-concurrency)|
|
130
|
-
|Code size|~2650LoC|~
|
153
|
+
|Code size|~2650LoC|~530LoC|
|
131
154
|
|Performance|1x|1.5x to 12.5x (see [below](#performance))|
|
132
155
|
|
133
156
|
## What about concurrency?
|
@@ -140,23 +163,33 @@ performance:
|
|
140
163
|
|
141
164
|
## Performance
|
142
165
|
|
143
|
-
A benchmark script is
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
166
|
+
A benchmark script is included, creating a table of various row counts, then
|
167
|
+
fetching the entire table using either `sqlite3` or `extralite`. This benchmark
|
168
|
+
shows Extralite to be up to 12.5 times faster than `sqlite3` when fetching a
|
169
|
+
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):
|
170
|
+
|
171
|
+
|Row count|sqlite3-ruby|Extralite|Advantage|
|
172
|
+
|-:|-:|-:|-:|
|
173
|
+
|10|57620 rows/s|95340 rows/s|__1.65x__|
|
174
|
+
|1K|286.8K rows/s|2106.4 rows/s|__7.35x__|
|
175
|
+
|100K|181K rows/s|2275.3K rows/s|__12.53x__|
|
149
176
|
|
150
|
-
|
151
|
-
|
152
|
-
|
|
153
|
-
|
154
|
-
|
|
177
|
+
When [fetching rows as arrays](https://github.com/digital-fabric/extralite/blob/main/test/perf_ary.rb) Extralite also significantly outperforms sqlite3-ruby:
|
178
|
+
|
179
|
+
|Row count|sqlite3-ruby|Extralite|Advantage|
|
180
|
+
|-:|-:|-:|-:|
|
181
|
+
|10|64365 rows/s|94031 rows/s|__1.46x__|
|
182
|
+
|1K|498.9K rows/s|2478.2K rows/s|__4.97x__|
|
183
|
+
|100K|441.1K rows/s|3023.4K rows/s|__6.85x__|
|
155
184
|
|
156
185
|
(If you're interested in checking this yourself, just run the script and let me
|
157
|
-
know if your results are
|
186
|
+
know if your results are better/worse.)
|
187
|
+
|
188
|
+
As those benchmarks show, Extralite is capabale of reading more than 3M
|
189
|
+
rows/second (when fetching rows as arrays), and more than 2.2M rows/second (when
|
190
|
+
fetching rows as hashes.)
|
158
191
|
|
159
192
|
## Contributing
|
160
193
|
|
161
194
|
Contributions in the form of issues, PRs or comments will be greatly
|
162
|
-
appreciated!
|
195
|
+
appreciated!
|
data/ext/extralite/extconf.rb
CHANGED
@@ -1,115 +1,9 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
# require 'rubygems'
|
4
|
-
# require 'mkmf'
|
5
|
-
|
6
|
-
# # $CFLAGS << "-Wdiscarded-qualifier"
|
7
|
-
# # $CFLAGS << " -Wno-comment"
|
8
|
-
# # $CFLAGS << " -Wno-unused-result"
|
9
|
-
# # $CFLAGS << " -Wno-dangling-else"
|
10
|
-
# # $CFLAGS << " -Wno-parentheses"
|
11
|
-
|
12
|
-
# dir_config 'extralite_ext'
|
13
|
-
# create_makefile 'extralite_ext'
|
14
|
-
|
15
|
-
ENV['RC_ARCHS'] = '' if RUBY_PLATFORM =~ /darwin/
|
16
|
-
|
17
3
|
require 'mkmf'
|
18
4
|
|
19
|
-
|
20
|
-
|
21
|
-
RbConfig::MAKEFILE_CONFIG['CC'] = ENV['CC'] if ENV['CC']
|
22
|
-
|
23
|
-
ldflags = cppflags = nil
|
24
|
-
if RbConfig::CONFIG["host_os"] =~ /darwin/
|
25
|
-
begin
|
26
|
-
if with_config('sqlcipher')
|
27
|
-
brew_prefix = `brew --prefix sqlcipher`.chomp
|
28
|
-
ldflags = "#{brew_prefix}/lib"
|
29
|
-
cppflags = "#{brew_prefix}/include/sqlcipher"
|
30
|
-
pkg_conf = "#{brew_prefix}/lib/pkgconfig"
|
31
|
-
else
|
32
|
-
brew_prefix = `brew --prefix sqlite3`.chomp
|
33
|
-
ldflags = "#{brew_prefix}/lib"
|
34
|
-
cppflags = "#{brew_prefix}/include"
|
35
|
-
pkg_conf = "#{brew_prefix}/lib/pkgconfig"
|
36
|
-
end
|
37
|
-
|
38
|
-
# pkg_config should be less error prone than parsing compiler
|
39
|
-
# commandline options, but we need to set default ldflags and cpp flags
|
40
|
-
# in case the user doesn't have pkg-config installed
|
41
|
-
ENV['PKG_CONFIG_PATH'] ||= pkg_conf
|
42
|
-
rescue
|
43
|
-
end
|
44
|
-
end
|
45
|
-
|
46
|
-
if with_config('sqlcipher')
|
47
|
-
pkg_config("sqlcipher")
|
48
|
-
else
|
49
|
-
pkg_config("sqlite3")
|
50
|
-
end
|
51
|
-
|
52
|
-
# --with-sqlite3-{dir,include,lib}
|
53
|
-
if with_config('sqlcipher')
|
54
|
-
$CFLAGS << ' -DUSING_SQLCIPHER'
|
55
|
-
dir_config("sqlcipher", cppflags, ldflags)
|
56
|
-
else
|
57
|
-
dir_config("sqlite3", cppflags, ldflags)
|
58
|
-
end
|
59
|
-
|
60
|
-
if RbConfig::CONFIG["host_os"] =~ /mswin/
|
61
|
-
$CFLAGS << ' -W3'
|
62
|
-
end
|
63
|
-
|
64
|
-
if RUBY_VERSION < '2.7'
|
65
|
-
$CFLAGS << ' -DTAINTING_SUPPORT'
|
66
|
-
end
|
67
|
-
|
68
|
-
def asplode missing
|
69
|
-
if RUBY_PLATFORM =~ /mingw|mswin/
|
70
|
-
abort "#{missing} is missing. Install SQLite3 from " +
|
71
|
-
"http://www.sqlite.org/ first."
|
72
|
-
else
|
73
|
-
abort <<-error
|
74
|
-
#{missing} is missing. Try 'brew install sqlite3',
|
75
|
-
'yum install sqlite-devel' or 'apt-get install libsqlite3-dev'
|
76
|
-
and check your shared library search path (the
|
77
|
-
location where your sqlite3 shared library is located).
|
78
|
-
error
|
79
|
-
end
|
80
|
-
end
|
81
|
-
|
82
|
-
asplode('sqlite3.h') unless find_header 'sqlite3.h'
|
83
|
-
find_library 'pthread', 'pthread_create' # 1.8 support. *shrug*
|
84
|
-
|
85
|
-
have_library 'dl' # for static builds
|
86
|
-
|
87
|
-
if with_config('sqlcipher')
|
88
|
-
asplode('sqlcipher') unless find_library 'sqlcipher', 'sqlite3_libversion_number'
|
89
|
-
else
|
90
|
-
asplode('sqlite3') unless find_library 'sqlite3', 'sqlite3_libversion_number'
|
91
|
-
end
|
92
|
-
|
93
|
-
# Functions defined in 1.9 but not 1.8
|
94
|
-
have_func('rb_proc_arity')
|
95
|
-
|
96
|
-
# Functions defined in 2.1 but not 2.0
|
97
|
-
have_func('rb_integer_pack')
|
98
|
-
|
99
|
-
# These functions may not be defined
|
100
|
-
have_func('sqlite3_initialize')
|
101
|
-
have_func('sqlite3_backup_init')
|
102
|
-
have_func('sqlite3_column_database_name')
|
103
|
-
have_func('sqlite3_enable_load_extension')
|
104
|
-
have_func('sqlite3_load_extension')
|
105
|
-
|
106
|
-
unless have_func('sqlite3_open_v2')
|
107
|
-
abort "Please use a newer version of SQLite3"
|
108
|
-
end
|
109
|
-
|
110
|
-
have_func('sqlite3_prepare_v2')
|
111
|
-
have_type('sqlite3_int64', 'sqlite3.h')
|
112
|
-
have_type('sqlite3_uint64', 'sqlite3.h')
|
5
|
+
$CFLAGS << " -Wno-undef"
|
6
|
+
$CFLAGS << " -Wno-discarded-qualifiers"
|
113
7
|
|
114
8
|
dir_config('extralite_ext')
|
115
|
-
create_makefile('extralite_ext')
|
9
|
+
create_makefile('extralite_ext')
|
data/ext/extralite/extralite.c
CHANGED
@@ -51,6 +51,15 @@ static VALUE Database_allocate(VALUE klass) {
|
|
51
51
|
} \
|
52
52
|
}
|
53
53
|
|
54
|
+
/* call-seq: sqlite3_version
|
55
|
+
*
|
56
|
+
* Returns the sqlite3 version used by Extralite.
|
57
|
+
*/
|
58
|
+
|
59
|
+
VALUE Extralite_sqlite3_version(VALUE self) {
|
60
|
+
return rb_str_new_cstr(sqlite3_version);
|
61
|
+
}
|
62
|
+
|
54
63
|
/* call-seq: initialize(path)
|
55
64
|
*
|
56
65
|
* Initializes a new SQLite database with the given path.
|
@@ -105,7 +114,7 @@ VALUE Database_closed_p(VALUE self) {
|
|
105
114
|
return db->sqlite3_db ? Qfalse : Qtrue;
|
106
115
|
}
|
107
116
|
|
108
|
-
inline VALUE get_column_value(sqlite3_stmt *stmt, int col, int type) {
|
117
|
+
static inline VALUE get_column_value(sqlite3_stmt *stmt, int col, int type) {
|
109
118
|
switch (type) {
|
110
119
|
case SQLITE_NULL:
|
111
120
|
return Qnil;
|
@@ -116,7 +125,7 @@ inline VALUE get_column_value(sqlite3_stmt *stmt, int col, int type) {
|
|
116
125
|
case SQLITE_TEXT:
|
117
126
|
return rb_str_new_cstr((char *)sqlite3_column_text(stmt, col));
|
118
127
|
case SQLITE_BLOB:
|
119
|
-
|
128
|
+
return rb_str_new((const char *)sqlite3_column_blob(stmt, col), (long)sqlite3_column_bytes(stmt, col));
|
120
129
|
default:
|
121
130
|
rb_raise(cError, "Unknown column type: %d", type);
|
122
131
|
}
|
@@ -128,24 +137,24 @@ static void bind_parameter_value(sqlite3_stmt *stmt, int pos, VALUE value);
|
|
128
137
|
|
129
138
|
static inline void bind_hash_parameter_values(sqlite3_stmt *stmt, VALUE hash) {
|
130
139
|
VALUE keys = rb_funcall(hash, ID_KEYS, 0);
|
131
|
-
|
132
|
-
for (
|
140
|
+
long len = RARRAY_LEN(keys);
|
141
|
+
for (long i = 0; i < len; i++) {
|
133
142
|
VALUE k = RARRAY_AREF(keys, i);
|
134
143
|
VALUE v = rb_hash_aref(hash, k);
|
135
144
|
|
136
145
|
switch (TYPE(k)) {
|
137
146
|
case T_FIXNUM:
|
138
147
|
bind_parameter_value(stmt, NUM2INT(k), v);
|
139
|
-
|
148
|
+
break;
|
140
149
|
case T_SYMBOL:
|
141
150
|
k = rb_funcall(k, ID_TO_S, 0);
|
142
151
|
case T_STRING:
|
143
152
|
if(RSTRING_PTR(k)[0] != ':') k = rb_str_plus(rb_str_new2(":"), k);
|
144
153
|
int pos = sqlite3_bind_parameter_index(stmt, StringValuePtr(k));
|
145
154
|
bind_parameter_value(stmt, pos, v);
|
146
|
-
|
155
|
+
break;
|
147
156
|
default:
|
148
|
-
|
157
|
+
rb_raise(cError, "Cannot bind hash key value idx %ld", i);
|
149
158
|
}
|
150
159
|
}
|
151
160
|
RB_GC_GUARD(keys);
|
@@ -218,7 +227,7 @@ struct multi_stmt_ctx {
|
|
218
227
|
sqlite3 *db;
|
219
228
|
sqlite3_stmt **stmt;
|
220
229
|
const char *str;
|
221
|
-
|
230
|
+
long len;
|
222
231
|
int rc;
|
223
232
|
};
|
224
233
|
|
@@ -256,7 +265,7 @@ statements. It will release the GVL while the statements are being prepared and
|
|
256
265
|
executed. All statements excluding the last one are executed. The last statement
|
257
266
|
is not executed, but instead handed back to the caller for looping over results.
|
258
267
|
*/
|
259
|
-
inline void prepare_multi_stmt(sqlite3 *db, sqlite3_stmt **stmt, VALUE sql) {
|
268
|
+
static inline void prepare_multi_stmt(sqlite3 *db, sqlite3_stmt **stmt, VALUE sql) {
|
260
269
|
struct multi_stmt_ctx ctx = {db, stmt, RSTRING_PTR(sql), RSTRING_LEN(sql), 0};
|
261
270
|
rb_thread_call_without_gvl(prepare_multi_stmt_without_gvl, (void *)&ctx, RUBY_UBF_IO, 0);
|
262
271
|
RB_GC_GUARD(sql);
|
@@ -284,7 +293,7 @@ void *stmt_iterate_without_gvl(void *ptr) {
|
|
284
293
|
return NULL;
|
285
294
|
}
|
286
295
|
|
287
|
-
inline int stmt_iterate(sqlite3_stmt *stmt, sqlite3 *db) {
|
296
|
+
static inline int stmt_iterate(sqlite3_stmt *stmt, sqlite3 *db) {
|
288
297
|
struct step_ctx ctx = {stmt, 0};
|
289
298
|
rb_thread_call_without_gvl(stmt_iterate_without_gvl, (void *)&ctx, RUBY_UBF_IO, 0);
|
290
299
|
switch (ctx.rc) {
|
@@ -658,6 +667,8 @@ VALUE Database_load_extension(VALUE self, VALUE path) {
|
|
658
667
|
|
659
668
|
void Init_Extralite() {
|
660
669
|
VALUE mExtralite = rb_define_module("Extralite");
|
670
|
+
rb_define_singleton_method(mExtralite, "sqlite3_version", Extralite_sqlite3_version, 0);
|
671
|
+
|
661
672
|
VALUE cDatabase = rb_define_class_under(mExtralite, "Database", rb_cObject);
|
662
673
|
rb_define_alloc_func(cDatabase, Database_allocate);
|
663
674
|
|