libsql 0.1.0-x86-mingw32
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/CONTRIBUTING.md +60 -0
- data/HISTORY.md +6 -0
- data/LICENSE +31 -0
- data/Manifest.txt +96 -0
- data/README.md +59 -0
- data/Rakefile +28 -0
- data/TODO.md +57 -0
- data/examples/a.rb +9 -0
- data/examples/blob.rb +106 -0
- data/examples/define_aggregate.rb +75 -0
- data/examples/define_function.rb +104 -0
- data/examples/fts5.rb +152 -0
- data/examples/gem-db.rb +94 -0
- data/examples/schema-info.rb +34 -0
- data/ext/libsql/c/extconf.rb +86 -0
- data/ext/libsql/c/gen_constants.rb +353 -0
- data/ext/libsql/c/libsql_blob.c +240 -0
- data/ext/libsql/c/libsql_constants.c +1518 -0
- data/ext/libsql/c/libsql_database.c +1188 -0
- data/ext/libsql/c/libsql_ext.c +383 -0
- data/ext/libsql/c/libsql_ext.h +149 -0
- data/ext/libsql/c/libsql_statement.c +649 -0
- data/ext/libsql/c/notes.txt +134 -0
- data/ext/libsql/c/sqlite3.c +247030 -0
- data/ext/libsql/c/sqlite3.h +13436 -0
- data/lib/libsql/3.0/libsql_ext.so +0 -0
- data/lib/libsql/3.1/libsql_ext.so +0 -0
- data/lib/libsql/3.2/libsql_ext.so +0 -0
- data/lib/libsql/aggregate.rb +73 -0
- data/lib/libsql/blob.rb +186 -0
- data/lib/libsql/boolean.rb +42 -0
- data/lib/libsql/busy_timeout.rb +47 -0
- data/lib/libsql/column.rb +99 -0
- data/lib/libsql/csv_table_importer.rb +75 -0
- data/lib/libsql/database.rb +933 -0
- data/lib/libsql/function.rb +61 -0
- data/lib/libsql/index.rb +43 -0
- data/lib/libsql/memory_database.rb +15 -0
- data/lib/libsql/paths.rb +80 -0
- data/lib/libsql/profile_tap.rb +131 -0
- data/lib/libsql/progress_handler.rb +21 -0
- data/lib/libsql/schema.rb +225 -0
- data/lib/libsql/sqlite3/constants.rb +95 -0
- data/lib/libsql/sqlite3/database/function.rb +48 -0
- data/lib/libsql/sqlite3/database/status.rb +68 -0
- data/lib/libsql/sqlite3/libsql_version.rb +32 -0
- data/lib/libsql/sqlite3/status.rb +60 -0
- data/lib/libsql/sqlite3/version.rb +55 -0
- data/lib/libsql/sqlite3.rb +7 -0
- data/lib/libsql/statement.rb +421 -0
- data/lib/libsql/table.rb +91 -0
- data/lib/libsql/taps/console.rb +27 -0
- data/lib/libsql/taps/io.rb +74 -0
- data/lib/libsql/taps.rb +2 -0
- data/lib/libsql/trace_tap.rb +35 -0
- data/lib/libsql/type_map.rb +63 -0
- data/lib/libsql/type_maps/default_map.rb +166 -0
- data/lib/libsql/type_maps/storage_map.rb +38 -0
- data/lib/libsql/type_maps/text_map.rb +21 -0
- data/lib/libsql/version.rb +8 -0
- data/lib/libsql/view.rb +26 -0
- data/lib/libsql-ruby.rb +1 -0
- data/lib/libsql.rb +51 -0
- data/spec/aggregate_spec.rb +158 -0
- data/spec/blob_spec.rb +78 -0
- data/spec/boolean_spec.rb +24 -0
- data/spec/busy_handler.rb +157 -0
- data/spec/data/iso-3166-country.txt +242 -0
- data/spec/data/iso-3166-schema.sql +22 -0
- data/spec/data/iso-3166-subcountry.txt +3995 -0
- data/spec/data/make-iso-db.sh +12 -0
- data/spec/database_spec.rb +505 -0
- data/spec/default_map_spec.rb +92 -0
- data/spec/function_spec.rb +78 -0
- data/spec/integeration_spec.rb +97 -0
- data/spec/iso_3166_database.rb +58 -0
- data/spec/json_spec.rb +24 -0
- data/spec/libsql_spec.rb +4 -0
- data/spec/paths_spec.rb +28 -0
- data/spec/progress_handler_spec.rb +91 -0
- data/spec/rtree_spec.rb +66 -0
- data/spec/schema_spec.rb +131 -0
- data/spec/spec_helper.rb +48 -0
- data/spec/sqlite3/constants_spec.rb +108 -0
- data/spec/sqlite3/database_status_spec.rb +36 -0
- data/spec/sqlite3/libsql_version_spec.rb +16 -0
- data/spec/sqlite3/status_spec.rb +22 -0
- data/spec/sqlite3/version_spec.rb +28 -0
- data/spec/sqlite3_spec.rb +53 -0
- data/spec/statement_spec.rb +168 -0
- data/spec/storage_map_spec.rb +38 -0
- data/spec/tap_spec.rb +57 -0
- data/spec/text_map_spec.rb +20 -0
- data/spec/type_map_spec.rb +14 -0
- data/spec/version_spec.rb +8 -0
- data/tasks/custom.rake +134 -0
- data/tasks/default.rake +257 -0
- data/tasks/extension.rake +29 -0
- data/tasks/this.rb +208 -0
- metadata +330 -0
@@ -0,0 +1,1188 @@
|
|
1
|
+
#include "libsql_ext.h"
|
2
|
+
/**
|
3
|
+
* Copyright (c) 2023 Jeremy Hinegardner
|
4
|
+
* All rights reserved. See LICENSE and/or COPYING for details.
|
5
|
+
*
|
6
|
+
* vim: shiftwidth=4
|
7
|
+
*/
|
8
|
+
|
9
|
+
VALUE cLS_Database; /* class Libsql::SQLite3::Database */
|
10
|
+
VALUE cLS_Database_Stat; /* class Libsql::SQLite3::Database::Stat */
|
11
|
+
|
12
|
+
/**
|
13
|
+
* Document-method: open
|
14
|
+
*
|
15
|
+
* call-seq:
|
16
|
+
* Libsql::SQLite3::Database.open( filename, flags = READWRITE | CREATE ) -> Database
|
17
|
+
*
|
18
|
+
* Create a new SQLite2 database with a UTF-8 encoding.
|
19
|
+
*
|
20
|
+
*/
|
21
|
+
VALUE libsql_ext_sqlite3_database_open(int argc, VALUE *argv, VALUE class)
|
22
|
+
{
|
23
|
+
VALUE self = libsql_ext_sqlite3_database_alloc(class);
|
24
|
+
VALUE rFlags;
|
25
|
+
VALUE rFilename;
|
26
|
+
int flags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE;
|
27
|
+
char* filename;
|
28
|
+
int rc;
|
29
|
+
libsql_ext_sqlite3* libsql_ext_db;
|
30
|
+
|
31
|
+
/* at least a filename argument is required */
|
32
|
+
rb_scan_args( argc, argv, "11", &rFilename, &rFlags );
|
33
|
+
|
34
|
+
/* convert flags to the sqlite version */
|
35
|
+
flags = ( Qnil == rFlags ) ? flags : FIX2INT(rFlags);
|
36
|
+
filename = StringValuePtr(rFilename);
|
37
|
+
|
38
|
+
/* extract the sqlite3 wrapper struct */
|
39
|
+
Data_Get_Struct(self, libsql_ext_sqlite3, libsql_ext_db);
|
40
|
+
|
41
|
+
/* open the sqlite3 database */
|
42
|
+
rc = sqlite3_open_v2( filename, &(libsql_ext_db->db), flags, 0);
|
43
|
+
if ( SQLITE_OK != rc ) {
|
44
|
+
rb_raise(eLS_Error, "Failure to open database %s : [SQLITE_ERROR %d] : %s\n",
|
45
|
+
filename, rc, sqlite3_errmsg(libsql_ext_db->db));
|
46
|
+
}
|
47
|
+
|
48
|
+
/* by default turn on the extended result codes */
|
49
|
+
rc = sqlite3_extended_result_codes( libsql_ext_db->db, 1);
|
50
|
+
if ( SQLITE_OK != rc ) {
|
51
|
+
rb_raise(eLS_Error, "Failure to set extended result codes %s : [SQLITE_ERROR %d] : %s\n",
|
52
|
+
filename, rc, sqlite3_errmsg(libsql_ext_db->db));
|
53
|
+
}
|
54
|
+
|
55
|
+
return self;
|
56
|
+
}
|
57
|
+
|
58
|
+
/**
|
59
|
+
* call-seq:
|
60
|
+
* Libsql::SQLite3::Database.open16( filename ) -> SQLite3::Database
|
61
|
+
*
|
62
|
+
* Create a new SQLite3 database with a UTF-16 encoding
|
63
|
+
*
|
64
|
+
*/
|
65
|
+
VALUE libsql_ext_sqlite3_database_open16(VALUE class, VALUE rFilename)
|
66
|
+
{
|
67
|
+
VALUE self = libsql_ext_sqlite3_database_alloc(class);
|
68
|
+
char* filename = StringValuePtr(rFilename);
|
69
|
+
libsql_ext_sqlite3* libsql_ext_db;
|
70
|
+
int rc;
|
71
|
+
|
72
|
+
Data_Get_Struct(self, libsql_ext_sqlite3, libsql_ext_db);
|
73
|
+
rc = sqlite3_open16( filename, &(libsql_ext_db->db) );
|
74
|
+
if ( SQLITE_OK != rc ) {
|
75
|
+
rb_raise(eLS_Error, "Failure to open UTF-16 database %s : [SQLITE_ERROR %d] : %s\n",
|
76
|
+
filename, rc, sqlite3_errmsg( libsql_ext_db->db ));
|
77
|
+
}
|
78
|
+
|
79
|
+
/* by default turn on the extended result codes */
|
80
|
+
rc = sqlite3_extended_result_codes( libsql_ext_db->db, 1);
|
81
|
+
if ( SQLITE_OK != rc ) {
|
82
|
+
rb_raise(eLS_Error, "Failure to set extended result codes on UTF-16 database %s : [SQLITE_ERROR %d] : %s\n",
|
83
|
+
filename, rc, (char*)sqlite3_errmsg16(libsql_ext_db->db));
|
84
|
+
}
|
85
|
+
|
86
|
+
return self;
|
87
|
+
}
|
88
|
+
|
89
|
+
/**
|
90
|
+
* call-seq:
|
91
|
+
* database.close
|
92
|
+
*
|
93
|
+
* Close the database
|
94
|
+
*/
|
95
|
+
VALUE libsql_ext_sqlite3_database_close(VALUE self)
|
96
|
+
{
|
97
|
+
libsql_ext_sqlite3 *libsql_ext_db;
|
98
|
+
int rc = 0;
|
99
|
+
|
100
|
+
Data_Get_Struct(self, libsql_ext_sqlite3, libsql_ext_db);
|
101
|
+
rc = sqlite3_close( libsql_ext_db->db );
|
102
|
+
libsql_ext_db->db = NULL;
|
103
|
+
if ( SQLITE_OK != rc ) {
|
104
|
+
rb_raise(eLS_Error, "Failure to close database : [SQLITE_ERROR %d] : %s\n",
|
105
|
+
rc, sqlite3_errmsg( libsql_ext_db->db ));
|
106
|
+
}
|
107
|
+
|
108
|
+
return self;
|
109
|
+
|
110
|
+
}
|
111
|
+
|
112
|
+
/**
|
113
|
+
* call-seq:
|
114
|
+
* database.last_insert_rowid -> Integer
|
115
|
+
*
|
116
|
+
* Return the rowid of the last row inserted into the database from this
|
117
|
+
* database connection.
|
118
|
+
*/
|
119
|
+
VALUE libsql_ext_sqlite3_database_last_insert_rowid(VALUE self)
|
120
|
+
{
|
121
|
+
libsql_ext_sqlite3 *libsql_ext_db;
|
122
|
+
sqlite3_int64 last_id;
|
123
|
+
|
124
|
+
Data_Get_Struct(self, libsql_ext_sqlite3, libsql_ext_db);
|
125
|
+
last_id = sqlite3_last_insert_rowid( libsql_ext_db->db );
|
126
|
+
|
127
|
+
return SQLINT64_2NUM( last_id );
|
128
|
+
}
|
129
|
+
|
130
|
+
/**
|
131
|
+
* call-seq:
|
132
|
+
* database.autocommit? -> true or false
|
133
|
+
*
|
134
|
+
* return true if the database is in autocommit mode, otherwise return false
|
135
|
+
*
|
136
|
+
*/
|
137
|
+
VALUE libsql_ext_sqlite3_database_is_autocommit(VALUE self)
|
138
|
+
{
|
139
|
+
libsql_ext_sqlite3 *libsql_ext_db;
|
140
|
+
int rc;
|
141
|
+
|
142
|
+
Data_Get_Struct(self, libsql_ext_sqlite3, libsql_ext_db);
|
143
|
+
rc = sqlite3_get_autocommit( libsql_ext_db->db );
|
144
|
+
|
145
|
+
return ( 0 == rc ) ? Qfalse : Qtrue ;
|
146
|
+
}
|
147
|
+
|
148
|
+
/**
|
149
|
+
* call-seq:
|
150
|
+
* database.row_changes -> Integer
|
151
|
+
*
|
152
|
+
* return the number of rows changed with the most recent INSERT, UPDATE or
|
153
|
+
* DELETE statement.
|
154
|
+
*
|
155
|
+
*/
|
156
|
+
VALUE libsql_ext_sqlite3_database_row_changes(VALUE self)
|
157
|
+
{
|
158
|
+
libsql_ext_sqlite3 *libsql_ext_db;
|
159
|
+
int rc;
|
160
|
+
|
161
|
+
Data_Get_Struct(self, libsql_ext_sqlite3, libsql_ext_db);
|
162
|
+
rc = sqlite3_changes( libsql_ext_db->db );
|
163
|
+
|
164
|
+
return INT2FIX(rc);
|
165
|
+
}
|
166
|
+
|
167
|
+
/**
|
168
|
+
* call-seq:
|
169
|
+
* database.last_error_code -> Integer
|
170
|
+
*
|
171
|
+
* return the last error code that happened in the database
|
172
|
+
*
|
173
|
+
*/
|
174
|
+
VALUE libsql_ext_sqlite3_database_last_error_code(VALUE self)
|
175
|
+
{
|
176
|
+
libsql_ext_sqlite3 *libsql_ext_db;
|
177
|
+
int code;
|
178
|
+
|
179
|
+
Data_Get_Struct(self, libsql_ext_sqlite3, libsql_ext_db);
|
180
|
+
code = sqlite3_errcode( libsql_ext_db->db );
|
181
|
+
|
182
|
+
return INT2FIX( code );
|
183
|
+
}
|
184
|
+
|
185
|
+
/**
|
186
|
+
* call-seq:
|
187
|
+
* database.last_error_message -> String
|
188
|
+
*
|
189
|
+
* return the last error message that happened in the database
|
190
|
+
*
|
191
|
+
*/
|
192
|
+
VALUE libsql_ext_sqlite3_database_last_error_message(VALUE self)
|
193
|
+
{
|
194
|
+
libsql_ext_sqlite3 *libsql_ext_db;
|
195
|
+
const char *message;
|
196
|
+
|
197
|
+
Data_Get_Struct(self, libsql_ext_sqlite3, libsql_ext_db);
|
198
|
+
message = sqlite3_errmsg( libsql_ext_db->db );
|
199
|
+
|
200
|
+
return rb_str_new2( message );
|
201
|
+
}
|
202
|
+
|
203
|
+
/**
|
204
|
+
* call-seq:
|
205
|
+
* database.total_changes -> Integer
|
206
|
+
*
|
207
|
+
* return the number of rows changed by INSERT, UPDATE or DELETE statements
|
208
|
+
* in the database connection since the connection was opened.
|
209
|
+
*
|
210
|
+
*/
|
211
|
+
VALUE libsql_ext_sqlite3_database_total_changes(VALUE self)
|
212
|
+
{
|
213
|
+
libsql_ext_sqlite3 *libsql_ext_db;
|
214
|
+
int rc;
|
215
|
+
|
216
|
+
Data_Get_Struct(self, libsql_ext_sqlite3, libsql_ext_db);
|
217
|
+
rc = sqlite3_total_changes( libsql_ext_db->db );
|
218
|
+
|
219
|
+
return INT2FIX(rc);
|
220
|
+
}
|
221
|
+
|
222
|
+
/*
|
223
|
+
* call-seq:
|
224
|
+
* stat.update!( reset = false ) -> nil
|
225
|
+
*
|
226
|
+
* Populates the _@current_ and _@higwater_ instance variables of the given
|
227
|
+
* Database Stat object with the values from the sqlite3_db_status call.
|
228
|
+
* If reset it true then the highwater mark for the stat is reset
|
229
|
+
*
|
230
|
+
*/
|
231
|
+
VALUE libsql_ext_sqlite3_database_stat_update_bang( int argc, VALUE *argv, VALUE self )
|
232
|
+
{
|
233
|
+
int current = -1;
|
234
|
+
int highwater = -1;
|
235
|
+
int reset_flag = 0;
|
236
|
+
int status_op = FIX2INT( rb_iv_get( self, "@code" ) );
|
237
|
+
int rc;
|
238
|
+
|
239
|
+
libsql_ext_sqlite3 *libsql_ext_db;
|
240
|
+
|
241
|
+
VALUE reset = Qfalse;
|
242
|
+
VALUE db = rb_iv_get( self, "@api_db" );
|
243
|
+
|
244
|
+
Data_Get_Struct(db, libsql_ext_sqlite3, libsql_ext_db);
|
245
|
+
|
246
|
+
if ( argc > 0 ) {
|
247
|
+
reset = argv[0];
|
248
|
+
reset_flag = ( Qtrue == reset ) ? 1 : 0 ;
|
249
|
+
}
|
250
|
+
|
251
|
+
rc = sqlite3_db_status( libsql_ext_db->db, status_op, ¤t, &highwater, reset_flag );
|
252
|
+
|
253
|
+
if ( SQLITE_OK != rc ) {
|
254
|
+
VALUE n = rb_iv_get( self, "@name");
|
255
|
+
char* name = StringValuePtr( n );
|
256
|
+
rb_raise(eLS_Error, "Failure to retrieve database status for %s : [SQLITE_ERROR %d] \n", name, rc);
|
257
|
+
}
|
258
|
+
|
259
|
+
rb_iv_set( self, "@current", INT2NUM( current ) );
|
260
|
+
rb_iv_set( self, "@highwater", INT2NUM( highwater) );
|
261
|
+
|
262
|
+
return Qnil;
|
263
|
+
}
|
264
|
+
|
265
|
+
|
266
|
+
|
267
|
+
/**
|
268
|
+
* call-seq:
|
269
|
+
* database.prepare( sql ) -> SQLite3::Statement
|
270
|
+
*
|
271
|
+
* Create a new SQLite3 statement.
|
272
|
+
*/
|
273
|
+
VALUE libsql_ext_sqlite3_database_prepare(VALUE self, VALUE rSQL)
|
274
|
+
{
|
275
|
+
VALUE sql = StringValue( rSQL );
|
276
|
+
VALUE stmt = libsql_ext_sqlite3_statement_alloc(cLS_Statement);
|
277
|
+
libsql_ext_sqlite3 *libsql_ext_db;
|
278
|
+
libsql_ext_sqlite3_stmt *libsql_ext_stmt;
|
279
|
+
const char *tail;
|
280
|
+
int rc;
|
281
|
+
|
282
|
+
Data_Get_Struct(self, libsql_ext_sqlite3, libsql_ext_db);
|
283
|
+
|
284
|
+
Data_Get_Struct(stmt, libsql_ext_sqlite3_stmt, libsql_ext_stmt);
|
285
|
+
rc = sqlite3_prepare_v2( libsql_ext_db->db, RSTRING_PTR(sql), (int)RSTRING_LEN(sql),
|
286
|
+
&(libsql_ext_stmt->stmt), &tail);
|
287
|
+
if ( SQLITE_OK != rc) {
|
288
|
+
rb_raise(eLS_Error, "Failure to prepare statement %s : [SQLITE_ERROR %d] : %s\n",
|
289
|
+
RSTRING_PTR(sql), rc, sqlite3_errmsg(libsql_ext_db->db));
|
290
|
+
libsql_ext_sqlite3_statement_free( libsql_ext_stmt );
|
291
|
+
}
|
292
|
+
|
293
|
+
if ( tail != NULL ) {
|
294
|
+
libsql_ext_stmt->remaining_sql = rb_str_new2( tail );
|
295
|
+
rb_gc_register_address( &(libsql_ext_stmt->remaining_sql) );
|
296
|
+
} else {
|
297
|
+
libsql_ext_stmt->remaining_sql = Qnil;
|
298
|
+
}
|
299
|
+
|
300
|
+
return stmt;
|
301
|
+
}
|
302
|
+
|
303
|
+
|
304
|
+
/**
|
305
|
+
* call-seqL
|
306
|
+
* database.execute_batch( sqls ) -> Boolean
|
307
|
+
*
|
308
|
+
* Execute the statements in a batch.
|
309
|
+
*/
|
310
|
+
VALUE libsql_ext_sqlite3_database_exec(VALUE self, VALUE rSQL)
|
311
|
+
{
|
312
|
+
VALUE sql = StringValue( rSQL );
|
313
|
+
libsql_ext_sqlite3 *libsql_ext_db;
|
314
|
+
int rc;
|
315
|
+
|
316
|
+
Data_Get_Struct(self, libsql_ext_sqlite3, libsql_ext_db);
|
317
|
+
|
318
|
+
rc = sqlite3_exec( libsql_ext_db->db, RSTRING_PTR(sql), NULL, NULL, NULL );
|
319
|
+
|
320
|
+
if ( SQLITE_OK != rc ){
|
321
|
+
rb_raise( eLS_Error, "Failed to execute bulk statements: [SQLITE_ERROR %d] : %s\n",
|
322
|
+
rc, sqlite3_errmsg(libsql_ext_db->db));
|
323
|
+
}
|
324
|
+
|
325
|
+
/* Presume that nobody will want to batch execute
|
326
|
+
more than a Fixnum's worth of statements */
|
327
|
+
return Qtrue;
|
328
|
+
}
|
329
|
+
|
330
|
+
/**
|
331
|
+
* This function is registered with a sqlite3 database using the
|
332
|
+
* sqlite3_trace_v2 function. During the registration process a handle on a
|
333
|
+
* VALUE is also registered.
|
334
|
+
*
|
335
|
+
* When this function is called, it calls the 'trace' method on the tap object,
|
336
|
+
* which is the VALUE that was registered during the sqlite3_trace_v2 call.
|
337
|
+
*
|
338
|
+
* This function corresponds to the SQLite xCallback function specification.
|
339
|
+
*
|
340
|
+
* https://sqlite.org/c3ref/c_trace.html
|
341
|
+
*
|
342
|
+
*/
|
343
|
+
int libsql_ext_xTraceCallback(unsigned trace_type, void* tap, void* prepared_statement, void* extra)
|
344
|
+
{
|
345
|
+
VALUE trace_obj = (VALUE) tap;
|
346
|
+
char* msg;
|
347
|
+
sqlite3_uint64 time;
|
348
|
+
|
349
|
+
switch(trace_type) {
|
350
|
+
case SQLITE_TRACE_STMT:
|
351
|
+
msg = (char*)extra;
|
352
|
+
|
353
|
+
/* The callback can compute the same text that would have been returned by the
|
354
|
+
* legacy sqlite3_trace() interface by using the X argument when X begins with
|
355
|
+
* "--" and invoking sqlite3_expanded_sql(P) otherwise.
|
356
|
+
*/
|
357
|
+
if (0 != strncmp(msg, "--", 2)) {
|
358
|
+
msg = sqlite3_expanded_sql(prepared_statement);
|
359
|
+
}
|
360
|
+
|
361
|
+
rb_funcall( trace_obj, rb_intern("trace"), 1, rb_str_new2( msg ) );
|
362
|
+
break;
|
363
|
+
|
364
|
+
case SQLITE_TRACE_PROFILE:
|
365
|
+
msg = sqlite3_expanded_sql(prepared_statement);
|
366
|
+
time = *(sqlite3_uint64*)extra;
|
367
|
+
rb_funcall( trace_obj, rb_intern("profile"),
|
368
|
+
2, rb_str_new2( msg ), SQLUINT64_2NUM(time) );
|
369
|
+
break;
|
370
|
+
|
371
|
+
case SQLITE_TRACE_ROW:
|
372
|
+
/* not implemented */
|
373
|
+
break;
|
374
|
+
|
375
|
+
case SQLITE_TRACE_CLOSE:
|
376
|
+
/* not implemented */
|
377
|
+
break;
|
378
|
+
|
379
|
+
default:
|
380
|
+
/* nothing */
|
381
|
+
break;
|
382
|
+
}
|
383
|
+
return 0;
|
384
|
+
}
|
385
|
+
|
386
|
+
|
387
|
+
/**
|
388
|
+
* call-seq:
|
389
|
+
* database.register_trace_tap( tap_obj )
|
390
|
+
*
|
391
|
+
* This registers an object to be called for all the trace objects in the sqlite
|
392
|
+
* system. From an SQLite perspective, the trace object is registered for both
|
393
|
+
* SQLITE_TRACE_STMT and SQLITE_TRACE_PROFILE.
|
394
|
+
*
|
395
|
+
* The object must respond to both `trace` and `profile` methods. See
|
396
|
+
* Libsql::Trace::
|
397
|
+
* This is an experimental api and is subject to change, or removal.
|
398
|
+
*
|
399
|
+
*/
|
400
|
+
VALUE libsql_ext_sqlite3_database_register_trace_tap(VALUE self, VALUE tap )
|
401
|
+
{
|
402
|
+
libsql_ext_sqlite3 *libsql_ext_db;
|
403
|
+
|
404
|
+
Data_Get_Struct(self, libsql_ext_sqlite3, libsql_ext_db);
|
405
|
+
|
406
|
+
/* Qnil, unregister the item and tell the garbage collector we are done with
|
407
|
+
* it.
|
408
|
+
*/
|
409
|
+
if ( Qnil == tap ) {
|
410
|
+
|
411
|
+
sqlite3_trace_v2( libsql_ext_db->db, 0, NULL, NULL );
|
412
|
+
rb_gc_unregister_address( &(libsql_ext_db->trace_obj) );
|
413
|
+
libsql_ext_db->trace_obj = Qnil;
|
414
|
+
|
415
|
+
/* register the item and store the reference to the object in the libsql_ext_db
|
416
|
+
* structure. We also have to tell the Ruby garbage collector that we
|
417
|
+
* point to the Ruby object from C.
|
418
|
+
*/
|
419
|
+
} else {
|
420
|
+
|
421
|
+
libsql_ext_db->trace_obj = tap;
|
422
|
+
rb_gc_register_address( &(libsql_ext_db->trace_obj) );
|
423
|
+
sqlite3_trace_v2( libsql_ext_db->db, SQLITE_TRACE_STMT | SQLITE_TRACE_PROFILE, libsql_ext_xTraceCallback, (void *)libsql_ext_db->trace_obj );
|
424
|
+
}
|
425
|
+
|
426
|
+
return Qnil;
|
427
|
+
}
|
428
|
+
|
429
|
+
/**
|
430
|
+
* invoke a ruby function. This is here to be used by rb_protect.
|
431
|
+
*/
|
432
|
+
VALUE libsql_ext_wrap_funcall2( VALUE arg )
|
433
|
+
{
|
434
|
+
libsql_ext_protected_t *protected = (libsql_ext_protected_t*) arg;
|
435
|
+
return rb_funcall2( protected->instance, protected->method, protected->argc, protected->argv );
|
436
|
+
}
|
437
|
+
|
438
|
+
/**
|
439
|
+
* Set the context result on the sqlite3_context based upon the ruby VALUE.
|
440
|
+
* This converts the ruby value to the appropriate C-type and makes the
|
441
|
+
* appropriate call sqlite3_result_* call
|
442
|
+
*/
|
443
|
+
void libsql_ext_set_context_result( sqlite3_context* context, VALUE result )
|
444
|
+
{
|
445
|
+
switch( TYPE(result) ) {
|
446
|
+
case T_FIXNUM:
|
447
|
+
case T_BIGNUM:
|
448
|
+
sqlite3_result_int64( context, NUM2SQLINT64(result) );
|
449
|
+
break;
|
450
|
+
case T_FLOAT:
|
451
|
+
sqlite3_result_double( context, NUM2DBL(result) );
|
452
|
+
break;
|
453
|
+
case T_NIL:
|
454
|
+
sqlite3_result_null( context );
|
455
|
+
break;
|
456
|
+
case T_TRUE:
|
457
|
+
sqlite3_result_int64( context, 1);
|
458
|
+
break;
|
459
|
+
case T_FALSE:
|
460
|
+
sqlite3_result_int64( context, 0);
|
461
|
+
break;
|
462
|
+
case T_STRING:
|
463
|
+
sqlite3_result_text( context, RSTRING_PTR(result), (int)RSTRING_LEN(result), NULL);
|
464
|
+
break;
|
465
|
+
default:
|
466
|
+
sqlite3_result_error( context, "Unable to convert ruby object to an SQL function result", -1 );
|
467
|
+
sqlite3_result_error_code( context, 42 );
|
468
|
+
break;
|
469
|
+
}
|
470
|
+
return;
|
471
|
+
}
|
472
|
+
|
473
|
+
/**
|
474
|
+
* Convert from a protected sqlite3_value to a ruby object
|
475
|
+
*/
|
476
|
+
VALUE sqlite3_value_to_ruby_value( sqlite3_value* s_value )
|
477
|
+
{
|
478
|
+
VALUE rb_value = Qnil;
|
479
|
+
sqlite3_int64 i64;
|
480
|
+
|
481
|
+
switch( sqlite3_value_type( s_value) ) {
|
482
|
+
case SQLITE_NULL:
|
483
|
+
rb_value = Qnil;
|
484
|
+
break;
|
485
|
+
case SQLITE_INTEGER:
|
486
|
+
i64 = sqlite3_value_int64( s_value);
|
487
|
+
rb_value = SQLINT64_2NUM(i64);
|
488
|
+
break;
|
489
|
+
case SQLITE_FLOAT:
|
490
|
+
rb_value = rb_float_new( sqlite3_value_double( s_value ) );
|
491
|
+
break;
|
492
|
+
case SQLITE_TEXT:
|
493
|
+
case SQLITE_BLOB:
|
494
|
+
rb_value = rb_str_new2((const char*) sqlite3_value_text( s_value ) );
|
495
|
+
break;
|
496
|
+
}
|
497
|
+
return rb_value;
|
498
|
+
}
|
499
|
+
|
500
|
+
|
501
|
+
/**
|
502
|
+
* the libsql_ext xBusy handler that is used to invoke the ruby function for
|
503
|
+
* doing busy callbacks.
|
504
|
+
*
|
505
|
+
* This function conforms to the xBusy function specification for
|
506
|
+
* sqlite3_busy_handler.
|
507
|
+
*/
|
508
|
+
int libsql_ext_xBusy( void *pArg , int nArg)
|
509
|
+
{
|
510
|
+
VALUE *args = ALLOCA_N( VALUE, 1 );
|
511
|
+
VALUE result = Qnil;
|
512
|
+
int state;
|
513
|
+
int busy = 1;
|
514
|
+
libsql_ext_protected_t protected;
|
515
|
+
|
516
|
+
args[0] = INT2FIX(nArg);
|
517
|
+
|
518
|
+
protected.instance = (VALUE)pArg;
|
519
|
+
protected.method = rb_intern("call");
|
520
|
+
protected.argc = 1;
|
521
|
+
protected.argv = args;
|
522
|
+
|
523
|
+
result = rb_protect( libsql_ext_wrap_funcall2, (VALUE)&protected, &state );
|
524
|
+
if ( state || ( Qnil == result || Qfalse == result ) ){
|
525
|
+
busy = 0;
|
526
|
+
}
|
527
|
+
return busy;
|
528
|
+
}
|
529
|
+
|
530
|
+
|
531
|
+
/**
|
532
|
+
* call-seq:
|
533
|
+
* database.busy_handler( proc_like or nil )
|
534
|
+
*
|
535
|
+
* register a busy handler. If the argument is nil, then an existing busy
|
536
|
+
* handler is removed. Otherwise the argument is registered as the busy
|
537
|
+
* handler.
|
538
|
+
*/
|
539
|
+
VALUE libsql_ext_sqlite3_database_busy_handler( VALUE self, VALUE handler )
|
540
|
+
{
|
541
|
+
libsql_ext_sqlite3 *libsql_ext_db;
|
542
|
+
int rc;
|
543
|
+
|
544
|
+
Data_Get_Struct(self, libsql_ext_sqlite3, libsql_ext_db);
|
545
|
+
|
546
|
+
/* Removing a busy handler case, remove it from sqlite and then remove it
|
547
|
+
* from the garbage collector if it existed */
|
548
|
+
if ( Qnil == handler ) {
|
549
|
+
rc = sqlite3_busy_handler( libsql_ext_db->db, NULL, NULL );
|
550
|
+
if ( SQLITE_OK != rc ) {
|
551
|
+
rb_raise(eLS_Error, "Failure removing busy handler : [SQLITE_ERROR %d] : %s\n",
|
552
|
+
rc, sqlite3_errmsg( libsql_ext_db->db ));
|
553
|
+
}
|
554
|
+
if ( Qnil != libsql_ext_db->busy_handler_obj ) {
|
555
|
+
rb_gc_unregister_address( &(libsql_ext_db->busy_handler_obj) );
|
556
|
+
}
|
557
|
+
} else {
|
558
|
+
/* installing a busy handler
|
559
|
+
* - register it with sqlite
|
560
|
+
* - keep a reference for ourselves with our database handle
|
561
|
+
* - registere the handler reference with the garbage collector
|
562
|
+
*/
|
563
|
+
rc = sqlite3_busy_handler( libsql_ext_db->db, libsql_ext_xBusy, (void*)handler );
|
564
|
+
if ( SQLITE_OK != rc ) {
|
565
|
+
rb_raise(eLS_Error, "Failure setting busy handler : [SQLITE_ERROR %d] : %s\n",
|
566
|
+
rc, sqlite3_errmsg( libsql_ext_db->db ));
|
567
|
+
}
|
568
|
+
libsql_ext_db->busy_handler_obj = handler;
|
569
|
+
rb_gc_register_address( &(libsql_ext_db->busy_handler_obj) );
|
570
|
+
}
|
571
|
+
return Qnil;
|
572
|
+
}
|
573
|
+
|
574
|
+
|
575
|
+
/**
|
576
|
+
* the libsql_ext xProgress handler that is used to invoke the ruby function for
|
577
|
+
* doing progress handler callbacks.
|
578
|
+
*
|
579
|
+
* This function conforms to the xProgress function specification for
|
580
|
+
* sqlite3_progress_handler.
|
581
|
+
*/
|
582
|
+
int libsql_ext_xProgress( void *pArg )
|
583
|
+
{
|
584
|
+
VALUE result = Qnil;
|
585
|
+
int state;
|
586
|
+
int cancel = 0;
|
587
|
+
libsql_ext_protected_t protected;
|
588
|
+
|
589
|
+
protected.instance = (VALUE)pArg;
|
590
|
+
protected.method = rb_intern("call");
|
591
|
+
protected.argc = 0;
|
592
|
+
protected.argv = NULL;
|
593
|
+
|
594
|
+
result = rb_protect( libsql_ext_wrap_funcall2, (VALUE)&protected, &state );
|
595
|
+
if ( state || ( Qnil == result || Qfalse == result ) ){
|
596
|
+
cancel = 1;
|
597
|
+
}
|
598
|
+
return cancel;
|
599
|
+
}
|
600
|
+
|
601
|
+
|
602
|
+
/**
|
603
|
+
* call-seq:
|
604
|
+
* database.progress_handler( op_count, proc_like or nil )
|
605
|
+
*
|
606
|
+
* register a progress handler. If the argument is nil, then an existing
|
607
|
+
* progress handler is removed. Otherwise the argument is registered as the
|
608
|
+
* progress handler.
|
609
|
+
*/
|
610
|
+
VALUE libsql_ext_sqlite3_database_progress_handler( VALUE self, VALUE op_count, VALUE handler )
|
611
|
+
{
|
612
|
+
libsql_ext_sqlite3 *libsql_ext_db;
|
613
|
+
|
614
|
+
Data_Get_Struct(self, libsql_ext_sqlite3, libsql_ext_db);
|
615
|
+
|
616
|
+
/* Removing a progress handler, remove it from sqlite and then remove it
|
617
|
+
* from the garbage collector if it existed */
|
618
|
+
if ( Qnil == handler ) {
|
619
|
+
sqlite3_progress_handler( libsql_ext_db->db, -1, NULL, (void*)NULL );
|
620
|
+
if ( Qnil != libsql_ext_db->progress_handler_obj ) {
|
621
|
+
rb_gc_unregister_address( &(libsql_ext_db->progress_handler_obj) );
|
622
|
+
}
|
623
|
+
} else {
|
624
|
+
int op_codes = FIX2INT( op_count );
|
625
|
+
/* installing a progress handler
|
626
|
+
* - register it with sqlite
|
627
|
+
* - keep a reference for ourselves with our database handle
|
628
|
+
* - register the handler reference with the garbage collector
|
629
|
+
*/
|
630
|
+
sqlite3_progress_handler( libsql_ext_db->db, op_codes, libsql_ext_xProgress, (void*)handler );
|
631
|
+
libsql_ext_db->progress_handler_obj = handler;
|
632
|
+
rb_gc_register_address( &(libsql_ext_db->progress_handler_obj) );
|
633
|
+
}
|
634
|
+
return Qnil;
|
635
|
+
}
|
636
|
+
|
637
|
+
|
638
|
+
/**
|
639
|
+
* the libsql_ext xFunc callback that is used to invoke the ruby function for
|
640
|
+
* doing scalar SQL functions.
|
641
|
+
*
|
642
|
+
* This function conforms to the xFunc function specification for
|
643
|
+
* sqlite3_create_function
|
644
|
+
*/
|
645
|
+
void libsql_ext_xFunc( sqlite3_context* context, int argc, sqlite3_value** argv )
|
646
|
+
{
|
647
|
+
VALUE *args = ALLOCA_N( VALUE, argc );
|
648
|
+
VALUE result;
|
649
|
+
int state;
|
650
|
+
int i;
|
651
|
+
libsql_ext_protected_t protected;
|
652
|
+
|
653
|
+
/* convert each item in argv to a VALUE object based upon its type via
|
654
|
+
* sqlite3_value_type( argv[n] )
|
655
|
+
*/
|
656
|
+
for( i = 0 ; i < argc ; i++) {
|
657
|
+
args[i] = sqlite3_value_to_ruby_value( argv[i] );
|
658
|
+
}
|
659
|
+
|
660
|
+
/* gather all the data to make the protected call */
|
661
|
+
protected.instance = (VALUE) sqlite3_user_data( context );
|
662
|
+
protected.method = rb_intern("call");
|
663
|
+
protected.argc = argc;
|
664
|
+
protected.argv = args;
|
665
|
+
|
666
|
+
result = rb_protect( libsql_ext_wrap_funcall2, (VALUE)&protected, &state );
|
667
|
+
/* check the results */
|
668
|
+
if ( state ) {
|
669
|
+
VALUE msg = ERROR_INFO_MESSAGE();
|
670
|
+
sqlite3_result_error( context, RSTRING_PTR(msg), (int)RSTRING_LEN(msg) );
|
671
|
+
} else {
|
672
|
+
libsql_ext_set_context_result( context, result );
|
673
|
+
}
|
674
|
+
|
675
|
+
return;
|
676
|
+
}
|
677
|
+
|
678
|
+
/**
|
679
|
+
* call-seq:
|
680
|
+
* database.define_function( name, proc_like )
|
681
|
+
*
|
682
|
+
* register the given function to be invoked as an sql function.
|
683
|
+
*/
|
684
|
+
VALUE libsql_ext_sqlite3_database_define_function( VALUE self, VALUE name, VALUE proc_like )
|
685
|
+
{
|
686
|
+
libsql_ext_sqlite3 *libsql_ext_db;
|
687
|
+
int rc;
|
688
|
+
VALUE arity = rb_funcall( proc_like, rb_intern( "arity" ), 0 );
|
689
|
+
char* zFunctionName = RSTRING_PTR(name);
|
690
|
+
int nArg = FIX2INT( arity );
|
691
|
+
|
692
|
+
Data_Get_Struct(self, libsql_ext_sqlite3, libsql_ext_db);
|
693
|
+
rc = sqlite3_create_function( libsql_ext_db->db,
|
694
|
+
zFunctionName, nArg,
|
695
|
+
SQLITE_UTF8,
|
696
|
+
(void *)proc_like, libsql_ext_xFunc,
|
697
|
+
NULL, NULL);
|
698
|
+
if ( SQLITE_OK != rc ) {
|
699
|
+
/* in the case of SQLITE_MISUSE the error message in the database may
|
700
|
+
* not be set. In this case, hardcode the error.
|
701
|
+
* http://sqlite.org/c3ref/errcode.html
|
702
|
+
*
|
703
|
+
* This is a result of 3.6.15 which has sqlite3_create_function return
|
704
|
+
* SQLITE_MISUSE intead of SQLITE_ERROR if called with incorrect
|
705
|
+
* parameters.
|
706
|
+
*/
|
707
|
+
if ( SQLITE_MISUSE == rc ) {
|
708
|
+
rb_raise(eLS_Error, "Failure defining SQL function '%s' with arity '%d' : [SQLITE_ERROR %d] : Library used incorrectly\n",
|
709
|
+
zFunctionName, nArg, rc);
|
710
|
+
} else {
|
711
|
+
rb_raise(eLS_Error, "Failure defining SQL function '%s' with arity '%d' : [SQLITE_ERROR %d] : %s\n",
|
712
|
+
zFunctionName, nArg, rc, sqlite3_errmsg( libsql_ext_db->db ));
|
713
|
+
}
|
714
|
+
}
|
715
|
+
rb_gc_register_address( &proc_like );
|
716
|
+
return Qnil;
|
717
|
+
}
|
718
|
+
|
719
|
+
/**
|
720
|
+
* call-seq:
|
721
|
+
* database.remove_function( name, proc_like )
|
722
|
+
*
|
723
|
+
* remove the given function from availability in SQL.
|
724
|
+
*/
|
725
|
+
VALUE libsql_ext_sqlite3_database_remove_function( VALUE self, VALUE name, VALUE proc_like )
|
726
|
+
{
|
727
|
+
libsql_ext_sqlite3 *libsql_ext_db;
|
728
|
+
int rc;
|
729
|
+
VALUE arity = rb_funcall( proc_like, rb_intern( "arity" ), 0 );
|
730
|
+
char* zFunctionName = RSTRING_PTR(name);
|
731
|
+
int nArg = FIX2INT( arity );
|
732
|
+
|
733
|
+
Data_Get_Struct(self, libsql_ext_sqlite3, libsql_ext_db);
|
734
|
+
rc = sqlite3_create_function( libsql_ext_db->db,
|
735
|
+
zFunctionName, nArg,
|
736
|
+
SQLITE_UTF8,
|
737
|
+
NULL, NULL,
|
738
|
+
NULL, NULL);
|
739
|
+
if ( SQLITE_OK != rc ) {
|
740
|
+
rb_raise(eLS_Error, "Failure removing SQL function '%s' with arity '%d' : [SQLITE_ERROR %d] : %s\n",
|
741
|
+
zFunctionName, nArg, rc, sqlite3_errmsg( libsql_ext_db->db ));
|
742
|
+
}
|
743
|
+
rb_gc_unregister_address( &proc_like );
|
744
|
+
return Qnil;
|
745
|
+
}
|
746
|
+
|
747
|
+
/* wrap rb_class_new_instance so it can be called from within an rb_protect */
|
748
|
+
VALUE libsql_ext_wrap_new_aggregate( VALUE arg )
|
749
|
+
{
|
750
|
+
return rb_class_new_instance( 0, 0, arg );
|
751
|
+
}
|
752
|
+
|
753
|
+
|
754
|
+
/**
|
755
|
+
* the libsql_ext xStep callback that is used to invoke the ruby method for
|
756
|
+
* doing aggregate step oprations as part of an aggregate SQL function.
|
757
|
+
*
|
758
|
+
* This function conforms to the xStep function specification for
|
759
|
+
* sqlite3_create_function.
|
760
|
+
*/
|
761
|
+
void libsql_ext_xStep( sqlite3_context* context, int argc, sqlite3_value** argv )
|
762
|
+
{
|
763
|
+
VALUE *args = ALLOCA_N( VALUE, argc );
|
764
|
+
VALUE result;
|
765
|
+
int state;
|
766
|
+
int i;
|
767
|
+
libsql_ext_protected_t protected;
|
768
|
+
VALUE *aggregate_context = (VALUE*)sqlite3_aggregate_context( context, sizeof( VALUE ) );
|
769
|
+
|
770
|
+
if ( 0 == aggregate_context ) {
|
771
|
+
sqlite3_result_error_nomem( context );
|
772
|
+
return;
|
773
|
+
}
|
774
|
+
|
775
|
+
/* instantiate an instance of the aggregate function class if the
|
776
|
+
* aggregate context is zero'd out .
|
777
|
+
*
|
778
|
+
* If there is an error in initialization of the aggregate, set the error
|
779
|
+
* context
|
780
|
+
*/
|
781
|
+
if ( *aggregate_context == T_NONE ) {
|
782
|
+
VALUE klass = (VALUE) sqlite3_user_data( context );
|
783
|
+
result = rb_protect( libsql_ext_wrap_new_aggregate, klass, &state );
|
784
|
+
|
785
|
+
/* exception was raised during initialization */
|
786
|
+
if ( state ) {
|
787
|
+
*aggregate_context = rb_gv_get("$!");
|
788
|
+
rb_gc_register_address( aggregate_context );
|
789
|
+
VALUE msg = rb_obj_as_string( *aggregate_context );
|
790
|
+
sqlite3_result_error( context, RSTRING_PTR(msg), (int)RSTRING_LEN(msg));
|
791
|
+
return;
|
792
|
+
} else {
|
793
|
+
*aggregate_context = result;
|
794
|
+
/* mark the instance as protected from collection */
|
795
|
+
rb_gc_register_address( aggregate_context );
|
796
|
+
rb_iv_set( *aggregate_context, "@_exception", Qnil );
|
797
|
+
}
|
798
|
+
}
|
799
|
+
|
800
|
+
/* convert each item in argv to a VALUE object based upon its type via
|
801
|
+
* sqlite3_value_type( argv[n] )
|
802
|
+
*/
|
803
|
+
for( i = 0 ; i < argc ; i++) {
|
804
|
+
args[i] = sqlite3_value_to_ruby_value( argv[i] );
|
805
|
+
}
|
806
|
+
|
807
|
+
/* gather all the data to make the protected call */
|
808
|
+
protected.instance = *aggregate_context;
|
809
|
+
protected.method = rb_intern("step");
|
810
|
+
protected.argc = argc;
|
811
|
+
protected.argv = args;
|
812
|
+
|
813
|
+
result = rb_protect( libsql_ext_wrap_funcall2, (VALUE)&protected, &state );
|
814
|
+
|
815
|
+
/* check the results, if there is an error, set the @exception ivar */
|
816
|
+
if ( state ) {
|
817
|
+
VALUE msg = ERROR_INFO_MESSAGE();
|
818
|
+
sqlite3_result_error( context, RSTRING_PTR(msg), (int)RSTRING_LEN(msg));
|
819
|
+
rb_iv_set( *aggregate_context, "@_exception", rb_gv_get("$!" ));
|
820
|
+
}
|
821
|
+
|
822
|
+
return ;
|
823
|
+
}
|
824
|
+
|
825
|
+
|
826
|
+
/**
|
827
|
+
* the libsql_ext xFinal callback that is used to invoke the ruby method for
|
828
|
+
* doing aggregate final operations as part of an aggregate SQL function.
|
829
|
+
*
|
830
|
+
* This function conforms to the xFinal function specification for
|
831
|
+
* sqlite3_create_function.
|
832
|
+
*/
|
833
|
+
void libsql_ext_xFinal( sqlite3_context* context )
|
834
|
+
{
|
835
|
+
VALUE result;
|
836
|
+
int state;
|
837
|
+
libsql_ext_protected_t protected;
|
838
|
+
VALUE exception = Qnil;
|
839
|
+
VALUE *aggregate_context = (VALUE*)sqlite3_aggregate_context( context, sizeof( VALUE ) );
|
840
|
+
|
841
|
+
/**
|
842
|
+
* check and see if an exception had been throw at some point during the
|
843
|
+
* initialization of hte aggregate or during the step function call
|
844
|
+
*
|
845
|
+
*/
|
846
|
+
if (TYPE(*aggregate_context) == T_OBJECT) {
|
847
|
+
/* if there is a @_exception value and it has a value then there was an
|
848
|
+
* exception during step function execution
|
849
|
+
*/
|
850
|
+
if (rb_ivar_defined( *aggregate_context, rb_intern("@_exception") )) {
|
851
|
+
exception = rb_iv_get( *aggregate_context, "@_exception" );
|
852
|
+
} else {
|
853
|
+
|
854
|
+
/* if the aggregate context itself is an exception, then there was
|
855
|
+
* an error during teh initialization of the aggregate context
|
856
|
+
*/
|
857
|
+
if (rb_obj_is_kind_of( *aggregate_context, rb_eException )) {
|
858
|
+
exception = *aggregate_context;
|
859
|
+
}
|
860
|
+
}
|
861
|
+
}
|
862
|
+
|
863
|
+
if ( Qnil == exception ) {
|
864
|
+
/* gather all the data to make the protected call */
|
865
|
+
protected.instance = *aggregate_context;
|
866
|
+
protected.method = rb_intern("finalize");
|
867
|
+
protected.argc = 0;
|
868
|
+
protected.argv = NULL;
|
869
|
+
|
870
|
+
result = rb_protect( libsql_ext_wrap_funcall2, (VALUE)&protected, &state );
|
871
|
+
|
872
|
+
/* check the results */
|
873
|
+
if ( state ) {
|
874
|
+
VALUE msg = ERROR_INFO_MESSAGE();
|
875
|
+
sqlite3_result_error( context, RSTRING_PTR(msg), (int)RSTRING_LEN(msg) );
|
876
|
+
} else {
|
877
|
+
libsql_ext_set_context_result( context, result );
|
878
|
+
}
|
879
|
+
} else {
|
880
|
+
VALUE msg = rb_obj_as_string( exception );
|
881
|
+
sqlite3_result_error( context, RSTRING_PTR(msg), (int)RSTRING_LEN(msg) );
|
882
|
+
}
|
883
|
+
|
884
|
+
/* release the aggregate instance from garbage collector protection */
|
885
|
+
rb_gc_unregister_address( aggregate_context );
|
886
|
+
|
887
|
+
return ;
|
888
|
+
}
|
889
|
+
|
890
|
+
|
891
|
+
|
892
|
+
/**
|
893
|
+
* call-seq:
|
894
|
+
* database.define_aggregate( name, arity, klass )
|
895
|
+
*
|
896
|
+
* register the given klass to be invoked as an sql aggregate.
|
897
|
+
*/
|
898
|
+
VALUE libsql_ext_sqlite3_database_define_aggregate( VALUE self, VALUE name, VALUE arity, VALUE klass )
|
899
|
+
{
|
900
|
+
libsql_ext_sqlite3 *libsql_ext_db;
|
901
|
+
int rc;
|
902
|
+
char* zFunctionName = RSTRING_PTR(name);
|
903
|
+
int nArg = FIX2INT( arity );
|
904
|
+
|
905
|
+
Data_Get_Struct(self, libsql_ext_sqlite3, libsql_ext_db);
|
906
|
+
rc = sqlite3_create_function( libsql_ext_db->db,
|
907
|
+
zFunctionName, nArg,
|
908
|
+
SQLITE_UTF8,
|
909
|
+
(void *)klass,
|
910
|
+
NULL, /* for scalar functions, not used here */
|
911
|
+
libsql_ext_xStep,
|
912
|
+
libsql_ext_xFinal);
|
913
|
+
if ( SQLITE_OK != rc ) {
|
914
|
+
/* in the case of SQLITE_MISUSE the error message in the database may
|
915
|
+
* not be set. In this case, hardcode the error.
|
916
|
+
* http://sqlite.org/c3ref/errcode.html
|
917
|
+
*
|
918
|
+
* This is a result of 3.6.15 which has sqlite3_create_function return
|
919
|
+
* SQLITE_MISUSE intead of SQLITE_ERROR if called with incorrect
|
920
|
+
* parameters.
|
921
|
+
*/
|
922
|
+
if ( SQLITE_MISUSE == rc ) {
|
923
|
+
rb_raise(eLS_Error, "Failure defining SQL function '%s' with arity '%d' : [SQLITE_ERROR %d] : Library used incorrectly\n",
|
924
|
+
zFunctionName, nArg, rc);
|
925
|
+
} else {
|
926
|
+
rb_raise(eLS_Error, "Failure defining SQL function '%s' with arity '%d' : [SQLITE_ERROR %d] : %s\n",
|
927
|
+
zFunctionName, nArg, rc, sqlite3_errmsg( libsql_ext_db->db ));
|
928
|
+
}
|
929
|
+
}
|
930
|
+
rb_gc_register_address( &klass );
|
931
|
+
return Qnil;
|
932
|
+
}
|
933
|
+
|
934
|
+
|
935
|
+
/**
|
936
|
+
* call-seq:
|
937
|
+
* database.remove_aggregate( name, arity, klass )
|
938
|
+
*
|
939
|
+
* remove the given klass from availability in SQL as an aggregate.
|
940
|
+
*/
|
941
|
+
VALUE libsql_ext_sqlite3_database_remove_aggregate( VALUE self, VALUE name, VALUE arity, VALUE klass )
|
942
|
+
{
|
943
|
+
libsql_ext_sqlite3 *libsql_ext_db;
|
944
|
+
int rc;
|
945
|
+
char* zFunctionName = RSTRING_PTR(name);
|
946
|
+
int nArg = FIX2INT( arity );
|
947
|
+
|
948
|
+
Data_Get_Struct(self, libsql_ext_sqlite3, libsql_ext_db);
|
949
|
+
rc = sqlite3_create_function( libsql_ext_db->db,
|
950
|
+
zFunctionName, nArg,
|
951
|
+
SQLITE_UTF8,
|
952
|
+
NULL, NULL,
|
953
|
+
NULL,
|
954
|
+
NULL);
|
955
|
+
if ( SQLITE_OK != rc ) {
|
956
|
+
rb_raise(eLS_Error, "Failure removing SQL aggregate '%s' with arity '%d' : [SQLITE_ERROR %d] : %s\n",
|
957
|
+
zFunctionName, nArg, rc, sqlite3_errmsg( libsql_ext_db->db ));
|
958
|
+
}
|
959
|
+
rb_gc_unregister_address( &klass );
|
960
|
+
return Qnil;
|
961
|
+
}
|
962
|
+
|
963
|
+
|
964
|
+
/**
|
965
|
+
* call-seq:
|
966
|
+
* database.interrupt!
|
967
|
+
*
|
968
|
+
* Cause another thread with a handle on this database to be interrupted and
|
969
|
+
* return at the earliest opportunity as interrupted.
|
970
|
+
*/
|
971
|
+
VALUE libsql_ext_sqlite3_database_interrupt_bang( VALUE self )
|
972
|
+
{
|
973
|
+
libsql_ext_sqlite3 *libsql_ext_db;
|
974
|
+
|
975
|
+
Data_Get_Struct(self, libsql_ext_sqlite3, libsql_ext_db);
|
976
|
+
sqlite3_interrupt( libsql_ext_db->db );
|
977
|
+
return Qnil;
|
978
|
+
}
|
979
|
+
|
980
|
+
/**
|
981
|
+
* call-seq:
|
982
|
+
* database.replicate_to( other_db ) -> other_db
|
983
|
+
*
|
984
|
+
* Replicates the current database to the database passed in using the
|
985
|
+
* sqlite3_backup api
|
986
|
+
*
|
987
|
+
*/
|
988
|
+
VALUE libsql_ext_sqlite3_database_replicate_to( VALUE self, VALUE other )
|
989
|
+
{
|
990
|
+
libsql_ext_sqlite3 *libsql_ext_src_db;
|
991
|
+
libsql_ext_sqlite3 *libsql_ext_dest_db;
|
992
|
+
|
993
|
+
sqlite3_backup *backup;
|
994
|
+
sqlite3 *src;
|
995
|
+
sqlite3 *dest;
|
996
|
+
|
997
|
+
int rc_s;
|
998
|
+
int rc_f;
|
999
|
+
|
1000
|
+
/* source database */
|
1001
|
+
Data_Get_Struct(self, libsql_ext_sqlite3, libsql_ext_src_db);
|
1002
|
+
src = libsql_ext_src_db->db;
|
1003
|
+
|
1004
|
+
/* destination database */
|
1005
|
+
Data_Get_Struct(other, libsql_ext_sqlite3, libsql_ext_dest_db);
|
1006
|
+
dest = libsql_ext_dest_db->db;
|
1007
|
+
|
1008
|
+
backup = sqlite3_backup_init( dest, "main", src, "main" );
|
1009
|
+
if ( NULL == backup ) {
|
1010
|
+
rb_raise(eLS_Error, "Failure to initialize replication: [SQLITE_ERROR %d] : %s\n",
|
1011
|
+
sqlite3_errcode( dest ), sqlite3_errmsg( dest ));
|
1012
|
+
}
|
1013
|
+
|
1014
|
+
rc_s = sqlite3_backup_step( backup, -1 ); /* copy the whole thing at once */
|
1015
|
+
rc_f = sqlite3_backup_finish( backup );
|
1016
|
+
|
1017
|
+
/* report the rc_s error if that one is bad,
|
1018
|
+
* else raise the rc_f error, or nothing */
|
1019
|
+
if ( SQLITE_DONE != rc_s ) {
|
1020
|
+
rb_raise(eLS_Error, "Failure in replication : [SQLITE_ERROR %d] : %s\n",
|
1021
|
+
sqlite3_errcode( dest ), sqlite3_errmsg( dest ) );
|
1022
|
+
} else if ( SQLITE_OK != rc_f ) {
|
1023
|
+
rb_raise(eLS_Error, "Failure in finishing replication: [SQLITE_ERROR %d] : %s\n",
|
1024
|
+
sqlite3_errcode( dest ), sqlite3_errmsg( dest ) );
|
1025
|
+
}
|
1026
|
+
|
1027
|
+
return other;
|
1028
|
+
}
|
1029
|
+
|
1030
|
+
/**
|
1031
|
+
* call-seq:
|
1032
|
+
* database.table_column_metadata( db_name, table_name, column_name) -> Hash
|
1033
|
+
*
|
1034
|
+
* Returns a hash containing the meta information about the column. The
|
1035
|
+
* available keys are:
|
1036
|
+
*
|
1037
|
+
* declared_data_type:: the declared data type of the column
|
1038
|
+
* collation_sequence_name:: the name of the collation sequence for the column
|
1039
|
+
* not_null_constraint:: True if the column has a NOT NULL constraint
|
1040
|
+
* primary_key:: True if the column is part of a primary key
|
1041
|
+
* auto_increment:: True if the column is AUTO INCREMENT
|
1042
|
+
*
|
1043
|
+
*/
|
1044
|
+
VALUE libsql_ext_sqlite3_database_table_column_metadata(VALUE self, VALUE db_name, VALUE tbl_name, VALUE col_name)
|
1045
|
+
{
|
1046
|
+
libsql_ext_sqlite3 *libsql_ext_db;
|
1047
|
+
int rc;
|
1048
|
+
|
1049
|
+
/* input */
|
1050
|
+
const char *zDbName = StringValuePtr( db_name );
|
1051
|
+
const char *zTableName = StringValuePtr( tbl_name );
|
1052
|
+
const char *zColumnName = StringValuePtr( col_name );
|
1053
|
+
|
1054
|
+
/* output */
|
1055
|
+
const char *pzDataType = NULL;
|
1056
|
+
const char *pzCollSeq = NULL;
|
1057
|
+
int pNotNull, pPrimaryKey, pAutoinc;
|
1058
|
+
VALUE rHash = rb_hash_new();
|
1059
|
+
VALUE rStr = Qnil;
|
1060
|
+
|
1061
|
+
Data_Get_Struct(self, libsql_ext_sqlite3, libsql_ext_db);
|
1062
|
+
|
1063
|
+
rc = sqlite3_table_column_metadata( libsql_ext_db->db,
|
1064
|
+
zDbName, zTableName, zColumnName,
|
1065
|
+
&pzDataType, &pzCollSeq,
|
1066
|
+
&pNotNull, &pPrimaryKey, &pAutoinc);
|
1067
|
+
if ( SQLITE_OK != rc ) {
|
1068
|
+
rb_raise(eLS_Error, "Failure retrieveing column meta data for table '%s' column '%s' : [SQLITE_ERROR %d] : %s\n",
|
1069
|
+
zTableName, zColumnName, rc, sqlite3_errmsg( libsql_ext_db-> db ));
|
1070
|
+
|
1071
|
+
}
|
1072
|
+
|
1073
|
+
rStr = ( NULL == pzDataType) ? Qnil : rb_str_new2( pzDataType );
|
1074
|
+
rb_hash_aset( rHash, rb_str_new2("declared_data_type"), rStr );
|
1075
|
+
|
1076
|
+
rStr = ( NULL == pzCollSeq) ? Qnil : rb_str_new2( pzCollSeq );
|
1077
|
+
rb_hash_aset( rHash, rb_str_new2("collation_sequence_name"), rStr );
|
1078
|
+
|
1079
|
+
rb_hash_aset( rHash, rb_str_new2("not_null_constraint"), ( pNotNull ? Qtrue : Qfalse ));
|
1080
|
+
rb_hash_aset( rHash, rb_str_new2("primary_key"), ( pPrimaryKey ? Qtrue : Qfalse ));
|
1081
|
+
rb_hash_aset( rHash, rb_str_new2("auto_increment"), ( pAutoinc ? Qtrue : Qfalse ));
|
1082
|
+
|
1083
|
+
return rHash;
|
1084
|
+
}
|
1085
|
+
|
1086
|
+
/***********************************************************************
|
1087
|
+
* Ruby life cycle methods
|
1088
|
+
***********************************************************************/
|
1089
|
+
|
1090
|
+
|
1091
|
+
/*
|
1092
|
+
* garbage collector free method for the libsql_ext_data structure. Make sure to un
|
1093
|
+
* register the trace and profile objects if they are not Qnil
|
1094
|
+
*/
|
1095
|
+
void libsql_ext_sqlite3_database_free(libsql_ext_sqlite3* libsql_ext_db)
|
1096
|
+
{
|
1097
|
+
if ( Qnil != libsql_ext_db->trace_obj ) {
|
1098
|
+
rb_gc_unregister_address( &(libsql_ext_db->trace_obj) );
|
1099
|
+
libsql_ext_db->trace_obj = Qnil;
|
1100
|
+
}
|
1101
|
+
|
1102
|
+
if ( Qnil != libsql_ext_db->profile_obj) {
|
1103
|
+
rb_gc_unregister_address( &(libsql_ext_db->profile_obj) );
|
1104
|
+
libsql_ext_db->profile_obj = Qnil;
|
1105
|
+
}
|
1106
|
+
|
1107
|
+
if ( Qnil != libsql_ext_db->busy_handler_obj ) {
|
1108
|
+
rb_gc_unregister_address( &(libsql_ext_db->busy_handler_obj) );
|
1109
|
+
libsql_ext_db->busy_handler_obj = Qnil;
|
1110
|
+
}
|
1111
|
+
|
1112
|
+
if ( Qnil != libsql_ext_db->progress_handler_obj ) {
|
1113
|
+
rb_gc_unregister_address( &(libsql_ext_db->progress_handler_obj) );
|
1114
|
+
libsql_ext_db->progress_handler_obj = Qnil;
|
1115
|
+
}
|
1116
|
+
libsql_ext_db->db = NULL;
|
1117
|
+
|
1118
|
+
free(libsql_ext_db);
|
1119
|
+
return;
|
1120
|
+
}
|
1121
|
+
|
1122
|
+
/*
|
1123
|
+
* allocate the libsql_ext_data structure
|
1124
|
+
*/
|
1125
|
+
VALUE libsql_ext_sqlite3_database_alloc(VALUE klass)
|
1126
|
+
{
|
1127
|
+
libsql_ext_sqlite3* libsql_ext_db = ALLOC(libsql_ext_sqlite3);
|
1128
|
+
VALUE obj ;
|
1129
|
+
|
1130
|
+
libsql_ext_db->trace_obj = Qnil;
|
1131
|
+
libsql_ext_db->profile_obj = Qnil;
|
1132
|
+
libsql_ext_db->busy_handler_obj = Qnil;
|
1133
|
+
libsql_ext_db->progress_handler_obj = Qnil;
|
1134
|
+
libsql_ext_db->db = NULL;
|
1135
|
+
|
1136
|
+
obj = Data_Wrap_Struct(klass, NULL, libsql_ext_sqlite3_database_free, libsql_ext_db);
|
1137
|
+
return obj;
|
1138
|
+
}
|
1139
|
+
|
1140
|
+
/**
|
1141
|
+
* Document-class: Libsql::SQLite3::Database
|
1142
|
+
*
|
1143
|
+
* The ruby extension wrapper around the core sqlite3 database object.
|
1144
|
+
*
|
1145
|
+
*/
|
1146
|
+
void Init_libsql_ext_database( )
|
1147
|
+
{
|
1148
|
+
|
1149
|
+
VALUE ma = rb_define_module("Libsql");
|
1150
|
+
VALUE mas = rb_define_module_under(ma, "SQLite3");
|
1151
|
+
|
1152
|
+
/*
|
1153
|
+
* Encapsulate an SQLite3 database
|
1154
|
+
*/
|
1155
|
+
cLS_Database = rb_define_class_under( mas, "Database", rb_cObject);
|
1156
|
+
|
1157
|
+
rb_define_alloc_func(cLS_Database, libsql_ext_sqlite3_database_alloc);
|
1158
|
+
rb_define_singleton_method(cLS_Database, "open", libsql_ext_sqlite3_database_open, -1);
|
1159
|
+
rb_define_singleton_method(cLS_Database, "open16", libsql_ext_sqlite3_database_open16, 1);
|
1160
|
+
rb_define_method(cLS_Database, "prepare", libsql_ext_sqlite3_database_prepare, 1);
|
1161
|
+
rb_define_method(cLS_Database, "close", libsql_ext_sqlite3_database_close, 0); /* in libsql_ext_database.c */
|
1162
|
+
rb_define_method(cLS_Database, "last_insert_rowid", libsql_ext_sqlite3_database_last_insert_rowid, 0); /* in libsql_ext_database.c */
|
1163
|
+
rb_define_method(cLS_Database, "autocommit?", libsql_ext_sqlite3_database_is_autocommit, 0); /* in libsql_ext_database.c */
|
1164
|
+
rb_define_method(cLS_Database, "register_trace_tap", libsql_ext_sqlite3_database_register_trace_tap, 1); /* in libsql_ext_database.c */
|
1165
|
+
rb_define_method(cLS_Database, "table_column_metadata", libsql_ext_sqlite3_database_table_column_metadata, 3); /* in libsql_ext_database.c */
|
1166
|
+
rb_define_method(cLS_Database, "row_changes", libsql_ext_sqlite3_database_row_changes, 0); /* in libsql_ext_database.c */
|
1167
|
+
rb_define_method(cLS_Database, "total_changes", libsql_ext_sqlite3_database_total_changes, 0); /* in libsql_ext_database.c */
|
1168
|
+
rb_define_method(cLS_Database, "last_error_code", libsql_ext_sqlite3_database_last_error_code, 0); /* in libsql_ext_database.c */
|
1169
|
+
rb_define_method(cLS_Database, "last_error_message", libsql_ext_sqlite3_database_last_error_message, 0); /* in libsql_ext_database.c */
|
1170
|
+
rb_define_method(cLS_Database, "define_function", libsql_ext_sqlite3_database_define_function, 2); /* in libsql_ext_database.c */
|
1171
|
+
rb_define_method(cLS_Database, "remove_function", libsql_ext_sqlite3_database_remove_function, 2); /* in libsql_ext_database.c */
|
1172
|
+
rb_define_method(cLS_Database, "define_aggregate", libsql_ext_sqlite3_database_define_aggregate, 3); /* in libsql_ext_database.c */
|
1173
|
+
rb_define_method(cLS_Database, "remove_aggregate", libsql_ext_sqlite3_database_remove_aggregate, 3); /* in libsql_ext_database.c */
|
1174
|
+
rb_define_method(cLS_Database, "busy_handler", libsql_ext_sqlite3_database_busy_handler, 1); /* in libsql_ext_database.c */
|
1175
|
+
rb_define_method(cLS_Database, "progress_handler", libsql_ext_sqlite3_database_progress_handler, 2); /* in libsql_ext_database.c */
|
1176
|
+
rb_define_method(cLS_Database, "interrupt!", libsql_ext_sqlite3_database_interrupt_bang, 0); /* in libsql_ext_database.c */
|
1177
|
+
rb_define_method(cLS_Database, "replicate_to", libsql_ext_sqlite3_database_replicate_to, 1); /* in libsql_ext_database.c */
|
1178
|
+
rb_define_method(cLS_Database, "execute_batch", libsql_ext_sqlite3_database_exec, 1); /* in libsql_ext_database.c */
|
1179
|
+
|
1180
|
+
|
1181
|
+
/*
|
1182
|
+
* Ecapuslate a SQLite3 Database stat
|
1183
|
+
*/
|
1184
|
+
cLS_Database_Stat = rb_define_class_under( cLS_Database, "Stat", rb_cObject );
|
1185
|
+
rb_define_method(cLS_Database_Stat, "update!", libsql_ext_sqlite3_database_stat_update_bang, -1); /* in libsql_ext_database.c */
|
1186
|
+
|
1187
|
+
}
|
1188
|
+
|