sqlite3 2.0.4 → 2.1.0.rc3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +39 -0
- data/CONTRIBUTING.md +12 -12
- data/FAQ.md +43 -34
- data/INSTALLATION.md +1 -3
- data/README.md +17 -0
- data/ext/sqlite3/database.c +98 -17
- data/ext/sqlite3/database.h +6 -0
- data/ext/sqlite3/extconf.rb +2 -0
- data/ext/sqlite3/statement.c +38 -1
- data/ext/sqlite3/statement.h +1 -0
- data/lib/sqlite3/database.rb +3 -1
- data/lib/sqlite3/fork_safety.rb +62 -0
- data/lib/sqlite3/version.rb +1 -1
- metadata +5 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f625987268dc96e27d1a765bc4a1be109444b46ed293ae332e104c8a988a5682
|
4
|
+
data.tar.gz: 4e85e9d72c9a58569d4f9c54b2a2186c958ced8a354d09274c22d760ad2d895c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6fe89b6a8af14427cf475bbcd68d6a55086d9857c44e7e84d380f0a28cf1e0f0dcfb2512391c48f8eef3ea1a22ab6b9ff9e9557f352ef5648a29d8f2b4adc84e
|
7
|
+
data.tar.gz: f0857a167acb9a134d5d6119b8621d47fdcb91a0eef076624cc4b563088283d0ad0f66cf7bf336e9b117e33422cc87d3b7ee50499e7d580912e0921517d88d27
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,44 @@
|
|
1
1
|
# sqlite3-ruby Changelog
|
2
2
|
|
3
|
+
## prerelease 2.1.0.rc3 / 2024-09-18
|
4
|
+
|
5
|
+
### Improved
|
6
|
+
|
7
|
+
- Allow suppression of fork safety warnings. [#566] @flavorjones
|
8
|
+
|
9
|
+
|
10
|
+
## prerelease 2.1.0.rc2 / 2024-09-18
|
11
|
+
|
12
|
+
### Improved
|
13
|
+
|
14
|
+
- Address a performance regression in 2.1.0.rc1.
|
15
|
+
|
16
|
+
|
17
|
+
## prerelease 2.1.0.rc1 / 2024-09-18
|
18
|
+
|
19
|
+
### Ruby
|
20
|
+
|
21
|
+
- This release drops support for Ruby 3.0. [#563] @flavorjones
|
22
|
+
|
23
|
+
|
24
|
+
### Fork safety improvements
|
25
|
+
|
26
|
+
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:
|
27
|
+
|
28
|
+
- 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.
|
29
|
+
- These connections will be incompletely closed ("discarded") which will result in a one-time memory leak in the child process.
|
30
|
+
|
31
|
+
If it's at all possible, we strongly recommend that you close writable database connections in the parent before forking.
|
32
|
+
|
33
|
+
See the README "Fork Safety" section and `adr/2024-09-fork-safety.md` for more information. [#558] @flavorjones
|
34
|
+
|
35
|
+
|
36
|
+
### Improved
|
37
|
+
|
38
|
+
- 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
|
39
|
+
- When setting a Database `busy_handler`, fire the write barrier to prevent potential crashes during the GC mark phase. [#556] @jhawthorn
|
40
|
+
|
41
|
+
|
3
42
|
## 2.0.4 / 2024-08-13
|
4
43
|
|
5
44
|
### 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.
|
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
|
21
|
-
|
22
|
-
|
23
|
-
|
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
|
-
|
213
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
230
|
-
|
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
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
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
|
-
|
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-
|
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
|
data/ext/sqlite3/database.c
CHANGED
@@ -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
|
-
|
26
|
-
|
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
|
-
|
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
|
-
|
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
|
-
/*
|
182
|
+
/*
|
183
|
+
* Close the database and release all associated resources.
|
123
184
|
*
|
124
|
-
*
|
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
|
-
|
134
|
-
CHECK(db, sqlite3_close(ctx->db));
|
199
|
+
close_or_discard_db(ctx);
|
135
200
|
|
136
|
-
|
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
|
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);
|
data/ext/sqlite3/database.h
CHANGED
@@ -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;
|
data/ext/sqlite3/extconf.rb
CHANGED
data/ext/sqlite3/statement.c
CHANGED
@@ -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);
|
data/ext/sqlite3/statement.h
CHANGED
data/lib/sqlite3/database.rb
CHANGED
@@ -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,62 @@
|
|
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
|
+
@suppress = false
|
21
|
+
|
22
|
+
class << self
|
23
|
+
def hook!
|
24
|
+
::Process.singleton_class.prepend(CoreExt)
|
25
|
+
end
|
26
|
+
|
27
|
+
def track(database)
|
28
|
+
@mutex.synchronize do
|
29
|
+
@databases << WeakRef.new(database)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def discard
|
34
|
+
warned = @suppress
|
35
|
+
@databases.each do |db|
|
36
|
+
next unless db.weakref_alive?
|
37
|
+
|
38
|
+
unless db.closed? || db.readonly?
|
39
|
+
unless warned
|
40
|
+
# If you are here, you may want to read
|
41
|
+
# https://github.com/sparklemotion/sqlite3-ruby/pull/558
|
42
|
+
warn("Writable sqlite database connection(s) were inherited from a forked process. " \
|
43
|
+
"This is unsafe and the connections are being closed to prevent possible data " \
|
44
|
+
"corruption. Please close writable sqlite database connections before forking.",
|
45
|
+
uplevel: 0)
|
46
|
+
warned = true
|
47
|
+
end
|
48
|
+
db.close
|
49
|
+
end
|
50
|
+
end
|
51
|
+
@databases.clear
|
52
|
+
end
|
53
|
+
|
54
|
+
# Call to suppress the fork-related warnings.
|
55
|
+
def suppress_warnings!
|
56
|
+
@suppress = true
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
SQLite3::ForkSafety.hook!
|
data/lib/sqlite3/version.rb
CHANGED
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
|
+
version: 2.1.0.rc3
|
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-
|
14
|
+
date: 2024-09-24 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.
|
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.
|
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).
|