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 +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
|