swift 0.4.3 → 0.5.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 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