lib/rom/sql/migration/schema_diff.rb in rom-sql-2.0.0.beta2 vs lib/rom/sql/migration/schema_diff.rb in rom-sql-2.0.0.beta3

- old
+ new

@@ -1,17 +1,25 @@ +require 'rom/sql/type_serializer' + module ROM module SQL module Migration + # @api private class SchemaDiff + extend Initializer + + param :database_type + + option :type_serializer, default: -> { ROM::SQL::TypeSerializer[database_type] } + class TableDiff - attr_reader :current_schema, :target_schema + extend Initializer - def initialize(current_schema: nil, target_schema: nil) - @current_schema = current_schema - @target_schema = target_schema - end + option :current_schema, optional: true + option :target_schema, optional: true + def empty? false end def table_name @@ -25,38 +33,38 @@ end end class TableCreated < TableDiff alias_method :schema, :target_schema - attr_reader :attributes, :indexes - def initialize(attributes:, indexes: EMPTY_ARRAY, **rest) - super(rest) + option :attributes - @attributes = attributes - @indexes = indexes - end + option :indexes, default: -> { EMPTY_ARRAY } + + option :foreign_keys, default: -> { EMPTY_ARRAY } end class TableAltered < TableDiff - attr_reader :attribute_changes, :index_changes - def initialize(attribute_changes: EMPTY_ARRAY, index_changes: EMPTY_ARRAY, **rest) - super(rest) + option :attribute_changes, default: -> { EMPTY_ARRAY } - @attribute_changes = attribute_changes - @index_changes = index_changes + option :index_changes, default: -> { EMPTY_ARRAY } + + option :foreign_key_changes, default: -> { EMPTY_ARRAY } + + def meta? + attribute_changes.empty? && index_changes.empty? end end class AttributeDiff - attr_reader :attr + extend Initializer - def initialize(attr) - @attr = attr - end + param :attr + option :type_serializer + def name attr.name end def null? @@ -72,114 +80,142 @@ end end class AttributeAdded < AttributeDiff def type - unwrap(attr).primitive + type_serializer.(unwrap(attr).type) end end class AttributeRemoved < AttributeDiff end class AttributeChanged < AttributeDiff - attr_reader :current + param :current alias_method :target, :attr - def initialize(current, target) - super(target) - - @current = current - end - - def to_a - [current, target] - end - def nullability_changed? current.optional? ^ target.optional? end def type_changed? - unwrap(current).meta(index: Set.new) != unwrap(target).meta(index: Set.new) + clean(current.qualified) != clean(target.qualified) end + + private + + def clean(type) + unwrap(type).meta(index: nil, foreign_key: nil, target: nil) + end end class IndexDiff attr_reader :index def initialize(index) @index = index end def attributes - index.attributes.map(&:name) + list = index.attributes.map(&:name) + + if list.size == 1 + list[0] + else + list + end end def name index.name end + end - def unique? - index.unique? + class IndexAdded < IndexDiff + def options + options = {} + options[:name] = index.name if !index.name.nil? + options[:unique] = true if index.unique? + options[:type] = index.type if !index.type.nil? + options[:where] = index.predicate if !index.predicate.nil? + options end + end - def type - index.type + class IndexRemoved < IndexDiff + def options + options = {} + options[:name] = index.name if !index.name.nil? + options end + end - def predicate - index.predicate + class ForeignKeyDiff + attr_reader :foreign_key + + def initialize(foreign_key) + @foreign_key = foreign_key end - def partial? - !predicate.nil? + def parent + foreign_key.parent_table end + + def parent_keys + foreign_key.parent_keys + end + + def child_keys + foreign_key.attributes.map(&:name) + end end - class IndexAdded < IndexDiff + class ForeignKeyAdded < ForeignKeyDiff end - class IndexRemoved < IndexDiff + class ForeignKeyRemoved < ForeignKeyDiff end def call(current, target) if current.empty? TableCreated.new( target_schema: target, - attributes: target.map { |attr| AttributeAdded.new(attr) }, - indexes: target.indexes.map { |idx| IndexAdded.new(idx) } + attributes: map_attributes(target.to_h, AttributeAdded), + indexes: target.indexes.map { |idx| IndexAdded.new(idx) }, + foreign_keys: target.foreign_keys.map { |fk| ForeignKeyAdded.new(fk) } ) else attribute_changes = compare_attributes(current.to_h, target.to_h) index_changes = compare_indexes(current, target) + fk_changes = compare_foreign_key_constraints(current, target) - if attribute_changes.empty? && index_changes.empty? + if attribute_changes.empty? && index_changes.empty? && fk_changes.empty? Empty.new(current_schema: current, target_schema: target) else TableAltered.new( current_schema: current, target_schema: target, attribute_changes: attribute_changes, - index_changes: index_changes + index_changes: index_changes, + foreign_key_changes: fk_changes ) end end end def compare_attributes(current, target) changed_attributes = target.select { |name, attr| - current.key?(name) && current[name] != attr + current.key?(name) && !attributes_equal?(current[name], attr) }.map { |name, target_attr| - [name, [current[name], target_attr]] + [name, [target_attr, current[name]]] }.to_h added_attributes = target.select { |name, _| !current.key?(name) } removed_attributes = current.select { |name, _| !target.key?(name) } - removed_attributes.values.map { |attr| AttributeRemoved.new(attr) } + - added_attributes.values.map { |attr| AttributeAdded.new(attr) } + - changed_attributes.values.map { |attrs| AttributeChanged.new(*attrs) } + map_attributes(removed_attributes, AttributeRemoved) + + map_attributes(added_attributes, AttributeAdded) + + map_attributes(changed_attributes, AttributeChanged) end def compare_indexes(current, target) added_indexes = target.indexes.reject { |idx| current.indexes.any? { |curr_idx| curr_idx.attributes == idx.attributes } @@ -188,9 +224,28 @@ target.indexes.none? { |tgt_idx| idx.attributes == tgt_idx.attributes } } removed_indexes.map { |idx| IndexRemoved.new(idx) } + added_indexes.map { |idx| IndexAdded.new(idx) } + end + + def compare_foreign_key_constraints(current, target) + target_fks = target.foreign_keys + current_fks = current.foreign_keys + + added_fks = target_fks - current_fks + removed_fks = current_fks - target_fks + + removed_fks.map { |fk| ForeignKeyRemoved.new(fk) } + + added_fks.map { |fk| ForeignKeyAdded.new(fk) } + end + + def attributes_equal?(a, b) + a.qualified == b.qualified + end + + def map_attributes(attributes, change_type) + attributes.values.map { |args| change_type.new(*args, type_serializer: type_serializer) } end end end end end