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

- old
+ new

@@ -1,18 +1,19 @@ +# frozen_string_literal: true + module ChronoModel class Adapter < ActiveRecord::ConnectionAdapters::PostgreSQLAdapter - module Migrations # Creates the given table, possibly creating the temporal schema # objects if the `:temporal` option is given and set to true. # - def create_table(table_name, options = {}) + def create_table(table_name, **options) # No temporal features requested, skip return super unless options[:temporal] if options[:id] == false - logger.warn "ChronoModel: Temporal Temporal tables require a primary key." + logger.warn 'ChronoModel: Temporal Temporal tables require a primary key.' logger.warn "ChronoModel: Adding a `__chrono_id' primary key to #{table_name} definition." options[:id] = '__chrono_id' end @@ -24,13 +25,17 @@ end end # If renaming a temporal table, rename the history and view as well. # - def rename_table(name, new_name) - return super unless is_chrono?(name) + def rename_table(name, new_name, **options) + unless is_chrono?(name) + return super(name, new_name) if method(:rename_table).super_method.arity == 2 + return super + end + clear_cache! transaction do # Rename tables # @@ -61,82 +66,50 @@ # # If the `:temporal` option is specified, enables or disables temporal # features on the given table. Please note that you'll lose your history # when demoting a temporal table to a plain one. # - def change_table(table_name, options = {}, &block) + def change_table(table_name, **options, &block) transaction do - # Add an empty proc to support calling change_table without a block. # - block ||= proc { } + block ||= proc {} - case options[:temporal] - when true - if !is_chrono?(table_name) + if options[:temporal] + unless is_chrono?(table_name) chrono_make_temporal_table(table_name, options) end drop_and_recreate_public_view(table_name, options) do - super table_name, options, &block + super(table_name, **options, &block) end - when false + else if is_chrono?(table_name) chrono_undo_temporal_table(table_name) end - super table_name, options, &block + super(table_name, **options, &block) end - end end # If dropping a temporal table, drops it from the temporal schema # adding the CASCADE option so to delete the history, view and triggers. # - def drop_table(table_name, *) + def drop_table(table_name, **options) return super unless is_chrono?(table_name) on_temporal_schema { execute "DROP TABLE #{table_name} CASCADE" } chrono_drop_trigger_functions_for(table_name) end - # If adding an index to a temporal table, add it to the one in the - # temporal schema and to the history one. If the `:unique` option is - # present, it is removed from the index created in the history table. - # - def add_index(table_name, column_name, options = {}) - return super unless is_chrono?(table_name) - - transaction do - on_temporal_schema { super } - - # Uniqueness constraints do not make sense in the history table - options = options.dup.tap {|o| o.delete(:unique)} if options[:unique].present? - - on_history_schema { super table_name, column_name, options } - end - end - - # If removing an index from a temporal table, remove it both from the - # temporal and the history schemas. - # - def remove_index(table_name, *) - return super unless is_chrono?(table_name) - - transaction do - on_temporal_schema { super } - on_history_schema { super } - end - end - # If adding a column to a temporal table, creates it in the table in # the temporal schema and updates the triggers. # - def add_column(table_name, *) + def add_column(table_name, column_name, type, **options) return super unless is_chrono?(table_name) transaction do # Add the column to the temporal table on_temporal_schema { super } @@ -164,119 +137,123 @@ # If removing a column from a temporal table, we are forced to drop the # view, then change the column from the table in the temporal schema and # eventually recreate the triggers. # - def change_column(table_name, *) + def change_column(table_name, column_name, type, **options) return super unless is_chrono?(table_name) + drop_and_recreate_public_view(table_name) { super } end # Change the default on the temporal schema table. # def change_column_default(table_name, *) return super unless is_chrono?(table_name) + on_temporal_schema { super } end # Change the null constraint on the temporal schema table. # def change_column_null(table_name, *) return super unless is_chrono?(table_name) + on_temporal_schema { super } end # If removing a column from a temporal table, we are forced to drop the # view, then drop the column from the table in the temporal schema and # eventually recreate the triggers. # - def remove_column(table_name, *) + def remove_column(table_name, column_name, type = nil, **options) return super unless is_chrono?(table_name) + drop_and_recreate_public_view(table_name) { super } end private - # In destructive changes, such as removing columns or changing column - # types, the view must be dropped and recreated, while the change has - # to be applied to the table in the temporal schema. - # - def drop_and_recreate_public_view(table_name, opts = {}) - transaction do - options = chrono_metadata_for(table_name).merge(opts) - execute "DROP VIEW #{table_name}" + # In destructive changes, such as removing columns or changing column + # types, the view must be dropped and recreated, while the change has + # to be applied to the table in the temporal schema. + # + def drop_and_recreate_public_view(table_name, opts = {}, &block) + transaction do + options = chrono_metadata_for(table_name).merge(opts) - on_temporal_schema { yield } + execute "DROP VIEW #{table_name}" - # Recreate the triggers - chrono_public_view_ddl(table_name, options) - end - end + on_temporal_schema(&block) - def chrono_make_temporal_table(table_name, options) - # Add temporal features to this table - # - if !primary_key(table_name) - execute "ALTER TABLE #{table_name} ADD __chrono_id SERIAL PRIMARY KEY" - end - - execute "ALTER TABLE #{table_name} SET SCHEMA #{TEMPORAL_SCHEMA}" - on_history_schema { chrono_history_table_ddl(table_name) } + # Recreate the triggers chrono_public_view_ddl(table_name, options) - chrono_copy_indexes_to_history(table_name) + end + end - # Optionally copy the plain table data, setting up history - # retroactively. - # - if options[:copy_data] - chrono_copy_temporal_to_history(table_name, options) - end + def chrono_make_temporal_table(table_name, options) + # Add temporal features to this table + # + unless primary_key(table_name) + execute "ALTER TABLE #{table_name} ADD __chrono_id SERIAL PRIMARY KEY" end - def chrono_copy_temporal_to_history(table_name, options) - seq = on_history_schema { serial_sequence(table_name, primary_key(table_name)) } - from = options[:validity] || '0001-01-01 00:00:00' + execute "ALTER TABLE #{table_name} SET SCHEMA #{TEMPORAL_SCHEMA}" + on_history_schema { chrono_history_table_ddl(table_name) } + chrono_public_view_ddl(table_name, options) + chrono_copy_indexes_to_history(table_name) - execute %[ + # Optionally copy the plain table data, setting up history + # retroactively. + # + return unless options[:copy_data] + + chrono_copy_temporal_to_history(table_name, options) + end + + def chrono_copy_temporal_to_history(table_name, options) + seq = on_history_schema { pk_and_sequence_for(table_name).last.to_s } + from = options[:validity] || '0001-01-01 00:00:00' + + execute %[ INSERT INTO #{HISTORY_SCHEMA}.#{table_name} SELECT *, nextval('#{seq}') AS hid, tsrange('#{from}', NULL) AS validity, timezone('UTC', now()) AS recorded_at FROM #{TEMPORAL_SCHEMA}.#{table_name} ] - end + end - # Removes temporal features from this table - # - def chrono_undo_temporal_table(table_name) - execute "DROP VIEW #{table_name}" + # Removes temporal features from this table + # + def chrono_undo_temporal_table(table_name) + execute "DROP VIEW #{table_name}" - chrono_drop_trigger_functions_for(table_name) + chrono_drop_trigger_functions_for(table_name) - on_history_schema { execute "DROP TABLE #{table_name}" } + on_history_schema { execute "DROP TABLE #{table_name}" } - default_schema = select_value 'SELECT current_schema()' - on_temporal_schema do - if primary_key(table_name) == '__chrono_id' - execute "ALTER TABLE #{table_name} DROP __chrono_id" - end - - execute "ALTER TABLE #{table_name} SET SCHEMA #{default_schema}" + default_schema = select_value 'SELECT current_schema()' + on_temporal_schema do + if primary_key(table_name) == '__chrono_id' + execute "ALTER TABLE #{table_name} DROP __chrono_id" end + + execute "ALTER TABLE #{table_name} SET SCHEMA #{default_schema}" end + end - # Renames a table and its primary key sequence name - # - def rename_table_and_pk(name, new_name) - seq = serial_sequence(name, primary_key(name)) - new_seq = seq.sub(name.to_s, new_name.to_s).split('.').last + # Renames a table and its primary key sequence name + # + def rename_table_and_pk(name, new_name) + seq = pk_and_sequence_for(name).last.to_s + new_seq = seq.sub(name.to_s, new_name.to_s).split('.').last - execute "ALTER SEQUENCE #{seq} RENAME TO #{new_seq}" - execute "ALTER TABLE #{name} RENAME TO #{new_name}" - end + execute "ALTER SEQUENCE #{seq} RENAME TO #{new_seq}" + execute "ALTER TABLE #{name} RENAME TO #{new_name}" + end # private end - end end