<file_sep>module Alignment
  # An Entry represents a single row or column of an alignment. It is generally
  # recommended that instead of generating these objects directly they are
  # instead accessed through the corresponding methods in Alignment::Instance.
  module Entry
    module ClassMethods
      # This is the table in the SQL database to store the entry in.
      attr_accessor :sql_table 
      alias :table :sql_table=

      # This is the key in the database to use with alignment_id for identifying
      attr_accessor :sql_key
      alias :key :sql_key=

      # This is the prefix to use when adding to the sequence database.
      attr_accessor :tt_prefix
      alias :prefix :tt_prefix=

      def sequence_of(instance, index)
          return Sequence.new(instance, self.tt_prefix, index).sequence
      end

      # Get the dataset for this entry.
      #
      # @return [Sequel::Dataset] The dataset.
      def dataset(instance)
        return instance.sql[@sql_table].filter(:alignment_id => instance[:id])
      end
    end

    module InstanceMethods
      # The instance this entry is a part of.
      attr_reader :instance

      # Create a new Entry.
      #
      # @param [Alignmnet::Instance] sql The instance this entry belongs to.
      # @param [String] entry_id The id of this Entry.
      def initialize(instance, entry_id)
        alignment_id = instance[:id]
        query = { 
          :alignment_id => alignment_id, 
          self.class.sql_key => entry_id
        }
        @instance = instance
        sql = instance.sql
        @meta = Alignment::MetaData.new(sql[self.class.sql_table], query)
        prefix = self.class.tt_prefix
        @sequence = Alignment::Sequence.new(instance, prefix, @meta[:index])
      end

      # Get the value of some key in this entry.
      # 
      # @param [String] key The key.
      # @return [String, nil] The value.
      def [](key)
        return (key == :sequence ? @sequence.sequence : @meta[key])
      end

      # Set the value of some key.
      #
      # @param [String] key The key to set.
      # @param [String] value The value.
      # @return [Alignment::Entry] This entry.
      def []=(key, value)
        (key == :sequence ? @sequence.sequence = value : @meta[key] = value)
        return self
      end

      # Test if this entry is new. Something is new if there is no saved meta
      # data about it.
      #
      # @return [Boolean] true if this is new.
      def new?
        return @meta.new?
      end

      # Save this entry. This will throw an error if the entry already exists or
      # if some problem occurs while saving.
      #
      # @return [Alignment::Entry] The entry.
      def save!
        @meta.save!
        @sequence.index = @meta[:index]
        @sequence.save!
        return self
      end

      # Delete all information associated with a specific entry.
      #
      # @return [Alignment::Entry] This entry.
      def delete!
        if new?
          raise ArgumentError, "Cannot delete new Entry"
        end
        @meta.delete!
        @sequence.delete!
        return self
      end
    end

    def self.included(receiver)
      receiver.extend         ClassMethods
      receiver.send :include, InstanceMethods
    end
  end
end
<file_sep>module Alignment
  class Import
    attr_accessor :parser

    def initialize(instance)
      @alignment = instance
      @alignment.save!
      @rows = []
      @columns = []
    end

    def save!
      @rows.each_slice(100) do |rows|
        @alignment.sql[:alignment_rows].multi_insert(rows)
      end
      @columns.each_slice(100) do |columns|
        @alignment.sql[:alignment_columns].multi_insert(columns)
      end
    end

    def rows(row_file)
      load_file(row_file, :row) do |header, index|
        @rows << header.merge(:index => index, :alignment_id => @alignment[:id])
      end
    end

    def columns(column_file)
      load_file(column_file, :column) do |header, index|
        @columns << header.merge(:index => index, :alignment_id => @alignment[:id])
      end
    end

    private
      def load_file(name, type)
        Fasta.open(name) do |file|
          index = 0
          prefix = type.to_s
          file.each do |header, sequence|
            header_data = @parser.send(type, header)
            yield header_data,index
            sequence.gsub!('t', 'u')
            sequence.gsub!('.', '-')
            seq = Alignment::Sequence.new(@alignment, prefix, index)
            seq.sequence = sequence
            seq.save!
            index += 1
          end
        end
      end
  end
