swift 0.4.3 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
data/API.rdoc ADDED
@@ -0,0 +1,90 @@
1
+ = Swift
2
+
3
+ == Public API
4
+
5
+ Public API minus the optional stuff like Pool, IdentityMap, Migrations etc.
6
+
7
+ Swift
8
+ .setup #=> Adapter
9
+ .db #=> Adapter
10
+ .schema #=> [Scheme, ...]
11
+ .trace
12
+
13
+ # Abstract.
14
+ Adapter
15
+ .new #=> Adapter
16
+ #all #=> Result
17
+ #begin #=> Adapter
18
+ #commit
19
+ #create #=> Scheme
20
+ #destroy #=> Result
21
+ #execute #=> Result
22
+ #first #=> Scheme
23
+ #get #=> Scheme
24
+ #prepare #=> Statement
25
+ #rollback
26
+ #transaction #=> Adapter
27
+ #update #=> Result
28
+
29
+ # TODO: DBI < Adapter
30
+ # returning? #=> true or false
31
+
32
+ # Concrete.
33
+ DB
34
+ Mysql < Adapter # TODO: Adapter::DBI?
35
+ Postgres < Adapter # TODO: Adapter::DBI?
36
+
37
+ # Enumerable collection of Scheme or Hash tuples.
38
+ Result
39
+ .new #=> Result
40
+ #insert_id #=> Numeric
41
+
42
+ Statement < Result
43
+ .new #=> Statement
44
+ #execute #=> Statement
45
+
46
+ Scheme
47
+ .all #=> Result
48
+ .attribute #=> Type
49
+ .create #=> Scheme
50
+ .first #=> Scheme
51
+ .get #=> Scheme
52
+ .header #=> Header
53
+ .load #=> Scheme
54
+ .new #=> Scheme
55
+ .scheme #=> Alias for self.class
56
+ .store #=> Symbol
57
+ #destroy #=> Result
58
+ #tuple #=> Hash
59
+ #update #=> Result
60
+
61
+ # Enumerable collection of Types for Scheme
62
+ Header
63
+ .new #=> Header
64
+ #all #=> [Type, ...]
65
+ #insertable #=> [Type, ...]
66
+ #keys #=> [Symbol, ...]
67
+ #new_tuple #=> Hash
68
+ #push #=> Type
69
+ #serial #=> Symbol or nil.
70
+ #updatable #=> [Type, ...]
71
+
72
+ # Abstract.
73
+ Attribute
74
+ .new #=> Attribute
75
+ #name #=> Symbol
76
+ #field #=> Symbol
77
+ #key #=> true or false
78
+ #serial #=> Symbol or nil
79
+ #default #=> Object
80
+ #define_scheme_methods
81
+
82
+ # Concrete
83
+ Type
84
+ BigDecimal < Attribute
85
+ Boolean < Attribute
86
+ Float < Attribute
87
+ Integer < Attribute
88
+ IO < Attribute
89
+ String < Attribute
90
+ Time < Attribute # Soon to be DateTime?
data/README.rdoc CHANGED
@@ -21,6 +21,7 @@ dbic++ can be found here http://github.com/deepfryed/dbicpp
21
21
  * Bind values.
22
22
  * Transactions and named save points.
23
23
  * EventMachine asynchronous interface.
24
+ * IdentityMap.
24
25
  * Migrations.
25
26
 
26
27
  == Synopsis
@@ -39,7 +40,7 @@ dbic++ can be found here http://github.com/deepfryed/dbicpp
39
40
 
40
41
  # Save points are supported.
41
42
  db.transaction :named_save_point do
42
- st = db.prepare('insert into users (name, email) values (?, ?)')
43
+ st = db.prepare('insert into users (name, email) values (?, ?) returning id')
43
44
  puts st.execute('Apple Arthurton', 'apple@arthurton.local').insert_id
44
45
  puts st.execute('Benny Arthurton', 'benny@arthurton.local').insert_id
45
46
  end
@@ -60,6 +61,7 @@ Rudimentary object mapping. Provides a definition to the db methods for prepared
60
61
  primitive Ruby type conversion.
