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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0f89c90ddd416f49777f9962a2abeac953eb1303cee79b2c612f6936cf7eb033
4
- data.tar.gz: fc2604b1b6ef38a7e52bdeaaf579a84728885a09e0005c4a436064b4a919f5d7
3
+ metadata.gz: bbe4f33eced375588c90c6f0e2c3dd5b25ca19e44e3a9b4a20f21e43d7a833ff
4
+ data.tar.gz: 709a92b6edd0b54531652d3c572b5dcac21e8db09b6c516c8f07332bb4daa6ee
5
5
  SHA512:
6
- metadata.gz: 892b73c7eb255b0e65f7a7b23b4b07fb9ad824a0b4766bcbcfff9d5996fb6954f664725f7bff0682a09b10c0d45690d53311b1e59038fce3b23805f919f53909
7
- data.tar.gz: 2e1a19b0d9053e40a13ae905fc6f78acead1d6a625c009e8d55b9b47402b392012c68ee2fa2acd0d6f1a78aa8c5da1bed946d6a20d85db44209f9a1b897eeca1
6
+ metadata.gz: b1c8f1cfc27bfe888f6c92be36f00631dda67471f937c7d94d5b731c94b90fd3b83f322891186f1cc9baf792dafe2df02cd1dc09f8cef7e4030bce79c048f9c1
7
+ data.tar.gz: 6e4de82d209255d623bc090b29b5d71da1922a6d70d5b3879a475ace8acb77de8b75ee688c51b45b71c9e9b66c56e71bc341da6377366baf66388cbc261f65b5
@@ -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,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
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- extralite (1.22)
4
+ extralite (1.24)
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
 
@@ -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, ID_KEYS, 0);
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, ID_TO_S, 0);
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, "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,10 +7,11 @@ VALUE cSQLError;
7
7
  VALUE cBusyError;
8
8
  VALUE cInterruptError;
9
9
 
10
- ID ID_KEYS;
11
- ID ID_NEW;
12
- ID ID_STRIP;
13
- ID ID_TO_S;
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) sqlite3_close(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
- sqlite3_close(db->sqlite3_db);
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
- sqlite3_close(db->sqlite3_db);
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 = sqlite3_close(db->sqlite3_db);
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], ID_STRIP, 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, ID_NEW, 2, self, sql);
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
- sqlite3_close(ctx->dst);
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
- sqlite3_close(dst_db);
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
- sqlite3_close(dst_db);
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* argv, VALUE self) {
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
- ID_KEYS = rb_intern("keys");
610
- ID_NEW = rb_intern("new");
611
- ID_STRIP = rb_intern("strip");
612
- ID_TO_S = rb_intern("to_s");
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 << " -Wno-undef"
6
- $CFLAGS << " -Wno-discarded-qualifiers"
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')
@@ -1,115 +1,93 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # require 'rubygems'
4
- # require 'mkmf'
5
-
6
- # # $CFLAGS << "-Wdiscarded-qualifier"
7
- # # $CFLAGS << " -Wno-comment"
8
- # # $CFLAGS << " -Wno-unused-result"
9
- # # $CFLAGS << " -Wno-dangling-else"
10
- # # $CFLAGS << " -Wno-parentheses"
11
-
12
- # dir_config 'extralite_ext'
13
- # create_makefile 'extralite_ext'
14
-
15
- ENV['RC_ARCHS'] = '' if RUBY_PLATFORM =~ /darwin/
16
-
17
- require 'mkmf'
18
-
19
- # :stopdoc:
20
-
21
- RbConfig::MAKEFILE_CONFIG['CC'] = ENV['CC'] if ENV['CC']
22
-
23
- ldflags = cppflags = nil
24
- if RbConfig::CONFIG["host_os"] =~ /darwin/
25
- begin
26
- if with_config('sqlcipher')
27
- brew_prefix = `brew --prefix sqlcipher`.chomp
28
- ldflags = "#{brew_prefix}/lib"
29
- cppflags = "#{brew_prefix}/include/sqlcipher"
30
- pkg_conf = "#{brew_prefix}/lib/pkgconfig"
31
- else
32
- brew_prefix = `brew --prefix sqlite3`.chomp
33
- ldflags = "#{brew_prefix}/lib"
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
- end
45
-
46
- if with_config('sqlcipher')
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
- abort <<-error
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
- end
81
-
82
- asplode('sqlite3.h') unless find_header 'sqlite3.h'
83
- find_library 'pthread', 'pthread_create' # 1.8 support. *shrug*
84
-
85
- have_library 'dl' # for static builds
86
-
87
- if with_config('sqlcipher')
88
- asplode('sqlcipher') unless find_library 'sqlcipher', 'sqlite3_libversion_number'
89
- else
90
- asplode('sqlite3') unless find_library 'sqlite3', 'sqlite3_libversion_number'
91
- end
92
-
93
- # Functions defined in 1.9 but not 1.8
94
- have_func('rb_proc_arity')
95
-
96
- # Functions defined in 2.1 but not 2.0
97
- have_func('rb_integer_pack')
98
-
99
- # These functions may not be defined
100
- have_func('sqlite3_initialize')
101
- have_func('sqlite3_enable_load_extension')
102
- have_func('sqlite3_load_extension')
103
-
104
- unless have_func('sqlite3_open_v2')
105
- abort 'Please use a newer version of SQLite3'
106
- end
107
-
108
- have_func('sqlite3_prepare_v2')
109
- have_type('sqlite3_int64', 'sqlite3.h')
110
- have_type('sqlite3_uint64', 'sqlite3.h')
111
-
112
- $defs << "-DEXTRALITE_NO_BUNDLE"
113
-
114
- dir_config('extralite_ext')
115
- create_makefile('extralite_ext')
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
@@ -27,18 +27,21 @@ extern VALUE cSQLError;
27
27
  extern VALUE cBusyError;
28
28
  extern VALUE cInterruptError;
29
29
 
30
- extern ID ID_KEYS;
31
- extern ID ID_NEW;
32
- extern ID ID_STRIP;
33
- extern ID ID_TO_S;
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
- "Database",
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, ID_STRIP, 0);
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
@@ -1,3 +1,3 @@
1
1
  module Extralite
2
- VERSION = '1.22'
2
+ VERSION = '1.24'
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
@@ -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.22'
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-01-23 00:00:00.000000000 Z
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