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 +15 -2
- data/README +3 -0
- data/examples/define_aggregate.rb +75 -0
- data/examples/define_function.rb +104 -0
- data/examples/gems.db +0 -0
- data/ext/amalgalite3.h +28 -2
- data/ext/amalgalite3_blob.c +1 -2
- data/ext/amalgalite3_database.c +567 -46
- data/ext/amalgalite3_requires_bootstrap.c +1 -3
- data/ext/amalgalite3_statement.c +2 -2
- data/ext/extconf.rb +1 -0
- data/ext/sqlite3.c +6521 -4518
- data/ext/sqlite3.h +50 -43
- data/ext/test_error.c +77 -0
- data/lib/amalgalite.rb +5 -0
- data/lib/amalgalite/aggregate.rb +67 -0
- data/lib/amalgalite/busy_timeout.rb +47 -0
- data/lib/amalgalite/column.rb +1 -1
- data/lib/amalgalite/database.rb +271 -2
- data/lib/amalgalite/function.rb +61 -0
- data/lib/amalgalite/progress_handler.rb +21 -0
- data/lib/amalgalite/sqlite3.rb +1 -0
- data/lib/amalgalite/sqlite3/database/function.rb +48 -0
- data/lib/amalgalite/version.rb +2 -2
- data/lib/amalgalite3.so +0 -0
- data/spec/aggregate_spec.rb +168 -0
- data/spec/busy_handler.rb +164 -0
- data/spec/database_spec.rb +97 -3
- data/spec/function_spec.rb +86 -0
- data/spec/progress_handler_spec.rb +103 -0
- data/spec/spec_helper.rb +1 -0
- data/spec/sqlite3/version_spec.rb +2 -2
- metadata +21 -3
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
|
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
|
data/ext/amalgalite3_blob.c
CHANGED
@@ -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
|
|
data/ext/amalgalite3_database.c
CHANGED
@@ -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
|
10
|
-
VALUE cAS_Database_Stat; /* class
|
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
|
-
*
|
16
|
+
* Amalgalite::SQLite3::Database.open( filename, flags = READWRITE | CREATE ) -> Database
|
17
17
|
*
|
18
|
-
* Create a new
|
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
|
-
*
|
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
|
505
|
-
am_db->profile_obj
|
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
|