61
62
 
62
63
  require 'swift'
64
+ require 'swift/migrations'
63
65
 
64
66
  Swift.trace true # Debugging.
65
67
  Swift.setup :default, Swift::DB::Postgres, db: 'swift'
@@ -92,6 +94,7 @@ primitive Ruby type conversion.
92
94
  Scheme/relation level helpers.
93
95
 
94
96
  require 'swift'
97
+ require 'swift/migrations'
95
98
 
96
99
  Swift.trace true # Debugging.
97
100
  Swift.setup :default, Swift::DB::Postgres, db: 'swift'
@@ -145,6 +148,7 @@ Swift comes with a simple identity map. Just require it after you load swift.
145
148
 
146
149
  require 'swift'
147
150
  require 'swift/identity_map'
151
+ require 'swift/migrations'
148
152
 
149
153
  class User < Swift::Scheme
150
154
  store :users
@@ -154,10 +158,15 @@ Swift comes with a simple identity map. Just require it after you load swift.
154
158
  attribute :email, Swift::Type::String, field: 'liame'
155
159
  end # User
156
160
 
161
+ # Migrate it.
162
+ User.migrate!
163
+
164
+ # Create
165
+ User.create name: 'James Arthurton', email: 'james@arthurton.local' # => User
166
+
157
167
  User.first(':name = ?', 'James Arthurton')
158
168
  User.first(':name = ?', 'James Arthurton') # Gets same object reference
159
169
 
160
-
161
170
  === Bulk inserts
162
171
 
163
172
  Swift comes with adapter level support for bulk inserts for MySQL and PostgreSQL. This
@@ -185,7 +194,6 @@ But you can do it almost as fast in ruby,
185
194
  You are not just limited to files - you can stream data from anywhere into MySQL and
186
195
  PostgreSQL directly without creating temporary files.
187
196
 
188
-
189
197
  == Performance
190
198
 
191
199
  Swift prefers performance when it doesn't compromise the Ruby-ish interface. It's unfair to compare Swift to DataMapper
@@ -222,12 +230,12 @@ Intel Core2Duo P8700 2.53GHz and stock PostgreSQL 8.4.1.
222
230
  swift #update 0.250000 0.610000 0.860000 1.996165 29.35m
223
231
  swift #write 0.000000 0.100000 0.100000 0.167199 6.23m
224
232
 
225
-
226
233
  == TODO
227
234
 
228
235
  * Tests.
229
- * Assertions for dumb stuff. model < Model for methods in Adapter.
230
- * Profile.
236
+ * Extension performance. Remove all repeated rb_intern() calls etc.
237
+ * Assertions for dumb stuff.
238
+ * Abstract interface for other adapters? Move dbic++ to Swift::DBI::(Adapter, Pool, Result, Statment etc.)
231
239
 
232
240
  == Contributing
233
241
 
data/Rakefile CHANGED
@@ -13,6 +13,7 @@ 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'
16
17
  end
17
18
  Jeweler::GemcutterTasks.new
18
19
  rescue LoadError
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.4.3
1
+ 0.5.0
data/examples/async.rb CHANGED
@@ -19,16 +19,21 @@ Swift.db do |db|
19
19
 
20
20
  puts '-- insert --'
21
21
  ins = db.prepare('insert into users(name, email) values(?, ?)')
22
- 10.times {|n| ins.execute(*sample[n%3]) }
22
+ 9.times {|n| ins.execute(*sample[n%3]) }
23
23
  end
24
24
 
25
+ sleep_clause = {
26
+ Swift::DB::Postgres => "case length(pg_sleep(%s)::text) when 0 then '%s' else '%s' end as sleep",
27
+ Swift::DB::Mysql => "if (sleep(%s), '%s', '%s') as sleep"
28
+ }
29
+
25
30
  puts '-- select 9 times with a pool of size 5 --'
26
31
  Swift.trace false
27
32
  Swift.pool(5) do |db|
28
33
  (1..9).each do |n|
29
34
  pause = '%0.3f' % ((20-n)/20.0)
