extralite 1.2 → 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 +17 -0
- data/Gemfile.lock +1 -9
- data/README.md +57 -17
- data/ext/extralite/extralite.c +74 -26
- data/extralite.gemspec +0 -1
- data/lib/extralite/version.rb +1 -1
- data/test/helper.rb +0 -5
- data/test/perf.rb +51 -0
- data/test/test_database.rb +82 -3
- metadata +4 -17
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
@@ -1,3 +1,20 @@
|
|
1
|
+
## 1.6 2021-12-13
|
2
|
+
|
3
|
+
- Release GVL while fetching rows
|
4
|
+
|
5
|
+
## 1.5 2021-12-13
|
6
|
+
|
7
|
+
- Release GVL while preparing statements
|
8
|
+
- Use `sqlite3_prepare_v2` instead of deprecated `sqlite_prepare`
|
9
|
+
|
10
|
+
## 1.4 2021-08-25
|
11
|
+
|
12
|
+
- Fix possible segfault in cleanup_stmt
|
13
|
+
|
14
|
+
## 1.3 2021-08-17
|
15
|
+
|
16
|
+
- Pin error classes (for better compatibility with `GC.compact`)
|
17
|
+
|
1
18
|
## 1.2 2021-06-06
|
2
19
|
|
3
20
|
- Add support for big integers
|
data/Gemfile.lock
CHANGED
@@ -1,24 +1,17 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
extralite (1.
|
4
|
+
extralite (1.6)
|
5
5
|
|
6
6
|
GEM
|
7
7
|
remote: https://rubygems.org/
|
8
8
|
specs:
|
9
|
-
ansi (1.5.0)
|
10
9
|
ast (2.4.2)
|
11
|
-
builder (3.2.4)
|
12
10
|
coderay (1.1.3)
|
13
11
|
docile (1.4.0)
|
14
12
|
json (2.5.1)
|
15
13
|
method_source (1.0.0)
|
16
14
|
minitest (5.14.4)
|
17
|
-
minitest-reporters (1.4.2)
|
18
|
-
ansi
|
19
|
-
builder
|
20
|
-
minitest (>= 5.0)
|
21
|
-
ruby-progressbar
|
22
15
|
parallel (1.20.1)
|
23
16
|
parser (3.0.1.1)
|
24
17
|
ast (~> 2.4.1)
|
@@ -56,7 +49,6 @@ PLATFORMS
|
|
56
49
|
DEPENDENCIES
|
57
50
|
extralite!
|
58
51
|
minitest (= 5.14.4)
|
59
|
-
minitest-reporters (= 1.4.2)
|
60
52
|
pry (= 0.13.1)
|
61
53
|
rake-compiler (= 1.1.1)
|
62
54
|
rubocop (= 0.85.1)
|
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
@@ -1,5 +1,6 @@
|
|
1
1
|
#include <stdio.h>
|
2
2
|
#include "ruby.h"
|
3
|
+
#include "ruby/thread.h"
|
3
4
|
#include <sqlite3.h>
|
4
5
|
|
5
6
|
VALUE cError;
|
@@ -109,7 +110,7 @@ inline VALUE get_column_value(sqlite3_stmt *stmt, int col, int type) {
|
|
109
110
|
}
|
110
111
|
|
111
112
|
static inline void bind_parameter_value(sqlite3_stmt *stmt, int pos, VALUE value) {
|
112
|
-
switch (TYPE(value)) {
|
113
|
+
switch (TYPE(value)) {
|
113
114
|
case T_NIL:
|
114
115
|
sqlite3_bind_null(stmt, pos);
|
115
116
|
return;
|
@@ -143,7 +144,7 @@ static inline void bind_all_parameters(sqlite3_stmt *stmt, int argc, VALUE *argv
|
|
143
144
|
|
144
145
|
static inline VALUE get_column_names(sqlite3_stmt *stmt, int column_count) {
|
145
146
|
VALUE arr = rb_ary_new2(column_count);
|
146
|
-
for (int i = 0; i < column_count; i++) {
|
147
|
+
for (int i = 0; i < column_count; i++) {
|
147
148
|
VALUE name = ID2SYM(rb_intern(sqlite3_column_name(stmt, i)));
|
148
149
|
rb_ary_push(arr, name);
|
149
150
|
}
|
@@ -168,36 +169,80 @@ static inline VALUE row_to_ary(sqlite3_stmt *stmt, int column_count) {
|
|
168
169
|
return row;
|
169
170
|
}
|
170
171
|
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
const char *
|
172
|
+
struct multi_stmt_ctx {
|
173
|
+
sqlite3 *db;
|
174
|
+
sqlite3_stmt **stmt;
|
175
|
+
const char *str;
|
176
|
+
int len;
|
177
|
+
int rc;
|
178
|
+
};
|
179
|
+
|
180
|
+
void *prepare_multi_stmt_without_gvl(void *ptr) {
|
181
|
+
struct multi_stmt_ctx *ctx = (struct multi_stmt_ctx *)ptr;
|
182
|
+
const char *rest = NULL;
|
183
|
+
const char *str = ctx->str;
|
184
|
+
const char *end = ctx->str + ctx->len;
|
175
185
|
while (1) {
|
176
|
-
|
177
|
-
if (rc) {
|
178
|
-
sqlite3_finalize(*stmt);
|
179
|
-
|
186
|
+
ctx->rc = sqlite3_prepare_v2(ctx->db, str, end - str, ctx->stmt, &rest);
|
187
|
+
if (ctx->rc) {
|
188
|
+
sqlite3_finalize(*ctx->stmt);
|
189
|
+
return NULL;
|
180
190
|
}
|
181
191
|
|
182
|
-
if (rest == end) return;
|
183
|
-
|
192
|
+
if (rest == end) return NULL;
|
193
|
+
|
184
194
|
// perform current query, but discard its results
|
185
|
-
rc = sqlite3_step(*stmt);
|
186
|
-
sqlite3_finalize(*stmt);
|
187
|
-
switch (rc) {
|
195
|
+
ctx->rc = sqlite3_step(*ctx->stmt);
|
196
|
+
sqlite3_finalize(*ctx->stmt);
|
197
|
+
switch (ctx->rc) {
|
188
198
|
case SQLITE_BUSY:
|
189
|
-
rb_raise(cBusyError, "Database is busy");
|
190
199
|
case SQLITE_ERROR:
|
191
|
-
|
200
|
+
case SQLITE_MISUSE:
|
201
|
+
return NULL;
|
192
202
|
}
|
193
|
-
|
203
|
+
str = rest;
|
194
204
|
}
|
205
|
+
return NULL;
|
195
206
|
}
|
196
207
|
|
197
|
-
|
208
|
+
/*
|
209
|
+
This function prepares a statement from an SQL string containing one or more SQL
|
210
|
+
statements. It will release the GVL while the statements are being prepared and
|
211
|
+
executed. All statements excluding the last one are executed. The last statement
|
212
|
+
is not executed, but instead handed back to the caller for looping over results.
|
213
|
+
*/
|
214
|
+
inline void prepare_multi_stmt(sqlite3 *db, sqlite3_stmt **stmt, VALUE sql) {
|
215
|
+
struct multi_stmt_ctx ctx = {db, stmt, RSTRING_PTR(sql), RSTRING_LEN(sql), 0};
|
216
|
+
rb_thread_call_without_gvl(prepare_multi_stmt_without_gvl, (void *)&ctx, RUBY_UBF_IO, 0);
|
217
|
+
RB_GC_GUARD(sql);
|
218
|
+
|
219
|
+
switch (ctx.rc) {
|
220
|
+
case 0:
|
221
|
+
return;
|
222
|
+
case SQLITE_BUSY:
|
223
|
+
rb_raise(cBusyError, "Database is busy");
|
224
|
+
case SQLITE_ERROR:
|
225
|
+
rb_raise(cSQLError, "%s", sqlite3_errmsg(db));
|
226
|
+
default:
|
227
|
+
rb_raise(cError, "Invalid return code for prepare_multi_stmt_without_gvl: %d (please open an issue on https://github.com/digital-fabric/extralite)", ctx.rc);
|
228
|
+
}
|
229
|
+
}
|
230
|
+
|
231
|
+
struct step_ctx {
|
232
|
+
sqlite3_stmt *stmt;
|
198
233
|
int rc;
|
199
|
-
|
200
|
-
|
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) {
|
201
246
|
case SQLITE_ROW:
|
202
247
|
return 1;
|
203
248
|
case SQLITE_DONE:
|
@@ -207,7 +252,7 @@ inline int stmt_iterate(sqlite3_stmt *stmt, sqlite3 *db) {
|
|
207
252
|
case SQLITE_ERROR:
|
208
253
|
rb_raise(cSQLError, "%s", sqlite3_errmsg(db));
|
209
254
|
default:
|
210
|
-
rb_raise(cError, "Invalid return code for sqlite3_step: %d", 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);
|
211
256
|
}
|
212
257
|
|
213
258
|
return 0;
|
@@ -222,7 +267,7 @@ typedef struct query_ctx {
|
|
222
267
|
|
223
268
|
VALUE cleanup_stmt(VALUE arg) {
|
224
269
|
query_ctx *ctx = (query_ctx *)arg;
|
225
|
-
sqlite3_finalize(ctx->stmt);
|
270
|
+
if (ctx->stmt) sqlite3_finalize(ctx->stmt);
|
226
271
|
return Qnil;
|
227
272
|
}
|
228
273
|
|
@@ -292,7 +337,7 @@ VALUE safe_query_ary(VALUE arg) {
|
|
292
337
|
row = row_to_ary(ctx->stmt, column_count);
|
293
338
|
if (yield_to_block) rb_yield(row); else rb_ary_push(result, row);
|
294
339
|
}
|
295
|
-
|
340
|
+
|
296
341
|
RB_GC_GUARD(row);
|
297
342
|
RB_GC_GUARD(result);
|
298
343
|
return result;
|
@@ -453,14 +498,14 @@ void Init_Extralite() {
|
|
453
498
|
rb_define_method(cDatabase, "initialize", Database_initialize, 1);
|
454
499
|
rb_define_method(cDatabase, "close", Database_close, 0);
|
455
500
|
rb_define_method(cDatabase, "closed?", Database_closed_p, 0);
|
456
|
-
|
501
|
+
|
457
502
|
rb_define_method(cDatabase, "query", Database_query_hash, -1);
|
458
503
|
rb_define_method(cDatabase, "query_hash", Database_query_hash, -1);
|
459
504
|
rb_define_method(cDatabase, "query_ary", Database_query_ary, -1);
|
460
505
|
rb_define_method(cDatabase, "query_single_row", Database_query_single_row, -1);
|
461
506
|
rb_define_method(cDatabase, "query_single_column", Database_query_single_column, -1);
|
462
507
|
rb_define_method(cDatabase, "query_single_value", Database_query_single_value, -1);
|
463
|
-
|
508
|
+
|
464
509
|
rb_define_method(cDatabase, "last_insert_rowid", Database_last_insert_rowid, 0);
|
465
510
|
rb_define_method(cDatabase, "changes", Database_changes, 0);
|
466
511
|
rb_define_method(cDatabase, "filename", Database_filename, -1);
|
@@ -470,6 +515,9 @@ void Init_Extralite() {
|
|
470
515
|
cError = rb_define_class_under(mExtralite, "Error", rb_eRuntimeError);
|
471
516
|
cSQLError = rb_define_class_under(mExtralite, "SQLError", cError);
|
472
517
|
cBusyError = rb_define_class_under(mExtralite, "BusyError", cError);
|
518
|
+
rb_gc_register_mark_object(cError);
|
519
|
+
rb_gc_register_mark_object(cSQLError);
|
520
|
+
rb_gc_register_mark_object(cBusyError);
|
473
521
|
|
474
522
|
ID_STRIP = rb_intern("strip");
|
475
523
|
}
|
data/extralite.gemspec
CHANGED
@@ -23,7 +23,6 @@ Gem::Specification.new do |s|
|
|
23
23
|
|
24
24
|
s.add_development_dependency 'rake-compiler', '1.1.1'
|
25
25
|
s.add_development_dependency 'minitest', '5.14.4'
|
26
|
-
s.add_development_dependency 'minitest-reporters', '1.4.2'
|
27
26
|
s.add_development_dependency 'simplecov', '0.17.1'
|
28
27
|
s.add_development_dependency 'rubocop', '0.85.1'
|
29
28
|
s.add_development_dependency 'pry', '0.13.1'
|
data/lib/extralite/version.rb
CHANGED
data/test/helper.rb
CHANGED
data/test/perf.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, :results_as_hash => true)
|
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('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
|
data/test/test_database.rb
CHANGED
@@ -4,7 +4,7 @@ require_relative 'helper'
|
|
4
4
|
|
5
5
|
class DatabaseTest < MiniTest::Test
|
6
6
|
def setup
|
7
|
-
@db = Extralite::Database.new('
|
7
|
+
@db = Extralite::Database.new(':memory:')
|
8
8
|
@db.query('create table if not exists t (x,y,z)')
|
9
9
|
@db.query('delete from t')
|
10
10
|
@db.query('insert into t values (1, 2, 3)')
|
@@ -55,7 +55,7 @@ class DatabaseTest < MiniTest::Test
|
|
55
55
|
def test_query_single_column
|
56
56
|
r = @db.query_single_column('select y from t')
|
57
57
|
assert_equal [2, 5], r
|
58
|
-
|
58
|
+
|
59
59
|
r = @db.query_single_column('select y from t where x = 2')
|
60
60
|
assert_equal [], r
|
61
61
|
end
|
@@ -82,6 +82,17 @@ end
|
|
82
82
|
assert_equal [1, 4, 'a', 'd'], @db.query_single_column('select x from t order by x')
|
83
83
|
end
|
84
84
|
|
85
|
+
def test_multiple_statements_with_error
|
86
|
+
error = nil
|
87
|
+
begin
|
88
|
+
@db.query("insert into t values foo; insert into t values ('d', 'e', 'f');")
|
89
|
+
rescue => error
|
90
|
+
end
|
91
|
+
|
92
|
+
assert_kind_of Extralite::SQLError, error
|
93
|
+
assert_equal 'near "foo": syntax error', error.message
|
94
|
+
end
|
95
|
+
|
85
96
|
def test_empty_sql
|
86
97
|
r = @db.query(' ')
|
87
98
|
assert_nil r
|
@@ -97,7 +108,75 @@ end
|
|
97
108
|
|
98
109
|
assert_equal @db, @db.close
|
99
110
|
assert_equal true, @db.closed?
|
100
|
-
|
111
|
+
|
101
112
|
assert_raises(Extralite::Error) { @db.query_single_value('select 42') }
|
102
113
|
end
|
103
114
|
end
|
115
|
+
|
116
|
+
class ScenarioTest < MiniTest::Test
|
117
|
+
def setup
|
118
|
+
@db = Extralite::Database.new('/tmp/extralite.db')
|
119
|
+
@db.query('create table if not exists t (x,y,z)')
|
120
|
+
@db.query('delete from t')
|
121
|
+
@db.query('insert into t values (1, 2, 3)')
|
122
|
+
@db.query('insert into t values (4, 5, 6)')
|
123
|
+
end
|
124
|
+
|
125
|
+
def test_concurrent_transactions
|
126
|
+
done = false
|
127
|
+
t = Thread.new do
|
128
|
+
db = Extralite::Database.new('/tmp/extralite.db')
|
129
|
+
db.query 'begin immediate'
|
130
|
+
sleep 0.01 until done
|
131
|
+
|
132
|
+
while true
|
133
|
+
begin
|
134
|
+
db.query 'commit'
|
135
|
+
break
|
136
|
+
rescue Extralite::BusyError
|
137
|
+
sleep 0.01
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
sleep 0.1
|
143
|
+
@db.query 'begin deferred'
|
144
|
+
result = @db.query_single_column('select x from t')
|
145
|
+
assert_equal [1, 4], result
|
146
|
+
|
147
|
+
assert_raises(Extralite::BusyError) do
|
148
|
+
@db.query('insert into t values (7, 8, 9)')
|
149
|
+
end
|
150
|
+
|
151
|
+
done = true
|
152
|
+
sleep 0.1
|
153
|
+
|
154
|
+
assert_raises(Extralite::BusyError) do
|
155
|
+
@db.query('insert into t values (7, 8, 9)')
|
156
|
+
end
|
157
|
+
|
158
|
+
assert_equal true, @db.transaction_active?
|
159
|
+
|
160
|
+
# the thing to do in this case is to commit the read transaction, allowing
|
161
|
+
# the other thread to commit its write transaction, and then we can
|
162
|
+
# "upgrade" to a write transaction
|
163
|
+
|
164
|
+
@db.query('commit')
|
165
|
+
|
166
|
+
while true
|
167
|
+
begin
|
168
|
+
@db.query('begin immediate')
|
169
|
+
break
|
170
|
+
rescue Extralite::BusyError
|
171
|
+
sleep 0.1
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
@db.query('insert into t values (7, 8, 9)')
|
176
|
+
@db.query('commit')
|
177
|
+
|
178
|
+
result = @db.query_single_column('select x from t')
|
179
|
+
assert_equal [1, 4, 7], result
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
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.6'
|
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-
|
11
|
+
date: 2021-12-13 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rake-compiler
|
@@ -38,20 +38,6 @@ dependencies:
|
|
38
38
|
- - '='
|
39
39
|
- !ruby/object:Gem::Version
|
40
40
|
version: 5.14.4
|
41
|
-
- !ruby/object:Gem::Dependency
|
42
|
-
name: minitest-reporters
|
43
|
-
requirement: !ruby/object:Gem::Requirement
|
44
|
-
requirements:
|
45
|
-
- - '='
|
46
|
-
- !ruby/object:Gem::Version
|
47
|
-
version: 1.4.2
|
48
|
-
type: :development
|
49
|
-
prerelease: false
|
50
|
-
version_requirements: !ruby/object:Gem::Requirement
|
51
|
-
requirements:
|
52
|
-
- - '='
|
53
|
-
- !ruby/object:Gem::Version
|
54
|
-
version: 1.4.2
|
55
41
|
- !ruby/object:Gem::Dependency
|
56
42
|
name: simplecov
|
57
43
|
requirement: !ruby/object:Gem::Requirement
|
@@ -118,6 +104,7 @@ files:
|
|
118
104
|
- lib/extralite.rb
|
119
105
|
- lib/extralite/version.rb
|
120
106
|
- test/helper.rb
|
107
|
+
- test/perf.rb
|
121
108
|
- test/test_database.rb
|
122
109
|
homepage: https://github.com/digital-fabric/extralite
|
123
110
|
licenses:
|
@@ -146,7 +133,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
146
133
|
- !ruby/object:Gem::Version
|
147
134
|
version: '0'
|
148
135
|
requirements: []
|
149
|
-
rubygems_version: 3.1.
|
136
|
+
rubygems_version: 3.1.6
|
150
137
|
signing_key:
|
151
138
|
specification_version: 4
|
152
139
|
summary: Extra-lightweight SQLite3 wrapper for Ruby
|