amalgalite 0.5.1 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
data/HISTORY CHANGED
@@ -1,4 +1,17 @@
1
1
  = Changelog
2
+ == Version 0.6.0 - 2009-01-10
3
+
4
+ === Major Enhancements
5
+
6
+ * Added ability to define custom SQL functions implemented in Ruby
7
+ * Added ability to define custom SQL aggregates implemented in Ruby
8
+ * Added support for Ruby busy handlers
9
+ * Added database 'interrupt' support
10
+ * Added support for Ruby progress handlers
11
+
12
+ === Minor Enhancement
13
+
14
+ * update to SQLite version 3.6.7
2
15
 
3
16
  == Version 0.5.1 - 2008-11-30
4
17
 
@@ -26,11 +39,11 @@
26
39
 
27
40
  == Version 0.4.1 - 2008-09-28
28
41
 
29
- === Minor Enhancement
42
+ === Minor Enhancement
30
43
 
31
44
  * update to SQLite3 version 3.6.3
32
45
  * change rdoc template to darkfish
33
-
46
+
34
47
  == Version 0.4.0 - 2008-09-14
35
48
 
36
49
  === Major Enhancements
data/README CHANGED
@@ -19,6 +19,9 @@ Look in the examples/ directory to see
19
19
  * general usage
20
20
  * blob io
21
21
  * schema information
22
+ * custom functions
23
+ * custom aggregates
24
+ * requiring ruby code from a database
22
25
 
23
26
  Also Scroll through Amalgalite::Database for a quick example, and a general
24
27
  overview of the API.
@@ -0,0 +1,75 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'rubygems'
4
+ $: << "../lib"
5
+ $: << "../ext"
6
+ require 'amalgalite'
7
+
8
+ #--
9
+ # Create a database and a table to put some results from the functions in
10
+ #--
11
+ db = Amalgalite::Database.new( ":memory:" )
12
+ db.execute( "CREATE TABLE atest( words )" )
13
+
14
+ #------------------------------------------------------------------------------
15
+ # Create unique word count aggregate
16
+ #------------------------------------------------------------------------------
17
+ class UniqueWordCount < ::Amalgalite::Aggregate
18
+ attr_accessor :words
19
+
20
+ def initialize
21
+ @name = 'unique_word_count'
22
+ @arity = 1
23
+ @words = Hash.new { |h,k| h[k] = 0 }
24
+ end
25
+
26
+ def step( str )
27
+ str.split(/\W+/).each do |word|
28
+ words[ word.downcase ] += 1
29
+ end
30
+ return nil
31
+ end
32
+
33
+ def finalize
34
+ return words.size
35
+ end
36
+ end
37
+
38
+ db.define_aggregate( 'unique_word_count', UniqueWordCount )
39
+
40
+ #------------------------------------------------------------------------------
41
+ # Now we have a new aggregate function, lets insert some rows into the database
42
+ # and see what we can find.
43
+ #------------------------------------------------------------------------------
44
+ sql = "INSERT INTO atest( words ) VALUES( ? )"
45
+ verify = {}
46
+ db.prepare( sql ) do |stmt|
47
+ DATA.each do |words|
48
+ words.strip!
49
+ puts "Inserting #{words}"
50
+ stmt.execute( words )
51
+ words.split(/\W+/).each { |w| verify[w] = true }
52
+ end
53
+ end
54
+
55
+ #------------------------------------------------------------------------------
56
+ # And show the results
57
+ #------------------------------------------------------------------------------
58
+ puts
59
+ puts "Getting results..."
60
+ puts
61
+ all_rows = db.execute("SELECT unique_word_count( words ) AS uwc FROM atest")
62
+ puts "#{all_rows.first['uwc']} unique words found"
63
+ puts "#{verify.size} unique words to verify"
64
+
65
+ __END__
66
+ some random
67
+ words with
68
+ which
69
+ to play
70
+ and there should
71
+ be a couple of different
72
+ words that appear
73
+ more than once and
74
+ some that appear only
75
+ once
@@ -0,0 +1,104 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'rubygems'
4
+ $: << "../lib"
5
+ $: << "../ext"
6
+ require 'amalgalite'
7
+
8
+ #--
9
+ # Create a database and a table to put some results from the functions in
10
+ #--
11
+ db = Amalgalite::Database.new( ":memory:" )
12
+ db.execute( "CREATE TABLE ftest( data, md5, sha1, sha2_bits, sha2)" )
13
+
14
+ #------------------------------------------------------------------------------
15
+ # Create an MD5 method using the block format of defining an sql fuction
16
+ #------------------------------------------------------------------------------
17
+ require 'digest/md5'
18
+ db.define_function( 'md5' ) do |x|
19
+ Digest::MD5.hexdigest( x.to_s )
20
+ end
21
+
22
+ #------------------------------------------------------------------------------
23
+ # Create a SHA1 method using the lambda format of defining an sql function
24
+ #------------------------------------------------------------------------------
25
+ require 'digest/sha1'
26
+ sha1 = lambda do |y|
27
+ Digest::SHA1.hexdigest( y.to_s )
28
+ end
29
+ db.define_function( "sha1", sha1 )
30
+
31
+ #------------------------------------------------------------------------------
32
+ # Create a SHA2 method using the class format for defining an sql function
33
+ # In this one we will allow any number of parameters, but we will only use the
34
+ # first two.
35
+ #------------------------------------------------------------------------------
36
+ require 'digest/sha2'
37
+ class SQLSha2
38
+ # track the number of invocations
39
+ attr_reader :call_count
40
+
41
+ def initialize
42
+ @call_count = 0
43
+ end
44
+
45
+ # the protocol that is used for sql function definition
46
+ def to_proc() self ; end
47
+
48
+ # say we take any number of parameters
49
+ def arity
50
+ -1
51
+ end
52
+
53
+ # The method that is called by SQLite, must be named 'call'
54
+ def call( *args )
55
+ text = args.shift.to_s
56
+ bitlength = (args.shift || 256).to_i
57
+ Digest::SHA2.new( bitlength ).hexdigest( text )
58
+ end
59
+ end
60
+ db.define_function('sha2', SQLSha2.new)
61
+
62
+
63
+ #------------------------------------------------------------------------------
64
+ # Now we have 3 new sql functions, each defined in one of the available methods
65
+ # to define sql functions in amalgalite. Lets insert some rows and look at the
66
+ # results
67
+ #------------------------------------------------------------------------------
68
+ possible_bits = [ 256, 384, 512 ]
69
+ sql = "INSERT INTO ftest( data, md5, sha1, sha2_bits, sha2 ) VALUES( @word , md5( @word ), sha1( @word ), @bits, sha2(@word,@bits) )"
70
+ db.prepare( sql ) do |stmt|
71
+ DATA.each do |word|
72
+ word.strip!
73
+ bits = possible_bits[ rand(3) ]
74
+ puts "Inserting #{word}, #{bits}"
75
+ stmt.execute( { '@word' => word, '@bits' => bits } )
76
+ end
77
+ end
78
+
79
+ #------------------------------------------------------------------------------
80
+ # And show the results
81
+ #------------------------------------------------------------------------------
82
+ puts
83
+ puts "Getting results..."
84
+ puts
85
+ columns = db.schema.tables['ftest'].columns.keys.sort
86
+ i = 0
87
+ db.execute("SELECT #{columns.join(',')} FROM ftest") do |row|
88
+ i += 1
89
+ puts "-----[ row #{i} ]-" + "-" * 42
90
+ columns.each do |col|
91
+ puts "#{col.ljust(10)} : #{row[col]}"
92
+ end
93
+ puts
94
+ end
95
+
96
+
97
+ __END__
98
+ some
99
+ random
100
+ words
101
+ with
102
+ which
103
+ to
104
+ play
Binary file
@@ -16,6 +16,8 @@ typedef struct am_sqlite3 {
16
16
  sqlite3 *db;
17
17
  VALUE trace_obj;
18
18
  VALUE profile_obj;
19
+ VALUE busy_handler_obj;
20
+ VALUE progress_handler_obj;
19
21
  } am_sqlite3;
