amalgalite 0.5.1-x86-mswin32-60 → 0.6.0-x86-mswin32-60
Sign up to get free protection for your applications and to get access to all the features.
- 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
|