extralite 1.1 → 1.5
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 +5 -1
- data/ext/extralite/extralite.c +62 -25
- data/extralite.gemspec +0 -1
- data/lib/extralite/version.rb +1 -1
- data/test/helper.rb +0 -5
- data/test/perf.rb +48 -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: ad8337aebac5ef75132340d8336389fe0c927ed454b5792d57f80efa4f362664
|
4
|
+
data.tar.gz: 602ad3648312e810398fac215353b0fac5aa916ff7a3a7901ba1990c9e3fb145
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a5831721c27d2eb27c8433bcd286ba826861946b7894eb2c1564f449d321d2eaeec7cce13a4166b10db799f9e9e391bc2ba65961edc7b144592d1994767e69b0
|
7
|
+
data.tar.gz: ce77ee584ec89bae2819a2c0384ca7cc08daf00f405d0bf379f322d6182b3bf55bceb0ac0136d52fddd3fe5a43e4e48eb23cbfc00eef74a0439ce25eeeb529f7
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,20 @@
|
|
1
|
+
## 1.5 2021-12-13
|
2
|
+
|
3
|
+
- Release GVL while preparing statements
|
4
|
+
- Use `sqlite3_prepare_v2` instead of deprecated `sqlite_prepare`
|
5
|
+
|
6
|
+
## 1.4 2021-08-25
|
7
|
+
|
8
|
+
- Fix possible segfault in cleanup_stmt
|
9
|
+
|
10
|
+
## 1.3 2021-08-17
|
11
|
+
|
12
|
+
- Pin error classes (for better compatibility with `GC.compact`)
|
13
|
+
|
14
|
+
## 1.2 2021-06-06
|
15
|
+
|
16
|
+
- Add support for big integers
|
17
|
+
|
1
18
|
## 1.1 2021-06-02
|
2
19
|
|
3
20
|
- Add `#close`, `#closed?` methods
|
data/Gemfile.lock
CHANGED
@@ -1,24 +1,17 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
extralite (1.
|
4
|
+
extralite (1.5)
|
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,7 +6,7 @@
|
|
6
6
|
|
7
7
|
## What is Extralite?
|
8
8
|
|
9
|
-
Extralite is an extra-lightweight (
|
9
|
+
Extralite is an extra-lightweight (less than 400 lines of C-code) SQLite3 wrapper for
|
10
10
|
Ruby. It provides a single class with a minimal set of methods to interact with
|
11
11
|
an SQLite3 database.
|
12
12
|
|
@@ -71,6 +71,10 @@ db.filename #=> "/tmp/my.db"
|
|
71
71
|
|
72
72
|
# load an extension
|
73
73
|
db.load_extension('/path/to/extension.so')
|
74
|
+
|
75
|
+
# close database
|
76
|
+
db.close
|
77
|
+
db.closed? #=> true
|
74
78
|
```
|
75
79
|
|
76
80
|
## Why not just use the sqlite3 gem?
|
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;
|
@@ -94,7 +95,7 @@ inline VALUE get_column_value(sqlite3_stmt *stmt, int col, int type) {
|
|
94
95
|
case SQLITE_NULL:
|
95
96
|
return Qnil;
|
96
97
|
case SQLITE_INTEGER:
|
97
|
-
return
|
98
|
+
return LL2NUM(sqlite3_column_int64(stmt, col));
|
98
99
|
case SQLITE_FLOAT:
|
99
100
|
return DBL2NUM(sqlite3_column_double(stmt, col));
|
100
101
|
case SQLITE_TEXT:
|
@@ -109,12 +110,12 @@ 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;
|
116
117
|
case T_FIXNUM:
|
117
|
-
|
118
|
+
sqlite3_bind_int64(stmt, pos, NUM2LL(value));
|
118
119
|
return;
|
119
120
|
case T_FLOAT:
|
120
121
|
sqlite3_bind_double(stmt, pos, NUM2DBL(value));
|
@@ -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,29 +169,62 @@ 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;
|
204
|
+
}
|
205
|
+
return NULL;
|
206
|
+
}
|
207
|
+
|
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);
|
194
228
|
}
|
195
229
|
}
|
196
230
|
|
@@ -207,7 +241,7 @@ inline int stmt_iterate(sqlite3_stmt *stmt, sqlite3 *db) {
|
|
207
241
|
case SQLITE_ERROR:
|
208
242
|
rb_raise(cSQLError, "%s", sqlite3_errmsg(db));
|
209
243
|
default:
|
210
|
-
rb_raise(cError, "Invalid return code for sqlite3_step: %d", rc);
|
244
|
+
rb_raise(cError, "Invalid return code for sqlite3_step: %d (please open an issue on https://github.com/digital-fabric/extralite)", rc);
|
211
245
|
}
|
212
246
|
|
213
247
|
return 0;
|
@@ -222,7 +256,7 @@ typedef struct query_ctx {
|
|
222
256
|
|
223
257
|
VALUE cleanup_stmt(VALUE arg) {
|
224
258
|
query_ctx *ctx = (query_ctx *)arg;
|
225
|
-
sqlite3_finalize(ctx->stmt);
|
259
|
+
if (ctx->stmt) sqlite3_finalize(ctx->stmt);
|
226
260
|
return Qnil;
|
227
261
|
}
|
228
262
|
|
@@ -292,7 +326,7 @@ VALUE safe_query_ary(VALUE arg) {
|
|
292
326
|
row = row_to_ary(ctx->stmt, column_count);
|
293
327
|
if (yield_to_block) rb_yield(row); else rb_ary_push(result, row);
|
294
328
|
}
|
295
|
-
|
329
|
+
|
296
330
|
RB_GC_GUARD(row);
|
297
331
|
RB_GC_GUARD(result);
|
298
332
|
return result;
|
@@ -453,14 +487,14 @@ void Init_Extralite() {
|
|
453
487
|
rb_define_method(cDatabase, "initialize", Database_initialize, 1);
|
454
488
|
rb_define_method(cDatabase, "close", Database_close, 0);
|
455
489
|
rb_define_method(cDatabase, "closed?", Database_closed_p, 0);
|
456
|
-
|
490
|
+
|
457
491
|
rb_define_method(cDatabase, "query", Database_query_hash, -1);
|
458
492
|
rb_define_method(cDatabase, "query_hash", Database_query_hash, -1);
|
459
493
|
rb_define_method(cDatabase, "query_ary", Database_query_ary, -1);
|
460
494
|
rb_define_method(cDatabase, "query_single_row", Database_query_single_row, -1);
|
461
495
|
rb_define_method(cDatabase, "query_single_column", Database_query_single_column, -1);
|
462
496
|
rb_define_method(cDatabase, "query_single_value", Database_query_single_value, -1);
|
463
|
-
|
497
|
+
|
464
498
|
rb_define_method(cDatabase, "last_insert_rowid", Database_last_insert_rowid, 0);
|
465
499
|
rb_define_method(cDatabase, "changes", Database_changes, 0);
|
466
500
|
rb_define_method(cDatabase, "filename", Database_filename, -1);
|
@@ -470,6 +504,9 @@ void Init_Extralite() {
|
|
470
504
|
cError = rb_define_class_under(mExtralite, "Error", rb_eRuntimeError);
|
471
505
|
cSQLError = rb_define_class_under(mExtralite, "SQLError", cError);
|
472
506
|
cBusyError = rb_define_class_under(mExtralite, "BusyError", cError);
|
507
|
+
rb_gc_register_mark_object(cError);
|
508
|
+
rb_gc_register_mark_object(cSQLError);
|
509
|
+
rb_gc_register_mark_object(cBusyError);
|
473
510
|
|
474
511
|
ID_STRIP = rb_intern("strip");
|
475
512
|
}
|
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,48 @@
|
|
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
|
+
COUNT = 10000
|
17
|
+
|
18
|
+
def prepare_database
|
19
|
+
FileUtils.rm(DB_PATH) rescue nil
|
20
|
+
db = Extralite::Database.new(DB_PATH)
|
21
|
+
db.query('create table foo ( a integer primary key, b text )')
|
22
|
+
db.query('begin')
|
23
|
+
COUNT.times { db.query('insert into foo (b) values (?)', "hello#{rand(1000)}" )}
|
24
|
+
db.query('commit')
|
25
|
+
end
|
26
|
+
|
27
|
+
def sqlite3_run
|
28
|
+
db = SQLite3::Database.new(DB_PATH, :results_as_hash => true)
|
29
|
+
results = db.execute('select * from foo')
|
30
|
+
raise unless results.size == COUNT
|
31
|
+
end
|
32
|
+
|
33
|
+
def extralite_run
|
34
|
+
db = Extralite::Database.new(DB_PATH)
|
35
|
+
results = db.query('select * from foo')
|
36
|
+
raise unless results.size == COUNT
|
37
|
+
end
|
38
|
+
|
39
|
+
prepare_database
|
40
|
+
|
41
|
+
Benchmark.ips do |x|
|
42
|
+
x.config(:time => 3, :warmup => 1)
|
43
|
+
|
44
|
+
x.report("sqlite3") { sqlite3_run }
|
45
|
+
x.report("extralite") { extralite_run }
|
46
|
+
|
47
|
+
x.compare!
|
48
|
+
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.5'
|
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
|