amalgalite 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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
+