amalgalite 0.1.0

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.
Files changed (64) hide show
  1. data/HISTORY +4 -0
  2. data/LICENSE +31 -0
  3. data/README +28 -0
  4. data/ext/amalgalite3.c +191 -0
  5. data/ext/amalgalite3.h +97 -0
  6. data/ext/amalgalite3_constants.c +179 -0
  7. data/ext/amalgalite3_database.c +458 -0
  8. data/ext/amalgalite3_statement.c +546 -0
  9. data/ext/gen_constants.rb +114 -0
  10. data/ext/mkrf_conf.rb +6 -0
  11. data/ext/sqlite3.c +87003 -0
  12. data/ext/sqlite3.h +5638 -0
  13. data/ext/sqlite3_options.h +4 -0
  14. data/ext/sqlite3ext.h +362 -0
  15. data/gemspec.rb +50 -0
  16. data/lib/amalgalite.rb +28 -0
  17. data/lib/amalgalite/blob.rb +14 -0
  18. data/lib/amalgalite/boolean.rb +42 -0
  19. data/lib/amalgalite/column.rb +83 -0
  20. data/lib/amalgalite/database.rb +505 -0
  21. data/lib/amalgalite/index.rb +27 -0
  22. data/lib/amalgalite/paths.rb +70 -0
  23. data/lib/amalgalite/profile_tap.rb +130 -0
  24. data/lib/amalgalite/schema.rb +90 -0
  25. data/lib/amalgalite/sqlite3.rb +4 -0
  26. data/lib/amalgalite/sqlite3/constants.rb +48 -0
  27. data/lib/amalgalite/sqlite3/version.rb +38 -0
  28. data/lib/amalgalite/statement.rb +307 -0
  29. data/lib/amalgalite/table.rb +34 -0
  30. data/lib/amalgalite/taps/console.rb +27 -0
  31. data/lib/amalgalite/taps/io.rb +71 -0
  32. data/lib/amalgalite/trace_tap.rb +35 -0
  33. data/lib/amalgalite/type_map.rb +60 -0
  34. data/lib/amalgalite/type_maps/default_map.rb +153 -0
  35. data/lib/amalgalite/type_maps/storage_map.rb +41 -0
  36. data/lib/amalgalite/type_maps/text_map.rb +23 -0
  37. data/lib/amalgalite/version.rb +32 -0
  38. data/lib/amalgalite/view.rb +24 -0
  39. data/spec/amalgalite_spec.rb +4 -0
  40. data/spec/boolean_spec.rb +26 -0
  41. data/spec/database_spec.rb +222 -0
  42. data/spec/default_map_spec.rb +85 -0
  43. data/spec/integeration_spec.rb +111 -0
  44. data/spec/paths_spec.rb +28 -0
  45. data/spec/schema_spec.rb +46 -0
  46. data/spec/spec_helper.rb +25 -0
  47. data/spec/sqlite3/constants_spec.rb +25 -0
  48. data/spec/sqlite3/version_spec.rb +14 -0
  49. data/spec/sqlite3_spec.rb +34 -0
  50. data/spec/statement_spec.rb +116 -0
  51. data/spec/storage_map_spec.rb +41 -0
  52. data/spec/tap_spec.rb +59 -0
  53. data/spec/text_map_spec.rb +23 -0
  54. data/spec/type_map_spec.rb +17 -0
  55. data/spec/version_spec.rb +9 -0
  56. data/tasks/announce.rake +38 -0
  57. data/tasks/config.rb +108 -0
  58. data/tasks/distribution.rake +38 -0
  59. data/tasks/documentation.rake +31 -0
  60. data/tasks/extension.rake +45 -0
  61. data/tasks/rspec.rake +32 -0
  62. data/tasks/rubyforge.rake +48 -0
  63. data/tasks/utils.rb +80 -0
  64. metadata +165 -0
