lib/chrono_model/adapter.rb in chrono_model-1.2.2 vs lib/chrono_model/adapter.rb in chrono_model-2.0.0

- old
+ new

@@ -1,15 +1,23 @@ +# frozen_string_literal: true + require 'active_record/connection_adapters/postgresql_adapter' require 'chrono_model/adapter/migrations' + +if ActiveRecord::VERSION::STRING >= '6.1' + require 'chrono_model/adapter/migrations_modules/stable' +else + require 'chrono_model/adapter/migrations_modules/legacy' +end + require 'chrono_model/adapter/ddl' require 'chrono_model/adapter/indexes' require 'chrono_model/adapter/tsrange' require 'chrono_model/adapter/upgrade' module ChronoModel - # 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. # class Adapter < ActiveRecord::ConnectionAdapters::PostgreSQLAdapter @@ -23,16 +31,32 @@ TEMPORAL_SCHEMA = 'temporal' # The schema holding historical data HISTORY_SCHEMA = 'history' + if ActiveRecord::VERSION::STRING >= '7.1' + 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 + end + # Returns true whether the connection adapter supports our # implementation of temporal tables. Currently, Chronomodel - # is supported starting with PostgreSQL 9.3. + # is supported starting with PostgreSQL 9.3 (90300 in PostgreSQL's + # `PG_VERSION_NUM` numeric format). # def chrono_supported? - postgresql_version >= 90300 + postgresql_version >= 90300 # rubocop:disable Style/NumericLiterals end def chrono_setup! chrono_ensure_schemas @@ -51,17 +75,17 @@ # metadata from the first caller's selected schema and not from # the current one. # # NOTE: These methods are dynamically defined, see the source. # - def primary_key(table_name) - end + def primary_key(table_name); end - [:primary_key, :indexes, :default_sequence_name].each do |method| + %i[primary_key indexes default_sequence_name].each do |method| define_method(method) do |*args| table_name = args.first return super(*args) unless is_chrono?(table_name) + on_schema(TEMPORAL_SCHEMA, recurse: :ignore) { super(*args) } end end # Runs column_definitions in the temporal schema, as the table there @@ -71,16 +95,16 @@ # may reference types defined in other schemas, which result in their # names becoming schema qualified, which will cause type resolutions to fail. # # NOTE: This method is dynamically defined, see the source. # - def column_definitions - end + def column_definitions; end define_method(:column_definitions) do |table_name| return super(table_name) unless is_chrono?(table_name) - on_schema(TEMPORAL_SCHEMA + ',' + self.schema_search_path, recurse: :ignore) { super(table_name) } + + on_schema("#{TEMPORAL_SCHEMA},#{schema_search_path}", recurse: :ignore) { super(table_name) } end # Evaluates the given block in the temporal schema. # def on_temporal_schema(&block) @@ -99,31 +123,30 @@ # at each recursive call. # # See specs for examples and behaviour. # def on_schema(schema, recurse: :follow) - old_path = self.schema_search_path + old_path = schema_search_path count_recursions do - if recurse == :follow or Thread.current['recursions'] == 1 + 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 = - @connection.transaction_status == PG::Connection::PQTRANS_INERROR + 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 @@ -141,41 +164,51 @@ # view name. # def chrono_metadata_for(view_name) comment = select_value( "SELECT obj_description(#{quote(view_name)}::regclass)", - "ChronoModel metadata for #{view_name}") if data_source_exists?(view_name) + "ChronoModel metadata for #{view_name}" + ) if data_source_exists?(view_name) MultiJson.load(comment || '{}').with_indifferent_access end # Writes Gem metadata on the COMMENT field in the given VIEW name. # def chrono_metadata_set(view_name, metadata) comment = MultiJson.dump(metadata) - execute %[ COMMENT ON VIEW #{view_name} IS #{quote(comment)} ] + execute %( COMMENT ON VIEW #{view_name} IS #{quote(comment)} ) end + def valid_table_definition_options + super + %i[temporal journal no_journal full_journal] + end + private - # Counts the number of recursions in a thread local variable - # - def count_recursions # yield - Thread.current['recursions'] ||= 0 - Thread.current['recursions'] += 1 - yield + # Rails 7.1 uses `@raw_connection`, older versions use `@connection` + # + def chrono_connection + @chrono_connection ||= @raw_connection || @connection + end - ensure - Thread.current['recursions'] -= 1 - end + # Counts the number of recursions in a thread local variable + # + def count_recursions # yield + Thread.current['recursions'] ||= 0 + Thread.current['recursions'] += 1 - # Create the temporal and history schemas, unless they already exist - # - def chrono_ensure_schemas - [TEMPORAL_SCHEMA, HISTORY_SCHEMA].each do |schema| - execute "CREATE SCHEMA #{schema}" unless schema_exists?(schema) - end + yield + ensure + Thread.current['recursions'] -= 1 + end + + # Create the temporal and history schemas, unless they already exist + # + def chrono_ensure_schemas + [TEMPORAL_SCHEMA, HISTORY_SCHEMA].each do |schema| + execute "CREATE SCHEMA #{schema}" unless schema_exists?(schema) end + end end - end