extralite 1.22 → 1.24
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/workflows/test.yml +1 -1
- data/CHANGELOG.md +19 -0
- data/Gemfile.lock +1 -1
- data/README.md +47 -40
- data/ext/extralite/common.c +6 -6
- data/ext/extralite/database.c +155 -18
- data/ext/extralite/extconf-bundle.rb +9 -2
- data/ext/extralite/extconf.rb +87 -109
- data/ext/extralite/extralite.h +10 -6
- data/ext/extralite/prepared_statement.c +5 -3
- data/lib/extralite/sqlite3_constants.rb +158 -0
- data/lib/extralite/version.rb +1 -1
- data/lib/extralite.rb +3 -38
- data/test/test_database.rb +118 -2
- data/test/test_prepared_statement.rb +11 -0
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: bbe4f33eced375588c90c6f0e2c3dd5b25ca19e44e3a9b4a20f21e43d7a833ff
|
4
|
+
data.tar.gz: 709a92b6edd0b54531652d3c572b5dcac21e8db09b6c516c8f07332bb4daa6ee
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b1c8f1cfc27bfe888f6c92be36f00631dda67471f937c7d94d5b731c94b90fd3b83f322891186f1cc9baf792dafe2df02cd1dc09f8cef7e4030bce79c048f9c1
|
7
|
+
data.tar.gz: 6e4de82d209255d623bc090b29b5d71da1922a6d70d5b3879a475ace8acb77de8b75ee688c51b45b71c9e9b66c56e71bc341da6377366baf66388cbc261f65b5
|
data/.github/workflows/test.yml
CHANGED
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,22 @@
|
|
1
|
+
# 1.24 2023-02-02
|
2
|
+
|
3
|
+
- Fix closing database with open statements
|
4
|
+
- Improve error reporting in `Database#initialize`
|
5
|
+
- Fix `extralite-bundle` gem compilation
|
6
|
+
- Improve error handling, add methods for error information
|
7
|
+
- Use extended result codes
|
8
|
+
- Add `Database#errcode`
|
9
|
+
- Add `Database#errmsg`
|
10
|
+
- Add `Database#error_offset`
|
11
|
+
|
12
|
+
# 1.23 2023-01-26
|
13
|
+
|
14
|
+
- Add `Database#trace` (#21)
|
15
|
+
- Add `Database#total_changes` (#20)
|
16
|
+
- Add `Database#busy_timeout=` (#19)
|
17
|
+
- Add `Database#limit` (#16)
|
18
|
+
- Improve error handling
|
19
|
+
|
1
20
|
# 1.22 2023-01-23
|
2
21
|
|
3
22
|
- Improve documentation (#17)
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
# Extralite -
|
1
|
+
# Extralite - a Super Fast Ruby Gem for Working with SQLite3 Databases
|
2
2
|
|
3
3
|
* Source code: https://github.com/digital-fabric/extralite
|
4
4
|
* Documentation: http://www.rubydoc.info/gems/extralite
|
@@ -19,7 +19,7 @@ latest features and enhancements.
|
|
19
19
|
|
20
20
|
## Features
|
21
21
|
|
22
|
-
- Super fast - [up to
|
22
|
+
- Super fast - [up to 11x faster](#performance) than the
|
23
23
|
[sqlite3](https://github.com/sparklemotion/sqlite3-ruby) gem (see also
|
24
24
|
[comparison](#why-not-just-use-the-sqlite3-gem).)
|
25
25
|
- A variety of methods for different data access patterns: rows as hashes, rows
|
@@ -48,7 +48,7 @@ gem 'extralite'
|
|
48
48
|
|
49
49
|
You can also run `gem install extralite` if you just want to check it out.
|
50
50
|
|
51
|
-
### Installing the Extralite-SQLite3
|
51
|
+
### Installing the Extralite-SQLite3 Bundle
|
52
52
|
|
53
53
|
If you don't have sqlite3 installed on your system, do not want to use the
|
54
54
|
system-installed version of SQLite3, or would like to use the latest version of
|
@@ -145,9 +145,9 @@ db.close
|
|
145
145
|
db.closed? #=> true
|
146
146
|
```
|
147
147
|
|
148
|
-
## More
|
148
|
+
## More Features
|
149
149
|
|
150
|
-
### Interrupting
|
150
|
+
### Interrupting Long-running Queries
|
151
151
|
|
152
152
|
When running long-running queries, you can use `Database#interrupt` to interrupt
|
153
153
|
the query:
|
@@ -168,7 +168,7 @@ ensure
|
|
168
168
|
end
|
169
169
|
```
|
170
170
|
|
171
|
-
### Creating
|
171
|
+
### Creating Backups
|
172
172
|
|
173
173
|
You can use `Database#backup` to create backup copies of a database. The
|
174
174
|
`#backup` method takes either a filename or a database instance:
|
@@ -191,7 +191,7 @@ db.backup('backup.db') do |remaining, total|
|
|
191
191
|
end
|
192
192
|
```
|
193
193
|
|
194
|
-
###
|
194
|
+
### Retrieving Status Information
|
195
195
|
|
196
196
|
Extralite provides methods for retrieving status information about the sqlite
|
197
197
|
runtime, database-specific status and prepared statement-specific status,
|
@@ -217,6 +217,42 @@ current, high_watermark = db.status(Extralite::SQLITE_DBSTATUS_CACHE_USED)
|
|
217
217
|
value = stmt.status(Extralite::SQLITE_STMTSTATUS_RUN)
|
218
218
|
```
|
219
219
|
|
220
|
+
### Working with Database Limits
|
221
|
+
|
222
|
+
The `Database#limit` can be used to get and set various database limits, as
|
223
|
+
[discussed in the SQLite docs](https://www.sqlite.org/limits.html):
|
224
|
+
|
225
|
+
```ruby
|
226
|
+
# get limit
|
227
|
+
value = db.limit(Extralite::SQLITE_LIMIT_ATTACHED)
|
228
|
+
|
229
|
+
# set limit
|
230
|
+
db.limit(Extralite::SQLITE_LIMIT_ATTACHED, new_value)
|
231
|
+
```
|
232
|
+
|
233
|
+
### Setting the Busy Timeout
|
234
|
+
|
235
|
+
When accessing a database concurrently it can be handy to set a busy timeout, in
|
236
|
+
order to not have to deal with rescuing `Extralite::BusyError` exceptions. The
|
237
|
+
timeout is given in seconds:
|
238
|
+
|
239
|
+
```ruby
|
240
|
+
db.busy_timeout = 5
|
241
|
+
```
|
242
|
+
|
243
|
+
### Tracing SQL Statements
|
244
|
+
|
245
|
+
To trace all SQL statements executed on the database, pass a block to
|
246
|
+
`Database#trace`. To disable tracing, call `Database#trace` without a block:
|
247
|
+
|
248
|
+
```ruby
|
249
|
+
# enable tracing
|
250
|
+
db.trace { |sql| puts sql: sql }
|
251
|
+
|
252
|
+
# disable tracing
|
253
|
+
db.trace
|
254
|
+
```
|
255
|
+
|
220
256
|
## Usage with Sequel
|
221
257
|
|
222
258
|
Extralite includes an adapter for
|
@@ -231,35 +267,6 @@ p articles.to_a
|
|
231
267
|
|
232
268
|
(Make sure you include `extralite` as a dependency in your `Gemfile`.)
|
233
269
|
|
234
|
-
## Why not just use the sqlite3 gem?
|
235
|
-
|
236
|
-
The [sqlite3](https://github.com/sparklemotion/sqlite3-ruby) gem is a
|
237
|
-
popular, solid, well-maintained project, used by thousands of developers. I've
|
238
|
-
been doing a lot of work with SQLite3 databases lately, and wanted to have a
|
239
|
-
simpler API that gives me query results in a variety of ways. Thus extralite was
|
240
|
-
born.
|
241
|
-
|
242
|
-
Extralite is significantly [faster](#performance) than the `sqlite3` gem and is
|
243
|
-
also [thread-friendly](#concurrency). On the other hand, Extralite does not have
|
244
|
-
support for defining custom functions, aggregates and collations. If you're
|
245
|
-
using any of those features, you'll have to stick to the `sqlite3` gem.
|
246
|
-
|
247
|
-
Here's a table summarizing the differences between the two gems:
|
248
|
-
|
249
|
-
| |sqlite3 1.6.0|Extralite 1.21|
|
250
|
-
|-|-|-|
|
251
|
-
|SQLite3 dependency|depends on OS-installed libsqlite3|Use either system sqlite3 or [bundled latest version of SQLite3](#installing-the-extralite-sqlite3-bundle)|
|
252
|
-
|API design|multiple classes|single class|
|
253
|
-
|Query results|row as hash, row as array, single row, single value|row as hash, row as array, __single column__, single row, single value|
|
254
|
-
|Execute multiple statements|separate API (#execute_batch)|integrated|
|
255
|
-
|Prepared statements|yes|yes|
|
256
|
-
|custom functions in Ruby|yes|no|
|
257
|
-
|custom collations|yes|no|
|
258
|
-
|custom aggregate functions|yes|no|
|
259
|
-
|Multithread friendly|no|[yes](#concurrency)|
|
260
|
-
|Code size|~2650LoC|~1300LoC|
|
261
|
-
|Performance|1x|1.5x to 10x (see [below](#performance))|
|
262
|
-
|
263
270
|
## Concurrency
|
264
271
|
|
265
272
|
Extralite releases the GVL while making blocking calls to the sqlite3 library,
|
@@ -272,10 +279,10 @@ performance, as you can see:
|
|
272
279
|
|
273
280
|
A benchmark script is included, creating a table of various row counts, then
|
274
281
|
fetching the entire table using either `sqlite3` or `extralite`. This benchmark
|
275
|
-
shows Extralite to be up to ~
|
282
|
+
shows Extralite to be up to ~11 times faster than `sqlite3` when fetching a
|
276
283
|
large number of rows.
|
277
284
|
|
278
|
-
### Rows as
|
285
|
+
### Rows as Hashes
|
279
286
|
|
280
287
|
[Benchmark source code](https://github.com/digital-fabric/extralite/blob/main/test/perf_hash.rb)
|
281
288
|
|
@@ -285,7 +292,7 @@ large number of rows.
|
|
285
292
|
|1K|299.2K rows/s|1.983M rows/s|__6.63x__|
|
286
293
|
|100K|185.4K rows/s|2.033M rows/s|__10.97x__|
|
287
294
|
|
288
|
-
### Rows as
|
295
|
+
### Rows as Arrays
|
289
296
|
|
290
297
|
[Benchmark source code](https://github.com/digital-fabric/extralite/blob/main/test/perf_ary.rb)
|
291
298
|
|
@@ -295,7 +302,7 @@ large number of rows.
|
|
295
302
|
|1K|502.1K rows/s|2.065M rows/s|__4.11x__|
|
296
303
|
|100K|455.7K rows/s|2.511M rows/s|__5.51x__|
|
297
304
|
|
298
|
-
### Prepared
|
305
|
+
### Prepared Statements
|
299
306
|
|
300
307
|
[Benchmark source code](https://github.com/digital-fabric/extralite/blob/main/test/perf_prepared.rb)
|
301
308
|
|
data/ext/extralite/common.c
CHANGED
@@ -23,7 +23,7 @@ static inline VALUE get_column_value(sqlite3_stmt *stmt, int col, int type) {
|
|
23
23
|
void bind_parameter_value(sqlite3_stmt *stmt, int pos, VALUE value);
|
24
24
|
|
25
25
|
void bind_hash_parameter_values(sqlite3_stmt *stmt, VALUE hash) {
|
26
|
-
VALUE keys = rb_funcall(hash,
|
26
|
+
VALUE keys = rb_funcall(hash, ID_keys, 0);
|
27
27
|
long len = RARRAY_LEN(keys);
|
28
28
|
for (long i = 0; i < len; i++) {
|
29
29
|
VALUE k = RARRAY_AREF(keys, i);
|
@@ -34,7 +34,7 @@ void bind_hash_parameter_values(sqlite3_stmt *stmt, VALUE hash) {
|
|
34
34
|
bind_parameter_value(stmt, FIX2INT(k), v);
|
35
35
|
break;
|
36
36
|
case T_SYMBOL:
|
37
|
-
k = rb_funcall(k,
|
37
|
+
k = rb_funcall(k, ID_to_s, 0);
|
38
38
|
case T_STRING:
|
39
39
|
if(RSTRING_PTR(k)[0] != ':') k = rb_str_plus(rb_str_new2(":"), k);
|
40
40
|
int pos = sqlite3_bind_parameter_index(stmt, StringValuePtr(k));
|
@@ -174,7 +174,7 @@ void prepare_multi_stmt(sqlite3 *db, sqlite3_stmt **stmt, VALUE sql) {
|
|
174
174
|
case SQLITE_ERROR:
|
175
175
|
rb_raise(cSQLError, "%s", sqlite3_errmsg(db));
|
176
176
|
default:
|
177
|
-
rb_raise(cError, "
|
177
|
+
rb_raise(cError, "%s", sqlite3_errmsg(db));
|
178
178
|
}
|
179
179
|
}
|
180
180
|
|
@@ -216,9 +216,9 @@ void prepare_single_stmt(sqlite3 *db, sqlite3_stmt **stmt, VALUE sql) {
|
|
216
216
|
case SQLITE_ERROR:
|
217
217
|
rb_raise(cSQLError, "%s", sqlite3_errmsg(db));
|
218
218
|
case SQLITE_MULTI_STMT:
|
219
|
-
rb_raise(
|
219
|
+
rb_raise(cError, "A prepared statement does not accept SQL strings with multiple queries");
|
220
220
|
default:
|
221
|
-
rb_raise(cError, "
|
221
|
+
rb_raise(cError, "%s", sqlite3_errmsg(db));
|
222
222
|
}
|
223
223
|
}
|
224
224
|
|
@@ -248,7 +248,7 @@ int stmt_iterate(sqlite3_stmt *stmt, sqlite3 *db) {
|
|
248
248
|
case SQLITE_ERROR:
|
249
249
|
rb_raise(cSQLError, "%s", sqlite3_errmsg(db));
|
250
250
|
default:
|
251
|
-
rb_raise(cError, "
|
251
|
+
rb_raise(cError, "%s", sqlite3_errmsg(db));
|
252
252
|
}
|
253
253
|
|
254
254
|
return 0;
|
data/ext/extralite/database.c
CHANGED
@@ -7,10 +7,11 @@ VALUE cSQLError;
|
|
7
7
|
VALUE cBusyError;
|
8
8
|
VALUE cInterruptError;
|
9
9
|
|
10
|
-
ID
|
11
|
-
ID
|
12
|
-
ID
|
13
|
-
ID
|
10
|
+
ID ID_call;
|
11
|
+
ID ID_keys;
|
12
|
+
ID ID_new;
|
13
|
+
ID ID_strip;
|
14
|
+
ID ID_to_s;
|
14
15
|
|
15
16
|
static size_t Database_size(const void *ptr) {
|
16
17
|
return sizeof(Database_t);
|
@@ -18,7 +19,7 @@ static size_t Database_size(const void *ptr) {
|
|
18
19
|
|
19
20
|
static void Database_free(void *ptr) {
|
20
21
|
Database_t *db = ptr;
|
21
|
-
if (db->sqlite3_db)
|
22
|
+
if (db->sqlite3_db) sqlite3_close_v2(db->sqlite3_db);
|
22
23
|
free(ptr);
|
23
24
|
}
|
24
25
|
|
@@ -45,6 +46,12 @@ static VALUE Database_allocate(VALUE klass) {
|
|
45
46
|
} \
|
46
47
|
}
|
47
48
|
|
49
|
+
Database_t *Database_struct(VALUE self) {
|
50
|
+
Database_t *db;
|
51
|
+
GetDatabase(self, db);
|
52
|
+
return db;
|
53
|
+
}
|
54
|
+
|
48
55
|
sqlite3 *Database_sqlite3_db(VALUE self) {
|
49
56
|
Database_t *db;
|
50
57
|
GetDatabase(self, db);
|
@@ -74,18 +81,27 @@ VALUE Database_initialize(VALUE self, VALUE path) {
|
|
74
81
|
|
75
82
|
rc = sqlite3_open(StringValueCStr(path), &db->sqlite3_db);
|
76
83
|
if (rc) {
|
77
|
-
|
84
|
+
sqlite3_close_v2(db->sqlite3_db);
|
85
|
+
rb_raise(cError, "%s", sqlite3_errstr(rc));
|
86
|
+
}
|
87
|
+
|
88
|
+
// Enable extended result codes
|
89
|
+
rc = sqlite3_extended_result_codes(db->sqlite3_db, 1);
|
90
|
+
if (rc) {
|
91
|
+
sqlite3_close_v2(db->sqlite3_db);
|
78
92
|
rb_raise(cError, "%s", sqlite3_errmsg(db->sqlite3_db));
|
79
93
|
}
|
80
94
|
|
81
95
|
#ifdef HAVE_SQLITE3_ENABLE_LOAD_EXTENSION
|
82
96
|
rc = sqlite3_enable_load_extension(db->sqlite3_db, 1);
|
83
97
|
if (rc) {
|
84
|
-
|
98
|
+
sqlite3_close_v2(db->sqlite3_db);
|
85
99
|
rb_raise(cError, "%s", sqlite3_errmsg(db->sqlite3_db));
|
86
100
|
}
|
87
101
|
#endif
|
88
102
|
|
103
|
+
db->trace_block = Qnil;
|
104
|
+
|
89
105
|
return Qnil;
|
90
106
|
}
|
91
107
|
|
@@ -99,7 +115,7 @@ VALUE Database_close(VALUE self) {
|
|
99
115
|
Database_t *db;
|
100
116
|
GetDatabase(self, db);
|
101
117
|
|
102
|
-
rc =
|
118
|
+
rc = sqlite3_close_v2(db->sqlite3_db);
|
103
119
|
if (rc) {
|
104
120
|
rb_raise(cError, "%s", sqlite3_errmsg(db->sqlite3_db));
|
105
121
|
}
|
@@ -129,12 +145,15 @@ static inline VALUE Database_perform_query(int argc, VALUE *argv, VALUE self, VA
|
|
129
145
|
|
130
146
|
// extract query from args
|
131
147
|
rb_check_arity(argc, 1, UNLIMITED_ARGUMENTS);
|
132
|
-
sql = rb_funcall(argv[0],
|
148
|
+
sql = rb_funcall(argv[0], ID_strip, 0);
|
133
149
|
if (RSTRING_LEN(sql) == 0) return Qnil;
|
134
150
|
|
135
151
|
// prepare query ctx
|
136
152
|
GetOpenDatabase(self, db);
|
153
|
+
if (db->trace_block != Qnil) rb_funcall(db->trace_block, ID_call, 1, sql);
|
137
154
|
prepare_multi_stmt(db->sqlite3_db, &stmt, sql);
|
155
|
+
RB_GC_GUARD(sql);
|
156
|
+
|
138
157
|
bind_all_parameters(stmt, argc - 1, argv + 1);
|
139
158
|
query_ctx ctx = { self, db->sqlite3_db, stmt };
|
140
159
|
|
@@ -384,7 +403,7 @@ VALUE Database_load_extension(VALUE self, VALUE path) {
|
|
384
403
|
* Creates a prepared statement with the given SQL query.
|
385
404
|
*/
|
386
405
|
VALUE Database_prepare(VALUE self, VALUE sql) {
|
387
|
-
return rb_funcall(cPreparedStatement,
|
406
|
+
return rb_funcall(cPreparedStatement, ID_new, 2, self, sql);
|
388
407
|
}
|
389
408
|
|
390
409
|
/* call-seq:
|
@@ -466,7 +485,7 @@ VALUE backup_cleanup(VALUE ptr) {
|
|
466
485
|
sqlite3_backup_finish(ctx->backup);
|
467
486
|
|
468
487
|
if (ctx->close_dst_on_cleanup)
|
469
|
-
|
488
|
+
sqlite3_close_v2(ctx->dst);
|
470
489
|
return Qnil;
|
471
490
|
}
|
472
491
|
|
@@ -498,7 +517,7 @@ VALUE Database_backup(int argc, VALUE *argv, VALUE self) {
|
|
498
517
|
if (dst_is_fn) {
|
499
518
|
int rc = sqlite3_open(StringValueCStr(dst), &dst_db);
|
500
519
|
if (rc) {
|
501
|
-
|
520
|
+
sqlite3_close_v2(dst_db);
|
502
521
|
rb_raise(cError, "%s", sqlite3_errmsg(dst_db));
|
503
522
|
}
|
504
523
|
}
|
@@ -514,7 +533,7 @@ VALUE Database_backup(int argc, VALUE *argv, VALUE self) {
|
|
514
533
|
backup = sqlite3_backup_init(dst_db, StringValueCStr(dst_name), src->sqlite3_db, StringValueCStr(src_name));
|
515
534
|
if (!backup) {
|
516
535
|
if (dst_is_fn)
|
517
|
-
|
536
|
+
sqlite3_close_v2(dst_db);
|
518
537
|
rb_raise(cError, "%s", sqlite3_errmsg(dst_db));
|
519
538
|
}
|
520
539
|
|
@@ -550,7 +569,7 @@ VALUE Extralite_runtime_status(int argc, VALUE* argv, VALUE self) {
|
|
550
569
|
* current value and the high water mark value. To reset the high water mark,
|
551
570
|
* pass true as reset.
|
552
571
|
*/
|
553
|
-
VALUE Database_status(int argc, VALUE*
|
572
|
+
VALUE Database_status(int argc, VALUE *argv, VALUE self) {
|
554
573
|
VALUE op, reset;
|
555
574
|
int cur, hwm;
|
556
575
|
|
@@ -565,6 +584,112 @@ VALUE Database_status(int argc, VALUE* argv, VALUE self) {
|
|
565
584
|
return rb_ary_new3(2, INT2NUM(cur), INT2NUM(hwm));
|
566
585
|
}
|
567
586
|
|
587
|
+
/* call-seq:
|
588
|
+
* db.limit(category) -> value
|
589
|
+
* db.limit(category, new_value) -> prev_value
|
590
|
+
*
|
591
|
+
* Returns the current limit for the given category. If a new value is given,
|
592
|
+
* sets the limit to the new value and returns the previous value.
|
593
|
+
*/
|
594
|
+
VALUE Database_limit(int argc, VALUE *argv, VALUE self) {
|
595
|
+
VALUE category, new_value;
|
596
|
+
|
597
|
+
rb_scan_args(argc, argv, "11", &category, &new_value);
|
598
|
+
|
599
|
+
Database_t *db;
|
600
|
+
GetOpenDatabase(self, db);
|
601
|
+
|
602
|
+
int value = sqlite3_limit(db->sqlite3_db, NUM2INT(category), RTEST(new_value) ? NUM2INT(new_value) : -1);
|
603
|
+
|
604
|
+
if (value == -1) rb_raise(cError, "Invalid limit category");
|
605
|
+
|
606
|
+
return INT2NUM(value);
|
607
|
+
}
|
608
|
+
|
609
|
+
/* call-seq:
|
610
|
+
* db.busy_timeout=(sec) -> db
|
611
|
+
* db.busy_timeout=nil -> db
|
612
|
+
*
|
613
|
+
* Sets the busy timeout for the database, in seconds or fractions thereof. To
|
614
|
+
* disable the busy timeout, set it to 0 or nil.
|
615
|
+
*/
|
616
|
+
VALUE Database_busy_timeout_set(VALUE self, VALUE sec) {
|
617
|
+
Database_t *db;
|
618
|
+
GetOpenDatabase(self, db);
|
619
|
+
|
620
|
+
int ms = (sec == Qnil) ? 0 : (int)(NUM2DBL(sec) * 1000);
|
621
|
+
int rc = sqlite3_busy_timeout(db->sqlite3_db, ms);
|
622
|
+
if (rc != SQLITE_OK) rb_raise(cError, "Failed to set busy timeout");
|
623
|
+
|
624
|
+
return self;
|
625
|
+
}
|
626
|
+
|
627
|
+
/* call-seq:
|
628
|
+
* db.total_changes -> value
|
629
|
+
*
|
630
|
+
* Returns the total number of changes made to the database since opening it.
|
631
|
+
*/
|
632
|
+
VALUE Database_total_changes(VALUE self) {
|
633
|
+
Database_t *db;
|
634
|
+
GetOpenDatabase(self, db);
|
635
|
+
|
636
|
+
int value = sqlite3_total_changes(db->sqlite3_db);
|
637
|
+
return INT2NUM(value);
|
638
|
+
}
|
639
|
+
|
640
|
+
/* call-seq:
|
641
|
+
* db.trace { |sql| } -> db
|
642
|
+
* db.trace -> db
|
643
|
+
*
|
644
|
+
* Installs or removes a block that will be invoked for every SQL statement
|
645
|
+
* executed.
|
646
|
+
*/
|
647
|
+
VALUE Database_trace(VALUE self) {
|
648
|
+
Database_t *db;
|
649
|
+
GetOpenDatabase(self, db);
|
650
|
+
|
651
|
+
db->trace_block = rb_block_given_p() ? rb_block_proc() : Qnil;
|
652
|
+
return self;
|
653
|
+
}
|
654
|
+
|
655
|
+
/* call-seq:
|
656
|
+
* db.errcode -> errcode
|
657
|
+
*
|
658
|
+
* Returns the last error code for the database.
|
659
|
+
*/
|
660
|
+
VALUE Database_errcode(VALUE self) {
|
661
|
+
Database_t *db;
|
662
|
+
GetOpenDatabase(self, db);
|
663
|
+
|
664
|
+
return INT2NUM(sqlite3_errcode(db->sqlite3_db));
|
665
|
+
}
|
666
|
+
|
667
|
+
/* call-seq:
|
668
|
+
* db.errmsg -> errmsg
|
669
|
+
*
|
670
|
+
* Returns the last error message for the database.
|
671
|
+
*/
|
672
|
+
VALUE Database_errmsg(VALUE self) {
|
673
|
+
Database_t *db;
|
674
|
+
GetOpenDatabase(self, db);
|
675
|
+
|
676
|
+
return rb_str_new2(sqlite3_errmsg(db->sqlite3_db));
|
677
|
+
}
|
678
|
+
|
679
|
+
#ifdef HAVE_SQLITE3_ERROR_OFFSET
|
680
|
+
/* call-seq:
|
681
|
+
* db.error_offset -> ofs
|
682
|
+
*
|
683
|
+
* Returns the offset for the last error
|
684
|
+
*/
|
685
|
+
VALUE Database_error_offset(VALUE self) {
|
686
|
+
Database_t *db;
|
687
|
+
GetOpenDatabase(self, db);
|
688
|
+
|
689
|
+
return INT2NUM(sqlite3_error_offset(db->sqlite3_db));
|
690
|
+
}
|
691
|
+
#endif
|
692
|
+
|
568
693
|
void Init_ExtraliteDatabase(void) {
|
569
694
|
VALUE mExtralite = rb_define_module("Extralite");
|
570
695
|
rb_define_singleton_method(mExtralite, "runtime_status", Extralite_runtime_status, -1);
|
@@ -574,15 +699,24 @@ void Init_ExtraliteDatabase(void) {
|
|
574
699
|
rb_define_alloc_func(cDatabase, Database_allocate);
|
575
700
|
|
576
701
|
rb_define_method(cDatabase, "backup", Database_backup, -1);
|
702
|
+
rb_define_method(cDatabase, "busy_timeout=", Database_busy_timeout_set, 1);
|
577
703
|
rb_define_method(cDatabase, "changes", Database_changes, 0);
|
578
704
|
rb_define_method(cDatabase, "close", Database_close, 0);
|
579
705
|
rb_define_method(cDatabase, "closed?", Database_closed_p, 0);
|
580
706
|
rb_define_method(cDatabase, "columns", Database_columns, 1);
|
707
|
+
rb_define_method(cDatabase, "errcode", Database_errcode, 0);
|
708
|
+
rb_define_method(cDatabase, "errmsg", Database_errmsg, 0);
|
709
|
+
|
710
|
+
#ifdef HAVE_SQLITE3_ERROR_OFFSET
|
711
|
+
rb_define_method(cDatabase, "error_offset", Database_error_offset, 0);
|
712
|
+
#endif
|
713
|
+
|
581
714
|
rb_define_method(cDatabase, "execute_multi", Database_execute_multi, 2);
|
582
715
|
rb_define_method(cDatabase, "filename", Database_filename, -1);
|
583
716
|
rb_define_method(cDatabase, "initialize", Database_initialize, 1);
|
584
717
|
rb_define_method(cDatabase, "interrupt", Database_interrupt, 0);
|
585
718
|
rb_define_method(cDatabase, "last_insert_rowid", Database_last_insert_rowid, 0);
|
719
|
+
rb_define_method(cDatabase, "limit", Database_limit, -1);
|
586
720
|
rb_define_method(cDatabase, "prepare", Database_prepare, 1);
|
587
721
|
rb_define_method(cDatabase, "query", Database_query_hash, -1);
|
588
722
|
rb_define_method(cDatabase, "query_ary", Database_query_ary, -1);
|
@@ -591,6 +725,8 @@ void Init_ExtraliteDatabase(void) {
|
|
591
725
|
rb_define_method(cDatabase, "query_single_row", Database_query_single_row, -1);
|
592
726
|
rb_define_method(cDatabase, "query_single_value", Database_query_single_value, -1);
|
593
727
|
rb_define_method(cDatabase, "status", Database_status, -1);
|
728
|
+
rb_define_method(cDatabase, "total_changes", Database_total_changes, 0);
|
729
|
+
rb_define_method(cDatabase, "trace", Database_trace, 0);
|
594
730
|
rb_define_method(cDatabase, "transaction_active?", Database_transaction_active_p, 0);
|
595
731
|
|
596
732
|
#ifdef HAVE_SQLITE3_LOAD_EXTENSION
|
@@ -606,8 +742,9 @@ void Init_ExtraliteDatabase(void) {
|
|
606
742
|
rb_gc_register_mark_object(cBusyError);
|
607
743
|
rb_gc_register_mark_object(cInterruptError);
|
608
744
|
|
609
|
-
|
610
|
-
|
611
|
-
|
612
|
-
|
745
|
+
ID_call = rb_intern("call");
|
746
|
+
ID_keys = rb_intern("keys");
|
747
|
+
ID_new = rb_intern("new");
|
748
|
+
ID_strip = rb_intern("strip");
|
749
|
+
ID_to_s = rb_intern("to_s");
|
613
750
|
}
|
@@ -2,8 +2,15 @@
|
|
2
2
|
|
3
3
|
require 'mkmf'
|
4
4
|
|
5
|
-
$CFLAGS <<
|
6
|
-
$CFLAGS <<
|
5
|
+
$CFLAGS << ' -Wno-undef'
|
6
|
+
$CFLAGS << ' -Wno-discarded-qualifiers'
|
7
|
+
$CFLAGS << ' -Wno-unused-function'
|
8
|
+
|
9
|
+
$defs << "-DHAVE_SQLITE3_ENABLE_LOAD_EXTENSION"
|
10
|
+
$defs << "-DHAVE_SQLITE3_LOAD_EXTENSION"
|
11
|
+
$defs << "-DHAVE_SQLITE3_ERROR_OFFSET"
|
12
|
+
|
13
|
+
have_func('usleep')
|
7
14
|
|
8
15
|
dir_config('extralite_ext')
|
9
16
|
create_makefile('extralite_ext')
|
data/ext/extralite/extconf.rb
CHANGED
@@ -1,115 +1,93 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
#
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
#
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
cppflags = "#{brew_prefix}/include"
|
35
|
-
pkg_conf = "#{brew_prefix}/lib/pkgconfig"
|
3
|
+
if ENV['EXTRALITE_BUNDLE']
|
4
|
+
require_relative('extconf-bundle')
|
5
|
+
else
|
6
|
+
ENV['RC_ARCHS'] = '' if RUBY_PLATFORM =~ /darwin/
|
7
|
+
|
8
|
+
require 'mkmf'
|
9
|
+
|
10
|
+
# :stopdoc:
|
11
|
+
|
12
|
+
RbConfig::MAKEFILE_CONFIG['CC'] = ENV['CC'] if ENV['CC']
|
13
|
+
|
14
|
+
ldflags = cppflags = nil
|
15
|
+
if RbConfig::CONFIG["host_os"] =~ /darwin/
|
16
|
+
begin
|
17
|
+
if with_config('sqlcipher')
|
18
|
+
brew_prefix = `brew --prefix sqlcipher`.chomp
|
19
|
+
ldflags = "#{brew_prefix}/lib"
|
20
|
+
cppflags = "#{brew_prefix}/include/sqlcipher"
|
21
|
+
pkg_conf = "#{brew_prefix}/lib/pkgconfig"
|
22
|
+
else
|
23
|
+
brew_prefix = `brew --prefix sqlite3`.chomp
|
24
|
+
ldflags = "#{brew_prefix}/lib"
|
25
|
+
cppflags = "#{brew_prefix}/include"
|
26
|
+
pkg_conf = "#{brew_prefix}/lib/pkgconfig"
|
27
|
+
end
|
28
|
+
|
29
|
+
# pkg_config should be less error prone than parsing compiler
|
30
|
+
# commandline options, but we need to set default ldflags and cpp flags
|
31
|
+
# in case the user doesn't have pkg-config installed
|
32
|
+
ENV['PKG_CONFIG_PATH'] ||= pkg_conf
|
33
|
+
rescue
|
36
34
|
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
35
|
end
|
44
|
-
|
45
|
-
|
46
|
-
|
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."
|
36
|
+
|
37
|
+
if with_config('sqlcipher')
|
38
|
+
pkg_config("sqlcipher")
|
72
39
|
else
|
73
|
-
|
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
|
40
|
+
pkg_config("sqlite3")
|
79
41
|
end
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
end
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
#
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
42
|
+
|
43
|
+
# --with-sqlite3-{dir,include,lib}
|
44
|
+
if with_config('sqlcipher')
|
45
|
+
$CFLAGS << ' -DUSING_SQLCIPHER'
|
46
|
+
dir_config("sqlcipher", cppflags, ldflags)
|
47
|
+
else
|
48
|
+
dir_config("sqlite3", cppflags, ldflags)
|
49
|
+
end
|
50
|
+
|
51
|
+
if RbConfig::CONFIG["host_os"] =~ /mswin/
|
52
|
+
$CFLAGS << ' -W3'
|
53
|
+
end
|
54
|
+
|
55
|
+
if RUBY_VERSION < '2.7'
|
56
|
+
$CFLAGS << ' -DTAINTING_SUPPORT'
|
57
|
+
end
|
58
|
+
|
59
|
+
def asplode missing
|
60
|
+
if RUBY_PLATFORM =~ /mingw|mswin/
|
61
|
+
abort "#{missing} is missing. Install SQLite3 from " +
|
62
|
+
"http://www.sqlite.org/ first."
|
63
|
+
else
|
64
|
+
abort <<-error
|
65
|
+
#{missing} is missing. Try 'brew install sqlite3',
|
66
|
+
'yum install sqlite-devel' or 'apt-get install libsqlite3-dev'
|
67
|
+
and check your shared library search path (the
|
68
|
+
location where your sqlite3 shared library is located).
|
69
|
+
error
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
asplode('sqlite3.h') unless find_header 'sqlite3.h'
|
74
|
+
find_library 'pthread', 'pthread_create' # 1.8 support. *shrug*
|
75
|
+
|
76
|
+
have_library 'dl' # for static builds
|
77
|
+
|
78
|
+
if with_config('sqlcipher')
|
79
|
+
asplode('sqlcipher') unless find_library 'sqlcipher', 'sqlite3_libversion_number'
|
80
|
+
else
|
81
|
+
asplode('sqlite3') unless find_library 'sqlite3', 'sqlite3_libversion_number'
|
82
|
+
end
|
83
|
+
|
84
|
+
have_func('sqlite3_enable_load_extension')
|
85
|
+
have_func('sqlite3_load_extension')
|
86
|
+
have_func('sqlite3_prepare_v2')
|
87
|
+
have_func('sqlite3_error_offset')
|
88
|
+
|
89
|
+
$defs << "-DEXTRALITE_NO_BUNDLE"
|
90
|
+
|
91
|
+
dir_config('extralite_ext')
|
92
|
+
create_makefile('extralite_ext')
|
93
|
+
end
|
data/ext/extralite/extralite.h
CHANGED
@@ -27,18 +27,21 @@ extern VALUE cSQLError;
|
|
27
27
|
extern VALUE cBusyError;
|
28
28
|
extern VALUE cInterruptError;
|
29
29
|
|
30
|
-
extern ID
|
31
|
-
extern ID
|
32
|
-
extern ID
|
33
|
-
extern ID
|
30
|
+
extern ID ID_call;
|
31
|
+
extern ID ID_keys;
|
32
|
+
extern ID ID_new;
|
33
|
+
extern ID ID_strip;
|
34
|
+
extern ID ID_to_s;
|
34
35
|
|
35
36
|
typedef struct {
|
36
37
|
sqlite3 *sqlite3_db;
|
38
|
+
VALUE trace_block;
|
37
39
|
} Database_t;
|
38
40
|
|
39
41
|
typedef struct {
|
40
42
|
VALUE db;
|
41
43
|
VALUE sql;
|
44
|
+
Database_t *db_struct;
|
42
45
|
sqlite3 *sqlite3_db;
|
43
46
|
sqlite3_stmt *stmt;
|
44
47
|
} PreparedStatement_t;
|
@@ -56,13 +59,13 @@ typedef struct {
|
|
56
59
|
sqlite3_backup *p;
|
57
60
|
} backup_t;
|
58
61
|
|
62
|
+
VALUE safe_execute_multi(query_ctx *ctx);
|
59
63
|
VALUE safe_query_ary(query_ctx *ctx);
|
64
|
+
VALUE safe_query_columns(query_ctx *ctx);
|
60
65
|
VALUE safe_query_hash(query_ctx *ctx);
|
61
66
|
VALUE safe_query_single_column(query_ctx *ctx);
|
62
67
|
VALUE safe_query_single_row(query_ctx *ctx);
|
63
68
|
VALUE safe_query_single_value(query_ctx *ctx);
|
64
|
-
VALUE safe_execute_multi(query_ctx *ctx);
|
65
|
-
VALUE safe_query_columns(query_ctx *ctx);
|
66
69
|
|
67
70
|
void prepare_single_stmt(sqlite3 *db, sqlite3_stmt **stmt, VALUE sql);
|
68
71
|
void prepare_multi_stmt(sqlite3 *db, sqlite3_stmt **stmt, VALUE sql);
|
@@ -72,5 +75,6 @@ int stmt_iterate(sqlite3_stmt *stmt, sqlite3 *db);
|
|
72
75
|
VALUE cleanup_stmt(query_ctx *ctx);
|
73
76
|
|
74
77
|
sqlite3 *Database_sqlite3_db(VALUE self);
|
78
|
+
Database_t *Database_struct(VALUE self);
|
75
79
|
|
76
80
|
#endif /* EXTRALITE_H */
|
@@ -20,7 +20,7 @@ static void PreparedStatement_free(void *ptr) {
|
|
20
20
|
}
|
21
21
|
|
22
22
|
static const rb_data_type_t PreparedStatement_type = {
|
23
|
-
"
|
23
|
+
"PreparedStatement",
|
24
24
|
{PreparedStatement_mark, PreparedStatement_free, PreparedStatement_size,},
|
25
25
|
0, 0, RUBY_TYPED_FREE_IMMEDIATELY
|
26
26
|
};
|
@@ -41,15 +41,15 @@ static VALUE PreparedStatement_allocate(VALUE klass) {
|
|
41
41
|
* Initializes a new SQLite prepared statement with the given path.
|
42
42
|
*/
|
43
43
|
VALUE PreparedStatement_initialize(VALUE self, VALUE db, VALUE sql) {
|
44
|
-
// int rc;
|
45
44
|
PreparedStatement_t *stmt;
|
46
45
|
GetPreparedStatement(self, stmt);
|
47
46
|
|
48
|
-
sql = rb_funcall(sql,
|
47
|
+
sql = rb_funcall(sql, ID_strip, 0);
|
49
48
|
if (!RSTRING_LEN(sql))
|
50
49
|
rb_raise(cError, "Cannot prepare an empty SQL query");
|
51
50
|
|
52
51
|
stmt->db = db;
|
52
|
+
stmt->db_struct = Database_struct(db);
|
53
53
|
stmt->sqlite3_db = Database_sqlite3_db(db);
|
54
54
|
stmt->sql = sql;
|
55
55
|
|
@@ -65,6 +65,8 @@ static inline VALUE PreparedStatement_perform_query(int argc, VALUE *argv, VALUE
|
|
65
65
|
if (!stmt->stmt)
|
66
66
|
rb_raise(cError, "Prepared statement is closed");
|
67
67
|
|
68
|
+
if (stmt->db_struct->trace_block != Qnil) rb_funcall(stmt->db_struct->trace_block, ID_call, 1, stmt->sql);
|
69
|
+
|
68
70
|
sqlite3_reset(stmt->stmt);
|
69
71
|
sqlite3_clear_bindings(stmt->stmt);
|
70
72
|
bind_all_parameters(stmt->stmt, argc, argv);
|
@@ -0,0 +1,158 @@
|
|
1
|
+
module Extralite
|
2
|
+
|
3
|
+
SQLITE_STATUS_MEMORY_USED = 0
|
4
|
+
SQLITE_STATUS_PAGECACHE_USED = 1
|
5
|
+
SQLITE_STATUS_PAGECACHE_OVERFLOW = 2
|
6
|
+
SQLITE_STATUS_SCRATCH_USED = 3 # NOT USED
|
7
|
+
SQLITE_STATUS_SCRATCH_OVERFLOW = 4 # NOT USED
|
8
|
+
SQLITE_STATUS_MALLOC_SIZE = 5
|
9
|
+
SQLITE_STATUS_PARSER_STACK = 6
|
10
|
+
SQLITE_STATUS_PAGECACHE_SIZE = 7
|
11
|
+
SQLITE_STATUS_SCRATCH_SIZE = 8 # NOT USED
|
12
|
+
SQLITE_STATUS_MALLOC_COUNT = 9
|
13
|
+
|
14
|
+
SQLITE_DBSTATUS_LOOKASIDE_USED = 0
|
15
|
+
SQLITE_DBSTATUS_CACHE_USED = 1
|
16
|
+
SQLITE_DBSTATUS_SCHEMA_USED = 2
|
17
|
+
SQLITE_DBSTATUS_STMT_USED = 3
|
18
|
+
SQLITE_DBSTATUS_LOOKASIDE_HIT = 4
|
19
|
+
SQLITE_DBSTATUS_LOOKASIDE_MISS_SIZE = 5
|
20
|
+
SQLITE_DBSTATUS_LOOKASIDE_MISS_FULL = 6
|
21
|
+
SQLITE_DBSTATUS_CACHE_HIT = 7
|
22
|
+
SQLITE_DBSTATUS_CACHE_MISS = 8
|
23
|
+
SQLITE_DBSTATUS_CACHE_WRITE = 9
|
24
|
+
SQLITE_DBSTATUS_DEFERRED_FKS = 10
|
25
|
+
SQLITE_DBSTATUS_CACHE_USED_SHARED = 11
|
26
|
+
SQLITE_DBSTATUS_CACHE_SPILL = 12
|
27
|
+
|
28
|
+
SQLITE_STMTSTATUS_FULLSCAN_STEP = 1
|
29
|
+
SQLITE_STMTSTATUS_SORT = 2
|
30
|
+
SQLITE_STMTSTATUS_AUTOINDEX = 3
|
31
|
+
SQLITE_STMTSTATUS_VM_STEP = 4
|
32
|
+
SQLITE_STMTSTATUS_REPREPARE = 5
|
33
|
+
SQLITE_STMTSTATUS_RUN = 6
|
34
|
+
SQLITE_STMTSTATUS_FILTER_MISS = 7
|
35
|
+
SQLITE_STMTSTATUS_FILTER_HIT = 8
|
36
|
+
SQLITE_STMTSTATUS_MEMUSED = 99
|
37
|
+
|
38
|
+
SQLITE_LIMIT_LENGTH = 0
|
39
|
+
SQLITE_LIMIT_SQL_LENGTH = 1
|
40
|
+
SQLITE_LIMIT_COLUMN = 2
|
41
|
+
SQLITE_LIMIT_EXPR_DEPTH = 3
|
42
|
+
SQLITE_LIMIT_COMPOUND_SELECT = 4
|
43
|
+
SQLITE_LIMIT_VDBE_OP = 5
|
44
|
+
SQLITE_LIMIT_FUNCTION_ARG = 6
|
45
|
+
SQLITE_LIMIT_ATTACHED = 7
|
46
|
+
SQLITE_LIMIT_LIKE_PATTERN_LENGTH = 8
|
47
|
+
SQLITE_LIMIT_VARIABLE_NUMBER = 9
|
48
|
+
SQLITE_LIMIT_TRIGGER_DEPTH = 10
|
49
|
+
SQLITE_LIMIT_WORKER_THREADS = 11
|
50
|
+
|
51
|
+
SQLITE_OK = 0
|
52
|
+
SQLITE_ERROR = 1
|
53
|
+
SQLITE_INTERNAL = 2
|
54
|
+
SQLITE_PERM = 3
|
55
|
+
SQLITE_ABORT = 4
|
56
|
+
SQLITE_BUSY = 5
|
57
|
+
SQLITE_LOCKED = 6
|
58
|
+
SQLITE_NOMEM = 7
|
59
|
+
SQLITE_READONLY = 8
|
60
|
+
SQLITE_INTERRUPT = 9
|
61
|
+
SQLITE_IOERR = 10
|
62
|
+
SQLITE_CORRUPT = 11
|
63
|
+
SQLITE_NOTFOUND = 12
|
64
|
+
SQLITE_FULL = 13
|
65
|
+
SQLITE_CANTOPEN = 14
|
66
|
+
SQLITE_PROTOCOL = 15
|
67
|
+
SQLITE_EMPTY = 16
|
68
|
+
SQLITE_SCHEMA = 17
|
69
|
+
SQLITE_TOOBIG = 18
|
70
|
+
SQLITE_CONSTRAINT = 19
|
71
|
+
SQLITE_MISMATCH = 20
|
72
|
+
SQLITE_MISUSE = 21
|
73
|
+
SQLITE_NOLFS = 22
|
74
|
+
SQLITE_AUTH = 23
|
75
|
+
SQLITE_FORMAT = 24
|
76
|
+
SQLITE_RANGE = 25
|
77
|
+
SQLITE_NOTADB = 26
|
78
|
+
SQLITE_NOTICE = 27
|
79
|
+
SQLITE_WARNING = 28
|
80
|
+
SQLITE_ROW = 100
|
81
|
+
SQLITE_DONE = 101
|
82
|
+
|
83
|
+
SQLITE_ERROR_MISSING_COLLSEQ = (SQLITE_ERROR | (1<<8))
|
84
|
+
SQLITE_ERROR_RETRY = (SQLITE_ERROR | (2<<8))
|
85
|
+
SQLITE_ERROR_SNAPSHOT = (SQLITE_ERROR | (3<<8))
|
86
|
+
SQLITE_IOERR_READ = (SQLITE_IOERR | (1<<8))
|
87
|
+
SQLITE_IOERR_SHORT_READ = (SQLITE_IOERR | (2<<8))
|
88
|
+
SQLITE_IOERR_WRITE = (SQLITE_IOERR | (3<<8))
|
89
|
+
SQLITE_IOERR_FSYNC = (SQLITE_IOERR | (4<<8))
|
90
|
+
SQLITE_IOERR_DIR_FSYNC = (SQLITE_IOERR | (5<<8))
|
91
|
+
SQLITE_IOERR_TRUNCATE = (SQLITE_IOERR | (6<<8))
|
92
|
+
SQLITE_IOERR_FSTAT = (SQLITE_IOERR | (7<<8))
|
93
|
+
SQLITE_IOERR_UNLOCK = (SQLITE_IOERR | (8<<8))
|
94
|
+
SQLITE_IOERR_RDLOCK = (SQLITE_IOERR | (9<<8))
|
95
|
+
SQLITE_IOERR_DELETE = (SQLITE_IOERR | (10<<8))
|
96
|
+
SQLITE_IOERR_BLOCKED = (SQLITE_IOERR | (11<<8))
|
97
|
+
SQLITE_IOERR_NOMEM = (SQLITE_IOERR | (12<<8))
|
98
|
+
SQLITE_IOERR_ACCESS = (SQLITE_IOERR | (13<<8))
|
99
|
+
SQLITE_IOERR_CHECKRESERVEDLOCK = (SQLITE_IOERR | (14<<8))
|
100
|
+
SQLITE_IOERR_LOCK = (SQLITE_IOERR | (15<<8))
|
101
|
+
SQLITE_IOERR_CLOSE = (SQLITE_IOERR | (16<<8))
|
102
|
+
SQLITE_IOERR_DIR_CLOSE = (SQLITE_IOERR | (17<<8))
|
103
|
+
SQLITE_IOERR_SHMOPEN = (SQLITE_IOERR | (18<<8))
|
104
|
+
SQLITE_IOERR_SHMSIZE = (SQLITE_IOERR | (19<<8))
|
105
|
+
SQLITE_IOERR_SHMLOCK = (SQLITE_IOERR | (20<<8))
|
106
|
+
SQLITE_IOERR_SHMMAP = (SQLITE_IOERR | (21<<8))
|
107
|
+
SQLITE_IOERR_SEEK = (SQLITE_IOERR | (22<<8))
|
108
|
+
SQLITE_IOERR_DELETE_NOENT = (SQLITE_IOERR | (23<<8))
|
109
|
+
SQLITE_IOERR_MMAP = (SQLITE_IOERR | (24<<8))
|
110
|
+
SQLITE_IOERR_GETTEMPPATH = (SQLITE_IOERR | (25<<8))
|
111
|
+
SQLITE_IOERR_CONVPATH = (SQLITE_IOERR | (26<<8))
|
112
|
+
SQLITE_IOERR_VNODE = (SQLITE_IOERR | (27<<8))
|
113
|
+
SQLITE_IOERR_AUTH = (SQLITE_IOERR | (28<<8))
|
114
|
+
SQLITE_IOERR_BEGIN_ATOMIC = (SQLITE_IOERR | (29<<8))
|
115
|
+
SQLITE_IOERR_COMMIT_ATOMIC = (SQLITE_IOERR | (30<<8))
|
116
|
+
SQLITE_IOERR_ROLLBACK_ATOMIC = (SQLITE_IOERR | (31<<8))
|
117
|
+
SQLITE_IOERR_DATA = (SQLITE_IOERR | (32<<8))
|
118
|
+
SQLITE_IOERR_CORRUPTFS = (SQLITE_IOERR | (33<<8))
|
119
|
+
SQLITE_LOCKED_SHAREDCACHE = (SQLITE_LOCKED | (1<<8))
|
120
|
+
SQLITE_LOCKED_VTAB = (SQLITE_LOCKED | (2<<8))
|
121
|
+
SQLITE_BUSY_RECOVERY = (SQLITE_BUSY | (1<<8))
|
122
|
+
SQLITE_BUSY_SNAPSHOT = (SQLITE_BUSY | (2<<8))
|
123
|
+
SQLITE_BUSY_TIMEOUT = (SQLITE_BUSY | (3<<8))
|
124
|
+
SQLITE_CANTOPEN_NOTEMPDIR = (SQLITE_CANTOPEN | (1<<8))
|
125
|
+
SQLITE_CANTOPEN_ISDIR = (SQLITE_CANTOPEN | (2<<8))
|
126
|
+
SQLITE_CANTOPEN_FULLPATH = (SQLITE_CANTOPEN | (3<<8))
|
127
|
+
SQLITE_CANTOPEN_CONVPATH = (SQLITE_CANTOPEN | (4<<8))
|
128
|
+
SQLITE_CANTOPEN_DIRTYWAL = (SQLITE_CANTOPEN | (5<<8))
|
129
|
+
SQLITE_CANTOPEN_SYMLINK = (SQLITE_CANTOPEN | (6<<8))
|
130
|
+
SQLITE_CORRUPT_VTAB = (SQLITE_CORRUPT | (1<<8))
|
131
|
+
SQLITE_CORRUPT_SEQUENCE = (SQLITE_CORRUPT | (2<<8))
|
132
|
+
SQLITE_CORRUPT_INDEX = (SQLITE_CORRUPT | (3<<8))
|
133
|
+
SQLITE_READONLY_RECOVERY = (SQLITE_READONLY | (1<<8))
|
134
|
+
SQLITE_READONLY_CANTLOCK = (SQLITE_READONLY | (2<<8))
|
135
|
+
SQLITE_READONLY_ROLLBACK = (SQLITE_READONLY | (3<<8))
|
136
|
+
SQLITE_READONLY_DBMOVED = (SQLITE_READONLY | (4<<8))
|
137
|
+
SQLITE_READONLY_CANTINIT = (SQLITE_READONLY | (5<<8))
|
138
|
+
SQLITE_READONLY_DIRECTORY = (SQLITE_READONLY | (6<<8))
|
139
|
+
SQLITE_ABORT_ROLLBACK = (SQLITE_ABORT | (2<<8))
|
140
|
+
SQLITE_CONSTRAINT_CHECK = (SQLITE_CONSTRAINT | (1<<8))
|
141
|
+
SQLITE_CONSTRAINT_COMMITHOOK = (SQLITE_CONSTRAINT | (2<<8))
|
142
|
+
SQLITE_CONSTRAINT_FOREIGNKEY = (SQLITE_CONSTRAINT | (3<<8))
|
143
|
+
SQLITE_CONSTRAINT_FUNCTION = (SQLITE_CONSTRAINT | (4<<8))
|
144
|
+
SQLITE_CONSTRAINT_NOTNULL = (SQLITE_CONSTRAINT | (5<<8))
|
145
|
+
SQLITE_CONSTRAINT_PRIMARYKEY = (SQLITE_CONSTRAINT | (6<<8))
|
146
|
+
SQLITE_CONSTRAINT_TRIGGER = (SQLITE_CONSTRAINT | (7<<8))
|
147
|
+
SQLITE_CONSTRAINT_UNIQUE = (SQLITE_CONSTRAINT | (8<<8))
|
148
|
+
SQLITE_CONSTRAINT_VTAB = (SQLITE_CONSTRAINT | (9<<8))
|
149
|
+
SQLITE_CONSTRAINT_ROWID = (SQLITE_CONSTRAINT |(10<<8))
|
150
|
+
SQLITE_CONSTRAINT_PINNED = (SQLITE_CONSTRAINT |(11<<8))
|
151
|
+
SQLITE_CONSTRAINT_DATATYPE = (SQLITE_CONSTRAINT |(12<<8))
|
152
|
+
SQLITE_NOTICE_RECOVER_WAL = (SQLITE_NOTICE | (1<<8))
|
153
|
+
SQLITE_NOTICE_RECOVER_ROLLBACK = (SQLITE_NOTICE | (2<<8))
|
154
|
+
SQLITE_WARNING_AUTOINDEX = (SQLITE_WARNING | (1<<8))
|
155
|
+
SQLITE_AUTH_USER = (SQLITE_AUTH | (1<<8))
|
156
|
+
SQLITE_OK_LOAD_PERMANENTLY = (SQLITE_OK | (1<<8))
|
157
|
+
SQLITE_OK_SYMLINK = (SQLITE_OK | (2<<8))
|
158
|
+
end
|
data/lib/extralite/version.rb
CHANGED
data/lib/extralite.rb
CHANGED
@@ -1,46 +1,11 @@
|
|
1
1
|
require_relative './extralite_ext'
|
2
|
+
require_relative './extralite/sqlite3_constants'
|
2
3
|
|
3
4
|
# Extralite is a Ruby gem for working with SQLite databases
|
4
5
|
module Extralite
|
5
|
-
|
6
|
-
SQLITE_STATUS_MEMORY_USED = 0
|
7
|
-
SQLITE_STATUS_PAGECACHE_USED = 1
|
8
|
-
SQLITE_STATUS_PAGECACHE_OVERFLOW = 2
|
9
|
-
SQLITE_STATUS_SCRATCH_USED = 3 # NOT USED
|
10
|
-
SQLITE_STATUS_SCRATCH_OVERFLOW = 4 # NOT USED
|
11
|
-
SQLITE_STATUS_MALLOC_SIZE = 5
|
12
|
-
SQLITE_STATUS_PARSER_STACK = 6
|
13
|
-
SQLITE_STATUS_PAGECACHE_SIZE = 7
|
14
|
-
SQLITE_STATUS_SCRATCH_SIZE = 8 # NOT USED
|
15
|
-
SQLITE_STATUS_MALLOC_COUNT = 9
|
16
6
|
|
17
|
-
|
18
|
-
|
19
|
-
SQLITE_DBSTATUS_SCHEMA_USED = 2
|
20
|
-
SQLITE_DBSTATUS_STMT_USED = 3
|
21
|
-
SQLITE_DBSTATUS_LOOKASIDE_HIT = 4
|
22
|
-
SQLITE_DBSTATUS_LOOKASIDE_MISS_SIZE = 5
|
23
|
-
SQLITE_DBSTATUS_LOOKASIDE_MISS_FULL = 6
|
24
|
-
SQLITE_DBSTATUS_CACHE_HIT = 7
|
25
|
-
SQLITE_DBSTATUS_CACHE_MISS = 8
|
26
|
-
SQLITE_DBSTATUS_CACHE_WRITE = 9
|
27
|
-
SQLITE_DBSTATUS_DEFERRED_FKS = 10
|
28
|
-
SQLITE_DBSTATUS_CACHE_USED_SHARED = 11
|
29
|
-
SQLITE_DBSTATUS_CACHE_SPILL = 12
|
30
|
-
|
31
|
-
SQLITE_STMTSTATUS_FULLSCAN_STEP = 1
|
32
|
-
SQLITE_STMTSTATUS_SORT = 2
|
33
|
-
SQLITE_STMTSTATUS_AUTOINDEX = 3
|
34
|
-
SQLITE_STMTSTATUS_VM_STEP = 4
|
35
|
-
SQLITE_STMTSTATUS_REPREPARE = 5
|
36
|
-
SQLITE_STMTSTATUS_RUN = 6
|
37
|
-
SQLITE_STMTSTATUS_FILTER_MISS = 7
|
38
|
-
SQLITE_STMTSTATUS_FILTER_HIT = 8
|
39
|
-
SQLITE_STMTSTATUS_MEMUSED = 99
|
40
|
-
|
41
|
-
# The following class definitions are not really needed, as they're already
|
42
|
-
# defined in the C extension. We put them here for the sake of generating
|
43
|
-
# docs.
|
7
|
+
# The following error classes are already defined in the C extension. We put
|
8
|
+
# them here for the sake of generating docs.
|
44
9
|
|
45
10
|
# A base class for Extralite exceptions
|
46
11
|
class Error < ::StandardError
|
data/test/test_database.rb
CHANGED
@@ -169,8 +169,6 @@ end
|
|
169
169
|
assert_nil r
|
170
170
|
end
|
171
171
|
|
172
|
-
|
173
|
-
|
174
172
|
def test_extension_loading
|
175
173
|
case RUBY_PLATFORM
|
176
174
|
when /linux/
|
@@ -269,6 +267,100 @@ end
|
|
269
267
|
def test_database_status
|
270
268
|
assert_operator 0, :<, @db.status(Extralite::SQLITE_DBSTATUS_SCHEMA_USED).first
|
271
269
|
end
|
270
|
+
|
271
|
+
def test_database_limit
|
272
|
+
result = @db.limit(Extralite::SQLITE_LIMIT_ATTACHED)
|
273
|
+
assert_equal 10, result
|
274
|
+
|
275
|
+
result = @db.limit(Extralite::SQLITE_LIMIT_ATTACHED, 5)
|
276
|
+
assert_equal 10, result
|
277
|
+
|
278
|
+
result = @db.limit(Extralite::SQLITE_LIMIT_ATTACHED)
|
279
|
+
assert_equal 5, result
|
280
|
+
|
281
|
+
assert_raises(Extralite::Error) { @db.limit(-999) }
|
282
|
+
end
|
283
|
+
|
284
|
+
def test_database_busy_timeout
|
285
|
+
fn = "/tmp/extralite-#{rand(10000)}.db"
|
286
|
+
db1 = Extralite::Database.new(fn)
|
287
|
+
db2 = Extralite::Database.new(fn)
|
288
|
+
|
289
|
+
db1.query('begin exclusive')
|
290
|
+
assert_raises(Extralite::BusyError) { db2.query('begin exclusive') }
|
291
|
+
|
292
|
+
db2.busy_timeout = 3
|
293
|
+
t0 = Time.now
|
294
|
+
t = Thread.new { sleep 0.1; db1.query('rollback') }
|
295
|
+
result = db2.query('begin exclusive')
|
296
|
+
t1 = Time.now
|
297
|
+
|
298
|
+
assert_equal [], result
|
299
|
+
assert t1 - t0 >= 0.1
|
300
|
+
db2.query('rollback')
|
301
|
+
t.join
|
302
|
+
|
303
|
+
# try to provoke a timeout
|
304
|
+
db1.query('begin exclusive')
|
305
|
+
db2.busy_timeout = nil
|
306
|
+
assert_raises(Extralite::BusyError) { db2.query('begin exclusive') }
|
307
|
+
|
308
|
+
db2.busy_timeout = 0.2
|
309
|
+
t0 = Time.now
|
310
|
+
t = Thread.new do
|
311
|
+
sleep 3
|
312
|
+
ensure
|
313
|
+
db1.query('rollback')
|
314
|
+
end
|
315
|
+
assert_raises(Extralite::BusyError) { db2.query('begin exclusive') }
|
316
|
+
|
317
|
+
t1 = Time.now
|
318
|
+
assert t1 - t0 >= 0.2
|
319
|
+
t.kill
|
320
|
+
t.join
|
321
|
+
|
322
|
+
db1.query('begin exclusive')
|
323
|
+
db2.busy_timeout = 0
|
324
|
+
assert_raises(Extralite::BusyError) { db2.query('begin exclusive') }
|
325
|
+
|
326
|
+
db2.busy_timeout = nil
|
327
|
+
assert_raises(Extralite::BusyError) { db2.query('begin exclusive') }
|
328
|
+
end
|
329
|
+
|
330
|
+
def test_database_total_changes
|
331
|
+
assert_equal 2, @db.total_changes
|
332
|
+
|
333
|
+
@db.query('insert into t values (7, 8, 9)')
|
334
|
+
|
335
|
+
assert_equal 3, @db.total_changes
|
336
|
+
end
|
337
|
+
|
338
|
+
def test_database_errcode_errmsg
|
339
|
+
assert_equal 0, @db.errcode
|
340
|
+
assert_equal 'not an error', @db.errmsg
|
341
|
+
|
342
|
+
@db.query('select foo') rescue nil
|
343
|
+
|
344
|
+
assert_equal 1, @db.errcode
|
345
|
+
assert_equal 'no such column: foo', @db.errmsg
|
346
|
+
|
347
|
+
if Extralite.sqlite3_version >= '3.38.5'
|
348
|
+
assert_equal 7, @db.error_offset
|
349
|
+
end
|
350
|
+
|
351
|
+
@db.query('create table t2 (v not null)')
|
352
|
+
|
353
|
+
assert_raises(Extralite::Error) { @db.query('insert into t2 values (null)') }
|
354
|
+
assert_equal Extralite::SQLITE_CONSTRAINT_NOTNULL, @db.errcode
|
355
|
+
assert_equal 'NOT NULL constraint failed: t2.v', @db.errmsg
|
356
|
+
end
|
357
|
+
|
358
|
+
|
359
|
+
def test_close_with_open_prepared_statement
|
360
|
+
stmt = @db.prepare('select * from t')
|
361
|
+
stmt.query
|
362
|
+
@db.close
|
363
|
+
end
|
272
364
|
end
|
273
365
|
|
274
366
|
class ScenarioTest < MiniTest::Test
|
@@ -336,6 +428,30 @@ class ScenarioTest < MiniTest::Test
|
|
336
428
|
result = @db.query_single_column('select x from t')
|
337
429
|
assert_equal [1, 4, 7], result
|
338
430
|
end
|
431
|
+
|
432
|
+
def test_database_trace
|
433
|
+
sqls = []
|
434
|
+
@db.trace { |sql| sqls << sql }
|
435
|
+
|
436
|
+
@db.query('select 1')
|
437
|
+
assert_equal ['select 1'], sqls
|
438
|
+
|
439
|
+
@db.query('select 2')
|
440
|
+
assert_equal ['select 1', 'select 2'], sqls
|
441
|
+
|
442
|
+
stmt = @db.prepare('select 3')
|
443
|
+
|
444
|
+
stmt.query
|
445
|
+
assert_equal ['select 1', 'select 2', 'select 3'], sqls
|
446
|
+
|
447
|
+
# turn off
|
448
|
+
@db.trace
|
449
|
+
|
450
|
+
stmt.query
|
451
|
+
|
452
|
+
@db.query('select 4')
|
453
|
+
assert_equal ['select 1', 'select 2', 'select 3'], sqls
|
454
|
+
end
|
339
455
|
end
|
340
456
|
|
341
457
|
class BackupTest < MiniTest::Test
|
@@ -37,6 +37,11 @@ class PreparedStatementTest < MiniTest::Test
|
|
37
37
|
assert_raises(Extralite::SQLError) { @db.prepare('blah') }
|
38
38
|
end
|
39
39
|
|
40
|
+
def test_prepared_statement_with_multiple_queries
|
41
|
+
error = begin; @db.prepare('select 1; select 2'); rescue => e; error = e; end
|
42
|
+
assert_equal Extralite::Error, error.class
|
43
|
+
end
|
44
|
+
|
40
45
|
def test_prepared_statement_query_hash
|
41
46
|
r = @stmt.query_hash(4)
|
42
47
|
assert_equal [{x: 4, y: 5, z: 6}], r
|
@@ -211,4 +216,10 @@ end
|
|
211
216
|
assert_equal 3, @stmt.status(Extralite::SQLITE_STMTSTATUS_RUN, true)
|
212
217
|
assert_equal 0, @stmt.status(Extralite::SQLITE_STMTSTATUS_RUN)
|
213
218
|
end
|
219
|
+
|
220
|
+
def test_query_after_db_close
|
221
|
+
assert_equal [{ x: 4, y: 5, z: 6}], @stmt.query(4)
|
222
|
+
@db.close
|
223
|
+
assert_equal [{ x: 4, y: 5, z: 6}], @stmt.query(4)
|
224
|
+
end
|
214
225
|
end
|
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.24'
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Sharon Rosner
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-
|
11
|
+
date: 2023-02-02 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rake-compiler
|
@@ -113,6 +113,7 @@ files:
|
|
113
113
|
- extralite.gemspec
|
114
114
|
- gemspec.rb
|
115
115
|
- lib/extralite.rb
|
116
|
+
- lib/extralite/sqlite3_constants.rb
|
116
117
|
- lib/extralite/version.rb
|
117
118
|
- lib/sequel/adapters/extralite.rb
|
118
119
|
- test/extensions/text.dylib
|