aim-sqlite3 2.9.6.1.rc1-x86_64-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 +7 -0
- data/.gemtest +0 -0
- data/CHANGELOG.md +1062 -0
- data/CONTRIBUTING.md +130 -0
- data/FAQ.md +399 -0
- data/INSTALLATION.md +267 -0
- data/LICENSE +23 -0
- data/README.md +218 -0
- data/dependencies.yml +13 -0
- data/ext/sqlite3/aggregator.c +314 -0
- data/ext/sqlite3/aggregator.h +14 -0
- data/ext/sqlite3/backup.c +190 -0
- data/ext/sqlite3/backup.h +15 -0
- data/ext/sqlite3/database.c +1263 -0
- data/ext/sqlite3/database.h +36 -0
- data/ext/sqlite3/exception.c +122 -0
- data/ext/sqlite3/exception.h +12 -0
- data/ext/sqlite3/extconf.rb +299 -0
- data/ext/sqlite3/gvl.c +88 -0
- data/ext/sqlite3/gvl.h +15 -0
- data/ext/sqlite3/sqlite3.c +225 -0
- data/ext/sqlite3/sqlite3_ruby.h +49 -0
- data/ext/sqlite3/statement.c +755 -0
- data/ext/sqlite3/statement.h +17 -0
- data/ext/sqlite3/timespec.h +20 -0
- data/lib/sqlite3/3.2/sqlite3_native.so +0 -0
- data/lib/sqlite3/3.3/sqlite3_native.so +0 -0
- data/lib/sqlite3/3.4/sqlite3_native.so +0 -0
- data/lib/sqlite3/4.0/sqlite3_native.so +0 -0
- data/lib/sqlite3/cli.rb +8 -0
- data/lib/sqlite3/constants.rb +198 -0
- data/lib/sqlite3/database.rb +798 -0
- data/lib/sqlite3/errors.rb +88 -0
- data/lib/sqlite3/fork_safety.rb +66 -0
- data/lib/sqlite3/pragmas.rb +648 -0
- data/lib/sqlite3/resultset.rb +96 -0
- data/lib/sqlite3/statement.rb +208 -0
- data/lib/sqlite3/value.rb +54 -0
- data/lib/sqlite3/version.rb +4 -0
- data/lib/sqlite3/version_info.rb +17 -0
- data/lib/sqlite3.rb +20 -0
- data/ports/x86_64-linux-gnu/sqlite3/3.53.2/bin/sqlite3 +0 -0
- metadata +105 -0
|
@@ -0,0 +1,1263 @@
|
|
|
1
|
+
#include <sqlite3_ruby.h>
|
|
2
|
+
#include <aggregator.h>
|
|
3
|
+
|
|
4
|
+
#ifdef _MSC_VER
|
|
5
|
+
#pragma warning( push )
|
|
6
|
+
#pragma warning( disable : 4028 )
|
|
7
|
+
#endif
|
|
8
|
+
|
|
9
|
+
#define REQUIRE_OPEN_DB(_ctxt) \
|
|
10
|
+
if(!_ctxt->db) \
|
|
11
|
+
rb_raise(rb_path2class("SQLite3::Exception"), "cannot use a closed database");
|
|
12
|
+
|
|
13
|
+
#define REQUIRE_CLOSED_DB(_ctxt) \
|
|
14
|
+
if(_ctxt->db) \
|
|
15
|
+
rb_raise(rb_path2class("SQLite3::Exception"), "cannot open a database that is already open");
|
|
16
|
+
|
|
17
|
+
VALUE cSqlite3Database;
|
|
18
|
+
|
|
19
|
+
/* See adr/2024-09-fork-safety.md */
|
|
20
|
+
static void
|
|
21
|
+
discard_db(sqlite3RubyPtr ctx)
|
|
22
|
+
{
|
|
23
|
+
sqlite3_file *sfile;
|
|
24
|
+
int status;
|
|
25
|
+
|
|
26
|
+
// release as much heap memory as possible by deallocating non-essential memory
|
|
27
|
+
// allocations held by the database library. Memory used to cache database pages to
|
|
28
|
+
// improve performance is an example of non-essential memory.
|
|
29
|
+
// on my development machine, this reduces the lost memory from 152k to 69k.
|
|
30
|
+
sqlite3_db_release_memory(ctx->db);
|
|
31
|
+
|
|
32
|
+
// release file descriptors
|
|
33
|
+
#ifdef HAVE_SQLITE3_DB_NAME
|
|
34
|
+
const char *db_name;
|
|
35
|
+
int j_db = 0;
|
|
36
|
+
while ((db_name = sqlite3_db_name(ctx->db, j_db)) != NULL) {
|
|
37
|
+
status = sqlite3_file_control(ctx->db, db_name, SQLITE_FCNTL_FILE_POINTER, &sfile);
|
|
38
|
+
if (status == 0 && sfile->pMethods != NULL) {
|
|
39
|
+
sfile->pMethods->xClose(sfile);
|
|
40
|
+
}
|
|
41
|
+
j_db++;
|
|
42
|
+
}
|
|
43
|
+
#else
|
|
44
|
+
status = sqlite3_file_control(ctx->db, NULL, SQLITE_FCNTL_FILE_POINTER, &sfile);
|
|
45
|
+
if (status == 0 && sfile->pMethods != NULL) {
|
|
46
|
+
sfile->pMethods->xClose(sfile);
|
|
47
|
+
}
|
|
48
|
+
#endif
|
|
49
|
+
|
|
50
|
+
status = sqlite3_file_control(ctx->db, NULL, SQLITE_FCNTL_JOURNAL_POINTER, &sfile);
|
|
51
|
+
if (status == 0 && sfile->pMethods != NULL) {
|
|
52
|
+
sfile->pMethods->xClose(sfile);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
ctx->db = NULL;
|
|
56
|
+
ctx->flags |= SQLITE3_RB_DATABASE_DISCARDED;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
static void
|
|
60
|
+
close_or_discard_db(sqlite3RubyPtr ctx)
|
|
61
|
+
{
|
|
62
|
+
if (ctx->db) {
|
|
63
|
+
int is_readonly = (ctx->flags & SQLITE3_RB_DATABASE_READONLY);
|
|
64
|
+
|
|
65
|
+
if (is_readonly || ctx->owner == getpid()) {
|
|
66
|
+
// Ordinary close.
|
|
67
|
+
sqlite3_close_v2(ctx->db);
|
|
68
|
+
ctx->db = NULL;
|
|
69
|
+
} else {
|
|
70
|
+
// This is an open connection carried across a fork(). "Discard" it.
|
|
71
|
+
discard_db(ctx);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
void
|
|
78
|
+
rb_sqlite3_pin_array_and_contents(VALUE ary)
|
|
79
|
+
{
|
|
80
|
+
long i;
|
|
81
|
+
|
|
82
|
+
if (NIL_P(ary) || !ary) { return; }
|
|
83
|
+
|
|
84
|
+
rb_gc_mark(ary);
|
|
85
|
+
for (i = 0; i < RARRAY_LEN(ary); i++) {
|
|
86
|
+
rb_gc_mark(RARRAY_AREF(ary, i));
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
static int
|
|
91
|
+
pin_hash_value(VALUE UNUSED(key), VALUE value, VALUE UNUSED(arg))
|
|
92
|
+
{
|
|
93
|
+
rb_gc_mark(value);
|
|
94
|
+
return ST_CONTINUE;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
static void
|
|
98
|
+
pin_hash_and_contents(VALUE hash)
|
|
99
|
+
{
|
|
100
|
+
if (NIL_P(hash) || !hash) { return; }
|
|
101
|
+
|
|
102
|
+
rb_gc_mark(hash);
|
|
103
|
+
rb_hash_foreach(hash, pin_hash_value, 0);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/* Each wrapper also owns live aggregate instances, whose VALUEs sqlite keeps in
|
|
107
|
+
* its own aggregate contexts. */
|
|
108
|
+
static void
|
|
109
|
+
pin_aggregators(VALUE aggregators)
|
|
110
|
+
{
|
|
111
|
+
long i;
|
|
112
|
+
|
|
113
|
+
rb_sqlite3_pin_array_and_contents(aggregators);
|
|
114
|
+
|
|
115
|
+
if (NIL_P(aggregators) || !aggregators) { return; }
|
|
116
|
+
|
|
117
|
+
for (i = 0; i < RARRAY_LEN(aggregators); i++) {
|
|
118
|
+
rb_sqlite3_aggregator_pin_instances(RARRAY_AREF(aggregators, i));
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
static void
|
|
123
|
+
database_mark(void *ctx)
|
|
124
|
+
{
|
|
125
|
+
sqlite3RubyPtr c = (sqlite3RubyPtr)ctx;
|
|
126
|
+
|
|
127
|
+
/* sqlite holds raw pointers to these, so they must not move. */
|
|
128
|
+
rb_gc_mark(c->busy_handler);
|
|
129
|
+
rb_gc_mark(c->trace_handler);
|
|
130
|
+
rb_gc_mark(c->authorizer);
|
|
131
|
+
|
|
132
|
+
rb_sqlite3_pin_array_and_contents(c->functions);
|
|
133
|
+
pin_hash_and_contents(c->collations);
|
|
134
|
+
pin_aggregators(c->aggregators);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
static void
|
|
138
|
+
deallocate(void *ctx)
|
|
139
|
+
{
|
|
140
|
+
close_or_discard_db((sqlite3RubyPtr)ctx);
|
|
141
|
+
xfree(ctx);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
static size_t
|
|
145
|
+
database_memsize(const void *ctx)
|
|
146
|
+
{
|
|
147
|
+
const sqlite3RubyPtr c = (const sqlite3RubyPtr)ctx;
|
|
148
|
+
// NB: can't account for ctx->db because the type is incomplete.
|
|
149
|
+
return sizeof(*c);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
static const rb_data_type_t database_type = {
|
|
153
|
+
.wrap_struct_name = "SQLite3::Backup",
|
|
154
|
+
.function = {
|
|
155
|
+
.dmark = database_mark,
|
|
156
|
+
.dfree = deallocate,
|
|
157
|
+
.dsize = database_memsize,
|
|
158
|
+
},
|
|
159
|
+
.flags = RUBY_TYPED_WB_PROTECTED, // Not freed immediately because the dfree function do IOs.
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
static VALUE
|
|
163
|
+
allocate(VALUE klass)
|
|
164
|
+
{
|
|
165
|
+
sqlite3RubyPtr ctx;
|
|
166
|
+
VALUE object = TypedData_Make_Struct(klass, sqlite3Ruby, &database_type, ctx);
|
|
167
|
+
ctx->owner = getpid();
|
|
168
|
+
return object;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
static char *
|
|
172
|
+
utf16_string_value_ptr(VALUE str)
|
|
173
|
+
{
|
|
174
|
+
VALUE utf16str, codepoints;
|
|
175
|
+
|
|
176
|
+
StringValue(str);
|
|
177
|
+
|
|
178
|
+
utf16str = rb_str_dup(str);
|
|
179
|
+
if (!UTF16_LE_P(utf16str) && !UTF16_BE_P(utf16str)) {
|
|
180
|
+
/* an untagged string (the utf16 option) holds native-byte-order UTF-16 */
|
|
181
|
+
const union { uint16_t u16; uint8_t u8[2]; } native = { 1 };
|
|
182
|
+
rb_enc_associate_index(utf16str, rb_enc_find_index(native.u8[0] ? "UTF-16LE" : "UTF-16BE"));
|
|
183
|
+
}
|
|
184
|
+
codepoints = rb_funcall(utf16str, rb_intern("codepoints"), 0);
|
|
185
|
+
if (RTEST(rb_funcall(codepoints, rb_intern("include?"), 1, INT2FIX(0)))) {
|
|
186
|
+
rb_raise(rb_eArgError, "string contains null char");
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
rb_str_buf_cat(str, "\x00\x00", 2L);
|
|
190
|
+
return RSTRING_PTR(str);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
sqlite3RubyPtr
|
|
194
|
+
sqlite3_database_unwrap(VALUE database)
|
|
195
|
+
{
|
|
196
|
+
sqlite3RubyPtr ctx;
|
|
197
|
+
TypedData_Get_Struct(database, sqlite3Ruby, &database_type, ctx);
|
|
198
|
+
return ctx;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
static VALUE
|
|
202
|
+
rb_sqlite3_open_v2(VALUE self, VALUE file, VALUE mode, VALUE zvfs)
|
|
203
|
+
{
|
|
204
|
+
sqlite3RubyPtr ctx;
|
|
205
|
+
int status;
|
|
206
|
+
int flags;
|
|
207
|
+
|
|
208
|
+
TypedData_Get_Struct(self, sqlite3Ruby, &database_type, ctx);
|
|
209
|
+
REQUIRE_CLOSED_DB(ctx);
|
|
210
|
+
|
|
211
|
+
#if defined TAINTING_SUPPORT
|
|
212
|
+
# if defined StringValueCStr
|
|
213
|
+
StringValuePtr(file);
|
|
214
|
+
rb_check_safe_obj(file);
|
|
215
|
+
# else
|
|
216
|
+
Check_SafeStr(file);
|
|
217
|
+
# endif
|
|
218
|
+
#endif
|
|
219
|
+
|
|
220
|
+
flags = NUM2INT(mode);
|
|
221
|
+
status = sqlite3_open_v2(
|
|
222
|
+
StringValueCStr(file),
|
|
223
|
+
&ctx->db,
|
|
224
|
+
flags,
|
|
225
|
+
NIL_P(zvfs) ? NULL : StringValueCStr(zvfs)
|
|
226
|
+
);
|
|
227
|
+
|
|
228
|
+
if (status != SQLITE_OK) {
|
|
229
|
+
char *msg = sqlite3_mprintf("%s", sqlite3_errmsg(ctx->db));
|
|
230
|
+
sqlite3_close_v2(ctx->db);
|
|
231
|
+
ctx->db = NULL;
|
|
232
|
+
CHECK_MSG(ctx->db, status, msg);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
if (flags & SQLITE_OPEN_READONLY) {
|
|
236
|
+
ctx->flags |= SQLITE3_RB_DATABASE_READONLY;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
return self;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
static VALUE
|
|
243
|
+
rb_sqlite3_disable_quirk_mode(VALUE self)
|
|
244
|
+
{
|
|
245
|
+
#if defined SQLITE_DBCONFIG_DQS_DDL
|
|
246
|
+
sqlite3RubyPtr ctx;
|
|
247
|
+
TypedData_Get_Struct(self, sqlite3Ruby, &database_type, ctx);
|
|
248
|
+
|
|
249
|
+
if (!ctx->db) { return Qfalse; }
|
|
250
|
+
|
|
251
|
+
sqlite3_db_config(ctx->db, SQLITE_DBCONFIG_DQS_DDL, 0, (void *)0);
|
|
252
|
+
sqlite3_db_config(ctx->db, SQLITE_DBCONFIG_DQS_DML, 0, (void *)0);
|
|
253
|
+
|
|
254
|
+
return Qtrue;
|
|
255
|
+
#else
|
|
256
|
+
return Qfalse;
|
|
257
|
+
#endif
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/*
|
|
261
|
+
* Close the database and release all associated resources.
|
|
262
|
+
*
|
|
263
|
+
* ⚠ Writable connections that are carried across a <tt>fork()</tt> are not completely
|
|
264
|
+
* closed. {Sqlite does not support forking}[https://www.sqlite.org/howtocorrupt.html],
|
|
265
|
+
* and fully closing a writable connection that has been carried across a fork may corrupt the
|
|
266
|
+
* database. Since it is an incomplete close, not all memory resources are freed, but this is safer
|
|
267
|
+
* than risking data loss.
|
|
268
|
+
*
|
|
269
|
+
* See rdoc-ref:adr/2024-09-fork-safety.md for more information on fork safety.
|
|
270
|
+
*/
|
|
271
|
+
static VALUE
|
|
272
|
+
sqlite3_rb_close(VALUE self)
|
|
273
|
+
{
|
|
274
|
+
sqlite3RubyPtr ctx;
|
|
275
|
+
TypedData_Get_Struct(self, sqlite3Ruby, &database_type, ctx);
|
|
276
|
+
|
|
277
|
+
close_or_discard_db(ctx);
|
|
278
|
+
|
|
279
|
+
return self;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/* private method, primarily for testing */
|
|
283
|
+
static VALUE
|
|
284
|
+
sqlite3_rb_discard(VALUE self)
|
|
285
|
+
{
|
|
286
|
+
sqlite3RubyPtr ctx;
|
|
287
|
+
TypedData_Get_Struct(self, sqlite3Ruby, &database_type, ctx);
|
|
288
|
+
|
|
289
|
+
discard_db(ctx);
|
|
290
|
+
|
|
291
|
+
return self;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/* call-seq: db.closed?
|
|
295
|
+
*
|
|
296
|
+
* Returns +true+ if this database instance has been closed (see #close).
|
|
297
|
+
*/
|
|
298
|
+
static VALUE
|
|
299
|
+
closed_p(VALUE self)
|
|
300
|
+
{
|
|
301
|
+
sqlite3RubyPtr ctx;
|
|
302
|
+
TypedData_Get_Struct(self, sqlite3Ruby, &database_type, ctx);
|
|
303
|
+
|
|
304
|
+
if (!ctx->db) { return Qtrue; }
|
|
305
|
+
|
|
306
|
+
return Qfalse;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/* call-seq: total_changes
|
|
310
|
+
*
|
|
311
|
+
* Returns the total number of changes made to this database instance
|
|
312
|
+
* since it was opened.
|
|
313
|
+
*/
|
|
314
|
+
static VALUE
|
|
315
|
+
total_changes(VALUE self)
|
|
316
|
+
{
|
|
317
|
+
sqlite3RubyPtr ctx;
|
|
318
|
+
TypedData_Get_Struct(self, sqlite3Ruby, &database_type, ctx);
|
|
319
|
+
REQUIRE_OPEN_DB(ctx);
|
|
320
|
+
|
|
321
|
+
return INT2NUM(sqlite3_total_changes(ctx->db));
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
static void
|
|
325
|
+
tracefunc(void *data, const char *sql)
|
|
326
|
+
{
|
|
327
|
+
sqlite3RubyPtr ctx = (sqlite3RubyPtr)data;
|
|
328
|
+
rb_funcall(ctx->trace_handler, rb_intern("call"), 1, rb_str_new2(sql));
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
typedef struct {
|
|
332
|
+
void *data;
|
|
333
|
+
const char *sql;
|
|
334
|
+
} tracefunc_args_t;
|
|
335
|
+
|
|
336
|
+
static void *
|
|
337
|
+
tracefunc_body(void *ptr)
|
|
338
|
+
{
|
|
339
|
+
tracefunc_args_t *args = (tracefunc_args_t *)ptr;
|
|
340
|
+
tracefunc(args->data, args->sql);
|
|
341
|
+
return NULL;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
static void
|
|
345
|
+
tracefunc_gvl(void *data, const char *sql)
|
|
346
|
+
{
|
|
347
|
+
tracefunc_args_t args = { data, sql };
|
|
348
|
+
rb_sqlite3_with_gvl(tracefunc_body, &args);
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
/* call-seq:
|
|
352
|
+
* trace { |sql| ... }
|
|
353
|
+
* trace(Class.new { def call sql; end }.new)
|
|
354
|
+
*
|
|
355
|
+
* Installs (or removes) a block that will be invoked for every SQL
|
|
356
|
+
* statement executed. The block receives one parameter: the SQL statement
|
|
357
|
+
* executed. If the block is +nil+, any existing tracer will be uninstalled.
|
|
358
|
+
*/
|
|
359
|
+
static VALUE
|
|
360
|
+
trace(int argc, VALUE *argv, VALUE self)
|
|
361
|
+
{
|
|
362
|
+
sqlite3RubyPtr ctx;
|
|
363
|
+
VALUE block;
|
|
364
|
+
|
|
365
|
+
TypedData_Get_Struct(self, sqlite3Ruby, &database_type, ctx);
|
|
366
|
+
REQUIRE_OPEN_DB(ctx);
|
|
367
|
+
|
|
368
|
+
rb_scan_args(argc, argv, "01", &block);
|
|
369
|
+
|
|
370
|
+
if (NIL_P(block) && rb_block_given_p()) { block = rb_block_proc(); }
|
|
371
|
+
|
|
372
|
+
RB_OBJ_WRITE(self, &ctx->trace_handler, block);
|
|
373
|
+
|
|
374
|
+
sqlite3_trace(ctx->db, NIL_P(block) ? NULL : tracefunc_gvl, (void *)ctx);
|
|
375
|
+
|
|
376
|
+
return self;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
static int
|
|
380
|
+
rb_sqlite3_busy_handler(void *context, int count)
|
|
381
|
+
{
|
|
382
|
+
sqlite3RubyPtr ctx = (sqlite3RubyPtr)context;
|
|
383
|
+
|
|
384
|
+
VALUE handle = ctx->busy_handler;
|
|
385
|
+
VALUE result = rb_funcall(handle, rb_intern("call"), 1, INT2NUM(count));
|
|
386
|
+
|
|
387
|
+
if (Qfalse == result) { return 0; }
|
|
388
|
+
|
|
389
|
+
return 1;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
typedef struct {
|
|
393
|
+
void *context;
|
|
394
|
+
int count;
|
|
395
|
+
} busy_handler_args_t;
|
|
396
|
+
|
|
397
|
+
static void *
|
|
398
|
+
rb_sqlite3_busy_handler_body(void *ptr)
|
|
399
|
+
{
|
|
400
|
+
busy_handler_args_t *args = (busy_handler_args_t *)ptr;
|
|
401
|
+
return (void *)(intptr_t)rb_sqlite3_busy_handler(args->context, args->count);
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
static int
|
|
405
|
+
rb_sqlite3_busy_handler_gvl(void *context, int count)
|
|
406
|
+
{
|
|
407
|
+
busy_handler_args_t args = { context, count };
|
|
408
|
+
return (int)(intptr_t)rb_sqlite3_with_gvl(rb_sqlite3_busy_handler_body, &args);
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
/* call-seq:
|
|
412
|
+
* busy_handler { |count| ... }
|
|
413
|
+
* busy_handler(Class.new { def call count; end }.new)
|
|
414
|
+
*
|
|
415
|
+
* Register a busy handler with this database instance. When a requested
|
|
416
|
+
* resource is busy, this handler will be invoked. If the handler returns
|
|
417
|
+
* +false+, the operation will be aborted; otherwise, the resource will
|
|
418
|
+
* be requested again.
|
|
419
|
+
*
|
|
420
|
+
* The handler will be invoked with the name of the resource that was
|
|
421
|
+
* busy, and the number of times it has been retried.
|
|
422
|
+
*
|
|
423
|
+
* See also the mutually exclusive #busy_timeout.
|
|
424
|
+
*/
|
|
425
|
+
static VALUE
|
|
426
|
+
busy_handler(int argc, VALUE *argv, VALUE self)
|
|
427
|
+
{
|
|
428
|
+
sqlite3RubyPtr ctx;
|
|
429
|
+
VALUE block;
|
|
430
|
+
int status;
|
|
431
|
+
|
|
432
|
+
TypedData_Get_Struct(self, sqlite3Ruby, &database_type, ctx);
|
|
433
|
+
REQUIRE_OPEN_DB(ctx);
|
|
434
|
+
|
|
435
|
+
rb_scan_args(argc, argv, "01", &block);
|
|
436
|
+
|
|
437
|
+
if (NIL_P(block) && rb_block_given_p()) { block = rb_block_proc(); }
|
|
438
|
+
RB_OBJ_WRITE(self, &ctx->busy_handler, block);
|
|
439
|
+
|
|
440
|
+
status = sqlite3_busy_handler(
|
|
441
|
+
ctx->db,
|
|
442
|
+
NIL_P(block) ? NULL : rb_sqlite3_busy_handler_gvl,
|
|
443
|
+
(void *)ctx
|
|
444
|
+
);
|
|
445
|
+
|
|
446
|
+
CHECK(ctx->db, status);
|
|
447
|
+
|
|
448
|
+
return self;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
static int
|
|
452
|
+
rb_sqlite3_statement_timeout(void *context)
|
|
453
|
+
{
|
|
454
|
+
sqlite3RubyPtr ctx = (sqlite3RubyPtr)context;
|
|
455
|
+
struct timespec currentTime;
|
|
456
|
+
clock_gettime(CLOCK_MONOTONIC, ¤tTime);
|
|
457
|
+
|
|
458
|
+
if (!timespecisset(&ctx->stmt_deadline)) {
|
|
459
|
+
// Set stmt_deadline if not already set
|
|
460
|
+
ctx->stmt_deadline = currentTime;
|
|
461
|
+
ctx->stmt_deadline.tv_sec += ctx->stmt_timeout / 1000;
|
|
462
|
+
ctx->stmt_deadline.tv_nsec += (ctx->stmt_timeout % 1000) * 1000000L;
|
|
463
|
+
if (ctx->stmt_deadline.tv_nsec >= 1000000000L) {
|
|
464
|
+
ctx->stmt_deadline.tv_sec++;
|
|
465
|
+
ctx->stmt_deadline.tv_nsec -= 1000000000L;
|
|
466
|
+
}
|
|
467
|
+
} else if (timespecafter(¤tTime, &ctx->stmt_deadline)) {
|
|
468
|
+
return 1;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
return 0;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
/* call-seq: db.statement_timeout = ms
|
|
475
|
+
*
|
|
476
|
+
* Indicates that if a query lasts longer than the indicated number of
|
|
477
|
+
* milliseconds, SQLite should interrupt that query and return an error.
|
|
478
|
+
* By default, SQLite does not interrupt queries. To restore the default
|
|
479
|
+
* behavior, send 0 as the +ms+ parameter.
|
|
480
|
+
*/
|
|
481
|
+
static VALUE
|
|
482
|
+
set_statement_timeout(VALUE self, VALUE milliseconds)
|
|
483
|
+
{
|
|
484
|
+
sqlite3RubyPtr ctx;
|
|
485
|
+
TypedData_Get_Struct(self, sqlite3Ruby, &database_type, ctx);
|
|
486
|
+
|
|
487
|
+
ctx->stmt_timeout = NUM2INT(milliseconds);
|
|
488
|
+
int n = NUM2INT(milliseconds) == 0 ? -1 : 1000;
|
|
489
|
+
|
|
490
|
+
sqlite3_progress_handler(ctx->db, n, rb_sqlite3_statement_timeout, (void *)ctx);
|
|
491
|
+
|
|
492
|
+
return self;
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
/* call-seq: last_insert_row_id
|
|
496
|
+
*
|
|
497
|
+
* Obtains the unique row ID of the last row to be inserted by this Database
|
|
498
|
+
* instance.
|
|
499
|
+
*/
|
|
500
|
+
static VALUE
|
|
501
|
+
last_insert_row_id(VALUE self)
|
|
502
|
+
{
|
|
503
|
+
sqlite3RubyPtr ctx;
|
|
504
|
+
TypedData_Get_Struct(self, sqlite3Ruby, &database_type, ctx);
|
|
505
|
+
REQUIRE_OPEN_DB(ctx);
|
|
506
|
+
|
|
507
|
+
return LL2NUM(sqlite3_last_insert_rowid(ctx->db));
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
VALUE
|
|
511
|
+
sqlite3val2rb(sqlite3_value *val)
|
|
512
|
+
{
|
|
513
|
+
VALUE rb_val;
|
|
514
|
+
|
|
515
|
+
switch (sqlite3_value_type(val)) {
|
|
516
|
+
case SQLITE_INTEGER:
|
|
517
|
+
rb_val = LL2NUM(sqlite3_value_int64(val));
|
|
518
|
+
break;
|
|
519
|
+
case SQLITE_FLOAT:
|
|
520
|
+
rb_val = rb_float_new(sqlite3_value_double(val));
|
|
521
|
+
break;
|
|
522
|
+
case SQLITE_TEXT: {
|
|
523
|
+
const char *text = (const char *)sqlite3_value_text(val);
|
|
524
|
+
int len = sqlite3_value_bytes(val);
|
|
525
|
+
rb_val = rb_utf8_str_new(text, len);
|
|
526
|
+
rb_obj_freeze(rb_val);
|
|
527
|
+
break;
|
|
528
|
+
}
|
|
529
|
+
case SQLITE_BLOB: {
|
|
530
|
+
int len = sqlite3_value_bytes(val);
|
|
531
|
+
rb_val = rb_str_new((const char *)sqlite3_value_blob(val), len);
|
|
532
|
+
rb_obj_freeze(rb_val);
|
|
533
|
+
break;
|
|
534
|
+
}
|
|
535
|
+
case SQLITE_NULL:
|
|
536
|
+
rb_val = Qnil;
|
|
537
|
+
break;
|
|
538
|
+
default:
|
|
539
|
+
rb_raise(rb_eRuntimeError, "bad type");
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
return rb_val;
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
void
|
|
546
|
+
set_sqlite3_func_result(sqlite3_context *ctx, VALUE result)
|
|
547
|
+
{
|
|
548
|
+
switch (TYPE(result)) {
|
|
549
|
+
case T_NIL:
|
|
550
|
+
sqlite3_result_null(ctx);
|
|
551
|
+
break;
|
|
552
|
+
case T_FIXNUM:
|
|
553
|
+
sqlite3_result_int64(ctx, (sqlite3_int64)FIX2LONG(result));
|
|
554
|
+
break;
|
|
555
|
+
case T_BIGNUM: {
|
|
556
|
+
#if SIZEOF_LONG < 8
|
|
557
|
+
sqlite3_int64 num64;
|
|
558
|
+
|
|
559
|
+
if (bignum_to_int64(result, &num64)) {
|
|
560
|
+
sqlite3_result_int64(ctx, num64);
|
|
561
|
+
break;
|
|
562
|
+
}
|
|
563
|
+
#endif
|
|
564
|
+
}
|
|
565
|
+
case T_FLOAT:
|
|
566
|
+
sqlite3_result_double(ctx, NUM2DBL(result));
|
|
567
|
+
break;
|
|
568
|
+
case T_STRING:
|
|
569
|
+
if (CLASS_OF(result) == cSqlite3Blob
|
|
570
|
+
|| rb_enc_get_index(result) == rb_ascii8bit_encindex()
|
|
571
|
+
) {
|
|
572
|
+
sqlite3_result_blob(
|
|
573
|
+
ctx,
|
|
574
|
+
(const void *)StringValuePtr(result),
|
|
575
|
+
(int)RSTRING_LEN(result),
|
|
576
|
+
SQLITE_TRANSIENT
|
|
577
|
+
);
|
|
578
|
+
} else {
|
|
579
|
+
sqlite3_result_text(
|
|
580
|
+
ctx,
|
|
581
|
+
(const char *)StringValuePtr(result),
|
|
582
|
+
(int)RSTRING_LEN(result),
|
|
583
|
+
SQLITE_TRANSIENT
|
|
584
|
+
);
|
|
585
|
+
}
|
|
586
|
+
break;
|
|
587
|
+
default:
|
|
588
|
+
rb_raise(rb_eRuntimeError, "can't return %s",
|
|
589
|
+
rb_class2name(CLASS_OF(result)));
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
typedef struct {
|
|
594
|
+
sqlite3_context *ctx;
|
|
595
|
+
int argc;
|
|
596
|
+
sqlite3_value **argv;
|
|
597
|
+
} rb_sqlite3_func_args_t;
|
|
598
|
+
|
|
599
|
+
static VALUE
|
|
600
|
+
rb_sqlite3_func_protected(VALUE func_args_value)
|
|
601
|
+
{
|
|
602
|
+
rb_sqlite3_func_args_t *args = (rb_sqlite3_func_args_t *)func_args_value;
|
|
603
|
+
VALUE callable = (VALUE)sqlite3_user_data(args->ctx);
|
|
604
|
+
VALUE params = rb_ary_new2(args->argc);
|
|
605
|
+
VALUE result;
|
|
606
|
+
int i;
|
|
607
|
+
|
|
608
|
+
for (i = 0; i < args->argc; i++) {
|
|
609
|
+
VALUE param = sqlite3val2rb(args->argv[i]);
|
|
610
|
+
rb_ary_push(params, param);
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
result = rb_apply(callable, rb_intern("call"), params);
|
|
614
|
+
|
|
615
|
+
set_sqlite3_func_result(args->ctx, result);
|
|
616
|
+
|
|
617
|
+
return Qnil;
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
static void
|
|
621
|
+
rb_sqlite3_func(sqlite3_context *ctx, int argc, sqlite3_value **argv)
|
|
622
|
+
{
|
|
623
|
+
rb_sqlite3_func_args_t args = { .ctx = ctx, .argc = argc, .argv = argv };
|
|
624
|
+
int exc_status;
|
|
625
|
+
|
|
626
|
+
rb_protect(rb_sqlite3_func_protected, (VALUE)&args, &exc_status);
|
|
627
|
+
|
|
628
|
+
if (exc_status) {
|
|
629
|
+
/* the user should never see this message, because Statement#step will
|
|
630
|
+
* re-raise the exception still in rb_errinfo */
|
|
631
|
+
sqlite3_result_error(ctx, "Ruby Exception occurred", -1);
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
static void *
|
|
636
|
+
rb_sqlite3_func_body(void *ptr)
|
|
637
|
+
{
|
|
638
|
+
rb_sqlite3_func_args_t *args = (rb_sqlite3_func_args_t *)ptr;
|
|
639
|
+
rb_sqlite3_func(args->ctx, args->argc, args->argv);
|
|
640
|
+
return NULL;
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
static void
|
|
644
|
+
rb_sqlite3_func_gvl(sqlite3_context *ctx, int argc, sqlite3_value **argv)
|
|
645
|
+
{
|
|
646
|
+
rb_sqlite3_func_args_t args = { .ctx = ctx, .argc = argc, .argv = argv };
|
|
647
|
+
rb_sqlite3_with_gvl(rb_sqlite3_func_body, &args);
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
#ifndef HAVE_RB_PROC_ARITY
|
|
651
|
+
int
|
|
652
|
+
rb_proc_arity(VALUE self)
|
|
653
|
+
{
|
|
654
|
+
return (int)NUM2INT(rb_funcall(self, rb_intern("arity"), 0));
|
|
655
|
+
}
|
|
656
|
+
#endif
|
|
657
|
+
|
|
658
|
+
/* call-seq: define_function_with_flags(name, flags) { |args,...| }
|
|
659
|
+
*
|
|
660
|
+
* Define a function named +name+ with +args+ using TextRep bitflags +flags+. The arity of the block
|
|
661
|
+
* will be used as the arity for the function defined.
|
|
662
|
+
*/
|
|
663
|
+
static VALUE
|
|
664
|
+
define_function_with_flags(VALUE self, VALUE name, VALUE flags)
|
|
665
|
+
{
|
|
666
|
+
sqlite3RubyPtr ctx;
|
|
667
|
+
VALUE block, functions;
|
|
668
|
+
int status;
|
|
669
|
+
|
|
670
|
+
TypedData_Get_Struct(self, sqlite3Ruby, &database_type, ctx);
|
|
671
|
+
REQUIRE_OPEN_DB(ctx);
|
|
672
|
+
|
|
673
|
+
block = rb_block_proc();
|
|
674
|
+
|
|
675
|
+
status = sqlite3_create_function(
|
|
676
|
+
ctx->db,
|
|
677
|
+
StringValuePtr(name),
|
|
678
|
+
rb_proc_arity(block),
|
|
679
|
+
NUM2INT(flags),
|
|
680
|
+
(void *)block,
|
|
681
|
+
rb_sqlite3_func_gvl,
|
|
682
|
+
NULL,
|
|
683
|
+
NULL
|
|
684
|
+
);
|
|
685
|
+
|
|
686
|
+
CHECK(ctx->db, status);
|
|
687
|
+
|
|
688
|
+
functions = rb_iv_get(self, "@functions");
|
|
689
|
+
rb_ary_push(functions, block);
|
|
690
|
+
RB_OBJ_WRITE(self, &ctx->functions, functions);
|
|
691
|
+
|
|
692
|
+
return self;
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
/* call-seq: define_function(name) { |args,...| }
|
|
696
|
+
*
|
|
697
|
+
* Define a function named +name+ with +args+. The arity of the block
|
|
698
|
+
* will be used as the arity for the function defined.
|
|
699
|
+
*/
|
|
700
|
+
static VALUE
|
|
701
|
+
define_function(VALUE self, VALUE name)
|
|
702
|
+
{
|
|
703
|
+
return define_function_with_flags(self, name, INT2FIX(SQLITE_UTF8));
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
/* call-seq: interrupt
|
|
707
|
+
*
|
|
708
|
+
* Interrupts the currently executing operation, causing it to abort.
|
|
709
|
+
*/
|
|
710
|
+
static VALUE
|
|
711
|
+
interrupt(VALUE self)
|
|
712
|
+
{
|
|
713
|
+
sqlite3RubyPtr ctx;
|
|
714
|
+
TypedData_Get_Struct(self, sqlite3Ruby, &database_type, ctx);
|
|
715
|
+
REQUIRE_OPEN_DB(ctx);
|
|
716
|
+
|
|
717
|
+
sqlite3_interrupt(ctx->db);
|
|
718
|
+
|
|
719
|
+
return self;
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
/* call-seq: errmsg
|
|
723
|
+
*
|
|
724
|
+
* Return a string describing the last error to have occurred with this
|
|
725
|
+
* database.
|
|
726
|
+
*/
|
|
727
|
+
static VALUE
|
|
728
|
+
errmsg(VALUE self)
|
|
729
|
+
{
|
|
730
|
+
sqlite3RubyPtr ctx;
|
|
731
|
+
TypedData_Get_Struct(self, sqlite3Ruby, &database_type, ctx);
|
|
732
|
+
REQUIRE_OPEN_DB(ctx);
|
|
733
|
+
|
|
734
|
+
return rb_str_new2(sqlite3_errmsg(ctx->db));
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
/* call-seq: errcode
|
|
738
|
+
*
|
|
739
|
+
* Return an integer representing the last error to have occurred with this
|
|
740
|
+
* database.
|
|
741
|
+
*/
|
|
742
|
+
static VALUE
|
|
743
|
+
errcode_(VALUE self)
|
|
744
|
+
{
|
|
745
|
+
sqlite3RubyPtr ctx;
|
|
746
|
+
TypedData_Get_Struct(self, sqlite3Ruby, &database_type, ctx);
|
|
747
|
+
REQUIRE_OPEN_DB(ctx);
|
|
748
|
+
|
|
749
|
+
return INT2NUM(sqlite3_errcode(ctx->db));
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
/* call-seq: complete?(sql)
|
|
753
|
+
*
|
|
754
|
+
* Return +true+ if the string is a valid (ie, parsable) SQL statement, and
|
|
755
|
+
* +false+ otherwise.
|
|
756
|
+
*/
|
|
757
|
+
static VALUE
|
|
758
|
+
complete_p(VALUE UNUSED(self), VALUE sql)
|
|
759
|
+
{
|
|
760
|
+
if (sqlite3_complete(StringValuePtr(sql))) {
|
|
761
|
+
return Qtrue;
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
return Qfalse;
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
/* call-seq: changes
|
|
768
|
+
*
|
|
769
|
+
* Returns the number of changes made to this database instance by the last
|
|
770
|
+
* operation performed. Note that a "delete from table" without a where
|
|
771
|
+
* clause will not affect this value.
|
|
772
|
+
*/
|
|
773
|
+
static VALUE
|
|
774
|
+
changes(VALUE self)
|
|
775
|
+
{
|
|
776
|
+
sqlite3RubyPtr ctx;
|
|
777
|
+
TypedData_Get_Struct(self, sqlite3Ruby, &database_type, ctx);
|
|
778
|
+
REQUIRE_OPEN_DB(ctx);
|
|
779
|
+
|
|
780
|
+
return INT2NUM(sqlite3_changes(ctx->db));
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
static int
|
|
784
|
+
rb_sqlite3_auth(
|
|
785
|
+
void *ctx,
|
|
786
|
+
int _action,
|
|
787
|
+
const char *_a,
|
|
788
|
+
const char *_b,
|
|
789
|
+
const char *_c,
|
|
790
|
+
const char *_d)
|
|
791
|
+
{
|
|
792
|
+
sqlite3RubyPtr db_ctx = (sqlite3RubyPtr)ctx;
|
|
793
|
+
VALUE action = INT2NUM(_action);
|
|
794
|
+
VALUE a = _a ? rb_str_new2(_a) : Qnil;
|
|
795
|
+
VALUE b = _b ? rb_str_new2(_b) : Qnil;
|
|
796
|
+
VALUE c = _c ? rb_str_new2(_c) : Qnil;
|
|
797
|
+
VALUE d = _d ? rb_str_new2(_d) : Qnil;
|
|
798
|
+
VALUE result = rb_funcall(db_ctx->authorizer, rb_intern("call"), 5, action, a, b, c, d);
|
|
799
|
+
|
|
800
|
+
if (T_FIXNUM == TYPE(result)) { return (int)NUM2INT(result); }
|
|
801
|
+
if (Qtrue == result) { return SQLITE_OK; }
|
|
802
|
+
if (Qfalse == result) { return SQLITE_DENY; }
|
|
803
|
+
|
|
804
|
+
return SQLITE_IGNORE;
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
typedef struct {
|
|
808
|
+
void *ctx;
|
|
809
|
+
int action;
|
|
810
|
+
const char *a;
|
|
811
|
+
const char *b;
|
|
812
|
+
const char *c;
|
|
813
|
+
const char *d;
|
|
814
|
+
} auth_args_t;
|
|
815
|
+
|
|
816
|
+
static void *
|
|
817
|
+
rb_sqlite3_auth_body(void *ptr)
|
|
818
|
+
{
|
|
819
|
+
auth_args_t *args = (auth_args_t *)ptr;
|
|
820
|
+
return (void *)(intptr_t)rb_sqlite3_auth(
|
|
821
|
+
args->ctx, args->action, args->a, args->b, args->c, args->d);
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
static int
|
|
825
|
+
rb_sqlite3_auth_gvl(
|
|
826
|
+
void *ctx,
|
|
827
|
+
int _action,
|
|
828
|
+
const char *_a,
|
|
829
|
+
const char *_b,
|
|
830
|
+
const char *_c,
|
|
831
|
+
const char *_d)
|
|
832
|
+
{
|
|
833
|
+
auth_args_t args = { ctx, _action, _a, _b, _c, _d };
|
|
834
|
+
return (int)(intptr_t)rb_sqlite3_with_gvl(rb_sqlite3_auth_body, &args);
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
/* call-seq: set_authorizer = auth
|
|
838
|
+
*
|
|
839
|
+
* Set the authorizer for this database. +auth+ must respond to +call+, and
|
|
840
|
+
* +call+ must take 5 arguments.
|
|
841
|
+
*
|
|
842
|
+
* Installs (or removes) a block that will be invoked for every access
|
|
843
|
+
* to the database. If the block returns 0 (or +true+), the statement
|
|
844
|
+
* is allowed to proceed. Returning 1 or false causes an authorization error to
|
|
845
|
+
* occur, and returning 2 or nil causes the access to be silently denied.
|
|
846
|
+
*/
|
|
847
|
+
static VALUE
|
|
848
|
+
set_authorizer(VALUE self, VALUE authorizer)
|
|
849
|
+
{
|
|
850
|
+
sqlite3RubyPtr ctx;
|
|
851
|
+
int status;
|
|
852
|
+
|
|
853
|
+
TypedData_Get_Struct(self, sqlite3Ruby, &database_type, ctx);
|
|
854
|
+
REQUIRE_OPEN_DB(ctx);
|
|
855
|
+
|
|
856
|
+
status = sqlite3_set_authorizer(
|
|
857
|
+
ctx->db, NIL_P(authorizer) ? NULL : rb_sqlite3_auth_gvl, (void *)ctx
|
|
858
|
+
);
|
|
859
|
+
|
|
860
|
+
CHECK(ctx->db, status);
|
|
861
|
+
|
|
862
|
+
RB_OBJ_WRITE(self, &ctx->authorizer, authorizer);
|
|
863
|
+
|
|
864
|
+
return self;
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
/* call-seq: db.busy_timeout = ms
|
|
868
|
+
*
|
|
869
|
+
* Indicates that if a request for a resource terminates because that
|
|
870
|
+
* resource is busy, SQLite should sleep and retry for up to the indicated
|
|
871
|
+
* number of milliseconds. By default, SQLite does not retry
|
|
872
|
+
* busy resources. To restore the default behavior, send 0 as the
|
|
873
|
+
* +ms+ parameter.
|
|
874
|
+
*
|
|
875
|
+
* See also the mutually exclusive #busy_handler.
|
|
876
|
+
*/
|
|
877
|
+
static VALUE
|
|
878
|
+
set_busy_timeout(VALUE self, VALUE timeout)
|
|
879
|
+
{
|
|
880
|
+
sqlite3RubyPtr ctx;
|
|
881
|
+
TypedData_Get_Struct(self, sqlite3Ruby, &database_type, ctx);
|
|
882
|
+
REQUIRE_OPEN_DB(ctx);
|
|
883
|
+
|
|
884
|
+
CHECK(ctx->db, sqlite3_busy_timeout(ctx->db, (int)NUM2INT(timeout)));
|
|
885
|
+
|
|
886
|
+
return self;
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
/* call-seq: db.extended_result_codes = true
|
|
890
|
+
*
|
|
891
|
+
* Enable extended result codes in SQLite. These result codes allow for more
|
|
892
|
+
* detailed exception reporting, such a which type of constraint is violated.
|
|
893
|
+
*/
|
|
894
|
+
static VALUE
|
|
895
|
+
set_extended_result_codes(VALUE self, VALUE enable)
|
|
896
|
+
{
|
|
897
|
+
sqlite3RubyPtr ctx;
|
|
898
|
+
TypedData_Get_Struct(self, sqlite3Ruby, &database_type, ctx);
|
|
899
|
+
REQUIRE_OPEN_DB(ctx);
|
|
900
|
+
|
|
901
|
+
CHECK(ctx->db, sqlite3_extended_result_codes(ctx->db, RTEST(enable) ? 1 : 0));
|
|
902
|
+
|
|
903
|
+
return self;
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
int
|
|
907
|
+
rb_comparator_func(void *ctx, int a_len, const void *a, int b_len, const void *b)
|
|
908
|
+
{
|
|
909
|
+
VALUE comparator;
|
|
910
|
+
VALUE a_str;
|
|
911
|
+
VALUE b_str;
|
|
912
|
+
VALUE comparison;
|
|
913
|
+
rb_encoding *internal_encoding;
|
|
914
|
+
|
|
915
|
+
internal_encoding = rb_default_internal_encoding();
|
|
916
|
+
|
|
917
|
+
comparator = (VALUE)ctx;
|
|
918
|
+
a_str = rb_str_new((const char *)a, a_len);
|
|
919
|
+
b_str = rb_str_new((const char *)b, b_len);
|
|
920
|
+
|
|
921
|
+
rb_enc_associate_index(a_str, rb_utf8_encindex());
|
|
922
|
+
rb_enc_associate_index(b_str, rb_utf8_encindex());
|
|
923
|
+
|
|
924
|
+
if (internal_encoding) {
|
|
925
|
+
a_str = rb_str_export_to_enc(a_str, internal_encoding);
|
|
926
|
+
b_str = rb_str_export_to_enc(b_str, internal_encoding);
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
comparison = rb_funcall(comparator, rb_intern("compare"), 2, a_str, b_str);
|
|
930
|
+
|
|
931
|
+
return NUM2INT(comparison);
|
|
932
|
+
}
|
|
933
|
+
|
|
934
|
+
typedef struct {
|
|
935
|
+
void *ctx;
|
|
936
|
+
int a_len;
|
|
937
|
+
const void *a;
|
|
938
|
+
int b_len;
|
|
939
|
+
const void *b;
|
|
940
|
+
} comparator_args_t;
|
|
941
|
+
|
|
942
|
+
static void *
|
|
943
|
+
rb_comparator_func_body(void *ptr)
|
|
944
|
+
{
|
|
945
|
+
comparator_args_t *args = (comparator_args_t *)ptr;
|
|
946
|
+
return (void *)(intptr_t)rb_comparator_func(
|
|
947
|
+
args->ctx, args->a_len, args->a, args->b_len, args->b);
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
static int
|
|
951
|
+
rb_comparator_func_gvl(void *ctx, int a_len, const void *a, int b_len, const void *b)
|
|
952
|
+
{
|
|
953
|
+
comparator_args_t args = { ctx, a_len, a, b_len, b };
|
|
954
|
+
return (int)(intptr_t)rb_sqlite3_with_gvl(rb_comparator_func_body, &args);
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
/* call-seq: db.collation(name, comparator)
|
|
958
|
+
*
|
|
959
|
+
* Add a collation with name +name+, and a +comparator+ object. The
|
|
960
|
+
* +comparator+ object should implement a method called "compare" that takes
|
|
961
|
+
* two parameters and returns an integer less than, equal to, or greater than
|
|
962
|
+
* 0.
|
|
963
|
+
*/
|
|
964
|
+
static VALUE
|
|
965
|
+
collation(VALUE self, VALUE name, VALUE comparator)
|
|
966
|
+
{
|
|
967
|
+
sqlite3RubyPtr ctx;
|
|
968
|
+
VALUE collations;
|
|
969
|
+
TypedData_Get_Struct(self, sqlite3Ruby, &database_type, ctx);
|
|
970
|
+
REQUIRE_OPEN_DB(ctx);
|
|
971
|
+
|
|
972
|
+
CHECK(ctx->db, sqlite3_create_collation(
|
|
973
|
+
ctx->db,
|
|
974
|
+
StringValuePtr(name),
|
|
975
|
+
SQLITE_UTF8,
|
|
976
|
+
(void *)comparator,
|
|
977
|
+
NIL_P(comparator) ? NULL : rb_comparator_func_gvl));
|
|
978
|
+
|
|
979
|
+
/* sqlite holds a raw pointer to the comparator, so keep it alive and unmoved. */
|
|
980
|
+
collations = rb_iv_get(self, "@collations");
|
|
981
|
+
rb_hash_aset(collations, name, comparator);
|
|
982
|
+
RB_OBJ_WRITE(self, &ctx->collations, collations);
|
|
983
|
+
|
|
984
|
+
return self;
|
|
985
|
+
}
|
|
986
|
+
|
|
987
|
+
#ifdef HAVE_SQLITE3_LOAD_EXTENSION
|
|
988
|
+
static VALUE
|
|
989
|
+
load_extension_internal(VALUE self, VALUE file)
|
|
990
|
+
{
|
|
991
|
+
sqlite3RubyPtr ctx;
|
|
992
|
+
int status;
|
|
993
|
+
char *errMsg;
|
|
994
|
+
|
|
995
|
+
TypedData_Get_Struct(self, sqlite3Ruby, &database_type, ctx);
|
|
996
|
+
REQUIRE_OPEN_DB(ctx);
|
|
997
|
+
|
|
998
|
+
status = sqlite3_load_extension(ctx->db, StringValuePtr(file), 0, &errMsg);
|
|
999
|
+
|
|
1000
|
+
CHECK_MSG(ctx->db, status, errMsg);
|
|
1001
|
+
|
|
1002
|
+
return self;
|
|
1003
|
+
}
|
|
1004
|
+
#endif
|
|
1005
|
+
|
|
1006
|
+
#if defined(HAVE_SQLITE3_ENABLE_LOAD_EXTENSION) && defined(HAVE_SQLITE3_LOAD_EXTENSION)
|
|
1007
|
+
/* call-seq: db.enable_load_extension(onoff)
|
|
1008
|
+
*
|
|
1009
|
+
* Enable or disable extension loading.
|
|
1010
|
+
*/
|
|
1011
|
+
static VALUE
|
|
1012
|
+
enable_load_extension(VALUE self, VALUE onoff)
|
|
1013
|
+
{
|
|
1014
|
+
sqlite3RubyPtr ctx;
|
|
1015
|
+
int onoffparam;
|
|
1016
|
+
TypedData_Get_Struct(self, sqlite3Ruby, &database_type, ctx);
|
|
1017
|
+
REQUIRE_OPEN_DB(ctx);
|
|
1018
|
+
|
|
1019
|
+
if (Qtrue == onoff) {
|
|
1020
|
+
onoffparam = 1;
|
|
1021
|
+
} else if (Qfalse == onoff) {
|
|
1022
|
+
onoffparam = 0;
|
|
1023
|
+
} else {
|
|
1024
|
+
onoffparam = (int)NUM2INT(onoff);
|
|
1025
|
+
}
|
|
1026
|
+
|
|
1027
|
+
CHECK(ctx->db, sqlite3_enable_load_extension(ctx->db, onoffparam));
|
|
1028
|
+
|
|
1029
|
+
return self;
|
|
1030
|
+
}
|
|
1031
|
+
#endif
|
|
1032
|
+
|
|
1033
|
+
/* call-seq: db.transaction_active?
|
|
1034
|
+
*
|
|
1035
|
+
* Returns +true+ if there is a transaction active, and +false+ otherwise.
|
|
1036
|
+
*
|
|
1037
|
+
*/
|
|
1038
|
+
static VALUE
|
|
1039
|
+
transaction_active_p(VALUE self)
|
|
1040
|
+
{
|
|
1041
|
+
sqlite3RubyPtr ctx;
|
|
1042
|
+
TypedData_Get_Struct(self, sqlite3Ruby, &database_type, ctx);
|
|
1043
|
+
REQUIRE_OPEN_DB(ctx);
|
|
1044
|
+
|
|
1045
|
+
return sqlite3_get_autocommit(ctx->db) ? Qfalse : Qtrue;
|
|
1046
|
+
}
|
|
1047
|
+
|
|
1048
|
+
static int
|
|
1049
|
+
hash_callback_function(VALUE callback_ary, int count, char **data, char **columns)
|
|
1050
|
+
{
|
|
1051
|
+
VALUE new_hash = rb_hash_new();
|
|
1052
|
+
int i;
|
|
1053
|
+
|
|
1054
|
+
for (i = 0; i < count; i++) {
|
|
1055
|
+
if (data[i] == NULL) {
|
|
1056
|
+
rb_hash_aset(new_hash, rb_str_new_cstr(columns[i]), Qnil);
|
|
1057
|
+
} else {
|
|
1058
|
+
rb_hash_aset(new_hash, rb_str_new_cstr(columns[i]), rb_str_new_cstr(data[i]));
|
|
1059
|
+
}
|
|
1060
|
+
}
|
|
1061
|
+
|
|
1062
|
+
rb_ary_push(callback_ary, new_hash);
|
|
1063
|
+
|
|
1064
|
+
return 0;
|
|
1065
|
+
}
|
|
1066
|
+
|
|
1067
|
+
static int
|
|
1068
|
+
regular_callback_function(VALUE callback_ary, int count, char **data, char **columns)
|
|
1069
|
+
{
|
|
1070
|
+
VALUE new_ary = rb_ary_new();
|
|
1071
|
+
int i;
|
|
1072
|
+
|
|
1073
|
+
for (i = 0; i < count; i++) {
|
|
1074
|
+
if (data[i] == NULL) {
|
|
1075
|
+
rb_ary_push(new_ary, Qnil);
|
|
1076
|
+
} else {
|
|
1077
|
+
rb_ary_push(new_ary, rb_str_new_cstr(data[i]));
|
|
1078
|
+
}
|
|
1079
|
+
}
|
|
1080
|
+
|
|
1081
|
+
rb_ary_push(callback_ary, new_ary);
|
|
1082
|
+
|
|
1083
|
+
return 0;
|
|
1084
|
+
}
|
|
1085
|
+
|
|
1086
|
+
typedef struct {
|
|
1087
|
+
VALUE callback_ary;
|
|
1088
|
+
int count;
|
|
1089
|
+
char **data;
|
|
1090
|
+
char **columns;
|
|
1091
|
+
} exec_callback_args_t;
|
|
1092
|
+
|
|
1093
|
+
static void *
|
|
1094
|
+
hash_callback_body(void *ptr)
|
|
1095
|
+
{
|
|
1096
|
+
exec_callback_args_t *args = (exec_callback_args_t *)ptr;
|
|
1097
|
+
return (void *)(intptr_t)hash_callback_function(
|
|
1098
|
+
args->callback_ary, args->count, args->data, args->columns);
|
|
1099
|
+
}
|
|
1100
|
+
|
|
1101
|
+
static int
|
|
1102
|
+
hash_callback_function_gvl(VALUE callback_ary, int count, char **data, char **columns)
|
|
1103
|
+
{
|
|
1104
|
+
exec_callback_args_t args = { callback_ary, count, data, columns };
|
|
1105
|
+
return (int)(intptr_t)rb_sqlite3_with_gvl(hash_callback_body, &args);
|
|
1106
|
+
}
|
|
1107
|
+
|
|
1108
|
+
static void *
|
|
1109
|
+
regular_callback_body(void *ptr)
|
|
1110
|
+
{
|
|
1111
|
+
exec_callback_args_t *args = (exec_callback_args_t *)ptr;
|
|
1112
|
+
return (void *)(intptr_t)regular_callback_function(
|
|
1113
|
+
args->callback_ary, args->count, args->data, args->columns);
|
|
1114
|
+
}
|
|
1115
|
+
|
|
1116
|
+
static int
|
|
1117
|
+
regular_callback_function_gvl(VALUE callback_ary, int count, char **data, char **columns)
|
|
1118
|
+
{
|
|
1119
|
+
exec_callback_args_t args = { callback_ary, count, data, columns };
|
|
1120
|
+
return (int)(intptr_t)rb_sqlite3_with_gvl(regular_callback_body, &args);
|
|
1121
|
+
}
|
|
1122
|
+
|
|
1123
|
+
|
|
1124
|
+
/* Is invoked by calling db.execute_batch2(sql, &block)
|
|
1125
|
+
*
|
|
1126
|
+
* Executes all statements in a given string separated by semicolons.
|
|
1127
|
+
* If a query is made, all values returned are strings
|
|
1128
|
+
* (except for 'NULL' values which return nil),
|
|
1129
|
+
* so the user may parse values with a block.
|
|
1130
|
+
* If no query is made, an empty array will be returned.
|
|
1131
|
+
*/
|
|
1132
|
+
static VALUE
|
|
1133
|
+
exec_batch(VALUE self, VALUE sql, VALUE results_as_hash)
|
|
1134
|
+
{
|
|
1135
|
+
sqlite3RubyPtr ctx;
|
|
1136
|
+
int status;
|
|
1137
|
+
VALUE callback_ary = rb_ary_new();
|
|
1138
|
+
char *errMsg;
|
|
1139
|
+
|
|
1140
|
+
TypedData_Get_Struct(self, sqlite3Ruby, &database_type, ctx);
|
|
1141
|
+
REQUIRE_OPEN_DB(ctx);
|
|
1142
|
+
|
|
1143
|
+
if (results_as_hash == Qtrue) {
|
|
1144
|
+
status = rb_sqlite3_exec_without_gvl(ctx->db, StringValuePtr(sql), (sqlite3_callback)hash_callback_function_gvl,
|
|
1145
|
+
(void *)callback_ary,
|
|
1146
|
+
&errMsg);
|
|
1147
|
+
} else {
|
|
1148
|
+
status = rb_sqlite3_exec_without_gvl(ctx->db, StringValuePtr(sql), (sqlite3_callback)regular_callback_function_gvl,
|
|
1149
|
+
(void *)callback_ary,
|
|
1150
|
+
&errMsg);
|
|
1151
|
+
}
|
|
1152
|
+
|
|
1153
|
+
CHECK_MSG(ctx->db, status, errMsg);
|
|
1154
|
+
|
|
1155
|
+
return callback_ary;
|
|
1156
|
+
}
|
|
1157
|
+
|
|
1158
|
+
/* call-seq: db.db_filename(database_name)
|
|
1159
|
+
*
|
|
1160
|
+
* Returns the file associated with +database_name+. Can return nil or an
|
|
1161
|
+
* empty string if the database is temporary, or in-memory.
|
|
1162
|
+
*/
|
|
1163
|
+
static VALUE
|
|
1164
|
+
db_filename(VALUE self, VALUE db_name)
|
|
1165
|
+
{
|
|
1166
|
+
sqlite3RubyPtr ctx;
|
|
1167
|
+
const char *fname;
|
|
1168
|
+
TypedData_Get_Struct(self, sqlite3Ruby, &database_type, ctx);
|
|
1169
|
+
REQUIRE_OPEN_DB(ctx);
|
|
1170
|
+
|
|
1171
|
+
fname = sqlite3_db_filename(ctx->db, StringValueCStr(db_name));
|
|
1172
|
+
|
|
1173
|
+
if (fname) { return SQLITE3_UTF8_STR_NEW2(fname); }
|
|
1174
|
+
return Qnil;
|
|
1175
|
+
}
|
|
1176
|
+
|
|
1177
|
+
static VALUE
|
|
1178
|
+
rb_sqlite3_open16(VALUE self, VALUE file)
|
|
1179
|
+
{
|
|
1180
|
+
int status;
|
|
1181
|
+
sqlite3RubyPtr ctx;
|
|
1182
|
+
|
|
1183
|
+
TypedData_Get_Struct(self, sqlite3Ruby, &database_type, ctx);
|
|
1184
|
+
REQUIRE_CLOSED_DB(ctx);
|
|
1185
|
+
|
|
1186
|
+
#if defined TAINTING_SUPPORT
|
|
1187
|
+
#if defined StringValueCStr
|
|
1188
|
+
StringValuePtr(file);
|
|
1189
|
+
rb_check_safe_obj(file);
|
|
1190
|
+
#else
|
|
1191
|
+
Check_SafeStr(file);
|
|
1192
|
+
#endif
|
|
1193
|
+
#endif
|
|
1194
|
+
|
|
1195
|
+
// sqlite3_open16 implicitly uses flags (SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE)
|
|
1196
|
+
// see https://www.sqlite.org/capi3ref.html#sqlite3_open
|
|
1197
|
+
// so we do not ever set SQLITE3_RB_DATABASE_READONLY in ctx->flags
|
|
1198
|
+
status = sqlite3_open16(utf16_string_value_ptr(file), &ctx->db);
|
|
1199
|
+
|
|
1200
|
+
if (status != SQLITE_OK) {
|
|
1201
|
+
char *msg = sqlite3_mprintf("%s", sqlite3_errmsg(ctx->db));
|
|
1202
|
+
sqlite3_close_v2(ctx->db);
|
|
1203
|
+
ctx->db = NULL;
|
|
1204
|
+
CHECK_MSG(ctx->db, status, msg);
|
|
1205
|
+
}
|
|
1206
|
+
|
|
1207
|
+
return INT2NUM(status);
|
|
1208
|
+
}
|
|
1209
|
+
|
|
1210
|
+
void
|
|
1211
|
+
init_sqlite3_database(void)
|
|
1212
|
+
{
|
|
1213
|
+
#if 0
|
|
1214
|
+
VALUE mSqlite3 = rb_define_module("SQLite3");
|
|
1215
|
+
#endif
|
|
1216
|
+
cSqlite3Database = rb_define_class_under(mSqlite3, "Database", rb_cObject);
|
|
1217
|
+
|
|
1218
|
+
rb_define_alloc_func(cSqlite3Database, allocate);
|
|
1219
|
+
rb_define_private_method(cSqlite3Database, "open_v2", rb_sqlite3_open_v2, 3);
|
|
1220
|
+
rb_define_private_method(cSqlite3Database, "open16", rb_sqlite3_open16, 1);
|
|
1221
|
+
rb_define_method(cSqlite3Database, "collation", collation, 2);
|
|
1222
|
+
rb_define_method(cSqlite3Database, "close", sqlite3_rb_close, 0);
|
|
1223
|
+
rb_define_private_method(cSqlite3Database, "discard", sqlite3_rb_discard, 0);
|
|
1224
|
+
rb_define_method(cSqlite3Database, "closed?", closed_p, 0);
|
|
1225
|
+
rb_define_method(cSqlite3Database, "total_changes", total_changes, 0);
|
|
1226
|
+
rb_define_method(cSqlite3Database, "trace", trace, -1);
|
|
1227
|
+
rb_define_method(cSqlite3Database, "last_insert_row_id", last_insert_row_id, 0);
|
|
1228
|
+
rb_define_method(cSqlite3Database, "define_function", define_function, 1);
|
|
1229
|
+
rb_define_method(cSqlite3Database, "define_function_with_flags", define_function_with_flags, 2);
|
|
1230
|
+
/* public "define_aggregator" is now a shim around define_aggregator2
|
|
1231
|
+
* implemented in Ruby */
|
|
1232
|
+
rb_define_private_method(cSqlite3Database, "define_aggregator2", rb_sqlite3_define_aggregator2, 2);
|
|
1233
|
+
rb_define_private_method(cSqlite3Database, "disable_quirk_mode", rb_sqlite3_disable_quirk_mode, 0);
|
|
1234
|
+
rb_define_method(cSqlite3Database, "interrupt", interrupt, 0);
|
|
1235
|
+
rb_define_method(cSqlite3Database, "errmsg", errmsg, 0);
|
|
1236
|
+
rb_define_method(cSqlite3Database, "errcode", errcode_, 0);
|
|
1237
|
+
rb_define_method(cSqlite3Database, "complete?", complete_p, 1);
|
|
1238
|
+
rb_define_method(cSqlite3Database, "changes", changes, 0);
|
|
1239
|
+
rb_define_method(cSqlite3Database, "authorizer=", set_authorizer, 1);
|
|
1240
|
+
rb_define_method(cSqlite3Database, "busy_handler", busy_handler, -1);
|
|
1241
|
+
rb_define_method(cSqlite3Database, "busy_timeout=", set_busy_timeout, 1);
|
|
1242
|
+
#ifndef SQLITE_OMIT_PROGRESS_CALLBACK
|
|
1243
|
+
rb_define_method(cSqlite3Database, "statement_timeout=", set_statement_timeout, 1);
|
|
1244
|
+
#endif
|
|
1245
|
+
rb_define_method(cSqlite3Database, "extended_result_codes=", set_extended_result_codes, 1);
|
|
1246
|
+
rb_define_method(cSqlite3Database, "transaction_active?", transaction_active_p, 0);
|
|
1247
|
+
rb_define_private_method(cSqlite3Database, "exec_batch", exec_batch, 2);
|
|
1248
|
+
rb_define_private_method(cSqlite3Database, "db_filename", db_filename, 1);
|
|
1249
|
+
|
|
1250
|
+
#ifdef HAVE_SQLITE3_LOAD_EXTENSION
|
|
1251
|
+
rb_define_private_method(cSqlite3Database, "load_extension_internal", load_extension_internal, 1);
|
|
1252
|
+
#ifdef HAVE_SQLITE3_ENABLE_LOAD_EXTENSION
|
|
1253
|
+
rb_define_method(cSqlite3Database, "enable_load_extension", enable_load_extension, 1);
|
|
1254
|
+
#endif
|
|
1255
|
+
#endif
|
|
1256
|
+
|
|
1257
|
+
|
|
1258
|
+
rb_sqlite3_aggregator_init();
|
|
1259
|
+
}
|
|
1260
|
+
|
|
1261
|
+
#ifdef _MSC_VER
|
|
1262
|
+
#pragma warning( pop )
|
|
1263
|
+
#endif
|