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 +90 -0
- data/README.rdoc +14 -6
- data/Rakefile +1 -0
- data/VERSION +1 -1
- data/examples/async.rb +11 -6
- data/examples/db.rb +5 -1
- data/examples/scheme.rb +10 -2
- data/ext/adapter.cc +259 -0
- data/ext/adapter.h +13 -0
- data/ext/extconf.rb +21 -0
- data/ext/iostream.cc +44 -0
- data/ext/iostream.h +17 -0
- data/ext/pool.cc +89 -0
- data/ext/pool.h +8 -0
- data/ext/query.cc +38 -0
- data/ext/query.h +17 -0
- data/ext/request.cc +44 -0
- data/ext/request.h +10 -0
- data/ext/result.cc +246 -0
- data/ext/result.h +17 -0
- data/ext/statement.cc +87 -0
- data/ext/statement.h +12 -0
- data/ext/swift.cc +31 -739
- data/ext/swift.h +35 -0
- data/lib/swift/adapter.rb +4 -7
- data/lib/swift/attribute.rb +8 -2
- data/lib/swift/db.rb +7 -16
- data/lib/swift/identity_map.rb +8 -0
- data/lib/swift/migrations.rb +15 -0
- data/lib/swift/pool.rb +2 -1
- data/lib/swift/scheme.rb +0 -8
- data/lib/swift/validations.rb +27 -0
- data/lib/swift.rb +0 -5
- data/swift.gemspec +40 -11
- data/test/helper.rb +6 -6
- data/test/test_adapter.rb +9 -16
- data/test/test_error.rb +26 -0
- data/test/test_io.rb +6 -4
- data/test/test_pool.rb +1 -1
- data/test/test_scheme.rb +51 -0
- data/test/test_timestamps.rb +27 -14
- data/test/test_transactions.rb +76 -0
- data/test/test_validations.rb +46 -0
- metadata +55 -10
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
|
-
*
|
230
|
-
*
|
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
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
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
|
-
|
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 =
|
31
|
-
db.execute("select #{pause}
|
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
|
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
|
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
|
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
data/examples/scheme.rb
CHANGED
@@ -1,6 +1,11 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
|
-
|
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
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
|