end
<file_sep>require 'set'

module Alignment
  # An instance is one specific alignment. This provides a easy way to access
  # and manipulate the metadata as well as sequence data for the alignment. This
  # can also find sub alignments efficiently.
  class Instance
    extend Forwardable

    # Options for aggregating in some methods.
    OPTIONS = { :aggregate => true }.freeze

    # The SQL database we connect to.
    attr_reader :sql 
    
    # The Tokyo Tyrant DB to connect to.
    attr_reader :tt

    # Force the method for accessing by either row or sequence.
    attr_reader :access_method

    # Method use to access by either row or column.
    attr_reader :method_used

    def_delegators :@data, :new?, :[], :[]=

    # Create a new Instance.
    #
    # @param [Rufus::Tokyo::Tyrant] db The db to connect to.
    # @param [String] id The ID of this alignment
    def initialize(database, query)
      @sql = database.sql
      @tt = database.tt
      @data = Alignment::MetaData.new(@sql[:alignments], query)
    end

    # Create a new Alignment::Import object for this class.
    #
    # @return [Alignment::Import] An importer for this class.
    def importer
      return Alignment::Import.new(self)
    end

    # Get the row count.
    #
    # @return [Sequel::Dataset] The dataset for each row.
    def rows
      return Alignment::Row.dataset(self)
    end

    # Get the column count.
    #
    # @return [Integer] The count of all columns.
    def columns
      return Alignment::Column.dataset(self)
    end

    # Get the data for a row with the given column id.
    #
    # @param [String] row_id The row id.
    # @return [Hash] A Hash with the information for the given row. Empty if no
    # such row exists.
    def row(row_id)
      return Alignment::Row.new(self, row_id)
    end

    # Get the data for a column with the given column id.
    #
    # @param [String] column_id The column id.
    # @return [Hash] A Hash with the information for the given column. Empty if
    # no such column exists.
    def column(column_id)
      return Alignment::Column.new(self, column_id)
    end

    # Save the metadata for this Instance.
    #
    # @return [Alignment::Instance] This instance.
    def save!
      @data.save!
      return self
    end

    # Delete all metadata for this Instance.
    #
    # @return [Alignment::Instance] This instance.
    def delete!
      @data.delete!
      return self
    end

    # Compute the unique sequences and their ounts for some row, column slice of
    # this alignment.
    #
    # @param [Array<String>] row_ids The ids of each row.
    # @param [Array<Array<String>>] column_ids The ids of each column.
    # @return [Hash<String, Integer>] Hash of each unique seqeunce and it's
    # counts.
    def variations(rows, columns, options = {})
      opts = OPTIONS.merge(options)
      vars = Hash.new { |h, k| h[k] = (opts[:aggregate] ? 0 : []) }
      each_subsequence(rows, columns) do |r, c, sequence|
        if opts[:aggregate]
          vars[sequence] += 1
        else
          vars[sequence] << r
          vars[sequence].uniq!
        end
      end
      return vars
    end

    # Find all unique subsequences for the given row, column slice.
    #
    # @param [Array<String>] row_ids The row ids.
    # @param [Array<Array<String>>] column_ids The column ids.
    # @return [Array<String>] THe unique subsequences.
    def unique_sequences(row_ids, column_ids)
      unique = Set.new
      each_subsequence(row_ids, column_ids) do |_, _, sequence|
        unique << sequence
      end
      return unique.to_a
    end

    # Find all subsequences for the given row, column slice.
    #
    # @param [Array<String>] row_ids The row ids.
    # @param [Array<Array<String>>] column_ids The column ids.
    # @return [Array<String>] THe subsequences.
    def subsequences(row_ids, column_ids)
      subsequences = []
      each_subsequence(row_ids, column_ids) do |_, _, sequence|
        subsequences << sequence
      end
      return subsequences
    end

    # Iterate over the given rows or all if none are given.
    #
    # @param [Array<String>] row_ids The ids.
    # @yeilds row, sequence
    def each_row(row_ids = nil)
      row_ids ||= self.rows.select_map(:row_id)
      row_ids.each do |id|
        yield self.row(id)
      end
    end

    # Iterate over the given columns or all if none are given.
    #
    # @param [Array<String>] column_ids The ids to iterate over, if none is
    # given all columns are used.
    # @yeilds column A Column object.
    def each_column(column_ids = nil)
      column_ids ||= self.columns.select_map(:column_id)
      column_ids.each do |id|
        yield self.column(id)
      end
    end

    # Iterate over each subsequence in the given row and column set.
    def each_subsequence(rows, column_set)
      if (@access_method && @access_method == :row) ||
        (!@access_method && row_cost(rows, column_set) < column_cost(rows, column_set))
        @method_used = :row
        each_sub_by_row(rows, column_set) { |r, c, s| yield r, c, s }
      elsif (@access_method && @access_method == :column) || !@access_method
        @method_used = :column
        each_sub_by_col(rows, column_set) { |r, c, s| yield r, c, s }
      end
      @access_method = nil
    end

    private
      def query(term, ids)
        return (ids ? { term => ids} : {})
      end

      def each_sub_by_row(rows, column_set)
        columns = column_set.map do |cols| 
          cols.map { |c| column(c)[:index] }
        end
        query = (rows == :all || rows == 'all' ? {} : { :row_id => rows })
        all = self.rows.filter(query).all

        all.each do |row|
          row_id = row[:row_id]
          seq = Row.sequence_of(self, row[:index])
          columns.each do |cols| 
            yield row_id, cols, cols.map { |c| seq[c] }.join
          end
        end
      end

      def each_sub_by_col(row_ids, column_set)
        query = (rows == :all || rows == 'all' ? {} : { :row_id => row_ids })
        rows = self.rows.filter(query).select_map(:index)
        sequences = Array.new(rows.size) { |i| '' }

        column_set.each do |columns|
          cols = columns.map { |c| column(c) }
          cols.each do |col|
            seq = Column.sequence_of(self, col[:index])
            rows.each_with_index do |seq_index, array_index|
              sequences[array_index] << seq[seq_index]
            end
          end

          sequences.each_with_index do |sequence, index| 
            yield row_ids[index], columns, sequence
          end
          sequences = Array.new(rows.size) { |i| '' }
        end
      end

      def row_cost(rows, columns)
        return (rows == :all || rows == 'all' ? self.rows.count : rows.size)
      end

      def column_cost(rows, columns)
        return columns.flatten.uniq.size
      end
  end
