extralite 1.5 → 1.6
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/CHANGELOG.md +4 -0
- data/Gemfile.lock +1 -1
- data/README.md +57 -17
- data/ext/extralite/extralite.c +15 -4
- data/lib/extralite/version.rb +1 -1
- data/test/perf.rb +16 -13
- metadata +1 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e2924788672c7c3960a5baeaeacf14d297cfe461cfb1d20db828d7a67b5536c8
|
4
|
+
data.tar.gz: fb4da5551c3b1ddca95ef05520eee477bfb7a29383b9c7e5f5c0638392eba3e9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 63b73c0d05cb294b75d79bef5c1362e2c422f16b0e7ae76c3d2b577af20f612007a4fe272942958b38d545efabe4d975c1a0475ee8dec4a68e957d61800ffca7
|
7
|
+
data.tar.gz: b79c50cb5c696cf1eb505bbd126b197f1160f56af70e669cfc432e2e16d5423848c139ff9e942fb26c06c3d15f94e1a43f22ab4c317dae4fb0c2290f8d431aa6
|
data/CHANGELOG.md
CHANGED
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -6,18 +6,23 @@
|
|
6
6
|
|
7
7
|
## What is Extralite?
|
8
8
|
|
9
|
-
Extralite is an extra-lightweight (less than
|
10
|
-
Ruby. It provides a single class with a minimal set of methods to
|
11
|
-
an SQLite3 database.
|
9
|
+
Extralite is an extra-lightweight (less than 430 lines of C-code) SQLite3
|
10
|
+
wrapper for Ruby. It provides a single class with a minimal set of methods to
|
11
|
+
interact with an SQLite3 database.
|
12
12
|
|
13
13
|
## Features
|
14
14
|
|
15
|
-
- A variety of methods for different data access patterns:
|
16
|
-
|
15
|
+
- A variety of methods for different data access patterns: rows as hashes, rows
|
16
|
+
as arrays, single row, single column, single value.
|
17
|
+
- Super fast - [up to 12.5x faster](#performance) than the
|
18
|
+
[sqlite3](https://github.com/sparklemotion/sqlite3-ruby) gem (see also
|
19
|
+
[comparison](#why-not-just-use-the-sqlite3-gem).)
|
20
|
+
- Improved [concurrency](#concurrency) for multithreaded apps: the Ruby GVL is
|
21
|
+
released while preparing SQL statements and while iterating over results.
|
17
22
|
- Iterate over records with a block, or collect records into an array.
|
18
23
|
- Parameter binding.
|
19
|
-
-
|
20
|
-
creating/modifying schemas).
|
24
|
+
- Automatically execute SQL strings containing multiple semicolon-separated
|
25
|
+
queries (handy for creating/modifying schemas).
|
21
26
|
- Get last insert rowid.
|
22
27
|
- Get number of rows changed by last query.
|
23
28
|
- Load extensions (loading of extensions is autmatically enabled. You can find
|
@@ -80,20 +85,55 @@ db.closed? #=> true
|
|
80
85
|
## Why not just use the sqlite3 gem?
|
81
86
|
|
82
87
|
The sqlite3-ruby gem is a popular, solid, well-maintained project, used by
|
83
|
-
thousands of developers. I've been doing a lot of work with SQLite3
|
84
|
-
wanted to have a simpler API that gives me query results in a
|
85
|
-
Thus extralite was born.
|
88
|
+
thousands of developers. I've been doing a lot of work with SQLite3 databases
|
89
|
+
lately, and wanted to have a simpler API that gives me query results in a
|
90
|
+
variety of ways. Thus extralite was born.
|
91
|
+
|
92
|
+
Here's a table summarizing the differences between the two gems:
|
93
|
+
|
94
|
+
| |sqlite3-ruby|Extralite|
|
95
|
+
|-|-|-|
|
96
|
+
|API design|multiple classes|single class|
|
97
|
+
|Query results|row as hash, row as array, single row, single value|row as hash, row as array, single column, single row, single value|
|
98
|
+
|execute multiple statements|separate API (#execute_batch)|integrated|
|
99
|
+
|custom functions in Ruby|yes|no|
|
100
|
+
|custom collations|yes|no|
|
101
|
+
|custom aggregate functions|yes|no|
|
102
|
+
|Multithread friendly|no|[yes](#concurrency)|
|
103
|
+
|Code size|~2650LoC|~500LoC|
|
104
|
+
|Performance|1x|1.5x to 12.5x (see [below](#performance))|
|
86
105
|
|
87
106
|
## What about concurrency?
|
88
107
|
|
89
|
-
Extralite
|
90
|
-
|
91
|
-
|
108
|
+
Extralite releases the GVL while making blocking calls to the sqlite3 library,
|
109
|
+
that is while preparing SQL statements and fetching rows. Releasing the GVL
|
110
|
+
allows other threads to run while the sqlite3 library is busy compiling SQL into
|
111
|
+
bytecode, or fetching the next row. This does not seem to hurt Extralite's
|
112
|
+
performance:
|
92
113
|
|
93
|
-
|
94
|
-
|
114
|
+
## Performance
|
115
|
+
|
116
|
+
A benchmark script is
|
117
|
+
[included](https://github.com/digital-fabric/extralite/blob/main/test/perf.rb),
|
118
|
+
creating a table of various row counts, then fetching the entire table using
|
119
|
+
either `sqlite3` or `extralite`. This benchmark shows Extralite to be up to 12.5
|
120
|
+
times faster than `sqlite3` when fetching a large number of rows. Here are the
|
121
|
+
results (using the `sqlite3` gem performance as baseline):
|
122
|
+
|
123
|
+
|Row count|sqlite3-ruby (baseline)|Extralite (relative - rounded)|
|
124
|
+
|-:|-:|-:|
|
125
|
+
|10|1x|1.5x|
|
126
|
+
|1K|1x|7x|
|
127
|
+
|100K|1x|12.5x|
|
128
|
+
|
129
|
+
(If you're interested in checking this yourself, just run the script and let me
|
130
|
+
know if your results are different.)
|
95
131
|
|
96
132
|
## Can I use it with an ORM like ActiveRecord or Sequel?
|
97
133
|
|
98
|
-
Not yet, but you are welcome to contribute adapters for those projects.
|
99
|
-
|
134
|
+
Not yet, but you are welcome to contribute adapters for those projects.
|
135
|
+
|
136
|
+
## Contributing
|
137
|
+
|
138
|
+
Contributions in the form of issues, PRs or comments will be greatly
|
139
|
+
appreciated!
|
data/ext/extralite/extralite.c
CHANGED
@@ -228,10 +228,21 @@ inline void prepare_multi_stmt(sqlite3 *db, sqlite3_stmt **stmt, VALUE sql) {
|
|
228
228
|
}
|
229
229
|
}
|
230
230
|
|
231
|
-
|
231
|
+
struct step_ctx {
|
232
|
+
sqlite3_stmt *stmt;
|
232
233
|
int rc;
|
233
|
-
|
234
|
-
|
234
|
+
};
|
235
|
+
|
236
|
+
void *stmt_iterate_without_gvl(void *ptr) {
|
237
|
+
struct step_ctx *ctx = (struct step_ctx *)ptr;
|
238
|
+
ctx->rc = sqlite3_step(ctx->stmt);
|
239
|
+
return NULL;
|
240
|
+
}
|
241
|
+
|
242
|
+
inline int stmt_iterate(sqlite3_stmt *stmt, sqlite3 *db) {
|
243
|
+
struct step_ctx ctx = {stmt, 0};
|
244
|
+
rb_thread_call_without_gvl(stmt_iterate_without_gvl, (void *)&ctx, RUBY_UBF_IO, 0);
|
245
|
+
switch (ctx.rc) {
|
235
246
|
case SQLITE_ROW:
|
236
247
|
return 1;
|
237
248
|
case SQLITE_DONE:
|
@@ -241,7 +252,7 @@ inline int stmt_iterate(sqlite3_stmt *stmt, sqlite3 *db) {
|
|
241
252
|
case SQLITE_ERROR:
|
242
253
|
rb_raise(cSQLError, "%s", sqlite3_errmsg(db));
|
243
254
|
default:
|
244
|
-
rb_raise(cError, "Invalid return code for sqlite3_step: %d (please open an issue on https://github.com/digital-fabric/extralite)", rc);
|
255
|
+
rb_raise(cError, "Invalid return code for sqlite3_step: %d (please open an issue on https://github.com/digital-fabric/extralite)", ctx.rc);
|
245
256
|
}
|
246
257
|
|
247
258
|
return 0;
|
data/lib/extralite/version.rb
CHANGED
data/test/perf.rb
CHANGED
@@ -13,36 +13,39 @@ require 'benchmark/ips'
|
|
13
13
|
require 'fileutils'
|
14
14
|
|
15
15
|
DB_PATH = '/tmp/extralite_sqlite3_perf.db'
|
16
|
-
COUNT = 10000
|
17
16
|
|
18
|
-
def prepare_database
|
17
|
+
def prepare_database(count)
|
19
18
|
FileUtils.rm(DB_PATH) rescue nil
|
20
19
|
db = Extralite::Database.new(DB_PATH)
|
21
20
|
db.query('create table foo ( a integer primary key, b text )')
|
22
21
|
db.query('begin')
|
23
|
-
|
22
|
+
count.times { db.query('insert into foo (b) values (?)', "hello#{rand(1000)}" )}
|
24
23
|
db.query('commit')
|
25
24
|
end
|
26
25
|
|
27
|
-
def sqlite3_run
|
26
|
+
def sqlite3_run(count)
|
28
27
|
db = SQLite3::Database.new(DB_PATH, :results_as_hash => true)
|
29
28
|
results = db.execute('select * from foo')
|
30
|
-
raise unless results.size ==
|
29
|
+
raise unless results.size == count
|
31
30
|
end
|
32
31
|
|
33
|
-
def extralite_run
|
32
|
+
def extralite_run(count)
|
34
33
|
db = Extralite::Database.new(DB_PATH)
|
35
34
|
results = db.query('select * from foo')
|
36
|
-
raise unless results.size ==
|
35
|
+
raise unless results.size == count
|
37
36
|
end
|
38
37
|
|
39
|
-
|
38
|
+
[10, 1000, 100000].each do |c|
|
39
|
+
puts; puts; puts "Record count: #{c}"
|
40
40
|
|
41
|
-
|
42
|
-
x.config(:time => 3, :warmup => 1)
|
41
|
+
prepare_database(c)
|
43
42
|
|
44
|
-
|
45
|
-
|
43
|
+
Benchmark.ips do |x|
|
44
|
+
x.config(:time => 3, :warmup => 1)
|
46
45
|
|
47
|
-
|
46
|
+
x.report("sqlite3") { sqlite3_run(c) }
|
47
|
+
x.report("extralite") { extralite_run(c) }
|
48
|
+
|
49
|
+
x.compare!
|
50
|
+
end
|
48
51
|
end
|