class Scenic::Adapters::Postgres
An adapter for managing Postgres
views.
These methods are used internally by Scenic
and are not intended for direct use. Methods that alter database schema are intended to be called via {Statements}, while {#refresh_materialized_view} is called via {Scenic.database}.
The methods are documented here for insight into specifics of how Scenic
integrates with Postgres
and the responsibilities of {Adapters}.
Attributes
Public Class Methods
Source
# File lib/scenic/adapters/postgres.rb, line 42 def initialize(connectable = ActiveRecord::Base) @connectable = connectable end
Creates an instance of the Scenic
Postgres
adapter.
This is the default adapter for Scenic
. Configuring it via {Scenic.configure} is not required, but the example below shows how one would explicitly set it.
@param [#connection] connectable An object that returns the connection
for Scenic to use. Defaults to `ActiveRecord::Base`.
@example
Scenic.configure do |config| config.database = Scenic::Adapters::Postgres.new end
Public Instance Methods
Source
# File lib/scenic/adapters/postgres.rb, line 273 def connection Connection.new(connectable.connection) end
A decorated ActiveRecord connection object with some Scenic-specific methods. Not intended for direct use outside of the Postgres
adapter.
@api private
Source
# File lib/scenic/adapters/postgres.rb, line 139 def create_materialized_view(name, sql_definition, no_data: false) raise_unless_materialized_views_supported execute <<-SQL CREATE MATERIALIZED VIEW #{quote_table_name(name)} AS #{sql_definition.rstrip.chomp(";")} #{"WITH NO DATA" if no_data}; SQL end
Creates a materialized view in the database
@param name The name of the materialized view to create @param sql_definition The SQL schema that defines the materialized view. @param no_data [Boolean] Default: false. Set to true to create
materialized view without running the associated query. You will need to perform a refresh to populate with data.
This is typically called in a migration via {Statements#create_view}.
@raise [MaterializedViewsNotSupportedError] if the version of Postgres
in use does not support materialized views.
@return [void]
Source
# File lib/scenic/adapters/postgres.rb, line 64 def create_view(name, sql_definition) execute "CREATE VIEW #{quote_table_name(name)} AS #{sql_definition};" end
Creates a view in the database.
This is typically called in a migration via {Statements#create_view}.
@param name The name of the view to create @param sql_definition The SQL schema for the view.
@return [void]
Source
# File lib/scenic/adapters/postgres.rb, line 194 def drop_materialized_view(name) raise_unless_materialized_views_supported execute "DROP MATERIALIZED VIEW #{quote_table_name(name)};" end
Drops a materialized view in the database
This is typically called in a migration via {Statements#update_view}.
@param name The name of the materialized view to drop. @raise [MaterializedViewsNotSupportedError] if the version of Postgres
in use does not support materialized views.
@return [void]
Source
# File lib/scenic/adapters/postgres.rb, line 121 def drop_view(name) execute "DROP VIEW #{quote_table_name(name)};" end
Drops the named view from the database
This is typically called in a migration via {Statements#drop_view}.
@param name The name of the view to drop
@return [void]
Source
# File lib/scenic/adapters/postgres.rb, line 254 def populated?(name) raise_unless_materialized_views_supported schemaless_name = name.to_s.split(".").last sql = "SELECT relispopulated FROM pg_class WHERE relname = '#{schemaless_name}'" relations = execute(sql) if relations.count.positive? relations.first["relispopulated"].in?(["t", true]) else false end end
True if supplied relation name is populated.
@param name The name of the relation
@raise [MaterializedViewsNotSupportedError] if the version of Postgres
in use does not support materialized views.
@return [boolean]
Source
# File lib/scenic/adapters/postgres.rb, line 228 def refresh_materialized_view(name, concurrently: false, cascade: false) raise_unless_materialized_views_supported if concurrently raise_unless_concurrent_refresh_supported end if cascade refresh_dependencies_for(name, concurrently: concurrently) end if concurrently && populated?(name) execute "REFRESH MATERIALIZED VIEW CONCURRENTLY #{quote_table_name(name)};" else execute "REFRESH MATERIALIZED VIEW #{quote_table_name(name)};" end end
Refreshes a materialized view from its SQL schema.
This is typically called from application code via {Scenic.database}.
@param name The name of the materialized view to refresh. @param concurrently [Boolean] Whether the refreshs hould happen
concurrently or not. A concurrent refresh allows the view to be refreshed without locking the view for select but requires that the table have at least one unique index that covers all rows. Attempts to refresh concurrently without a unique index will raise a descriptive error. This option is ignored if the view is not populated, as it would cause an error to be raised by Postgres. Default: false.
@param cascade [Boolean] Whether to refresh dependent materialized
views. Default: false.
@raise [MaterializedViewsNotSupportedError] if the version of Postgres
in use does not support materialized views.
@raise [ConcurrentRefreshesNotSupportedError] when attempting a
concurrent refresh on version of Postgres that does not support concurrent materialized view refreshes.
@example Non-concurrent refresh
Scenic.database.refresh_materialized_view(:search_results)
@example Concurrent refresh
Scenic.database.refresh_materialized_view(:posts, concurrently: true)
@example Cascade refresh
Scenic.database.refresh_materialized_view(:posts, cascade: true)
@return [void]
Source
# File lib/scenic/adapters/postgres.rb, line 110 def replace_view(name, sql_definition) execute "CREATE OR REPLACE VIEW #{quote_table_name(name)} AS #{sql_definition};" end
Replaces a view in the database using ‘CREATE OR REPLACE VIEW`.
This results in a ‘CREATE OR REPLACE VIEW`. Most of the time the explicitness of the two step process used in {#update_view} is preferred to `CREATE OR REPLACE VIEW` because the former ensures that the view you are trying to update did, in fact, already exist. Additionally, `CREATE OR REPLACE VIEW` is allowed only to add new columns to the end of an existing view schema. Existing columns cannot be re-ordered, removed, or have their types changed. Drop and create overcomes this limitation as well.
However, when there is a tangled dependency tree ‘CREATE OR REPLACE VIEW` can be preferable.
This is typically called in a migration via {Statements#replace_view}.
@param name The name of the view to update @param sql_definition The SQL schema for the updated view.
@return [void]
Source
# File lib/scenic/adapters/postgres.rb, line 170 def update_materialized_view(name, sql_definition, no_data: false, side_by_side: false) raise_unless_materialized_views_supported if side_by_side SideBySide .new(adapter: self, name: name, definition: sql_definition) .update else IndexReapplication.new(connection: connection).on(name) do drop_materialized_view(name) create_materialized_view(name, sql_definition, no_data: no_data) end end end
Updates a materialized view in the database.
Drops and recreates the materialized view. Attempts to maintain all previously existing and still applicable indexes on the materialized view after the view is recreated.
This is typically called in a migration via {Statements#update_view}.
@param name The name of the view to update @param sql_definition The SQL schema for the updated view. @param no_data [Boolean] Default: false. Set to true to create
materialized view without running the associated query. You will need to perform a refresh to populate with data.
@param side_by_side [Boolean] Default: false. Set to true to create the
new version under a different name and atomically swap them, limiting the time that a view is inaccessible at the cost of doubling disk usage
@raise [MaterializedViewsNotSupportedError] if the version of Postgres
in use does not support materialized views.
@return [void]
Source
# File lib/scenic/adapters/postgres.rb, line 84 def update_view(name, sql_definition) drop_view(name) create_view(name, sql_definition) end
Updates a view in the database.
This results in a {#drop_view} followed by a {#create_view}. The explicitness of that two step process is preferred to ‘CREATE OR REPLACE VIEW` because the former ensures that the view you are trying to update did, in fact, already exist. Additionally, `CREATE OR REPLACE VIEW` is allowed only to add new columns to the end of an existing view schema. Existing columns cannot be re-ordered, removed, or have their types changed. Drop and create overcomes this limitation as well.
This is typically called in a migration via {Statements#update_view}.
@param name The name of the view to update @param sql_definition The SQL schema for the updated view.
@return [void]
Source
# File lib/scenic/adapters/postgres.rb, line 52 def views Views.new(connection).all end
Returns an array of views in the database.
This collection of views is used by the [Scenic::SchemaDumper] to populate the ‘schema.rb` file.
@return [Array<Scenic::View>]
Private Instance Methods
Source
# File lib/scenic/adapters/postgres.rb, line 288 def raise_unless_concurrent_refresh_supported unless connection.supports_concurrent_refreshes? raise ConcurrentRefreshesNotSupportedError end end
Source
# File lib/scenic/adapters/postgres.rb, line 282 def raise_unless_materialized_views_supported unless connection.supports_materialized_views? raise MaterializedViewsNotSupportedError end end
Source
# File lib/scenic/adapters/postgres.rb, line 294 def refresh_dependencies_for(name, concurrently: false) Scenic::Adapters::Postgres::RefreshDependencies.call( name, self, connection, concurrently: concurrently ) end