extralite 2.4 → 2.6

Sign up to get free protection for your applications and to get access to all the features.
@@ -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);