Class: Scenic::Adapters::Postgres

Inherits:
Object
  • Object
show all
Defined in:
lib/scenic/adapters/postgres.rb,
lib/scenic/adapters/postgres/views.rb,
lib/scenic/adapters/postgres/errors.rb,
lib/scenic/adapters/postgres/indexes.rb,
lib/scenic/adapters/postgres/connection.rb,
lib/scenic/adapters/postgres/side_by_side.rb,
lib/scenic/adapters/postgres/index_creation.rb,
lib/scenic/adapters/postgres/temporary_name.rb,
lib/scenic/adapters/postgres/index_migration.rb,
lib/scenic/adapters/postgres/index_reapplication.rb,
lib/scenic/adapters/postgres/refresh_dependencies.rb

Overview

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 Scenic::Adapters.

Defined Under Namespace

Classes: ConcurrentRefreshesNotSupportedError, MaterializedViewsNotSupportedError, RefreshDependencies, SideBySide

Instance Method Summary collapse

Constructor Details

#initialize(connectable = ActiveRecord::Base) ⇒ Postgres

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.

Examples:

Scenic.configure do |config|
  config.database = Scenic::Adapters::Postgres.new
end

Parameters:

  • connectable (#connection) (defaults to: ActiveRecord::Base)

    An object that returns the connection for Scenic to use. Defaults to ActiveRecord::Base.



42
43
44
# File 'lib/scenic/adapters/postgres.rb', line 42

def initialize(connectable = ActiveRecord::Base)
  @connectable = connectable
end

Instance Method Details

#create_materialized_view(name, sql_definition, no_data: false) ⇒ void

This method returns an undefined value.

Creates a materialized view in the database

This is typically called in a migration via Statements#create_view.

Parameters:

  • name

    The name of the materialized view to create

  • sql_definition

    The SQL schema that defines the materialized view.

  • no_data (Boolean) (defaults to: false)

    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.

Raises:



139
140
141
142
143
144
145
146
147
# 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

#create_view(name, sql_definition) ⇒ void

This method returns an undefined value.

Creates a view in the database.

This is typically called in a migration via Statements#create_view.

Parameters:

  • name

    The name of the view to create

  • sql_definition

    The SQL schema for the view.



64
65
66
# 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

#drop_materialized_view(name) ⇒ void

This method returns an undefined value.

Drops a materialized view in the database

This is typically called in a migration via Statements#update_view.

Parameters:

  • name

    The name of the materialized view to drop.

Raises:



194
195
196
197
# 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

#drop_view(name) ⇒ void

This method returns an undefined value.

Drops the named view from the database

This is typically called in a migration via Statements#drop_view.

Parameters:

  • name

    The name of the view to drop



121
122
123
# File 'lib/scenic/adapters/postgres.rb', line 121

def drop_view(name)
  execute "DROP VIEW #{quote_table_name(name)};"
end

#populated?(name) ⇒ boolean

True if supplied relation name is populated.

Parameters:

  • name

    The name of the relation

Returns:

  • (boolean)

Raises:



254
255
256
257
258
259
260
261
262
263
264
265
266
267
# 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

#refresh_materialized_view(name, concurrently: false, cascade: false) ⇒ void

This method returns an undefined value.

Refreshes a materialized view from its SQL schema.

This is typically called from application code via Scenic.database.

Examples:

Non-concurrent refresh

Scenic.database.refresh_materialized_view(:search_results)

Concurrent refresh

Scenic.database.refresh_materialized_view(:posts, concurrently: true)

Cascade refresh

Scenic.database.refresh_materialized_view(:posts, cascade: true)

Parameters:

  • name

    The name of the materialized view to refresh.

  • concurrently (Boolean) (defaults to: false)

    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.

  • cascade (Boolean) (defaults to: false)

    Whether to refresh dependent materialized views. Default: false.

Raises:



228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
# 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

#replace_view(name, sql_definition) ⇒ void

This method returns an undefined value.

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.

Parameters:

  • name

    The name of the view to update

  • sql_definition

    The SQL schema for the updated view.



110
111
112
# 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

#update_materialized_view(name, sql_definition, no_data: false, side_by_side: false) ⇒ void

This method returns an undefined value.

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.

Parameters:

  • name

    The name of the view to update

  • sql_definition

    The SQL schema for the updated view.

  • no_data (Boolean) (defaults to: false)

    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.

  • side_by_side (Boolean) (defaults to: false)

    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

Raises:



170
171
172
173
174
175
176
177
178
179
180
181
182
183
# 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

#update_view(name, sql_definition) ⇒ void

This method returns an undefined value.

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.

Parameters:

  • name

    The name of the view to update

  • sql_definition

    The SQL schema for the updated view.



84
85
86
87
# File 'lib/scenic/adapters/postgres.rb', line 84

def update_view(name, sql_definition)
  drop_view(name)
  create_view(name, sql_definition)
end

#viewsArray<Scenic::View>

Returns an array of views in the database.

This collection of views is used by the [Scenic::SchemaDumper] to populate the schema.rb file.

Returns:



52
53
54
# File 'lib/scenic/adapters/postgres.rb', line 52

def views
  Views.new(connection).all
end