class Perpetuity::Postgres

Constants

InvalidTableName
VERSION

Attributes

connection[R]
db[R]
host[R]
password[R]
pool_size[R]
port[R]
username[R]

Public Class Methods

new(options) click to toggle source
# File lib/perpetuity/postgres.rb, line 19
def initialize options
  @host      = options.fetch(:host) { 'localhost' }
  @port      = options.fetch(:port) { 5432 }
  @db        = options.fetch(:db)
  @pool_size = options.fetch(:pool_size) { 5 }
  @username  = options.fetch(:username) { ENV['USER'] }
  @password  = options.fetch(:password) {}

  @connection ||= ConnectionPool.new(
    db:       db,
    host:     host,
    port:     port,
    username: username,
    password: password,
    pool_size: pool_size
  )
end

Public Instance Methods

activate_index!(index) click to toggle source
# File lib/perpetuity/postgres.rb, line 169
def activate_index! index
  return if index.active?

  attribute_names = index.attribute_names.join('_')
  index_name = "#{index.table}_#{attribute_names}_index"
  sql = "CREATE "
  sql << "UNIQUE " if index.unique?
  sql << "INDEX \"#{index_name}\" "
  sql << "ON #{TableName.new(index.table)} (#{attribute_names})"
  connection.execute(sql)
  index.activate!
rescue PG::UndefinedTable => e
  create_table_with_attributes index.table, index.attributes
  retry
end
active_indexes(table) click to toggle source
# File lib/perpetuity/postgres.rb, line 185
    def active_indexes table
      sql = <<-SQL
      SELECT pg_class.relname AS name,
             ARRAY(
               SELECT pg_get_indexdef(pg_index.indexrelid, k + 1, true)
               FROM generate_subscripts(pg_index.indkey, 1) AS k
               ORDER BY k
             ) AS attributes,
             pg_index.indisunique AS unique,
             pg_index.indisready AS active
      FROM pg_class
      INNER JOIN pg_index ON pg_class.oid = pg_index.indexrelid
      WHERE pg_class.relname ~ '^#{table}.*index$'
      SQL

      indexes = connection.execute(sql).map do |index|
        Index.from_sql(index)
      end
      IndexCollection.new(table, indexes)
    end