20
22
 
21
23
  /* wrapper struct around the sqlite3_statement opaque pointer */
@@ -32,6 +34,15 @@ typedef struct am_sqlite3_blob {
32
34
  int current_offset;
33
35
  } am_sqlite3_blob;
34
36
 
37
+ /* wrapper struct around the information needed to call rb_apply
38
+ * used to encapsulate data into a call for amalgalite_wrap_apply
39
+ */
40
+ typedef struct am_protected {
41
+ VALUE instance;
42
+ ID method;
43
+ int argc;
44
+ VALUE *argv;
45
+ } am_protected_t;
35
46
 
36
47
  /** module and classes **/
37
48
  extern VALUE mA; /* module Amalgalite */
@@ -41,11 +52,10 @@ extern VALUE eAS_Error; /* class Amalgalite::SQLite3::Error */
41
52
  extern VALUE cAR; /* class Amalgalite::Requries */
42
53
  extern VALUE cARB; /* class Amalgalite::Requries::Bootstrap */
43
54
 
44
-
45
55
  /*----------------------------------------------------------------------
46
56
  * Prototype for Amalgalite::SQLite3::Database
47
57
  *---------------------------------------------------------------------*/
48
- extern VALUE cAS_Database; /* class Amalgliate::SQLite3::Database */
58
+ extern VALUE cAS_Database; /* class Amalgalite::SQLite3::Database */
49
59
 
50
60
  extern void am_define_constants_under(VALUE);
51
61
  extern VALUE am_sqlite3_database_alloc(VALUE klass);
@@ -62,6 +72,7 @@ extern VALUE am_sqlite3_database_table_column_metadata(VALUE self, VALUE db_name
62
72
  extern VALUE am_sqlite3_database_prepare(VALUE self, VALUE rSQL);
63
73
  extern VALUE am_sqlite3_database_register_trace_tap(VALUE self, VALUE tap);
64
74
  extern VALUE am_sqlite3_database_register_profile_tap(VALUE self, VALUE tap);
75
+ extern VALUE am_sqlite3_database_busy_handler(VALUE self, VALUE handler);
65
76
 
66
77
  /*----------------------------------------------------------------------
67
78
  * Prototype for Amalgalite::SQLite3::Statement
@@ -113,6 +124,16 @@ extern VALUE am_sqlite3_blob_write(VALUE self, VALUE buffer);
113
124
  extern VALUE am_sqlite3_blob_close(VALUE self);
114
125
  extern VALUE am_sqlite3_blob_length(VALUE self);
115
126
 
127
+ /*----------------------------------------------------------------------
128
+ * more initialization methods
129
+ *----------------------------------------------------------------------*/
130
+ extern void Init_amalgalite3_constants( );
131
+ extern void Init_amalgalite3_database( );
132
+ extern void Init_amalgalite3_statement( );
133
+ extern void Init_amalgalite3_blob( );
134
+ extern void Init_amalgalite3_requires_bootstrap( );
135
+
136
+
116
137
  /***********************************************************************
117
138
  * Type conversion macros between sqlite data types and ruby types
118
139
  **********************************************************************/
@@ -121,4 +142,9 @@ extern VALUE am_sqlite3_blob_length(VALUE self);
121
142
  #define SQLUINT64_2NUM(x) ( ULL2NUM( x ) )
122
143
  #define NUM2SQLINT64( obj ) ( NUM2LL( obj ) )
123
144
  #define NUM2SQLUINT64( obj ) ( NUM2ULL( obj ) )
145
+
146
+ /***********************************************************************
147
+ * return the last exception in ruby's error message
148
+ */
149
+ #define ERROR_INFO_MESSAGE() ( rb_obj_as_string( rb_gv_get("$!") ) )
124
150
  #endif
@@ -99,10 +99,9 @@ VALUE am_sqlite3_blob_close( VALUE self )
99
99
  VALUE am_sqlite3_blob_length( VALUE self )
100
100
  {
101
101
  am_sqlite3_blob *am_blob;
102
- int n;
103
102
 
104
103
  Data_Get_Struct(self, am_sqlite3_blob, am_blob);
105
-
104
+
106
105
  return INT2FIX( am_blob->length );
107
106
  }
108
107
 
@@ -3,21 +3,21 @@
3
3
  * Copyright (c) 2008 Jeremy Hinegardner
4
4
  * All rights reserved. See LICENSE and/or COPYING for details.
5
5
  *
6
- * vim: shiftwidth=4
7
- */
6
+ * vim: shiftwidth=4
7
+ */
8
8
 
9
- VALUE cAS_Database; /* class Amalgliate::SQLite3::Database */
10
- VALUE cAS_Database_Stat; /* class Amalgliate::SQLite3::Database::Stat */
9
+ VALUE cAS_Database; /* class Amalgalite::SQLite3::Database */
10
+ VALUE cAS_Database_Stat; /* class Amalgalite::SQLite3::Database::Stat */
11
11
 
12
12
  /**
13
13
  * Document-method: open
14
14
  *
15
15
  * call-seq:
16
- * Amalagliate::SQLite3::Database.open( filename, flags = READWRITE | CREATE ) -> Database
16
+ * Amalgalite::SQLite3::Database.open( filename, flags = READWRITE | CREATE ) -> Database
17
17
  *
18
- * Create a new SQLite3 database with a UTF-8 encoding.
18
+ * Create a new SQLite2 database with a UTF-8 encoding.
19
19
  *
20
- */
20
+ */
21
21
  VALUE am_sqlite3_database_open(int argc, VALUE *argv, VALUE class)
22
22
  {
23
23
  VALUE self = am_sqlite3_database_alloc(class);
@@ -28,7 +28,6 @@ VALUE am_sqlite3_database_open(int argc, VALUE *argv, VALUE class)
28
28
  int rc;
29
29
  am_sqlite3* am_db;
30
30
 
31
-
32
31
  /* at least a filename argument is required */
33
32
  rb_scan_args( argc, argv, "11", &rFilename, &rFlags );
34
33
 
@@ -89,7 +88,7 @@ VALUE am_sqlite3_database_open16(VALUE class, VALUE rFilename)
89
88
 
90
89
  /**
91
90
  * call-seq:
92
- * database.close
91
+ * database.close
93
92
  *
94
93
  * Close the database
95
94
  */
@@ -106,7 +105,7 @@ VALUE am_sqlite3_database_close(VALUE self)
106
105
  }
107
106
 
108
107
  return self;
109
-
108
+
110
109
  }
