extralite 1.22 → 1.24

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: 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