swift 0.4.1
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/LICENSE +20 -0
- data/README.rdoc +236 -0
- data/Rakefile +40 -0
- data/VERSION +1 -0
- data/examples/async.rb +60 -0
- data/examples/db.rb +40 -0
- data/examples/scheme.rb +46 -0
- data/ext/extconf.rb +39 -0
- data/ext/swift.cc +756 -0
- data/lib/swift.rb +48 -0
- data/lib/swift/adapter.rb +120 -0
- data/lib/swift/attribute.rb +25 -0
- data/lib/swift/db.rb +39 -0
- data/lib/swift/header.rb +45 -0
- data/lib/swift/identity_map.rb +41 -0
- data/lib/swift/pool.rb +74 -0
- data/lib/swift/scheme.rb +70 -0
- data/lib/swift/type.rb +12 -0
- data/swift.gemspec +75 -0
- data/test/helper.rb +31 -0
- data/test/house-explode.jpg +0 -0
- data/test/test_adapter.rb +127 -0
- data/test/test_encoding.rb +40 -0
- data/test/test_identity_map.rb +17 -0
- data/test/test_io.rb +27 -0
- data/test/test_timestamps.rb +27 -0
- metadata +109 -0
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2010 Stateless Systems
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,236 @@
|
|
1
|
+
= Swift
|
2
|
+
|
3
|
+
* http://github.com/shanna/swift
|
4
|
+
|
5
|
+
== Description
|
6
|
+
|
7
|
+
A rational rudimentary object relational mapper.
|
8
|
+
|
9
|
+
== Dependencies
|
10
|
+
|
11
|
+
* ruby >= 1.9.1
|
12
|
+
* dbic++ >= 0.1.6
|
13
|
+
* mysql >= 5.0.17 or postgresql >= 8.4
|
14
|
+
|
15
|
+
dbic++ can be found here http://github.com/deepfryed/dbicpp
|
16
|
+
|
17
|
+
== Features
|
18
|
+
|
19
|
+
* Multiple databases.
|
20
|
+
* Prepared statements.
|
21
|
+
* Bind values.
|
22
|
+
* Transactions and named save points.
|
23
|
+
* EventMachine asynchronous interface.
|
24
|
+
* Migrations.
|
25
|
+
|
26
|
+
== Performance
|
27
|
+
|
28
|
+
Swift prefers performance when it doesn't compromise the Ruby-ish interface. It's unfair to compare Swift to DataMapper
|
29
|
+
and ActiveRecord which suffer under the weight of support for many more databases and legacy/alternative Ruby
|
30
|
+
implementations. That said obviously if Swift were slower it would be redundant so benchmark code does exist in
|
31
|
+
http://github.com/shanna/swift/tree/master/benchmarks
|
32
|
+
|
33
|
+
== Synopsis
|
34
|
+
|
35
|
+
=== DB
|
36
|
+
|
37
|
+
require 'swift'
|
38
|
+
|
39
|
+
Swift.trace true # Debugging.
|
40
|
+
Swift.setup :default, Swift::DB::Postgres, db: 'swift'
|
41
|
+
|
42
|
+
# Block form db context.
|
43
|
+
Swift.db do |db|
|
44
|
+
db.execute('drop table if exists users')
|
45
|
+
db.execute('create table users(id serial, name text, email text)')
|
46
|
+
|
47
|
+
# Save points are supported.
|
48
|
+
db.transaction :named_save_point do
|
49
|
+
st = db.prepare('insert into users (name, email) values (?, ?)')
|
50
|
+
puts st.execute('Apple Arthurton', 'apple@arthurton.local').insert_id
|
51
|
+
puts st.execute('Benny Arthurton', 'benny@arthurton.local').insert_id
|
52
|
+
end
|
53
|
+
|
54
|
+
# Block result iteration.
|
55
|
+
db.prepare('select * from users').execute do |row|
|
56
|
+
puts row.inspect
|
57
|
+
end
|
58
|
+
|
59
|
+
# Enumerable.
|
60
|
+
result = db.prepare('select * from users where name like ?').execute('Benny%')
|
61
|
+
puts result.first
|
62
|
+
end
|
63
|
+
|
64
|
+
=== DB Scheme Operations
|
65
|
+
|
66
|
+
Rudimentary object mapping. Provides a definition to the db methods for prepared (and cached) statements plus native
|
67
|
+
primitive Ruby type conversion.
|
68
|
+
|
69
|
+
require 'swift'
|
70
|
+
|
71
|
+
Swift.trace true # Debugging.
|
72
|
+
Swift.setup :default, Swift::DB::Postgres, db: 'swift'
|
73
|
+
|
74
|
+
class User < Swift::Scheme
|
75
|
+
store :users
|
76
|
+
attribute :id, Swift::Type::Integer, serial: true, key: true
|
77
|
+
attribute :name, Swift::Type::String
|
78
|
+
attribute :email, Swift::Type::String
|
79
|
+
attribute :updated_at, Swift::Type::Time
|
80
|
+
end # User
|
81
|
+
|
82
|
+
Swift.db do |db|
|
83
|
+
db.migrate! User
|
84
|
+
|
85
|
+
# Select Scheme instance (relation) instead of Hash.
|
86
|
+
users = db.prepare(User, 'select * from users limit 1').execute
|
87
|
+
|
88
|
+
# Make a change and update.
|
89
|
+
users.each{|user| user.updated_at = Time.now}
|
90
|
+
db.update(User, *users)
|
91
|
+
|
92
|
+
# Get a specific user by id.
|
93
|
+
user = db.get(User, id: 1)
|
94
|
+
puts user.name, user.email
|
95
|
+
end
|
96
|
+
|
97
|
+
=== Scheme CRUD
|
98
|
+
|
99
|
+
Scheme/relation level helpers.
|
100
|
+
|
101
|
+
require 'swift'
|
102
|
+
|
103
|
+
Swift.trace true # Debugging.
|
104
|
+
Swift.setup :default, Swift::DB::Postgres, db: 'swift'
|
105
|
+
|
106
|
+
class User < Swift::Scheme
|
107
|
+
store :users
|
108
|
+
attribute :id, Swift::Type::Integer, serial: true, key: true
|
109
|
+
attribute :name, Swift::Type::String
|
110
|
+
attribute :email, Swift::Type::String
|
111
|
+
end # User
|
112
|
+
|
113
|
+
# Migrate it.
|
114
|
+
User.migrate!
|
115
|
+
|
116
|
+
# Create
|
117
|
+
User.create name: 'Apple Arthurton', email: 'apple@arthurton.local' # => User
|
118
|
+
|
119
|
+
# Get by key.
|
120
|
+
user = User.get id: 1
|
121
|
+
|
122
|
+
# Alter attribute and update in one.
|
123
|
+
user.update name: 'Jimmy Arthurton'
|
124
|
+
|
125
|
+
# Alter attributes and update.
|
126
|
+
user.name = 'Apple Arthurton'
|
127
|
+
user.update
|
128
|
+
|
129
|
+
# Destroy
|
130
|
+
user.destroy
|
131
|
+
|
132
|
+
=== Conditions SQL syntax.
|
133
|
+
|
134
|
+
SQL is easy and most people know it so Swift ORM provides a simple symbol like syntax to convert resource
|
135
|
+
names to field names.
|
136
|
+
|
137
|
+
class User < Swift::Scheme
|
138
|
+
store :users
|
139
|
+
attribute :id, Swift::Type::Integer, serial: true, key: true
|
140
|
+
attribute :age, Swift::Type::Integer, field: 'ega'
|
141
|
+
attribute :name, Swift::Type::String, field: 'eman'
|
142
|
+
attribute :email, Swift::Type::String, field: 'liame'
|
143
|
+
end # User
|
144
|
+
|
145
|
+
# Convert :name and :age to fields.
|
146
|
+
# select * from users where eman like '%Arthurton' and ega > 20
|
147
|
+
users = User.all(':name like ? and :age > ?', '%Arthurton', 20)
|
148
|
+
|
149
|
+
=== Identity Map
|
150
|
+
|
151
|
+
Swift comes with a simple identity map. Just require it after you load swift.
|
152
|
+
|
153
|
+
require 'swift'
|
154
|
+
require 'swift/identity_map'
|
155
|
+
|
156
|
+
class User < Swift::Scheme
|
157
|
+
store :users
|
158
|
+
attribute :id, Swift::Type::Integer, serial: true, key: true
|
159
|
+
attribute :age, Swift::Type::Integer, field: 'ega'
|
160
|
+
attribute :name, Swift::Type::String, field: 'eman'
|
161
|
+
attribute :email, Swift::Type::String, field: 'liame'
|
162
|
+
end # User
|
163
|
+
|
164
|
+
User.first(':name = ?', 'James Arthurton')
|
165
|
+
User.first(':name = ?', 'James Arthurton') # Gets same object reference
|
166
|
+
|
167
|
+
|
168
|
+
=== Bulk inserts
|
169
|
+
|
170
|
+
Swift comes with adapter level support for bulk inserts for MySQL and PostgreSQL. This
|
171
|
+
is usually very fast (~5-10x faster) than regular prepared insert statements for larger
|
172
|
+
sets of data.
|
173
|
+
|
174
|
+
MySQL adapter - Overrides the MySQL C API and implements its own _infile_ handlers. This
|
175
|
+
means currently you *cannot* execute the following SQL using Swift
|
176
|
+
|
177
|
+
LOAD DATA LOCAL INFILE '/tmp/users.tab' INTO TABLE users;
|
178
|
+
|
179
|
+
But you can do it almost as fast in ruby,
|
180
|
+
|
181
|
+
require 'swift'
|
182
|
+
|
183
|
+
Swift.setup :default, Swift::DB::Mysql, db: 'swift'
|
184
|
+
|
185
|
+
# MySQL packet size is the usual limit, 8k is the packet size by default.
|
186
|
+
Swift.db do |db|
|
187
|
+
File.open('/tmp/users.tab') do |file|
|
188
|
+
count = db.write('users', %w{name email balance}, file)
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
You are not just limited to files - you can stream data from anywhere into MySQL and
|
193
|
+
PostgreSQL directly without creating temporary files.
|
194
|
+
|
195
|
+
|
196
|
+
== Benchmarks
|
197
|
+
|
198
|
+
The following bechmarks were run on a machine with 4G ram, 5200rpm sata drive,
|
199
|
+
Intel Core2Duo P8700 2.53GHz and stock PostgreSQL 8.4.1.
|
200
|
+
|
201
|
+
* 10,000 rows are created once.
|
202
|
+
* All the rows are selected once.
|
203
|
+
* All the rows are selected once and updated once.
|
204
|
+
* Memory footprint(rss) shows how much memory the benchmark used with GC disabled.
|
205
|
+
This gives an idea of total memory use and indirectly an idea of the number of
|
206
|
+
objects allocated and the pressure on Ruby GC if it were running. When GC is enabled,
|
207
|
+
the actual memory consumption might be much lower than the numbers below.
|
208
|
+
|
209
|
+
./benchmarks/simple.rb -r 10000 -n 1
|
210
|
+
|
211
|
+
benchmark sys user total real rss
|
212
|
+
dm #create 0.390000 3.950000 4.340000 5.771812 244.32m
|
213
|
+
dm #select 0.150000 1.760000 1.910000 2.035583 128.97m
|
214
|
+
dm #update 0.690000 7.880000 8.570000 11.295239 603.30m
|
215
|
+
|
216
|
+
ar #create 0.930000 6.620000 7.550000 10.002911 367.82m
|
217
|
+
ar #select 0.050000 0.310000 0.360000 0.417127 38.82m
|
218
|
+
ar #update 0.770000 6.180000 6.950000 9.711788 361.93m
|
219
|
+
|
220
|
+
swift #create 0.180000 0.820000 1.000000 1.968757 27.35m
|
221
|
+
swift #select 0.010000 0.070000 0.080000 0.130234 9.85m
|
222
|
+
swift #update 0.250000 0.610000 0.860000 1.996165 29.35m
|
223
|
+
swift #write 0.000000 0.100000 0.100000 0.167199 6.23m
|
224
|
+
|
225
|
+
|
226
|
+
== TODO
|
227
|
+
|
228
|
+
* Tests.
|
229
|
+
* Assertions for dumb stuff. model < Model for methods in Adapter.
|
230
|
+
* Profile.
|
231
|
+
|
232
|
+
== Contributing
|
233
|
+
|
234
|
+
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
|
235
|
+
send a pull request, tweet, email or yell it at me line by line in person.
|
236
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'rake'
|
2
|
+
|
3
|
+
begin
|
4
|
+
require 'jeweler'
|
5
|
+
Jeweler::Tasks.new do |gem|
|
6
|
+
gem.name = 'swift'
|
7
|
+
gem.summary = %q{A rational rudimentary database abstraction.}
|
8
|
+
gem.description = %q{A rational rudimentary database abstraction.}
|
9
|
+
gem.email = %w{shane.hanna@gmail.com deepfryed@gmail.com}
|
10
|
+
gem.homepage = 'http://github.com/shanna/swift'
|
11
|
+
gem.authors = ["Shane Hanna", "Bharanee 'Barney' Rathna"]
|
12
|
+
gem.extensions = FileList['ext/extconf.rb']
|
13
|
+
gem.files.reject!{|f| f =~ %r{\.gitignore|examples|benchmarks|memory/.*}}
|
14
|
+
|
15
|
+
gem.add_development_dependency 'minitest', '>= 1.7.0'
|
16
|
+
end
|
17
|
+
Jeweler::GemcutterTasks.new
|
18
|
+
rescue LoadError
|
19
|
+
puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
|
20
|
+
end
|
21
|
+
|
22
|
+
require 'rake/testtask'
|
23
|
+
Rake::TestTask.new(:test) do |test|
|
24
|
+
test.libs << 'lib' << 'test'
|
25
|
+
test.pattern = 'test/**/test_*.rb'
|
26
|
+
test.verbose = true
|
27
|
+
end
|
28
|
+
|
29
|
+
task :test => :check_dependencies
|
30
|
+
task :default => :test
|
31
|
+
|
32
|
+
require 'rake/rdoctask'
|
33
|
+
Rake::RDocTask.new do |rdoc|
|
34
|
+
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
35
|
+
|
36
|
+
rdoc.rdoc_dir = 'rdoc'
|
37
|
+
rdoc.title = "swift #{version}"
|
38
|
+
rdoc.rdoc_files.include('README*')
|
39
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
40
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.4.1
|
data/examples/async.rb
ADDED
@@ -0,0 +1,60 @@
|
|
1
|
+
#!/usr/bin/ruby
|
2
|
+
|
3
|
+
require_relative '../lib/swift'
|
4
|
+
require_relative '../lib/swift/pool'
|
5
|
+
|
6
|
+
adapter = ARGV.first =~ /mysql/i ? Swift::DB::Mysql : Swift::DB::Postgres
|
7
|
+
puts "Using DB: #{adapter}"
|
8
|
+
|
9
|
+
Swift.setup :default, adapter, db: 'swift'
|
10
|
+
Swift.trace true
|
11
|
+
|
12
|
+
# create test table
|
13
|
+
Swift.db do |db|
|
14
|
+
puts '-- create --'
|
15
|
+
db.execute('DROP TABLE IF EXISTS users')
|
16
|
+
db.execute('CREATE TABLE users(id serial, name text, email text)')
|
17
|
+
|
18
|
+
sample = DATA.read.split(/\n/).map {|v| v.split(/\t+/) }
|
19
|
+
|
20
|
+
puts '-- insert --'
|
21
|
+
ins = db.prepare('insert into users(name, email) values(?, ?)')
|
22
|
+
10.times {|n| ins.execute(*sample[n%3]) }
|
23
|
+
end
|
24
|
+
|
25
|
+
puts '-- select 9 times with a pool of size 5 --'
|
26
|
+
Swift.trace false
|
27
|
+
Swift.pool(5) do |db|
|
28
|
+
(1..9).each do |n|
|
29
|
+
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 }
|
32
|
+
end
|
33
|
+
end
|
34
|
+
Swift.trace true
|
35
|
+
|
36
|
+
puts '-- multiple pools: size 2, size 1 --'
|
37
|
+
EM.run {
|
38
|
+
pool1 = Swift.pool(2)
|
39
|
+
pool2 = Swift.pool(1)
|
40
|
+
|
41
|
+
pool1.execute("select * from users limit 5 offset 0") do |rs|
|
42
|
+
puts '-- Inside pool1 #callback --'
|
43
|
+
rs.each {|r| p r }
|
44
|
+
pool1.execute("select * from users limit 5 offset 5") do |rs|
|
45
|
+
puts '-- Inside pool1 #callback again --'
|
46
|
+
rs.each {|r| p r }
|
47
|
+
EM.stop
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
pool2.execute("select * from users limit 5 offset 10") do |rs|
|
52
|
+
puts '-- Inside pool2 #callback --'
|
53
|
+
rs.each {|r| p r }
|
54
|
+
end
|
55
|
+
}
|
56
|
+
|
57
|
+
__END__
|
58
|
+
Apple Arthurton apple@example.com
|
59
|
+
Benny Arthurton benny@example.com
|
60
|
+
James Arthurton james@example.com
|
data/examples/db.rb
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require_relative '../lib/swift'
|
3
|
+
require 'pp'
|
4
|
+
|
5
|
+
class User < Swift::Scheme
|
6
|
+
store :users
|
7
|
+
attribute :id, Swift::Type::Integer, serial: true, key: true
|
8
|
+
attribute :name, Swift::Type::String
|
9
|
+
attribute :email, Swift::Type::String
|
10
|
+
end # User
|
11
|
+
|
12
|
+
adapter = ARGV.first =~ /mysql/i ? Swift::DB::Mysql : Swift::DB::Postgres
|
13
|
+
puts "Using DB: #{adapter}"
|
14
|
+
|
15
|
+
Swift.setup :default, adapter, db: 'swift'
|
16
|
+
Swift.trace true
|
17
|
+
|
18
|
+
Swift.db do |db|
|
19
|
+
db.migrate! User
|
20
|
+
|
21
|
+
puts '-- create --'
|
22
|
+
db.create(User,
|
23
|
+
{name: 'Apple Arthurton', email: 'apple@arthurton.local'},
|
24
|
+
{name: 'Benny Arthurton', email: 'benny@arthurton.local'}
|
25
|
+
)
|
26
|
+
|
27
|
+
puts '', '-- select --'
|
28
|
+
pp users = db.prepare(User, 'select * from users').execute.to_a
|
29
|
+
|
30
|
+
puts '', '-- update --'
|
31
|
+
db.update(User, *users.map!{|user| user.name = 'Fred Nurk'; user})
|
32
|
+
pp db.prepare(User, 'select * from users').execute.to_a
|
33
|
+
|
34
|
+
puts '', '-- get --'
|
35
|
+
pp db.get(User, id: 1)
|
36
|
+
|
37
|
+
puts '', '-- destroy --'
|
38
|
+
pp db.destroy(User, id: 1)
|
39
|
+
end
|
40
|
+
|
data/examples/scheme.rb
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require_relative '../lib/swift'
|
3
|
+
require 'pp'
|
4
|
+
|
5
|
+
class User < Swift::Scheme
|
6
|
+
store :users
|
7
|
+
attribute :id, Swift::Type::Integer, serial: true, key: true
|
8
|
+
attribute :name, Swift::Type::String
|
9
|
+
attribute :email, Swift::Type::String
|
10
|
+
attribute :active, Swift::Type::Boolean
|
11
|
+
attribute :created, Swift::Type::Time, default: proc { Time.now }
|
12
|
+
attribute :optional, Swift::Type::String, default: 'woot'
|
13
|
+
end # User
|
14
|
+
|
15
|
+
adapter = ARGV.first =~ /mysql/i ? Swift::DB::Mysql : Swift::DB::Postgres
|
16
|
+
puts "Using DB: #{adapter}"
|
17
|
+
|
18
|
+
Swift.setup :default, adapter, db: 'swift'
|
19
|
+
Swift.trace true
|
20
|
+
|
21
|
+
puts '-- migrate! --'
|
22
|
+
User.migrate!
|
23
|
+
|
24
|
+
puts '', '-- create --'
|
25
|
+
User.create name: 'Apple Arthurton', email: 'apple@arthurton.local'
|
26
|
+
User.create name: 'Benny Arthurton', email: 'benny@arthurton.local'
|
27
|
+
|
28
|
+
puts '', '-- all --'
|
29
|
+
pp User.all.to_a
|
30
|
+
# pp User.all(':name like ?', '%Arthurton').to_a
|
31
|
+
|
32
|
+
puts '', '-- first --'
|
33
|
+
pp User.first(':name like ?', '%Arthurton')
|
34
|
+
|
35
|
+
puts '', '-- get --'
|
36
|
+
pp user = User.get(id: 2)
|
37
|
+
pp user = User.get(id: 2)
|
38
|
+
|
39
|
+
puts '', '-- update --'
|
40
|
+
user.update(name: 'Jimmy Arthurton')
|
41
|
+
|
42
|
+
puts '', '-- destroy --'
|
43
|
+
user.destroy
|
44
|
+
|
45
|
+
puts '', '-- all --'
|
46
|
+
pp User.all.to_a
|