111
110
 
112
111
  /**
@@ -114,7 +113,7 @@ VALUE am_sqlite3_database_close(VALUE self)
114
113
  * database.last_insert_rowid -> Integer
115
114
  *
116
115
  * Return the rowid of the last row inserted into the database from this
117
- * database connection.
116
+ * database connection.
118
117
  */
119
118
  VALUE am_sqlite3_database_last_insert_rowid(VALUE self)
120
119
  {
@@ -141,7 +140,7 @@ VALUE am_sqlite3_database_is_autocommit(VALUE self)
141
140
 
142
141
  Data_Get_Struct(self, am_sqlite3, am_db);
143
142
  rc = sqlite3_get_autocommit( am_db->db );
144
-
143
+
145
144
  return ( 0 == rc ) ? Qfalse : Qtrue ;
146
145
  }
147
146
 
@@ -149,7 +148,7 @@ VALUE am_sqlite3_database_is_autocommit(VALUE self)
149
148
  * call-seq:
150
149
  * database.row_changes -> Integer
151
150
  *
152
- * return the number of rows changed with the most recent INSERT, UPDATE or
151
+ * return the number of rows changed with the most recent INSERT, UPDATE or
153
152
  * DELETE statement.
154
153
  *
155
154
  */
@@ -160,7 +159,7 @@ VALUE am_sqlite3_database_row_changes(VALUE self)
160
159
 
161
160
  Data_Get_Struct(self, am_sqlite3, am_db);
162
161
  rc = sqlite3_changes( am_db->db );
163
-
162
+
164
163
  return INT2FIX(rc);
165
164
  }
166
165
 
@@ -178,7 +177,7 @@ VALUE am_sqlite3_database_last_error_code(VALUE self)
178
177
 
179
178
  Data_Get_Struct(self, am_sqlite3, am_db);
180
179
  code = sqlite3_errcode( am_db->db );
181
-
180
+
182
181
  return INT2FIX( code );
183
182
  }
184
183
 
@@ -196,7 +195,7 @@ VALUE am_sqlite3_database_last_error_message(VALUE self)
196
195
 
197
196
  Data_Get_Struct(self, am_sqlite3, am_db);
198
197
  message = sqlite3_errmsg( am_db->db );
199
-
198
+
200
199
  return rb_str_new2( message );
201
200
  }
202
201
 
@@ -204,7 +203,7 @@ VALUE am_sqlite3_database_last_error_message(VALUE self)
204
203
  * call-seq:
205
204
  * database.total_changes -> Integer
206
205
  *
207
- * return the number of rows changed by INSERT, UPDATE or DELETE statements
206
+ * return the number of rows changed by INSERT, UPDATE or DELETE statements
208
207
  * in the database connection since the connection was opened.
209
208
  *
210
209
  */
@@ -215,7 +214,7 @@ VALUE am_sqlite3_database_total_changes(VALUE self)
215
214
 
216
215
  Data_Get_Struct(self, am_sqlite3, am_db);
217
216
  rc = sqlite3_total_changes( am_db->db );
218
-
217
+
219
218
  return INT2FIX(rc);
220
219
  }
221
220
 
@@ -223,8 +222,8 @@ VALUE am_sqlite3_database_total_changes(VALUE self)
223
222
  * call-seq:
224
223
  * stat.update!( reset = false ) -> nil
225
224
  *
226
- * Populates the _@current_ and _@higwater_ instance variables of the given
227
- * Database Stat object with the values from the sqlite3_db_status call.
225
+ * Populates the _@current_ and _@higwater_ instance variables of the given
226
+ * Database Stat object with the values from the sqlite3_db_status call.
228
227
  * If reset it true then the highwater mark for the stat is reset
229
228
  *
230
229
  */
@@ -270,10 +269,10 @@ VALUE am_sqlite3_database_stat_update_bang( int argc, VALUE *argv, VALUE self )
270
269
  *
271
270
  * Create a new SQLite3 statement.
272
271
  */
