sqlite3 2.0.4-x86-linux-gnu → 2.1.0.rc1-x86-linux-gnu

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9fdfac5bb93255bc74be7a8c24cd77063ac87888e968841bd3048a08f1fe84d2
4
- data.tar.gz: 91b91bb9ebadc353897e22cb73e0c08d84d27ee0e56758b107e511835fc46f16
3
+ metadata.gz: a16b32b82de53a779e08dee8b72fa1e0ef35426e598710b67ffaa8f36e73d929
4
+ data.tar.gz: 4276af64a88ca8ce057983cc3c19760d850a6962284a612ddc69ef0a37995b5b
5
5
  SHA512:
6
- metadata.gz: edf4dc5bfcf971f2bba9b17de49df3cad694f81317d6243306ac5619738e2eaa011cb810537d8e3d0f44220b837b9d937189d6c032e2b7ad45ed7306a23c39d0
7
- data.tar.gz: 9c755db0c3e337e26a103c29e7ebb8962a647fd8920c4891d251abc13c42c5d83d5a534efd8a857bbf23a9d32a06f77d3fb9caa6d60a901b411691e79de6224f
6
+ metadata.gz: 238ad0289cfefa90fd12cfd0117c64d0bf322a4c03b74fc27e1c25c81d493e992eb1bf27384b1dce438f8faa3caae1cdaf66f17372d4bc3e1046eab733bb3ca8
7
+ data.tar.gz: ca0d6c86ebe2973eb661c9391a148f297d683ea783758047e7644bd2b3a7fd7751781e4d20fe51075c254bbba35c467f0578aa62db9d4678f077f30ed072c5e5
data/CHANGELOG.md CHANGED
@@ -1,5 +1,30 @@
1
1
  # sqlite3-ruby Changelog
2
2
 
