sqlite3 2.0.4 → 2.1.0.rc2

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: e6f21b08329df02d797b229d8f1cddd1dcc6b3d9239a07446798c02d0ecfe420
4
- data.tar.gz: 7d0b94143e5f0239e9870bc041cedcfe06dc3f02ccdd38890207a432ab4f8f08
3
+ metadata.gz: 0e0baabcefbf214965bc1e3b8d5629e6d1f88189b4d2b46c7478d3b84dcb5be6
4
+ data.tar.gz: 1eccf88cf4cf709a6d2ca405db8c8bcdd5873eafa3e59b65aedd9fa264699447
5
5
  SHA512:
6
- metadata.gz: 5ff806e2353d99c1e5eb642872d215f911cb952d7f6d34b326af2fb64bcd0ecafd84474499e352dd1e49cc35e8ffefa5658bf7c6e9bc0b4dfc46a2250c3cd300
7
- data.tar.gz: c1ed5e1a6716f82af63291450955c8afac317297beae7e9ba3acf2d75ff9de48ac022373de14a54a8d7c9294585015edf90e70261c719a73d3749f3e1a0f14d4
6
+ metadata.gz: 5842bc31c7491258b18d11b7393df8580535e8d6f45993d664439d31c74f7182429e2e8c1f0a88769a00221e32feae64b9aeacf993c44dafb54c9a98f780d755
7
+ data.tar.gz: 83d7603fc03a6306aa202402e7349ae4c0b409b92d429b95333fda06936e1baa987e3603e180de3871a3d4951a6064a11053e280dc3d3097ccc4a2deb1f89e00
data/CHANGELOG.md CHANGED
@@ -1,5 +1,37 @@
1
1
  # sqlite3-ruby Changelog
2
2
 
