Swift
=====

* [source](http://github.com/shanna/swift)
* [documentation](http://rubydoc.info/gems/swift/file/README.md)

## Description

A rational rudimentary object relational mapper.

## Dependencies

* MRI Ruby >= 1.9.1
* swift-db-sqlite3 or swift-db-postgres or swift-db-mysql

## Installation

### Dependencies

Install one of the following drivers you would like to use.

```
gem install swift-db-sqlite3
gem install swift-db-postgres
gem install swift-db-mysql
```

### Install Swift

```
gem install swift
```

## Features

* Multiple databases.
* Prepared statements.
* Bind values.
* Transactions and named save points.
* Asynchronous API for PostgreSQL and MySQL.
* IdentityMap.
* Migrations.

### DB

```ruby
  require 'swift'
  require 'swift/adapter/postgres'

  Swift.trace true # Debugging.
  Swift.setup :default, Swift::Adapter::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 Record Operations

Rudimentary object mapping. Provides a definition to the db methods for prepared (and cached) statements plus native
primitive Ruby type conversion.

```ruby
  require 'swift'
  require 'swift/adapter/postgres'
  require 'swift/migrations'

  Swift.trace true # Debugging.
  Swift.setup :default, Swift::Adapter::Postgres, db: 'swift'

  class User < Swift::Record
    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::DateTime
  end # User

  Swift.db do |db|
    db.migrate! User

    # Select Record 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
```

### Record CRUD

Record/relation level helpers.

```ruby
  require 'swift'
  require 'swift/adapter/postgres'
  require 'swift/migrations'

  Swift.trace true # Debugging.
  Swift.setup :default, Swift::Adapter::Postgres, db: 'swift'

  class User < Swift::Record
    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.delete
```

### Conditions SQL syntax.

SQL is easy and most people know it so Swift ORM provides simple #to_s
attribute to table and field name typecasting.

```ruby
  class User < Swift::Record
    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.execute(
    %Q{select * from #{User} where #{User.name} like ? and #{User.age} > ?},
    '%Arthurton', 20
  )
```

### Identity Map

Swift comes with a simple identity map. Just require it after you load swift.

```ruby
  require 'swift'
  require 'swift/adapter/postgres'
  require 'swift/identity_map'
  require 'swift/migrations'

  Swift.setup :default, Swift::Adapter::Postgres, db: 'swift'

  class User < Swift::Record
    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

  find_user = User.prepare(%Q{select * from #{User} where #{User.name = ?})
  find_user.execute('James Arthurton')
  find_user.execute('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

```sql
  LOAD DATA LOCAL INFILE '/tmp/users.tab' INTO TABLE users;
```

But you can do it almost as fast in ruby,

```ruby
  require 'swift'
  require 'swift/adapter/mysql'

  Swift.setup :default, Swift::Adapter::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.

### Asynchronous API

`Swift::Adapter::Sql#query` runs a query asynchronously. You can either poll the corresponding
`Swift::Adapter::Sql#fileno` and then call `Swift::Adapter::Sql#result` when ready or use a block form like below
which implicitly uses `rb_thread_wait_fd`

```ruby
  require 'swift'
  require 'swift/adapter/postgres'

  pool = 3.times.map.with_index {|n| Swift.setup n, Swift::Adapter::Postgres, db: 'swift' }

  Thread.new do
    pool[0].query('select pg_sleep(3), 1 as qid') {|row| p row}
  end

  Thread.new do
    pool[1].query('select pg_sleep(2), 2 as qid') {|row| p row}
  end

  Thread.new do
    pool[2].query('select pg_sleep(1), 3 as qid') {|row| p row}
  end

  Thread.list.reject {|thread| Thread.current == thread}.each(&:join)
```

or use the `swift/eventmachine` api.

```ruby
  require 'swift'
  require 'swift/adapter/em/postgres'

  EM.run do
    pool = 3.times.map { Swift.setup(:default, Swift::Adapter::EM::Postgres, db: "swift") }

    3.times.each do |n|
      defer = pool[n].execute("select pg_sleep(3 - #{n}), #{n + 1} as qid")

      defer.callback do |res|
        p res.first
      end

      defer.errback do |e|
        p 'error', e
      end
    end
  end
```

or use the `em-synchrony` api for `swift`

```ruby
  require 'swift'
  require 'swift/adapter/synchrony/postgres'

  EM.run do
    3.times.each do |n|
      EM.synchrony do
        db     = Swift.setup(:default, Swift::Adapter::Synchrony::Postgres, db: "swift")
        result = db.execute("select pg_sleep(3 - #{n}), #{n + 1} as qid")

        p result.first
        EM.stop if n == 0
      end
    end
  end
```

### Fibers and Connection Pools

If you intend to use `Swift::Record` with `em-synchrony` you will need to use a fiber aware connection pool.

```ruby
require 'swift/fiber_connection_pool'

EM.run do
  Swift.setup(:default) do
    Swift::FiberConnectionPool.new(size: 5) {Swift::Adapter::Synchrony::Postgres.new(db: 'swift')}
  end

  5.times do
    EM.synchrony do
      p User.execute("select * from users").entries
    end
  end
end
```

## 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 test environment:

```
$ uname -a

Linux deepfryed.local 3.0.0-1-amd64 #1 SMP Sun Jul 24 02:24:44 UTC 2011 x86_64 GNU/Linux

$ cat /proc/cpuinfo | grep "processor\|model name"

processor    : 0
model name   : Intel(R) Core(TM) i7-2677M CPU @ 1.80GHz
processor    : 1
model name   : Intel(R) Core(TM) i7-2677M CPU @ 1.80GHz
processor    : 2
model name   : Intel(R) Core(TM) i7-2677M CPU @ 1.80GHz
processor    : 3
model name   : Intel(R) Core(TM) i7-2677M CPU @ 1.80GHz

$ ruby -v

ruby 1.9.3p125 (2012-02-16 revision 34643) [x86_64-linux]
```

PostgreSQL config:

```
shared_buffers           = 800MB     # min 128kB
effective_cache_size     = 512MB
work_mem                 = 64MB      # min 64kB
maintenance_work_mem     = 64MB      # min 1MB
```

The test setup:

* 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          1.960000    15.81000   17.770000   22.753109   266.21m
    ar #select          0.020000     0.38000    0.400000    0.433041    50.82m
    ar #update          2.000000    17.90000   19.900000   26.674921   317.48m

    dm #create          0.660000    11.55000   12.210000   15.592424   236.86m
    dm #select          0.030000     1.30000    1.330000    1.351911    87.18m
    dm #update          0.950000    17.25000   18.200000   22.109859   474.81m

    sequel #create      1.960000    14.48000   16.440000   23.004864   226.68m
    sequel #select      0.000000     0.09000    0.090000    0.134619    12.77m
    sequel #update      1.900000    14.37000   16.270000   22.945636   200.20m

    swift #create       0.520000     1.95000    2.470000    5.828846    75.26m
    swift #select       0.010000    0.070000    0.080000    0.095124    11.23m
    swift #update       0.440000     1.95000    2.390000    6.044971    59.35m
    swift #write        0.010000    0.050000    0.060000    0.117195    13.46m

```

## TODO

* More tests.
* Assertions for dumb stuff.
* Auto-generate schema?
* Move examples to Wiki. Examples of models built on top of Schema.

## 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.
