swift 0.6.1 → 0.7.0
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.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
|
+
}
|