class PgBundle::Extension

The Extension class provides the api for defining an Extension it installation source, and dependencies example:

define an extension named 'foo' at version '0.1.1', with source on github depending on hstore
Extension.new('foo', '0.1.1', github: 'me/foo', requires: 'hstore')
you can then check if the Extension is available on a given database
extension.available?(database)
or install it along with it's dependencies
extension.install(database)

Attributes

flags[RW]
name[RW]
resolving_dependencies[RW]
source[RW]
version[RW]

Public Class Methods

new(*args) click to toggle source
# File lib/pgbundle/extension.rb, line 14
def initialize(*args)
  opts = args.last.is_a?(Hash) ? args.pop : {}
  @name, @version = args
  validate(opts)
  self.dependencies = opts[:requires]
  self.flags = opts[:flags]
  set_source(opts)
end

Public Instance Methods

available?(database) click to toggle source

returns true if extension is available for installation on a given database

# File lib/pgbundle/extension.rb, line 60
def available?(database)
  return false unless installed?(database)
  return true if created?(database)

  created_any_version?(database) ? updatable?(database) : creatable?(database)
  rescue ExtensionCreateError
    false
end
create!(con) click to toggle source

create extension on a given database connection

# File lib/pgbundle/extension.rb, line 158
def create!(con)
  con.exec create_stmt
end
create_dependencies(con) click to toggle source

create the dependency graph on the given connection

# File lib/pgbundle/extension.rb, line 147
def create_dependencies(con)
  @resolving_dependencies = true
  dependencies.each do |_, d|
    fail CircularDependencyError.new(name, d.name) if d.resolving_dependencies
    d.create_dependencies(con)
    d.create_or_update!(con)
  end
  @resolving_dependencies = false
end
create_or_update!(con) click to toggle source
# File lib/pgbundle/extension.rb, line 162
def create_or_update!(con)
  return true if created?(con)
  if created_any_version?(con)
    con.exec update_stmt
  else
    create!(con)
  end
  true
end
create_with_dependencies(database) click to toggle source

create the extension along with it's dependencies in a transaction

# File lib/pgbundle/extension.rb, line 128
def create_with_dependencies(database)
  return true if created?(database)
  return false if database.slave?

  database.transaction do |con|
    begin
      create_dependencies(con)
      create_or_update!(con)
    rescue PG::UndefinedFile => err
      raise ExtensionNotFound.new(name, version)
    rescue PG::UndefinedObject => err
      raise MissingDependency.new(name, err.message)
    end
  end

  true
end
created?(database_or_connection) click to toggle source

returns true if extension is already created with the correct version in the given database

# File lib/pgbundle/extension.rb, line 70
def created?(database_or_connection)
  if version
    result = database_or_connection.exec("SELECT * FROM pg_available_extension_versions WHERE name ='#{name}' AND version = '#{version}' AND installed").to_a
  else
    result = database_or_connection.exec("SELECT * FROM pg_available_extension_versions WHERE name ='#{name}' AND installed").to_a
  end

  if result.empty?
    false
  else
    true
  end
end
created_any_version?(database_or_connection) click to toggle source

checks if Extension is already installed at any version thus need ALTER EXTENSION to install

# File lib/pgbundle/extension.rb, line 175
def created_any_version?(database_or_connection)
  result = database_or_connection.exec("SELECT * FROM pg_available_extension_versions WHERE name ='#{name}' AND installed").to_a
  if result.empty?
    false
  else
    true
  end
end
dependencies() click to toggle source
# File lib/pgbundle/extension.rb, line 23
def dependencies
  @dependencies
end
dependencies=(obj = nil) click to toggle source

set dependency hash with different options dependencies= {foo: Extension.new('foo'), bar: Extension.new('bar')}

> {'foo' => Extension.new('foo'), 'bar' Extension.new('bar')}

dependencies= 'foo'

> {foo: Extension.new('foo')}

dependencies= Extension.new('foo')

> {foo: Extension.new('foo')}

dependencies= ['foo', 'bar']

> {'foo' => Extension.new('foo'), 'bar' Extension.new('bar')}

# File lib/pgbundle/extension.rb, line 36
def dependencies=(obj = nil)
  @dependencies = case obj
  when nil
    {}
  when Hash
    Hash[obj.map { |k, v| [k.to_s, v] }]
  when String, Symbol
    { obj.to_s => Extension.new(obj.to_s) }
  when Extension
    { obj.name => obj }
  when Array
    hashable = obj.map do |o|
      case o
      when String, Symbol
        [o.to_s, Extension.new(obj.to_s)]
      when Extension
        [o.name, o]
      end
    end
    Hash[hashable]
  end
end
dependencies_installed?(database) click to toggle source

checks that all dependencies are installed on a given database

# File lib/pgbundle/extension.rb, line 101
def dependencies_installed?(database)
  dependencies.all?{|_, d| d.installed?(database)}
end
install(database, force = false) click to toggle source

installs extension and all dependencies using make install if optional parameter force is true the extension will be installed even if it's already there returns true if Extension can successfully be created using CREATE EXTENSION

# File lib/pgbundle/extension.rb, line 109
def install(database, force = false)
  unless dependencies.empty?
    install_dependencies(database, force)
  end
  make_install(database, force)
  raise ExtensionNotFound.new(name, version) unless installed?(database)

  add_missing_required_dependencies(database)

  creatable?(database)
end
installed?(database_or_connection) click to toggle source

returns if the extension is already installed on the database system if it is also already created it returns the installed version