@@ -0,0 +1,458 @@
1
+ #include "amalgalite3.h"
2
+ /**
3
+ * Copyright (c) 2008 Jeremy Hinegardner
4
+ * All rights reserved. See LICENSE and/or COPYING for details.
5
+ *
6
+ * vim: shiftwidth=4
7
+ */
8
+
9
+ VALUE cAS_Database; /* class Amalgliate::SQLite3::Database */
10
+
11
+ /**
12
+ * Document-method: open
13
+ *
14
+ * call-seq:
15
+ * Amalagliate::SQLite3::Database.open( filename, flags = READWRITE | CREATE ) -> Database
16
+ *
17
+ * Create a new SQLite3 database with a UTF-8 encoding.
18
+ *
19
+ */
20
+ VALUE am_sqlite3_database_open(int argc, VALUE *argv, VALUE class)
21
+ {
22
+ VALUE self = am_sqlite3_database_alloc(class);
23
+ VALUE rFlags;
24
+ VALUE rFilename;
25
+ int flags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE;
26
+ char* filename;
27
+ int rc;
28
+ am_sqlite3* am_db;
29
+
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, am_sqlite3, am_db);
40
+
41
+ /* open the sqlite3 database */
42
+ rc = sqlite3_open_v2( filename, &(am_db->db), flags, 0);
43
+ if ( SQLITE_OK != rc ) {
44
+ rb_raise(eAS_Error, "Failure to open database %s : [SQLITE_ERROR %d] : %s\n",
45
+ filename, rc, sqlite3_errmsg(am_db->db));
46
+ }
47
+
48
+ /* by default turn on the extended result codes */
49
+ rc = sqlite3_extended_result_codes( am_db->db, 1);
50
+ if ( SQLITE_OK != rc ) {
51
+ rb_raise(eAS_Error, "Failure to set extended result codes %s : [SQLITE_ERROR %d] : %s\n",
52
+ filename, rc, sqlite3_errmsg(am_db->db));
53
+ }
54
+
55
+ return self;
56
+ }
57
+
58
+ /**
59
+ * call-seq:
60
+ * Amalgalite::SQLite3::Database.open16( filename ) -> SQLite3::Database
61
+ *
62
+ * Create a new SQLite3 database with a UTF-16 encoding
63
+ *
64
+ */
65
+ VALUE am_sqlite3_database_open16(VALUE class, VALUE rFilename)
66
+ {
67
+ VALUE self = am_sqlite3_database_alloc(class);
68
+ char* filename = StringValuePtr(rFilename);
69
+ am_sqlite3* am_db;
70
+ int rc;
71
+
72
+ Data_Get_Struct(self, am_sqlite3, am_db);
73
+ rc = sqlite3_open16( filename, &(am_db->db) );
74
+ if ( SQLITE_OK != rc ) {
75
+ rb_raise(eAS_Error, "Failure to open UTF-16 database %s : [SQLITE_ERROR %d] : %s\n",
76
+ filename, rc, sqlite3_errmsg( am_db->db ));
77
+ }
78
+
79
+ /* by default turn on the extended result codes */
80
+ rc = sqlite3_extended_result_codes( am_db->db, 1);
81
+ if ( SQLITE_OK != rc ) {
82
+ rb_raise(eAS_Error, "Failure to set extended result codes on UTF-16 database %s : [SQLITE_ERROR %d] : %s\n",
83
+ filename, rc, sqlite3_errmsg16(am_db->db));
84
+ }
85
+
86
+ return self;
87
+ }
88
+
89
+ /**
90
+ * call-seq:
91
+ * database.close
92
+ *
93
+ * Close the database
94
+ */
95
+ VALUE am_sqlite3_database_close(VALUE self)
96
+ {
97
+ am_sqlite3 *am_db;
98
+ int rc = 0;
99
+
100
+ Data_Get_Struct(self, am_sqlite3, am_db);
101
+ rc = sqlite3_close( am_db->db );
102
+ if ( SQLITE_OK != rc ) {
103
+ rb_raise(eAS_Error, "Failure to close database : [SQLITE_ERROR %d] : %s\n",
104
+ rc, sqlite3_errmsg( am_db->db ));
105
+ }
106
+
107
+ return self;
108
+
109
+ }
110
+
111
+ /**
112
+ * call-seq:
113
+ * database.last_insert_rowid -> Integer
114
+ *
115
+ * Return the rowid of the last row inserted into the database from this
116
+ * database connection.
117
+ */
118
+ VALUE am_sqlite3_database_last_insert_rowid(VALUE self)
119
+ {
120
+ am_sqlite3 *am_db;
121
+ sqlite3_int64 last_id;
122
+
123
+ Data_Get_Struct(self, am_sqlite3, am_db);
124
+ last_id = sqlite3_last_insert_rowid( am_db->db );
125
+
126
+ return SQLINT64_2NUM( last_id );
127
+ }
128
+
129
+ /**
130
+ * call-seq:
131
+ * database.autocommit? -> true or false
132
+ *
133
+ * return true if the database is in autocommit mode, otherwise return false
134
+ *
135
+ */
136
+ VALUE am_sqlite3_database_is_autocommit(VALUE self)
137
+ {
138
+ am_sqlite3 *am_db;
139
+ int rc;
140
+
141
+ Data_Get_Struct(self, am_sqlite3, am_db);
142
+ rc = sqlite3_get_autocommit( am_db->db );
143
+
144
+ return ( 0 == rc ) ? Qfalse : Qtrue ;
145
+ }
146
+
147
+ /**
148
+ * call-seq:
149
+ * database.row_changes -> Integer
150
+ *
151
+ * return the number of rows changed with the most recent INSERT, UPDATE or
152
+ * DELETE statement.
153
+ *
154
+ */
155
+ VALUE am_sqlite3_database_row_changes(VALUE self)
156
+ {
157
+ am_sqlite3 *am_db;
158
+ int rc;
159
+
160
+ Data_Get_Struct(self, am_sqlite3, am_db);
161
+ rc = sqlite3_changes( am_db->db );
162
+
163
+ return INT2FIX(rc);
164
+ }
165
+
166
+ /**
167
+ * call-seq:
168
+ * database.total_changes -> Integer
169
+ *
170
+ * return the number of rows changed by INSERT, UPDATE or DELETE statements
171
+ * in the database connection since the connection was opened.
172
+ *
173
+ */
174
+ VALUE am_sqlite3_database_total_changes(VALUE self)
175
+ {
176
+ am_sqlite3 *am_db;
177
+ int rc;
178
+
179
+ Data_Get_Struct(self, am_sqlite3, am_db);
180
+ rc = sqlite3_total_changes( am_db->db );
181
+
182
+ return INT2FIX(rc);
183
+ }
184
+
185
+
186
+ /**
187
+ * call-seq:
188
+ * database.prepare( sql ) -> SQLite3::Statement
189
+ *
190
+ * Create a new SQLite3 statement.
191
+ */
192
+ VALUE am_sqlite3_database_prepare(VALUE self, VALUE rSQL)
193
+ {
194
+ VALUE sql = StringValue( rSQL );
195
+ VALUE stmt = am_sqlite3_statement_alloc(cAS_Statement);
196
+ am_sqlite3 *am_db;
197
+ am_sqlite3_stmt *am_stmt;
198
+ const char *tail;
199
+ int rc;
200
+
201
+ Data_Get_Struct(self, am_sqlite3, am_db);
202
+
203
+ Data_Get_Struct(stmt, am_sqlite3_stmt, am_stmt);
204
+ rc = sqlite3_prepare_v2( am_db->db, RSTRING(sql)->ptr, RSTRING(sql)->len,
205
+ &(am_stmt->stmt), &tail);
206
+ if ( SQLITE_OK != rc) {
207
+ rb_raise(eAS_Error, "Failure to prepare statement %s : [SQLITE_ERROR %d] : %s\n",
208
+ RSTRING(sql)->ptr, rc, sqlite3_errmsg(am_db->db));
209
+ am_sqlite3_statement_free( am_stmt );
210
+ }
211
+
212
+ if ( tail != NULL ) {
213
+ am_stmt->remaining_sql = rb_str_new2( tail );
214
+ } else {
215
+ am_stmt->remaining_sql = Qnil;
216
+ }
217
+
218
+ return stmt;
219
+ }
220
+
221
+ /**
222
+ * This function is registered with a sqlite3 database using the sqlite3_trace
223
+ * function. During the registration process a handle on a VALUE is also
224
+ * registered.
225
+ *
226
+ * When this function is called, it calls the 'trace' method on the tap object,
227
+ * which is the VALUE that was registered during the sqlite3_trace call.
228
+ *
229
+ * This function corresponds to the SQLite xTrace function specification.
230
+ *
231
+ */
232
+ void amalgalite_xTrace(void* tap, const char* msg)
233
+ {
234
+ VALUE trace_obj = (VALUE) tap;
235
+
236
+ rb_funcall( trace_obj, rb_intern("trace"), 1, rb_str_new2( msg ) );
237
+ return;
238
+ }
239
+
240
+
241
+ /**
242
+ * call-seq:
243
+ * database.register_trace_tap( tap_obj )
244
+ *
245
+ * This registers an object to be called with every trace event in SQLite.
246
+ *
247
+ * This is an experimental api and is subject to change, or removal.
248
+ *
249
+ */
250
+ VALUE am_sqlite3_database_register_trace_tap(VALUE self, VALUE tap)
251
+ {
252
+ am_sqlite3 *am_db;
253
+ int rc;
254
+
255
+ Data_Get_Struct(self, am_sqlite3, am_db);
256
+
257
+ /* Qnil, unregister the item and tell the garbage collector we are done with
258
+ * it.
259
+ */
260
+ if ( Qnil == tap ) {
261
+
262
+ sqlite3_trace( am_db->db, NULL, NULL );
263
+ rb_gc_unregister_address( &(am_db->trace_obj) );
264
+ am_db->trace_obj = Qnil;
265
+
266
+ /* register the item and store the reference to the object in the am_db
267
+ * structure. We also have to tell the Ruby garbage collector that we
268
+ * point to the Ruby object from C.
269
+ */
270
+ } else {
271
+
272
+ am_db->trace_obj = tap;
273
+ rb_gc_register_address( &(am_db->trace_obj) );
274
+ sqlite3_trace( am_db->db, amalgalite_xTrace, (void *)am_db->trace_obj );
275
+ }
276
+
277
+ return Qnil;
278
+ }
279
+
280
+ /**
281
+ * the amagliate trace function to be registered with register_trace_tap
282
+ * When it is called, it calls the 'trace' method on the tap object.
283
+ *
284
+ * This function conforms to the sqlite3 xProfile function specification.
285
+ */
286
+ void amalgalite_xProfile(void* tap, const char* msg, sqlite3_uint64 time)
287
+ {
288
+ VALUE trace_obj = (VALUE) tap;
289
+
290
+ rb_funcall( trace_obj, rb_intern("profile"),
291
+ 2, rb_str_new2( msg ), SQLUINT64_2NUM(time) );
292
+
293
+ return;
294
+ }
295
+
296
+ /**
297
+ * call-seq:
298
+ * database.register_profile_tap( tap_obj )
299
+ *
300
+ * This registers an object to be called with every profile event in SQLite.
301
+ *
302
+ * This is an experimental api and is subject to change or removal.
303
+ *
304
+ */
305
+ VALUE am_sqlite3_database_register_profile_tap(VALUE self, VALUE tap)
306
+ {
307
+ am_sqlite3 *am_db;
308
+ int rc;
309
+
310
+ Data_Get_Struct(self, am_sqlite3, am_db);
311
+
312
+ /* Qnil, unregister the item and tell the garbage collector we are done with
313
+ * it.
314
+ */
315
+
316
+ if ( tap == Qnil ) {
317
+ sqlite3_profile( am_db->db, NULL, NULL );
318
+ rb_gc_unregister_address( &(am_db->profile_obj) );
319
+ am_db->profile_obj = Qnil;
320
+
321
+ /* register the item and store the reference to the object in the am_db
322
+ * structure. We also have to tell the Ruby garbage collector that we
323
+ * point to the Ruby object from C.
324
+ */
325
+ } else {
326
+ am_db->profile_obj = tap;
327
+ rb_gc_register_address( &(am_db->profile_obj) );
328
+ sqlite3_profile( am_db->db, amalgalite_xProfile, (void *)am_db->profile_obj );
329
+ }
330
+ return Qnil;
331
+ }
332
+
333
+ /**
334
+ * call-seq:
335
+ * datatabase.table_column_metadata( db_name, table_name, column_name) -> Hash
336
+ *
337
+ * Returns a hash containing the meta information about the column. The
338
+ * available keys are:
339
+ *
340
+ * declared_data_type:: the declared data type of the column
341
+ * collation_sequence_name:: the name of the collation sequence for the column
342
+ * not_null_constraint:: True if the column has a NOT NULL constraint
343
+ * primary_key:: True if the column is part of a primary key
344
+ * auto_increment:: True if the column is AUTO INCREMENT
345
+ *
346
+ */
347
+ VALUE am_sqlite3_database_table_column_metadata(VALUE self, VALUE db_name, VALUE tbl_name, VALUE col_name)
348
+ {
349
+ am_sqlite3 *am_db;
350
+ int rc;
351
+
352
+ /* input */
353
+ const char *zDbName = StringValuePtr( db_name );
354
+ const char *zTableName = StringValuePtr( tbl_name );
355
+ const char *zColumnName = StringValuePtr( col_name );
356
+
357
+ /* output */
358
+ const char *pzDataType = NULL;
359
+ const char *pzCollSeq = NULL;
360
+ int pNotNull, pPrimaryKey, pAutoinc;
361
+ VALUE rHash = rb_hash_new();
362
+ VALUE rStr = Qnil;
363
+
364
+ Data_Get_Struct(self, am_sqlite3, am_db);
365
+
366
+ rc = sqlite3_table_column_metadata( am_db->db,
367
+ zDbName, zTableName, zColumnName,
368
+ &pzDataType, &pzCollSeq,
369
+ &pNotNull, &pPrimaryKey, &pAutoinc);
370
+ if ( SQLITE_OK != rc ) {
371
+ rb_raise(eAS_Error, "Failure retrieveing column meta data for table '%s' column '%s' : [SQLITE_ERROR %d] : %s\n",
372
+ zTableName, zColumnName, rc, sqlite3_errmsg( am_db-> db ));
373
+
374
+ }
375
+
376
+ rStr = ( NULL == pzDataType) ? Qnil : rb_str_new2( pzDataType );
377
+ rb_hash_aset( rHash, rb_str_new2("declared_data_type"), rStr );
378
+
379
+ rStr = ( NULL == pzCollSeq) ? Qnil : rb_str_new2( pzCollSeq );
380
+ rb_hash_aset( rHash, rb_str_new2("collation_sequence_name"), rStr );
381
+
382
+ rb_hash_aset( rHash, rb_str_new2("not_null_constraint"), ( pNotNull ? Qtrue : Qfalse ));
383
+ rb_hash_aset( rHash, rb_str_new2("primary_key"), ( pPrimaryKey ? Qtrue : Qfalse ));
384
+ rb_hash_aset( rHash, rb_str_new2("auto_increment"), ( pAutoinc ? Qtrue : Qfalse ));
385
+
386
+ return rHash;
387
+ }
388
+
389
+ /***********************************************************************
390
+ * Ruby life cycle methods
391
+ ***********************************************************************/
392
+
393
+
394
+ /*
395
+ * garbage collector free method for the am_data structure. Make sure to un
396
+ * registere the trace and profile objects if they are not Qnil
397
+ */
398
+ void am_sqlite3_database_free(am_sqlite3* am_db)
399
+ {
400
+ if ( Qnil != am_db->trace_obj ) {
401
+ rb_gc_unregister_address( &(am_db->trace_obj) );
402
+ am_db->trace_obj = Qnil;
403
+ }
404
+
405
+ if ( Qnil != am_db->profile_obj) {
406
+ rb_gc_unregister_address( &(am_db->profile_obj) );
407
+ am_db->profile_obj = Qnil;
408
+ }
409
+
410
+ free(am_db);
411
+ return;
412
+ }
413
+
414
+ /*
415
+ * allocate the am_data structure
416
+ */
417
+ VALUE am_sqlite3_database_alloc(VALUE klass)
418
+ {
419
+ am_sqlite3* am_db = ALLOC(am_sqlite3);
420
+ VALUE obj ;
421
+
422
+ am_db->trace_obj = Qnil;
423
+ am_db->profile_obj = Qnil;
424
+
425
+ obj = Data_Wrap_Struct(klass, NULL, am_sqlite3_database_free, am_db);
426
+ return obj;
427
+ }
428
+
429
+ void Init_amalgalite3_database( )
430
+ {
431
+
432
+ /** :stopdoc:
433
+ * These calls are here just to allow for rdoc generation
434
+ * :startdoc:
435
+ */
436
+ VALUE ma = rb_define_module("Amalgalite");
437
+ VALUE mas = rb_define_module_under(ma, "SQLite3");
438
+
439
+ /*
440
+ * Encapsulate an SQLite3 database
441
+ */
442
+ cAS_Database = rb_define_class_under( mas, "Database", rb_cObject);
443
+
444
+ rb_define_alloc_func(cAS_Database, am_sqlite3_database_alloc);
445
+ rb_define_singleton_method(cAS_Database, "open", am_sqlite3_database_open, -1);
446
+ rb_define_singleton_method(cAS_Database, "open16", am_sqlite3_database_open16, 1);
447
+ rb_define_method(cAS_Database, "prepare", am_sqlite3_database_prepare, 1);
448
+ rb_define_method(cAS_Database, "close", am_sqlite3_database_close, 0); /* in amalgalite3_database.c */
449
+ rb_define_method(cAS_Database, "last_insert_rowid", am_sqlite3_database_last_insert_rowid, 0); /* in amalgalite3_database.c */
450
+ rb_define_method(cAS_Database, "autocommit?", am_sqlite3_database_is_autocommit, 0); /* in amalgalite3_database.c */
451
+ rb_define_method(cAS_Database, "register_trace_tap", am_sqlite3_database_register_trace_tap, 1); /* in amalgalite3_database.c */
452
+ rb_define_method(cAS_Database, "register_profile_tap", am_sqlite3_database_register_profile_tap, 1); /* in amalgalite3_database.c */
453
+ rb_define_method(cAS_Database, "table_column_metadata", am_sqlite3_database_table_column_metadata, 3); /* in amalgalite3_database.c */
454
+ rb_define_method(cAS_Database, "row_changes", am_sqlite3_database_row_changes, 0); /* in amalgalite3_database.c */
455
+ rb_define_method(cAS_Database, "total_changes", am_sqlite3_database_total_changes, 0); /* in amalgalite3_database.c */
456
+
457
+ }
458
+