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.
- data/.gitignore +19 -0
- data/.rspec +1 -0
- data/Gemfile +9 -0
- data/LICENSE +22 -0
- data/README.md +89 -0
- data/Rakefile +14 -0
- data/ext/rdo_sqlite/driver.c +99 -0
- data/ext/rdo_sqlite/driver.h +18 -0
- data/ext/rdo_sqlite/extconf.rb +23 -0
- data/ext/rdo_sqlite/macros.h +193 -0
- data/ext/rdo_sqlite/rdo_sqlite.c +15 -0
- data/ext/rdo_sqlite/statements.c +269 -0
- data/ext/rdo_sqlite/statements.h +15 -0
- data/lib/rdo/sqlite/driver.rb +23 -0
- data/lib/rdo/sqlite/version.rb +12 -0
- data/lib/rdo/sqlite.rb +16 -0
- data/lib/rdo-sqlite.rb +1 -0
- data/rdo-sqlite.gemspec +23 -0
- data/spec/spec_helper.rb +10 -0
- data/spec/sqlite/bind_params_spec.rb +316 -0
- data/spec/sqlite/driver_spec.rb +279 -0
- data/spec/sqlite/type_cast_spec.rb +48 -0
- metadata +121 -0
@@ -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
|
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"
|
data/rdo-sqlite.gemspec
ADDED
@@ -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
|
data/spec/spec_helper.rb
ADDED
@@ -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
|