swift 0.6.1 → 0.7.0
Sign up to get free protection for your applications and to get access to all the features.
- data/API.rdoc +1 -0
- data/README.rdoc +60 -22
- data/VERSION +1 -1
- data/examples/scheme.rb +1 -1
- data/ext/adapter.cc +12 -8
- data/ext/attribute.cc +22 -0
- data/ext/attribute.h +8 -0
- data/ext/extconf.rb +1 -1
- data/ext/pool.cc +20 -13
- data/ext/query.cc +21 -1
- data/ext/query.h +2 -1
- data/ext/result.cc +136 -85
- data/ext/result.h +14 -4
- data/ext/statement.cc +38 -11
- data/ext/statement.h +11 -1
- data/ext/swift.cc +19 -7
- data/ext/swift.h +1 -0
- data/lib/swift/adapter.rb +12 -3
- data/lib/swift/attribute.rb +7 -15
- data/lib/swift/db.rb +41 -2
- data/lib/swift/pool.rb +10 -5
- data/lib/swift/type.rb +1 -1
- data/swift.gemspec +4 -2
- data/test/helper.rb +6 -5
- data/test/test_adapter.rb +83 -86
- data/test/test_encoding.rb +3 -3
- data/test/test_io.rb +1 -1
- data/test/test_pool.rb +19 -15
- data/test/test_scheme.rb +1 -1
- data/test/test_timestamps.rb +20 -3
- data/test/test_transactions.rb +37 -14
- data/test/test_types.rb +17 -3
- metadata +6 -4
data/API.rdoc
CHANGED
data/README.rdoc
CHANGED
@@ -9,10 +9,16 @@ A rational rudimentary object relational mapper.
|
|
9
9
|
== Dependencies
|
10
10
|
|
11
11
|
* ruby >= 1.9.1
|
12
|
-
* dbic++ >= 0.
|
13
|
-
* mysql >= 5.0.17 or postgresql >= 8.4
|
12
|
+
* dbic++ >= 0.4.0 (http://github.com/deepfryed/dbicpp)
|
13
|
+
* mysql >= 5.0.17 or postgresql >= 8.4 or db2 >= 9.7.2
|
14
14
|
|
15
|
-
|
15
|
+
== Caveats
|
16
|
+
|
17
|
+
=== DB2
|
18
|
+
* The server needs to be running under DB2_COMPATIBILITY_VECTOR=77FF if you want to use the ORM
|
19
|
+
features of swift.
|
20
|
+
* DB2 asynchronous operations are highly experimental at this point due to inherent limitations of the
|
21
|
+
underlying api. It is more an academic exercise and is not ready for real-world use.
|
16
22
|
|
17
23
|
== Features
|
18
24
|
|
@@ -71,7 +77,7 @@ primitive Ruby type conversion.
|
|
71
77
|
attribute :id, Swift::Type::Integer, serial: true, key: true
|
72
78
|
attribute :name, Swift::Type::String
|
73
79
|
attribute :email, Swift::Type::String
|
74
|
-
attribute :updated_at, Swift::Type::
|
80
|
+
attribute :updated_at, Swift::Type::Time
|
75
81
|
end # User
|
76
82
|
|
77
83
|
Swift.db do |db|
|
@@ -169,7 +175,7 @@ Swift comes with a simple identity map. Just require it after you load swift.
|
|
169
175
|
|
170
176
|
=== Bulk inserts
|
171
177
|
|
172
|
-
Swift comes with adapter level support for bulk inserts for MySQL and
|
178
|
+
Swift comes with adapter level support for bulk inserts for MySQL, PostgreSQL and DB2. This
|
173
179
|
is usually very fast (~5-10x faster) than regular prepared insert statements for larger
|
174
180
|
sets of data.
|
175
181
|
|
@@ -191,8 +197,8 @@ But you can do it almost as fast in ruby,
|
|
191
197
|
end
|
192
198
|
end
|
193
199
|
|
194
|
-
You are not just limited to files - you can stream data from anywhere into
|
195
|
-
|
200
|
+
You are not just limited to files - you can stream data from anywhere into your database without
|
201
|
+
creating temporary files.
|
196
202
|
|
197
203
|
== Performance
|
198
204
|
|
@@ -203,6 +209,8 @@ http://github.com/shanna/swift/tree/master/benchmarks
|
|
203
209
|
|
204
210
|
=== Benchmarks
|
205
211
|
|
212
|
+
==== ORM
|
213
|
+
|
206
214
|
The following bechmarks were run on a machine with 4G ram, 5200rpm sata drive,
|
207
215
|
Intel Core2Duo P8700 2.53GHz and stock PostgreSQL 8.4.1.
|
208
216
|
|
@@ -214,26 +222,57 @@ Intel Core2Duo P8700 2.53GHz and stock PostgreSQL 8.4.1.
|
|
214
222
|
objects allocated and the pressure on Ruby GC if it were running. When GC is enabled,
|
215
223
|
the actual memory consumption might be much lower than the numbers below.
|
216
224
|
|
217
|
-
./benchmarks/simple.rb -r 10000 -n 1
|
218
225
|
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
226
|
+
./simple.rb -n1 -r10000 -s ar -s dm -s sequel -s swift
|
227
|
+
|
228
|
+
benchmark sys user total real rss
|
229
|
+
ar #create 0.800000 6.620000 7.420000 9.898821 369.44m
|
230
|
+
ar #select 0.020000 0.300000 0.320000 0.372809 38.83m
|
231
|
+
ar #update 0.770000 6.550000 7.320000 10.02434 361.92m
|
232
|
+
|
233
|
+
dm #create 0.110000 3.590000 3.700000 4.847609 248.74m
|
234
|
+
dm #select 0.120000 1.840000 1.960000 2.029552 128.98m
|
235
|
+
dm #update 0.400000 7.750000 8.150000 9.741249 599.52m
|
236
|
+
|
237
|
+
sequel #create 0.770000 3.910000 4.680000 7.432611 263.59m
|
238
|
+
sequel #select 0.020000 0.080000 0.100000 0.147321 9.82m
|
239
|
+
sequel #update 0.730000 3.910000 4.640000 7.594949 259.18m
|
240
|
+
|
241
|
+
swift #create 0.210000 0.850000 1.060000 2.618661 32.72m
|
242
|
+
swift #select 0.000000 0.080000 0.080000 0.132245 9.91m
|
243
|
+
swift #update 0.270000 0.650000 0.920000 2.204108 37.48m
|
244
|
+
|
245
|
+
-- bulk insert api --
|
246
|
+
swift #write 0.000000 0.080000 0.080000 0.151146 7.29m
|
223
247
|
|
224
|
-
ar #create 0.930000 6.620000 7.550000 10.002911 367.82m
|
225
|
-
ar #select 0.050000 0.310000 0.360000 0.417127 38.82m
|
226
|
-
ar #update 0.770000 6.180000 6.950000 9.711788 361.93m
|
227
248
|
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
249
|
+
==== Adapter
|
250
|
+
|
251
|
+
The adapter level SELECT benchmarks without using ORM.
|
252
|
+
|
253
|
+
* Same dataset as above.
|
254
|
+
* All rows are selected 5 times.
|
255
|
+
* The pg benchmark uses pg_typecast gem to provide typecasting support
|
256
|
+
for pg gem and also makes the benchmarks more fair.
|
257
|
+
|
258
|
+
===== PostgreSQL
|
259
|
+
|
260
|
+
benchmark sys user total real rss
|
261
|
+
do #select 0.060000 1.070000 1.130000 1.370092 99.83m
|
262
|
+
pg #select 0.030000 0.270000 0.300000 0.584091 46.13m
|
263
|
+
swift #select 0.020000 0.290000 0.310000 0.571635 39.08m
|
264
|
+
|
265
|
+
===== MySQL
|
266
|
+
|
267
|
+
benchmark sys user total real rss
|
268
|
+
do #select 0.030000 1.070000 1.100000 1.200177 99.26m
|
269
|
+
mysql2 #select 0.060000 0.450000 0.510000 0.609236 76.44m
|
270
|
+
swift #select 0.050000 0.170000 0.220000 0.334932 33.61m
|
232
271
|
|
233
272
|
== TODO
|
234
273
|
|
235
|
-
*
|
236
|
-
*
|
274
|
+
* More tests.
|
275
|
+
* Make db2 async api more stable.
|
237
276
|
* Assertions for dumb stuff.
|
238
277
|
* Abstract interface for other adapters? Move dbic++ to Swift::DBI::(Adapter, Pool, Result, Statment etc.)
|
239
278
|
|
@@ -241,4 +280,3 @@ Intel Core2Duo P8700 2.53GHz and stock PostgreSQL 8.4.1.
|
|
241
280
|
|
242
281
|
Go nuts! There is no style guide and I do not care if you write tests or comment code. If you write something neat just
|
243
282
|
send a pull request, tweet, email or yell it at me line by line in person.
|
244
|
-
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.7.0
|
data/examples/scheme.rb
CHANGED
@@ -13,7 +13,7 @@ class User < Swift::Scheme
|
|
13
13
|
attribute :name, Swift::Type::String
|
14
14
|
attribute :email, Swift::Type::String
|
15
15
|
attribute :active, Swift::Type::Boolean
|
16
|
-
attribute :created, Swift::Type::
|
16
|
+
attribute :created, Swift::Type::Time, default: proc { Time.now }
|
17
17
|
attribute :optional, Swift::Type::String, default: 'woot'
|
18
18
|
|
19
19
|
validations do |errors|
|
data/ext/adapter.cc
CHANGED
@@ -3,7 +3,10 @@
|
|
3
3
|
static VALUE cSwiftAdapter;
|
4
4
|
|
5
5
|
static void adapter_free(dbi::Handle *handle) {
|
6
|
-
if (handle)
|
6
|
+
if (handle) {
|
7
|
+
handle->conn()->cleanup();
|
8
|
+
delete handle;
|
9
|
+
}
|
7
10
|
}
|
8
11
|
|
9
12
|
VALUE adapter_alloc(VALUE klass) {
|
@@ -83,15 +86,15 @@ static VALUE adapter_execute(int argc, VALUE *argv, VALUE self) {
|
|
83
86
|
query.sql = CSTRING(statement);
|
84
87
|
query.handle = handle;
|
85
88
|
|
86
|
-
if (RARRAY_LEN(bind_values) > 0) query_bind_values(&query, bind_values);
|
89
|
+
if (RARRAY_LEN(bind_values) > 0) query_bind_values(&query, bind_values, handle->driver());
|
87
90
|
if (dbi::_trace) dbi::logMessage(dbi::_trace_fd, dbi::formatParams(query.sql, query.bind));
|
88
91
|
|
89
92
|
if ((rows = rb_thread_blocking_region(((VALUE (*)(void*))query_execute), &query, RUBY_UBF_IO, 0)) == Qfalse)
|
90
93
|
rb_raise(eSwiftRuntimeError, "%s", query.error);
|
91
94
|
|
92
95
|
if (rb_block_given_p()) {
|
93
|
-
dbi::
|
94
|
-
return result_each(
|
96
|
+
dbi::AbstractResult *result = handle->results();
|
97
|
+
return result_each(result_wrap_handle(cSwiftResult, self, result, false));
|
95
98
|
}
|
96
99
|
else
|
97
100
|
return rows;
|
@@ -121,7 +124,8 @@ static VALUE adapter_initialize(VALUE self, VALUE options) {
|
|
121
124
|
}
|
122
125
|
CATCH_DBI_EXCEPTIONS();
|
123
126
|
|
124
|
-
rb_iv_set(self, "@
|
127
|
+
rb_iv_set(self, "@timezone", rb_hash_aref(options, ID2SYM(rb_intern("timezone"))));
|
128
|
+
rb_iv_set(self, "@options", options);
|
125
129
|
return Qnil;
|
126
130
|
}
|
127
131
|
|
@@ -139,7 +143,7 @@ static VALUE adapter_prepare(int argc, VALUE *argv, VALUE self) {
|
|
139
143
|
try {
|
140
144
|
// TODO: Move to statement_* constructor.
|
141
145
|
statement = handle->conn()->prepare(CSTRING(sql));
|
142
|
-
prepared =
|
146
|
+
prepared = statement_wrap_handle(cSwiftStatement, self, statement);
|
143
147
|
rb_iv_set(prepared, "@scheme", scheme);
|
144
148
|
return prepared;
|
145
149
|
}
|
@@ -226,8 +230,8 @@ static VALUE adapter_write(int argc, VALUE *argv, VALUE self) {
|
|
226
230
|
VALUE adapter_results(VALUE self) {
|
227
231
|
dbi::Handle *handle = adapter_handle(self);
|
228
232
|
try {
|
229
|
-
|
230
|
-
|
233
|
+
dbi::AbstractResult *result = handle->results();
|
234
|
+
return result_wrap_handle(cSwiftResult, self, result, false);
|
231
235
|
}
|
232
236
|
CATCH_DBI_EXCEPTIONS();
|
233
237
|
}
|
data/ext/attribute.cc
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
#include "attribute.h"
|
2
|
+
|
3
|
+
ID fcall;
|
4
|
+
|
5
|
+
VALUE attribute_default(VALUE self) {
|
6
|
+
VALUE value = rb_iv_get(self, "@default");
|
7
|
+
|
8
|
+
if (NIL_P(value) || rb_special_const_p(value))
|
9
|
+
return value;
|
10
|
+
else if (rb_respond_to(value, fcall))
|
11
|
+
return rb_funcall(value, fcall, 0);
|
12
|
+
else
|
13
|
+
return rb_obj_dup(value);
|
14
|
+
}
|
15
|
+
|
16
|
+
void init_swift_attribute() {
|
17
|
+
VALUE mSwift = rb_define_module("Swift");
|
18
|
+
VALUE cSwiftAttribute = rb_define_class_under(mSwift, "Attribute", rb_cObject);
|
19
|
+
|
20
|
+
fcall = rb_intern("call");
|
21
|
+
rb_define_method(cSwiftAttribute, "default", RUBY_METHOD_FUNC(attribute_default), 0);
|
22
|
+
}
|
data/ext/attribute.h
ADDED
data/ext/extconf.rb
CHANGED
@@ -55,6 +55,6 @@ exit 1 unless library_installed? 'pcrecpp', apt_install_hint('libpcre3-dev')
|
|
55
55
|
exit 1 unless library_installed? 'uuid', apt_install_hint('uuid-dev')
|
56
56
|
exit 1 unless library_installed? 'dbic++', apt_install_hint('dbic++-dev')
|
57
57
|
|
58
|
-
assert_dbicpp_version '0.
|
58
|
+
assert_dbicpp_version '0.4.0'
|
59
59
|
|
60
60
|
create_makefile 'swift'
|
data/ext/pool.cc
CHANGED
@@ -18,15 +18,10 @@ static dbi::ConnectionPool* pool_handle(VALUE self) {
|
|
18
18
|
return pool;
|
19
19
|
}
|
20
20
|
|
21
|
-
// TODO: Remove unnecessary assignments. See Adapter.
|
22
21
|
VALUE pool_init(VALUE self, VALUE n, VALUE options) {
|
23
22
|
VALUE db = rb_hash_aref(options, ID2SYM(rb_intern("db")));
|
24
|
-
VALUE host = rb_hash_aref(options, ID2SYM(rb_intern("host")));
|
25
|
-
VALUE port = rb_hash_aref(options, ID2SYM(rb_intern("port")));
|
26
23
|
VALUE user = rb_hash_aref(options, ID2SYM(rb_intern("user")));
|
27
24
|
VALUE driver = rb_hash_aref(options, ID2SYM(rb_intern("driver")));
|
28
|
-
VALUE password = rb_hash_aref(options, ID2SYM(rb_intern("password")));
|
29
|
-
VALUE zone = rb_hash_aref(options, ID2SYM(rb_intern("timezone")));
|
30
25
|
|
31
26
|
if (NIL_P(db)) rb_raise(eSwiftArgumentError, "Pool#new called without :db");
|
32
27
|
if (NIL_P(driver)) rb_raise(eSwiftArgumentError, "#new called without :driver");
|
@@ -39,20 +34,28 @@ VALUE pool_init(VALUE self, VALUE n, VALUE options) {
|
|
39
34
|
NUM2INT(n),
|
40
35
|
CSTRING(driver),
|
41
36
|
CSTRING(user),
|
42
|
-
CSTRING(password),
|
37
|
+
CSTRING(rb_hash_aref(options, ID2SYM(rb_intern("password")))),
|
43
38
|
CSTRING(db),
|
44
|
-
CSTRING(host),
|
45
|
-
CSTRING(port)
|
39
|
+
CSTRING(rb_hash_aref(options, ID2SYM(rb_intern("host")))),
|
40
|
+
CSTRING(rb_hash_aref(options, ID2SYM(rb_intern("port"))))
|
46
41
|
);
|
42
|
+
|
43
|
+
rb_iv_set(self, "@timezone", rb_hash_aref(options, ID2SYM(rb_intern("timezone"))));
|
47
44
|
}
|
48
45
|
CATCH_DBI_EXCEPTIONS();
|
46
|
+
|
49
47
|
return Qnil;
|
50
48
|
}
|
51
49
|
|
52
|
-
void pool_callback(dbi::
|
50
|
+
void pool_callback(dbi::AbstractResult *result) {
|
53
51
|
VALUE callback = (VALUE)result->context;
|
54
|
-
|
55
|
-
|
52
|
+
|
53
|
+
// NOTE: C Result object will be deallocated in dbic++
|
54
|
+
if (!NIL_P(callback)) {
|
55
|
+
VALUE obj = result_wrap_handle(cSwiftResult, 0, result, false);
|
56
|
+
rb_iv_set(obj, "@timezone", rb_iv_get(callback, "@timezone"));
|
57
|
+
rb_proc_call(callback, rb_ary_new3(1, obj));
|
58
|
+
}
|
56
59
|
}
|
57
60
|
|
58
61
|
VALUE pool_execute(int argc, VALUE *argv, VALUE self) {
|
@@ -65,11 +68,15 @@ VALUE pool_execute(int argc, VALUE *argv, VALUE self) {
|
|
65
68
|
dbi::ConnectionPool *pool = pool_handle(self);
|
66
69
|
rb_scan_args(argc, argv, "1*&", &sql, &bind_values, &callback);
|
67
70
|
|
68
|
-
|
71
|
+
// The only way to pass timezone to the C callback routine.
|
72
|
+
if (NIL_P(callback))
|
73
|
+
rb_raise(eSwiftArgumentError, "No block given in Pool#execute");
|
74
|
+
else
|
75
|
+
rb_iv_set(callback, "@timezone", rb_iv_get(self, "@timezone"));
|
69
76
|
|
70
77
|
try {
|
71
78
|
Query query;
|
72
|
-
query_bind_values(&query, bind_values);
|
79
|
+
query_bind_values(&query, bind_values, pool->driver());
|
73
80
|
request = request_alloc(cSwiftRequest);
|
74
81
|
DATA_PTR(request) = pool->execute(CSTRING(sql), query.bind, pool_callback, (void*)callback);
|
75
82
|
return request;
|
data/ext/query.cc
CHANGED
@@ -1,5 +1,8 @@
|
|
1
1
|
#include "query.h"
|
2
2
|
|
3
|
+
ID fstrftime, fto_s, fusec;
|
4
|
+
VALUE dtformat, tzformat;
|
5
|
+
|
3
6
|
VALUE query_execute(Query *query) {
|
4
7
|
try {
|
5
8
|
return UINT2NUM(
|
@@ -28,7 +31,7 @@ VALUE query_execute_statement(Query *query) {
|
|
28
31
|
}
|
29
32
|
}
|
30
33
|
|
31
|
-
void query_bind_values(Query *query, VALUE bind_values) {
|
34
|
+
void query_bind_values(Query *query, VALUE bind_values, std::string driver) {
|
32
35
|
for (int i = 0; i < RARRAY_LEN(bind_values); i++) {
|
33
36
|
VALUE bind_value = rb_ary_entry(bind_values, i);
|
34
37
|
|
@@ -45,6 +48,16 @@ void query_bind_values(Query *query, VALUE bind_values) {
|
|
45
48
|
bind_value = rb_funcall(bind_value, rb_intern("read"), 0);
|
46
49
|
query->bind.push_back(dbi::PARAM_BINARY((unsigned char*)RSTRING_PTR(bind_value), RSTRING_LEN(bind_value)));
|
47
50
|
}
|
51
|
+
// TODO convert timestamps to server timezone if @timezone is set in adapter.
|
52
|
+
else if (rb_obj_is_kind_of(bind_value, rb_cTime)) {
|
53
|
+
std::string timestamp = RSTRING_PTR(rb_funcall(bind_value, fstrftime, 1, dtformat));
|
54
|
+
|
55
|
+
timestamp += RSTRING_PTR(rb_funcall(rb_funcall(bind_value, fusec, 0), fto_s, 0));
|
56
|
+
if (driver != "db2")
|
57
|
+
timestamp += RSTRING_PTR(rb_funcall(bind_value, fstrftime, 1, tzformat));
|
58
|
+
|
59
|
+
query->bind.push_back(dbi::PARAM(timestamp));
|
60
|
+
}
|
48
61
|
else {
|
49
62
|
bind_value = TO_S(bind_value);
|
50
63
|
if (strcmp(rb_enc_get(bind_value)->name, "UTF-8") != 0)
|
@@ -54,3 +67,10 @@ void query_bind_values(Query *query, VALUE bind_values) {
|
|
54
67
|
}
|
55
68
|
}
|
56
69
|
|
70
|
+
void init_swift_query() {
|
71
|
+
fstrftime = rb_intern("strftime");
|
72
|
+
fto_s = rb_intern("to_s");
|
73
|
+
fusec = rb_intern("usec");
|
74
|
+
dtformat = rb_str_new2("%F %T.");
|
75
|
+
tzformat = rb_str_new2("%z");
|
76
|
+
}
|