amalgalite 0.5.1-x86-mswin32-60 → 0.6.0-x86-mswin32-60

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.
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
data/examples/gems.db ADDED
Binary file
data/ext/amalgalite3.h CHANGED
@@ -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