extralite 2.4 → 2.6

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.
@@ -13,11 +13,19 @@ VALUE eArgumentError;
13
13
 
14
14
  ID ID_bind;
15
15
  ID ID_call;
16
+ ID ID_each;
16
17
  ID ID_keys;
17
18
  ID ID_new;
18
19
  ID ID_strip;
20
+ ID ID_to_s;
21
+ ID ID_track;
19
22
 
23
+ VALUE SYM_gvl_release_threshold;
20
24
  VALUE SYM_read_only;
25
+ VALUE SYM_synchronous;
26
+ VALUE SYM_wal_journal_mode;
27
+
28
+ #define DB_GVL_MODE(db) Database_prepare_gvl_mode(db)
21
29
 
22
30
  static size_t Database_size(const void *ptr) {
23
31
  return sizeof(Database_t);
@@ -25,7 +33,14 @@ static size_t Database_size(const void *ptr) {
25
33
 
26
34
  static void Database_mark(void *ptr) {
27
35
  Database_t *db = ptr;
28
- rb_gc_mark(db->trace_block);
36
+ rb_gc_mark_movable(db->trace_proc);
37
+ rb_gc_mark_movable(db->progress_handler_proc);
38
+ }
39
+
40
+ static void Database_compact(void *ptr) {
41
+ Database_t *db = ptr;
42
+ db->trace_proc = rb_gc_location(db->trace_proc);
43
+ db->progress_handler_proc = rb_gc_location(db->progress_handler_proc);
29
44
  }
30
45
 
31
46
  static void Database_free(void *ptr) {
@@ -36,7 +51,7 @@ static void Database_free(void *ptr) {
36
51
 
37
52
  static const rb_data_type_t Database_type = {
38
53
  "Database",
39
- {Database_mark, Database_free, Database_size,},
54
+ {Database_mark, Database_free, Database_size, Database_compact},
40
55
  0, 0, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED
41
56
  };
42
57
 
@@ -85,14 +100,40 @@ default_flags:
85
100
  return SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE;
86
101
  }
87
102
 
103
+ VALUE Database_execute(int argc, VALUE *argv, VALUE self);
104
+
105
+ void Database_apply_opts(VALUE self, Database_t *db, VALUE opts) {
106
+ VALUE value = Qnil;
107
+
108
+ value = rb_hash_aref(opts, SYM_gvl_release_threshold);
109
+ if (!NIL_P(value)) db->gvl_release_threshold = NUM2INT(value);
110
+
111
+ value = rb_hash_aref(opts, SYM_wal_journal_mode);
112
+ if (RTEST(value)) {
113
+ value = rb_str_new_literal("PRAGMA journal_mode=wal");
114
+ Database_execute(1, &value, self);
115
+ }
116
+
117
+ value = rb_hash_aref(opts, SYM_synchronous);
118
+ if (RTEST(value)) {
119
+ value = rb_str_new_literal("PRAGMA synchronous=1");
120
+ Database_execute(1, &value, self);
121
+ }
122
+
123
+ RB_GC_GUARD(value);
124
+ }
125
+
88
126
  /* Initializes a new SQLite database with the given path and options.
89
127
  *
90
128
  * @overload initialize(path)
91
129
  * @param path [String] file path (or ':memory:' for memory database)
92
130
  * @return [void]
93
- * @overload initialize(path, read_only: false)
131
+ * @overload initialize(path, gvl_release_threshold: , read_only: , synchronous: , wal_journal_mode: )
94
132
  * @param path [String] file path (or ':memory:' for memory database)
133
+ * @param gvl_release_threshold [Integer] GVL release threshold
95
134
  * @param read_only [boolean] true for opening the database for reading only
135
+ * @param synchronous [boolean] true to set PRAGMA synchronous=1
136
+ * @param wal_journal_mode [boolean] true to set PRAGMA journal_mode=wal
96
137
  * @return [void]
97
138
  */
98
139
  VALUE Database_initialize(int argc, VALUE *argv, VALUE self) {
@@ -124,9 +165,12 @@ VALUE Database_initialize(int argc, VALUE *argv, VALUE self) {
124
165
  }
125
166
  #endif
126
167
 
127
- db->trace_block = Qnil;
168
+ db->trace_proc = Qnil;
169
+ db->progress_handler_proc = Qnil;
128
170
  db->gvl_release_threshold = DEFAULT_GVL_RELEASE_THRESHOLD;
129
171
 
172
+ if (!NIL_P(opts)) Database_apply_opts(self, db, opts);
173
+
130
174
  return Qnil;
131
175
  }
132
176
 
@@ -170,6 +214,10 @@ VALUE Database_closed_p(VALUE self) {
170
214
  return db->sqlite3_db ? Qfalse : Qtrue;
171
215
  }
172
216
 
217
+ inline enum gvl_mode Database_prepare_gvl_mode(Database_t *db) {
218
+ return db->gvl_release_threshold < 0 ? GVL_HOLD : GVL_RELEASE;
219
+ }
220
+
173
221
  static inline VALUE Database_perform_query(int argc, VALUE *argv, VALUE self, VALUE (*call)(query_ctx *)) {
174
222
  Database_t *db = self_to_open_database(self);
175
223
  sqlite3_stmt *stmt;
@@ -180,9 +228,8 @@ static inline VALUE Database_perform_query(int argc, VALUE *argv, VALUE self, VA
180
228
  sql = rb_funcall(argv[0], ID_strip, 0);
181
229
  if (RSTRING_LEN(sql) == 0) return Qnil;
182
230
 
183
- // prepare query ctx
184
- if (db->trace_block != Qnil) rb_funcall(db->trace_block, ID_call, 1, sql);
185
- prepare_multi_stmt(db->sqlite3_db, &stmt, sql);
231
+ TRACE_SQL(db, sql);
232
+ prepare_multi_stmt(DB_GVL_MODE(db), db->sqlite3_db, &stmt, sql);
186
233
  RB_GC_GUARD(sql);
187
234
 
188
235
  bind_all_parameters(stmt, argc - 1, argv + 1);
@@ -342,30 +389,150 @@ VALUE Database_execute(int argc, VALUE *argv, VALUE self) {
342
389
  }
343
390
 
344
391
  /* call-seq:
345
- * db.execute_multi(sql, params_array) -> changes
392
+ * db.batch_execute(sql, params_array) -> changes
393
+ * db.batch_execute(sql, enumerable) -> changes
394
+ * db.batch_execute(sql, callable) -> changes
395
+ *
396
+ * Executes the given query for each list of parameters in the paramter source.
397
+ * If an enumerable is given, it is iterated and each of its values is used as
398
+ * the parameters for running the query. If a callable is given, it is called
399
+ * repeatedly and each of its return values is used as the parameters, until nil
400
+ * is returned.
346
401
  *
347
- * Executes the given query for each list of parameters in params_array. Returns
348
- * the number of changes effected. This method is designed for inserting
349
- * multiple records.
402
+ * Returns the number of changes effected. This method is designed for inserting
403
+ * multiple records or performing other mass operations.
350
404
  *
351
405
  * records = [
352
406
  * [1, 2, 3],
353
407
  * [4, 5, 6]
354
408
  * ]
355
- * db.execute_multi('insert into foo values (?, ?, ?)', records)
409
+ * db.batch_execute('insert into foo values (?, ?, ?)', records)
356
410
  *
411
+ * source = [
412
+ * [1, 2, 3],
413
+ * [4, 5, 6]
414
+ * ]
415
+ * db.batch_execute('insert into foo values (?, ?, ?)', -> { records.shift })
416
+ *
417
+ * @param sql [String] query SQL
418
+ * @param parameters [Array<Array, Hash>, Enumerable, Enumerator, Callable] parameters to run query with
419
+ * @return [Integer] Total number of changes effected
357
420
  */
358
- VALUE Database_execute_multi(VALUE self, VALUE sql, VALUE params_array) {
421
+ VALUE Database_batch_execute(VALUE self, VALUE sql, VALUE parameters) {
359
422
  Database_t *db = self_to_open_database(self);
360
423
  sqlite3_stmt *stmt;
361
424
 
362
425
  if (RSTRING_LEN(sql) == 0) return Qnil;
363
426
 
364
- // prepare query ctx
365
- prepare_single_stmt(db->sqlite3_db, &stmt, sql);
366
- query_ctx ctx = QUERY_CTX(self, db, stmt, params_array, QUERY_MULTI_ROW, ALL_ROWS);
427
+ prepare_single_stmt(DB_GVL_MODE(db), db->sqlite3_db, &stmt, sql);
428
+ query_ctx ctx = QUERY_CTX(self, db, stmt, parameters, QUERY_MULTI_ROW, ALL_ROWS);
429
+
430
+ return rb_ensure(SAFE(safe_batch_execute), (VALUE)&ctx, SAFE(cleanup_stmt), (VALUE)&ctx);
431
+ }
432
+
433
+ /* call-seq:
434
+ * db.batch_query(sql, params_array) -> rows
435
+ * db.batch_query(sql, enumerable) -> rows
436
+ * db.batch_query(sql, callable) -> rows
437
+ * db.batch_query(sql, params_array) { |rows| ... } -> changes
438
+ * db.batch_query(sql, enumerable) { |rows| ... } -> changes
439
+ * db.batch_query(sql, callable) { |rows| ... } -> changes
440
+ *
441
+ * Executes the given query for each list of parameters in the given paramter
442
+ * source. If a block is given, it is called with the resulting rows for each
443
+ * invocation of the query, and the total number of changes is returned.
444
+ * Otherwise, an array containing the resulting rows for each invocation is
445
+ * returned.
446
+ *
447
+ * records = [
448
+ * [1, 2],
449
+ * [3, 4]
450
+ * ]
451
+ * db.batch_query('insert into foo values (?, ?) returning bar, baz', records)
452
+ * #=> [{ bar: 1, baz: 2 }, { bar: 3, baz: 4}]
453
+ * *
454
+ * @param sql [String] query SQL
455
+ * @param parameters [Array<Array, Hash>, Enumerable, Enumerator, Callable] parameters to run query with
456
+ * @return [Array<Hash>, Integer] Total number of changes effected
457
+ */
458
+ VALUE Database_batch_query(VALUE self, VALUE sql, VALUE parameters) {
459
+ Database_t *db = self_to_open_database(self);
460
+ sqlite3_stmt *stmt;
461
+
462
+ prepare_single_stmt(DB_GVL_MODE(db), db->sqlite3_db, &stmt, sql);
463
+ query_ctx ctx = QUERY_CTX(self, db, stmt, parameters, QUERY_MULTI_ROW, ALL_ROWS);
464
+
465
+ return rb_ensure(SAFE(safe_batch_query), (VALUE)&ctx, SAFE(cleanup_stmt), (VALUE)&ctx);
466
+ }
467
+
468
+ /* call-seq:
469
+ * db.batch_query_ary(sql, params_array) -> rows
470
+ * db.batch_query_ary(sql, enumerable) -> rows
471
+ * db.batch_query_ary(sql, callable) -> rows
472
+ * db.batch_query_ary(sql, params_array) { |rows| ... } -> changes
473
+ * db.batch_query_ary(sql, enumerable) { |rows| ... } -> changes
474
+ * db.batch_query_ary(sql, callable) { |rows| ... } -> changes
475
+ *
476
+ * Executes the given query for each list of parameters in the given paramter
477
+ * source. If a block is given, it is called with the resulting rows for each
478
+ * invocation of the query, and the total number of changes is returned.
479
+ * Otherwise, an array containing the resulting rows for each invocation is
480
+ * returned. Rows are represented as arrays.
481
+ *
482
+ * records = [
483
+ * [1, 2],
484
+ * [3, 4]
485
+ * ]
486
+ * db.batch_query_ary('insert into foo values (?, ?) returning bar, baz', records)
487
+ * #=> [[1, 2], [3, 4]]
488
+ * *
489
+ * @param sql [String] query SQL
490
+ * @param parameters [Array<Array, Hash>, Enumerable, Enumerator, Callable] parameters to run query with
491
+ * @return [Array<Array>, Integer] Total number of changes effected
492
+ */
493
+ VALUE Database_batch_query_ary(VALUE self, VALUE sql, VALUE parameters) {
494
+ Database_t *db = self_to_open_database(self);
495
+ sqlite3_stmt *stmt;
496
+
497
+ prepare_single_stmt(DB_GVL_MODE(db), db->sqlite3_db, &stmt, sql);
498
+ query_ctx ctx = QUERY_CTX(self, db, stmt, parameters, QUERY_MULTI_ROW, ALL_ROWS);
499
+
500
+ return rb_ensure(SAFE(safe_batch_query_ary), (VALUE)&ctx, SAFE(cleanup_stmt), (VALUE)&ctx);
501
+ }
502
+
503
+ /* call-seq:
504
+ * db.batch_query_single_column(sql, params_array) -> rows
505
+ * db.batch_query_single_column(sql, enumerable) -> rows
506
+ * db.batch_query_single_column(sql, callable) -> rows
507
+ * db.batch_query_single_column(sql, params_array) { |rows| ... } -> changes
508
+ * db.batch_query_single_column(sql, enumerable) { |rows| ... } -> changes
509
+ * db.batch_query_single_column(sql, callable) { |rows| ... } -> changes
510
+ *
511
+ * Executes the given query for each list of parameters in the given paramter
512
+ * source. If a block is given, it is called with the resulting rows for each
513
+ * invocation of the query, and the total number of changes is returned.
514
+ * Otherwise, an array containing the resulting rows for each invocation is
515
+ * returned. Rows are single values.
516
+ *
517
+ * records = [
518
+ * [1, 2],
519
+ * [3, 4]
520
+ * ]
521
+ * db.batch_query_ary('insert into foo values (?, ?) returning baz', records)
522
+ * #=> [2, 4]
523
+ * *
524
+ * @param sql [String] query SQL
525
+ * @param parameters [Array<Array, Hash>, Enumerable, Enumerator, Callable] parameters to run query with
526
+ * @return [Array<any>, Integer] Total number of changes effected
527
+ */
528
+ VALUE Database_batch_query_single_column(VALUE self, VALUE sql, VALUE parameters) {
529
+ Database_t *db = self_to_open_database(self);
530
+ sqlite3_stmt *stmt;
531
+
532
+ prepare_single_stmt(DB_GVL_MODE(db), db->sqlite3_db, &stmt, sql);
533
+ query_ctx ctx = QUERY_CTX(self, db, stmt, parameters, QUERY_MULTI_ROW, ALL_ROWS);
367
534
 
368
- return rb_ensure(SAFE(safe_execute_multi), (VALUE)&ctx, SAFE(cleanup_stmt), (VALUE)&ctx);
535
+ return rb_ensure(SAFE(safe_batch_query_single_column), (VALUE)&ctx, SAFE(cleanup_stmt), (VALUE)&ctx);
369
536
  }
370
537
 
371
538
  /* call-seq:
@@ -449,8 +616,10 @@ VALUE Database_load_extension(VALUE self, VALUE path) {
449
616
 
450
617
  /* call-seq:
451
618
  * db.prepare(sql) -> Extralite::Query
619
+ * db.prepare(sql, ...) -> Extralite::Query
452
620
  *
453
- * Creates a prepared statement with the given SQL query.
621
+ * Creates a prepared statement with the given SQL query. If query parameters
622
+ * are given, they are bound to the query.
454
623
  */
455
624
  VALUE Database_prepare(int argc, VALUE *argv, VALUE self) {
456
625
  rb_check_arity(argc, 1, UNLIMITED_ARGUMENTS);
@@ -697,7 +866,127 @@ VALUE Database_total_changes(VALUE self) {
697
866
  VALUE Database_trace(VALUE self) {
698
867
  Database_t *db = self_to_open_database(self);
699
868
 
700
- RB_OBJ_WRITE(self, &db->trace_block, rb_block_given_p() ? rb_block_proc() : Qnil);
869
+ RB_OBJ_WRITE(self, &db->trace_proc, rb_block_given_p() ? rb_block_proc() : Qnil);
870
+ return self;
871
+ }
872
+
873
+ #ifdef EXTRALITE_ENABLE_CHANGESET
874
+ /* Tracks changes to the database and returns a changeset. The changeset can
875
+ * then be used to store the changes to a file, apply them to another database,
876
+ * or undo the changes. The given table names specify which tables should be
877
+ * tracked for changes. Passing a value of nil causes all tables to be tracked.
878
+ *
879
+ * changeset = db.track_changes(:foo, :bar) do
880
+ * perform_a_bunch_of_queries
881
+ * end
882
+ *
883
+ * File.open('my.changes', 'w+') { |f| f << changeset.to_blob }
884
+ *
885
+ * @param table [String, Symbol] table to track
886
+ * @return [Extralite::Changeset] changeset
887
+ */
888
+ VALUE Database_track_changes(int argc, VALUE *argv, VALUE self) {
889
+ self_to_open_database(self);
890
+
891
+ VALUE changeset = rb_funcall(cChangeset, ID_new, 0);
892
+ VALUE tables = rb_ary_new_from_values(argc, argv);
893
+
894
+ rb_funcall(changeset, ID_track, 2, self, tables);
895
+
896
+ RB_GC_GUARD(changeset);
897
+ RB_GC_GUARD(tables);
898
+ return changeset;
899
+ }
900
+ #endif
901
+
902
+ int Database_progress_handler(void *ptr) {
903
+ Database_t *db = (Database_t *)ptr;
904
+ rb_funcall(db->progress_handler_proc, ID_call, 0);
905
+ return 0;
906
+ }
907
+
908
+ int Database_busy_handler(void *ptr, int v) {
909
+ Database_t *db = (Database_t *)ptr;
910
+ rb_funcall(db->progress_handler_proc, ID_call, 0);
911
+ return 1;
912
+ }
913
+
914
+ void Database_reset_progress_handler(VALUE self, Database_t *db) {
915
+ RB_OBJ_WRITE(self, &db->progress_handler_proc, Qnil);
916
+ sqlite3_progress_handler(db->sqlite3_db, 0, NULL, NULL);
917
+ sqlite3_busy_handler(db->sqlite3_db, NULL, NULL);
918
+ }
919
+
920
+ /* call-seq:
921
+ * db.on_progress(period) { } -> db
922
+ * db.on_progress(0) -> db
923
+ *
924
+ * Installs or removes a progress handler that will be executed periodically
925
+ * while a query is running. This method can be used to support switching
926
+ * between fibers and threads or implementing timeouts for running queries.
927
+ *
928
+ * The given period parameter specifies the approximate number of SQLite virtual
929
+ * machine instructions that are evaluated between successive invocations of the
930
+ * progress handler. A period of less than 1 removes the progress handler.
931
+ *
932
+ * The progress handler is called also when the database is busy. This lets the
933
+ * application perform work while waiting for the database to become unlocked,
934
+ * or implement a timeout. Note that setting the database's busy_timeout _after_
935
+ * setting a progress handler may lead to undefined behaviour in a concurrent
936
+ * application.
937
+ *
938
+ * When the progress handler is set, the gvl release threshold value is set to
939
+ * -1, which means that the GVL will not be released at all when preparing or
940
+ * running queries. It is the application's responsibility to let other threads
941
+ * or fibers run by calling e.g. Thread.pass:
942
+ *
943
+ * db.on_progress(1000) do
944
+ * do_something_interesting
945
+ * Thread.pass # let other threads run
946
+ * end
947
+ *
948
+ * Note that the progress handler is set globally for the database and that
949
+ * Extralite does provide any hooks for telling which queries are currently
950
+ * running or at what time they were started. This means that you'll need
951
+ * to wrap the stock #query_xxx and #execute methods with your own code that
952
+ * calculates timeouts, for example:
953
+ *
954
+ * def setup_progress_handler
955
+ * @db.on_progress(1000) do
956
+ * raise TimeoutError if Time.now - @t0 >= @timeout
957
+ * Thread.pass
958
+ * end
959
+ * end
960
+ *
961
+ * def query(sql, *)
962
+ * @t0 = Time.now
963
+ * @db.query(sql, *)
964
+ * end
965
+ *
966
+ * If the gvl release threshold is set to a value equal to or larger than 0
967
+ * after setting the progress handler, the progress handler will be reset.
968
+ *
969
+ * @param period [Integer] progress handler period
970
+ * @returns [Extralite::Database] database
971
+ */
972
+ VALUE Database_on_progress(VALUE self, VALUE period) {
973
+ Database_t *db = self_to_open_database(self);
974
+ int period_int = NUM2INT(period);
975
+
976
+ if (period_int > 0 && rb_block_given_p()) {
977
+ RB_OBJ_WRITE(self, &db->progress_handler_proc, rb_block_proc());
978
+ db->gvl_release_threshold = -1;
979
+
980
+ sqlite3_progress_handler(db->sqlite3_db, period_int, &Database_progress_handler, db);
981
+ sqlite3_busy_handler(db->sqlite3_db, &Database_busy_handler, db);
982
+ }
983
+ else {
984
+ RB_OBJ_WRITE(self, &db->progress_handler_proc, Qnil);
985
+ db->gvl_release_threshold = DEFAULT_GVL_RELEASE_THRESHOLD;
986
+ sqlite3_progress_handler(db->sqlite3_db, 0, NULL, NULL);
987
+ sqlite3_busy_handler(db->sqlite3_db, NULL, NULL);
988
+ }
989
+
701
990
  return self;
702
991
  }
703
992
 
@@ -777,8 +1066,16 @@ VALUE Database_gvl_release_threshold_set(VALUE self, VALUE value) {
777
1066
 
778
1067
  switch (TYPE(value)) {
779
1068
  case T_FIXNUM:
780
- db->gvl_release_threshold = NUM2INT(value);
781
- break;
1069
+ {
1070
+ int value_int = NUM2INT(value);
1071
+ if (value_int < -1)
1072
+ rb_raise(eArgumentError, "Invalid GVL release threshold value (expect integer >= -1)");
1073
+
1074
+ if (value_int > -1 && !NIL_P(db->progress_handler_proc))
1075
+ Database_reset_progress_handler(self, db);
1076
+ db->gvl_release_threshold = value_int;
1077
+ break;
1078
+ }
782
1079
  case T_NIL:
783
1080
  db->gvl_release_threshold = DEFAULT_GVL_RELEASE_THRESHOLD;
784
1081
  break;
@@ -811,7 +1108,10 @@ void Init_ExtraliteDatabase(void) {
811
1108
  #endif
812
1109
 
813
1110
  rb_define_method(cDatabase, "execute", Database_execute, -1);
814
- rb_define_method(cDatabase, "execute_multi", Database_execute_multi, 2);
1111
+ rb_define_method(cDatabase, "batch_execute", Database_batch_execute, 2);
1112
+ rb_define_method(cDatabase, "batch_query", Database_batch_query, 2);
1113
+ rb_define_method(cDatabase, "batch_query_ary", Database_batch_query_ary, 2);
1114
+ rb_define_method(cDatabase, "batch_query_single_column", Database_batch_query_single_column, 2);
815
1115
  rb_define_method(cDatabase, "filename", Database_filename, -1);
816
1116
  rb_define_method(cDatabase, "gvl_release_threshold", Database_gvl_release_threshold_get, 0);
817
1117
  rb_define_method(cDatabase, "gvl_release_threshold=", Database_gvl_release_threshold_set, 1);
@@ -820,6 +1120,7 @@ void Init_ExtraliteDatabase(void) {
820
1120
  rb_define_method(cDatabase, "interrupt", Database_interrupt, 0);
821
1121
  rb_define_method(cDatabase, "last_insert_rowid", Database_last_insert_rowid, 0);
822
1122
  rb_define_method(cDatabase, "limit", Database_limit, -1);
1123
+ rb_define_method(cDatabase, "on_progress", Database_on_progress, 1);
823
1124
  rb_define_method(cDatabase, "prepare", Database_prepare, -1);
824
1125
  rb_define_method(cDatabase, "query", Database_query_hash, -1);
825
1126
  rb_define_method(cDatabase, "query_ary", Database_query_ary, -1);
@@ -831,6 +1132,11 @@ void Init_ExtraliteDatabase(void) {
831
1132
  rb_define_method(cDatabase, "status", Database_status, -1);
832
1133
  rb_define_method(cDatabase, "total_changes", Database_total_changes, 0);
833
1134
  rb_define_method(cDatabase, "trace", Database_trace, 0);
1135
+
1136
+ #ifdef EXTRALITE_ENABLE_CHANGESET
1137
+ rb_define_method(cDatabase, "track_changes", Database_track_changes, -1);
1138
+ #endif
1139
+
834
1140
  rb_define_method(cDatabase, "transaction_active?", Database_transaction_active_p, 0);
835
1141
 
836
1142
  #ifdef HAVE_SQLITE3_LOAD_EXTENSION
@@ -849,12 +1155,22 @@ void Init_ExtraliteDatabase(void) {
849
1155
 
850
1156
  ID_bind = rb_intern("bind");
851
1157
  ID_call = rb_intern("call");
1158
+ ID_each = rb_intern("each");
852
1159
  ID_keys = rb_intern("keys");
853
1160
  ID_new = rb_intern("new");
854
1161
  ID_strip = rb_intern("strip");
1162
+ ID_to_s = rb_intern("to_s");
1163
+ ID_track = rb_intern("track");
1164
+
1165
+ SYM_gvl_release_threshold = ID2SYM(rb_intern("gvl_release_threshold"));
1166
+ SYM_read_only = ID2SYM(rb_intern("read_only"));
1167
+ SYM_synchronous = ID2SYM(rb_intern("synchronous"));
1168
+ SYM_wal_journal_mode = ID2SYM(rb_intern("wal_journal_mode"));
855
1169
 
856
- SYM_read_only = ID2SYM(rb_intern("read_only"));
1170
+ rb_gc_register_mark_object(SYM_gvl_release_threshold);
857
1171
  rb_gc_register_mark_object(SYM_read_only);
1172
+ rb_gc_register_mark_object(SYM_synchronous);
1173
+ rb_gc_register_mark_object(SYM_wal_journal_mode);
858
1174
 
859
1175
  UTF8_ENCODING = rb_utf8_encoding();
860
1176
  }
@@ -6,11 +6,17 @@ $CFLAGS << ' -Wno-undef'
6
6
  $CFLAGS << ' -Wno-discarded-qualifiers'
7
7
  $CFLAGS << ' -Wno-unused-function'
8
8
 
9
- $defs << "-DHAVE_SQLITE3_ENABLE_LOAD_EXTENSION"
10
- $defs << "-DHAVE_SQLITE3_LOAD_EXTENSION"
11
- $defs << "-DHAVE_SQLITE3_ERROR_OFFSET"
9
+ # enable the session extension
10
+ $defs << '-DSQLITE_ENABLE_SESSION'
11
+ $defs << '-DSQLITE_ENABLE_PREUPDATE_HOOK'
12
+ $defs << '-DEXTRALITE_ENABLE_CHANGESET'
12
13
 
13
- have_func('usleep')
14
+ $defs << '-DHAVE_SQLITE3_ENABLE_LOAD_EXTENSION'
15
+ $defs << '-DHAVE_SQLITE3_LOAD_EXTENSION'
16
+ $defs << '-DHAVE_SQLITE3_PREPARE_V2'
17
+ $defs << '-DHAVE_SQLITE3_ERROR_OFFSET'
18
+ $defs << '-DHAVE_SQLITE3SESSION_CHANGESET'
14
19
 
20
+ have_func('usleep')
15
21
  dir_config('extralite_ext')
16
22
  create_makefile('extralite_ext')
@@ -52,43 +52,47 @@ else
52
52
  $CFLAGS << ' -W3'
53
53
  end
54
54
 
55
- if RUBY_VERSION < '2.7'
56
- $CFLAGS << ' -DTAINTING_SUPPORT'
57
- end
58
-
59
55
  # @!visibility private
60
56
  def asplode missing
61
57
  if RUBY_PLATFORM =~ /mingw|mswin/
62
58
  abort "#{missing} is missing. Install SQLite3 from " +
63
59
  "http://www.sqlite.org/ first."
64
60
  else
65
- abort <<-error
66
- #{missing} is missing. Try 'brew install sqlite3',
67
- 'yum install sqlite-devel' or 'apt-get install libsqlite3-dev'
68
- and check your shared library search path (the
69
- location where your sqlite3 shared library is located).
70
- error
71
- end
61
+ abort <<~error
62
+ #{missing} is missing. Try 'brew install sqlite3',
63
+ 'yum install sqlite-devel' or 'apt-get install libsqlite3-dev'
64
+ and check your shared library search path (the location where
65
+ your sqlite3 shared library is located).
66
+ error
72
67
  end
68
+ end
73
69
 
74
- asplode('sqlite3.h') unless find_header 'sqlite3.h'
75
- find_library 'pthread', 'pthread_create' # 1.8 support. *shrug*
70
+ asplode('sqlite3.h') unless find_header('sqlite3.h')
71
+ # find_library 'pthread', 'pthread_create' # 1.8 support. *shrug*
76
72
 
77
- have_library 'dl' # for static builds
73
+ have_library 'dl' # for static builds
78
74
 
79
- if with_config('sqlcipher')
80
- asplode('sqlcipher') unless find_library 'sqlcipher', 'sqlite3_libversion_number'
81
- else
82
- asplode('sqlite3') unless find_library 'sqlite3', 'sqlite3_libversion_number'
83
- end
75
+ if with_config('sqlcipher')
76
+ asplode('sqlcipher') unless find_library 'sqlcipher', 'sqlite3_libversion_number'
77
+ else
78
+ asplode('sqlite3') unless find_library 'sqlite3', 'sqlite3_libversion_number'
79
+ end
80
+
81
+ have_func('sqlite3_enable_load_extension')
82
+ have_func('sqlite3_load_extension')
83
+ have_func('sqlite3_prepare_v2')
84
+ have_func('sqlite3_error_offset')
85
+ have_func('sqlite3session_changeset')
86
+
87
+ if have_type('sqlite3_session', 'sqlite.h')
88
+ $defs << '-DEXTRALITE_ENABLE_CHANGESET'
89
+ end
90
+ # have_macro('__SQLITESESSION_H_')
91
+ # have_macro('SQLITE3_H')
84
92
 
85
- have_func('sqlite3_enable_load_extension')
86
- have_func('sqlite3_load_extension')
87
- have_func('sqlite3_prepare_v2')
88
- have_func('sqlite3_error_offset')
89
93
 
90
- $defs << "-DEXTRALITE_NO_BUNDLE"
94
+ $defs << "-DEXTRALITE_NO_BUNDLE"
91
95
 
92
- dir_config('extralite_ext')
93
- create_makefile('extralite_ext')
94
- end
96
+ dir_config('extralite_ext')
97
+ create_makefile('extralite_ext')
98
+ end
@@ -23,6 +23,7 @@
23
23
  extern VALUE cDatabase;
24
24
  extern VALUE cQuery;
25
25
  extern VALUE cIterator;
26
+ extern VALUE cChangeset;
26
27
  extern VALUE cBlob;
27
28
 
28
29
  extern VALUE cError;
@@ -32,17 +33,21 @@ extern VALUE cInterruptError;
32
33
  extern VALUE cParameterError;
33
34
 
34
35
  extern ID ID_call;
36
+ extern ID ID_each;
35
37
  extern ID ID_keys;
36
38
  extern ID ID_new;
37
39
  extern ID ID_strip;
40
+ extern ID ID_to_s;
41
+ extern ID ID_track;
38
42
 
39
- extern VALUE SYM_hash;
40
43
  extern VALUE SYM_ary;
44
+ extern VALUE SYM_hash;
41
45
  extern VALUE SYM_single_column;
42
46
 
43
47
  typedef struct {
44
48
  sqlite3 *sqlite3_db;
45
- VALUE trace_block;
49
+ VALUE trace_proc;
50
+ VALUE progress_handler_proc;
46
51
  int gvl_release_threshold;
47
52
  } Database_t;
48
53
 
@@ -67,6 +72,13 @@ typedef struct {
67
72
  enum iterator_mode mode;
68
73
  } Iterator_t;
69
74
 
75
+ #ifdef EXTRALITE_ENABLE_CHANGESET
76
+ typedef struct {
77
+ int changeset_len;
78
+ void *changeset_ptr;
79
+ } Changeset_t;
80
+ #endif
81
+
70
82
  enum query_mode {
71
83
  QUERY_YIELD,
72
84
  QUERY_MULTI_ROW,
@@ -96,11 +108,18 @@ enum gvl_mode {
96
108
  #define MULTI_ROW_P(mode) (mode == QUERY_MULTI_ROW)
97
109
  #define QUERY_CTX(self, db, stmt, params, mode, max_rows) \
98
110
  { self, db->sqlite3_db, stmt, params, mode, max_rows, 0, db->gvl_release_threshold, 0 }
111
+ #define TRACE_SQL(db, sql) \
112
+ if (db->trace_proc != Qnil) rb_funcall(db->trace_proc, ID_call, 1, sql);
113
+
99
114
  #define DEFAULT_GVL_RELEASE_THRESHOLD 1000
100
115
 
116
+
101
117
  extern rb_encoding *UTF8_ENCODING;
102
118
 
103
- VALUE safe_execute_multi(query_ctx *ctx);
119
+ VALUE safe_batch_execute(query_ctx *ctx);
120
+ VALUE safe_batch_query(query_ctx *ctx);
121
+ VALUE safe_batch_query_ary(query_ctx *ctx);
122
+ VALUE safe_batch_query_single_column(query_ctx *ctx);
104
123
  VALUE safe_query_ary(query_ctx *ctx);
105
124
  VALUE safe_query_changes(query_ctx *ctx);
106
125
  VALUE safe_query_columns(query_ctx *ctx);
@@ -121,14 +140,15 @@ VALUE Query_to_a_hash(VALUE self);
121
140
  VALUE Query_to_a_ary(VALUE self);
122
141
  VALUE Query_to_a_single_column(VALUE self);
123
142
 
124
- void prepare_single_stmt(sqlite3 *db, sqlite3_stmt **stmt, VALUE sql);
125
- void prepare_multi_stmt(sqlite3 *db, sqlite3_stmt **stmt, VALUE sql);
143
+ void prepare_single_stmt(enum gvl_mode mode, sqlite3 *db, sqlite3_stmt **stmt, VALUE sql);
144
+ void prepare_multi_stmt(enum gvl_mode mode, sqlite3 *db, sqlite3_stmt **stmt, VALUE sql);
126
145
  void bind_all_parameters(sqlite3_stmt *stmt, int argc, VALUE *argv);
127
146
  void bind_all_parameters_from_object(sqlite3_stmt *stmt, VALUE obj);
128
147
  int stmt_iterate(query_ctx *ctx);
129
148
  VALUE cleanup_stmt(query_ctx *ctx);
130
149
 
131
150
  sqlite3 *Database_sqlite3_db(VALUE self);
151
+ enum gvl_mode Database_prepare_gvl_mode(Database_t *db);
132
152
  Database_t *self_to_database(VALUE self);
133
153
 
134
154
  void *gvl_call(enum gvl_mode mode, void *(*fn)(void *), void *data);