Class: ChronoModel::Adapter

Inherits:
ActiveRecord::ConnectionAdapters::PostgreSQLAdapter
  • Object
show all
Includes:
DDL, Indexes, Migrations, Upgrade
Defined in:
lib/chrono_model/adapter.rb,
lib/chrono_model/adapter/ddl.rb,
lib/chrono_model/adapter/indexes.rb,
lib/chrono_model/adapter/upgrade.rb,
lib/chrono_model/adapter/migrations.rb,
lib/chrono_model/adapter/migrations_modules/stable.rb

Overview

This class implements all ActiveRecord::ConnectionAdapters::SchemaStatements methods adding support for temporal extensions. It inherits from the Postgres adapter for a clean override of its methods using super.

Defined Under Namespace

Modules: DDL, Indexes, Migrations, MigrationsModules, Upgrade

Constant Summary collapse

TEMPORAL_SCHEMA =

The schema holding current data

'temporal'
HISTORY_SCHEMA =

The schema holding historical data

'history'

Instance Method Summary collapse

Methods included from Upgrade

#chrono_upgrade!

Methods included from Indexes

#add_temporal_indexes, #add_timeline_consistency_constraint, #remove_temporal_indexes, #remove_timeline_consistency_constraint, #timeline_consistency_constraint_name

Methods included from Migrations

#add_column, #change_column, #change_column_default, #change_column_null, #change_table, #create_table, #drop_table, #remove_column, #rename_column, #rename_table

Methods included from MigrationsModules::Stable

#add_index, #remove_index

Constructor Details

#initializeAdapter

Returns a new instance of Adapter.



30
31
32
33
34
35
36
37
38
39
40
41
# File 'lib/chrono_model/adapter.rb', line 30

def initialize(*)
  super

  connect!

  unless chrono_supported?
    raise ChronoModel::Error, 'Your database server is not supported by ChronoModel. ' \
                              'Currently, only PostgreSQL >= 9.3 is supported.'
  end

  chrono_setup!
end

Instance Method Details

#chrono_metadata_for(view_name) ⇒ Object

Reads the Gem metadata from the COMMENT set on the given PostgreSQL view name.



154
155
156
157
158
159
160
161
# File 'lib/chrono_model/adapter.rb', line 154

def (view_name)
  comment = select_value(
    "SELECT obj_description(#{quote(view_name)}::regclass)",
    "ChronoModel metadata for #{view_name}"
  ) if data_source_exists?(view_name)

  MultiJson.load(comment || '{}').with_indifferent_access
end

#chrono_metadata_set(view_name, metadata) ⇒ Object

Writes Gem metadata on the COMMENT field in the given VIEW name.



165
166
167
168
169
# File 'lib/chrono_model/adapter.rb', line 165

def (view_name, )
  comment = MultiJson.dump()

  execute "COMMENT ON VIEW #{view_name} IS #{quote(comment)}"
end

#chrono_setup!Object



53
54
55
56
57
# File 'lib/chrono_model/adapter.rb', line 53

def chrono_setup!
  chrono_ensure_schemas

  chrono_upgrade_warning
end

#chrono_supported?Boolean

Returns true whether the connection adapter supports our implementation of temporal tables. Currently, Chronomodel is supported starting with PostgreSQL 9.3 (90300 in PostgreSQL's PG_VERSION_NUM numeric format).

Returns:

  • (Boolean)


49
50
51
# File 'lib/chrono_model/adapter.rb', line 49

def chrono_supported?
  postgresql_version >= 90300 # rubocop:disable Style/NumericLiterals
end

#column_definitions(table_name) ⇒ Object

Runs column_definitions in the temporal schema, as the table there defined is the source for this information.

The default search path is included however, since the table may reference types defined in other schemas, which result in their names becoming schema qualified, which will cause type resolutions to fail.



90
91
92
93
94
# File 'lib/chrono_model/adapter.rb', line 90

def column_definitions(table_name)
  return super unless is_chrono?(table_name)

  on_schema("#{TEMPORAL_SCHEMA},#{schema_search_path}", recurse: :ignore) { super }
end

#is_chrono?(table) ⇒ Boolean

Returns true if the given name references a temporal table.

Returns:

  • (Boolean)


146
147
148
149
# File 'lib/chrono_model/adapter.rb', line 146

def is_chrono?(table)
  on_temporal_schema { data_source_exists?(table) } &&
    on_history_schema { data_source_exists?(table) }
end

#on_history_schema(&block) ⇒ Object

Evaluates the given block in the history schema.



104
105
106
# File 'lib/chrono_model/adapter.rb', line 104

def on_history_schema(&block)
  on_schema(HISTORY_SCHEMA, &block)
end

#on_schema(schema, recurse: :follow) ⇒ Object

Evaluates the given block in the given schema search path.

Recursion works by saving the old_path the function closure at each recursive call.

See specs for examples and behaviour.



115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
# File 'lib/chrono_model/adapter.rb', line 115

def on_schema(schema, recurse: :follow)
  old_path = schema_search_path

  count_recursions do
    if (recurse == :follow) || (Thread.current['recursions'] == 1)
      self.schema_search_path = schema
    end

    yield
  end
ensure
  # If the transaction is aborted, any execute() call will raise
  # "transaction is aborted errors" - thus calling the Adapter's
  # setter won't update the memoized variable.
  #
  # Here we reset it to +nil+ to refresh it on the next call, as
  # there is no way to know which path will be restored when the
  # transaction ends.
  #
  transaction_aborted =
    chrono_connection.transaction_status == PG::Connection::PQTRANS_INERROR

  if transaction_aborted && Thread.current['recursions'] == 1
    @schema_search_path = nil
  else
    self.schema_search_path = old_path
  end
end

#on_temporal_schema(&block) ⇒ Object

Evaluates the given block in the temporal schema.



98
99
100
# File 'lib/chrono_model/adapter.rb', line 98

def on_temporal_schema(&block)
  on_schema(TEMPORAL_SCHEMA, &block)
end

#primary_key(table_name) ⇒ Object

Runs primary_key, indexes and default_sequence_name in the temporal schema, as the table there defined is the source for this information.

Moreover, the PostgreSQLAdapter indexes method uses current_schema(), thus this is the only (and cleanest) way to make injection work.

Schema nesting is disabled on these calls, make sure to fetch metadata from the first caller's selected schema and not from the current one.

NOTE: These methods are dynamically defined, see the source.



73
# File 'lib/chrono_model/adapter.rb', line 73

def primary_key(table_name); end

#valid_table_definition_optionsObject



171
172
173
# File 'lib/chrono_model/adapter.rb', line 171

def valid_table_definition_options
  super + %i[temporal journal no_journal full_journal]
end