sqlite3-ruby 1.2.5-x86-mswin32 → 1.3.0.beta.1-x86-mswin32
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/API_CHANGES.rdoc +48 -0
- data/{History.txt → CHANGELOG.rdoc} +24 -0
- data/Manifest.txt +14 -14
- data/{README.txt → README.rdoc} +1 -6
- data/Rakefile +8 -3
- data/ext/sqlite3/database.c +687 -0
- data/ext/sqlite3/database.h +15 -0
- data/ext/sqlite3/exception.c +94 -0
- data/ext/sqlite3/exception.h +8 -0
- data/ext/sqlite3/extconf.rb +26 -0
- data/ext/sqlite3/sqlite3.c +33 -0
- data/ext/sqlite3/sqlite3_ruby.h +43 -0
- data/ext/sqlite3/statement.c +412 -0
- data/ext/sqlite3/statement.h +16 -0
- data/lib/sqlite3.rb +9 -0
- data/lib/sqlite3/1.8/sqlite3_native.so +0 -0
- data/lib/sqlite3/1.9/sqlite3_native.so +0 -0
- data/lib/sqlite3/database.rb +94 -302
- data/lib/sqlite3/errors.rb +0 -24
- data/lib/sqlite3/pragmas.rb +16 -7
- data/lib/sqlite3/resultset.rb +25 -81
- data/lib/sqlite3/statement.rb +22 -107
- data/lib/sqlite3/version.rb +4 -4
- data/setup.rb +2 -2
- data/tasks/native.rake +13 -17
- data/tasks/vendor_sqlite3.rake +10 -7
- data/test/helper.rb +1 -65
- data/test/test_database.rb +239 -189
- data/test/test_encoding.rb +115 -0
- data/test/test_integration.rb +38 -35
- data/test/test_integration_open_close.rb +1 -1
- data/test/test_integration_pending.rb +6 -4
- data/test/test_integration_resultset.rb +20 -8
- data/test/test_integration_statement.rb +1 -2
- data/test/test_sqlite3.rb +9 -0
- data/test/test_statement.rb +193 -0
- metadata +84 -49
- data/ext/sqlite3_api/extconf.rb +0 -10
- data/ext/sqlite3_api/sqlite3_api.i +0 -362
- data/ext/sqlite3_api/sqlite3_api_wrap.c +0 -5018
- data/lib/1.8/sqlite3_api.so +0 -0
- data/lib/1.9/sqlite3_api.so +0 -0
- data/lib/sqlite3/driver/dl/api.rb +0 -152
- data/lib/sqlite3/driver/dl/driver.rb +0 -307
- data/lib/sqlite3/driver/native/driver.rb +0 -219
- data/tasks/benchmark.rake +0 -9
- data/tasks/gem.rake +0 -32
- data/test/bm.rb +0 -140
- data/test/driver/dl/tc_driver.rb +0 -292
- data/test/native-vs-dl.rb +0 -126
- data/test/test_errors.rb +0 -17
data/API_CHANGES.rdoc
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
= API Changes
|
2
|
+
|
3
|
+
* SQLite3::ResultSet used to query the database for the first row, regardless
|
4
|
+
of whether the user asked for it or not. I have removed that so that rows
|
5
|
+
will not be returned until the user asks for them. This is a subtle but
|
6
|
+
sometimes important change in behavior.
|
7
|
+
|
8
|
+
83882d2208ed189361617d5ab8532a325aaf729d
|
9
|
+
|
10
|
+
* SQLite3::Database#trace now takes either a block or an object that responds
|
11
|
+
to "call". The previous implementation passed around a VALUE that was cast
|
12
|
+
to a void *. This is dangerous because the value could get garbage collected
|
13
|
+
before the proc was called. If the user wants data passed around with the
|
14
|
+
block, they should use variables available to the closure or create an
|
15
|
+
object.
|
16
|
+
|
17
|
+
* SQLite3::Statement#step automatically converts to ruby types, where before
|
18
|
+
all values were automatically yielded as strings. This will only be a
|
19
|
+
problem for people who were accessing information about the database that
|
20
|
+
wasn't previously passed through the pure ruby conversion code.
|
21
|
+
|
22
|
+
* SQLite3::Database#errmsg no longer takes a parameter to return error
|
23
|
+
messages as UTF-16. Do people even use that? I opt for staying UTF-8 when
|
24
|
+
possible. See test_integration.rb test_errmsg_utf16
|
25
|
+
|
26
|
+
* SQLite3::Database#authorize same changes as trace
|
27
|
+
|
28
|
+
* test/test_tc_database.rb was removed because we no longer use the Driver
|
29
|
+
design pattern.
|
30
|
+
|
31
|
+
= Garbage Collection Strategy
|
32
|
+
|
33
|
+
All statements keep pointers back to their respective database connections.
|
34
|
+
The @connection instance variable on the Statement handle keeps the database
|
35
|
+
connection alive. Memory allocated for a statement handler will be freed in
|
36
|
+
two cases:
|
37
|
+
|
38
|
+
* close is called on the statement
|
39
|
+
* The SQLite3::Database object gets garbage collected
|
40
|
+
|
41
|
+
We can't free the memory for the statement in the garbage collection function
|
42
|
+
for the statement handler. The reason is because there exists a race
|
43
|
+
condition. We cannot guarantee the order in which objects will be garbage
|
44
|
+
collected. So, it is possible that a connection and a statement are up for
|
45
|
+
garbage collection. If the database connection were to be free'd before the
|
46
|
+
statement, then boom. Instead we'll be conservative and free unclosed
|
47
|
+
statements when the connection is terminated.
|
48
|
+
|
@@ -1,3 +1,26 @@
|
|
1
|
+
=== 1.3.0.beta.1 / 2010-05-10
|
2
|
+
|
3
|
+
* Enhancements
|
4
|
+
* Complete rewrite of C-based adapter from SWIG to hand-crafted one [tenderlove]
|
5
|
+
See API_CHANGES document for details.
|
6
|
+
This closes: Bug #27300, Bug #27241, Patch #16020
|
7
|
+
* Improved UTF, Unicode, M17N, all that handling and proper BLOB handling [tenderlove, nurse]
|
8
|
+
|
9
|
+
* Experimental
|
10
|
+
* Added API to access and load extensions. [kashif]
|
11
|
+
These functions maps directly into SQLite3 own enable_load_extension()
|
12
|
+
and load_extension() C-API functions. See SQLite3::Database API documentation for details.
|
13
|
+
This closes: Patches #9178
|
14
|
+
|
15
|
+
* Bugfixes
|
16
|
+
* Corrected gem dependencies (runtime and development)
|
17
|
+
* Fixed threaded tests [Alexey Borzenkov]
|
18
|
+
* Removed GitHub gemspec
|
19
|
+
* Fixed "No definition for" warnings from RDoc
|
20
|
+
* Generate zip and tgz files for releases
|
21
|
+
* Added Luis Lavena as gem Author (maintainer)
|
22
|
+
* Prevent mkmf interfere with Mighty Snow Leopard
|
23
|
+
|
1
24
|
=== 1.2.5 / 25 Jul 2009
|
2
25
|
|
3
26
|
* Check for illegal nil before executing SQL [Erik Veenstra]
|
@@ -6,6 +29,7 @@
|
|
6
29
|
* Build gem binaries for Windows.
|
7
30
|
* Improved Ruby 1.9 support compatibility.
|
8
31
|
* Taint returned values. Patch #20325.
|
32
|
+
* Database.open and Database.new now take an optional block [Gerrit Kaiser]
|
9
33
|
|
10
34
|
|
11
35
|
=== 1.2.4.1 (internal) / 5 Jul 2009
|
data/Manifest.txt
CHANGED
@@ -1,20 +1,24 @@
|
|
1
|
+
API_CHANGES.rdoc
|
2
|
+
CHANGELOG.rdoc
|
1
3
|
ChangeLog.cvs
|
2
|
-
History.txt
|
3
4
|
LICENSE
|
4
5
|
Manifest.txt
|
5
|
-
README.
|
6
|
+
README.rdoc
|
6
7
|
Rakefile
|
7
|
-
ext/
|
8
|
-
ext/
|
9
|
-
ext/
|
8
|
+
ext/sqlite3/database.c
|
9
|
+
ext/sqlite3/database.h
|
10
|
+
ext/sqlite3/exception.c
|
11
|
+
ext/sqlite3/exception.h
|
12
|
+
ext/sqlite3/extconf.rb
|
13
|
+
ext/sqlite3/sqlite3.c
|
14
|
+
ext/sqlite3/sqlite3_ruby.h
|
15
|
+
ext/sqlite3/statement.c
|
16
|
+
ext/sqlite3/statement.h
|
10
17
|
faq/faq.rb
|
11
18
|
faq/faq.yml
|
12
19
|
lib/sqlite3.rb
|
13
20
|
lib/sqlite3/constants.rb
|
14
21
|
lib/sqlite3/database.rb
|
15
|
-
lib/sqlite3/driver/dl/api.rb
|
16
|
-
lib/sqlite3/driver/dl/driver.rb
|
17
|
-
lib/sqlite3/driver/native/driver.rb
|
18
22
|
lib/sqlite3/errors.rb
|
19
23
|
lib/sqlite3/pragmas.rb
|
20
24
|
lib/sqlite3/resultset.rb
|
@@ -23,19 +27,15 @@ lib/sqlite3/translator.rb
|
|
23
27
|
lib/sqlite3/value.rb
|
24
28
|
lib/sqlite3/version.rb
|
25
29
|
setup.rb
|
26
|
-
tasks/benchmark.rake
|
27
30
|
tasks/faq.rake
|
28
|
-
tasks/gem.rake
|
29
31
|
tasks/native.rake
|
30
32
|
tasks/vendor_sqlite3.rake
|
31
|
-
test/bm.rb
|
32
|
-
test/driver/dl/tc_driver.rb
|
33
33
|
test/helper.rb
|
34
|
-
test/native-vs-dl.rb
|
35
34
|
test/test_database.rb
|
36
|
-
test/test_errors.rb
|
37
35
|
test/test_integration.rb
|
38
36
|
test/test_integration_open_close.rb
|
39
37
|
test/test_integration_pending.rb
|
40
38
|
test/test_integration_resultset.rb
|
41
39
|
test/test_integration_statement.rb
|
40
|
+
test/test_sqlite3.rb
|
41
|
+
test/test_statement.rb
|
data/{README.txt → README.rdoc}
RENAMED
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
* http://sqlite3-ruby.rubyforge.org
|
4
4
|
* http://rubyforge.org/projects/sqlite3-ruby
|
5
|
-
* http://github.com/
|
5
|
+
* http://github.com/luislavena/sqlite3-ruby
|
6
6
|
|
7
7
|
== DESCRIPTION
|
8
8
|
|
@@ -30,11 +30,6 @@ If you have sqlite3 installed in a non-standard location, you can specify the lo
|
|
30
30
|
gem install sqlite3-ruby -- --with-sqlite3-include=/opt/local/include \
|
31
31
|
--with-sqlite3-lib=/opt/local/lib
|
32
32
|
|
33
|
-
Also, the gem ships with the C source-code pre-built, so (as of version 1.1.1)
|
34
|
-
you no longer need to have SWIG installed. However, if you have SWIG installed
|
35
|
-
and you want to generate the C file yourself, you can specify the
|
36
|
-
<code>--with-swig</code> option.
|
37
|
-
|
38
33
|
== Usage
|
39
34
|
|
40
35
|
For help figuring out the SQLite3/Ruby interface, check out the
|
data/Rakefile
CHANGED
@@ -1,5 +1,10 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
#
|
2
|
+
# NOTE: Keep this file clean.
|
3
|
+
# Add your customizations inside tasks directory.
|
4
|
+
# Thank You.
|
5
|
+
#
|
3
6
|
|
4
7
|
# load rakefile extensions (tasks)
|
5
|
-
Dir['tasks/*.rake'].each { |f|
|
8
|
+
Dir['tasks/*.rake'].sort.each { |f| load f }
|
9
|
+
|
10
|
+
# vim: syntax=ruby
|
@@ -0,0 +1,687 @@
|
|
1
|
+
#include <sqlite3_ruby.h>
|
2
|
+
|
3
|
+
#define REQUIRE_OPEN_DB(_ctxt) \
|
4
|
+
if(!_ctxt->db) \
|
5
|
+
rb_raise(rb_path2class("SQLite3::Exception"), "cannot use a closed database");
|
6
|
+
|
7
|
+
VALUE cSqlite3Database;
|
8
|
+
static VALUE sym_utf16, sym_results_as_hash, sym_type_translation;
|
9
|
+
|
10
|
+
static void deallocate(void * ctx)
|
11
|
+
{
|
12
|
+
sqlite3RubyPtr c = (sqlite3RubyPtr)ctx;
|
13
|
+
sqlite3 * db = c->db;
|
14
|
+
sqlite3_stmt * stmt;
|
15
|
+
|
16
|
+
if(db) {
|
17
|
+
while((stmt = sqlite3_next_stmt(db, NULL)) != NULL) {
|
18
|
+
sqlite3_finalize(stmt);
|
19
|
+
}
|
20
|
+
sqlite3_close(db);
|
21
|
+
}
|
22
|
+
xfree(c);
|
23
|
+
}
|
24
|
+
|
25
|
+
static VALUE allocate(VALUE klass)
|
26
|
+
{
|
27
|
+
sqlite3RubyPtr ctx = xcalloc((size_t)1, sizeof(sqlite3Ruby));
|
28
|
+
return Data_Wrap_Struct(klass, NULL, deallocate, ctx);
|
29
|
+
}
|
30
|
+
|
31
|
+
static char *
|
32
|
+
utf16_string_value_ptr(VALUE str)
|
33
|
+
{
|
34
|
+
StringValue(str);
|
35
|
+
rb_str_buf_cat(str, "\x00", 1L);
|
36
|
+
return RSTRING_PTR(str);
|
37
|
+
}
|
38
|
+
|
39
|
+
/* call-seq: SQLite3::Database.new(file, options = {})
|
40
|
+
*
|
41
|
+
* Create a new Database object that opens the given file. If utf16
|
42
|
+
* is +true+, the filename is interpreted as a UTF-16 encoded string.
|
43
|
+
*
|
44
|
+
* By default, the new database will return result rows as arrays
|
45
|
+
* (#results_as_hash) and has type translation disabled (#type_translation=).
|
46
|
+
*/
|
47
|
+
static VALUE initialize(int argc, VALUE *argv, VALUE self)
|
48
|
+
{
|
49
|
+
sqlite3RubyPtr ctx;
|
50
|
+
VALUE file;
|
51
|
+
VALUE opts;
|
52
|
+
VALUE zvfs;
|
53
|
+
int status;
|
54
|
+
|
55
|
+
Data_Get_Struct(self, sqlite3Ruby, ctx);
|
56
|
+
|
57
|
+
rb_scan_args(argc, argv, "12", &file, &opts, &zvfs);
|
58
|
+
if(NIL_P(opts)) opts = rb_hash_new();
|
59
|
+
|
60
|
+
#ifdef HAVE_RUBY_ENCODING_H
|
61
|
+
if(UTF16_LE_P(file)) {
|
62
|
+
status = sqlite3_open16(utf16_string_value_ptr(file), &ctx->db);
|
63
|
+
} else {
|
64
|
+
#endif
|
65
|
+
|
66
|
+
if(Qtrue == rb_hash_aref(opts, sym_utf16)) {
|
67
|
+
status = sqlite3_open16(utf16_string_value_ptr(file), &ctx->db);
|
68
|
+
} else {
|
69
|
+
|
70
|
+
#ifdef HAVE_RUBY_ENCODING_H
|
71
|
+
if(!UTF8_P(file)) {
|
72
|
+
file = rb_str_export_to_enc(file, rb_utf8_encoding());
|
73
|
+
}
|
74
|
+
#endif
|
75
|
+
|
76
|
+
status = sqlite3_open_v2(
|
77
|
+
StringValuePtr(file),
|
78
|
+
&ctx->db,
|
79
|
+
SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE,
|
80
|
+
NIL_P(zvfs) ? NULL : StringValuePtr(zvfs)
|
81
|
+
);
|
82
|
+
}
|
83
|
+
|
84
|
+
#ifdef HAVE_RUBY_ENCODING_H
|
85
|
+
}
|
86
|
+
#endif
|
87
|
+
|
88
|
+
CHECK(ctx->db, status)
|
89
|
+
|
90
|
+
rb_iv_set(self, "@tracefunc", Qnil);
|
91
|
+
rb_iv_set(self, "@authorizer", Qnil);
|
92
|
+
rb_iv_set(self, "@encoding", Qnil);
|
93
|
+
rb_iv_set(self, "@busy_handler", Qnil);
|
94
|
+
rb_iv_set(self, "@results_as_hash", rb_hash_aref(opts, sym_results_as_hash));
|
95
|
+
rb_iv_set(self, "@type_translation", rb_hash_aref(opts, sym_type_translation));
|
96
|
+
|
97
|
+
if(rb_block_given_p()) {
|
98
|
+
rb_yield(self);
|
99
|
+
rb_funcall(self, rb_intern("close"), 0);
|
100
|
+
}
|
101
|
+
|
102
|
+
return self;
|
103
|
+
}
|
104
|
+
|
105
|
+
/* call-seq: db.close
|
106
|
+
*
|
107
|
+
* Closes this database.
|
108
|
+
*/
|
109
|
+
static VALUE sqlite3_rb_close(VALUE self)
|
110
|
+
{
|
111
|
+
sqlite3RubyPtr ctx;
|
112
|
+
sqlite3 * db;
|
113
|
+
Data_Get_Struct(self, sqlite3Ruby, ctx);
|
114
|
+
|
115
|
+
db = ctx->db;
|
116
|
+
CHECK(db, sqlite3_close(ctx->db));
|
117
|
+
|
118
|
+
ctx->db = NULL;
|
119
|
+
|
120
|
+
return self;
|
121
|
+
}
|
122
|
+
|
123
|
+
/* call-seq: db.closed?
|
124
|
+
*
|
125
|
+
* Returns +true+ if this database instance has been closed (see #close).
|
126
|
+
*/
|
127
|
+
static VALUE closed_p(VALUE self)
|
128
|
+
{
|
129
|
+
sqlite3RubyPtr ctx;
|
130
|
+
Data_Get_Struct(self, sqlite3Ruby, ctx);
|
131
|
+
|
132
|
+
if(!ctx->db) return Qtrue;
|
133
|
+
|
134
|
+
return Qfalse;
|
135
|
+
}
|
136
|
+
|
137
|
+
/* call-seq: total_changes
|
138
|
+
*
|
139
|
+
* Returns the total number of changes made to this database instance
|
140
|
+
* since it was opened.
|
141
|
+
*/
|
142
|
+
static VALUE total_changes(VALUE self)
|
143
|
+
{
|
144
|
+
sqlite3RubyPtr ctx;
|
145
|
+
Data_Get_Struct(self, sqlite3Ruby, ctx);
|
146
|
+
REQUIRE_OPEN_DB(ctx);
|
147
|
+
|
148
|
+
return INT2NUM((long)sqlite3_total_changes(ctx->db));
|
149
|
+
}
|
150
|
+
|
151
|
+
static void tracefunc(void * data, const char *sql)
|
152
|
+
{
|
153
|
+
VALUE self = (VALUE)data;
|
154
|
+
VALUE thing = rb_iv_get(self, "@tracefunc");
|
155
|
+
rb_funcall(thing, rb_intern("call"), 1, rb_str_new2(sql));
|
156
|
+
}
|
157
|
+
|
158
|
+
/* call-seq:
|
159
|
+
* trace { |sql| ... }
|
160
|
+
* trace(Class.new { def call sql; end }.new)
|
161
|
+
*
|
162
|
+
* Installs (or removes) a block that will be invoked for every SQL
|
163
|
+
* statement executed. The block receives one parameter: the SQL statement
|
164
|
+
* executed. If the block is +nil+, any existing tracer will be uninstalled.
|
165
|
+
*/
|
166
|
+
static VALUE trace(int argc, VALUE *argv, VALUE self)
|
167
|
+
{
|
168
|
+
sqlite3RubyPtr ctx;
|
169
|
+
VALUE block;
|
170
|
+
|
171
|
+
Data_Get_Struct(self, sqlite3Ruby, ctx);
|
172
|
+
REQUIRE_OPEN_DB(ctx);
|
173
|
+
|
174
|
+
rb_scan_args(argc, argv, "01", &block);
|
175
|
+
|
176
|
+
if(NIL_P(block) && rb_block_given_p()) block = rb_block_proc();
|
177
|
+
|
178
|
+
rb_iv_set(self, "@tracefunc", block);
|
179
|
+
|
180
|
+
sqlite3_trace(ctx->db, NIL_P(block) ? NULL : tracefunc, (void *)self);
|
181
|
+
|
182
|
+
return self;
|
183
|
+
}
|
184
|
+
|
185
|
+
static int rb_sqlite3_busy_handler(void * ctx, int count)
|
186
|
+
{
|
187
|
+
VALUE self = (VALUE)(ctx);
|
188
|
+
VALUE handle = rb_iv_get(self, "@busy_handler");
|
189
|
+
VALUE result = rb_funcall(handle, rb_intern("call"), 1, INT2NUM((long)count));
|
190
|
+
|
191
|
+
if(Qfalse == result) return 0;
|
192
|
+
|
193
|
+
return 1;
|
194
|
+
}
|
195
|
+
|
196
|
+
/* call-seq:
|
197
|
+
* busy_handler { |count| ... }
|
198
|
+
* busy_handler(Class.new { def call count; end }.new)
|
199
|
+
*
|
200
|
+
* Register a busy handler with this database instance. When a requested
|
201
|
+
* resource is busy, this handler will be invoked. If the handler returns
|
202
|
+
* +false+, the operation will be aborted; otherwise, the resource will
|
203
|
+
* be requested again.
|
204
|
+
*
|
205
|
+
* The handler will be invoked with the name of the resource that was
|
206
|
+
* busy, and the number of times it has been retried.
|
207
|
+
*
|
208
|
+
* See also the mutually exclusive #busy_timeout.
|
209
|
+
*/
|
210
|
+
static VALUE busy_handler(int argc, VALUE *argv, VALUE self)
|
211
|
+
{
|
212
|
+
sqlite3RubyPtr ctx;
|
213
|
+
VALUE block;
|
214
|
+
int status;
|
215
|
+
|
216
|
+
Data_Get_Struct(self, sqlite3Ruby, ctx);
|
217
|
+
REQUIRE_OPEN_DB(ctx);
|
218
|
+
|
219
|
+
rb_scan_args(argc, argv, "01", &block);
|
220
|
+
|
221
|
+
if(NIL_P(block) && rb_block_given_p()) block = rb_block_proc();
|
222
|
+
|
223
|
+
rb_iv_set(self, "@busy_handler", block);
|
224
|
+
|
225
|
+
status = sqlite3_busy_handler(
|
226
|
+
ctx->db, NIL_P(block) ? NULL : rb_sqlite3_busy_handler, (void *)self);
|
227
|
+
|
228
|
+
CHECK(ctx->db, status);
|
229
|
+
|
230
|
+
return self;
|
231
|
+
}
|
232
|
+
|
233
|
+
/* call-seq: last_insert_row_id
|
234
|
+
*
|
235
|
+
* Obtains the unique row ID of the last row to be inserted by this Database
|
236
|
+
* instance.
|
237
|
+
*/
|
238
|
+
static VALUE last_insert_row_id(VALUE self)
|
239
|
+
{
|
240
|
+
sqlite3RubyPtr ctx;
|
241
|
+
Data_Get_Struct(self, sqlite3Ruby, ctx);
|
242
|
+
REQUIRE_OPEN_DB(ctx);
|
243
|
+
|
244
|
+
return LL2NUM(sqlite3_last_insert_rowid(ctx->db));
|
245
|
+
}
|
246
|
+
|
247
|
+
static VALUE sqlite3val2rb(sqlite3_value * val)
|
248
|
+
{
|
249
|
+
switch(sqlite3_value_type(val)) {
|
250
|
+
case SQLITE_INTEGER:
|
251
|
+
return LL2NUM(sqlite3_value_int64(val));
|
252
|
+
break;
|
253
|
+
case SQLITE_FLOAT:
|
254
|
+
return rb_float_new(sqlite3_value_double(val));
|
255
|
+
break;
|
256
|
+
case SQLITE_TEXT:
|
257
|
+
return rb_tainted_str_new2((const char *)sqlite3_value_text(val));
|
258
|
+
break;
|
259
|
+
case SQLITE_BLOB:
|
260
|
+
return rb_tainted_str_new2((const char *)sqlite3_value_blob(val));
|
261
|
+
break;
|
262
|
+
case SQLITE_NULL:
|
263
|
+
return Qnil;
|
264
|
+
break;
|
265
|
+
default:
|
266
|
+
rb_raise(rb_eRuntimeError, "bad type"); /* FIXME */
|
267
|
+
}
|
268
|
+
}
|
269
|
+
|
270
|
+
static void set_sqlite3_func_result(sqlite3_context * ctx, VALUE result)
|
271
|
+
{
|
272
|
+
switch(TYPE(result)) {
|
273
|
+
case T_NIL:
|
274
|
+
sqlite3_result_null(ctx);
|
275
|
+
break;
|
276
|
+
case T_FIXNUM:
|
277
|
+
sqlite3_result_int64(ctx, (sqlite3_int64)FIX2LONG(result));
|
278
|
+
break;
|
279
|
+
case T_BIGNUM:
|
280
|
+
#if SIZEOF_LONG < 8
|
281
|
+
if (RBIGNUM_LEN(result) * SIZEOF_BDIGITS <= 8) {
|
282
|
+
sqlite3_result_int64(ctx, NUM2LL(result));
|
283
|
+
break;
|
284
|
+
}
|
285
|
+
#endif
|
286
|
+
case T_FLOAT:
|
287
|
+
sqlite3_result_double(ctx, NUM2DBL(result));
|
288
|
+
break;
|
289
|
+
case T_STRING:
|
290
|
+
sqlite3_result_text(
|
291
|
+
ctx,
|
292
|
+
(const char *)StringValuePtr(result),
|
293
|
+
(int)RSTRING_LEN(result),
|
294
|
+
SQLITE_TRANSIENT
|
295
|
+
);
|
296
|
+
break;
|
297
|
+
default:
|
298
|
+
rb_raise(rb_eRuntimeError, "can't return %s",
|
299
|
+
rb_class2name(CLASS_OF(result)));
|
300
|
+
}
|
301
|
+
}
|
302
|
+
|
303
|
+
static void rb_sqlite3_func(sqlite3_context * ctx, int argc, sqlite3_value **argv)
|
304
|
+
{
|
305
|
+
VALUE callable = (VALUE)sqlite3_user_data(ctx);
|
306
|
+
VALUE * params = NULL;
|
307
|
+
VALUE result;
|
308
|
+
int i;
|
309
|
+
|
310
|
+
if (argc > 0) {
|
311
|
+
params = xcalloc((size_t)argc, sizeof(VALUE *));
|
312
|
+
|
313
|
+
for(i = 0; i < argc; i++) {
|
314
|
+
params[i] = sqlite3val2rb(argv[i]);
|
315
|
+
}
|
316
|
+
}
|
317
|
+
|
318
|
+
result = rb_funcall2(callable, rb_intern("call"), argc, params);
|
319
|
+
xfree(params);
|
320
|
+
|
321
|
+
set_sqlite3_func_result(ctx, result);
|
322
|
+
}
|
323
|
+
|
324
|
+
#ifndef HAVE_RB_PROC_ARITY
|
325
|
+
int rb_proc_arity(VALUE self)
|
326
|
+
{
|
327
|
+
return (int)NUM2INT(rb_funcall(self, rb_intern("arity"), 0));
|
328
|
+
}
|
329
|
+
#endif
|
330
|
+
|
331
|
+
/* call-seq: define_function(name) { |args,...| }
|
332
|
+
*
|
333
|
+
* Define a function named +name+ with +args+. The arity of the block
|
334
|
+
* will be used as the arity for the function defined.
|
335
|
+
*/
|
336
|
+
static VALUE define_function(VALUE self, VALUE name)
|
337
|
+
{
|
338
|
+
sqlite3RubyPtr ctx;
|
339
|
+
VALUE block;
|
340
|
+
int status;
|
341
|
+
|
342
|
+
Data_Get_Struct(self, sqlite3Ruby, ctx);
|
343
|
+
REQUIRE_OPEN_DB(ctx);
|
344
|
+
|
345
|
+
block = rb_block_proc();
|
346
|
+
|
347
|
+
status = sqlite3_create_function(
|
348
|
+
ctx->db,
|
349
|
+
StringValuePtr(name),
|
350
|
+
rb_proc_arity(block),
|
351
|
+
SQLITE_UTF8,
|
352
|
+
(void *)block,
|
353
|
+
rb_sqlite3_func,
|
354
|
+
NULL,
|
355
|
+
NULL
|
356
|
+
);
|
357
|
+
|
358
|
+
CHECK(ctx->db, status);
|
359
|
+
|
360
|
+
return self;
|
361
|
+
}
|
362
|
+
|
363
|
+
static int sqlite3_obj_method_arity(VALUE obj, ID id)
|
364
|
+
{
|
365
|
+
VALUE method = rb_funcall(obj, rb_intern("method"), 1, ID2SYM(id));
|
366
|
+
VALUE arity = rb_funcall(method, rb_intern("arity"), 0);
|
367
|
+
|
368
|
+
return (int)NUM2INT(arity);
|
369
|
+
}
|
370
|
+
|
371
|
+
static void rb_sqlite3_step(sqlite3_context * ctx, int argc, sqlite3_value **argv)
|
372
|
+
{
|
373
|
+
VALUE callable = (VALUE)sqlite3_user_data(ctx);
|
374
|
+
VALUE * params = NULL;
|
375
|
+
int i;
|
376
|
+
|
377
|
+
if (argc > 0) {
|
378
|
+
params = xcalloc((size_t)argc, sizeof(VALUE *));
|
379
|
+
for(i = 0; i < argc; i++) {
|
380
|
+
params[i] = sqlite3val2rb(argv[i]);
|
381
|
+
}
|
382
|
+
}
|
383
|
+
rb_funcall2(callable, rb_intern("step"), argc, params);
|
384
|
+
xfree(params);
|
385
|
+
}
|
386
|
+
|
387
|
+
static void rb_sqlite3_final(sqlite3_context * ctx)
|
388
|
+
{
|
389
|
+
VALUE callable = (VALUE)sqlite3_user_data(ctx);
|
390
|
+
VALUE result = rb_funcall(callable, rb_intern("finalize"), 0);
|
391
|
+
set_sqlite3_func_result(ctx, result);
|
392
|
+
}
|
393
|
+
|
394
|
+
/* call-seq: define_aggregator(name, aggregator)
|
395
|
+
*
|
396
|
+
* Define an aggregate function named +name+ using the object +aggregator+.
|
397
|
+
* +aggregator+ must respond to +step+ and +finalize+. +step+ will be called
|
398
|
+
* with row information and +finalize+ must return the return value for the
|
399
|
+
* aggregator function.
|
400
|
+
*/
|
401
|
+
static VALUE define_aggregator(VALUE self, VALUE name, VALUE aggregator)
|
402
|
+
{
|
403
|
+
sqlite3RubyPtr ctx;
|
404
|
+
int arity, status;
|
405
|
+
|
406
|
+
Data_Get_Struct(self, sqlite3Ruby, ctx);
|
407
|
+
REQUIRE_OPEN_DB(ctx);
|
408
|
+
|
409
|
+
arity = sqlite3_obj_method_arity(aggregator, rb_intern("step"));
|
410
|
+
|
411
|
+
status = sqlite3_create_function(
|
412
|
+
ctx->db,
|
413
|
+
StringValuePtr(name),
|
414
|
+
arity,
|
415
|
+
SQLITE_UTF8,
|
416
|
+
(void *)aggregator,
|
417
|
+
NULL,
|
418
|
+
rb_sqlite3_step,
|
419
|
+
rb_sqlite3_final
|
420
|
+
);
|
421
|
+
|
422
|
+
rb_iv_set(self, "@agregator", aggregator);
|
423
|
+
|
424
|
+
CHECK(ctx->db, status);
|
425
|
+
|
426
|
+
return self;
|
427
|
+
}
|
428
|
+
|
429
|
+
/* call-seq: interrupt
|
430
|
+
*
|
431
|
+
* Interrupts the currently executing operation, causing it to abort.
|
432
|
+
*/
|
433
|
+
static VALUE interrupt(VALUE self)
|
434
|
+
{
|
435
|
+
sqlite3RubyPtr ctx;
|
436
|
+
Data_Get_Struct(self, sqlite3Ruby, ctx);
|
437
|
+
REQUIRE_OPEN_DB(ctx);
|
438
|
+
|
439
|
+
sqlite3_interrupt(ctx->db);
|
440
|
+
|
441
|
+
return self;
|
442
|
+
}
|
443
|
+
|
444
|
+
/* call-seq: errmsg
|
445
|
+
*
|
446
|
+
* Return a string describing the last error to have occurred with this
|
447
|
+
* database.
|
448
|
+
*/
|
449
|
+
static VALUE errmsg(VALUE self)
|
450
|
+
{
|
451
|
+
sqlite3RubyPtr ctx;
|
452
|
+
Data_Get_Struct(self, sqlite3Ruby, ctx);
|
453
|
+
REQUIRE_OPEN_DB(ctx);
|
454
|
+
|
455
|
+
return rb_str_new2(sqlite3_errmsg(ctx->db));
|
456
|
+
}
|
457
|
+
|
458
|
+
/* call-seq: errcode
|
459
|
+
*
|
460
|
+
* Return an integer representing the last error to have occurred with this
|
461
|
+
* database.
|
462
|
+
*/
|
463
|
+
static VALUE errcode(VALUE self)
|
464
|
+
{
|
465
|
+
sqlite3RubyPtr ctx;
|
466
|
+
Data_Get_Struct(self, sqlite3Ruby, ctx);
|
467
|
+
REQUIRE_OPEN_DB(ctx);
|
468
|
+
|
469
|
+
return INT2NUM((long)sqlite3_errcode(ctx->db));
|
470
|
+
}
|
471
|
+
|
472
|
+
/* call-seq: complete?(sql)
|
473
|
+
*
|
474
|
+
* Return +true+ if the string is a valid (ie, parsable) SQL statement, and
|
475
|
+
* +false+ otherwise.
|
476
|
+
*/
|
477
|
+
static VALUE complete_p(VALUE UNUSED(self), VALUE sql)
|
478
|
+
{
|
479
|
+
if(sqlite3_complete(StringValuePtr(sql)))
|
480
|
+
return Qtrue;
|
481
|
+
|
482
|
+
return Qfalse;
|
483
|
+
}
|
484
|
+
|
485
|
+
/* call-seq: changes
|
486
|
+
*
|
487
|
+
* Returns the number of changes made to this database instance by the last
|
488
|
+
* operation performed. Note that a "delete from table" without a where
|
489
|
+
* clause will not affect this value.
|
490
|
+
*/
|
491
|
+
static VALUE changes(VALUE self)
|
492
|
+
{
|
493
|
+
sqlite3RubyPtr ctx;
|
494
|
+
Data_Get_Struct(self, sqlite3Ruby, ctx);
|
495
|
+
REQUIRE_OPEN_DB(ctx);
|
496
|
+
|
497
|
+
return INT2NUM(sqlite3_changes(ctx->db));
|
498
|
+
}
|
499
|
+
|
500
|
+
static int rb_sqlite3_auth(
|
501
|
+
void *ctx,
|
502
|
+
int _action,
|
503
|
+
const char * _a,
|
504
|
+
const char * _b,
|
505
|
+
const char * _c,
|
506
|
+
const char * _d)
|
507
|
+
{
|
508
|
+
VALUE self = (VALUE)ctx;
|
509
|
+
VALUE action = INT2NUM(_action);
|
510
|
+
VALUE a = _a ? rb_str_new2(_a) : Qnil;
|
511
|
+
VALUE b = _b ? rb_str_new2(_b) : Qnil;
|
512
|
+
VALUE c = _c ? rb_str_new2(_c) : Qnil;
|
513
|
+
VALUE d = _d ? rb_str_new2(_d) : Qnil;
|
514
|
+
VALUE callback = rb_iv_get(self, "@authorizer");
|
515
|
+
VALUE result = rb_funcall(callback, rb_intern("call"), 5, action, a, b, c, d);
|
516
|
+
|
517
|
+
if(T_FIXNUM == TYPE(result)) return (int)NUM2INT(result);
|
518
|
+
if(Qtrue == result) return SQLITE_OK;
|
519
|
+
if(Qfalse == result) return SQLITE_DENY;
|
520
|
+
|
521
|
+
return SQLITE_IGNORE;
|
522
|
+
}
|
523
|
+
|
524
|
+
/* call-seq: set_authorizer = auth
|
525
|
+
*
|
526
|
+
* Set the authorizer for this database. +auth+ must respond to +call+, and
|
527
|
+
* +call+ must take 5 arguments.
|
528
|
+
*
|
529
|
+
* Installs (or removes) a block that will be invoked for every access
|
530
|
+
* to the database. If the block returns 0 (or +true+), the statement
|
531
|
+
* is allowed to proceed. Returning 1 or false causes an authorization error to
|
532
|
+
* occur, and returning 2 or nil causes the access to be silently denied.
|
533
|
+
*/
|
534
|
+
static VALUE set_authorizer(VALUE self, VALUE authorizer)
|
535
|
+
{
|
536
|
+
sqlite3RubyPtr ctx;
|
537
|
+
int status;
|
538
|
+
|
539
|
+
Data_Get_Struct(self, sqlite3Ruby, ctx);
|
540
|
+
REQUIRE_OPEN_DB(ctx);
|
541
|
+
|
542
|
+
status = sqlite3_set_authorizer(
|
543
|
+
ctx->db, NIL_P(authorizer) ? NULL : rb_sqlite3_auth, (void *)self
|
544
|
+
);
|
545
|
+
|
546
|
+
CHECK(ctx->db, status);
|
547
|
+
|
548
|
+
rb_iv_set(self, "@authorizer", authorizer);
|
549
|
+
|
550
|
+
return self;
|
551
|
+
}
|
552
|
+
|
553
|
+
/* call-seq: db.busy_timeout = ms
|
554
|
+
*
|
555
|
+
* Indicates that if a request for a resource terminates because that
|
556
|
+
* resource is busy, SQLite should sleep and retry for up to the indicated
|
557
|
+
* number of milliseconds. By default, SQLite does not retry
|
558
|
+
* busy resources. To restore the default behavior, send 0 as the
|
559
|
+
* +ms+ parameter.
|
560
|
+
*
|
561
|
+
* See also the mutually exclusive #busy_handler.
|
562
|
+
*/
|
563
|
+
static VALUE set_busy_timeout(VALUE self, VALUE timeout)
|
564
|
+
{
|
565
|
+
sqlite3RubyPtr ctx;
|
566
|
+
Data_Get_Struct(self, sqlite3Ruby, ctx);
|
567
|
+
REQUIRE_OPEN_DB(ctx);
|
568
|
+
|
569
|
+
CHECK(ctx->db, sqlite3_busy_timeout(ctx->db, (int)NUM2INT(timeout)));
|
570
|
+
|
571
|
+
return self;
|
572
|
+
}
|
573
|
+
|
574
|
+
/* call-seq: db.load_extension(file)
|
575
|
+
*
|
576
|
+
* Loads an SQLite extension library from the named file. Extension
|
577
|
+
* loading must be enabled using db.enable_load_extension(1) prior
|
578
|
+
* to calling this API.
|
579
|
+
*/
|
580
|
+
static VALUE load_extension(VALUE self, VALUE file)
|
581
|
+
{
|
582
|
+
sqlite3RubyPtr ctx;
|
583
|
+
int status;
|
584
|
+
char *errMsg;
|
585
|
+
VALUE errexp;
|
586
|
+
Data_Get_Struct(self, sqlite3Ruby, ctx);
|
587
|
+
REQUIRE_OPEN_DB(ctx);
|
588
|
+
|
589
|
+
status = sqlite3_load_extension(ctx->db, RSTRING_PTR(file), 0, &errMsg);
|
590
|
+
if (status != SQLITE_OK)
|
591
|
+
{
|
592
|
+
errexp = rb_exc_new2(rb_eRuntimeError, errMsg);
|
593
|
+
sqlite3_free(errMsg);
|
594
|
+
rb_exc_raise(errexp);
|
595
|
+
}
|
596
|
+
|
597
|
+
return self;
|
598
|
+
}
|
599
|
+
|
600
|
+
/* call-seq: db.enable_load_extension(onoff)
|
601
|
+
*
|
602
|
+
* Enable or disable extension loading.
|
603
|
+
*/
|
604
|
+
static VALUE enable_load_extension(VALUE self, VALUE onoff)
|
605
|
+
{
|
606
|
+
sqlite3RubyPtr ctx;
|
607
|
+
Data_Get_Struct(self, sqlite3Ruby, ctx);
|
608
|
+
REQUIRE_OPEN_DB(ctx);
|
609
|
+
|
610
|
+
CHECK(ctx->db, sqlite3_enable_load_extension(ctx->db, (int)NUM2INT(onoff)));
|
611
|
+
|
612
|
+
return self;
|
613
|
+
}
|
614
|
+
|
615
|
+
#ifdef HAVE_RUBY_ENCODING_H
|
616
|
+
static int enc_cb(void * _self, int UNUSED(columns), char **data, char **UNUSED(names))
|
617
|
+
{
|
618
|
+
VALUE self = (VALUE)_self;
|
619
|
+
|
620
|
+
int index = rb_enc_find_index(data[0]);
|
621
|
+
rb_encoding * e = rb_enc_from_index(index);
|
622
|
+
rb_iv_set(self, "@encoding", rb_enc_from_encoding(e));
|
623
|
+
|
624
|
+
return 0;
|
625
|
+
}
|
626
|
+
|
627
|
+
/* call-seq: db.encoding
|
628
|
+
*
|
629
|
+
* Fetch the encoding set on this database
|
630
|
+
*/
|
631
|
+
static VALUE db_encoding(VALUE self)
|
632
|
+
{
|
633
|
+
sqlite3RubyPtr ctx;
|
634
|
+
VALUE enc;
|
635
|
+
|
636
|
+
Data_Get_Struct(self, sqlite3Ruby, ctx);
|
637
|
+
REQUIRE_OPEN_DB(ctx);
|
638
|
+
|
639
|
+
enc = rb_iv_get(self, "@encoding");
|
640
|
+
|
641
|
+
if(NIL_P(enc)) {
|
642
|
+
sqlite3_exec(ctx->db, "PRAGMA encoding", enc_cb, (void *)self, NULL);
|
643
|
+
}
|
644
|
+
|
645
|
+
return rb_iv_get(self, "@encoding");
|
646
|
+
}
|
647
|
+
#endif
|
648
|
+
|
649
|
+
void init_sqlite3_database()
|
650
|
+
{
|
651
|
+
ID id_utf16, id_results_as_hash, id_type_translation;
|
652
|
+
#if 0
|
653
|
+
VALUE mSqlite3 = rb_define_module("SQLite3");
|
654
|
+
#endif
|
655
|
+
cSqlite3Database = rb_define_class_under(mSqlite3, "Database", rb_cObject);
|
656
|
+
|
657
|
+
rb_define_alloc_func(cSqlite3Database, allocate);
|
658
|
+
rb_define_method(cSqlite3Database, "initialize", initialize, -1);
|
659
|
+
rb_define_method(cSqlite3Database, "close", sqlite3_rb_close, 0);
|
660
|
+
rb_define_method(cSqlite3Database, "closed?", closed_p, 0);
|
661
|
+
rb_define_method(cSqlite3Database, "total_changes", total_changes, 0);
|
662
|
+
rb_define_method(cSqlite3Database, "trace", trace, -1);
|
663
|
+
rb_define_method(cSqlite3Database, "last_insert_row_id", last_insert_row_id, 0);
|
664
|
+
rb_define_method(cSqlite3Database, "define_function", define_function, 1);
|
665
|
+
rb_define_method(cSqlite3Database, "define_aggregator", define_aggregator, 2);
|
666
|
+
rb_define_method(cSqlite3Database, "interrupt", interrupt, 0);
|
667
|
+
rb_define_method(cSqlite3Database, "errmsg", errmsg, 0);
|
668
|
+
rb_define_method(cSqlite3Database, "errcode", errcode, 0);
|
669
|
+
rb_define_method(cSqlite3Database, "complete?", complete_p, 1);
|
670
|
+
rb_define_method(cSqlite3Database, "changes", changes, 0);
|
671
|
+
rb_define_method(cSqlite3Database, "authorizer=", set_authorizer, 1);
|
672
|
+
rb_define_method(cSqlite3Database, "busy_handler", busy_handler, -1);
|
673
|
+
rb_define_method(cSqlite3Database, "busy_timeout=", set_busy_timeout, 1);
|
674
|
+
rb_define_method(cSqlite3Database, "load_extension", load_extension, 1);
|
675
|
+
rb_define_method(cSqlite3Database, "enable_load_extension", enable_load_extension, 1);
|
676
|
+
|
677
|
+
#ifdef HAVE_RUBY_ENCODING_H
|
678
|
+
rb_define_method(cSqlite3Database, "encoding", db_encoding, 0);
|
679
|
+
#endif
|
680
|
+
|
681
|
+
id_utf16 = rb_intern("utf16");
|
682
|
+
sym_utf16 = ID2SYM(id_utf16);
|
683
|
+
id_results_as_hash = rb_intern("results_as_hash");
|
684
|
+
sym_results_as_hash = ID2SYM(id_results_as_hash);
|
685
|
+
id_type_translation = rb_intern("type_translation");
|
686
|
+
sym_type_translation = ID2SYM(id_type_translation);
|
687
|
+
}
|