swift 0.4.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/LICENSE +20 -0
- data/README.rdoc +236 -0
- data/Rakefile +40 -0
- data/VERSION +1 -0
- data/examples/async.rb +60 -0
- data/examples/db.rb +40 -0
- data/examples/scheme.rb +46 -0
- data/ext/extconf.rb +39 -0
- data/ext/swift.cc +756 -0
- data/lib/swift.rb +48 -0
- data/lib/swift/adapter.rb +120 -0
- data/lib/swift/attribute.rb +25 -0
- data/lib/swift/db.rb +39 -0
- data/lib/swift/header.rb +45 -0
- data/lib/swift/identity_map.rb +41 -0
- data/lib/swift/pool.rb +74 -0
- data/lib/swift/scheme.rb +70 -0
- data/lib/swift/type.rb +12 -0
- data/swift.gemspec +75 -0
- data/test/helper.rb +31 -0
- data/test/house-explode.jpg +0 -0
- data/test/test_adapter.rb +127 -0
- data/test/test_encoding.rb +40 -0
- data/test/test_identity_map.rb +17 -0
- data/test/test_io.rb +27 -0
- data/test/test_timestamps.rb +27 -0
- metadata +109 -0
data/ext/extconf.rb
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
#!/usr/bin/ruby
|
2
|
+
require 'mkmf'
|
3
|
+
|
4
|
+
Config::CONFIG['CC'] = 'g++'
|
5
|
+
Config::CONFIG['CPP'] = 'g++'
|
6
|
+
|
7
|
+
$CFLAGS = '-fPIC -O3'
|
8
|
+
|
9
|
+
def apt_install_hint pkg
|
10
|
+
"sudo apt-get install #{pkg}"
|
11
|
+
end
|
12
|
+
|
13
|
+
def library_installed? name, hint
|
14
|
+
if have_library(name)
|
15
|
+
true
|
16
|
+
else
|
17
|
+
$stderr.puts <<-ERROR
|
18
|
+
|
19
|
+
Unable to find required library: #{name}.
|
20
|
+
On debian systems, it can be installed as,
|
21
|
+
|
22
|
+
#{hint}
|
23
|
+
|
24
|
+
You may have to add the following ppa to your sources,
|
25
|
+
|
26
|
+
sudo add-apt-repository ppa:deepfryed
|
27
|
+
|
28
|
+
to install dbic++-dev and associated drivers dbic++-mysql or dbic++-pg
|
29
|
+
|
30
|
+
ERROR
|
31
|
+
false
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
exit 1 unless library_installed? 'pcrecpp', apt_install_hint('libpcre3-dev')
|
36
|
+
exit 1 unless library_installed? 'uuid', apt_install_hint('uuid-dev')
|
37
|
+
exit 1 unless library_installed? 'dbic++', apt_install_hint('dbic++-dev')
|
38
|
+
|
39
|
+
create_makefile 'swift'
|
data/ext/swift.cc
ADDED
@@ -0,0 +1,756 @@
|
|
1
|
+
#include <dbic++.h>
|
2
|
+
#include <ruby/ruby.h>
|
3
|
+
#include <ruby/io.h>
|
4
|
+
#include <time.h>
|
5
|
+
#include <unistd.h>
|
6
|
+
|
7
|
+
#define CONST_GET(scope, constant) rb_const_get(scope, rb_intern(constant))
|
8
|
+
|
9
|
+
static VALUE mSwift;
|
10
|
+
static VALUE cAdapter;
|
11
|
+
static VALUE cStatement;
|
12
|
+
static VALUE cResultSet;
|
13
|
+
static VALUE cPool;
|
14
|
+
static VALUE cRequest;
|
15
|
+
static VALUE cBigDecimal;
|
16
|
+
|
17
|
+
static VALUE eRuntimeError;
|
18
|
+
static VALUE eArgumentError;
|
19
|
+
static VALUE eStandardError;
|
20
|
+
static VALUE eConnectionError;
|
21
|
+
|
22
|
+
static VALUE fLoad;
|
23
|
+
static VALUE fStringify;
|
24
|
+
static VALUE fNew;
|
25
|
+
static VALUE fRead;
|
26
|
+
static VALUE fWrite;
|
27
|
+
|
28
|
+
size_t tzoffset;
|
29
|
+
char errstr[8192];
|
30
|
+
|
31
|
+
#define CSTRING(v) RSTRING_PTR(TYPE(v) == T_STRING ? v : rb_funcall(v, fStringify, 0))
|
32
|
+
#define OBJ2STRING(v) (TYPE(v) == T_STRING ? v : rb_funcall(v, fStringify, 0))
|
33
|
+
|
34
|
+
#define EXCEPTION(type) (dbi::ConnectionError &e) { \
|
35
|
+
snprintf(errstr, 4096, "%s", e.what()); \
|
36
|
+
rb_raise(eConnectionError, "%s : %s", type, errstr); \
|
37
|
+
} \
|
38
|
+
catch (dbi::Error &e) {\
|
39
|
+
snprintf(errstr, 4096, "%s", e.what()); \
|
40
|
+
rb_raise(eRuntimeError, "%s : %s", type, errstr); \
|
41
|
+
}
|
42
|
+
|
43
|
+
|
44
|
+
class IOStream : public dbi::IOStream {
|
45
|
+
private:
|
46
|
+
string empty, data;
|
47
|
+
VALUE stream;
|
48
|
+
public:
|
49
|
+
IOStream(VALUE s) {
|
50
|
+
stream = s;
|
51
|
+
}
|
52
|
+
string& read() {
|
53
|
+
VALUE response = rb_funcall(stream, fRead, 0);
|
54
|
+
if (response == Qnil)
|
55
|
+
return empty;
|
56
|
+
else {
|
57
|
+
if (TYPE(response) != T_STRING)
|
58
|
+
rb_raise(eArgumentError,
|
59
|
+
"Adapter#write can only process string data. You need to stringify values returned in the callback.");
|
60
|
+
data = string(RSTRING_PTR(response), RSTRING_LEN(response));
|
61
|
+
return data;
|
62
|
+
}
|
63
|
+
}
|
64
|
+
|
65
|
+
uint read(char *buffer, uint len) {
|
66
|
+
VALUE response = rb_funcall(stream, fRead, 1, INT2NUM(len));
|
67
|
+
if (response == Qnil)
|
68
|
+
return 0;
|
69
|
+
else {
|
70
|
+
len = len < RSTRING_LEN(response) ? len : RSTRING_LEN(response);
|
71
|
+
memcpy(buffer, RSTRING_PTR(response), len);
|
72
|
+
return len;
|
73
|
+
}
|
74
|
+
}
|
75
|
+
|
76
|
+
void write(const char *str) {
|
77
|
+
rb_funcall(stream, fWrite, 1, rb_str_new2(str));
|
78
|
+
}
|
79
|
+
void write(const char *str, ulong l) {
|
80
|
+
rb_funcall(stream, fWrite, 1, rb_str_new(str, l));
|
81
|
+
}
|
82
|
+
void truncate() {
|
83
|
+
data = "";
|
84
|
+
}
|
85
|
+
};
|
86
|
+
|
87
|
+
|
88
|
+
static dbi::Handle* DBI_HANDLE(VALUE self) {
|
89
|
+
dbi::Handle *h;
|
90
|
+
Data_Get_Struct(self, dbi::Handle, h);
|
91
|
+
if (!h) rb_raise(eRuntimeError, "Invalid object, did you forget to call #super ?");
|
92
|
+
return h;
|
93
|
+
}
|
94
|
+
|
95
|
+
static dbi::AbstractStatement* DBI_STATEMENT(VALUE self) {
|
96
|
+
dbi::AbstractStatement *st;
|
97
|
+
Data_Get_Struct(self, dbi::AbstractStatement, st);
|
98
|
+
if (!st) rb_raise(eRuntimeError, "Invalid object, did you forget to call #super ?");
|
99
|
+
return st;
|
100
|
+
}
|
101
|
+
|
102
|
+
static dbi::ConnectionPool* DBI_CPOOL(VALUE self) {
|
103
|
+
dbi::ConnectionPool *cp;
|
104
|
+
Data_Get_Struct(self, dbi::ConnectionPool, cp);
|
105
|
+
if (!cp) rb_raise(eRuntimeError, "Invalid object, did you forget to call #super ?");
|
106
|
+
return cp;
|
107
|
+
}
|
108
|
+
|
109
|
+
static dbi::Request* DBI_REQUEST(VALUE self) {
|
110
|
+
dbi::Request *r;
|
111
|
+
Data_Get_Struct(self, dbi::Request, r);
|
112
|
+
if (!r) rb_raise(eRuntimeError, "Invalid object, did you forget to call #super ?");
|
113
|
+
return r;
|
114
|
+
}
|
115
|
+
|
116
|
+
void static inline rb_extract_bind_params(int argc, VALUE* argv, std::vector<dbi::Param> &bind) {
|
117
|
+
for (int i = 0; i < argc; i++) {
|
118
|
+
VALUE arg = argv[i];
|
119
|
+
if (arg == Qnil)
|
120
|
+
bind.push_back(dbi::PARAM(dbi::null()));
|
121
|
+
else if (rb_obj_is_kind_of(arg, rb_cIO) == Qtrue) {
|
122
|
+
arg = rb_funcall(arg, fRead, 0);
|
123
|
+
bind.push_back(dbi::PARAM_BINARY((unsigned char*)RSTRING_PTR(arg), RSTRING_LEN(arg)));
|
124
|
+
}
|
125
|
+
else {
|
126
|
+
arg = OBJ2STRING(arg);
|
127
|
+
if (strcmp(rb_enc_get(arg)->name, "UTF-8") != 0)
|
128
|
+
arg = rb_str_encode(arg, rb_str_new2("UTF-8"), 0, Qnil);
|
129
|
+
bind.push_back(dbi::PARAM((unsigned char*)RSTRING_PTR(arg), RSTRING_LEN(arg)));
|
130
|
+
}
|
131
|
+
}
|
132
|
+
}
|
133
|
+
|
134
|
+
VALUE rb_swift_init(VALUE self, VALUE path) {
|
135
|
+
try { dbi::dbiInitialize(CSTRING(path)); } catch EXCEPTION("Swift#init");
|
136
|
+
return Qtrue;
|
137
|
+
}
|
138
|
+
|
139
|
+
static void free_connection(dbi::Handle *self) {
|
140
|
+
if (self) delete self;
|
141
|
+
}
|
142
|
+
|
143
|
+
int compute_tzoffset() {
|
144
|
+
struct tm tm;
|
145
|
+
memset(&tm, 0, sizeof(struct tm));
|
146
|
+
tm.tm_year = 70;
|
147
|
+
tm.tm_mday = 1;
|
148
|
+
return -1 * mktime(&tm);
|
149
|
+
}
|
150
|
+
|
151
|
+
VALUE rb_adapter_alloc(VALUE klass) {
|
152
|
+
dbi::Handle *h = 0;
|
153
|
+
return Data_Wrap_Struct(klass, 0, free_connection, h);
|
154
|
+
}
|
155
|
+
|
156
|
+
VALUE rb_adapter_init(VALUE self, VALUE opts) {
|
157
|
+
VALUE db = rb_hash_aref(opts, ID2SYM(rb_intern("db")));
|
158
|
+
VALUE host = rb_hash_aref(opts, ID2SYM(rb_intern("host")));
|
159
|
+
VALUE port = rb_hash_aref(opts, ID2SYM(rb_intern("port")));
|
160
|
+
VALUE user = rb_hash_aref(opts, ID2SYM(rb_intern("user")));
|
161
|
+
VALUE driver = rb_hash_aref(opts, ID2SYM(rb_intern("driver")));
|
162
|
+
VALUE password = rb_hash_aref(opts, ID2SYM(rb_intern("password")));
|
163
|
+
|
164
|
+
if (NIL_P(db)) rb_raise(eArgumentError, "Adapter#new called without :db");
|
165
|
+
if (NIL_P(driver)) rb_raise(eArgumentError, "Adapter#new called without :driver");
|
166
|
+
|
167
|
+
host = NIL_P(host) ? rb_str_new2("") : host;
|
168
|
+
port = NIL_P(port) ? rb_str_new2("") : port;
|
169
|
+
user = NIL_P(user) ? rb_str_new2(getlogin()) : user;
|
170
|
+
password = NIL_P(password) ? rb_str_new2("") : password;
|
171
|
+
|
172
|
+
try {
|
173
|
+
DATA_PTR(self) = new dbi::Handle(
|
174
|
+
CSTRING(driver), CSTRING(user), CSTRING(password),
|
175
|
+
CSTRING(db), CSTRING(host), CSTRING(port)
|
176
|
+
);
|
177
|
+
} catch EXCEPTION("Adapter#new");
|
178
|
+
|
179
|
+
rb_iv_set(self, "@options", opts);
|
180
|
+
return Qnil;
|
181
|
+
}
|
182
|
+
|
183
|
+
VALUE rb_adapter_close(VALUE self) {
|
184
|
+
dbi::Handle *h = DBI_HANDLE(self);
|
185
|
+
try {
|
186
|
+
h->close();
|
187
|
+
} catch EXCEPTION("Adapter#close");
|
188
|
+
return Qtrue;
|
189
|
+
}
|
190
|
+
|
191
|
+
static void free_statement(dbi::AbstractStatement *self) {
|
192
|
+
if (self) {
|
193
|
+
self->cleanup();
|
194
|
+
delete self;
|
195
|
+
}
|
196
|
+
}
|
197
|
+
|
198
|
+
static VALUE rb_adapter_prepare(int argc, VALUE *argv, VALUE self) {
|
199
|
+
VALUE sql, scheme, prepared;
|
200
|
+
dbi::Handle *h = DBI_HANDLE(self);
|
201
|
+
|
202
|
+
rb_scan_args(argc, argv, "11", &scheme, &sql);
|
203
|
+
if (TYPE(scheme) != T_CLASS) {
|
204
|
+
sql = scheme;
|
205
|
+
scheme = Qnil;
|
206
|
+
}
|
207
|
+
|
208
|
+
try {
|
209
|
+
dbi::AbstractStatement *st = h->conn()->prepare(CSTRING(sql));
|
210
|
+
prepared = Data_Wrap_Struct(cStatement, 0, free_statement, st);
|
211
|
+
rb_iv_set(prepared, "@scheme", scheme);
|
212
|
+
rb_iv_set(prepared, "@adapter", self);
|
213
|
+
} catch EXCEPTION("Adapter#prepare");
|
214
|
+
|
215
|
+
return prepared;
|
216
|
+
}
|
217
|
+
|
218
|
+
static VALUE rb_statement_each(VALUE self);
|
219
|
+
VALUE rb_statement_execute(int argc, VALUE *argv, VALUE self);
|
220
|
+
|
221
|
+
VALUE rb_adapter_execute(int argc, VALUE *argv, VALUE self) {
|
222
|
+
uint rows = 0;
|
223
|
+
VALUE result = 0;
|
224
|
+
dbi::Handle *h = DBI_HANDLE(self);
|
225
|
+
if (argc == 0 || NIL_P(argv[0]))
|
226
|
+
rb_raise(eArgumentError, "Adapter#execute called without a SQL command");
|
227
|
+
try {
|
228
|
+
if (argc == 1)
|
229
|
+
rows = h->execute(CSTRING(argv[0]));
|
230
|
+
else {
|
231
|
+
dbi::ResultRow bind;
|
232
|
+
rb_extract_bind_params(argc-1, argv+1, bind);
|
233
|
+
if (dbi::_trace)
|
234
|
+
dbi::logMessage(dbi::_trace_fd, dbi::formatParams(CSTRING(argv[0]), bind));
|
235
|
+
rows = h->conn()->execute(CSTRING(argv[0]), bind);
|
236
|
+
}
|
237
|
+
if (rb_block_given_p()) {
|
238
|
+
dbi::AbstractResultSet *rs = h->results();
|
239
|
+
result = Data_Wrap_Struct(cResultSet, 0, free_statement, rs);
|
240
|
+
rb_iv_set(result, "@adapter", self);
|
241
|
+
}
|
242
|
+
} catch EXCEPTION("Adapter#execute");
|
243
|
+
|
244
|
+
return result ? rb_statement_each(result) : INT2NUM(rows);
|
245
|
+
}
|
246
|
+
|
247
|
+
VALUE rb_adapter_results(VALUE self) {
|
248
|
+
VALUE result = Qnil;
|
249
|
+
dbi::Handle *h = DBI_HANDLE(self);
|
250
|
+
try {
|
251
|
+
dbi::AbstractResultSet *rs = h->results();
|
252
|
+
result = Data_Wrap_Struct(cResultSet, 0, free_statement, rs);
|
253
|
+
rb_iv_set(result, "@adapter", self);
|
254
|
+
} catch EXCEPTION("Adapter#results");
|
255
|
+
return result;
|
256
|
+
}
|
257
|
+
|
258
|
+
VALUE rb_adapter_begin(int argc, VALUE *argv, VALUE self) {
|
259
|
+
dbi::Handle *h = DBI_HANDLE(self);
|
260
|
+
VALUE save;
|
261
|
+
rb_scan_args(argc, argv, "01", &save);
|
262
|
+
try { NIL_P(save) ? h->begin() : h->begin(CSTRING(save)); } catch EXCEPTION("Adapter#begin");
|
263
|
+
}
|
264
|
+
|
265
|
+
VALUE rb_adapter_commit(int argc, VALUE *argv, VALUE self) {
|
266
|
+
dbi::Handle *h = DBI_HANDLE(self);
|
267
|
+
VALUE save;
|
268
|
+
rb_scan_args(argc, argv, "01", &save);
|
269
|
+
try { NIL_P(save) ? h->commit() : h->commit(CSTRING(save)); } catch EXCEPTION("Adapter#commit");
|
270
|
+
}
|
271
|
+
|
272
|
+
VALUE rb_adapter_rollback(int argc, VALUE *argv, VALUE self) {
|
273
|
+
dbi::Handle *h = DBI_HANDLE(self);
|
274
|
+
VALUE save_point;
|
275
|
+
rb_scan_args(argc, argv, "01", &save_point);
|
276
|
+
try { NIL_P(save_point) ? h->rollback() : h->rollback(CSTRING(save_point)); } catch EXCEPTION("Adapter#rollback");
|
277
|
+
}
|
278
|
+
|
279
|
+
VALUE rb_adapter_transaction(int argc, VALUE *argv, VALUE self) {
|
280
|
+
int status;
|
281
|
+
VALUE sp, block;
|
282
|
+
rb_scan_args(argc, argv, "01&", &sp, &block);
|
283
|
+
|
284
|
+
std::string save_point = NIL_P(sp) ? "SP" + dbi::generateCompactUUID() : CSTRING(sp);
|
285
|
+
dbi::Handle *h = DBI_HANDLE(self);
|
286
|
+
|
287
|
+
try {
|
288
|
+
h->begin(save_point);
|
289
|
+
rb_protect(rb_yield, self, &status);
|
290
|
+
if (status == 0 && h->transactions().back() == save_point) {
|
291
|
+
h->commit(save_point);
|
292
|
+
}
|
293
|
+
else if (status != 0) {
|
294
|
+
if (h->transactions().back() == save_point) h->rollback(save_point);
|
295
|
+
rb_jump_tag(status);
|
296
|
+
}
|
297
|
+
} catch EXCEPTION("Adapter#transaction{}");
|
298
|
+
}
|
299
|
+
|
300
|
+
VALUE rb_adapter_write(int argc, VALUE *argv, VALUE self) {
|
301
|
+
ulong rows = 0;
|
302
|
+
VALUE stream, table, fields;
|
303
|
+
|
304
|
+
rb_scan_args(argc, argv, "30", &table, &fields, &stream);
|
305
|
+
if (TYPE(stream) != T_STRING && !rb_respond_to(stream, fRead))
|
306
|
+
rb_raise(eArgumentError, "Adapter#write: stream should be a string or kind_of?(IO)");
|
307
|
+
if (TYPE(fields) != T_ARRAY)
|
308
|
+
rb_raise(eArgumentError, "Adapter#write: fields should be an array of string values");
|
309
|
+
|
310
|
+
dbi::Handle *h = DBI_HANDLE(self);
|
311
|
+
try {
|
312
|
+
dbi::ResultRow rfields;
|
313
|
+
for (int n = 0; n < RARRAY_LEN(fields); n++) {
|
314
|
+
VALUE f = rb_ary_entry(fields, n);
|
315
|
+
rfields << std::string(RSTRING_PTR(f), RSTRING_LEN(f));
|
316
|
+
}
|
317
|
+
// This is just for the friggin mysql support - mysql does not like a statement close
|
318
|
+
// command being send on a handle when the writing has started.
|
319
|
+
rb_gc();
|
320
|
+
if (TYPE(stream) == T_STRING) {
|
321
|
+
dbi::IOStream io(RSTRING_PTR(stream), RSTRING_LEN(stream));
|
322
|
+
rows = h->copyIn(RSTRING_PTR(table), rfields, &io);
|
323
|
+
}
|
324
|
+
else {
|
325
|
+
IOStream io(stream);
|
326
|
+
rows = h->copyIn(RSTRING_PTR(table), rfields, &io);
|
327
|
+
}
|
328
|
+
} catch EXCEPTION("Adapter#write");
|
329
|
+
|
330
|
+
return ULONG2NUM(rows);
|
331
|
+
}
|
332
|
+
|
333
|
+
VALUE rb_adapter_timezone(int argc, VALUE *argv, VALUE self) {
|
334
|
+
VALUE name, tzhour, tzmin;
|
335
|
+
if (argc == 1)
|
336
|
+
rb_scan_args(argc, argv, "10", &name);
|
337
|
+
else {
|
338
|
+
rb_scan_args(argc, argv, "20", &tzhour, &tzmin);
|
339
|
+
if (TYPE(tzhour) != T_FIXNUM && TYPE(tzmin) != T_FIXNUM)
|
340
|
+
rb_raise(eArgumentError, "Adapter#timezone: tzhour and tzmin must be Fixnum");
|
341
|
+
}
|
342
|
+
dbi::Handle *h = DBI_HANDLE(self);
|
343
|
+
try {
|
344
|
+
if (argc == 1)
|
345
|
+
h->setTimeZone(CSTRING(name));
|
346
|
+
else
|
347
|
+
h->setTimeZoneOffset(NUM2INT(tzhour), NUM2INT(tzmin));
|
348
|
+
} catch EXCEPTION("Adapter#timezone");
|
349
|
+
return Qtrue;
|
350
|
+
}
|
351
|
+
|
352
|
+
VALUE rb_statement_alloc(VALUE klass) {
|
353
|
+
dbi::AbstractStatement *st = 0;
|
354
|
+
return Data_Wrap_Struct(klass, 0, free_statement, st);
|
355
|
+
}
|
356
|
+
|
357
|
+
VALUE rb_statement_init(VALUE self, VALUE hl, VALUE sql) {
|
358
|
+
dbi::Handle *h = DBI_HANDLE(hl);
|
359
|
+
|
360
|
+
if (NIL_P(hl) || !h)
|
361
|
+
rb_raise(eArgumentError, "Statement#new called without an Adapter instance");
|
362
|
+
if (NIL_P(sql))
|
363
|
+
rb_raise(eArgumentError, "Statement#new called without a SQL command");
|
364
|
+
|
365
|
+
try {
|
366
|
+
DATA_PTR(self) = h->conn()->prepare(CSTRING(sql));
|
367
|
+
} catch EXCEPTION("Statement#new");
|
368
|
+
|
369
|
+
return Qnil;
|
370
|
+
}
|
371
|
+
|
372
|
+
VALUE rb_statement_execute(int argc, VALUE *argv, VALUE self) {
|
373
|
+
dbi::AbstractStatement *st = DBI_STATEMENT(self);
|
374
|
+
try {
|
375
|
+
if (argc == 0) {
|
376
|
+
dbi::ResultRow params;
|
377
|
+
if (dbi::_trace)
|
378
|
+
dbi::logMessage(dbi::_trace_fd, dbi::formatParams(st->command(), params));
|
379
|
+
st->execute();
|
380
|
+
}
|
381
|
+
else {
|
382
|
+
dbi::ResultRow bind;
|
383
|
+
rb_extract_bind_params(argc, argv, bind);
|
384
|
+
if (dbi::_trace)
|
385
|
+
dbi::logMessage(dbi::_trace_fd, dbi::formatParams(st->command(), bind));
|
386
|
+
st->execute(bind);
|
387
|
+
}
|
388
|
+
} catch EXCEPTION("Statement#execute");
|
389
|
+
|
390
|
+
if (rb_block_given_p()) return rb_statement_each(self);
|
391
|
+
return self;
|
392
|
+
}
|
393
|
+
|
394
|
+
VALUE rb_statement_finish(VALUE self) {
|
395
|
+
dbi::AbstractStatement *st = DBI_STATEMENT(self);
|
396
|
+
try {
|
397
|
+
st->finish();
|
398
|
+
} catch EXCEPTION("Statement#finish");
|
399
|
+
}
|
400
|
+
|
401
|
+
VALUE rb_statement_rows(VALUE self) {
|
402
|
+
uint rows;
|
403
|
+
dbi::AbstractStatement *st = DBI_STATEMENT(self);
|
404
|
+
try { rows = st->rows(); } catch EXCEPTION("Statement#rows");
|
405
|
+
return INT2NUM(rows);
|
406
|
+
}
|
407
|
+
|
408
|
+
VALUE rb_statement_insert_id(VALUE self) {
|
409
|
+
dbi::AbstractStatement *st = DBI_STATEMENT(self);
|
410
|
+
VALUE insert_id = Qnil;
|
411
|
+
try {
|
412
|
+
if (st->rows() > 0) insert_id = LONG2NUM(st->lastInsertID());
|
413
|
+
} catch EXCEPTION("Statement#insert_id");
|
414
|
+
|
415
|
+
return insert_id;
|
416
|
+
}
|
417
|
+
|
418
|
+
VALUE rb_field_typecast(VALUE adapter, int type, const char *data, ulong len) {
|
419
|
+
time_t epoch, offset;
|
420
|
+
struct tm tm;
|
421
|
+
|
422
|
+
char datetime[512], tzsign = ' ';
|
423
|
+
int hour = 0, min = 0, sec = 0, tzhour = 0, tzmin = 0;
|
424
|
+
double usec = 0;
|
425
|
+
|
426
|
+
switch(type) {
|
427
|
+
case DBI_TYPE_BOOLEAN:
|
428
|
+
return strcmp(data, "t") == 0 || strcmp(data, "1") == 0 ? Qtrue : Qfalse;
|
429
|
+
case DBI_TYPE_INT:
|
430
|
+
return rb_cstr2inum(data, 10);
|
431
|
+
case DBI_TYPE_BLOB:
|
432
|
+
return rb_str_new(data, len);
|
433
|
+
// forcing UTF8 convention here - do we really care about people using non utf8
|
434
|
+
// client encodings and databases ?
|
435
|
+
case DBI_TYPE_TEXT:
|
436
|
+
return rb_enc_str_new(data, len, rb_utf8_encoding());
|
437
|
+
case DBI_TYPE_TIME:
|
438
|
+
// if timestamp field has usec resolution, parse it.
|
439
|
+
if (strlen(data) > 19 && data[19] == '.') {
|
440
|
+
sscanf(data, "%s %d:%d:%d%lf%c%02d:%02d",
|
441
|
+
datetime, &hour, &min, &sec, &usec, &tzsign, &tzhour, &tzmin);
|
442
|
+
}
|
443
|
+
else {
|
444
|
+
sscanf(data, "%s %d:%d:%d%c%02d:%02d",
|
445
|
+
datetime, &hour, &min, &sec, &tzsign, &tzhour, &tzmin);
|
446
|
+
}
|
447
|
+
sprintf(datetime, "%s %02d:%02d:%02d", datetime, hour, min, sec);
|
448
|
+
memset(&tm, 0, sizeof(struct tm));
|
449
|
+
if (strptime(datetime, "%F %T", &tm)) {
|
450
|
+
offset = tzoffset;
|
451
|
+
epoch = mktime(&tm);
|
452
|
+
if (tzsign == '+' || tzsign == '-') {
|
453
|
+
offset += tzsign == '+' ?
|
454
|
+
(time_t)tzhour * -3600 + (time_t)tzmin * -60
|
455
|
+
: (time_t)tzhour * 3600 + (time_t)tzmin * 60;
|
456
|
+
}
|
457
|
+
else {
|
458
|
+
VALUE database_offset = rb_iv_get(adapter, "@tzoffset");
|
459
|
+
offset -= NIL_P(database_offset) ? 0 : NUM2ULONG(database_offset);
|
460
|
+
}
|
461
|
+
return rb_time_new(epoch + offset, usec*1000000);
|
462
|
+
}
|
463
|
+
else {
|
464
|
+
fprintf(stderr, "typecast failed to parse date: %s\n", data);
|
465
|
+
return rb_str_new(data, len);
|
466
|
+
}
|
467
|
+
// does bigdecimal solve all floating point woes ? dunno :)
|
468
|
+
case DBI_TYPE_NUMERIC:
|
469
|
+
return rb_funcall(cBigDecimal, fNew, 1, rb_str_new2(data));
|
470
|
+
case DBI_TYPE_FLOAT:
|
471
|
+
return rb_float_new(atof(data));
|
472
|
+
}
|
473
|
+
}
|
474
|
+
|
475
|
+
static VALUE rb_statement_each(VALUE self) {
|
476
|
+
uint r, c;
|
477
|
+
ulong len;
|
478
|
+
const char *data;
|
479
|
+
|
480
|
+
dbi::AbstractStatement *st = DBI_STATEMENT(self);
|
481
|
+
VALUE scheme = rb_iv_get(self, "@scheme");
|
482
|
+
VALUE adapter = rb_iv_get(self, "@adapter");
|
483
|
+
|
484
|
+
try {
|
485
|
+
VALUE attrs = rb_ary_new();
|
486
|
+
std::vector<string> fields = st->fields();
|
487
|
+
std::vector<int> types = st->types();
|
488
|
+
for (c = 0; c < fields.size(); c++) {
|
489
|
+
rb_ary_push(attrs, ID2SYM(rb_intern(fields[c].c_str())));
|
490
|
+
}
|
491
|
+
|
492
|
+
// TODO Code duplication
|
493
|
+
// Avoiding a rb_yield(NIL_P(scheme) ? row : rb_funcall(scheme, load, row))
|
494
|
+
// Maybe an inline method will help ?
|
495
|
+
st->seek(0);
|
496
|
+
tzoffset = compute_tzoffset();
|
497
|
+
if (NIL_P(scheme) || scheme == Qnil) {
|
498
|
+
for (r = 0; r < st->rows(); r++) {
|
499
|
+
VALUE row = rb_hash_new();
|
500
|
+
for (c = 0; c < st->columns(); c++) {
|
501
|
+
data = (const char*)st->fetchValue(r,c, &len);
|
502
|
+
if (data)
|
503
|
+
rb_hash_aset(row, rb_ary_entry(attrs, c), rb_field_typecast(adapter, types[c], data, len));
|
504
|
+
else
|
505
|
+
rb_hash_aset(row, rb_ary_entry(attrs, c), Qnil);
|
506
|
+
}
|
507
|
+
rb_yield(row);
|
508
|
+
}
|
509
|
+
}
|
510
|
+
else {
|
511
|
+
for (r = 0; r < st->rows(); r++) {
|
512
|
+
VALUE row = rb_hash_new();
|
513
|
+
for (c = 0; c < st->columns(); c++) {
|
514
|
+
data = (const char*)st->fetchValue(r,c, &len);
|
515
|
+
if (data)
|
516
|
+
rb_hash_aset(row, rb_ary_entry(attrs, c), rb_field_typecast(adapter, types[c], data, len));
|
517
|
+
else
|
518
|
+
rb_hash_aset(row, rb_ary_entry(attrs, c), Qnil);
|
519
|
+
}
|
520
|
+
rb_yield(rb_funcall(scheme, fLoad, 1, row));
|
521
|
+
}
|
522
|
+
}
|
523
|
+
} catch EXCEPTION("Statment#each");
|
524
|
+
return Qnil;
|
525
|
+
}
|
526
|
+
|
527
|
+
VALUE rb_statement_fetchrow(VALUE self) {
|
528
|
+
const char *data;
|
529
|
+
uint r, c;
|
530
|
+
ulong len;
|
531
|
+
VALUE row = Qnil;
|
532
|
+
dbi::AbstractStatement *st = DBI_STATEMENT(self);
|
533
|
+
try {
|
534
|
+
r = st->currentRow();
|
535
|
+
if (r < st->rows()) {
|
536
|
+
row = rb_ary_new();
|
537
|
+
for (c = 0; c < st->columns(); c++) {
|
538
|
+
data = (const char*)st->fetchValue(r, c, &len);
|
539
|
+
rb_ary_push(row, data ? rb_str_new(data, len) : Qnil);
|
540
|
+
}
|
541
|
+
}
|
542
|
+
} catch EXCEPTION("Statement#fetchrow");
|
543
|
+
|
544
|
+
return row;
|
545
|
+
}
|
546
|
+
|
547
|
+
VALUE rb_statement_rewind(VALUE self) {
|
548
|
+
dbi::AbstractStatement *st = DBI_STATEMENT(self);
|
549
|
+
try { st->rewind(); } catch EXCEPTION("Statement#rewind");
|
550
|
+
return Qnil;
|
551
|
+
}
|
552
|
+
|
553
|
+
VALUE rb_swift_trace(int argc, VALUE *argv, VALUE self) {
|
554
|
+
// by default log all messages to stderr.
|
555
|
+
int fd = 2;
|
556
|
+
rb_io_t *fptr;
|
557
|
+
VALUE flag, io;
|
558
|
+
|
559
|
+
rb_scan_args(argc, argv, "11", &flag, &io);
|
560
|
+
|
561
|
+
if (TYPE(flag) != T_TRUE && TYPE(flag) != T_FALSE)
|
562
|
+
rb_raise(eArgumentError, "Swift#trace expects a boolean flag, got %s", CSTRING(flag));
|
563
|
+
|
564
|
+
if (!NIL_P(io)) {
|
565
|
+
GetOpenFile(rb_convert_type(io, T_FILE, "IO", "to_io"), fptr);
|
566
|
+
fd = fptr->fd;
|
567
|
+
}
|
568
|
+
|
569
|
+
dbi::trace(flag == Qtrue ? true : false, fd);
|
570
|
+
}
|
571
|
+
|
572
|
+
VALUE rb_adapter_dup(VALUE self) {
|
573
|
+
rb_raise(eRuntimeError, "Adapter#dup or Adapter#clone is not allowed.");
|
574
|
+
}
|
575
|
+
|
576
|
+
VALUE rb_statement_dup(VALUE self) {
|
577
|
+
rb_raise(eRuntimeError, "Statement#dup or Statement#clone is not allowed.");
|
578
|
+
}
|
579
|
+
|
580
|
+
static void free_request(dbi::Request *self) {
|
581
|
+
if(self) delete self;
|
582
|
+
}
|
583
|
+
|
584
|
+
VALUE rb_request_alloc(VALUE klass) {
|
585
|
+
dbi::Request *r = 0;
|
586
|
+
return Data_Wrap_Struct(klass, 0, free_request, r);
|
587
|
+
}
|
588
|
+
|
589
|
+
static void free_cpool(dbi::ConnectionPool *self) {
|
590
|
+
if (self) delete self;
|
591
|
+
}
|
592
|
+
|
593
|
+
VALUE rb_cpool_alloc(VALUE klass) {
|
594
|
+
dbi::ConnectionPool *c = 0;
|
595
|
+
return Data_Wrap_Struct(klass, 0, free_cpool, c);
|
596
|
+
}
|
597
|
+
|
598
|
+
VALUE rb_cpool_init(VALUE self, VALUE n, VALUE opts) {
|
599
|
+
VALUE db = rb_hash_aref(opts, ID2SYM(rb_intern("db")));
|
600
|
+
VALUE host = rb_hash_aref(opts, ID2SYM(rb_intern("host")));
|
601
|
+
VALUE port = rb_hash_aref(opts, ID2SYM(rb_intern("port")));
|
602
|
+
VALUE user = rb_hash_aref(opts, ID2SYM(rb_intern("user")));
|
603
|
+
VALUE driver = rb_hash_aref(opts, ID2SYM(rb_intern("driver")));
|
604
|
+
VALUE password = rb_hash_aref(opts, ID2SYM(rb_intern("password")));
|
605
|
+
|
606
|
+
if (NIL_P(db)) rb_raise(eArgumentError, "ConnectionPool#new called without :db");
|
607
|
+
if (NIL_P(driver)) rb_raise(eArgumentError, "ConnectionPool#new called without :driver");
|
608
|
+
|
609
|
+
host = NIL_P(host) ? rb_str_new2("") : host;
|
610
|
+
port = NIL_P(port) ? rb_str_new2("") : port;
|
611
|
+
user = NIL_P(user) ? rb_str_new2(getlogin()) : user;
|
612
|
+
password = NIL_P(password) ? rb_str_new2("") : password;
|
613
|
+
|
614
|
+
if (NUM2INT(n) < 1) rb_raise(eArgumentError, "ConnectionPool#new called with invalid pool size.");
|
615
|
+
|
616
|
+
try {
|
617
|
+
DATA_PTR(self) = new dbi::ConnectionPool(
|
618
|
+
NUM2INT(n), CSTRING(driver), CSTRING(user), CSTRING(password), CSTRING(db), CSTRING(host), CSTRING(port)
|
619
|
+
);
|
620
|
+
} catch EXCEPTION("ConnectionPool#new");
|
621
|
+
|
622
|
+
return Qnil;
|
623
|
+
}
|
624
|
+
|
625
|
+
void rb_cpool_callback(dbi::AbstractResultSet *rs) {
|
626
|
+
VALUE callback = (VALUE)rs->context;
|
627
|
+
// NOTE: ResultSet will be free'd by the underlying connection pool dispatcher.
|
628
|
+
if (!NIL_P(callback))
|
629
|
+
rb_proc_call(callback, rb_ary_new3(1, Data_Wrap_Struct(cResultSet, 0, 0, rs)));
|
630
|
+
}
|
631
|
+
|
632
|
+
VALUE rb_cpool_execute(int argc, VALUE *argv, VALUE self) {
|
633
|
+
dbi::ConnectionPool *cp = DBI_CPOOL(self);
|
634
|
+
int n;
|
635
|
+
VALUE sql;
|
636
|
+
VALUE args;
|
637
|
+
VALUE callback;
|
638
|
+
VALUE request = Qnil;
|
639
|
+
|
640
|
+
rb_scan_args(argc, argv, "1*&", &sql, &args, &callback);
|
641
|
+
try {
|
642
|
+
dbi::ResultRow bind;
|
643
|
+
for (n = 0; n < RARRAY_LEN(args); n++) {
|
644
|
+
VALUE arg = rb_ary_entry(args, n);
|
645
|
+
if (arg == Qnil)
|
646
|
+
bind.push_back(dbi::PARAM(dbi::null()));
|
647
|
+
else if (rb_obj_is_kind_of(arg, rb_cIO) == Qtrue) {
|
648
|
+
arg = rb_funcall(arg, fRead, 0);
|
649
|
+
bind.push_back(dbi::PARAM_BINARY((unsigned char*)RSTRING_PTR(arg), RSTRING_LEN(arg)));
|
650
|
+
}
|
651
|
+
else {
|
652
|
+
arg = OBJ2STRING(arg);
|
653
|
+
if (strcmp(rb_enc_get(arg)->name, "UTF-8") != 0)
|
654
|
+
arg = rb_str_encode(arg, rb_str_new2("UTF-8"), 0, Qnil);
|
655
|
+
bind.push_back(dbi::PARAM((unsigned char*)RSTRING_PTR(arg), RSTRING_LEN(arg)));
|
656
|
+
}
|
657
|
+
}
|
658
|
+
// TODO GC mark callback.
|
659
|
+
request = rb_request_alloc(cRequest);
|
660
|
+
DATA_PTR(request) = cp->execute(CSTRING(sql), bind, rb_cpool_callback, (void*)callback);
|
661
|
+
} catch EXCEPTION("ConnectionPool#execute");
|
662
|
+
|
663
|
+
return DATA_PTR(request) ? request : Qnil;
|
664
|
+
}
|
665
|
+
|
666
|
+
VALUE rb_request_socket(VALUE self) {
|
667
|
+
dbi::Request *r = DBI_REQUEST(self);
|
668
|
+
VALUE fd = Qnil;
|
669
|
+
try {
|
670
|
+
fd = INT2NUM(r->socket());
|
671
|
+
} catch EXCEPTION("Request#socket");
|
672
|
+
return fd;
|
673
|
+
}
|
674
|
+
|
675
|
+
VALUE rb_request_process(VALUE self) {
|
676
|
+
VALUE rc = Qnil;
|
677
|
+
dbi::Request *r = DBI_REQUEST(self);
|
678
|
+
|
679
|
+
try {
|
680
|
+
rc = r->process() ? Qtrue : Qfalse;
|
681
|
+
} catch EXCEPTION("Request#process");
|
682
|
+
|
683
|
+
return rc;
|
684
|
+
}
|
685
|
+
|
686
|
+
extern "C" {
|
687
|
+
void Init_swift(void) {
|
688
|
+
rb_require("bigdecimal");
|
689
|
+
|
690
|
+
fNew = rb_intern("new");
|
691
|
+
fStringify = rb_intern("to_s");
|
692
|
+
fLoad = rb_intern("load");
|
693
|
+
fRead = rb_intern("read");
|
694
|
+
fWrite = rb_intern("write");
|
695
|
+
|
696
|
+
eRuntimeError = CONST_GET(rb_mKernel, "RuntimeError");
|
697
|
+
eArgumentError = CONST_GET(rb_mKernel, "ArgumentError");
|
698
|
+
eStandardError = CONST_GET(rb_mKernel, "StandardError");
|
699
|
+
cBigDecimal = CONST_GET(rb_mKernel, "BigDecimal");
|
700
|
+
eConnectionError = rb_define_class("ConnectionError", eRuntimeError);
|
701
|
+
|
702
|
+
mSwift = rb_define_module("Swift");
|
703
|
+
cAdapter = rb_define_class_under(mSwift, "Adapter", rb_cObject);
|
704
|
+
cStatement = rb_define_class_under(mSwift, "Statement", rb_cObject);
|
705
|
+
cResultSet = rb_define_class_under(mSwift, "ResultSet", cStatement);
|
706
|
+
cPool = rb_define_class_under(mSwift, "ConnectionPool", rb_cObject);
|
707
|
+
cRequest = rb_define_class_under(mSwift, "Request", rb_cObject);
|
708
|
+
|
709
|
+
rb_define_module_function(mSwift, "init", RUBY_METHOD_FUNC(rb_swift_init), 1);
|
710
|
+
rb_define_module_function(mSwift, "trace", RUBY_METHOD_FUNC(rb_swift_trace), -1);
|
711
|
+
|
712
|
+
rb_define_alloc_func(cAdapter, rb_adapter_alloc);
|
713
|
+
|
714
|
+
rb_define_method(cAdapter, "initialize", RUBY_METHOD_FUNC(rb_adapter_init), 1);
|
715
|
+
rb_define_method(cAdapter, "prepare", RUBY_METHOD_FUNC(rb_adapter_prepare), -1);
|
716
|
+
rb_define_method(cAdapter, "execute", RUBY_METHOD_FUNC(rb_adapter_execute), -1);
|
717
|
+
rb_define_method(cAdapter, "begin", RUBY_METHOD_FUNC(rb_adapter_begin), -1);
|
718
|
+
rb_define_method(cAdapter, "commit", RUBY_METHOD_FUNC(rb_adapter_commit), -1);
|
719
|
+
rb_define_method(cAdapter, "rollback", RUBY_METHOD_FUNC(rb_adapter_rollback), -1);
|
720
|
+
rb_define_method(cAdapter, "transaction", RUBY_METHOD_FUNC(rb_adapter_transaction), -1);
|
721
|
+
rb_define_method(cAdapter, "close", RUBY_METHOD_FUNC(rb_adapter_close), 0);
|
722
|
+
rb_define_method(cAdapter, "dup", RUBY_METHOD_FUNC(rb_adapter_dup), 0);
|
723
|
+
rb_define_method(cAdapter, "clone", RUBY_METHOD_FUNC(rb_adapter_dup), 0);
|
724
|
+
rb_define_method(cAdapter, "write", RUBY_METHOD_FUNC(rb_adapter_write), -1);
|
725
|
+
rb_define_method(cAdapter, "results", RUBY_METHOD_FUNC(rb_adapter_results), 0);
|
726
|
+
rb_define_method(cAdapter, "timezone", RUBY_METHOD_FUNC(rb_adapter_timezone), -1);
|
727
|
+
|
728
|
+
rb_define_alloc_func(cStatement, rb_statement_alloc);
|
729
|
+
|
730
|
+
rb_define_method(cStatement, "initialize", RUBY_METHOD_FUNC(rb_statement_init), 2);
|
731
|
+
rb_define_method(cStatement, "execute", RUBY_METHOD_FUNC(rb_statement_execute), -1);
|
732
|
+
rb_define_method(cStatement, "each", RUBY_METHOD_FUNC(rb_statement_each), 0);
|
733
|
+
rb_define_method(cStatement, "rows", RUBY_METHOD_FUNC(rb_statement_rows), 0);
|
734
|
+
rb_define_method(cStatement, "fetchrow", RUBY_METHOD_FUNC(rb_statement_fetchrow), 0);
|
735
|
+
rb_define_method(cStatement, "finish", RUBY_METHOD_FUNC(rb_statement_finish), 0);
|
736
|
+
rb_define_method(cStatement, "dup", RUBY_METHOD_FUNC(rb_statement_dup), 0);
|
737
|
+
rb_define_method(cStatement, "clone", RUBY_METHOD_FUNC(rb_statement_dup), 0);
|
738
|
+
rb_define_method(cStatement, "insert_id", RUBY_METHOD_FUNC(rb_statement_insert_id), 0);
|
739
|
+
rb_define_method(cStatement, "rewind", RUBY_METHOD_FUNC(rb_statement_rewind), 0);
|
740
|
+
|
741
|
+
rb_include_module(cStatement, CONST_GET(rb_mKernel, "Enumerable"));
|
742
|
+
|
743
|
+
|
744
|
+
rb_define_alloc_func(cPool, rb_cpool_alloc);
|
745
|
+
|
746
|
+
rb_define_method(cPool, "initialize", RUBY_METHOD_FUNC(rb_cpool_init), 2);
|
747
|
+
rb_define_method(cPool, "execute", RUBY_METHOD_FUNC(rb_cpool_execute), -1);
|
748
|
+
|
749
|
+
rb_define_alloc_func(cRequest, rb_request_alloc);
|
750
|
+
|
751
|
+
rb_define_method(cRequest, "socket", RUBY_METHOD_FUNC(rb_request_socket), 0);
|
752
|
+
rb_define_method(cRequest, "process", RUBY_METHOD_FUNC(rb_request_process), 0);
|
753
|
+
|
754
|
+
rb_define_method(cResultSet, "execute", RUBY_METHOD_FUNC(Qnil), 0);
|
755
|
+
}
|
756
|
+
}
|