end
<file_sep>require 'forwardable'

module Alignment
  # Class to manage sequences in the database.
  class Sequence
    extend Forwardable

    # Access and set the sequence. This only manipulates the data in memory.
    attr_accessor :sequence

    # The index to use for saving
    attr_accessor :index

    def_delegator :@sequence, :[]

    # Create a new sequence.
    #
    # @param [Rufus::Tokyo::Tyrant] tt The database to store the sequence in.
    # @param [String] key The key to access.
    def initialize(instance, prefix, index)
      @tt = instance.tt
      @id = instance[:id]
      @prefix = prefix
      @index = index
      if @index
        update_key
        @sequence = @tt[@key]
      end
    end

    # Remove the sequence from the database.
    #
    # @return [Alignment::Sequence] The sequence.
    def delete!
      update_key
      raise ArgumentError, "Must specify index to delete" if !@key
      @sequence = nil
      @tt.delete(@key)
      return self
    end

    # Update the sequence in the database.
    #
    # @return [Alignment::Sequence] The sequence.
    def save!
      update_key
      raise ArgumentError, "Must specify index to save" if !@key
      @tt[@key] = @sequence
      return self
    end

    private
      def update_key
        @key = (@index ? "%s-%s:%s" % [@id, @prefix, @index] : nil)
      end
  end
end
<file_sep>require 'tempfile'
require "alignment"
require "minitest/pride"
require "minitest/autorun"

# TokyoTyrant DB to connect to
temp_file = Tempfile.new(["alignment-test", ".tch"])
TT = Rufus::Tokyo::Cabinet.new(temp_file.path)

