extralite-bundle 1.22 → 1.23
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 +8 -0
- data/Gemfile.lock +1 -1
- data/README.md +47 -40
- data/ext/extralite/common.c +4 -4
- data/ext/extralite/database.c +85 -1
- data/ext/extralite/extralite.h +4 -0
- data/ext/extralite/prepared_statement.c +3 -0
- data/lib/extralite/sqlite3_constants.rb +50 -0
- data/lib/extralite/version.rb +1 -1
- data/lib/extralite.rb +3 -38
- data/test/test_database.rb +86 -0
- data/test/test_prepared_statement.rb +5 -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: 6622a72a5d4e7ea9670be11b21708f2f8eedda7d94e870ea283d6e8b0d13471e
|
4
|
+
data.tar.gz: 1eed8cd7ca3fe7844dead0024463efe285f4979eef2be94741c96bf42b447a42
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3300b9369bffded248aa4291db0f18da92a2c95015b0fbd4afc668c2783c223cb72d4cfb207ca1dace057a41c3e61a38ee37e0b8ffe5a1199a147143374c9f6d
|
7
|
+
data.tar.gz: 17d532b9c92238f3b94f073b2e7c13d793a872511778f7138e371a8e0a8f4e776d830a00874df203de514d2143e8340d42f479e2f267105a907226892540d74b
|
data/.github/workflows/test.yml
CHANGED
data/CHANGELOG.md
CHANGED
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
@@ -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,6 +7,7 @@ VALUE cSQLError;
|
|
7
7
|
VALUE cBusyError;
|
8
8
|
VALUE cInterruptError;
|
9
9
|
|
10
|
+
ID ID_CALL;
|
10
11
|
ID ID_KEYS;
|
11
12
|
ID ID_NEW;
|
12
13
|
ID ID_STRIP;
|
@@ -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);
|
@@ -86,6 +93,8 @@ VALUE Database_initialize(VALUE self, VALUE path) {
|
|
86
93
|
}
|
87
94
|
#endif
|
88
95
|
|
96
|
+
db->trace_block = Qnil;
|
97
|
+
|
89
98
|
return Qnil;
|
90
99
|
}
|
91
100
|
|
@@ -134,6 +143,7 @@ static inline VALUE Database_perform_query(int argc, VALUE *argv, VALUE self, VA
|
|
134
143
|
|
135
144
|
// prepare query ctx
|
136
145
|
GetOpenDatabase(self, db);
|
146
|
+
if (db->trace_block != Qnil) rb_funcall(db->trace_block, ID_CALL, 1, sql);
|
137
147
|
prepare_multi_stmt(db->sqlite3_db, &stmt, sql);
|
138
148
|
bind_all_parameters(stmt, argc - 1, argv + 1);
|
139
149
|
query_ctx ctx = { self, db->sqlite3_db, stmt };
|
@@ -550,7 +560,7 @@ VALUE Extralite_runtime_status(int argc, VALUE* argv, VALUE self) {
|
|
550
560
|
* current value and the high water mark value. To reset the high water mark,
|
551
561
|
* pass true as reset.
|
552
562
|
*/
|
553
|
-
VALUE Database_status(int argc, VALUE*
|
563
|
+
VALUE Database_status(int argc, VALUE *argv, VALUE self) {
|
554
564
|
VALUE op, reset;
|
555
565
|
int cur, hwm;
|
556
566
|
|
@@ -565,6 +575,75 @@ VALUE Database_status(int argc, VALUE* argv, VALUE self) {
|
|
565
575
|
return rb_ary_new3(2, INT2NUM(cur), INT2NUM(hwm));
|
566
576
|
}
|
567
577
|
|
578
|
+
/* call-seq:
|
579
|
+
* db.limit(category) -> value
|
580
|
+
* db.limit(category, new_value) -> prev_value
|
581
|
+
*
|
582
|
+
* Returns the current limit for the given category. If a new value is given,
|
583
|
+
* sets the limit to the new value and returns the previous value.
|
584
|
+
*/
|
585
|
+
VALUE Database_limit(int argc, VALUE *argv, VALUE self) {
|
586
|
+
VALUE category, new_value;
|
587
|
+
|
588
|
+
rb_scan_args(argc, argv, "11", &category, &new_value);
|
589
|
+
|
590
|
+
Database_t *db;
|
591
|
+
GetOpenDatabase(self, db);
|
592
|
+
|
593
|
+
int value = sqlite3_limit(db->sqlite3_db, NUM2INT(category), RTEST(new_value) ? NUM2INT(new_value) : -1);
|
594
|
+
|
595
|
+
if (value == -1) rb_raise(cError, "Invalid limit category");
|
596
|
+
|
597
|
+
return INT2NUM(value);
|
598
|
+
}
|
599
|
+
|
600
|
+
/* call-seq:
|
601
|
+
* db.busy_timeout=(sec) -> db
|
602
|
+
* db.busy_timeout=nil -> db
|
603
|
+
*
|
604
|
+
* Sets the busy timeout for the database, in seconds or fractions thereof. To
|
605
|
+
* disable the busy timeout, set it to 0 or nil.
|
606
|
+
*/
|
607
|
+
VALUE Database_busy_timeout_set(VALUE self, VALUE sec) {
|
608
|
+
Database_t *db;
|
609
|
+
GetOpenDatabase(self, db);
|
610
|
+
|
611
|
+
int ms = (sec == Qnil) ? 0 : (int)(NUM2DBL(sec) * 1000);
|
612
|
+
|
613
|
+
int rc = sqlite3_busy_timeout(db->sqlite3_db, ms);
|
614
|
+
if (rc != SQLITE_OK) rb_raise(cError, "Failed to set busy timeout");
|
615
|
+
|
616
|
+
return self;
|
617
|
+
}
|
618
|
+
|
619
|
+
/* call-seq:
|
620
|
+
* db.total_changes -> value
|
621
|
+
*
|
622
|
+
* Returns the total number of changes made to the database since opening it.
|
623
|
+
*/
|
624
|
+
VALUE Database_total_changes(VALUE self) {
|
625
|
+
Database_t *db;
|
626
|
+
GetOpenDatabase(self, db);
|
627
|
+
|
628
|
+
int value = sqlite3_total_changes(db->sqlite3_db);
|
629
|
+
return INT2NUM(value);
|
630
|
+
}
|
631
|
+
|
632
|
+
/* call-seq:
|
633
|
+
* db.trace { |sql| } -> db
|
634
|
+
* db.trace -> db
|
635
|
+
*
|
636
|
+
* Installs or removes a block that will be invoked for every SQL statement
|
637
|
+
* executed.
|
638
|
+
*/
|
639
|
+
VALUE Database_trace(VALUE self) {
|
640
|
+
Database_t *db;
|
641
|
+
GetOpenDatabase(self, db);
|
642
|
+
|
643
|
+
db->trace_block = rb_block_given_p() ? rb_block_proc() : Qnil;
|
644
|
+
return self;
|
645
|
+
}
|
646
|
+
|
568
647
|
void Init_ExtraliteDatabase(void) {
|
569
648
|
VALUE mExtralite = rb_define_module("Extralite");
|
570
649
|
rb_define_singleton_method(mExtralite, "runtime_status", Extralite_runtime_status, -1);
|
@@ -574,6 +653,7 @@ void Init_ExtraliteDatabase(void) {
|
|
574
653
|
rb_define_alloc_func(cDatabase, Database_allocate);
|
575
654
|
|
576
655
|
rb_define_method(cDatabase, "backup", Database_backup, -1);
|
656
|
+
rb_define_method(cDatabase, "busy_timeout=", Database_busy_timeout_set, 1);
|
577
657
|
rb_define_method(cDatabase, "changes", Database_changes, 0);
|
578
658
|
rb_define_method(cDatabase, "close", Database_close, 0);
|
579
659
|
rb_define_method(cDatabase, "closed?", Database_closed_p, 0);
|
@@ -583,6 +663,7 @@ void Init_ExtraliteDatabase(void) {
|
|
583
663
|
rb_define_method(cDatabase, "initialize", Database_initialize, 1);
|
584
664
|
rb_define_method(cDatabase, "interrupt", Database_interrupt, 0);
|
585
665
|
rb_define_method(cDatabase, "last_insert_rowid", Database_last_insert_rowid, 0);
|
666
|
+
rb_define_method(cDatabase, "limit", Database_limit, -1);
|
586
667
|
rb_define_method(cDatabase, "prepare", Database_prepare, 1);
|
587
668
|
rb_define_method(cDatabase, "query", Database_query_hash, -1);
|
588
669
|
rb_define_method(cDatabase, "query_ary", Database_query_ary, -1);
|
@@ -591,6 +672,8 @@ void Init_ExtraliteDatabase(void) {
|
|
591
672
|
rb_define_method(cDatabase, "query_single_row", Database_query_single_row, -1);
|
592
673
|
rb_define_method(cDatabase, "query_single_value", Database_query_single_value, -1);
|
593
674
|
rb_define_method(cDatabase, "status", Database_status, -1);
|
675
|
+
rb_define_method(cDatabase, "total_changes", Database_total_changes, 0);
|
676
|
+
rb_define_method(cDatabase, "trace", Database_trace, 0);
|
594
677
|
rb_define_method(cDatabase, "transaction_active?", Database_transaction_active_p, 0);
|
595
678
|
|
596
679
|
#ifdef HAVE_SQLITE3_LOAD_EXTENSION
|
@@ -606,6 +689,7 @@ void Init_ExtraliteDatabase(void) {
|
|
606
689
|
rb_gc_register_mark_object(cBusyError);
|
607
690
|
rb_gc_register_mark_object(cInterruptError);
|
608
691
|
|
692
|
+
ID_CALL = rb_intern("call");
|
609
693
|
ID_KEYS = rb_intern("keys");
|
610
694
|
ID_NEW = rb_intern("new");
|
611
695
|
ID_STRIP = rb_intern("strip");
|
data/ext/extralite/extralite.h
CHANGED
@@ -27,6 +27,7 @@ extern VALUE cSQLError;
|
|
27
27
|
extern VALUE cBusyError;
|
28
28
|
extern VALUE cInterruptError;
|
29
29
|
|
30
|
+
extern ID ID_CALL;
|
30
31
|
extern ID ID_KEYS;
|
31
32
|
extern ID ID_NEW;
|
32
33
|
extern ID ID_STRIP;
|
@@ -34,11 +35,13 @@ 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;
|
@@ -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 */
|
@@ -50,6 +50,7 @@ VALUE PreparedStatement_initialize(VALUE self, VALUE db, VALUE sql) {
|
|
50
50
|
rb_raise(cError, "Cannot prepare an empty SQL query");
|
51
51
|
|
52
52
|
stmt->db = db;
|
53
|
+
stmt->db_struct = Database_struct(db);
|
53
54
|
stmt->sqlite3_db = Database_sqlite3_db(db);
|
54
55
|
stmt->sql = sql;
|
55
56
|
|
@@ -65,6 +66,8 @@ static inline VALUE PreparedStatement_perform_query(int argc, VALUE *argv, VALUE
|
|
65
66
|
if (!stmt->stmt)
|
66
67
|
rb_raise(cError, "Prepared statement is closed");
|
67
68
|
|
69
|
+
if (stmt->db_struct->trace_block != Qnil) rb_funcall(stmt->db_struct->trace_block, ID_CALL, 1, stmt->sql);
|
70
|
+
|
68
71
|
sqlite3_reset(stmt->stmt);
|
69
72
|
sqlite3_clear_bindings(stmt->stmt);
|
70
73
|
bind_all_parameters(stmt->stmt, argc, argv);
|
@@ -0,0 +1,50 @@
|
|
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
|
+
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
@@ -269,6 +269,68 @@ end
|
|
269
269
|
def test_database_status
|
270
270
|
assert_operator 0, :<, @db.status(Extralite::SQLITE_DBSTATUS_SCHEMA_USED).first
|
271
271
|
end
|
272
|
+
|
273
|
+
def test_database_limit
|
274
|
+
result = @db.limit(Extralite::SQLITE_LIMIT_ATTACHED)
|
275
|
+
assert_equal 10, result
|
276
|
+
|
277
|
+
result = @db.limit(Extralite::SQLITE_LIMIT_ATTACHED, 5)
|
278
|
+
assert_equal 10, result
|
279
|
+
|
280
|
+
result = @db.limit(Extralite::SQLITE_LIMIT_ATTACHED)
|
281
|
+
assert_equal 5, result
|
282
|
+
|
283
|
+
assert_raises(Extralite::Error) { @db.limit(-999) }
|
284
|
+
end
|
285
|
+
|
286
|
+
def test_database_busy_timeout
|
287
|
+
fn = "/tmp/extralite-#{rand(10000)}.db"
|
288
|
+
db1 = Extralite::Database.new(fn)
|
289
|
+
db2 = Extralite::Database.new(fn)
|
290
|
+
|
291
|
+
db1.query('begin exclusive')
|
292
|
+
assert_raises(Extralite::BusyError) { db2.query('begin exclusive') }
|
293
|
+
|
294
|
+
db2.busy_timeout = 0.3
|
295
|
+
t0 = Time.now
|
296
|
+
t = Thread.new { sleep 0.1; db1.query('rollback') }
|
297
|
+
result = db2.query('begin exclusive')
|
298
|
+
t1 = Time.now
|
299
|
+
|
300
|
+
assert_equal [], result
|
301
|
+
assert t1 - t0 >= 0.1
|
302
|
+
db2.query('rollback')
|
303
|
+
|
304
|
+
# try to provoke a timeout
|
305
|
+
db1.query('begin exclusive')
|
306
|
+
db2.busy_timeout = 0.1
|
307
|
+
t0 = Time.now
|
308
|
+
t = Thread.new do
|
309
|
+
sleep 0.5
|
310
|
+
ensure
|
311
|
+
db1.query('rollback')
|
312
|
+
end
|
313
|
+
assert_raises(Extralite::BusyError) { db2.query('begin exclusive') }
|
314
|
+
t1 = Time.now
|
315
|
+
assert t1 - t0 >= 0.1
|
316
|
+
t.kill
|
317
|
+
t.join
|
318
|
+
|
319
|
+
db1.query('begin exclusive')
|
320
|
+
db2.busy_timeout = 0
|
321
|
+
assert_raises(Extralite::BusyError) { db2.query('begin exclusive') }
|
322
|
+
|
323
|
+
db2.busy_timeout = nil
|
324
|
+
assert_raises(Extralite::BusyError) { db2.query('begin exclusive') }
|
325
|
+
end
|
326
|
+
|
327
|
+
def test_database_total_changes
|
328
|
+
assert_equal 2, @db.total_changes
|
329
|
+
|
330
|
+
@db.query('insert into t values (7, 8, 9)')
|
331
|
+
|
332
|
+
assert_equal 3, @db.total_changes
|
333
|
+
end
|
272
334
|
end
|
273
335
|
|
274
336
|
class ScenarioTest < MiniTest::Test
|
@@ -336,6 +398,30 @@ class ScenarioTest < MiniTest::Test
|
|
336
398
|
result = @db.query_single_column('select x from t')
|
337
399
|
assert_equal [1, 4, 7], result
|
338
400
|
end
|
401
|
+
|
402
|
+
def test_database_trace
|
403
|
+
sqls = []
|
404
|
+
@db.trace { |sql| sqls << sql }
|
405
|
+
|
406
|
+
@db.query('select 1')
|
407
|
+
assert_equal ['select 1'], sqls
|
408
|
+
|
409
|
+
@db.query('select 2')
|
410
|
+
assert_equal ['select 1', 'select 2'], sqls
|
411
|
+
|
412
|
+
stmt = @db.prepare('select 3')
|
413
|
+
|
414
|
+
stmt.query
|
415
|
+
assert_equal ['select 1', 'select 2', 'select 3'], sqls
|
416
|
+
|
417
|
+
# turn off
|
418
|
+
@db.trace
|
419
|
+
|
420
|
+
stmt.query
|
421
|
+
|
422
|
+
@db.query('select 4')
|
423
|
+
assert_equal ['select 1', 'select 2', 'select 3'], sqls
|
424
|
+
end
|
339
425
|
end
|
340
426
|
|
341
427
|
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
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: extralite-bundle
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: '1.
|
4
|
+
version: '1.23'
|
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-01-
|
11
|
+
date: 2023-01-26 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
|