swift 0.13.0 → 0.14.0
Sign up to get free protection for your applications and to get access to all the features.
- data/API.rdoc +16 -12
- data/{README.rdoc → README.md} +120 -66
- data/Rakefile +0 -1
- data/VERSION +1 -1
- data/ext/adapter.cc +84 -5
- data/ext/datetime.cc +96 -0
- data/ext/datetime.h +12 -0
- data/ext/extconf.rb +5 -4
- data/ext/query.cc +42 -18
- data/ext/query.h +2 -1
- data/ext/result.cc +65 -148
- data/ext/result.h +1 -1
- data/ext/statement.cc +1 -1
- data/ext/swift.cc +1 -2
- data/ext/swift.h +4 -2
- data/lib/swift/adapter/sql.rb +11 -1
- data/lib/swift/db.rb +14 -1
- data/lib/swift/type.rb +9 -2
- data/lib/swift.rb +9 -1
- data/swift.gemspec +8 -13
- data/test/test_adapter.rb +18 -4
- data/test/test_async.rb +28 -0
- data/test/test_datetime_parser.rb +12 -0
- data/test/test_error.rb +23 -0
- data/test/test_scheme.rb +1 -1
- data/test/test_timestamps.rb +22 -12
- data/test/test_types.rb +2 -2
- metadata +10 -23
- data/ext/pool.cc +0 -96
- data/ext/pool.h +0 -8
- data/ext/request.cc +0 -44
- data/ext/request.h +0 -10
- data/lib/swift/pool.rb +0 -76
- data/test/test_pool.rb +0 -38
data/API.rdoc
CHANGED
@@ -12,17 +12,18 @@ Public API minus the optional stuff like Pool, IdentityMap, Migrations etc.
|
|
12
12
|
|
13
13
|
# Abstract.
|
14
14
|
Adapter
|
15
|
-
.new
|
16
|
-
#begin
|
15
|
+
.new #=> Adapter
|
16
|
+
#begin #=> Adapter
|
17
17
|
#commit
|
18
|
-
#create
|
19
|
-
#delete
|
20
|
-
#execute
|
21
|
-
#
|
22
|
-
#
|
18
|
+
#create #=> Scheme or Result
|
19
|
+
#delete #=> Result
|
20
|
+
#execute #=> Result
|
21
|
+
#async_execute #=> Result
|
22
|
+
#get #=> Scheme
|
23
|
+
#prepare #=> Statement
|
23
24
|
#rollback
|
24
|
-
#transaction
|
25
|
-
#update
|
25
|
+
#transaction #=> Adapter
|
26
|
+
#update #=> Scheme or Result
|
26
27
|
#reconnect
|
27
28
|
|
28
29
|
# Concrete.
|
@@ -33,9 +34,12 @@ Public API minus the optional stuff like Pool, IdentityMap, Migrations etc.
|
|
33
34
|
|
34
35
|
# Enumerable collection of Scheme or Hash tuples.
|
35
36
|
Result
|
36
|
-
.new
|
37
|
-
#insert_id
|
38
|
-
#fields
|
37
|
+
.new #=> Result
|
38
|
+
#insert_id #=> Numeric
|
39
|
+
#fields #=> [Symbol, ...] # Field names identical to .first.keys if rows > 0
|
40
|
+
#field_types #=> [String, ...] # Type names: boolean, integer, float, numeric, timestamp, date, time, blob, text
|
41
|
+
#rows #=> Fixnum
|
42
|
+
#columns #=> Fixnum
|
39
43
|
|
40
44
|
Statement
|
41
45
|
.new #=> Statement
|
data/{README.rdoc → README.md}
RENAMED
@@ -1,31 +1,39 @@
|
|
1
|
-
|
1
|
+
Swift
|
2
|
+
=====
|
2
3
|
|
3
|
-
* http://github.com/shanna/swift
|
4
|
+
* [source](http://github.com/shanna/swift)
|
5
|
+
* [documentation](http://rubydoc.info/gems/swift/file/README.md)
|
4
6
|
|
5
|
-
|
7
|
+
## Description
|
6
8
|
|
7
9
|
A rational rudimentary object relational mapper.
|
8
10
|
|
9
|
-
|
11
|
+
## Dependencies
|
10
12
|
|
11
13
|
* ruby >= 1.9.1
|
12
|
-
* dbic++
|
14
|
+
* [dbic++](http://github.com/deepfryed/dbicpp) >= 0.6.0
|
13
15
|
* mysql >= 5.0.17, postgresql >= 8.4 or sqlite3 >= 3.7
|
14
16
|
|
15
|
-
|
17
|
+
## Features
|
16
18
|
|
17
19
|
* Multiple databases.
|
18
20
|
* Prepared statements.
|
19
21
|
* Bind values.
|
20
22
|
* Transactions and named save points.
|
21
|
-
*
|
23
|
+
* Asynchronous API for PostgreSQL and MySQL.
|
22
24
|
* IdentityMap.
|
23
25
|
* Migrations.
|
24
26
|
|
25
|
-
|
27
|
+
## Performance notes
|
26
28
|
|
27
|
-
|
29
|
+
1. The current version creates DateTime objects for timestamp fields and this is roughly 80% slower on
|
30
|
+
rubies older than 1.9.3.
|
31
|
+
2. On rubies older than 1.9.3, Swift will try using [home_run](https://github.com/jeremyevans/home_run)
|
32
|
+
for performance.
|
28
33
|
|
34
|
+
### DB
|
35
|
+
|
36
|
+
```ruby
|
29
37
|
require 'swift'
|
30
38
|
|
31
39
|
Swift.trace true # Debugging.
|
@@ -52,12 +60,14 @@ A rational rudimentary object relational mapper.
|
|
52
60
|
result = db.prepare('select * from users where name like ?').execute('Benny%')
|
53
61
|
puts result.first
|
54
62
|
end
|
63
|
+
```
|
55
64
|
|
56
|
-
|
65
|
+
### DB Scheme Operations
|
57
66
|
|
58
67
|
Rudimentary object mapping. Provides a definition to the db methods for prepared (and cached) statements plus native
|
59
68
|
primitive Ruby type conversion.
|
60
69
|
|
70
|
+
```ruby
|
61
71
|
require 'swift'
|
62
72
|
require 'swift/migrations'
|
63
73
|
|
@@ -69,7 +79,7 @@ primitive Ruby type conversion.
|
|
69
79
|
attribute :id, Swift::Type::Integer, serial: true, key: true
|
70
80
|
attribute :name, Swift::Type::String
|
71
81
|
attribute :email, Swift::Type::String
|
72
|
-
attribute :updated_at, Swift::Type::
|
82
|
+
attribute :updated_at, Swift::Type::DateTime
|
73
83
|
end # User
|
74
84
|
|
75
85
|
Swift.db do |db|
|
@@ -86,11 +96,13 @@ primitive Ruby type conversion.
|
|
86
96
|
user = db.get(User, id: 1)
|
87
97
|
puts user.name, user.email
|
88
98
|
end
|
99
|
+
```
|
89
100
|
|
90
|
-
|
101
|
+
### Scheme CRUD
|
91
102
|
|
92
103
|
Scheme/relation level helpers.
|
93
104
|
|
105
|
+
```ruby
|
94
106
|
require 'swift'
|
95
107
|
require 'swift/migrations'
|
96
108
|
|
@@ -122,12 +134,14 @@ Scheme/relation level helpers.
|
|
122
134
|
|
123
135
|
# Destroy
|
124
136
|
user.delete
|
137
|
+
```
|
125
138
|
|
126
|
-
|
139
|
+
### Conditions SQL syntax.
|
127
140
|
|
128
141
|
SQL is easy and most people know it so Swift ORM provides simple #to_s
|
129
|
-
attribute to field name typecasting.
|
142
|
+
attribute to table and field name typecasting.
|
130
143
|
|
144
|
+
```ruby
|
131
145
|
class User < Swift::Scheme
|
132
146
|
store :users
|
133
147
|
attribute :id, Swift::Type::Integer, serial: true, key: true
|
@@ -142,11 +156,13 @@ attribute to field name typecasting.
|
|
142
156
|
%Q{select * from #{User} where #{User.name} like ? and #{User.age} > ?},
|
143
157
|
'%Arthurton', 20
|
144
158
|
)
|
159
|
+
```
|
145
160
|
|
146
|
-
|
161
|
+
### Identity Map
|
147
162
|
|
148
163
|
Swift comes with a simple identity map. Just require it after you load swift.
|
149
164
|
|
165
|
+
```ruby
|
150
166
|
require 'swift'
|
151
167
|
require 'swift/identity_map'
|
152
168
|
require 'swift/migrations'
|
@@ -168,8 +184,9 @@ Swift comes with a simple identity map. Just require it after you load swift.
|
|
168
184
|
find_user = User.prepare(%Q{select * from #{User} where #{User.name = ?})
|
169
185
|
find_user.execute('James Arthurton')
|
170
186
|
find_user.execute('James Arthurton') # Gets same object reference
|
187
|
+
```
|
171
188
|
|
172
|
-
|
189
|
+
### Bulk inserts
|
173
190
|
|
174
191
|
Swift comes with adapter level support for bulk inserts for MySQL and PostgreSQL. This
|
175
192
|
is usually very fast (~5-10x faster) than regular prepared insert statements for larger
|
@@ -178,10 +195,13 @@ sets of data.
|
|
178
195
|
MySQL adapter - Overrides the MySQL C API and implements its own _infile_ handlers. This
|
179
196
|
means currently you *cannot* execute the following SQL using Swift
|
180
197
|
|
198
|
+
```sql
|
181
199
|
LOAD DATA LOCAL INFILE '/tmp/users.tab' INTO TABLE users;
|
200
|
+
```
|
182
201
|
|
183
202
|
But you can do it almost as fast in ruby,
|
184
203
|
|
204
|
+
```ruby
|
185
205
|
require 'swift'
|
186
206
|
|
187
207
|
Swift.setup :default, Swift::DB::Mysql, db: 'swift'
|
@@ -192,23 +212,82 @@ But you can do it almost as fast in ruby,
|
|
192
212
|
count = db.write('users', %w{name email balance}, file)
|
193
213
|
end
|
194
214
|
end
|
215
|
+
```
|
195
216
|
|
196
217
|
You are not just limited to files - you can stream data from anywhere into your database without
|
197
218
|
creating temporary files.
|
198
219
|
|
199
|
-
|
220
|
+
### Asynchronous API
|
221
|
+
|
222
|
+
`Swift::Adapter#async_execute` returns a `Swift::Result` instance. You can either poll the corresponding
|
223
|
+
`Swift::Adapter#fileno` and then call `Swift::Result#retrieve` when ready or use a block form like below
|
224
|
+
which implicitly uses `rb_thread_wait_fd`
|
225
|
+
|
226
|
+
```ruby
|
227
|
+
require 'swift'
|
228
|
+
|
229
|
+
pool = 3.times.map.with_index {|n| Swift.setup n, Swift::DB::Postgres, db: 'swift' }
|
230
|
+
|
231
|
+
Thread.new do
|
232
|
+
pool[0].async_execute('select pg_sleep(3), 1 as qid') {|row| p row}
|
233
|
+
end
|
234
|
+
|
235
|
+
Thread.new do
|
236
|
+
pool[1].async_execute('select pg_sleep(2), 2 as qid') {|row| p row}
|
237
|
+
end
|
238
|
+
|
239
|
+
Thread.new do
|
240
|
+
pool[2].async_execute('select pg_sleep(1), 3 as qid') {|row| p row}
|
241
|
+
end
|
242
|
+
|
243
|
+
Thread.list.reject {|thread| Thread.current == thread}.each(&:join)
|
244
|
+
```
|
245
|
+
|
246
|
+
```ruby
|
247
|
+
require 'swift'
|
248
|
+
require 'eventmachine'
|
249
|
+
|
250
|
+
pool = 3.times.map.with_index {|n| Swift.setup n, Swift::DB::Postgres, db: 'swift'}
|
251
|
+
|
252
|
+
module Handler
|
253
|
+
attr_reader :result
|
254
|
+
|
255
|
+
def initialize result
|
256
|
+
@result = result
|
257
|
+
end
|
258
|
+
|
259
|
+
def notify_readable
|
260
|
+
result.retrieve
|
261
|
+
result.each {|row| p row }
|
262
|
+
unbind
|
263
|
+
end
|
264
|
+
end
|
265
|
+
|
266
|
+
EM.run do
|
267
|
+
EM.watch(pool[0].fileno, Handler, pool[0].async_execute('select pg_sleep(3), 1 as qid')){|c| c.notify_readable = true}
|
268
|
+
EM.watch(pool[1].fileno, Handler, pool[1].async_execute('select pg_sleep(2), 2 as qid')){|c| c.notify_readable = true}
|
269
|
+
EM.watch(pool[2].fileno, Handler, pool[2].async_execute('select pg_sleep(1), 3 as qid')){|c| c.notify_readable = true}
|
270
|
+
EM.add_timer(4) { EM.stop }
|
271
|
+
end
|
272
|
+
```
|
273
|
+
|
274
|
+
## Performance
|
200
275
|
|
201
276
|
Swift prefers performance when it doesn't compromise the Ruby-ish interface. It's unfair to compare Swift to DataMapper
|
202
277
|
and ActiveRecord which suffer under the weight of support for many more databases and legacy/alternative Ruby
|
203
278
|
implementations. That said obviously if Swift were slower it would be redundant so benchmark code does exist in
|
204
279
|
http://github.com/shanna/swift/tree/master/benchmarks
|
205
280
|
|
206
|
-
|
281
|
+
### Benchmarks
|
282
|
+
|
283
|
+
#### ORM
|
207
284
|
|
208
|
-
|
285
|
+
The test environment:
|
209
286
|
|
210
|
-
|
211
|
-
Intel Core2Duo P8700 2.53GHz and
|
287
|
+
* ruby 1.9.3p0
|
288
|
+
* Intel Core2Duo P8700 2.53GHz, 4G RAM and Kingston SATA2 SSD
|
289
|
+
|
290
|
+
The test setup:
|
212
291
|
|
213
292
|
* 10,000 rows are created once.
|
214
293
|
* All the rows are selected once.
|
@@ -218,63 +297,38 @@ Intel Core2Duo P8700 2.53GHz and stock PostgreSQL 8.4.1.
|
|
218
297
|
objects allocated and the pressure on Ruby GC if it were running. When GC is enabled,
|
219
298
|
the actual memory consumption might be much lower than the numbers below.
|
220
299
|
|
221
|
-
|
300
|
+
```
|
222
301
|
./simple.rb -n1 -r10000 -s ar -s dm -s sequel -s swift
|
223
302
|
|
224
|
-
benchmark sys user total real
|
225
|
-
ar #create
|
226
|
-
ar #select 0.
|
227
|
-
ar #update 0.
|
303
|
+
benchmark sys user total real rss
|
304
|
+
ar #create 0.75 7.18 7.93 10.5043 366.95m
|
305
|
+
ar #select 0.07 0.26 0.33 0.3680 40.71m
|
306
|
+
ar #update 0.96 7.92 8.88 11.7537 436.38m
|
228
307
|
|
229
|
-
dm #create 0.
|
230
|
-
dm #select 0.
|
231
|
-
dm #update 0.
|
308
|
+
dm #create 0.33 3.73 4.06 5.0908 245.68m
|
309
|
+
dm #select 0.08 1.51 1.59 1.6154 87.95m
|
310
|
+
dm #update 0.44 7.09 7.53 8.8685 502.77m
|
232
311
|
|
233
|
-
sequel #create 0.
|
234
|
-
sequel #select 0.
|
235
|
-
sequel #update 0.
|
312
|
+
sequel #create 0.60 5.07 5.67 7.9804 236.69m
|
313
|
+
sequel #select 0.02 0.12 0.14 0.1778 12.75m
|
314
|
+
sequel #update 0.82 4.95 5.77 8.2062 230.00m
|
236
315
|
|
237
|
-
swift #create 0.
|
238
|
-
swift #select 0.
|
239
|
-
swift #update 0.
|
316
|
+
swift #create 0.27 0.59 0.86 1.5085 84.85m
|
317
|
+
swift #select 0.03 0.06 0.09 0.1037 11.24m
|
318
|
+
swift #update 0.20 0.69 0.89 1.5867 62.19m
|
240
319
|
|
241
320
|
-- bulk insert api --
|
242
|
-
swift #write 0.
|
243
|
-
|
244
|
-
|
245
|
-
==== Adapter
|
321
|
+
swift #write 0.04 0.06 0.10 0.1699 14.05m
|
322
|
+
```
|
246
323
|
|
247
|
-
|
248
|
-
|
249
|
-
* Same dataset as above.
|
250
|
-
* All rows are selected 5 times.
|
251
|
-
* The pg benchmark uses pg_typecast gem to provide typecasting support
|
252
|
-
for pg gem and also makes the benchmarks more fair.
|
253
|
-
|
254
|
-
===== PostgreSQL
|
255
|
-
|
256
|
-
benchmark sys user total real rss
|
257
|
-
do #select 0.020000 1.250000 1.270000 1.441281 71.98m
|
258
|
-
pg #select 0.000000 0.580000 0.580000 0.769186 42.93m
|
259
|
-
swift #select 0.040000 0.510000 0.550000 0.627581 43.23m
|
260
|
-
|
261
|
-
===== MySQL
|
262
|
-
|
263
|
-
benchmark sys user total real rss
|
264
|
-
do #select 0.030000 1.130000 1.160000 1.172205 71.86m
|
265
|
-
mysql2 #select 0.040000 0.660000 0.700000 0.704414 72.72m
|
266
|
-
swift #select 0.010000 0.480000 0.490000 0.499643 42.03m
|
267
|
-
|
268
|
-
== TODO
|
324
|
+
## TODO
|
269
325
|
|
270
326
|
* More tests.
|
271
327
|
* Assertions for dumb stuff.
|
328
|
+
* Auto-generate schema?
|
329
|
+
* Move examples to Wiki. Examples of models built on top of Schema.
|
272
330
|
|
273
|
-
|
331
|
+
## Contributing
|
274
332
|
|
275
333
|
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
|
276
334
|
send a pull request, tweet, email or yell it at me line by line in person.
|
277
|
-
|
278
|
-
== Feature suggestions and support
|
279
|
-
|
280
|
-
{Suggest features and support Swift ORM on fundry.}[https://fundry.com/project/14-swift-orm]
|
data/Rakefile
CHANGED
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.14.0
|
data/ext/adapter.cc
CHANGED
@@ -1,4 +1,7 @@
|
|
1
|
+
// vim:ts=2:sts=2:sw=2:expandtab
|
2
|
+
|
1
3
|
#include "adapter.h"
|
4
|
+
#include "sys/select.h"
|
2
5
|
|
3
6
|
// Extend the default dbi::FieldSet class with some ruby love.
|
4
7
|
class Fields : public dbi::FieldSet {
|
@@ -71,9 +74,21 @@ static VALUE adapter_begin(int argc, VALUE *argv, VALUE self) {
|
|
71
74
|
static VALUE adapter_close(VALUE self) {
|
72
75
|
dbi::Handle *handle = adapter_handle(self);
|
73
76
|
try { handle->close(); } CATCH_DBI_EXCEPTIONS();
|
77
|
+
rb_iv_set(self, "@closed", true);
|
74
78
|
return Qtrue;
|
75
79
|
}
|
76
80
|
|
81
|
+
|
82
|
+
/*
|
83
|
+
Check if connection is closed.
|
84
|
+
*/
|
85
|
+
static VALUE adapter_closed(VALUE self) {
|
86
|
+
return rb_iv_get(self, "@closed");
|
87
|
+
}
|
88
|
+
|
89
|
+
|
90
|
+
|
91
|
+
|
77
92
|
/*
|
78
93
|
Shallow copy of adapter.
|
79
94
|
|
@@ -166,7 +181,7 @@ static VALUE adapter_execute(int argc, VALUE *argv, VALUE self) {
|
|
166
181
|
if (dbi::_trace) dbi::logMessage(dbi::_trace_fd, dbi::formatParams(query.sql, query.bind));
|
167
182
|
|
168
183
|
if ((rows = rb_thread_blocking_region(((VALUE (*)(void*))query_execute), &query, RUBY_UBF_IO, 0)) == Qfalse)
|
169
|
-
rb_raise(
|
184
|
+
rb_raise(query.error_klass, "%s", query.error_message);
|
170
185
|
|
171
186
|
VALUE result = result_wrap_handle(cSwiftResult, self, handle->conn()->result(), true);
|
172
187
|
if (!NIL_P(scheme))
|
@@ -181,7 +196,11 @@ static VALUE adapter_execute(int argc, VALUE *argv, VALUE self) {
|
|
181
196
|
*/
|
182
197
|
static VALUE adapter_reconnect(VALUE self) {
|
183
198
|
dbi::Handle *handle = adapter_handle(self);
|
184
|
-
try {
|
199
|
+
try {
|
200
|
+
handle->reconnect();
|
201
|
+
rb_iv_set(self, "@closed", false);
|
202
|
+
}
|
203
|
+
CATCH_DBI_EXCEPTIONS();
|
185
204
|
return Qtrue;
|
186
205
|
}
|
187
206
|
|
@@ -202,7 +221,6 @@ static VALUE adapter_reconnect(VALUE self) {
|
|
202
221
|
@option options [String] :password ('')
|
203
222
|
@option options [String] :host ('localhost')
|
204
223
|
@option options [Integer] :port (DB default)
|
205
|
-
@option options [String] :timezone (*nix TZ format) See http://en.wikipedia.org/wiki/List_of_tz_database_time_zones
|
206
224
|
@return [Swift::Adapter]
|
207
225
|
|
208
226
|
@see Swift::DB
|
@@ -225,7 +243,6 @@ static VALUE adapter_initialize(VALUE self, VALUE options) {
|
|
225
243
|
rb_hash_delete(extra, ID2SYM(rb_intern("password")));
|
226
244
|
rb_hash_delete(extra, ID2SYM(rb_intern("host")));
|
227
245
|
rb_hash_delete(extra, ID2SYM(rb_intern("port")));
|
228
|
-
rb_hash_delete(extra, ID2SYM(rb_intern("timezone")));
|
229
246
|
|
230
247
|
std::string extra_options_string = parse_extra_options(extra);
|
231
248
|
|
@@ -243,7 +260,6 @@ static VALUE adapter_initialize(VALUE self, VALUE options) {
|
|
243
260
|
CATCH_DBI_EXCEPTIONS();
|
244
261
|
|
245
262
|
rb_iv_set(self, "@options", options);
|
246
|
-
rb_iv_set(self, "@timezone", rb_hash_aref(options, ID2SYM(rb_intern("timezone"))));
|
247
263
|
|
248
264
|
return Qnil;
|
249
265
|
}
|
@@ -377,6 +393,64 @@ static VALUE adapter_write(int argc, VALUE *argv, VALUE self) {
|
|
377
393
|
CATCH_DBI_EXCEPTIONS();
|
378
394
|
}
|
379
395
|
|
396
|
+
/*
|
397
|
+
Returns the socket fileno for the connection.
|
398
|
+
|
399
|
+
@overload fileno()
|
400
|
+
@return [Fixnum]
|
401
|
+
*/
|
402
|
+
VALUE adapter_fileno(VALUE self) {
|
403
|
+
dbi::Handle *handle = adapter_handle(self);
|
404
|
+
return INT2NUM(handle->conn()->socket());
|
405
|
+
}
|
406
|
+
|
407
|
+
/*
|
408
|
+
Executes a query asynchronously and returns the result instance.
|
409
|
+
|
410
|
+
@example
|
411
|
+
|
412
|
+
@overload async_execute(statement = '', *binds, &block)
|
413
|
+
@param [String] statement Query statement.
|
414
|
+
@param [*Object] binds Bind values.
|
415
|
+
@return [Swift::Result]
|
416
|
+
*/
|
417
|
+
VALUE adapter_async_execute(int argc, VALUE *argv, VALUE self) {
|
418
|
+
VALUE statement, bind_values, block, scheme = Qnil, result;
|
419
|
+
|
420
|
+
dbi::Handle *handle = adapter_handle(self);
|
421
|
+
rb_scan_args(argc, argv, "1*&", &statement, &bind_values, &block);
|
422
|
+
|
423
|
+
if (TYPE(statement) == T_CLASS) {
|
424
|
+
scheme = statement;
|
425
|
+
statement = rb_ary_shift(bind_values);
|
426
|
+
}
|
427
|
+
|
428
|
+
try {
|
429
|
+
dbi::AbstractResult *dbi_result;
|
430
|
+
if (RARRAY_LEN(bind_values) > 0) {
|
431
|
+
Query query;
|
432
|
+
query_bind_values(&query, bind_values);
|
433
|
+
dbi_result = handle->conn()->aexecute(CSTRING(statement), query.bind);
|
434
|
+
}
|
435
|
+
else
|
436
|
+
dbi_result = handle->conn()->aexecute(CSTRING(statement));
|
437
|
+
|
438
|
+
result = result_wrap_handle(cSwiftResult, self, dbi_result, true);
|
439
|
+
if (!NIL_P(scheme))
|
440
|
+
rb_iv_set(result, "@scheme", scheme);
|
441
|
+
|
442
|
+
// if block given, just use rb_thread_select
|
443
|
+
if (rb_block_given_p()) {
|
444
|
+
rb_thread_wait_fd(handle->socket());
|
445
|
+
while (dbi_result->consumeResult());
|
446
|
+
dbi_result->prepareResult();
|
447
|
+
}
|
448
|
+
}
|
449
|
+
CATCH_DBI_EXCEPTIONS();
|
450
|
+
|
451
|
+
return rb_block_given_p() ? result_each(result) : result;
|
452
|
+
}
|
453
|
+
|
380
454
|
void init_swift_adapter() {
|
381
455
|
VALUE mSwift = rb_define_module("Swift");
|
382
456
|
cSwiftAdapter = rb_define_class_under(mSwift, "Adapter", rb_cObject);
|
@@ -384,6 +458,7 @@ void init_swift_adapter() {
|
|
384
458
|
rb_define_method(cSwiftAdapter, "begin", RUBY_METHOD_FUNC(adapter_begin), -1);
|
385
459
|
rb_define_method(cSwiftAdapter, "clone", RUBY_METHOD_FUNC(adapter_clone), 0);
|
386
460
|
rb_define_method(cSwiftAdapter, "close", RUBY_METHOD_FUNC(adapter_close), 0);
|
461
|
+
rb_define_method(cSwiftAdapter, "closed?", RUBY_METHOD_FUNC(adapter_closed), 0);
|
387
462
|
rb_define_method(cSwiftAdapter, "commit", RUBY_METHOD_FUNC(adapter_commit), -1);
|
388
463
|
rb_define_method(cSwiftAdapter, "dup", RUBY_METHOD_FUNC(adapter_dup), 0);
|
389
464
|
rb_define_method(cSwiftAdapter, "escape", RUBY_METHOD_FUNC(adapter_escape), 1);
|
@@ -395,6 +470,10 @@ void init_swift_adapter() {
|
|
395
470
|
rb_define_method(cSwiftAdapter, "write", RUBY_METHOD_FUNC(adapter_write), -1);
|
396
471
|
rb_define_method(cSwiftAdapter, "reconnect", RUBY_METHOD_FUNC(adapter_reconnect), 0);
|
397
472
|
|
473
|
+
// stuff you need for async
|
474
|
+
rb_define_method(cSwiftAdapter, "fileno", RUBY_METHOD_FUNC(adapter_fileno), 0);
|
475
|
+
rb_define_method(cSwiftAdapter, "async_execute", RUBY_METHOD_FUNC(adapter_async_execute), -1);
|
476
|
+
|
398
477
|
rb_define_alloc_func(cSwiftAdapter, adapter_alloc);
|
399
478
|
}
|
400
479
|
|
data/ext/datetime.cc
ADDED
@@ -0,0 +1,96 @@
|
|
1
|
+
#include "datetime.h"
|
2
|
+
|
3
|
+
extern VALUE dtformat;
|
4
|
+
|
5
|
+
VALUE cSwiftDateTime, day_seconds;
|
6
|
+
ID fcivil, fparse, fstrptime;
|
7
|
+
|
8
|
+
// NOTE: only parses '%F %T.%N %z' format and falls back to the built-in DateTime#parse
|
9
|
+
// and is almost 2x faster than doing:
|
10
|
+
//
|
11
|
+
// rb_funcall(klass, fstrptime, 2, rb_str_new(data, size), dtformat);
|
12
|
+
//
|
13
|
+
VALUE datetime_parse(VALUE klass, const char *data, uint64_t size) {
|
14
|
+
struct tm tm;
|
15
|
+
double seconds;
|
16
|
+
const char *ptr;
|
17
|
+
char tzsign = 0, fraction[32];
|
18
|
+
int tzhour = 0, tzmin = 0, lastmatch = -1, offset = 0, idx;
|
19
|
+
|
20
|
+
memset(&tm, 0, sizeof(struct tm));
|
21
|
+
sscanf(data, "%04d-%02d-%02d %02d:%02d:%02d%n",
|
22
|
+
&tm.tm_year, &tm.tm_mon, &tm.tm_mday, &tm.tm_hour, &tm.tm_min, &tm.tm_sec, &lastmatch);
|
23
|
+
|
24
|
+
// fallback to default datetime parser, this is more expensive.
|
25
|
+
if (tm.tm_mday == 0)
|
26
|
+
return Qnil;
|
27
|
+
|
28
|
+
seconds = tm.tm_sec;
|
29
|
+
|
30
|
+
// parse millisecs if any -- tad faster than using %lf in sscanf above.
|
31
|
+
if (lastmatch > 0 && lastmatch < size && *(data + lastmatch) == '.') {
|
32
|
+
idx = 0;
|
33
|
+
ptr = data + ++lastmatch;
|
34
|
+
while (*ptr && isdigit(*ptr) && idx < 31) {
|
35
|
+
lastmatch++;
|
36
|
+
fraction[idx++] = *ptr++;
|
37
|
+
}
|
38
|
+
|
39
|
+
fraction[idx] = 0;
|
40
|
+
seconds += (double)atoll(fraction) / pow(10, idx);
|
41
|
+
}
|
42
|
+
|
43
|
+
// parse timezone offsets if any - matches +HH:MM +HH MM +HHMM
|
44
|
+
if (lastmatch > 0 && lastmatch < size) {
|
45
|
+
const char *ptr = data + lastmatch;
|
46
|
+
while(*ptr && *ptr != '+' && *ptr != '-') ptr++;
|
47
|
+
tzsign = *ptr++;
|
48
|
+
if (*ptr && isdigit(*ptr)) {
|
49
|
+
tzhour = *ptr++ - '0';
|
50
|
+
if (*ptr && isdigit(*ptr)) tzhour = tzhour * 10 + *ptr++ - '0';
|
51
|
+
while(*ptr && !isdigit(*ptr)) ptr++;
|
52
|
+
if (*ptr) {
|
53
|
+
tzmin = *ptr++ - '0';
|
54
|
+
if (*ptr && isdigit(*ptr)) tzmin = tzmin * 10 + *ptr++ - '0';
|
55
|
+
}
|
56
|
+
}
|
57
|
+
}
|
58
|
+
|
59
|
+
if (tzsign) {
|
60
|
+
offset = tzsign == '+'
|
61
|
+
? (time_t)tzhour * 3600 + (time_t)tzmin * 60
|
62
|
+
: (time_t)tzhour * -3600 + (time_t)tzmin * -60;
|
63
|
+
}
|
64
|
+
|
65
|
+
return rb_funcall(klass, fcivil, 7,
|
66
|
+
INT2FIX(tm.tm_year), INT2FIX(tm.tm_mon), INT2FIX(tm.tm_mday),
|
67
|
+
INT2FIX(tm.tm_hour), INT2FIX(tm.tm_min), DBL2NUM(seconds),
|
68
|
+
offset == 0 ? INT2FIX(0) : rb_Rational(INT2FIX(offset), day_seconds)
|
69
|
+
);
|
70
|
+
}
|
71
|
+
|
72
|
+
VALUE rb_datetime_parse(VALUE self, VALUE string) {
|
73
|
+
const char *data = CSTRING(string);
|
74
|
+
int size = TYPE(string) == T_STRING ? RSTRING_LEN(string) : strlen(data);
|
75
|
+
|
76
|
+
if (NIL_P(string))
|
77
|
+
return Qnil;
|
78
|
+
|
79
|
+
VALUE datetime = datetime_parse(self, data, size);
|
80
|
+
return NIL_P(datetime) ? rb_call_super(1, &string) : datetime;
|
81
|
+
}
|
82
|
+
|
83
|
+
void init_swift_datetime() {
|
84
|
+
rb_require("date");
|
85
|
+
|
86
|
+
VALUE mSwift = rb_define_module("Swift");
|
87
|
+
VALUE cDateTime = CONST_GET(rb_mKernel, "DateTime");
|
88
|
+
cSwiftDateTime = rb_define_class_under(mSwift, "DateTime", cDateTime);
|
89
|
+
fcivil = rb_intern("civil");
|
90
|
+
fparse = rb_intern("parse");
|
91
|
+
fstrptime = rb_intern("strptime");
|
92
|
+
day_seconds = INT2FIX(86400);
|
93
|
+
|
94
|
+
rb_global_variable(&day_seconds);
|
95
|
+
rb_define_singleton_method(cSwiftDateTime, "parse", RUBY_METHOD_FUNC(rb_datetime_parse), 1);
|
96
|
+
}
|
data/ext/datetime.h
ADDED
data/ext/extconf.rb
CHANGED
@@ -1,8 +1,10 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
require 'mkmf'
|
3
3
|
|
4
|
-
|
5
|
-
|
4
|
+
ConfigClass = defined?(RbConfig) ? RbConfig : Config
|
5
|
+
|
6
|
+
ConfigClass::CONFIG['CC'] = 'g++'
|
7
|
+
ConfigClass::CONFIG['CPP'] = 'g++'
|
6
8
|
|
7
9
|
$CFLAGS = '-fPIC -Os -I/usr/include -I/opt/local/include -I/usr/local/include'
|
8
10
|
|
@@ -55,6 +57,5 @@ exit 1 unless library_installed? 'pcrecpp', apt_install_hint('libpcre3-dev')
|
|
55
57
|
exit 1 unless library_installed? 'uuid', apt_install_hint('uuid-dev')
|
56
58
|
exit 1 unless library_installed? 'dbic++', apt_install_hint('dbic++-dev')
|
57
59
|
|
58
|
-
assert_dbicpp_version '0.
|
59
|
-
|
60
|
+
assert_dbicpp_version '0.6.0'
|
60
61
|
create_makefile 'swift'
|