# SQL database to connect to
SQL = Sequel.sqlite
Sequel.extension :migration
Sequel::Migrator.apply(SQL, 'spec/migrations')
<file_sep>require "spec_helper"

describe 'Alignment::Import' do
  before do
    class Parser
      def row(l)
        return { :row_id => l.to_i }
      end
      def column(l)
        return { :column_id => l.to_i }
      end
    end
    conn = Alignment::Database.new(SQL, TT)
    alignment = conn.instance(:source => 'gg', :version => 1)
    @id = alignment[:id]
    @import = alignment.importer
    @import.parser = Parser.new
  end

  describe 'importing rows' do
    before do
      @dataset = SQL[:alignment_rows]
      @dataset.delete
      @import.rows('spec/files/simple_rows.fasta')
      @import.save!
    end
    it 'can import all meta data' do
      val = @dataset.select_order_map(:row_id)
      val.must_equal %w{10 3}
    end
    it 'can import all sequences' do
      val = TT["#{@id}-row:0"]
      val.must_equal '--a'
    end
  end

  describe 'importing columns' do
    before do
      @dataset = SQL[:alignment_columns]
      @dataset.delete
      @import.columns('spec/files/simple_columns')
      @import.save!
    end
    it 'can import all meta data' do
      val = @dataset.select_order_map(:column_id)
      val.must_equal %w{1 2 3}
    end
    it 'can imoprt all sequences' do
      val = TT["#{@id}-column:1"]
      val.must_equal '-u'
    end
  end
end
<file_sep>module Alignment
  # An Alignment Database is a way of managing both the sequence information as
  # well as the meta data associated with the sequence.
  class Database
    # The connection the SQL database.
    attr_reader :sql

    # The connection to the tokyo tyrant.
    attr_accessor :tt

    # Create a new connection to the alignment.
    #
    # @param [Hash] sequel_db Hash specifying which SQL database to connect to.
    # @param [String] tt_db The socket to connect to.
    def initialize(sequel, tt)
      @sql = sequel
      @tt = tt
    end

    # Get an instance using a specific query.
    #
    # @param [Hash] query The query to find the instance.
    # @return [Alignment::Instance] The instance.
    def instance(query)
      return Alignment::Instance.new(self, query)
    end

    # Close the connection to the database.
    #
    # @return [Alignment::Database] This database.
    def close
      @sql.close
      @tt.close
      return self
    end
  end
end
<file_sep>Alignment DB
============

This is a tool for accessing both the rows and columns of large alignments
rapidly. This also provides some simple analysis of the columns such as unique
sequences and counts of unique sequences. It is primarly geared towards rapid
analysis of small sections of large alignments but may be useful for other
things.
<file_sep>require "spec_helper"

describe Alignment::Sequence do
  before do
    TT['1-ex:0'] = 'bob'
    conn = Alignment::Database.new(SQL, TT)
    @instance = conn.instance(:source => 'test', :version => 1)
    @instance.save!
    @sequence = Alignment::Sequence.new(@instance, 'ex', 0)
  end

  after do
    TT.delete('1-ex:0')
  end

  describe 'building' do
    describe 'with a null index' do
      before do
        @sequence = Alignment::Sequence.new(@instance, 'ex', nil)
      end
      it 'does not load any sequence' do
        @sequence.sequence.must_equal nil
      end
      it 'cannot save' do
        lambda do
          @sequence.save!
        end.must_raise ArgumentError
      end
      it 'cannot delete' do
        lambda do
          @sequence.delete!
        end.must_raise ArgumentError
      end
    end
  end

  describe 'properties' do
    it 'has a sequence property as in the database' do
      val = @sequence.sequence
      val.must_equal 'bob'
    end
    it 'can access a sequence index using []' do
      val = @sequence[1]
      val.must_equal 'o'
    end
    it 'can access a sequence range using []' do
      val = @sequence[1..2]
      val.must_equal 'ob'
    end
  end

  describe 'editing the sequence' do
    before do
      @sequence.sequence = 'jones'
    end

    describe 'without saving' do
      it 'changes the in memory sequence' do
        @sequence.sequence.must_equal 'jones'
      end
      it 'does not change the sequence in the database' do
        TT['1-ex:0'].must_equal 'bob'
      end
    end

    describe 'with saving' do
      before do
        @sequence.save!
      end
      it 'changes the in memory sequence' do
        @sequence.sequence.must_equal 'jones'
      end
      it 'changes the sequence in the database' do
        TT['1-ex:0'].must_equal 'jones'
      end
    end
  end

  describe 'deleting a sequence' do
    before do
      @sequence.delete!
    end
    it 'sets the database sequence to nil' do
      TT['1-ex:0'].must_equal nil
    end
    it 'sets the in memory sequence to nil' do
      @sequence.sequence.must_equal nil
    end
  end