30
- pause = "case length(pg_sleep(#{pause})::text) when 0 then '#{pause}' else '' end as sleep"
31
- db.execute("select #{pause}, * from users where id = ?", n) {|r| p r.first }
35
+ pause = sleep_clause[adapter] % 3.times.map { pause }
36
+ db.execute("select *, #{pause} from users where id = ?", n) {|r| p r.first }
32
37
  end
33
38
  end
34
39
  Swift.trace true
@@ -38,17 +43,17 @@ EM.run {
38
43
  pool1 = Swift.pool(2)
39
44
  pool2 = Swift.pool(1)
40
45
 
41
- pool1.execute("select * from users limit 5 offset 0") do |rs|
46
+ pool1.execute("select * from users limit 3 offset 0") do |rs|
42
47
  puts '-- Inside pool1 #callback --'
43
48
  rs.each {|r| p r }
44
- pool1.execute("select * from users limit 5 offset 5") do |rs|
49
+ pool1.execute("select * from users limit 3 offset 3") do |rs|
45
50
  puts '-- Inside pool1 #callback again --'
46
51
  rs.each {|r| p r }
47
52
  EM.stop
48
53
  end
49
54
  end
50
55
 
51
- pool2.execute("select * from users limit 5 offset 10") do |rs|
56
+ pool2.execute("select * from users limit 3 offset 6") do |rs|
52
57
  puts '-- Inside pool2 #callback --'
53
58
  rs.each {|r| p r }
54
59
  end
data/examples/db.rb CHANGED
@@ -1,6 +1,10 @@
1
1
  #!/usr/bin/env ruby
2
- require_relative '../lib/swift'
2
+
3
+ $:.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
4
+
3
5
  require 'pp'
6
+ require 'swift'
7
+ require 'swift/migrations'
4
8
 
5
9
  class User < Swift::Scheme
6
10
  store :users
data/examples/scheme.rb CHANGED
@@ -1,6 +1,11 @@
1
1
  #!/usr/bin/env ruby
2
- require_relative '../lib/swift'
2
+
3
+ $:.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
4
+
3
5
  require 'pp'
6
+ require 'swift'
7
+ require 'swift/migrations'
8
+ require 'swift/validations'
4
9
 
5
10
  class User < Swift::Scheme
6
11
  store :users
@@ -10,6 +15,10 @@ class User < Swift::Scheme
10
15
  attribute :active, Swift::Type::Boolean
11
16
  attribute :created, Swift::Type::Time, default: proc { Time.now }
12
17
  attribute :optional, Swift::Type::String, default: 'woot'
18
+
19
+ validations do |errors|
20
+ errors << [:name, 'is blank'] if name.to_s.empty?
21
+ end
13
22
  end # User
14
23
 
15
24
  adapter = ARGV.first =~ /mysql/i ? Swift::DB::Mysql : Swift::DB::Postgres
@@ -27,7 +36,6 @@ User.create name: 'Benny Arthurton', email: 'benny@arthurton.local'
27
36
 
28
37
  puts '', '-- all --'
29
38
  pp User.all.to_a
30
- # pp User.all(':name like ?', '%Arthurton').to_a
31
39
 
32
40
  puts '', '-- first --'
33
41
  pp User.first(':name like ?', '%Arthurton')
data/ext/adapter.cc ADDED
@@ -0,0 +1,259 @@
1
+ #include "adapter.h"
2
+
3
+ static VALUE cSwiftAdapter;
4
+
5
+ static void adapter_free(dbi::Handle *handle) {
6
+ if (handle) delete handle;
7
+ }
8
+
9
+ VALUE adapter_alloc(VALUE klass) {
10
+ dbi::Handle *handle = 0;
11
+ return Data_Wrap_Struct(klass, 0, adapter_free, handle);
12
+ }
13
+
14
+ dbi::Handle* adapter_handle(VALUE self) {
15
+ dbi::Handle *handle;
16
+ Data_Get_Struct(self, dbi::Handle, handle);
17
+ if (!handle) rb_raise(eSwiftRuntimeError, "Invalid object, did you forget to call #super?");
18
+
19
+ return handle;
20
+ }
21
+
22
+ static VALUE adapter_begin(int argc, VALUE *argv, VALUE self) {
23
+ VALUE save_point;
24
+ rb_scan_args(argc, argv, "01", &save_point);
25
+
26
+ dbi::Handle *handle = adapter_handle(self);
27
+ try {
28
+ NIL_P(save_point) ? handle->begin() : handle->begin(CSTRING(save_point));
29
+ }
30
+ CATCH_DBI_EXCEPTIONS();
31
+ return Qtrue;
32
+ }
33
+
34
+ static VALUE adapter_close(VALUE self) {
35
+ dbi::Handle *handle = adapter_handle(self);
36
+ try { handle->close(); } CATCH_DBI_EXCEPTIONS();
37
+ return Qtrue;
38
+ }
39
+
40
+ // TODO:
41
+ static VALUE adapter_clone(VALUE self) {
42
+ rb_raise(eSwiftRuntimeError, "clone is not allowed.");
43
+ }
44
+
45
+ static VALUE adapter_commit(int argc, VALUE *argv, VALUE self) {
46
+ VALUE save_point;
47
+ rb_scan_args(argc, argv, "01", &save_point);
48
+ dbi::Handle *handle = adapter_handle(self);
49
+
50
+ try {
51
+ NIL_P(save_point) ? handle->commit() : handle->commit(CSTRING(save_point));
52
+ }
53
+ CATCH_DBI_EXCEPTIONS();
54
+ return Qtrue;
55
+ }
56
+
57
+ // TODO:
58
+ static VALUE adapter_dup(VALUE self) {
59
+ rb_raise(eSwiftRuntimeError, "dup is not allowed.");
60
+ }
61
+
62
+ // TODO: Attempt TO_S() before escaping?
63
+ static VALUE adapter_escape(VALUE self, VALUE value) {
64
+ if (TYPE(value) != T_STRING) rb_raise(eSwiftArgumentError, "Cannot escape non-string value.");
65
+
66
+ dbi::Handle *handle = adapter_handle(self);
67
+ try {
68
+ std::string safe = handle->escape(std::string(RSTRING_PTR(value), RSTRING_LEN(value)));
69
+ return rb_str_new(safe.data(), safe.length());
70
+ }
71
+ CATCH_DBI_EXCEPTIONS();
72
+ }
73
+
74
+ // TODO: Change bind_values to an array in the interface? Avoid array -> splat -> array.
75
+ static VALUE adapter_execute(int argc, VALUE *argv, VALUE self) {
76
+ VALUE statement, bind_values, block, rows;
77
+
78
+ dbi::Handle *handle = adapter_handle(self);
79
+ rb_scan_args(argc, argv, "1*&", &statement, &bind_values, &block);
80
+
81
+ try {
82
+ Query query;
83
+ query.sql = CSTRING(statement);
84
+ query.handle = handle;
85
+
86
+ if (RARRAY_LEN(bind_values) > 0) query_bind_values(&query, bind_values);
87
+ if (dbi::_trace) dbi::logMessage(dbi::_trace_fd, dbi::formatParams(query.sql, query.bind));
88
+
89
+ // TODO: http://redmine.ruby-lang.org/issues/show/3762
90
+ // rb_thread_blocking_region and C++ exceptions don't mix in 1.9.2.
91
+ // rows = rb_thread_blocking_region(((VALUE (*)(void*))query_execute), &query, RUBY_UBF_IO, 0);
92
+ rows = query_execute(&query);
93
+ if (rb_block_given_p()) {
94
+ dbi::AbstractResultSet *result = handle->results();
95
+ return result_each(Data_Wrap_Struct(cSwiftResult, 0, result_free, result));
96
+ }
97
+ else
98
+ return rows;
99
+ }
100
+ CATCH_DBI_EXCEPTIONS();
101
+ }
102
+
103
+ static VALUE adapter_initialize(VALUE self, VALUE options) {
104
+ VALUE db = rb_hash_aref(options, ID2SYM(rb_intern("db")));
105
+ VALUE driver = rb_hash_aref(options, ID2SYM(rb_intern("driver")));
106
+ VALUE user = rb_hash_aref(options, ID2SYM(rb_intern("user")));
107
+
108
+ if (NIL_P(db)) rb_raise(eSwiftArgumentError, "Adapter#new called without :db");
109
+ if (NIL_P(driver)) rb_raise(eSwiftArgumentError, "Adapter#new called without :driver");
110
+
111
+ user = NIL_P(user) ? rb_str_new2(getlogin()) : user;
112
+
113
+ try {
114
+ DATA_PTR(self) = new dbi::Handle(
115
+ CSTRING(driver),
116
+ CSTRING(user),
117
+ CSTRING(rb_hash_aref(options, ID2SYM(rb_intern("password")))),
118
+ CSTRING(db),
119
+ CSTRING(rb_hash_aref(options, ID2SYM(rb_intern("host")))),
120
+ CSTRING(rb_hash_aref(options, ID2SYM(rb_intern("port"))))
121
+ );
122
+ }
123
+ CATCH_DBI_EXCEPTIONS();
124
+
125
+ rb_iv_set(self, "@options", options);
126
+ return Qnil;
127
+ }
128
+
129
+ static VALUE adapter_prepare(int argc, VALUE *argv, VALUE self) {
130
+ VALUE sql, scheme, prepared;
131
+ dbi::AbstractStatement *statement;
132
+
133
+ rb_scan_args(argc, argv, "11", &scheme, &sql);
134
+ if (TYPE(scheme) != T_CLASS) {
135
+ sql = scheme;
136
+ scheme = Qnil;
137
+ }
138
+
139
+ dbi::Handle *handle = adapter_handle(self);
140
+ try {
141
+ // TODO: Move to statement_* constructor.
142
+ statement = handle->conn()->prepare(CSTRING(sql));
143
+ prepared = Data_Wrap_Struct(cSwiftStatement, 0, statement_free, statement);
144
+ rb_iv_set(prepared, "@scheme", scheme);
145
+ return prepared;
146
+ }
147
+ CATCH_DBI_EXCEPTIONS();
148
+ }
149
+
150
+ static VALUE adapter_rollback(int argc, VALUE *argv, VALUE self) {
151
+ VALUE save_point;
152
+ dbi::Handle *handle = adapter_handle(self);
153
+ rb_scan_args(argc, argv, "01", &save_point);
154
+
155
+ try {
156
+ NIL_P(save_point) ? handle->rollback() : handle->rollback(CSTRING(save_point));
157
+ }
158
+ CATCH_DBI_EXCEPTIONS();
159
+ return Qtrue;
160
+ }
161
+
162
+ static VALUE adapter_transaction(int argc, VALUE *argv, VALUE self) {
163
+ int status;
164
+ VALUE sp, block;
165
+
166
+ dbi::Handle *handle = adapter_handle(self);
167
+
168
+ rb_scan_args(argc, argv, "01&", &sp, &block);
169
+
170
+ if (NIL_P(block)) rb_raise(eSwiftArgumentError, "Transaction called without a block.");
171
+ std::string save_point = NIL_P(sp) ? "SP" + dbi::generateCompactUUID() : CSTRING(sp);
172
+
173
+ try {
174
+ handle->begin(save_point);
175
+ rb_protect(rb_yield, self, &status);
176
+ if (!status && handle->transactions().size() > 0) {
177
+ handle->commit(save_point);
178
+ }
179
+ else if (status && handle->transactions().size() > 0) {
180
+ handle->rollback(save_point);
181
+ rb_jump_tag(status);
182
+ }
183
+ }
184
+ CATCH_DBI_EXCEPTIONS();
185
+
186
+ return Qtrue;
187
+ }
188
+
189
+ static VALUE adapter_write(int argc, VALUE *argv, VALUE self) {
190
+ uint64_t rows = 0;
191
+ VALUE stream, table, fields;
192
+ dbi::Handle *handle = adapter_handle(self);
193
+
194
+ rb_scan_args(argc, argv, "30", &table, &fields, &stream);
195
+ if (TYPE(stream) != T_STRING && !rb_respond_to(stream, rb_intern("read")))
196
+ rb_raise(eSwiftArgumentError, "Stream must be a String or IO object.");
197
+ if (TYPE(fields) != T_ARRAY)
198
+ rb_raise(eSwiftArgumentError, "Fields must be an Array.");
199
+
200
+ try {
201
+ dbi::FieldSet write_fields;
202
+ for (int i = 0; i < RARRAY_LEN(fields); i++) {
203
+ VALUE field = TO_S(rb_ary_entry(fields, i));
204
+ write_fields << std::string(RSTRING_PTR(field), RSTRING_LEN(field));
205
+ }
206
+
207
+ /*
208
+ TODO: Adapter specific code is balls.
209
+ This is just for the friggin mysql support - mysql does not like a statement close command being send on a
210
+ handle when the writing has started.
211
+ */
212
+ rb_gc();
213
+
214
+ if (TYPE(stream) == T_STRING) {
215
+ dbi::IOStream io(RSTRING_PTR(stream), RSTRING_LEN(stream));
216
+ rows = handle->write(RSTRING_PTR(table), write_fields, &io);
217
+ }
218
+ else {
219
+ IOStream io(stream);
220
+ rows = handle->write(RSTRING_PTR(table), write_fields, &io);
221
+ }
222
+ return SIZET2NUM(rows);
223
+ }
224
+ CATCH_DBI_EXCEPTIONS();
225
+ }
226
+
227
+ VALUE adapter_results(VALUE self) {
228
+ dbi::Handle *handle = adapter_handle(self);
229
+ try {
230
+ dbi::AbstractResultSet *result = handle->results();
231
+ return Data_Wrap_Struct(cSwiftResult, 0, result_free, result);
232
+ }
233
+ CATCH_DBI_EXCEPTIONS();
234
+ }
235
+
236
+ void init_swift_adapter() {
237
+ VALUE mSwift = rb_define_module("Swift");
238
+ cSwiftAdapter = rb_define_class_under(mSwift, "Adapter", rb_cObject);
239
+
240
+ rb_define_method(cSwiftAdapter, "begin", RUBY_METHOD_FUNC(adapter_begin), -1);
241
+ rb_define_method(cSwiftAdapter, "clone", RUBY_METHOD_FUNC(adapter_clone), 0);
242
+ rb_define_method(cSwiftAdapter, "close", RUBY_METHOD_FUNC(adapter_close), 0);
243
+ rb_define_method(cSwiftAdapter, "commit", RUBY_METHOD_FUNC(adapter_commit), -1);
244
+ rb_define_method(cSwiftAdapter, "dup", RUBY_METHOD_FUNC(adapter_dup), 0);
245
+ rb_define_method(cSwiftAdapter, "escape", RUBY_METHOD_FUNC(adapter_escape), 1);
246
+ rb_define_method(cSwiftAdapter, "execute", RUBY_METHOD_FUNC(adapter_execute), -1);
247
+ rb_define_method(cSwiftAdapter, "initialize", RUBY_METHOD_FUNC(adapter_initialize), 1);
248
+ rb_define_method(cSwiftAdapter, "prepare", RUBY_METHOD_FUNC(adapter_prepare), -1);
249
+ rb_define_method(cSwiftAdapter, "rollback", RUBY_METHOD_FUNC(adapter_rollback), -1);
250
+ rb_define_method(cSwiftAdapter, "transaction", RUBY_METHOD_FUNC(adapter_transaction), -1);
251
+ rb_define_method(cSwiftAdapter, "write", RUBY_METHOD_FUNC(adapter_write), -1);
252
+
253
+ rb_define_alloc_func(cSwiftAdapter, adapter_alloc);
254
+
255
+ // TODO Figure out how to avoid race conditions.
256
+ rb_define_method(cSwiftAdapter, "results", RUBY_METHOD_FUNC(adapter_results), 0);
257
+ }
258
+
259
+
data/ext/adapter.h ADDED
@@ -0,0 +1,13 @@
1
+ #ifndef SWIFT_ADAPTER_H
2
+ #define SWIFT_ADAPTER_H
3
+
4
+ #include "swift.h"
5
+ #include "query.h"
6
+ #include "result.h"
7
+ #include "statement.h"
8
+
9
+ void init_swift_adapter();
10
+ dbi::Handle *adapter_handle(VALUE);
11
+
12
+ #endif
13
+
data/ext/extconf.rb CHANGED
@@ -32,8 +32,29 @@ def library_installed? name, hint
32
32
  end
33
33
  end
34
34
 
35
+ def assert_dbicpp_version ver
36
+ passed = false
37
+ header = '/usr/include/dbic++.h'
38
+ message = "Swift needs dbic++ >= #{ver}. Please update your dbic++ installation."
39
+
40
+ if File.exists?(header) && match = File.read(header).match(/DBI_VERSION\s+(.*?)\n/mi)
41
+ rmajor, rminor, rbuild = ver.strip.split(/\./).map(&:to_i)
42
+ imajor, iminor, ibuild = match.captures.first.strip.split(/\./).map(&:to_i)
43
+ passed = (imajor > rmajor) ||
44
+ (imajor == rmajor && iminor > rminor) ||
45
+ (imajor == rmajor && iminor == rminor && ibuild >= rbuild)
46
+ else
47
+ message = "Cannot find #{header} or version number. You need to install dbic++ >= #{ver}"
48
+ passed = false
49
+ end
50
+
51
+ raise message unless passed
52
+ end
53
+
35
54
  exit 1 unless library_installed? 'pcrecpp', apt_install_hint('libpcre3-dev')
36
55
  exit 1 unless library_installed? 'uuid', apt_install_hint('uuid-dev')
37
56
  exit 1 unless library_installed? 'dbic++', apt_install_hint('dbic++-dev')
38
57
 
58
+ assert_dbicpp_version '0.3.0'
59
+
39
60
  create_makefile 'swift'
data/ext/iostream.cc ADDED
@@ -0,0 +1,44 @@
1
+ #include "iostream.h"
2
+
3
+ IOStream::IOStream(VALUE s) {
4
+ stream = s;
5
+ }
6
+
7
+ std::string& IOStream::read() {
8
+ VALUE response = rb_funcall(stream, rb_intern("read"), 0);
9
+ if (response == Qnil) {
10
+ return empty;
11
+ }
12
+ else {
13
+ // Attempt TO_S first before complaining?
14
+ if (TYPE(response) != T_STRING) {
15
+ rb_raise(
16
+ CONST_GET(rb_mKernel, "ArgumentError"),
17
+ "Write can only process string data. You need to stringify values returned in the callback."
18
+ );
19
+ }
20
+ data = string(RSTRING_PTR(response), RSTRING_LEN(response));
21
+ return data;
22
+ }
23
+ }
24
+
25
+ uint32_t IOStream::read(char *buffer, uint32_t length) {
26
+ VALUE response = rb_funcall(stream, rb_intern("read"), 1, INT2NUM(length));
27
+ if (response == Qnil) {
28
+ return 0;
29
+ }
30
+ else {
31
+ length = length < RSTRING_LEN(response) ? length : RSTRING_LEN(response);
32
+ memcpy(buffer, RSTRING_PTR(response), length);
33
+ return length;
34
+ }
35
+ }
36
+
37
+ void IOStream::write(const char *str) {
38
+ rb_funcall(stream, rb_intern("write"), 1, rb_str_new2(str));
39
+ }
40
+
41
+ void IOStream::write(const char *str, uint64_t l) {
42
+ rb_funcall(stream, rb_intern("write"), 1, rb_str_new(str, l));
43
+ }
44
+
data/ext/iostream.h ADDED
@@ -0,0 +1,17 @@
1
+ #ifndef SWIFT_IOSTREAM_H
2
+ #define SWIFT_IOSTREAM_H
3
+
4
+ #include "swift.h"
5
+
6
+ class IOStream : public dbi::IOStream {
7
+ private:
8
+ VALUE stream;
9
+ public:
10
+ IOStream(VALUE);
11
+ std::string& read();
12
+ uint32_t read(char *, uint32_t);
13
+ void write(const char *);
14
+ void write(const char *, uint64_t);
15
+ };
16
+
17
+ #endif