extralite 1.22 → 1.23

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0f89c90ddd416f49777f9962a2abeac953eb1303cee79b2c612f6936cf7eb033
4
- data.tar.gz: fc2604b1b6ef38a7e52bdeaaf579a84728885a09e0005c4a436064b4a919f5d7
3
+ metadata.gz: 7466fb655d5f2cc65862d604190d52208773a9cc8fb611b184bc46a20ccb8d1f
4
+ data.tar.gz: bbc9b59c5c2297df34d716a19b53ccc95008bea3be4d38d3cdd91340089962b5
5
5
  SHA512:
6
- metadata.gz: 892b73c7eb255b0e65f7a7b23b4b07fb9ad824a0b4766bcbcfff9d5996fb6954f664725f7bff0682a09b10c0d45690d53311b1e59038fce3b23805f919f53909
7
- data.tar.gz: 2e1a19b0d9053e40a13ae905fc6f78acead1d6a625c009e8d55b9b47402b392012c68ee2fa2acd0d6f1a78aa8c5da1bed946d6a20d85db44209f9a1b897eeca1
6
+ metadata.gz: 227a7fb703cca6eed8b25f0adb32cf8421f3c503149a715006c381d5cca475e692048650acc561ccfcb5ff1203c489fa6fe429922aecb2c0486cd0ec83c85360
7
+ data.tar.gz: 131aa35e939c4623a306be6e7a756d40cfe77a5b199ffc90cb1937194e7066ba6fa4de3d4cc8618b8123b68dcfe9e92a60b3e821c49b51145497deb005467f24
@@ -8,7 +8,7 @@ jobs:
8
8
  fail-fast: false
9
9
  matrix:
10
10
  os: [ubuntu-latest, macos-latest]
11
- ruby: ['2.7', '3.0', '3.1', truffleruby]
11
+ ruby: ['3.0', '3.1', '3.2', truffleruby]
12
12
 
13
13
  name: >-
14
14
  ${{matrix.os}}, ${{matrix.ruby}}
data/CHANGELOG.md CHANGED
@@ -1,3 +1,11 @@
1
+ # 1.23 2023-01-26
2
+
3
+ - Add `Database#trace` (#21)
4
+ - Add `Database#total_changes` (#20)
5
+ - Add `Database#busy_timeout=` (#19)
6
+ - Add `Database#limit` (#16)
7
+ - Improve error handling
8
+
1
9
  # 1.22 2023-01-23
2
10
 
3
11
  - Improve documentation (#17)
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- extralite (1.22)
4
+ extralite (1.23)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
data/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # Extralite - A super fast Ruby gem for working with SQLite3 databases
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 10x faster](#performance) than the
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 bundle
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 features
148
+ ## More Features
149
149
 
150
- ### Interrupting long-running queries
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 backups
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
- ### Retrieve status information
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 ~10 times faster than `sqlite3` when fetching a
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 hashes
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 arrays
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 statements
305
+ ### Prepared Statements
299
306
 
300
307
  [Benchmark source code](https://github.com/digital-fabric/extralite/blob/main/test/perf_prepared.rb)
301
308
 
@@ -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, "Invalid return code for prepare_multi_stmt_without_gvl: %d (please open an issue on https://github.com/digital-fabric/extralite)", ctx.rc);
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(cSQLError, "A prepared statement does not accept SQL strings with multiple queries");
219
+ rb_raise(cError, "A prepared statement does not accept SQL strings with multiple queries");
220
220
  default:
221
- 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);
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, "Invalid return code for sqlite3_step: %d (please open an issue on https://github.com/digital-fabric/extralite)", ctx.rc);
251
+ rb_raise(cError, "%s", sqlite3_errmsg(db));
252
252
  }
253
253
 
254
254
  return 0;
@@ -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* argv, VALUE self) {
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");
@@ -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
@@ -1,3 +1,3 @@
1
1
  module Extralite
2
- VERSION = '1.22'
2
+ VERSION = '1.23'
3
3
  end
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
- SQLITE_DBSTATUS_LOOKASIDE_USED = 0
18
- SQLITE_DBSTATUS_CACHE_USED = 1
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
@@ -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
3
3
  version: !ruby/object:Gem::Version
4
- version: '1.22'
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-23 00:00:00.000000000 Z
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