end
<file_sep>require "spec_helper"

describe 'Alignment::Instance' do
  def add(type, id, seq, index)
    entry = @alignment.send(type, id)
    entry[:sequence] = seq
    entry[:index] = index
    entry.save!
  end

  before do
    @conn = Alignment::Database.new(SQL, TT)
    @alignment = @conn.instance(:source => 'test', :version => 1)
    add(:row, '10', 'aa', 0)
    add(:row, '11', 'ag', 1)
    add(:row, '12', 'ac', 2)
    add(:row, '13', 'aa', 3)
    add(:column, '1', 'aaaa', 0)
    add(:column, '2', 'agca', 1)
    @alignment.save!
    # Run importer to import some data for testing
  end

  describe 'creating' do
    before do
      @alignment = @conn.instance(:source => 'a', :version => 2)
    end
    describe 'before saving' do
      it 'knows it is new' do
        @alignment.new?.must_equal true
      end
    end
  end

  describe 'editing the metadata' do
    describe 'after saving' do
      before do
        @alignment = @conn.instance(:source => 'test2', :version => 1)
        @alignment.save!
      end
      it 'can get the id' do
        @alignment[:id].wont_equal nil
      end
    end
  end

  describe 'accessing' do
    it 'can get a row' do
      row = @alignment.row('10')
      row[:row_id].must_equal '10'
      row[:sequence].must_equal 'aa'
      row[:index].must_equal(0)
    end
    it 'can get a column' do
      col = @alignment.column('1')
      col[:sequence].must_equal 'aaaa'
      col[:index].must_equal 0
    end
  end

  describe 'iterating' do
    describe 'over all rows' do
      it 'can get meta data' do
        ans = %w{10 11 12 13}
        val = []
        @alignment.each_row { |row| val << row[:row_id] }
        val.must_equal ans
      end
      it 'can get sequence' do
        ans = %w{aa ag ac aa}
        val = []
        @alignment.each_row { |row| val << row[:sequence] }
        val.must_equal ans
      end
    end

    describe 'over some rows' do
      it 'can get the meta data' do
        ids = %w{11 12}
        val = []
        @alignment.each_row(ids) { |row| val << row[:row_id] }
        val.must_equal ids
      end
      it 'can get the sequence' do
        ids = %w{11 12}
        ans = %w{ag ac}
        val = []
        @alignment.each_row(ids) { |row| val << row[:sequence] }
        val.must_equal ans
      end
    end

    describe 'over all columns' do
      it 'can get meta data' do
        ans = %w{1 2}
        val = []
        @alignment.each_column { |col| val << col[:column_id] }
        val.must_equal ans
      end
      it 'can get sequences' do
        ans = %w{aaaa agca}
        val = []
        @alignment.each_column { |col| val << col[:sequence] }
        val.must_equal ans
     end
    end

    describe 'over some columns' do
      before do
        @ids = %w{1}
        @val_ids = []
        @val_seqs = []
        @alignment.each_column(@ids) do |col|
          @val_ids << col[:column_id]
          @val_seqs << col[:sequence]
        end
      end
      it 'can get meta data' do
        @val_ids.must_equal %w{1}
      end
      it 'can get sequences' do
        @val_seqs.must_equal %w{aaaa}
      end
    end
  end

  describe 'analyzing' do
    describe 'generic analysis' do
      it 'can get all subsequences with counts' do
        rows = %w{10 11 13}
        cols = [%w{1 2}]
        ans = %w{aa ag aa}
        val = @alignment.subsequences(rows, cols)
        val.must_equal ans
      end
      it 'can get all unique subsequences with rows' do
        rows = %w{10 11 13}
        cols = [%w{1 2}]
        ans = %w{aa ag}
        val = @alignment.unique_sequences(rows, cols)
        val.must_equal ans
      end
    end

    describe 'when not aggregating' do
      it 'gets all subsequences and the rows' do
        rows = %w{10 11 13}
        cols = [%w{1 2}, %w{2 1}]
        ans = { 'aa' => %w{10 13}, 'ag' => %w{11}, 'ga' => %w{11} }
        val = @alignment.variations(rows, cols, :aggregate => false)
        val.must_equal ans
      end
    end

    describe 'when aggregating' do
      it 'can get all subsequences and counts' do
        rows = %w{10 11 13}
        cols = [%w{1 2}, %w{2 1}]
        ans = { 'aa' => 4, 'ag' => 1, 'ga' => 1 }
        val = @alignment.variations(rows, cols)
        val.must_equal ans
      end
    end
  end
