= Swift * http://github.com/shanna/swift == Description A rational rudimentary object relational mapper. == Dependencies * ruby >= 1.9.1 * dbic++ >= 0.4.0 (http://github.com/deepfryed/dbicpp) * mysql >= 5.0.17, postgresql >= 8.4 or sqlite3 >= 3.7 == Features * Multiple databases. * Prepared statements. * Bind values. * Transactions and named save points. * EventMachine asynchronous interface (mysql and postgresql). * IdentityMap. * Migrations. == Synopsis === DB require 'swift' Swift.trace true # Debugging. Swift.setup :default, Swift::DB::Postgres, db: 'swift' # Block form db context. Swift.db do |db| db.execute('drop table if exists users') db.execute('create table users(id serial, name text, email text)') # Save points are supported. db.transaction :named_save_point do st = db.prepare('insert into users (name, email) values (?, ?) returning id') puts st.execute('Apple Arthurton', 'apple@arthurton.local').insert_id puts st.execute('Benny Arthurton', 'benny@arthurton.local').insert_id end # Block result iteration. db.prepare('select * from users').execute do |row| puts row.inspect end # Enumerable. result = db.prepare('select * from users where name like ?').execute('Benny%') puts result.first end === DB Scheme Operations Rudimentary object mapping. Provides a definition to the db methods for prepared (and cached) statements plus native primitive Ruby type conversion. require 'swift' require 'swift/migrations' Swift.trace true # Debugging. Swift.setup :default, Swift::DB::Postgres, db: 'swift' class User < Swift::Scheme store :users attribute :id, Swift::Type::Integer, serial: true, key: true attribute :name, Swift::Type::String attribute :email, Swift::Type::String attribute :updated_at, Swift::Type::Time end # User Swift.db do |db| db.migrate! User # Select Scheme instance (relation) instead of Hash. users = db.prepare(User, 'select * from users limit 1').execute # Make a change and update. users.each{|user| user.updated_at = Time.now} db.update(User, *users) # Get a specific user by id. user = db.get(User, id: 1) puts user.name, user.email end === Scheme CRUD Scheme/relation level helpers. require 'swift' require 'swift/migrations' Swift.trace true # Debugging. Swift.setup :default, Swift::DB::Postgres, db: 'swift' class User < Swift::Scheme store :users attribute :id, Swift::Type::Integer, serial: true, key: true attribute :name, Swift::Type::String attribute :email, Swift::Type::String end # User # Migrate it. User.migrate! # Create User.create name: 'Apple Arthurton', email: 'apple@arthurton.local' # => User # Get by key. user = User.get id: 1 # Alter attribute and update in one. user.update name: 'Jimmy Arthurton' # Alter attributes and update. user.name = 'Apple Arthurton' user.update # Destroy user.destroy === Conditions SQL syntax. SQL is easy and most people know it so Swift ORM provides a simple symbol like syntax to convert resource names to field names. class User < Swift::Scheme store :users attribute :id, Swift::Type::Integer, serial: true, key: true attribute :age, Swift::Type::Integer, field: 'ega' attribute :name, Swift::Type::String, field: 'eman' attribute :email, Swift::Type::String, field: 'liame' end # User # Convert :name and :age to fields. # select * from users where eman like '%Arthurton' and ega > 20 users = User.all(':name like ? and :age > ?', '%Arthurton', 20) === Identity Map Swift comes with a simple identity map. Just require it after you load swift. require 'swift' require 'swift/identity_map' require 'swift/migrations' class User < Swift::Scheme store :users attribute :id, Swift::Type::Integer, serial: true, key: true attribute :age, Swift::Type::Integer, field: 'ega' attribute :name, Swift::Type::String, field: 'eman' attribute :email, Swift::Type::String, field: 'liame' end # User # Migrate it. User.migrate! # Create User.create name: 'James Arthurton', email: 'james@arthurton.local' # => User User.first(':name = ?', 'James Arthurton') User.first(':name = ?', 'James Arthurton') # Gets same object reference === Bulk inserts Swift comes with adapter level support for bulk inserts for MySQL and PostgreSQL. This is usually very fast (~5-10x faster) than regular prepared insert statements for larger sets of data. MySQL adapter - Overrides the MySQL C API and implements its own _infile_ handlers. This means currently you *cannot* execute the following SQL using Swift LOAD DATA LOCAL INFILE '/tmp/users.tab' INTO TABLE users; But you can do it almost as fast in ruby, require 'swift' Swift.setup :default, Swift::DB::Mysql, db: 'swift' # MySQL packet size is the usual limit, 8k is the packet size by default. Swift.db do |db| File.open('/tmp/users.tab') do |file| count = db.write('users', %w{name email balance}, file) end end You are not just limited to files - you can stream data from anywhere into your database without creating temporary files. == Performance Swift prefers performance when it doesn't compromise the Ruby-ish interface. It's unfair to compare Swift to DataMapper and ActiveRecord which suffer under the weight of support for many more databases and legacy/alternative Ruby implementations. That said obviously if Swift were slower it would be redundant so benchmark code does exist in http://github.com/shanna/swift/tree/master/benchmarks === Benchmarks ==== ORM The following bechmarks were run on a machine with 4G ram, 5200rpm sata drive, Intel Core2Duo P8700 2.53GHz and stock PostgreSQL 8.4.1. * 10,000 rows are created once. * All the rows are selected once. * All the rows are selected once and updated once. * Memory footprint(rss) shows how much memory the benchmark used with GC disabled. This gives an idea of total memory use and indirectly an idea of the number of objects allocated and the pressure on Ruby GC if it were running. When GC is enabled, the actual memory consumption might be much lower than the numbers below. ./simple.rb -n1 -r10000 -s ar -s dm -s sequel -s swift benchmark sys user total real rss ar #create 0.790000 8.290000 9.08000 11.679886 405.07m ar #select 0.040000 0.310000 0.35000 0.383573 40.56m ar #update 0.720000 9.890000 10.6100 13.765735 503.48m dm #create 0.310000 3.300000 3.61000 4.593075 211.01m dm #select 0.040000 1.720000 1.76000 1.776852 114.51m dm #update 0.450000 7.600000 8.05000 9.610320 531.26m sequel #create 0.670000 4.670000 5.34000 7.991811 235.39m sequel #select 0.000000 0.130000 0.13000 0.178447 12.76m sequel #update 0.790000 4.540000 5.33000 7.854936 229.70m swift #create 0.100000 0.710000 0.81000 1.562289 85.84m swift #select 0.000000 0.120000 0.12000 0.145567 8.96m swift #update 0.190000 0.690000 0.88000 1.628918 59.50m -- bulk insert api -- swift #write 0.010000 0.100000 0.11000 0.180514 14.80m ==== Adapter The adapter level SELECT benchmarks without using ORM. * Same dataset as above. * All rows are selected 5 times. * The pg benchmark uses pg_typecast gem to provide typecasting support for pg gem and also makes the benchmarks more fair. ===== PostgreSQL benchmark sys user total real rss do #select 0.020000 1.250000 1.270000 1.441281 71.98m pg #select 0.000000 0.580000 0.580000 0.769186 42.93m swift #select 0.040000 0.510000 0.550000 0.627581 43.23m ===== MySQL benchmark sys user total real rss do #select 0.030000 1.130000 1.160000 1.172205 71.86m mysql2 #select 0.040000 0.660000 0.700000 0.704414 72.72m swift #select 0.010000 0.480000 0.490000 0.499643 42.03m == TODO * More tests. * Assertions for dumb stuff. * Abstract interface for other adapters? Move dbic++ to Swift::DBI::(Adapter, Pool, Result, Statment etc.) == Contributing 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 send a pull request, tweet, email or yell it at me line by line in person. == Feature suggestions and support {Suggest features and support Swift ORM on fundry.}[https://fundry.com/project/14-swift-orm]