3
+ ## prerelease 2.1.0.rc2 / 2024-09-18
4
+
5
+ ### Improved
6
+
7
+ - Address a performance regression in 2.1.0.rc1.
8
+
9
+
10
+ ## prerelease 2.1.0.rc1 / 2024-09-18
11
+
12
+ ### Ruby
13
+
14
+ - This release drops support for Ruby 3.0. [#563] @flavorjones
15
+
16
+
17
+ ### Fork safety improvements
18
+
19
+ 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:
20
+
21
+ - 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.
22
+ - These connections will be incompletely closed ("discarded") which will result in a one-time memory leak in the child process.
23
+
24
+ If it's at all possible, we strongly recommend that you close writable database connections in the parent before forking.
25
+
26
+ See the README "Fork Safety" section and `adr/2024-09-fork-safety.md` for more information. [#558] @flavorjones
27
+
28
+
29
+ ### Improved
30
+
31
+ - 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
32
+ - When setting a Database `busy_handler`, fire the write barrier to prevent potential crashes during the GC mark phase. [#556] @jhawthorn
33
+
34
+
3
35
  ## 2.0.4 / 2024-08-13
4
36
 
5
37
  ### Dependencies
data/CONTRIBUTING.md CHANGED
@@ -7,24 +7,24 @@ 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.
13
18
  The `@connection` instance variable on the `Statement` handle keeps the database
14
- connection alive. Memory allocated for a statement handler will be freed in
15
- two cases:
16
-
17
- 1. `#close` is called on the statement
18
- 2. The `SQLite3::Database` object gets garbage collected
19
+ connection alive.
19
20
 
20
- We can't free the memory for the statement in the garbage collection function
21
- for the statement handler. The reason is because there exists a race
22
- condition. We cannot guarantee the order in which objects will be garbage
23
- collected. So, it is possible that a connection and a statement are up for
24
- garbage collection. If the database connection were to be free'd before the
25
- statement, then boom. Instead we'll be conservative and free unclosed
26
- statements when the connection is terminated.
21
+ We use `sqlite3_close_v2` in `Database#close` since v2.1.0 which defers _actually_ closing the
22
+ connection and freeing the underlying memory until all open statments are closed; though the
23
+ `Database` object will immediately behave as though it's been fully closed. If a Database is not
24
+ explicitly closed, it will be closed when it is GCed.
27
25
 
26
+ `Statement#close` finalizes the underlying statement. If a Statement is not explicitly closed, it
27
+ will be closed/finalized when it is GCed.
28
28
 
29
29
 
30
30
  ## Building gems
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,64 @@
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
+ ctx->flags |= SQLITE3_RB_DATABASE_DISCARDED;
53
+ }
54
+
55
+ static void
56
+ close_or_discard_db(sqlite3RubyPtr ctx)
57
+ {
58
+ if (ctx->db) {
59
+ int is_readonly = (ctx->flags & SQLITE3_RB_DATABASE_READONLY);
60
+
61
+ if (is_readonly || ctx->owner == getpid()) {
62
+ // Ordinary close.
63
+ sqlite3_close_v2(ctx->db);
64
+ ctx->db = NULL;
65
+ } else {
66
+ // This is an open connection carried across a fork(). "Discard" it.
67
+ discard_db(ctx);
68
+ }
69
+ }
70
+ }
71
+
72
+
15
73
  static void
16
74
  database_mark(void *ctx)
17
75
  {
@@ -22,11 +80,8 @@ database_mark(void *ctx)
22
80
  static void
23
81
  deallocate(void *ctx)
24
82
  {
25
- sqlite3RubyPtr c = (sqlite3RubyPtr)ctx;
26
- sqlite3 *db = c->db;
27
-
28
- if (db) { sqlite3_close(db); }
29
- xfree(c);
83
+ close_or_discard_db((sqlite3RubyPtr)ctx);
84
+ xfree(ctx);
30
85
  }
31
86
 
32
87
  static size_t
@@ -51,7 +106,9 @@ static VALUE
51
106
  allocate(VALUE klass)
52
107
  {
53
108
  sqlite3RubyPtr ctx;
54
- return TypedData_Make_Struct(klass, sqlite3Ruby, &database_type, ctx);
109
+ VALUE object = TypedData_Make_Struct(klass, sqlite3Ruby, &database_type, ctx);
110
+ ctx->owner = getpid();
111
+ return object;
55
112
  }
56
113
 
57
114
  static char *
@@ -62,8 +119,6 @@ utf16_string_value_ptr(VALUE str)
62
119
  return RSTRING_PTR(str);
63
120
  }
64
121
 
65
- static VALUE sqlite3_rb_close(VALUE self);
66
-
67
122
  sqlite3RubyPtr
68
123
  sqlite3_database_unwrap(VALUE database)
69
124
  {
@@ -77,6 +132,7 @@ rb_sqlite3_open_v2(VALUE self, VALUE file, VALUE mode, VALUE zvfs)
77
132
  {
78
133
  sqlite3RubyPtr ctx;
79
134
  int status;
135
+ int flags;
80
136
 
81
137
  TypedData_Get_Struct(self, sqlite3Ruby, &database_type, ctx);
82
138
 
@@ -89,14 +145,18 @@ rb_sqlite3_open_v2(VALUE self, VALUE file, VALUE mode, VALUE zvfs)
89
145
  # endif
90
146
  #endif
91
147
 
148
+ flags = NUM2INT(mode);
92
149
  status = sqlite3_open_v2(
93
150
  StringValuePtr(file),
94
151
  &ctx->db,
95
- NUM2INT(mode),
152
+ flags,
96
153
  NIL_P(zvfs) ? NULL : StringValuePtr(zvfs)
97
154
  );
98
155
 
99
- CHECK(ctx->db, status)
156
+ CHECK(ctx->db, status);
157
+ if (flags & SQLITE_OPEN_READONLY) {
158
+ ctx->flags |= SQLITE3_RB_DATABASE_READONLY;
159
+ }
100
160
 
101
161
  return self;
102
162
  }
@@ -119,21 +179,38 @@ rb_sqlite3_disable_quirk_mode(VALUE self)
119
179
  #endif
120
180
  }
121
181
 
122
- /* call-seq: db.close
182
+ /*
183
+ * Close the database and release all associated resources.
123
184
  *
124
- * Closes this database.
185
+ * Writable connections that are carried across a <tt>fork()</tt> are not completely
186
+ * closed. {Sqlite does not support forking}[https://www.sqlite.org/howtocorrupt.html],
187
+ * and fully closing a writable connection that has been carried across a fork may corrupt the
188
+ * database. Since it is an incomplete close, not all memory resources are freed, but this is safer
189
+ * than risking data loss.
190
+ *
191
+ * See rdoc-ref:adr/2024-09-fork-safety.md for more information on fork safety.
125
192
  */
126
193
  static VALUE
127
194
  sqlite3_rb_close(VALUE self)
128
195
  {
129
196
  sqlite3RubyPtr ctx;
130
- sqlite3 *db;
131
197
  TypedData_Get_Struct(self, sqlite3Ruby, &database_type, ctx);
132
198
 
133
- db = ctx->db;
134
- CHECK(db, sqlite3_close(ctx->db));
199
+ close_or_discard_db(ctx);
135
200
 
136
- ctx->db = NULL;
201
+ rb_iv_set(self, "-aggregators", Qnil);
202
+
203
+ return self;
204
+ }
205
+
206
+ /* private method, primarily for testing */
207
+ static VALUE
208
+ sqlite3_rb_discard(VALUE self)
209
+ {
210
+ sqlite3RubyPtr ctx;
211
+ TypedData_Get_Struct(self, sqlite3Ruby, &database_type, ctx);
212
+
213
+ discard_db(ctx);
137
214
 
138
215
  rb_iv_set(self, "-aggregators", Qnil);
139
216
 
@@ -246,7 +323,7 @@ busy_handler(int argc, VALUE *argv, VALUE self)
246
323
  rb_scan_args(argc, argv, "01", &block);
247
324
 
248
325
  if (NIL_P(block) && rb_block_given_p()) { block = rb_block_proc(); }
249
- ctx->busy_handler = block;
326
+ RB_OBJ_WRITE(self, &ctx->busy_handler, block);
250
327
 
251
328
  status = sqlite3_busy_handler(
252
329
  ctx->db,
@@ -869,6 +946,9 @@ rb_sqlite3_open16(VALUE self, VALUE file)
869
946
  #endif
870
947
  #endif
871
948
 
949
+ // sqlite3_open16 implicitly uses flags (SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE)
950
+ // see https://www.sqlite.org/capi3ref.html#sqlite3_open
951
+ // so we do not ever set SQLITE3_RB_DATABASE_READONLY in ctx->flags
872
952
  status = sqlite3_open16(utf16_string_value_ptr(file), &ctx->db);
873
953
 
874
954
  CHECK(ctx->db, status)
@@ -889,6 +969,7 @@ init_sqlite3_database(void)
889
969
  rb_define_private_method(cSqlite3Database, "open16", rb_sqlite3_open16, 1);
890
970
  rb_define_method(cSqlite3Database, "collation", collation, 2);
891
971
  rb_define_method(cSqlite3Database, "close", sqlite3_rb_close, 0);
972
+ rb_define_private_method(cSqlite3Database, "discard", sqlite3_rb_discard, 0);
892
973
  rb_define_method(cSqlite3Database, "closed?", closed_p, 0);
893
974
  rb_define_method(cSqlite3Database, "total_changes", total_changes, 0);
894
975
  rb_define_method(cSqlite3Database, "trace", trace, -1);
@@ -3,11 +3,17 @@
3
3
 
4
4
  #include <sqlite3_ruby.h>
5
5
 
6
+ /* bits in the `flags` field */
7
+ #define SQLITE3_RB_DATABASE_READONLY 0x01
8
+ #define SQLITE3_RB_DATABASE_DISCARDED 0x02
9
+
6
10
  struct _sqlite3Ruby {
7
11
  sqlite3 *db;
8
12
  VALUE busy_handler;
9
13
  int stmt_timeout;
10
14
  struct timespec stmt_deadline;
15
+ rb_pid_t owner;
16
+ int flags;
11
17
  };
12
18
 
13
19
  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
@@ -1,9 +1,13 @@
1
1
  #include <sqlite3_ruby.h>
2
2
 
3
3
  #define REQUIRE_OPEN_STMT(_ctxt) \
4
- if(!_ctxt->st) \
4
+ if (!_ctxt->st) \
5
5
  rb_raise(rb_path2class("SQLite3::Exception"), "cannot use a closed statement");
6
6
 
7
+ #define REQUIRE_LIVE_DB(_ctxt) \
8
+ if (_ctxt->db->flags & SQLITE3_RB_DATABASE_DISCARDED) \
9
+ rb_raise(rb_path2class("SQLite3::Exception"), "cannot use a statement associated with a discarded database");
10
+
7
11
  VALUE cSqlite3Statement;
8
12
 
9
13
  static void
@@ -57,6 +61,11 @@ prepare(VALUE self, VALUE db, VALUE sql)
57
61
 
58
62
  TypedData_Get_Struct(self, sqlite3StmtRuby, &statement_type, ctx);
59
63
 
64
+ /* Dereferencing a pointer to the database struct will be faster than accessing it through the
65
+ * instance variable @connection. The struct pointer is guaranteed to be live because instance
66
+ * variable will keep it from being GCed. */
67
+ ctx->db = db_ctx;
68
+
60
69
  #ifdef HAVE_SQLITE3_PREPARE_V2
61
70
  status = sqlite3_prepare_v2(
62
71
  #else
@@ -121,6 +130,7 @@ step(VALUE self)
121
130
 
122
131
  TypedData_Get_Struct(self, sqlite3StmtRuby, &statement_type, ctx);
123
132
 
133
+ REQUIRE_LIVE_DB(ctx);
124
134
  REQUIRE_OPEN_STMT(ctx);
125
135
 
126
136
  if (ctx->done_p) { return Qnil; }
@@ -216,6 +226,8 @@ bind_param(VALUE self, VALUE key, VALUE value)
216
226
  int index;
217
227
 
218
228
  TypedData_Get_Struct(self, sqlite3StmtRuby, &statement_type, ctx);
229
+
230
+ REQUIRE_LIVE_DB(ctx);
219
231
  REQUIRE_OPEN_STMT(ctx);
220
232
 
221
233
  switch (TYPE(key)) {
@@ -308,6 +320,8 @@ reset_bang(VALUE self)
308
320
  sqlite3StmtRubyPtr ctx;
309
321
 
310
322
  TypedData_Get_Struct(self, sqlite3StmtRuby, &statement_type, ctx);
323
+
324
+ REQUIRE_LIVE_DB(ctx);
311
325
  REQUIRE_OPEN_STMT(ctx);
312
326
 
313
327
  sqlite3_reset(ctx->st);
@@ -328,6 +342,8 @@ clear_bindings_bang(VALUE self)
328
342
  sqlite3StmtRubyPtr ctx;
329
343
 
330
344
  TypedData_Get_Struct(self, sqlite3StmtRuby, &statement_type, ctx);
345
+
346
+ REQUIRE_LIVE_DB(ctx);
331
347
  REQUIRE_OPEN_STMT(ctx);
332
348
 
333
349
  sqlite3_clear_bindings(ctx->st);
@@ -360,6 +376,8 @@ column_count(VALUE self)
360
376
  {
361
377
  sqlite3StmtRubyPtr ctx;
362
378
  TypedData_Get_Struct(self, sqlite3StmtRuby, &statement_type, ctx);
379
+
380
+ REQUIRE_LIVE_DB(ctx);
363
381
  REQUIRE_OPEN_STMT(ctx);
364
382
 
365
383
  return INT2NUM(sqlite3_column_count(ctx->st));
@@ -391,6 +409,8 @@ column_name(VALUE self, VALUE index)
391
409
  const char *name;
392
410
 
393
411
  TypedData_Get_Struct(self, sqlite3StmtRuby, &statement_type, ctx);
412
+
413
+ REQUIRE_LIVE_DB(ctx);
394
414
  REQUIRE_OPEN_STMT(ctx);
395
415
 
396
416
  name = sqlite3_column_name(ctx->st, (int)NUM2INT(index));
@@ -414,6 +434,8 @@ column_decltype(VALUE self, VALUE index)
414
434
  const char *name;
415
435
 
416
436
  TypedData_Get_Struct(self, sqlite3StmtRuby, &statement_type, ctx);
437
+
438
+ REQUIRE_LIVE_DB(ctx);
417
439
  REQUIRE_OPEN_STMT(ctx);
418
440
 
419
441
  name = sqlite3_column_decltype(ctx->st, (int)NUM2INT(index));
@@ -431,6 +453,8 @@ bind_parameter_count(VALUE self)
431
453
  {
432
454
  sqlite3StmtRubyPtr ctx;
433
455
  TypedData_Get_Struct(self, sqlite3StmtRuby, &statement_type, ctx);
456
+
457
+ REQUIRE_LIVE_DB(ctx);
434
458
  REQUIRE_OPEN_STMT(ctx);
435
459
 
436
460
  return INT2NUM(sqlite3_bind_parameter_count(ctx->st));
@@ -538,7 +562,10 @@ stats_as_hash(VALUE self)
538
562
  {
539
563
  sqlite3StmtRubyPtr ctx;
540
564
  TypedData_Get_Struct(self, sqlite3StmtRuby, &statement_type, ctx);
565
+
566
+ REQUIRE_LIVE_DB(ctx);
541
567
  REQUIRE_OPEN_STMT(ctx);
568
+
542
569
  VALUE arg = rb_hash_new();
543
570
 
544
571
  stmt_stat_internal(arg, ctx->st);
@@ -554,6 +581,8 @@ stat_for(VALUE self, VALUE key)
554
581
  {
555
582
  sqlite3StmtRubyPtr ctx;
556
583
  TypedData_Get_Struct(self, sqlite3StmtRuby, &statement_type, ctx);
584
+
585
+ REQUIRE_LIVE_DB(ctx);
557
586
  REQUIRE_OPEN_STMT(ctx);
558
587
 
559
588
  if (SYMBOL_P(key)) {
@@ -574,6 +603,8 @@ memused(VALUE self)
574
603
  {
575
604
  sqlite3StmtRubyPtr ctx;
576
605
  TypedData_Get_Struct(self, sqlite3StmtRuby, &statement_type, ctx);
606
+
607
+ REQUIRE_LIVE_DB(ctx);
577
608
  REQUIRE_OPEN_STMT(ctx);
578
609
 
579
610
  return INT2NUM(sqlite3_stmt_status(ctx->st, SQLITE_STMTSTATUS_MEMUSED, 0));
@@ -591,6 +622,8 @@ database_name(VALUE self, VALUE index)
591
622
  {
592
623
  sqlite3StmtRubyPtr ctx;
593
624
  TypedData_Get_Struct(self, sqlite3StmtRuby, &statement_type, ctx);
625
+
626
+ REQUIRE_LIVE_DB(ctx);
594
627
  REQUIRE_OPEN_STMT(ctx);
595
628
 
596
629
  return SQLITE3_UTF8_STR_NEW2(
@@ -608,6 +641,8 @@ get_sql(VALUE self)
608
641
  {
609
642
  sqlite3StmtRubyPtr ctx;
610
643
  TypedData_Get_Struct(self, sqlite3StmtRuby, &statement_type, ctx);
644
+
645
+ REQUIRE_LIVE_DB(ctx);
611
646
  REQUIRE_OPEN_STMT(ctx);
612
647
 
613
648
  return rb_obj_freeze(SQLITE3_UTF8_STR_NEW2(sqlite3_sql(ctx->st)));
@@ -626,6 +661,8 @@ get_expanded_sql(VALUE self)
626
661
  VALUE rb_expanded_sql;
627
662
 
628
663
  TypedData_Get_Struct(self, sqlite3StmtRuby, &statement_type, ctx);
664
+
665
+ REQUIRE_LIVE_DB(ctx);
629
666
  REQUIRE_OPEN_STMT(ctx);
630
667
 
631
668
  expanded_sql = sqlite3_expanded_sql(ctx->st);
@@ -5,6 +5,7 @@
5
5
 
6
6
  struct _sqlite3StmtRuby {
7
7
  sqlite3_stmt *st;
8
+ sqlite3Ruby *db;
8
9
  int done_p;
9
10
  };
10
11
 
@@ -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.rc2"
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.rc2
5
5
  platform: ruby
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-19 00:00:00.000000000 Z
15
15
  dependencies:
16
16
  - !ruby/object:Gem::Dependency
17
17
  name: mini_portile2
@@ -27,7 +27,6 @@ dependencies:
27
27
  - - "~>"
28
28
  - !ruby/object:Gem::Version
29
29
  version: 2.8.0
30
- force_ruby_platform: false
31
30
  description: |
32
31
  Ruby library to interface with the SQLite3 database engine (http://www.sqlite.org). Precompiled
33
32
  binaries are available for common platforms for recent versions of Ruby.
@@ -71,6 +70,7 @@ files:
71
70
  - lib/sqlite3/constants.rb
72
71
  - lib/sqlite3/database.rb
73
72
  - lib/sqlite3/errors.rb
73
+ - lib/sqlite3/fork_safety.rb
74
74
  - lib/sqlite3/pragmas.rb
75
75
  - lib/sqlite3/resultset.rb
76
76
  - lib/sqlite3/statement.rb
@@ -98,14 +98,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
98
98
  requirements:
99
99
  - - ">="
100
100
  - !ruby/object:Gem::Version
101
- version: '3.0'
101
+ version: '3.1'
102
102
  required_rubygems_version: !ruby/object:Gem::Requirement
103
103
  requirements:
104
104
  - - ">="
105
105
  - !ruby/object:Gem::Version
106
106
  version: '0'
107
107
  requirements: []
108
- rubygems_version: 3.5.11
108
+ rubygems_version: 3.5.16
109
109
  signing_key:
110
110
  specification_version: 4
111
111
  summary: Ruby library to interface with the SQLite3 database engine (http://www.sqlite.org).