rdo-sqlite 0.0.1

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.
@@ -0,0 +1,269 @@
1
+ /*
2
+ * RDO SQLite3 Driver.
3
+ * Copyright © 2012 Chris Corbyn.
4
+ *
5
+ * See LICENSE file for details.
6
+ */
7
+
8
+ #include <stdio.h>
9
+ #include <stdlib.h>
10
+ #include "statements.h"
11
+ #include "driver.h"
12
+ #include "macros.h"
13
+
14
+ /** Sruct wrapped by class RDO::SQLite::StatementExecutor */
15
+ typedef struct {
16
+ RDOSQLiteDriver * driver;
17
+ char * cmd;
18
+ sqlite3_stmt * stmt;
19
+ } RDOSQLiteStatementExecutor;
20
+
21
+ /** RDO::SQLite::StatementExecutor class */
22
+ VALUE rdo_sqlite_cStatementExecutor;
23
+
24
+ /** Free memory associated with the statement during GC */
25
+ static void rdo_sqlite_statement_executor_free(RDOSQLiteStatementExecutor * executor) {
26
+ sqlite3_finalize(executor->stmt);
27
+ free(executor);
28
+ }
29
+
30
+ /** Check if the 'tail' of a query points to something other than a comment */
31
+ static int rdo_sqlite_inert_p(char * s) {
32
+ int inslcmt = 0;
33
+ int inmlcmt = 0;
34
+
35
+ for (; *s; ++s) {
36
+ switch (*s) {
37
+ case ' ':
38
+ case '\t':
39
+ break;
40
+
41
+ case '\r':
42
+ case '\n':
43
+ inslcmt = 0;
44
+ break;
45
+
46
+ case '/':
47
+ if (!inslcmt && *(s + 1) == '*') {
48
+ inmlcmt = 1;
49
+ ++s;
50
+ } else if (!inslcmt && !inmlcmt) {
51
+ return 0;
52
+ }
53
+ break;
54
+
55
+ case '*':
56
+ if (inmlcmt && *(s + 1) == '/') {
57
+ inmlcmt = 0;
58
+ ++s;
59
+ } else if (!inslcmt && !inmlcmt) {
60
+ return 0;
61
+ }
62
+ break;
63
+
64
+ case '-':
65
+ if (!inmlcmt && *(s + 1) == '-') {
66
+ inslcmt = 1;
67
+ ++s;
68
+ } else if (!inslcmt && !inmlcmt) {
69
+ return 0;
70
+ }
71
+ break;
72
+
73
+ default:
74
+ if (!inslcmt && !inmlcmt) {
75
+ return 0;
76
+ }
77
+ }
78
+ }
79
+
80
+ return 1;
81
+ }
82
+
83
+ /** Create a new statement executor for the given command */
84
+ VALUE rdo_sqlite_statement_executor_new(VALUE driver, VALUE cmd) {
85
+ Check_Type(cmd, T_STRING);
86
+
87
+ RDOSQLiteStatementExecutor * executor =
88
+ malloc(sizeof(RDOSQLiteStatementExecutor));
89
+
90
+ Data_Get_Struct(driver, RDOSQLiteDriver, executor->driver);
91
+ executor->cmd = strdup(RSTRING_PTR(cmd));
92
+ executor->stmt = NULL;
93
+
94
+ VALUE self = Data_Wrap_Struct(rdo_sqlite_cStatementExecutor, 0,
95
+ rdo_sqlite_statement_executor_free, executor);
96
+
97
+ rb_obj_call_init(self, 1, &driver);
98
+
99
+ return self;
100
+ }
101
+
102
+ /** Initialize the statement (prepare it) */
103
+ static VALUE rdo_sqlite_statement_executor_initialize(VALUE self, VALUE driver) {
104
+ rb_iv_set(self, "driver", driver); // GC safety
105
+ RDOSQLiteStatementExecutor * executor;
106
+ Data_Get_Struct(self, RDOSQLiteStatementExecutor, executor);
107
+
108
+ if (!(executor->driver->is_open)) {
109
+ RDO_ERROR("Cannot execute prepare statement: database is not open");
110
+ }
111
+
112
+ const char * tail;
113
+
114
+ int status = sqlite3_prepare_v2(executor->driver->db,
115
+ executor->cmd,
116
+ (int) strlen(executor->cmd) + 1,
117
+ &(executor->stmt),
118
+ &tail);
119
+
120
+ if ((status != SQLITE_OK) || sqlite3_errcode(executor->driver->db)) {
121
+ RDO_ERROR("Failed to prepare statement: %s",
122
+ sqlite3_errmsg(executor->driver->db));
123
+ }
124
+
125
+ if (!rdo_sqlite_inert_p((char *) tail)) {
126
+ rb_raise(rb_eArgError, "Only one statement can be executed at a time");
127
+ }
128
+
129
+ return self;
130
+ }
131
+
132
+ /** Get the command this statement will execute */
133
+ static VALUE rdo_sqlite_statement_executor_command(VALUE self) {
134
+ RDOSQLiteStatementExecutor * executor;
135
+ Data_Get_Struct(self, RDOSQLiteStatementExecutor, executor);
136
+ return rb_str_new2(executor->cmd);
137
+ }
138
+
139
+ /** Fetch the value from the given column in the result and convert to a Ruby type */
140
+ static VALUE rdo_sqlite_cast_value(sqlite3_stmt * stmt, int col) {
141
+ switch (sqlite3_column_type(stmt, col)) {
142
+ case SQLITE_NULL:
143
+ return Qnil;
144
+
145
+ case SQLITE_INTEGER:
146
+ return LL2NUM(sqlite3_column_int64(stmt, col));
147
+
148
+ case SQLITE_FLOAT:
149
+ return DBL2NUM(sqlite3_column_double(stmt, col));
150
+
151
+ case SQLITE_TEXT:
152
+ return RDO_STRING((const char *) sqlite3_column_text(stmt, col),
153
+ sqlite3_column_bytes(stmt, col), 1);
154
+
155
+ case SQLITE_BLOB:
156
+ return RDO_BINARY_STRING((const char *) sqlite3_column_blob(stmt, col),
157
+ sqlite3_column_bytes(stmt, col));
158
+
159
+ default:
160
+ return RDO_BINARY_STRING((const char *) sqlite3_column_text(stmt, col),
161
+ sqlite3_column_bytes(stmt, col));
162
+ }
163
+ }
164
+
165
+ /** Extract useful result information from the db and the statement */
166
+ static VALUE rdo_sqlite_result_info(sqlite3 * db, sqlite3_stmt * stmt) {
167
+ VALUE info = rb_hash_new();
168
+ rb_hash_aset(info, ID2SYM(rb_intern("insert_id")),
169
+ LL2NUM(sqlite3_last_insert_rowid(db)));
170
+ rb_hash_aset(info, ID2SYM(rb_intern("affected_rows")),
171
+ LL2NUM(sqlite3_changes(db)));
172
+ return info;
173
+ }
174
+
175
+ /** Bind all input values to the statement */
176
+ static void rdo_sqlite_statement_bind_args(sqlite3_stmt * stmt, int argc, VALUE * args) {
177
+ if (sqlite3_bind_parameter_count(stmt) != argc) {
178
+ rb_raise(rb_eArgError,
179
+ "Bind parameter count mismatch: wanted %i, got %i",
180
+ sqlite3_bind_parameter_count(stmt),
181
+ argc);
182
+ }
183
+
184
+ VALUE v;
185
+ int i = 0;
186
+
187
+ for (; i < argc; ++i) {
188
+ v = args[i];
189
+
190
+ if (v == Qnil) {
191
+ sqlite3_bind_null(stmt, i);
192
+ } else {
193
+ if (v == Qtrue) v = INT2NUM(1);
194
+ if (v == Qfalse) v = INT2NUM(0);
195
+
196
+ if ((rb_funcall(v, rb_intern("kind_of?"), 1, rb_cTime) == Qtrue)
197
+ || rb_funcall(v, rb_intern("kind_of?"), 1, rb_path2class("DateTime"))) {
198
+ v = rb_funcall(v, rb_intern("strftime"), 1, rb_str_new2("%F %T"));
199
+ }
200
+
201
+ if (TYPE(v) != T_STRING) v = RDO_OBJ_TO_S(v);
202
+
203
+ sqlite3_bind_text(stmt, i + 1,
204
+ RSTRING_PTR(v), (int) RSTRING_LEN(v), NULL);
205
+ }
206
+ }
207
+ }
208
+
209
+ /** Iterate over all rows in the result and return an Array */
210
+ static VALUE rdo_sqlite_statement_extract_tuples(sqlite3 * db, sqlite3_stmt * stmt) {
211
+ int status;
212
+ int col = 0;
213
+ int ncols = 0;
214
+ VALUE hash;
215
+ VALUE tuples = rb_ary_new();
216
+
217
+ while ((status = sqlite3_step(stmt)) == SQLITE_ROW) {
218
+ hash = rb_hash_new();
219
+ ncols = sqlite3_column_count(stmt);
220
+
221
+ for (col = 0; col < ncols; ++col) {
222
+ rb_hash_aset(hash,
223
+ ID2SYM(rb_intern(sqlite3_column_name(stmt, col))),
224
+ rdo_sqlite_cast_value(stmt, col));
225
+ }
226
+
227
+ rb_ary_push(tuples, hash);
228
+ }
229
+
230
+ if (status != SQLITE_DONE) {
231
+ RDO_ERROR("Failed to execute statement: %s", sqlite3_errmsg(db));
232
+ }
233
+
234
+ return tuples;
235
+ }
236
+
237
+ /** Execute the statement with the given bind parameters and return a Result */
238
+ static VALUE rdo_sqlite_statement_executor_execute(int argc, VALUE * args, VALUE self) {
239
+ RDOSQLiteStatementExecutor * executor;
240
+ Data_Get_Struct(self, RDOSQLiteStatementExecutor, executor);
241
+
242
+ if (!(executor->driver->is_open)) {
243
+ RDO_ERROR("Cannot execute execute statement: database is not open");
244
+ }
245
+
246
+ rdo_sqlite_statement_bind_args(executor->stmt, argc, args);
247
+
248
+ VALUE tuples = rdo_sqlite_statement_extract_tuples(executor->driver->db, executor->stmt);
249
+ VALUE info = rdo_sqlite_result_info(executor->driver->db, executor->stmt);
250
+ sqlite3_reset(executor->stmt);
251
+
252
+ return RDO_RESULT(tuples, info);
253
+ }
254
+
255
+ /** Initialize the statements framework */
256
+ void Init_rdo_sqlite_statements(void) {
257
+ rb_require("date");
258
+
259
+ VALUE mSQLite = rb_path2class("RDO::SQLite");
260
+ rdo_sqlite_cStatementExecutor = rb_define_class_under(
261
+ mSQLite, "StatementExecutor", rb_cObject);
262
+
263
+ rb_define_method(rdo_sqlite_cStatementExecutor,
264
+ "initialize", rdo_sqlite_statement_executor_initialize, 1);
265
+ rb_define_method(rdo_sqlite_cStatementExecutor,
266
+ "command", rdo_sqlite_statement_executor_command, 0);
267
+ rb_define_method(rdo_sqlite_cStatementExecutor,
268
+ "execute", rdo_sqlite_statement_executor_execute, -1);
269
+ }
@@ -0,0 +1,15 @@
1
+ /*
2
+ * RDO SQLite3 Driver.
3
+ * Copyright © 2012 Chris Corbyn.
4
+ *
5
+ * See LICENSE file for details.
6
+ */
7
+
8
+ #include <ruby.h>
9
+ #include <sqlite3.h>
10
+
11
+ /** Create a new prepared statement executor for the given command */
12
+ VALUE rdo_sqlite_statement_executor_new(VALUE driver, VALUE cmd);
13
+
14
+ /** Initialize the prepared statements class */
15
+ void Init_rdo_sqlite_statements(void);
@@ -0,0 +1,23 @@
1
+ ##
2
+ # RDO SQLite3 driver.
3
+ # Copyright © 2012 Chris Corbyn.
4
+ #
5
+ # See LICENSE file for details.
6
+ ##
7
+
8
+ module RDO
9
+ module SQLite
10
+ # Main Driver class to hook into sqlite3 API
11
+ class Driver < RDO::Driver
12
+ def execute(stmt, *args)
13
+ prepare(stmt).execute(*args)
14
+ end
15
+
16
+ private
17
+
18
+ def filename
19
+ options[:path].to_s
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,12 @@
1
+ ##
2
+ # RDO SQLite3 driver.
3
+ # Copyright © 2012 Chris Corbyn.
4
+ #
5
+ # See LICENSE file for details.
6
+ ##
7
+
8
+ module RDO
9
+ module SQLite
10
+ VERSION = "0.0.1"
11
+ end
12
+ end
data/lib/rdo/sqlite.rb ADDED
@@ -0,0 +1,16 @@
1
+ ##
2
+ # RDO SQLite3 driver.
3
+ # Copyright © 2012 Chris Corbyn.
4
+ #
5
+ # See LICENSE file for details.
6
+ ##
7
+
8
+ require "rdo"
9
+ require "rdo/sqlite/version"
10
+ require "rdo/sqlite/driver"
11
+ require "rdo_sqlite/rdo_sqlite" # c extension
12
+
13
+ # Register driver with RDO
14
+ %w[sqlite sqlite3].each do |name|
15
+ RDO::Connection.register_driver(name, RDO::SQLite::Driver)
16
+ end
data/lib/rdo-sqlite.rb ADDED
@@ -0,0 +1 @@
1
+ require "rdo/sqlite"
@@ -0,0 +1,23 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/rdo/sqlite/version', __FILE__)
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.authors = ["d11wtq"]
6
+ gem.email = ["chris@w3style.co.uk"]
7
+ gem.description = "Provides access to SQLite3 using the RDO interface"
8
+ gem.summary = "SQLite3 Driver for RDO"
9
+ gem.homepage = "https://github.com/d11wtq/rdo-sqlite"
10
+
11
+ gem.files = `git ls-files`.split($\)
12
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
13
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
14
+ gem.name = "rdo-sqlite"
15
+ gem.require_paths = ["lib"]
16
+ gem.version = RDO::SQLite::VERSION
17
+ gem.extensions = ["ext/rdo_sqlite/extconf.rb"]
18
+
19
+ gem.add_runtime_dependency "rdo", ">= 0.0.1"
20
+
21
+ gem.add_development_dependency "rspec"
22
+ gem.add_development_dependency "rake-compiler"
23
+ end
@@ -0,0 +1,10 @@
1
+ require "rdo"
2
+ require "rdo/sqlite"
3
+
4
+ ENV["CONNECTION"] ||= "sqlite://memory?encoding=utf-8"
5
+
6
+ RSpec.configure do |config|
7
+ def connection_uri
8
+ ENV["CONNECTION"]
9
+ end
10
+ end
@@ -0,0 +1,316 @@
1
+ require "spec_helper"
2
+ require "bigdecimal"
3
+ require "date"
4
+
5
+ describe RDO::SQLite::Driver, "bind params" do
6
+ let(:options) { "sqlite::memory:" }
7
+ let(:db) { RDO.connect(options) }
8
+ let(:table) { "" }
9
+ let(:tuple) { db.execute("SELECT * FROM test").first }
10
+
11
+ before(:each) { db.execute(table) }
12
+
13
+ describe "nil param" do
14
+ context "against a text field" do
15
+ let(:table) { "CREATE TABLE test (name text)" }
16
+
17
+ before(:each) { db.execute("INSERT INTO test (name) VALUES (?)", nil) }
18
+
19
+ it "is inferred correctly" do
20
+ tuple[:name].should be_nil
21
+ end
22
+ end
23
+
24
+ context "against an integer field" do
25
+ let(:table) { "CREATE TABLE test (age integer)" }
26
+
27
+ before(:each) { db.execute("INSERT INTO test (age) VALUES (?)", nil) }
28
+
29
+ it "is inferred correctly" do
30
+ tuple[:age].should be_nil
31
+ end
32
+ end
33
+
34
+ context "against a float field" do
35
+ let(:table) { "CREATE TABLE test (score float)" }
36
+
37
+ before(:each) { db.execute("INSERT INTO test (score) VALUES (?)", nil) }
38
+
39
+ it "is inferred correctly" do
40
+ tuple[:score].should be_nil
41
+ end
42
+ end
43
+
44
+ context "against a blob field" do
45
+ let(:table) { "CREATE TABLE test (salt blob)" }
46
+
47
+ before(:each) { db.execute("INSERT INTO test (salt) VALUES (?)", nil) }
48
+
49
+ it "is inferred correctly" do
50
+ tuple[:salt].should be_nil
51
+ end
52
+ end
53
+ end
54
+
55
+ describe "String param" do
56
+ context "against a text field" do
57
+ let(:table) { "CREATE TABLE test (name text)" }
58
+
59
+ before(:each) { db.execute("INSERT INTO test (name) VALUES (?)", "jim") }
60
+
61
+ it "is inferred correctly" do
62
+ tuple[:name].should == "jim"
63
+ end
64
+ end
65
+
66
+ context "against an integer field" do
67
+ let(:table) { "CREATE TABLE test (age integer)" }
68
+
69
+ before(:each) { db.execute("INSERT INTO test (age) VALUES (?)", "29") }
70
+
71
+ it "is inferred correctly" do
72
+ tuple[:age].should == 29
73
+ end
74
+ end
75
+
76
+ context "against an float field" do
77
+ let(:table) { "CREATE TABLE test (score float)" }
78
+
79
+ before(:each) { db.execute("INSERT INTO test (score) VALUES (?)", "56.4") }
80
+
81
+ it "is inferred correctly" do
82
+ tuple[:score].should == 56.4
83
+ end
84
+ end
85
+
86
+ context "against an blob field" do
87
+ let(:table) { "CREATE TABLE test (salt blob)" }
88
+
89
+ before(:each) { db.execute("INSERT INTO test (salt) VALUES (?)", "\x00\x11\x22") }
90
+
91
+ it "is inferred correctly" do
92
+ tuple[:salt].should == "\x00\x11\x22"
93
+ end
94
+ end
95
+ end
96
+
97
+ describe "Fixnum param" do
98
+ context "against an integer field" do
99
+ let(:table) { "CREATE TABLE test (age integer)" }
100
+
101
+ before(:each) { db.execute("INSERT INTO test (age) VALUES (?)", 29) }
102
+
103
+ it "is inferred correctly" do
104
+ tuple[:age].should == 29
105
+ end
106
+ end
107
+
108
+ context "against a text field" do
109
+ let(:table) { "CREATE TABLE test (name text)" }
110
+
111
+ before(:each) { db.execute("INSERT INTO test (name) VALUES (?)", 27) }
112
+
113
+ it "is inferred correctly" do
114
+ tuple[:name].should == "27"
115
+ end
116
+ end
117
+
118
+ context "against an float field" do
119
+ let(:table) { "CREATE TABLE test (score float)" }
120
+
121
+ before(:each) { db.execute("INSERT INTO test (score) VALUES (?)", 56) }
122
+
123
+ it "is inferred correctly" do
124
+ tuple[:score].should == 56.0
125
+ end
126
+ end
127
+
128
+ context "against an blob field" do
129
+ let(:table) { "CREATE TABLE test (salt blob)" }
130
+
131
+ before(:each) { db.execute("INSERT INTO test (salt) VALUES (?)", 19) }
132
+
133
+ it "is inferred correctly" do
134
+ tuple[:salt].should == "19"
135
+ end
136
+ end
137
+ end
138
+
139
+ describe "Float param" do
140
+ context "against an float field" do
141
+ let(:table) { "CREATE TABLE test (score float)" }
142
+
143
+ before(:each) { db.execute("INSERT INTO test (score) VALUES (?)", 56.4) }
144
+
145
+ it "is inferred correctly" do
146
+ tuple[:score].should == 56.4
147
+ end
148
+ end
149
+
150
+ context "against an integer field" do
151
+ let(:table) { "CREATE TABLE test (age integer)" }
152
+
153
+ before(:each) { db.execute("INSERT INTO test (age) VALUES (?)", 29.2) }
154
+
155
+ it "is inferred correctly" do
156
+ tuple[:age].should == 29.2 # sqlite type affinity
157
+ end
158
+ end
159
+
160
+ context "against a text field" do
161
+ let(:table) { "CREATE TABLE test (name text)" }
162
+
163
+ before(:each) { db.execute("INSERT INTO test (name) VALUES (?)", 27.4) }
164
+
165
+ it "is inferred correctly" do
166
+ tuple[:name].should == "27.4"
167
+ end
168
+ end
169
+
170
+ context "against an blob field" do
171
+ let(:table) { "CREATE TABLE test (salt blob)" }
172
+
173
+ before(:each) { db.execute("INSERT INTO test (salt) VALUES (?)", 19.2) }
174
+
175
+ it "is inferred correctly" do
176
+ tuple[:salt].should == "19.2"
177
+ end
178
+ end
179
+ end
180
+
181
+ describe "Boolean param" do
182
+ context "against an boolean (i.e. numeric) field" do
183
+ context "when it is true" do
184
+ let(:table) { "CREATE TABLE test (rad boolean)" }
185
+
186
+ before(:each) { db.execute("INSERT INTO test (rad) VALUES (?)", true) }
187
+
188
+ it "is inferred as integer 1" do
189
+ tuple[:rad].should == 1
190
+ end
191
+ end
192
+
193
+ context "when it is false" do
194
+ let(:table) { "CREATE TABLE test (rad boolean)" }
195
+
196
+ before(:each) { db.execute("INSERT INTO test (rad) VALUES (?)", false) }
197
+
198
+ it "is inferred as integer 0" do
199
+ tuple[:rad].should == 0
200
+ end
201
+ end
202
+ end
203
+ end
204
+
205
+ describe "BigDecimal param" do
206
+ context "against an float field" do
207
+ let(:table) { "CREATE TABLE test (score float)" }
208
+
209
+ before(:each) { db.execute("INSERT INTO test (score) VALUES (?)", BigDecimal("56.4")) }
210
+
211
+ it "is inferred correctly" do
212
+ tuple[:score].should == 56.4
213
+ end
214
+ end
215
+
216
+ context "against an integer field" do
217
+ let(:table) { "CREATE TABLE test (age integer)" }
218
+
219
+ before(:each) { db.execute("INSERT INTO test (age) VALUES (?)", BigDecimal("29.2")) }
220
+
221
+ it "is inferred correctly" do
222
+ tuple[:age].should == 29.2 # sqlite type affinity
223
+ end
224
+ end
225
+
226
+ context "against a text field" do
227
+ let(:table) { "CREATE TABLE test (name text)" }
228
+
229
+ before(:each) { db.execute("INSERT INTO test (name) VALUES (?)", BigDecimal("27.4")) }
230
+
231
+ it "is inferred correctly" do
232
+ tuple[:name].should == "0.274E2"
233
+ end
234
+ end
235
+
236
+ context "against an blob field" do
237
+ let(:table) { "CREATE TABLE test (salt blob)" }
238
+
239
+ before(:each) { db.execute("INSERT INTO test (salt) VALUES (?)", BigDecimal("27.4")) }
240
+
241
+ it "is inferred correctly" do
242
+ tuple[:salt].should == "0.274E2"
243
+ end
244
+ end
245
+ end
246
+
247
+ describe "Date param" do
248
+ context "against an date (numeric) field" do
249
+ let(:table) { "CREATE TABLE test (dob date)" }
250
+
251
+ before(:each) { db.execute("INSERT INTO test (dob) VALUES (?)", Date.new(1983, 5, 3)) }
252
+
253
+ it "is inferred correctly" do
254
+ tuple[:dob].should == "1983-05-03"
255
+ end
256
+ end
257
+ end
258
+
259
+ describe "Time param" do
260
+ context "against an datetime (numeric) field" do
261
+ let(:table) { "CREATE TABLE test (created_at datetime)" }
262
+
263
+ before(:each) do
264
+ db.execute(
265
+ "INSERT INTO test (created_at) VALUES (?)",
266
+ Time.new(1983, 5, 3, 7, 15, 43)
267
+ )
268
+ end
269
+
270
+ it "is inferred correctly" do
271
+ tuple[:created_at].should == "1983-05-03 07:15:43"
272
+ end
273
+
274
+ it "is parseable by sqlite" do
275
+ tuple
276
+ db.execute("SELECT date(created_at, '+1 day') FROM test").first_value.should ==
277
+ "1983-05-04"
278
+ end
279
+ end
280
+ end
281
+
282
+ describe "DateTime param" do
283
+ context "against an datetime (numeric) field" do
284
+ let(:table) { "CREATE TABLE test (created_at datetime)" }
285
+
286
+ before(:each) do
287
+ db.execute(
288
+ "INSERT INTO test (created_at) VALUES (?)",
289
+ DateTime.new(1983, 5, 3, 7, 15, 43)
290
+ )
291
+ end
292
+
293
+ it "is inferred correctly" do
294
+ tuple[:created_at].should == "1983-05-03 07:15:43"
295
+ end
296
+
297
+ it "is parseable by sqlite" do
298
+ tuple
299
+ db.execute("SELECT date(created_at, '+1 day') FROM test").first_value.should ==
300
+ "1983-05-04"
301
+ end
302
+ end
303
+ end
304
+
305
+ describe "multiple params" do
306
+ let(:table) { "CREATE TABLE test (name text, age integer)" }
307
+
308
+ before(:each) do
309
+ db.execute("INSERT INTO test (name, age) VALUES (?, ?)", "bob", 27)
310
+ end
311
+
312
+ it "binds them all" do
313
+ tuple.should == {name: "bob", age: 27}
314
+ end
315
+ end
316
+ end