extralite 1.8.2 → 1.12
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 +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
|
|