end
<file_sep>$:.unshift("./lib")
require "alignment"
require "benchmark"

# Read only database
MAX_THREAD_COUNT = 10
connection = Alignment::Connection.new('/tmp/alignment_socket')
alignment = connection['greengenes', 'current']

id_count = 100
ids = alignment.info.keys.shuffle[0 .. id_count]
ids.delete('rows')

max_col = 300
col_count = 900
columns = []
col_count.times do |t| 
  c1 = rand(max_col) + 1
  c2 = rand(max_col) + 1
  columns << [c1, c2]
end

Benchmark.bmbm(5) do |x|
  x.report('100') do
    cols = columns[0..100]
    alignment.count_subsequences(ids, cols)
  end
  x.report('400') do
    cols = columns[0..200]
    alignment.count_subsequences(ids, cols)
  end
  x.report('900') do
    alignment.count_subsequences(ids, columns)
  end
  x.report('all') do
    cols = columns[0..20]
    rows = alignment.info.keys
    rows.delete('rows')
    alignment.count_subsequences(rows, cols)
  end
end
<file_sep>module Alignment
  class Column
    include Alignment::Entry

    table :alignment_columns
    prefix 'column'
    key :column_id
  end
end
<file_sep>Sequel.migration do
  change do
    create_table :entry_test do
      primary_key :id
      Integer :alignment_id
      Integer :entry_id
      Integer :index
      String :type
    end
  end
end
<file_sep>class Parser
  def parse(header)
    header << "AWESOME"
  end
end
<file_sep>Sequel.migration do
  change do
    create_table :alignment_rows do
      primary_key :id
      String :row_id, :length => 10
      Integer :index
      foreign_key :alignment_id, :alignments
    end

    create_table :alignment_columns do
      primary_key :id
      String :column_id, :length => 10
      Integer :index
      foreign_key :alignment_id, :alignments
    end
  end
end
<file_sep>module Alignment
  # MetaData represents data about sequences stored in an SQL database. This
  # manages accessing and manipulating the data.
  class MetaData
    extend Forwardable

    # Delegate accessing and setting methods
    def_delegators :@data, :[], :[]=

    # Create new MetaData.
    #
    # @param [Sequel::Dataset] dataset The dataset to use.
    # @param [Hash] search A search which should specify one row in the dataset.
    # If more than one row is found an error is raised. If no rows are found a
    # new in memory entry is created using the query.
    def initialize(dataset, search)
      @dataset = dataset.filter(search)
      count = @dataset.count
      if count > 1
        throw ArgumentError.new("Metadata may specify at most 1 row")
      end
      @data = (count == 0 ? search : @dataset.first)
    end

    # Check if this meta data is new. New means this is not yet stored in the
    # database. This does not insure that the database and in memory data are
    # synced.
    #
    # @return [Boolean] true if the meta data is new.
    def new?
      return @dataset.count == 0
    end

    # Save the meta data. If the data already exists it will be updated,
    # otherwise it is inserted to the database.
    #
    # @return [Alignment::MetaData] Returns itself
    def save!
      method = (new? ? :insert : :update)
      @dataset.send(method, @data)
      @data = @dataset.first
      return self
    end

    # Delete this meta data. If it does not exist nothing is done, otherwise the
    # data is removed both from the database and memory.
    #
    # @return [Alignment::MetaData] Returns itself
    def delete!
     @dataset.delete
     @data = {}
     return self 
    end
  end