3
+ ## prerelease 2.1.0.rc1 / 2024-09-18
4
+
5
+ ### Ruby
6
+
7
+ - This release drops support for Ruby 3.0. [#563] @flavorjones
8
+
9
+
10
+ ### Fork safety improvements
11
+
12
+ Sqlite itself is [not fork-safe](https://www.sqlite.org/howtocorrupt.html#_carrying_an_open_database_connection_across_a_fork_). Specifically, writing in a child process to a database connection that was created in the parent process may corrupt the database file. To mitigate this risk, sqlite3-ruby has implemented the following changes:
13
+
14
+ - All open writable database connections carried across a `fork()` will immediately be closed in the child process to mitigate the risk of corrupting the database file.
15
+ - These connections will be incompletely closed ("discarded") which will result in a one-time memory leak in the child process.
16
+
17
+ If it's at all possible, we strongly recommend that you close writable database connections in the parent before forking.
18
+
19
+ See the README "Fork Safety" section and `adr/2024-09-fork-safety.md` for more information. [#558] @flavorjones
20
+
21
+
22
+ ### Improved
23
+
24
+ - Use `sqlite3_close_v2` to close databases in a deferred manner if there are unclosed prepared statements. Previously closing a database while statements were open resulted in a `BusyException`. See https://www.sqlite.org/c3ref/close.html for more context. [#557] @flavorjones
25
+ - When setting a Database `busy_handler`, fire the write barrier to prevent potential crashes during the GC mark phase. [#556] @jhawthorn
26
+
27
+
3
28
  ## 2.0.4 / 2024-08-13
4
29
 
5
30
  ### Dependencies
data/CONTRIBUTING.md CHANGED
@@ -7,6 +7,11 @@ This doc is a short introduction on how to modify and maintain the sqlite3-ruby
7
7
 
8
8
  ## Architecture notes
9
9
 
10
+ ### Decision record
11
+
12
+ As of 2024-09, we're starting to keep some architecture decisions in the subdirectory `/adr`, so
13
+ please look there for additional information.
14
+
10
15
  ### Garbage collection
11
16
 
12
17
  All statements keep pointers back to their respective database connections.
data/FAQ.md CHANGED
@@ -207,48 +207,46 @@ Or do a `Database#prepare` to get the `Statement`, and then use either
207
207
  stmt.bind_params( "value", "name" => "bob" )
208
208
  ```
209
209
 
210
- ## How do I discover metadata about a query?
210
+ ## How do I discover metadata about a query result?
211
211
 
212
- If you ever want to know the names or types of the columns in a result
213
- set, you can do it in several ways.
212
+ IMPORTANT: `Database#execute` returns an Array of Array of Strings
213
+ which will have no metadata about the query or the result, such
214
+ as column names.
214
215
 
215
216
 
216
- The first way is to ask the row object itself. Each row will have a
217
- property "fields" that returns an array of the column names. The row
218
- will also have a property "types" that returns an array of the column
219
- types:
217
+ There are 2 main sources of query metadata:
220
218
 
221
-
222
- ```ruby
223
- rows = db.execute( "select * from table" )
224
- p rows[0].fields
225
- p rows[0].types
226
- ```
219
+ * `Statement`
220
+ * `ResultSet`
227
221
 
228
222
 
229
- Obviously, this approach requires you to execute a statement that actually
230
- returns data. If you don't know if the statement will return any rows, but
231
- you still need the metadata, you can use `Database#query` and ask the
232
- `ResultSet` object itself:
223
+ You can get a `Statement` via `Database#prepare`, and you can get
224
+ a `ResultSet` via `Statement#execute` or `Database#query`.
233
225
 
234
226
 
235
227
  ```ruby
236
- db.query( "select * from table" ) do |result|
237
- p result.columns
238
- p result.types
239
- ...
240
- end
241
- ```
242
-
243
-
244
- Lastly, you can use `Database#prepare` and ask the `Statement` object what
245
- the metadata are:
246
-
247
-
248
- ```ruby
249
- stmt = db.prepare( "select * from table" )
250
- p stmt.columns
251
- p stmt.types
228
+ sql = 'select * from table'
229
+
230
+ # No metadata
231
+ rows = db.execute(sql)
232
+ rows.class # => Array, no metadata
233
+ rows.first.class # => Array, no metadata
234
+ rows.first.first.class #=> String, no metadata
235
+
236
+ # Statement has metadata
237
+ stmt = db.prepare(sql)
238
+ stmt.columns # => [ ... ]
239
+ stmt.types # => [ ... ]
240
+
241
+ # ResultSet has metadata
242
+ results = stmt.execute
243
+ results.columns # => [ ... ]
244
+ results.types # => [ ... ]
245
+
246
+ # ResultSet has metadata
247
+ results = db.query(sql)
248
+ results.columns # => [ ... ]
249
+ results.types # => [ ... ]
252
250
  ```
253
251
 
254
252
  ## I'd like the rows to be indexible by column name.
@@ -273,7 +271,18 @@ is unavailable on the row, although the "types" property remains.)
273
271
  ```
274
272
 
275
273
 
276
- The other way is to use Ara Howard's
274
+ A more granular way to do this is via `ResultSet#next_hash` or
275
+ `ResultSet#each_hash`.
276
+
277
+
278
+ ```ruby
279
+ results = db.query( "select * from table" )
280
+ row = results.next_hash
281
+ p row['column1']
282
+ ```
283
+
284
+
285
+ Another way is to use Ara Howard's
277
286
  [`ArrayFields`](http://rubyforge.org/projects/arrayfields)
278
287
  module. Just `require "arrayfields"`, and all of your rows will be indexable
279
288
  by column name, even though they are still arrays!
data/INSTALLATION.md CHANGED
@@ -14,15 +14,13 @@ In v2.0.0 and later, native (precompiled) gems are available for recent Ruby ver
14
14
  - `arm-linux-gnu` (requires: glibc >= 2.29)
15
15
  - `arm-linux-musl`
16
16
  - `arm64-darwin`
17
- - `x64-mingw32` / `x64-mingw-ucrt`
17
+ - `x64-mingw-ucrt`
18
18
  - `x86-linux-gnu` (requires: glibc >= 2.17)
19
19
  - `x86-linux-musl`
20
20
  - `x86_64-darwin`
21
21
  - `x86_64-linux-gnu` (requires: glibc >= 2.17)
22
22
  - `x86_64-linux-musl`
23
23
 
24
- ⚠ Ruby 3.0 linux users must use Rubygems >= 3.3.22 in order to use these gems.
25
-
26
24
  ⚠ Musl linux users should update to Bundler >= 2.5.6 to avoid https://github.com/rubygems/rubygems/issues/7432
27
25
 
28
26
  If you are using one of these Ruby versions on one of these platforms, the native gem is the recommended way to install sqlite3-ruby.
data/README.md CHANGED
@@ -148,6 +148,23 @@ It is generally recommended that if applications want to share a database among
148
148
  threads, they _only_ share the database instance object. Other objects are
149
149
  fine to share, but may require manual locking for thread safety.
150
150
 
151
+
152
+ ## Fork Safety
153
+
154
+ [Sqlite is not fork
155
+ safe](https://www.sqlite.org/howtocorrupt.html#_carrying_an_open_database_connection_across_a_fork_)
156
+ and instructs users to not carry an open writable database connection across a `fork()`. Using an inherited
157
+ connection in the child may corrupt your database, leak memory, or cause other undefined behavior.
158
+
159
+ To help protect users of this gem from accidental corruption due to this lack of fork safety, the gem will immediately close any open writable databases in the child after a fork. Discarding writable
160
+ connections in the child will incur a small one-time memory leak per connection, but that's
161
+ preferable to potentially corrupting your database.
162
+
163
+ Whenever possible, close writable connections in the parent before forking.
164
+
165
+ See [./adr/2024-09-fork-safety.md](./adr/2024-09-fork-safety.md) for more information and context.
166
+
167
+
151
168
  ## Support
152
169
 
153
170
  ### Installation or database extensions
@@ -12,6 +12,63 @@
12
12
 
13
13
  VALUE cSqlite3Database;
14
14
 
15
+ /* See adr/2024-09-fork-safety.md */
16
+ static void
17
+ discard_db(sqlite3RubyPtr ctx)
18
+ {
19
+ sqlite3_file *sfile;
20
+ int status;
21
+
22
+ // release as much heap memory as possible by deallocating non-essential memory
23
+ // allocations held by the database library. Memory used to cache database pages to
24
+ // improve performance is an example of non-essential memory.
25
+ // on my development machine, this reduces the lost memory from 152k to 69k.
26
+ sqlite3_db_release_memory(ctx->db);
27
+
28
+ // release file descriptors
29
+ #ifdef HAVE_SQLITE3_DB_NAME
30
+ const char *db_name;
31
+ int j_db = 0;
32
+ while ((db_name = sqlite3_db_name(ctx->db, j_db)) != NULL) {
33
+ status = sqlite3_file_control(ctx->db, db_name, SQLITE_FCNTL_FILE_POINTER, &sfile);
34
+ if (status == 0 && sfile->pMethods != NULL) {
35
+ sfile->pMethods->xClose(sfile);
36
+ }
37
+ j_db++;
38
+ }
39
+ #else
40
+ status = sqlite3_file_control(ctx->db, NULL, SQLITE_FCNTL_FILE_POINTER, &sfile);
41
+ if (status == 0 && sfile->pMethods != NULL) {
42
+ sfile->pMethods->xClose(sfile);
43
+ }
44
+ #endif
45
+
46
+ status = sqlite3_file_control(ctx->db, NULL, SQLITE_FCNTL_JOURNAL_POINTER, &sfile);
47
+ if (status == 0 && sfile->pMethods != NULL) {
48
+ sfile->pMethods->xClose(sfile);
49
+ }
50
+
51
+ ctx->db = NULL;
52
+ }
53
+
54
+ static void
55
+ close_or_discard_db(sqlite3RubyPtr ctx)
56
+ {
57
+ if (ctx->db) {
58
+ int isReadonly = (ctx->flags & SQLITE_OPEN_READONLY);
59
+
60
+ if (isReadonly || ctx->owner == getpid()) {
61
+ // Ordinary close.
62
+ sqlite3_close_v2(ctx->db);
63
+ ctx->db = NULL;
64
+ } else {
65
+ // This is an open connection carried across a fork(). "Discard" it.
66
+ discard_db(ctx);
67
+ }
68
+ }
69
+ }
70
+
71
+
15
72
  static void
16
73
  database_mark(void *ctx)
17
74
  {
@@ -22,11 +79,8 @@ database_mark(void *ctx)
22
79
  static void
23
80
  deallocate(void *ctx)
24
81
  {
25
- sqlite3RubyPtr c = (sqlite3RubyPtr)ctx;
26
- sqlite3 *db = c->db;
27
-
28
- if (db) { sqlite3_close(db); }
29
- xfree(c);
82
+ close_or_discard_db((sqlite3RubyPtr)ctx);
83
+ xfree(ctx);
30
84
  }
31
85
 
32
86
  static size_t
@@ -51,7 +105,9 @@ static VALUE
51
105
  allocate(VALUE klass)
52
106
  {
53
107
  sqlite3RubyPtr ctx;
54
- return TypedData_Make_Struct(klass, sqlite3Ruby, &database_type, ctx);
108
+ VALUE object = TypedData_Make_Struct(klass, sqlite3Ruby, &database_type, ctx);
109
+ ctx->owner = getpid();
110
+ return object;
55
111
  }
56
112
 
57
113
  static char *
@@ -62,8 +118,6 @@ utf16_string_value_ptr(VALUE str)
62
118
  return RSTRING_PTR(str);
63
119
  }
64
120
 
65
- static VALUE sqlite3_rb_close(VALUE self);
66
-
67
121
  sqlite3RubyPtr
68
122
  sqlite3_database_unwrap(VALUE database)
69
123
  {
@@ -77,6 +131,7 @@ rb_sqlite3_open_v2(VALUE self, VALUE file, VALUE mode, VALUE zvfs)
77
131
  {
78
132
  sqlite3RubyPtr ctx;
79
133
  int status;
134
+ int flags;
80
135
 
81
136
  TypedData_Get_Struct(self, sqlite3Ruby, &database_type, ctx);
82
137
 
@@ -89,14 +144,16 @@ rb_sqlite3_open_v2(VALUE self, VALUE file, VALUE mode, VALUE zvfs)
89
144
  # endif
90
145
  #endif
91
146
 
147
+ flags = NUM2INT(mode);
92
148
  status = sqlite3_open_v2(
93
149
  StringValuePtr(file),
94
150
  &ctx->db,
95
- NUM2INT(mode),
151
+ flags,
96
152
  NIL_P(zvfs) ? NULL : StringValuePtr(zvfs)
97
153
  );
98
154
 
99
- CHECK(ctx->db, status)
155
+ CHECK(ctx->db, status);
156
+ ctx->flags = flags;
100
157
 
101
158
  return self;
102
159
  }
@@ -119,21 +176,38 @@ rb_sqlite3_disable_quirk_mode(VALUE self)
119
176
  #endif
120
177
  }
121
178
 
122
- /* call-seq: db.close
179
+ /*
180
+ * Close the database and release all associated resources.
181
+ *
182
+ * ⚠ Writable connections that are carried across a <tt>fork()</tt> are not completely
183
+ * closed. {Sqlite does not support forking}[https://www.sqlite.org/howtocorrupt.html],
184
+ * and fully closing a writable connection that has been carried across a fork may corrupt the
185
+ * database. Since it is an incomplete close, not all memory resources are freed, but this is safer
186
+ * than risking data loss.
123
187
  *
124
- * Closes this database.
188
+ * See rdoc-ref:adr/2024-09-fork-safety.md for more information on fork safety.
125
189
  */
126
190
  static VALUE
127
191
  sqlite3_rb_close(VALUE self)
128
192
  {
129
193
  sqlite3RubyPtr ctx;
130
- sqlite3 *db;
131
194
  TypedData_Get_Struct(self, sqlite3Ruby, &database_type, ctx);
132
195
 
133
- db = ctx->db;
134
- CHECK(db, sqlite3_close(ctx->db));
196
+ close_or_discard_db(ctx);
135
197
 
136
- ctx->db = NULL;
198
+ rb_iv_set(self, "-aggregators", Qnil);
199
+
200
+ return self;
201
+ }
202
+
203
+ /* private method, primarily for testing */
204
+ static VALUE
205
+ sqlite3_rb_discard(VALUE self)
206
+ {
207
+ sqlite3RubyPtr ctx;
208
+ TypedData_Get_Struct(self, sqlite3Ruby, &database_type, ctx);
209
+
210
+ discard_db(ctx);
137
211
 
138
212
  rb_iv_set(self, "-aggregators", Qnil);
139
213
 
@@ -246,7 +320,7 @@ busy_handler(int argc, VALUE *argv, VALUE self)
246
320
  rb_scan_args(argc, argv, "01", &block);
247
321
 
248
322
  if (NIL_P(block) && rb_block_given_p()) { block = rb_block_proc(); }
249
- ctx->busy_handler = block;
323
+ RB_OBJ_WRITE(self, &ctx->busy_handler, block);
250
324
 
251
325
  status = sqlite3_busy_handler(
252
326
  ctx->db,
@@ -871,6 +945,10 @@ rb_sqlite3_open16(VALUE self, VALUE file)
871
945
 
872
946
  status = sqlite3_open16(utf16_string_value_ptr(file), &ctx->db);
873
947
 
948
+ // these are the perm flags used implicitly by sqlite3_open16,
949
+ // see https://www.sqlite.org/capi3ref.html#sqlite3_open
950
+ ctx->flags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE;
951
+
874
952
  CHECK(ctx->db, status)
875
953
 
876
954
  return INT2NUM(status);
@@ -889,6 +967,7 @@ init_sqlite3_database(void)
889
967
  rb_define_private_method(cSqlite3Database, "open16", rb_sqlite3_open16, 1);
890
968
  rb_define_method(cSqlite3Database, "collation", collation, 2);
891
969
  rb_define_method(cSqlite3Database, "close", sqlite3_rb_close, 0);
970
+ rb_define_private_method(cSqlite3Database, "discard", sqlite3_rb_discard, 0);
892
971
  rb_define_method(cSqlite3Database, "closed?", closed_p, 0);
893
972
  rb_define_method(cSqlite3Database, "total_changes", total_changes, 0);
894
973
  rb_define_method(cSqlite3Database, "trace", trace, -1);
@@ -8,6 +8,8 @@ struct _sqlite3Ruby {
8
8
  VALUE busy_handler;
9
9
  int stmt_timeout;
10
10
  struct timespec stmt_deadline;
11
+ rb_pid_t owner;
12
+ int flags;
11
13
  };
12
14
 
13
15
  typedef struct _sqlite3Ruby sqlite3Ruby;
@@ -131,6 +131,8 @@ module Sqlite3
131
131
  end
132
132
 
133
133
  have_func("sqlite3_prepare_v2")
134
+ have_func("sqlite3_db_name", "sqlite3.h") # v3.39.0
135
+
134
136
  have_type("sqlite3_int64", "sqlite3.h")
135
137
  have_type("sqlite3_uint64", "sqlite3.h")
136
138
  end
@@ -4,6 +4,20 @@
4
4
  if(!_ctxt->st) \
5
5
  rb_raise(rb_path2class("SQLite3::Exception"), "cannot use a closed statement");
6
6
 
7
+ static void
8
+ require_open_db(VALUE stmt_rb)
9
+ {
10
+ VALUE closed_p = rb_funcall(
11
+ rb_iv_get(stmt_rb, "@connection"),
12
+ rb_intern("closed?"), 0);
13
+
14
+ if (RTEST(closed_p)) {
15
+ rb_raise(rb_path2class("SQLite3::Exception"),
16
+ "cannot use a statement associated with a closed database");
17
+ }
18
+ }
19
+
20
+
7
21
  VALUE cSqlite3Statement;
8
22
 
9
23
  static void
@@ -121,6 +135,7 @@ step(VALUE self)
121
135
 
122
136
  TypedData_Get_Struct(self, sqlite3StmtRuby, &statement_type, ctx);
123
137
 
138
+ require_open_db(self);
124
139
  REQUIRE_OPEN_STMT(ctx);
125
140
 
126
141
  if (ctx->done_p) { return Qnil; }
@@ -216,6 +231,8 @@ bind_param(VALUE self, VALUE key, VALUE value)
216
231
  int index;
217
232
 
218
233
  TypedData_Get_Struct(self, sqlite3StmtRuby, &statement_type, ctx);
234
+
235
+ require_open_db(self);
219
236
  REQUIRE_OPEN_STMT(ctx);
220
237
 
221
238
  switch (TYPE(key)) {
@@ -308,6 +325,8 @@ reset_bang(VALUE self)
308
325
  sqlite3StmtRubyPtr ctx;
309
326
 
310
327
  TypedData_Get_Struct(self, sqlite3StmtRuby, &statement_type, ctx);
328
+
329
+ require_open_db(self);
311
330
  REQUIRE_OPEN_STMT(ctx);
312
331
 
313
332
  sqlite3_reset(ctx->st);
@@ -328,6 +347,8 @@ clear_bindings_bang(VALUE self)
328
347
  sqlite3StmtRubyPtr ctx;
329
348
 
330
349
  TypedData_Get_Struct(self, sqlite3StmtRuby, &statement_type, ctx);
350
+
351
+ require_open_db(self);
331
352
  REQUIRE_OPEN_STMT(ctx);
332
353
 
333
354
  sqlite3_clear_bindings(ctx->st);
@@ -360,6 +381,8 @@ column_count(VALUE self)
360
381
  {
361
382
  sqlite3StmtRubyPtr ctx;
362
383
  TypedData_Get_Struct(self, sqlite3StmtRuby, &statement_type, ctx);
384
+
385
+ require_open_db(self);
363
386
  REQUIRE_OPEN_STMT(ctx);
364
387
 
365
388
  return INT2NUM(sqlite3_column_count(ctx->st));
@@ -391,6 +414,8 @@ column_name(VALUE self, VALUE index)
391
414
  const char *name;
392
415
 
393
416
  TypedData_Get_Struct(self, sqlite3StmtRuby, &statement_type, ctx);
417
+
418
+ require_open_db(self);
394
419
  REQUIRE_OPEN_STMT(ctx);
395
420
 
396
421
  name = sqlite3_column_name(ctx->st, (int)NUM2INT(index));
@@ -414,6 +439,8 @@ column_decltype(VALUE self, VALUE index)
414
439
  const char *name;
415
440
 
416
441
  TypedData_Get_Struct(self, sqlite3StmtRuby, &statement_type, ctx);
442
+
443
+ require_open_db(self);
417
444
  REQUIRE_OPEN_STMT(ctx);
418
445
 
419
446
  name = sqlite3_column_decltype(ctx->st, (int)NUM2INT(index));
@@ -431,6 +458,8 @@ bind_parameter_count(VALUE self)
431
458
  {
432
459
  sqlite3StmtRubyPtr ctx;
433
460
  TypedData_Get_Struct(self, sqlite3StmtRuby, &statement_type, ctx);
461
+
462
+ require_open_db(self);
434
463
  REQUIRE_OPEN_STMT(ctx);
435
464
 
436
465
  return INT2NUM(sqlite3_bind_parameter_count(ctx->st));
@@ -538,7 +567,10 @@ stats_as_hash(VALUE self)
538
567
  {
539
568
  sqlite3StmtRubyPtr ctx;
540
569
  TypedData_Get_Struct(self, sqlite3StmtRuby, &statement_type, ctx);
570
+
571
+ require_open_db(self);
541
572
  REQUIRE_OPEN_STMT(ctx);
573
+
542
574
  VALUE arg = rb_hash_new();
543
575
 
544
576
  stmt_stat_internal(arg, ctx->st);
@@ -554,6 +586,8 @@ stat_for(VALUE self, VALUE key)
554
586
  {
555
587
  sqlite3StmtRubyPtr ctx;
556
588
  TypedData_Get_Struct(self, sqlite3StmtRuby, &statement_type, ctx);
589
+
590
+ require_open_db(self);
557
591
  REQUIRE_OPEN_STMT(ctx);
558
592
 
559
593
  if (SYMBOL_P(key)) {
@@ -574,6 +608,8 @@ memused(VALUE self)
574
608
  {
575
609
  sqlite3StmtRubyPtr ctx;
576
610
  TypedData_Get_Struct(self, sqlite3StmtRuby, &statement_type, ctx);
611
+
612
+ require_open_db(self);
577
613
  REQUIRE_OPEN_STMT(ctx);
578
614
 
579
615
  return INT2NUM(sqlite3_stmt_status(ctx->st, SQLITE_STMTSTATUS_MEMUSED, 0));
@@ -591,6 +627,8 @@ database_name(VALUE self, VALUE index)
591
627
  {
592
628
  sqlite3StmtRubyPtr ctx;
593
629
  TypedData_Get_Struct(self, sqlite3StmtRuby, &statement_type, ctx);
630
+
631
+ require_open_db(self);
594
632
  REQUIRE_OPEN_STMT(ctx);
595
633
 
596
634
  return SQLITE3_UTF8_STR_NEW2(
@@ -608,6 +646,8 @@ get_sql(VALUE self)
608
646
  {
609
647
  sqlite3StmtRubyPtr ctx;
610
648
  TypedData_Get_Struct(self, sqlite3StmtRuby, &statement_type, ctx);
649
+
650
+ require_open_db(self);
611
651
  REQUIRE_OPEN_STMT(ctx);
612
652
 
613
653
  return rb_obj_freeze(SQLITE3_UTF8_STR_NEW2(sqlite3_sql(ctx->st)));
@@ -626,6 +666,8 @@ get_expanded_sql(VALUE self)
626
666
  VALUE rb_expanded_sql;
627
667
 
628
668
  TypedData_Get_Struct(self, sqlite3StmtRuby, &statement_type, ctx);
669
+
670
+ require_open_db(self);
629
671
  REQUIRE_OPEN_STMT(ctx);
630
672
 
631
673
  expanded_sql = sqlite3_expanded_sql(ctx->st);
Binary file
Binary file
Binary file
@@ -5,6 +5,7 @@ require "sqlite3/errors"
5
5
  require "sqlite3/pragmas"
6
6
  require "sqlite3/statement"
7
7
  require "sqlite3/value"
8
+ require "sqlite3/fork_safety"
8
9
 
9
10
  module SQLite3
10
11
  # The Database class encapsulates a single connection to a SQLite3 database.
@@ -127,7 +128,6 @@ module SQLite3
127
128
 
128
129
  @tracefunc = nil
129
130
  @authorizer = nil
130
- @busy_handler = nil
131
131
  @progress_handler = nil
132
132
  @collations = {}
133
133
  @functions = {}
@@ -135,6 +135,8 @@ module SQLite3
135
135
  @readonly = mode & Constants::Open::READONLY != 0
136
136
  @default_transaction_mode = options[:default_transaction_mode] || :deferred
137
137
 
138
+ ForkSafety.track(self)
139
+
138
140
  if block_given?
139
141
  begin
140
142
  yield self
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "weakref"
4
+
5
+ # based on Rails's active_support/fork_tracker.rb
6
+ module SQLite3
7
+ module ForkSafety
8
+ module CoreExt
9
+ def _fork
10
+ pid = super
11
+ if pid == 0
12
+ ForkSafety.discard
13
+ end
14
+ pid
15
+ end
16
+ end
17
+
18
+ @databases = []
19
+ @mutex = Mutex.new
20
+
21
+ class << self
22
+ def hook!
23
+ ::Process.singleton_class.prepend(CoreExt)
24
+ end
25
+
26
+ def track(database)
27
+ @mutex.synchronize do
28
+ @databases << WeakRef.new(database)
29
+ end
30
+ end
31
+
32
+ def discard
33
+ warned = false
34
+ @databases.each do |db|
35
+ next unless db.weakref_alive?
36
+
37
+ unless db.closed? || db.readonly?
38
+ unless warned
39
+ # If you are here, you may want to read
40
+ # https://github.com/sparklemotion/sqlite3-ruby/pull/558
41
+ warn("Writable sqlite database connection(s) were inherited from a forked process. " \
42
+ "This is unsafe and the connections are being closed to prevent possible data " \
43
+ "corruption. Please close writable sqlite database connections before forking.",
44
+ uplevel: 0)
45
+ warned = true
46
+ end
47
+ db.close
48
+ end
49
+ end
50
+ @databases.clear
51
+ end
52
+ end
53
+ end
54
+ end
55
+
56
+ SQLite3::ForkSafety.hook!
@@ -1,3 +1,3 @@
1
1
  module SQLite3
2
- VERSION = "2.0.4"
2
+ VERSION = "2.1.0.rc1"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sqlite3
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.4
4
+ version: 2.1.0.rc1
5
5
  platform: x86-linux-gnu
6
6
  authors:
7
7
  - Jamis Buck
@@ -11,7 +11,7 @@ authors:
11
11
  autorequire:
12
12
  bindir: bin
13
13
  cert_chain: []
14
- date: 2024-08-13 00:00:00.000000000 Z
14
+ date: 2024-09-18 00:00:00.000000000 Z
15
15
  dependencies: []
16
16
  description: |
17
17
  Ruby library to interface with the SQLite3 database engine (http://www.sqlite.org). Precompiled
@@ -52,13 +52,13 @@ files:
52
52
  - ext/sqlite3/statement.h
53
53
  - ext/sqlite3/timespec.h
54
54
  - lib/sqlite3.rb
55
- - lib/sqlite3/3.0/sqlite3_native.so
56
55
  - lib/sqlite3/3.1/sqlite3_native.so
57
56
  - lib/sqlite3/3.2/sqlite3_native.so
58
57
  - lib/sqlite3/3.3/sqlite3_native.so
59
58
  - lib/sqlite3/constants.rb
60
59
  - lib/sqlite3/database.rb
61
60
  - lib/sqlite3/errors.rb
61
+ - lib/sqlite3/fork_safety.rb
62
62
  - lib/sqlite3/pragmas.rb
63
63
  - lib/sqlite3/resultset.rb
64
64
  - lib/sqlite3/statement.rb
@@ -84,12 +84,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
84
84
  requirements:
85
85
  - - ">="
86
86
  - !ruby/object:Gem::Version
87
- version: '3.0'
87
+ version: '3.1'
88
88
  - - "<"
89
89
  - !ruby/object:Gem::Version
90
90
  version: 3.4.dev
91
91
  required_rubygems_version: !ruby/object:Gem::Requirement
92
92
  requirements:
93
+ - - ">"
94
+ - !ruby/object:Gem::Version
95
+ version: 1.3.1
93
96
  - - ">="
94
97
  - !ruby/object:Gem::Version
95
98
  version: 3.3.22
Binary file