= 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 or postgresql >= 8.4 or db2 >= 9.7.2

== Caveats

=== DB2
* The server needs to be running under DB2_COMPATIBILITY_VECTOR=77FF if you want to use the ORM
  features of swift.
* DB2 asynchronous operations are highly experimental at this point due to inherent limitations of the
  underlying api. It is more an academic exercise and is not ready for real-world use.

== Features

* Multiple databases.
* Prepared statements.
* Bind values.
* Transactions and named save points.
* EventMachine asynchronous interface.
* 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, PostgreSQL and DB2. 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.800000  6.620000  7.420000  9.898821  369.44m
    ar #select      0.020000  0.300000  0.320000  0.372809  38.83m
    ar #update      0.770000  6.550000  7.320000  10.02434  361.92m

    dm #create      0.110000  3.590000  3.700000  4.847609  248.74m
    dm #select      0.120000  1.840000  1.960000  2.029552  128.98m
    dm #update      0.400000  7.750000  8.150000  9.741249  599.52m

    sequel #create  0.770000  3.910000  4.680000  7.432611  263.59m
    sequel #select  0.020000  0.080000  0.100000  0.147321  9.82m
    sequel #update  0.730000  3.910000  4.640000  7.594949  259.18m

    swift #create   0.210000  0.850000  1.060000  2.618661  32.72m
    swift #select   0.000000  0.080000  0.080000  0.132245  9.91m
    swift #update   0.270000  0.650000  0.920000  2.204108  37.48m

    -- bulk insert api --
    swift #write    0.000000  0.080000  0.080000  0.151146  7.29m


==== 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.060000  1.070000  1.130000  1.370092  99.83m
    pg #select      0.030000  0.270000  0.300000  0.584091  46.13m
    swift #select   0.020000  0.290000  0.310000  0.571635  39.08m

===== MySQL

    benchmark       sys       user      total     real      rss
    do #select      0.030000  1.070000  1.100000  1.200177  99.26m
    mysql2 #select  0.060000  0.450000  0.510000  0.609236  76.44m
    swift #select   0.050000  0.170000  0.220000  0.334932  33.61m

== TODO

* More tests.
* Make db2 async api more stable.
* 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.