end
<file_sep>module Alignment
  class Row
    include Alignment::Entry

    table :alignment_rows
    prefix 'row'
    key :row_id
  end
end
<file_sep>require "rufus/tokyo/tyrant"
require "sequel"

require "fasta"

require "alignment/database"
require "alignment/sequence"
require "alignment/meta_data"
require "alignment/entry"
require "alignment/row"
require "alignment/column"
require "alignment/instance"
require "alignment/import"

# This alignment class is intended to make it easy to store and manipulate large
# alignments rapidly. Alignments are a combination of Sequence information in a
# key value store and meta data in an SQL store. This manages both of them. The
# basic assumptions are that specific alignments are identified by source and
# version.
module Alignment
end
<file_sep>require "spec_helper"

describe Alignment::MetaData do
  before do
    @dataset = SQL[:meta_data]
    @dataset.delete
    @raw = [ { :bob => 'jones', :type => 'A', :id => 1 } ]
    @dataset.multi_insert(@raw)
    @data = Alignment::MetaData.new(@dataset, :id => 1)
  end

  after do
    @dataset.delete
  end

  describe 'properties' do
    describe 'for meta data that already exists' do
      it 'can access properties from the search using []' do
        @data[:id].must_equal 1
      end
      it 'can access properites from the db using []' do
        @data[:type] = 'A'
      end
    end

    describe 'for a new meta data' do
      before do
        @data = Alignment::MetaData.new(@dataset, :id => 2)
      end
      it 'can access properties from the search' do
        @data[:id].must_equal 2
      end
      it 'knows it is new' do
        @data.new?.must_equal true
      end
      it 'does not add things to the db' do
        @dataset.all.must_equal @raw
      end
    end
  end

  describe 'editing the meta data' do
    before do
      @data[:bob] = 'test'
    end

    describe 'without saving' do
      it 'changes the in memory data' do
        @data[:bob].must_equal 'test'
      end
      it 'does not change the data in SQL' do
        @dataset.all.must_equal @raw
      end
    end

    describe 'with saving' do
      before do
        @data.save!
      end
      it 'changes the data in memory' do
        @data[:bob].must_equal 'test'
      end
      it 'does not insert a new row' do
        @dataset.count.must_equal 1
      end
      it 'changes the data in SQL' do
        ans = @raw.first.merge(:bob => 'test')
        @dataset.first.must_equal ans
      end
    end
  end

  describe 'deleting' do
    describe 'an entry that does not exist' do
      before do
        @data = Alignment::MetaData.new(@dataset, :id => 2)
        @data.delete!
      end
      it 'does not change the database' do
        @dataset.all.must_equal @raw
      end
      it 'erases the data in memory' do
        @data[:bob].must_equal nil
      end
    end

    describe 'an entry that does exist' do
      before do
        @data.delete!
      end
      it 'erases in memory data' do
        @data['bob'].must_equal nil
      end
      it 'erases data in SQL' do
        @dataset.all.must_equal []
      end
    end
  end
end
<file_sep>require "spec_helper"