add_column(table_name, column_name, attributes) click to toggle source
# File lib/perpetuity/postgres.rb, line 286
def add_column table_name, column_name, attributes
  attr = attributes.detect { |a| a.name.to_s == column_name.to_s }
  column = Table::Attribute.new(attr.name, attr.type, attr.options)

  sql = %Q(ALTER TABLE "#{table_name}" ADD #{column.sql_declaration})
  connection.execute sql
end
cast_id(id, id_attribute) click to toggle source
# File lib/perpetuity/postgres.rb, line 256
def cast_id id, id_attribute
  return id if id_attribute.nil?

  if [Bignum, Fixnum, Integer].include? id_attribute.type
    id.to_i
  else
    id
  end
end
count(klass, query='TRUE', options={}) click to toggle source
# File lib/perpetuity/postgres.rb, line 92
def count klass, query='TRUE', options={}, &block
  where = if block_given?
            query(&block)
          else
            query
          end
  options = translate_options(options).merge(from: klass, where: where)
  table = table_name(klass)
  sql = select 'COUNT(*)', options
  connection.execute(sql).to_a.first['count'].to_i
rescue PG::UndefinedTable
  # Table does not exist, so there are 0 records
  0
end
create_table(name, attributes) click to toggle source
# File lib/perpetuity/postgres.rb, line 236
def create_table name, attributes
  connection.execute Table.new(name, attributes).create_table_sql
end
create_table_with_attributes(klass, attributes) click to toggle source
# File lib/perpetuity/postgres.rb, line 276
def create_table_with_attributes klass, attributes
  table_attributes = attributes.map do |attr|
    name = attr.name
    type = attr.type
    options = attr.options
    Table::Attribute.new name, type, options
  end
  create_table klass.to_s, table_attributes
end
delete(ids, klass) click to toggle source
# File lib/perpetuity/postgres.rb, line 73
def delete ids, klass
  ids = Array(ids)
  table = TableName.new(klass)

  if ids.one?
    id_string = TextValue.new(ids.first)
    sql = "DELETE FROM #{table} WHERE id = #{id_string}"

    connection.execute(sql).to_a
  elsif ids.none?
    # Do nothing, we weren't given anything to delete
  else
    id_string = ids.map { |id| TextValue.new(id) }
    sql = "DELETE FROM #{table} WHERE id IN (#{id_string.join(',')})"

    connection.execute(sql).to_a
  end
end
delete_all(klass) click to toggle source
# File lib/perpetuity/postgres.rb, line 115
def delete_all klass
  table = table_name(klass)
  sql = "DELETE FROM #{table}"
  connection.execute(sql)
rescue PG::UndefinedTable
  # Do nothing. There is already nothing here.
end
drop_collection(name)
Alias for: drop_table
drop_table(name) click to toggle source
# File lib/perpetuity/postgres.rb, line 231
def drop_table name
  connection.execute "DROP TABLE IF EXISTS #{table_name(name)}"
end
Also aliased as: drop_collection
find(klass, id) click to toggle source
# File lib/perpetuity/postgres.rb, line 107
def find klass, id
  retrieve(klass, query { |o| o.id == id }.to_db).first
end
has_table?(name) click to toggle source
# File lib/perpetuity/postgres.rb, line 240
def has_table? name
  connection.tables.include? name
end
increment(klass, id, attribute, count=1) click to toggle source
# File lib/perpetuity/postgres.rb, line 270
def increment klass, id, attribute, count=1
  table = TableName.new(klass)
  sql = %Q{UPDATE #{table} SET #{attribute} = #{attribute} + #{count} WHERE id = #{SQLValue.new(id)} RETURNING #{attribute}}
  connection.execute(sql).to_a
end
index(klass, attributes, options={}) click to toggle source
# File lib/perpetuity/postgres.rb, line 154
def index klass, attributes, options={}
  name = "#{klass}_#{Array(attributes).map(&:name).join('_')}_index"
  index = Index.new(name: name,
                    attributes: Array(attributes),
                    unique: !!options[:unique],
                    active: false)
  indexes(klass) << index
  index
end
indexes(klass) click to toggle source
# File lib/perpetuity/postgres.rb, line 164
def indexes klass
  @indexes ||= {}
  @indexes[klass] ||= active_indexes(klass)
end
insert(klass, serialized_objects, attributes) click to toggle source
# File lib/perpetuity/postgres.rb, line 37
def insert klass, serialized_objects, attributes
  table = TableName.new(klass)
  data = serialized_objects.reduce(:+)
  sql = "INSERT INTO #{table} #{data} RETURNING id"

  results = connection.execute(sql).to_a
  ids = results.map { |result| cast_id(result['id'], attributes[:id]) }

  ids
rescue PG::UndefinedTable => e # Table doesn't exist, so we create it.
  retries ||= 0
  retries += 1
  create_table_with_attributes klass, attributes
  retry unless retries > 1
  raise e
rescue PG::UndefinedColumn => e
  retries ||= 0
  retries += 1
  error ||= nil

  if retries > 1 && e.message == error
    # We've retried more than once and we're getting the same error
    raise
  end

  error = e.message
  if error =~ /column "(.+)" of relation "(.+)" does not exist/
    column_name = $1
    table_name = $2
    add_column table_name, column_name, attributes
    retry
  end

  raise
end
negate_query(&block) click to toggle source
# File lib/perpetuity/postgres.rb, line 127
def negate_query &block
  NegatedQuery.new(&block)
end
postgresify(value) click to toggle source
# File lib/perpetuity/postgres.rb, line 244
def postgresify value
  Serializer.new(nil).serialize_attribute value
end
query(&block) click to toggle source
# File lib/perpetuity/postgres.rb, line 123
def query &block
  Query.new(&block)
end
remove_index(index) click to toggle source
# File lib/perpetuity/postgres.rb, line 206
def remove_index index
  sql = %Q{DROP INDEX IF EXISTS #{TableName.new(index.name)}}
  connection.execute(sql)
end
retrieve(klass, criteria, options={}) click to toggle source
# File lib/perpetuity/postgres.rb, line 131
def retrieve klass, criteria, options={}
  options = translate_options(options).merge from: klass, where: criteria

  sql = select options
  connection.execute(sql).to_a
rescue PG::UndefinedTable
  []
end
select(*args) click to toggle source
# File lib/perpetuity/postgres.rb, line 227
def select *args
  SQLSelect.new(*args).to_s
end
serialize(object, mapper) click to toggle source
# File lib/perpetuity/postgres.rb, line 248
def serialize object, mapper
  Serializer.new(mapper).serialize object
end
serialize_changed_attributes(object, original, mapper) click to toggle source
# File lib/perpetuity/postgres.rb, line 252
def serialize_changed_attributes object, original, mapper
  Serializer.new(mapper).serialize_changes object, original
end
table_name(klass) click to toggle source
# File lib/perpetuity/postgres.rb, line 111
def table_name klass
  TableName.new(klass)
end
translate_options(options) click to toggle source
# File lib/perpetuity/postgres.rb, line 211
def translate_options options
  options = options.dup
  if options[:attribute]
    options[:order] = options.delete(:attribute)
    if direction = options.delete(:direction)
      direction = direction.to_s[/(asc|desc)/i]
      options[:order] = { options[:order] => direction }
    end
  end
  if options[:skip]
    options[:offset] = options.delete(:skip)
  end

  options
end
unserialize(data, mapper) click to toggle source
# File lib/perpetuity/postgres.rb, line 266
def unserialize data, mapper
  Serializer.new(mapper).unserialize data
end
update(klass, id, attributes) click to toggle source
# File lib/perpetuity/postgres.rb, line 140
def update klass, id, attributes
  sql = SQLUpdate.new(klass, id, attributes).to_s
  connection.execute(sql).to_a
rescue PG::UndefinedColumn => e
  if e.message =~ /column "(.*)" of relation "(.*)" does not exist/
    column_name = $1
    table_name = $2
    add_column table_name, column_name, [Attribute.new(column_name, attributes[column_name].class)]
    retry
  else
    raise e
  end
end