libsql 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (98) hide show
  1. checksums.yaml +7 -0
  2. data/CONTRIBUTING.md +60 -0
  3. data/HISTORY.md +6 -0
  4. data/LICENSE +31 -0
  5. data/Manifest.txt +96 -0
  6. data/README.md +59 -0
  7. data/Rakefile +28 -0
  8. data/TODO.md +57 -0
  9. data/examples/a.rb +9 -0
  10. data/examples/blob.rb +106 -0
  11. data/examples/define_aggregate.rb +75 -0
  12. data/examples/define_function.rb +104 -0
  13. data/examples/fts5.rb +152 -0
  14. data/examples/gem-db.rb +94 -0
  15. data/examples/schema-info.rb +34 -0
  16. data/ext/libsql/c/extconf.rb +86 -0
  17. data/ext/libsql/c/gen_constants.rb +353 -0
  18. data/ext/libsql/c/libsql_blob.c +240 -0
  19. data/ext/libsql/c/libsql_constants.c +1518 -0
  20. data/ext/libsql/c/libsql_database.c +1188 -0
  21. data/ext/libsql/c/libsql_ext.c +383 -0
  22. data/ext/libsql/c/libsql_ext.h +149 -0
  23. data/ext/libsql/c/libsql_statement.c +649 -0
  24. data/ext/libsql/c/notes.txt +134 -0
  25. data/ext/libsql/c/sqlite3.c +247030 -0
  26. data/ext/libsql/c/sqlite3.h +13436 -0
  27. data/lib/libsql/aggregate.rb +73 -0
  28. data/lib/libsql/blob.rb +186 -0
  29. data/lib/libsql/boolean.rb +42 -0
  30. data/lib/libsql/busy_timeout.rb +47 -0
  31. data/lib/libsql/column.rb +99 -0
  32. data/lib/libsql/csv_table_importer.rb +75 -0
  33. data/lib/libsql/database.rb +933 -0
  34. data/lib/libsql/function.rb +61 -0
  35. data/lib/libsql/index.rb +43 -0
  36. data/lib/libsql/memory_database.rb +15 -0
  37. data/lib/libsql/paths.rb +80 -0
  38. data/lib/libsql/profile_tap.rb +131 -0
  39. data/lib/libsql/progress_handler.rb +21 -0
  40. data/lib/libsql/schema.rb +225 -0
  41. data/lib/libsql/sqlite3/constants.rb +95 -0
  42. data/lib/libsql/sqlite3/database/function.rb +48 -0
  43. data/lib/libsql/sqlite3/database/status.rb +68 -0
  44. data/lib/libsql/sqlite3/libsql_version.rb +32 -0
  45. data/lib/libsql/sqlite3/status.rb +60 -0
  46. data/lib/libsql/sqlite3/version.rb +55 -0
  47. data/lib/libsql/sqlite3.rb +7 -0
  48. data/lib/libsql/statement.rb +421 -0
  49. data/lib/libsql/table.rb +91 -0
  50. data/lib/libsql/taps/console.rb +27 -0
  51. data/lib/libsql/taps/io.rb +74 -0
  52. data/lib/libsql/taps.rb +2 -0
  53. data/lib/libsql/trace_tap.rb +35 -0
  54. data/lib/libsql/type_map.rb +63 -0
  55. data/lib/libsql/type_maps/default_map.rb +166 -0
  56. data/lib/libsql/type_maps/storage_map.rb +38 -0
  57. data/lib/libsql/type_maps/text_map.rb +21 -0
  58. data/lib/libsql/version.rb +8 -0
  59. data/lib/libsql/view.rb +26 -0
  60. data/lib/libsql-ruby.rb +1 -0
  61. data/lib/libsql.rb +51 -0
  62. data/spec/aggregate_spec.rb +158 -0
  63. data/spec/blob_spec.rb +78 -0
  64. data/spec/boolean_spec.rb +24 -0
  65. data/spec/busy_handler.rb +157 -0
  66. data/spec/data/iso-3166-country.txt +242 -0
  67. data/spec/data/iso-3166-schema.sql +22 -0
  68. data/spec/data/iso-3166-subcountry.txt +3995 -0
  69. data/spec/data/make-iso-db.sh +12 -0
  70. data/spec/database_spec.rb +505 -0
  71. data/spec/default_map_spec.rb +92 -0
  72. data/spec/function_spec.rb +78 -0
  73. data/spec/integeration_spec.rb +97 -0
  74. data/spec/iso_3166_database.rb +58 -0
  75. data/spec/json_spec.rb +24 -0
  76. data/spec/libsql_spec.rb +4 -0
  77. data/spec/paths_spec.rb +28 -0
  78. data/spec/progress_handler_spec.rb +91 -0
  79. data/spec/rtree_spec.rb +66 -0
  80. data/spec/schema_spec.rb +131 -0
  81. data/spec/spec_helper.rb +48 -0
  82. data/spec/sqlite3/constants_spec.rb +108 -0
  83. data/spec/sqlite3/database_status_spec.rb +36 -0
  84. data/spec/sqlite3/libsql_version_spec.rb +16 -0
  85. data/spec/sqlite3/status_spec.rb +22 -0
  86. data/spec/sqlite3/version_spec.rb +28 -0
  87. data/spec/sqlite3_spec.rb +53 -0
  88. data/spec/statement_spec.rb +168 -0
  89. data/spec/storage_map_spec.rb +38 -0
  90. data/spec/tap_spec.rb +57 -0
  91. data/spec/text_map_spec.rb +20 -0
  92. data/spec/type_map_spec.rb +14 -0
  93. data/spec/version_spec.rb +8 -0
  94. data/tasks/custom.rake +134 -0
  95. data/tasks/default.rake +257 -0
  96. data/tasks/extension.rake +29 -0
  97. data/tasks/this.rb +208 -0
  98. metadata +325 -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, &current, &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
+