describe Alignment::Entry do
  before do
    connection = Alignment::Database.new(SQL, TT)
    @instance = connection.instance(:source => 'test', :version => 1)
    @instance.save!
    @klass = Class.new
    @klass.send(:include, Alignment::Entry)
    @klass.table :entry_test
    @klass.prefix 'ex'
    @klass.key :entry_id
  end

  after do
    @instance.sql[:entry_test].delete
  end

  describe 'class methods' do
    it 'can get the dataset for the entry' do
      ans = @instance.sql[:entry_test].filter(:alignment_id => 1)
      @klass.dataset(@instance).must_equal ans
    end
  end

  describe 'a new entry' do
    before do
      @data = @klass.new(@instance, '10')
    end

    describe 'deleting' do
      it 'cannot delete' do
        lambda { @data.delete! }.must_raise ArgumentError
      end
    end

    describe 'accessing' do
      it 'has no sequence' do
        @data[:sequence].must_equal nil
      end
      it 'has no id' do
        @data[:id].must_equal nil
      end
      it 'has no metadata' do
        @data[:type].must_equal nil
      end
    end

    describe 'editing the data' do
      before do
        @data[:type] = 'A'
        @data[:sequence] = 'some'
        @data[:index] = 4
      end

      describe 'after saving' do
        before do
          @data.save!
        end
        it 'updates meta data in SQL' do
          val = @instance.sql[:entry_test].filter(:entry_id => 10).first
          val[:type].must_equal 'A'
        end
        it 'updates the sequence in TT' do
          TT['1-ex:4'].must_equal 'some'
        end
      end
    end
  end

  describe 'an existing entry' do
    before do
      dataset = @instance.sql[:entry_test]
      dataset.delete
      @raw = [ 
        { :alignment_id => 1, :entry_id => 0, :index => 0, :type => 'A', :id => 1 } 
      ]
      TT['1-ex:0'] = 'bob'
      dataset.multi_insert(@raw)
      @data = @klass.new(@instance, 0)
    end

    describe 'accessing' do
      it 'can get the associated sequence' do
        @data[:sequence].must_equal 'bob'
      end
      it 'can get the associated meta data' do
        @data[:type].must_equal 'A'
      end
    end

    describe 'editing the data' do
      before do
        @data[:type] = 'c'
        @data[:sequence] = 'something'
      end

      describe 'after saving' do
        before do
          @data.save!
        end
        it 'changes the meta data in memory' do
          @data[:type].must_equal 'c'
        end
        it 'changes the meta data in sql' do
          dataset = @instance.sql[:entry_test]
          val = dataset.filter(:id => 1).first
          val[:type].must_equal 'c'
        end
        it 'changes the sequence in memory' do
          @data[:sequence].must_equal 'something'
        end
        it 'changes the sequence on disk' do
          TT['1-ex:0'].must_equal 'something'
        end
        it 'knows the entry is not new' do
          @data.new?.must_equal false
        end
      end

      describe 'after deleting' do
        before do
          @data.delete!
        end
        it 'removes the meta data in memory' do
          @data[:type].must_equal nil
        end
        it 'removes the meta data in sql' do
          dataset = @instance.sql[:entry_test]
          val = dataset.count.must_equal 0
        end
        it 'removes the sequence in memory' do
          @data[:sequence].must_equal nil
        end
        it 'removes the sequence on disk' do
          TT['1-ex:0'].must_equal nil
        end
      end
    end

    describe 'editing the data' do
      before do
        @data[:type] = 'c'
        @data[:sequence] = 'something'
      end

      describe 'without saving' do
        it 'changes the meta data in memory' do
          @data[:type].must_equal 'c'
        end
        it 'does not change the meta data in sql' do
          dataset = @instance.sql[:entry_test]
          val = dataset.filter(:id => 1).first
          val[:type].must_equal 'A'
        end
        it 'changes the sequence in memory' do
          @data[:sequence].must_equal 'something'
        end
        it 'does not change the sequence on disk' do
          TT['1-ex:0'].must_equal 'bob'
        end
      end
    end
  end

end
<file_sep>$:.unshift("./lib")
require "alignment"

#alignment = Alignment.new(:main_port => 1979)
connection = Alignment::Connection.new("/tmp/alignment_test_socket")
alignment = connection['test', 'generate']
rows = []
15_000.times do |count|
  seq = ''
  400.times { |t| seq << 'acgun-'[rand(6)] }
  rows << seq
end

columns = Array.new(400) { |i| '' }
rows.each_with_index do |row, index|
  alignment.add_row(index, row)
  row.each_char.with_index do |char, index|
    columns[index] << char
  end
end

columns.each_with_index do |column, index|
  alignment.add_column(index + 1, column)
end
