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 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 #=> Adapter
16
- #begin #=> Adapter
15
+ .new #=> Adapter
16
+ #begin #=> Adapter
17
17
  #commit
18
- #create #=> Scheme or Result
19
- #delete #=> Result
20
- #execute #=> Result
21
- #get #=> Scheme
22
- #prepare #=> Statement
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 #=> Adapter
25
- #update #=> Scheme or Result
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 #=> Result
37
- #insert_id #=> Numeric
38
- #fields #=> [Symbol, ...] # Field names identical to .first.keys if rows > 0
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
@@ -1,31 +1,39 @@
1
- = Swift
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
- == Description
7
+ ## Description
6
8
 
7
9
  A rational rudimentary object relational mapper.
8
10
 
9
- == Dependencies
11
+ ## Dependencies
10
12
 
11
13
  * ruby >= 1.9.1
12
- * dbic++ >= 0.5.9 (http://github.com/deepfryed/dbicpp)
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
- == Features
17
+ ## Features
16
18
 
17
19
  * Multiple databases.
18
20
  * Prepared statements.
19
21
  * Bind values.
20
22
  * Transactions and named save points.
21
- * EventMachine asynchronous interface (mysql and postgresql).
23
+ * Asynchronous API for PostgreSQL and MySQL.
22
24
  * IdentityMap.
23
25
  * Migrations.
24
26
 
25
- == Synopsis
27
+ ## Performance notes
26
28
 
27
- === DB
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
- === DB Scheme Operations
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::Time
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
- === Scheme CRUD
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
- === Conditions SQL syntax.
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
- === Identity Map
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
- === Bulk inserts
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
- == Performance
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
- === Benchmarks
281
+ ### Benchmarks
282
+
283
+ #### ORM
207
284
 
208
- ==== ORM
285
+ The test environment:
209
286
 
210
- The following bechmarks were run on a machine with 4G ram, 5200rpm sata drive,
211
- Intel Core2Duo P8700 2.53GHz and stock PostgreSQL 8.4.1.
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 rss
225
- ar #create 1.01 7.91 8.92 11.426 406.22m
226
- ar #select 0.02 0.31 0.33 0.378 40.69m
227
- ar #update 0.88 9.64 10.52 13.908 504.93m
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.23 3.52 3.75 5.405 211.00m
230
- dm #select 0.11 1.67 1.78 1.912 114.57m
231
- dm #update 0.54 7.34 7.88 9.453 531.30m
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.77 4.61 5.38 8.194 235.50m
234
- sequel #select 0.01 0.13 0.14 0.180 12.73m
235
- sequel #update 0.64 4.76 5.40 7.790 229.69m
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.13 0.66 0.79 1.463 85.77m
238
- swift #select 0.01 0.10 0.11 0.135 8.92m
239
- swift #update 0.14 0.75 0.89 1.585 59.56m
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.00 0.10 0.10 0.180 14.79m
243
-
244
-
245
- ==== Adapter
321
+ swift #write 0.04 0.06 0.10 0.1699 14.05m
322
+ ```
246
323
 
247
- The adapter level SELECT benchmarks without using ORM.
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
- == Contributing
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
@@ -13,7 +13,6 @@ begin
13
13
  gem.files.reject!{|f| f =~ %r{\.gitignore|examples|benchmarks|memory/.*}}
14
14
 
15
15
  gem.add_development_dependency 'minitest', '>= 1.7.0'
16
- gem.add_development_dependency 'eventmachine'
17
16
  end
18
17
  Jeweler::GemcutterTasks.new
19
18
  rescue LoadError
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.13.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(eSwiftRuntimeError, "%s", query.error);
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 { handle->reconnect(); } CATCH_DBI_EXCEPTIONS();
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
@@ -0,0 +1,12 @@
1
+ #ifndef SWIFT_DATETIME_H
2
+ #define SWIFT_DATETIME_H
3
+
4
+ #include "swift.h"
5
+ #include <math.h>
6
+
7
+ void init_swift_datetime();
8
+ VALUE datetime_parse(VALUE klass, const char *data, uint64_t size);
9
+
10
+ extern VALUE cSwiftDateTime;
11
+
12
+ #endif
data/ext/extconf.rb CHANGED
@@ -1,8 +1,10 @@
1
1
  #!/usr/bin/env ruby
2
2
  require 'mkmf'
3
3
 
4
- Config::CONFIG['CC'] = 'g++'
5
- Config::CONFIG['CPP'] = 'g++'
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.5.9'
59
-
60
+ assert_dbicpp_version '0.6.0'
60
61
  create_makefile 'swift'