# File lib/pgbundle/extension.rb, line 86
def installed?(database_or_connection)
  if version
    result = database_or_connection.exec("SELECT * FROM pg_available_extension_versions WHERE name ='#{name}' AND version = '#{version}'").to_a
  else
    result = database_or_connection.exec("SELECT * FROM pg_available_extension_versions WHERE name ='#{name}'").to_a
  end

  if result.empty?
    false
  else
    true
  end
end
uninstall!(database) click to toggle source

completely removes extension be running make uninstall and DROP EXTENSION

# File lib/pgbundle/extension.rb, line 122
def uninstall!(database)
  drop_extension(database)
  make_uninstall(database)
end

Private Instance Methods

add_missing_required_dependencies(database) click to toggle source

adds dependencies that are required but not defined yet

# File lib/pgbundle/extension.rb, line 202
def add_missing_required_dependencies(database)
  requires = requires(database)
  requires.each do |name|
    unless dependencies[name]
      dependencies[name] = Extension.new(name)
    end
  end
end
creatable!(database) click to toggle source

hard checks that the dependency can be created running CREATE command in a transaction

# File lib/pgbundle/extension.rb, line 285
def creatable!(database)
  return false if database.slave?
  database.transaction_rollback do |con|
    begin
      create_dependencies(con)
      create!(con)
    rescue PG::UndefinedFile => err
      raise ExtensionNotFound.new(name, version)
    rescue PG::UndefinedObject => err
      raise MissingDependency.new(name, err.message)
    rescue PG::ReadOnlySqlTransaction
      raise ReadOnlyDb.new(database, name)
    end
  end

  true
end
creatable?(database) click to toggle source
# File lib/pgbundle/extension.rb, line 280
def creatable?(database)
  dependencies_installed?(database) && installed?(database)
end
create_stmt() click to toggle source
# File lib/pgbundle/extension.rb, line 257
def create_stmt
  stmt = "CREATE EXTENSION #{name}"
  stmt += " VERSION '#{version}'" unless version.nil? || version.empty?

  stmt
end
drop_extension(database) click to toggle source
# File lib/pgbundle/extension.rb, line 242
def drop_extension(database)
  database.drop_extension(name) unless database.slave?
end
install_dependencies(database, force = false) click to toggle source
# File lib/pgbundle/extension.rb, line 268
def install_dependencies(database, force = false)
  begin
    dependencies.each do |_, d|
      d.install(database, force)
    end
  rescue InstallError, ExtensionCreateError => e
    raise DependencyNotFound.new(name, e.message)
  end

  true
end
make_install(database, force = false) click to toggle source

loads the source and runs make install returns: self

# File lib/pgbundle/extension.rb, line 248
def make_install(database, force = false)
  return self if installed?(database) && (!force || source.nil?)

  fail SourceNotFound, name if source.nil?

  database.make_install(source, name, flags)
  self
end
make_uninstall(database) click to toggle source

loads the source and runs make uninstall returns: self

# File lib/pgbundle/extension.rb, line 237
def make_uninstall(database)
  database.make_uninstall(source, name, flags)
  self
end
requires(database) click to toggle source

returns an array of required Extension specified in the extensions control file

# File lib/pgbundle/extension.rb, line 212
    def requires(database)
      fail "Extension #{name} not (yet) installed" unless installed?(database)

      stmt = if version
        <<-SQL
          SELECT unnest(requires) as name FROM
          ( SELECT requires FROM  pg_available_extension_versions where name='#{name}' AND version ='#{version}') t
        SQL
      else
        <<-SQL
          SELECT unnest(requires) as name FROM
          (SELECT requires FROM
            pg_available_extensions a
            JOIN pg_available_extension_versions v ON v.name = a.name AND a.default_version = v.version
            WHERE v.name = '#{name}')t
        SQL
      end

      result = database.execute(stmt).to_a

      requires = result.map{|r| r['name']}
    end
set_source(opts) click to toggle source
# File lib/pgbundle/extension.rb, line 318
def set_source(opts)
  if opts[:path]
    @source = PathSource.new(opts[:path])
  elsif opts[:git]
    @source = GitSource.new(opts[:git], opts[:branch])
  elsif opts[:github]
    @source = GithubSource.new(opts[:github], opts[:branch])
  elsif opts[:pgxn]
    @source = PgxnSource.new(name, version)
  end
end
updatable?(database) click to toggle source

checks that the extension can be updated running ALTER EXTENSION command in a transaction

# File lib/pgbundle/extension.rb, line 304
def updatable?(database)
  result = true
  database.execute 'BEGIN'
  begin
    database.execute update_stmt
  rescue PG::UndefinedFile, PG::UndefinedObject, PG::ReadOnlySqlTransaction => err
    @error = err.message
    result = false
  end
  database.execute 'ROLLBACK'

  result
end
update_stmt() click to toggle source
# File lib/pgbundle/extension.rb, line 264
def update_stmt
  "ALTER EXTENSION #{name} UPDATE TO '#{version}'"
end
validate(opts) click to toggle source

validates the options hash

# File lib/pgbundle/extension.rb, line 187
def validate(opts)
  opts = opts.clone
  opts.delete(:requires)
  opts.delete(:branch)
  opts.delete(:flags)
  if opts.size > 1
    fail PgfileError.new "multiple sources given for #{name} #{opts}"
  end

  unless opts.empty? || [:path, :git, :github, :pgxn].include?(opts.keys.first)
    fail PgfileError.new "invalid source #{opts.keys.first} for #{name}"
  end
end