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
Public Class Methods
# 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
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 extension on a given database connection
# File lib/pgbundle/extension.rb, line 158 def create!(con) con.exec create_stmt end
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
# 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 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
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
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
# File lib/pgbundle/extension.rb, line 23 def dependencies @dependencies end
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
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
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
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
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
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
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
# File lib/pgbundle/extension.rb, line 280 def creatable?(database) dependencies_installed?(database) && installed?(database) end
# File lib/pgbundle/extension.rb, line 257 def create_stmt stmt = "CREATE EXTENSION #{name}" stmt += " VERSION '#{version}'" unless version.nil? || version.empty? stmt end
# File lib/pgbundle/extension.rb, line 242 def drop_extension(database) database.drop_extension(name) unless database.slave? end
# 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
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
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
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
# 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
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
# File lib/pgbundle/extension.rb, line 264 def update_stmt "ALTER EXTENSION #{name} UPDATE TO '#{version}'" end
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