273
- VALUE am_sqlite3_database_prepare(VALUE self, VALUE rSQL)
272
+ VALUE am_sqlite3_database_prepare(VALUE self, VALUE rSQL)
274
273
  {
275
274
  VALUE sql = StringValue( rSQL );
276
- VALUE stmt = am_sqlite3_statement_alloc(cAS_Statement);
275
+ VALUE stmt = am_sqlite3_statement_alloc(cAS_Statement);
277
276
  am_sqlite3 *am_db;
278
277
  am_sqlite3_stmt *am_stmt;
279
278
  const char *tail;
@@ -282,7 +281,7 @@ VALUE am_sqlite3_database_prepare(VALUE self, VALUE rSQL)
282
281
  Data_Get_Struct(self, am_sqlite3, am_db);
283
282
 
284
283
  Data_Get_Struct(stmt, am_sqlite3_stmt, am_stmt);
285
- rc = sqlite3_prepare_v2( am_db->db, RSTRING(sql)->ptr, RSTRING(sql)->len,
284
+ rc = sqlite3_prepare_v2( am_db->db, RSTRING(sql)->ptr, RSTRING(sql)->len,
286
285
  &(am_stmt->stmt), &tail);
287
286
  if ( SQLITE_OK != rc) {
288
287
  rb_raise(eAS_Error, "Failure to prepare statement %s : [SQLITE_ERROR %d] : %s\n",
@@ -303,9 +302,9 @@ VALUE am_sqlite3_database_prepare(VALUE self, VALUE rSQL)
303
302
  /**
304
303
  * This function is registered with a sqlite3 database using the sqlite3_trace
305
304
  * function. During the registration process a handle on a VALUE is also
306
- * registered.
305
+ * registered.
307
306
  *
308
- * When this function is called, it calls the 'trace' method on the tap object,
307
+ * When this function is called, it calls the 'trace' method on the tap object,
309
308
  * which is the VALUE that was registered during the sqlite3_trace call.
310
309
  *
311
310
  * This function corresponds to the SQLite xTrace function specification.
@@ -318,21 +317,20 @@ void amalgalite_xTrace(void* tap, const char* msg)
318
317
  rb_funcall( trace_obj, rb_intern("trace"), 1, rb_str_new2( msg ) );
319
318
  return;
320
319
  }
321
-
320
+
322
321
 
323
322
  /**
324
323
  * call-seq:
325
324
  * database.register_trace_tap( tap_obj )
326
325
  *
327
326
  * This registers an object to be called with every trace event in SQLite.
328
- *
327
+ *
329
328
  * This is an experimental api and is subject to change, or removal.
330
329
  *
331
330
  */
332
331
  VALUE am_sqlite3_database_register_trace_tap(VALUE self, VALUE tap)
333
332
  {
334
333
  am_sqlite3 *am_db;
335
- int rc;
336
334
 
337
335
  Data_Get_Struct(self, am_sqlite3, am_db);
338
336
 
@@ -357,7 +355,8 @@ VALUE am_sqlite3_database_register_trace_tap(VALUE self, VALUE tap)
357
355
  }
358
356
 
359
357
  return Qnil;
360
- }
358
+ }
359
+
361
360
 
362
361
  /**
363
362
  * the amagliate trace function to be registered with register_trace_tap
@@ -369,7 +368,7 @@ void amalgalite_xProfile(void* tap, const char* msg, sqlite3_uint64 time)
369
368
  {
370
369
  VALUE trace_obj = (VALUE) tap;
371
370
 
372
- rb_funcall( trace_obj, rb_intern("profile"),
371
+ rb_funcall( trace_obj, rb_intern("profile"),
373
372
  2, rb_str_new2( msg ), SQLUINT64_2NUM(time) );
374
373
 
375
374
  return;
@@ -387,7 +386,6 @@ void amalgalite_xProfile(void* tap, const char* msg, sqlite3_uint64 time)
387
386
  VALUE am_sqlite3_database_register_profile_tap(VALUE self, VALUE tap)
388
387
  {
389
388
  am_sqlite3 *am_db;
390
- int rc;
391
389
 
392
390
  Data_Get_Struct(self, am_sqlite3, am_db);
393
391
 
@@ -410,11 +408,512 @@ VALUE am_sqlite3_database_register_profile_tap(VALUE self, VALUE tap)
410
408
  sqlite3_profile( am_db->db, amalgalite_xProfile, (void *)am_db->profile_obj );
411
409
  }
412
410
  return Qnil;
413
- }
411
+ }
412
+
413
+ /**
414
+ * invoke a ruby function. This is here to be used by rb_protect.
415
+ */
416
+ VALUE amalgalite_wrap_funcall2( VALUE arg )
417
+ {
418
+ am_protected_t *protected = (am_protected_t*) arg;
419
+ return rb_funcall2( protected->instance, protected->method, protected->argc, protected->argv );
420
+ }
421
+
422
+ /**
423
+ * Set the context result on the sqlite3_context based upon the ruby VALUE.
424
+ * This converts the ruby value to the appropriate C-type and makes the
425
+ * appropriate call sqlite3_result_* call
426
+ */
427
+ void amalgalite_set_context_result( sqlite3_context* context, VALUE result )
428
+ {
429
+ switch( TYPE(result) ) {
430
+ case T_FIXNUM:
431
+ case T_BIGNUM:
432
+ sqlite3_result_int64( context, NUM2SQLINT64(result) );
433
+ break;
434
+ case T_FLOAT:
435
+ sqlite3_result_double( context, NUM2DBL(result) );
436
+ break;
437
+ case T_NIL:
438
+ sqlite3_result_null( context );
439
+ break;
440
+ case T_TRUE:
441
+ sqlite3_result_int64( context, 1);
442
+ break;
443
+ case T_FALSE:
444
+ sqlite3_result_int64( context, 0);
445
+ break;
446
+ case T_STRING:
447
+ sqlite3_result_text( context, RSTRING(result)->ptr, RSTRING(result)->len, NULL);
448
+ break;
449
+ default:
450
+ sqlite3_result_error( context, "Unable to convert ruby object to an SQL function result", -1 );
451
+ sqlite3_result_error_code( context, 42 );
452
+ break;
453
+ }
454
+ return;
455
+ }
456
+
457
+ /**
458
+ * Convert from a protected sqlite3_value to a ruby object
459
+ */
460
+ VALUE sqlite3_value_to_ruby_value( sqlite3_value* s_value )
461
+ {
462
+ VALUE rb_value = Qnil;
463
+ sqlite3_int64 i64;
464
+
465
+ switch( sqlite3_value_type( s_value) ) {
466
+ case SQLITE_NULL:
467
+ rb_value = Qnil;
468
+ break;
469
+ case SQLITE_INTEGER:
470
+ i64 = sqlite3_value_int64( s_value);
471
+ rb_value = SQLINT64_2NUM(i64);
472
+ break;
473
+ case SQLITE_FLOAT:
474
+ rb_value = rb_float_new( sqlite3_value_double( s_value ) );
475
+ break;
476
+ case SQLITE_TEXT:
477
+ case SQLITE_BLOB:
478
+ rb_value = rb_str_new2((const char*) sqlite3_value_text( s_value ) );
479
+ break;
480
+ }
481
+ return rb_value;
482
+ }
483
+
484
+
485
+ /**
486
+ * the amalgalite xBusy handler that is used to invoke the ruby function for
487
+ * doing busy callbacks.
488
+ *
489
+ * This function conforms to the xBusy function specification for
490
+ * sqlite3_busy_handler.
491
+ */
492
+ int amalgalite_xBusy( void *pArg , int nArg)
493
+ {
494
+ VALUE *args = ALLOCA_N( VALUE, 1 );
495
+ VALUE result = Qnil;
496
+ int state;
497
+ int busy = 1;
498
+ am_protected_t protected;
499
+
500
+ args[0] = INT2FIX(nArg);
501
+
502
+ protected.instance = (VALUE)pArg;
503
+ protected.method = rb_intern("call");
504
+ protected.argc = 1;
505
+ protected.argv = args;
506
+
507
+ result = rb_protect( amalgalite_wrap_funcall2, (VALUE)&protected, &state );
508
+ if ( state || ( Qnil == result || Qfalse == result ) ){
509
+ busy = 0;
510
+ }
511
+ return busy;
512
+ }
513
+
514
+
515
+ /**
516
+ * call-seq:
517
+ * database.busy_handler( proc_like or nil )
518
+ *
519
+ * register a busy handler. If the argument is nil, then an existing busy
520
+ * handler is removed. Otherwise the argument is registered as the busy
521
+ * handler.
522
+ */
523
+ VALUE am_sqlite3_database_busy_handler( VALUE self, VALUE handler )
524
+ {
525
+ am_sqlite3 *am_db;
526
+ int rc;
527
+
528
+ Data_Get_Struct(self, am_sqlite3, am_db);
529
+
530
+ /* Removing a busy handler case, remove it from sqlite and then remove it
531
+ * from the garbage collector if it existed */
532
+ if ( Qnil == handler ) {
533
+ rc = sqlite3_busy_handler( am_db->db, NULL, NULL );
534
+ if ( SQLITE_OK != rc ) {
535
+ rb_raise(eAS_Error, "Failure removing busy handler : [SQLITE_ERROR %d] : %s\n",
536
+ rc, sqlite3_errmsg( am_db->db ));
537
+ }
538
+ if ( Qnil != am_db->busy_handler_obj ) {
539
+ rb_gc_unregister_address( &(am_db->busy_handler_obj) );
540
+ }
541
+ } else {
542
+ /* installing a busy handler
543
+ * - register it with sqlite
544
+ * - keep a reference for ourselves with our database handle
545
+ * - registere the handler reference with the garbage collector
546
+ */
547
+ rc = sqlite3_busy_handler( am_db->db, amalgalite_xBusy, (void*)handler );
548
+ if ( SQLITE_OK != rc ) {
549
+ rb_raise(eAS_Error, "Failure setting busy handler : [SQLITE_ERROR %d] : %s\n",
550
+ rc, sqlite3_errmsg( am_db->db ));
551
+ }
552
+ am_db->busy_handler_obj = handler;
553
+ rb_gc_register_address( &(am_db->busy_handler_obj) );
554
+ }
555
+ return Qnil;
556
+ }
557
+
558
+
559
+ /**
560
+ * the amalgalite xProgress handler that is used to invoke the ruby function for
561
+ * doing progress handler callbacks.
562
+ *
563
+ * This function conforms to the xProgress function specification for
564
+ * sqlite3_progress_handler.
565
+ */
566
+ int amalgalite_xProgress( void *pArg )
567
+ {
568
+ VALUE result = Qnil;
569
+ int state;
570
+ int cancel = 0;
571
+ am_protected_t protected;
572
+
573
+ protected.instance = (VALUE)pArg;
574
+ protected.method = rb_intern("call");
575
+ protected.argc = 0;
576
+ protected.argv = NULL;
577
+
578
+ result = rb_protect( amalgalite_wrap_funcall2, (VALUE)&protected, &state );
579
+ if ( state || ( Qnil == result || Qfalse == result ) ){
580
+ cancel = 1;
581
+ }
582
+ return cancel;
583
+ }
584
+
585
+
586
+ /**
587
+ * call-seq:
588
+ * database.progress_handler( op_count, proc_like or nil )
589
+ *
590
+ * register a progress handler. If the argument is nil, then an existing
591
+ * progress handler is removed. Otherwise the argument is registered as the
592
+ * progress handler.
593
+ */
594
+ VALUE am_sqlite3_database_progress_handler( VALUE self, VALUE op_count, VALUE handler )
595
+ {
596
+ am_sqlite3 *am_db;
597
+ int op_codes = FIX2INT( op_count );
598
+
599
+ Data_Get_Struct(self, am_sqlite3, am_db);
600
+
601
+ /* Removing a progress handler, remove it from sqlite and then remove it
602
+ * from the garbage collector if it existed */
603
+ if ( Qnil == handler ) {
604
+ sqlite3_progress_handler( am_db->db, 0, NULL, (void*)NULL );
605
+ if ( Qnil != am_db->progress_handler_obj ) {
606
+ rb_gc_unregister_address( &(am_db->progress_handler_obj) );
607
+ }
608
+ } else {
609
+ /* installing a progress handler
610
+ * - register it with sqlite
611
+ * - keep a reference for ourselves with our database handle
612
+ * - register the handler reference with the garbage collector
613
+ */
614
+ sqlite3_progress_handler( am_db->db, op_codes, amalgalite_xProgress, (void*)handler );
615
+ am_db->progress_handler_obj = handler;
616
+ rb_gc_register_address( &(am_db->progress_handler_obj) );
617
+ }
618
+ return Qnil;
619
+ }
620
+
621
+
622
+ /**
623
+ * the amalgalite xFunc callback that is used to invoke the ruby function for
624
+ * doing scalar SQL functions.
625
+ *
626
+ * This function conforms to the xFunc function specification for
627
+ * sqlite3_create_function
628
+ */
629
+ void amalgalite_xFunc( sqlite3_context* context, int argc, sqlite3_value** argv )
630
+ {
631
+ VALUE *args = ALLOCA_N( VALUE, argc );
632
+ VALUE result;
633
+ int state;
634
+ int i;
635
+ am_protected_t protected;
636
+
637
+ /* convert each item in argv to a VALUE object based upon its type via
638
+ * sqlite3_value_type( argv[n] )
639
+ */
640
+ for( i = 0 ; i < argc ; i++) {
641
+ args[i] = sqlite3_value_to_ruby_value( argv[i] );
642
+ }
643
+
644
+ /* gather all the data to make the protected call */
645
+ protected.instance = (VALUE) sqlite3_user_data( context );
646
+ protected.method = rb_intern("call");
647
+ protected.argc = argc;
648
+ protected.argv = args;
649
+
650
+ result = rb_protect( amalgalite_wrap_funcall2, (VALUE)&protected, &state );
651
+ /* check the results */
652
+ if ( state ) {
653
+ VALUE msg = ERROR_INFO_MESSAGE();
654
+ sqlite3_result_error( context, RSTRING(msg)->ptr, RSTRING(msg)->len );
655
+ } else {
656
+ amalgalite_set_context_result( context, result );
657
+ }
658
+
659
+ return;
660
+ }
661
+
662
+ /**
663
+ * call-seq:
664
+ * database.define_function( name, proc_like )
665
+ *
666
+ * register the given function to be invoked as an sql function.
667
+ */
668
+ VALUE am_sqlite3_database_define_function( VALUE self, VALUE name, VALUE proc_like )
669
+ {
670
+ am_sqlite3 *am_db;
671
+ int rc;
672
+ VALUE arity = rb_funcall( proc_like, rb_intern( "arity" ), 0 );
673
+ char* zFunctionName = RSTRING(name)->ptr;
674
+ int nArg = FIX2INT( arity );
675
+
676
+ Data_Get_Struct(self, am_sqlite3, am_db);
677
+ rc = sqlite3_create_function( am_db->db,
678
+ zFunctionName, nArg,
679
+ SQLITE_ANY,
680
+ (void *)proc_like, amalgalite_xFunc,
681
+ NULL, NULL);
682
+ if ( SQLITE_OK != rc ) {
683
+ rb_raise(eAS_Error, "Failure defining SQL function '%s' with arity '%d' : [SQLITE_ERROR %d] : %s\n",
684
+ zFunctionName, nArg, rc, sqlite3_errmsg( am_db->db ));
685
+ }
686
+ rb_gc_register_address( &proc_like );
687
+ return Qnil;
688
+ }
689
+
690
+ /**
691
+ * call-seq:
692
+ * database.remove_function( name, proc_like )
693
+ *
694
+ * remove the given function from availability in SQL.
695
+ */
696
+ VALUE am_sqlite3_database_remove_function( VALUE self, VALUE name, VALUE proc_like )
697
+ {
698
+ am_sqlite3 *am_db;
699
+ int rc;
700
+ VALUE arity = rb_funcall( proc_like, rb_intern( "arity" ), 0 );
701
+ char* zFunctionName = RSTRING(name)->ptr;
702
+ int nArg = FIX2INT( arity );
703
+
704
+ Data_Get_Struct(self, am_sqlite3, am_db);
705
+ rc = sqlite3_create_function( am_db->db,
706
+ zFunctionName, nArg,
707
+ SQLITE_ANY,
708
+ NULL, NULL,
709
+ NULL, NULL);
710
+ if ( SQLITE_OK != rc ) {
711
+ rb_raise(eAS_Error, "Failure removing SQL function '%s' with arity '%d' : [SQLITE_ERROR %d] : %s\n",
712
+ zFunctionName, nArg, rc, sqlite3_errmsg( am_db->db ));
713
+ }
714
+ rb_gc_unregister_address( &proc_like );
715
+ return Qnil;
716
+ }
717
+
718
+ /* wrap rb_class_new_instance so it can be called from within an rb_protect */
719
+ VALUE amalgalite_wrap_new_aggregate( VALUE arg )
720
+ {
721
+ return rb_class_new_instance( 0, 0, arg );
722
+ }
723
+
724
+
725
+ /**
726
+ * the amalgalite xStep callback that is used to invoke the ruby method for
727
+ * doing aggregate step oprations as part of an aggregate SQL function.
728
+ *
729
+ * This function conforms to the xStep function specification for
730
+ * sqlite3_create_function.
731
+ */
732
+ void amalgalite_xStep( sqlite3_context* context, int argc, sqlite3_value** argv )
733
+ {
734
+ VALUE *args = ALLOCA_N( VALUE, argc );
735
+ VALUE result;
736
+ int state;
737
+ int i;
738
+ am_protected_t protected;
739
+ VALUE *aggregate_context = (VALUE*)sqlite3_aggregate_context( context, sizeof( VALUE ) );
740
+
741
+ if ( 0 == aggregate_context ) {
742
+ sqlite3_result_error_nomem( context );
743
+ return;
744
+ }
745
+
746
+ /* instantiate an instance of the aggregate function class if the
747
+ * aggregate context is zero'd out .
748
+ *
749
+ * If there is an error in initialization of the aggregate, set the error
750
+ * context
751
+ */
752
+ if ( *aggregate_context == T_NONE ) {
753
+ VALUE klass = (VALUE) sqlite3_user_data( context );
754
+ result = rb_protect( amalgalite_wrap_new_aggregate, klass, &state );
755
+ *aggregate_context = result;
756
+ /* mark the instance as protected from collection */
757
+ rb_gc_register_address( aggregate_context );
758
+ if ( state ) {
759
+ VALUE msg = ERROR_INFO_MESSAGE();
760
+ sqlite3_result_error( context, RSTRING(msg)->ptr, RSTRING(msg)->len);
761
+ rb_iv_set( *aggregate_context, "@_exception", rb_gv_get("$!" ));
762
+ return;
763
+ } else {
764
+ rb_iv_set( *aggregate_context, "@_exception", Qnil );
765
+ }
766
+ }
767
+
768
+ /* convert each item in argv to a VALUE object based upon its type via
769
+ * sqlite3_value_type( argv[n] )
770
+ */
771
+ for( i = 0 ; i < argc ; i++) {
772
+ args[i] = sqlite3_value_to_ruby_value( argv[i] );
773
+ }
774
+
775
+ /* gather all the data to make the protected call */
776
+ protected.instance = *aggregate_context;
777
+ protected.method = rb_intern("step");
778
+ protected.argc = argc;
779
+ protected.argv = args;
780
+
781
+ result = rb_protect( amalgalite_wrap_funcall2, (VALUE)&protected, &state );
782
+
783
+ /* check the results, if there is an error, set the @exception ivar */
784
+ if ( state ) {
785
+ VALUE msg = ERROR_INFO_MESSAGE();
786
+ sqlite3_result_error( context, RSTRING(msg)->ptr, RSTRING(msg)->len);
787
+ rb_iv_set( *aggregate_context, "@_exception", rb_gv_get("$!" ));
788
+ }
789
+
790
+ return ;
791
+ }
792
+
793
+
794
+ /**
795
+ * the amalgalite xFinal callback that is used to invoke the ruby method for
796
+ * doing aggregate final operations as part of an aggregate SQL function.
797
+ *
798
+ * This function conforms to the xFinal function specification for
799
+ * sqlite3_create_function.
800
+ */
801
+ void amalgalite_xFinal( sqlite3_context* context )
802
+ {
803
+ VALUE result;
804
+ int state;
805
+ am_protected_t protected;
806
+ VALUE *aggregate_context = (VALUE*)sqlite3_aggregate_context( context, sizeof( VALUE ) );
807
+ VALUE exception = rb_iv_get( *aggregate_context, "@_exception" );
808
+
809
+ if ( Qnil == exception ) {
810
+ /* gather all the data to make the protected call */
811
+ protected.instance = *aggregate_context;
812
+ protected.method = rb_intern("finalize");
813
+ protected.argc = 0;
814
+ protected.argv = NULL;
815
+
816
+ result = rb_protect( amalgalite_wrap_funcall2, (VALUE)&protected, &state );
817
+
818
+ /* check the results */
819
+ if ( state ) {
820
+ VALUE msg = ERROR_INFO_MESSAGE();
821
+ sqlite3_result_error( context, RSTRING(msg)->ptr, RSTRING(msg)->len );
822
+ } else {
823
+ amalgalite_set_context_result( context, result );
824
+ }
825
+ } else {
826
+ VALUE msg = rb_obj_as_string( exception );
827
+ sqlite3_result_error( context, RSTRING(msg)->ptr, RSTRING(msg)->len );
828
+ }
829
+
830
+
831
+
832
+ /* release the aggregate instance from garbage collector protection */
833
+ rb_gc_unregister_address( aggregate_context );
834
+
835
+ return ;
836
+ }
837
+
838
+
414
839
 
415
840
  /**
416
841
  * call-seq:
417
- * datatabase.table_column_metadata( db_name, table_name, column_name) -> Hash
842
+ * database.define_aggregate( name, arity, klass )
843
+ *
844
+ * register the given klass to be invoked as an sql aggregate.
845
+ */
846
+ VALUE am_sqlite3_database_define_aggregate( VALUE self, VALUE name, VALUE arity, VALUE klass )
847
+ {
848
+ am_sqlite3 *am_db;
849
+ int rc;
850
+ char* zFunctionName = RSTRING(name)->ptr;
851
+ int nArg = FIX2INT( arity );
852
+
853
+ Data_Get_Struct(self, am_sqlite3, am_db);
854
+ rc = sqlite3_create_function( am_db->db,
855
+ zFunctionName, nArg,
856
+ SQLITE_ANY,
857
+ (void *)klass, NULL,
858
+ amalgalite_xStep,
859
+ amalgalite_xFinal);
860
+ if ( SQLITE_OK != rc ) {
861
+ rb_raise(eAS_Error, "Failure defining SQL aggregate '%s' with arity '%d' : [SQLITE_ERROR %d] : %s\n",
862
+ zFunctionName, nArg, rc, sqlite3_errmsg( am_db->db ));
863
+ }
864
+ rb_gc_register_address( &klass );
865
+ return Qnil;
866
+ }
867
+
868
+
869
+ /**
870
+ * call-seq:
871
+ * database.remove_aggregate( name, arity, klass )
872
+ *
873
+ * remove the given klass from availability in SQL as an aggregate.
874
+ */
875
+ VALUE am_sqlite3_database_remove_aggregate( VALUE self, VALUE name, VALUE arity, VALUE klass )
876
+ {
877
+ am_sqlite3 *am_db;
878
+ int rc;
879
+ char* zFunctionName = RSTRING(name)->ptr;
880
+ int nArg = FIX2INT( arity );
881
+
882
+ Data_Get_Struct(self, am_sqlite3, am_db);
883
+ rc = sqlite3_create_function( am_db->db,
884
+ zFunctionName, nArg,
885
+ SQLITE_ANY,
886
+ NULL, NULL,
887
+ NULL,
888
+ NULL);
889
+ if ( SQLITE_OK != rc ) {
890
+ rb_raise(eAS_Error, "Failure removing SQL aggregate '%s' with arity '%d' : [SQLITE_ERROR %d] : %s\n",
891
+ zFunctionName, nArg, rc, sqlite3_errmsg( am_db->db ));
892
+ }
893
+ rb_gc_unregister_address( &klass );
894
+ return Qnil;
895
+ }
896
+
897
+
898
+ /**
899
+ * call-seq:
900
+ * database.interrupt!
901
+ *
902
+ * Cause another thread with a handle on this database to be interrupted and
903
+ * return at the earliest opportunity as interrupted.
904
+ */
905
+ VALUE am_sqlite3_database_interrupt_bang( VALUE self )
906
+ {
907
+ am_sqlite3 *am_db;
908
+
909
+ Data_Get_Struct(self, am_sqlite3, am_db);
910
+ sqlite3_interrupt( am_db->db );
911
+ return Qnil;
912
+ }
913
+
914
+ /**
915
+ * call-seq:
916
+ * database.table_column_metadata( db_name, table_name, column_name) -> Hash
418
917
  *
419
918
  * Returns a hash containing the meta information about the column. The
420
919
  * available keys are:
@@ -434,7 +933,7 @@ VALUE am_sqlite3_database_table_column_metadata(VALUE self, VALUE db_name, VALUE
434
933
  /* input */
435
934
  const char *zDbName = StringValuePtr( db_name );
436
935
  const char *zTableName = StringValuePtr( tbl_name );
437
- const char *zColumnName = StringValuePtr( col_name );
936
+ const char *zColumnName = StringValuePtr( col_name );
438
937
 
439
938
  /* output */
440
939
  const char *pzDataType = NULL;
@@ -452,9 +951,9 @@ VALUE am_sqlite3_database_table_column_metadata(VALUE self, VALUE db_name, VALUE
452
951
  if ( SQLITE_OK != rc ) {
453
952
  rb_raise(eAS_Error, "Failure retrieveing column meta data for table '%s' column '%s' : [SQLITE_ERROR %d] : %s\n",
454
953
  zTableName, zColumnName, rc, sqlite3_errmsg( am_db-> db ));
455
-
954
+
456
955
  }
457
-
956
+
458
957
  rStr = ( NULL == pzDataType) ? Qnil : rb_str_new2( pzDataType );
459
958
  rb_hash_aset( rHash, rb_str_new2("declared_data_type"), rStr );
460
959
 
@@ -489,6 +988,18 @@ void am_sqlite3_database_free(am_sqlite3* am_db)
489
988
  am_db->profile_obj = Qnil;
490
989
  }
491
990
 
991
+ if ( Qnil != am_db->busy_handler_obj ) {
992
+ rb_gc_unregister_address( &(am_db->busy_handler_obj) );
993
+ am_db->busy_handler_obj = Qnil;
994
+ }
995
+
996
+ if ( Qnil != am_db->progress_handler_obj ) {
997
+ rb_gc_unregister_address( &(am_db->progress_handler_obj) );
998
+ am_db->progress_handler_obj = Qnil;
999
+ }
1000
+
1001
+
1002
+
492
1003
  free(am_db);
493
1004
  return;
494
1005
  }
@@ -501,8 +1012,10 @@ VALUE am_sqlite3_database_alloc(VALUE klass)
501
1012
  am_sqlite3* am_db = ALLOC(am_sqlite3);
502
1013
  VALUE obj ;
503
1014
 
504
- am_db->trace_obj = Qnil;
505
- am_db->profile_obj = Qnil;
1015
+ am_db->trace_obj = Qnil;
1016
+ am_db->profile_obj = Qnil;
1017
+ am_db->busy_handler_obj = Qnil;
1018
+ am_db->progress_handler_obj = Qnil;
506
1019
 
507
1020
  obj = Data_Wrap_Struct(klass, NULL, am_sqlite3_database_free, am_db);
508
1021
  return obj;
@@ -516,19 +1029,19 @@ VALUE am_sqlite3_database_alloc(VALUE klass)
516
1029
  */
517
1030
  void Init_amalgalite3_database( )
518
1031
  {
519
-
1032
+
520
1033
  VALUE ma = rb_define_module("Amalgalite");
521
1034
  VALUE mas = rb_define_module_under(ma, "SQLite3");
522
-
523
- /*
1035
+
1036
+ /*
524
1037
  * Encapsulate an SQLite3 database
525
1038
  */
526
- cAS_Database = rb_define_class_under( mas, "Database", rb_cObject);
1039
+ cAS_Database = rb_define_class_under( mas, "Database", rb_cObject);
527
1040
 
528
- rb_define_alloc_func(cAS_Database, am_sqlite3_database_alloc);
529
- rb_define_singleton_method(cAS_Database, "open", am_sqlite3_database_open, -1);
530
- rb_define_singleton_method(cAS_Database, "open16", am_sqlite3_database_open16, 1);
531
- rb_define_method(cAS_Database, "prepare", am_sqlite3_database_prepare, 1);
1041
+ rb_define_alloc_func(cAS_Database, am_sqlite3_database_alloc);
1042
+ rb_define_singleton_method(cAS_Database, "open", am_sqlite3_database_open, -1);
1043
+ rb_define_singleton_method(cAS_Database, "open16", am_sqlite3_database_open16, 1);
1044
+ rb_define_method(cAS_Database, "prepare", am_sqlite3_database_prepare, 1);
532
1045
  rb_define_method(cAS_Database, "close", am_sqlite3_database_close, 0); /* in amalgalite3_database.c */
533
1046
  rb_define_method(cAS_Database, "last_insert_rowid", am_sqlite3_database_last_insert_rowid, 0); /* in amalgalite3_database.c */
534
1047
  rb_define_method(cAS_Database, "autocommit?", am_sqlite3_database_is_autocommit, 0); /* in amalgalite3_database.c */
@@ -539,6 +1052,14 @@ void Init_amalgalite3_database( )
539
1052
  rb_define_method(cAS_Database, "total_changes", am_sqlite3_database_total_changes, 0); /* in amalgalite3_database.c */
540
1053
  rb_define_method(cAS_Database, "last_error_code", am_sqlite3_database_last_error_code, 0); /* in amalgalite3_database.c */
541
1054
  rb_define_method(cAS_Database, "last_error_message", am_sqlite3_database_last_error_message, 0); /* in amalgalite3_database.c */
1055
+ rb_define_method(cAS_Database, "define_function", am_sqlite3_database_define_function, 2); /* in amalgalite3_database.c */
1056
+ rb_define_method(cAS_Database, "remove_function", am_sqlite3_database_remove_function, 2); /* in amalgalite3_database.c */
1057
+ rb_define_method(cAS_Database, "define_aggregate", am_sqlite3_database_define_aggregate, 3); /* in amalgalite3_database.c */
1058
+ rb_define_method(cAS_Database, "remove_aggregate", am_sqlite3_database_remove_aggregate, 3); /* in amalgalite3_database.c */
1059
+ rb_define_method(cAS_Database, "busy_handler", am_sqlite3_database_busy_handler, 1); /* in amalgalite3_database.c */
1060
+ rb_define_method(cAS_Database, "progress_handler", am_sqlite3_database_progress_handler, 2); /* in amalgalite3_database.c */
1061
+ rb_define_method(cAS_Database, "interrupt!", am_sqlite3_database_interrupt_bang, 0); /* in amalgalite3_database.c */
1062
+
542
1063
 
543
1064
  /*
544
1065
  * Ecapuslate a SQLite3 Database stat