NAME

DBIO::Diff::Op - Base class for DBIO driver diff operation objects

VERSION

version 0.900000

DESCRIPTION

Base class for the per-engine diff operation objects (the Diff::Table, Diff::Column, Diff::Index, Diff::ForeignKey classes each driver ships). Those classes were byte-for-byte identical in their new/accessor boilerplate and 90-95% identical in their diff() create/drop/alter walks across every driver; only as_sql (quoting, ENGINE=, schema qualification, type rendering) is genuinely engine-specific.

This base hosts the shared parts:

  • "new" and the action accessor, plus "mk_diff_accessors" to declare the rest without hand-rolling sub foo { $_[0]->{foo} } lines.

  • "diff_toplevel" -- the create/drop walk for a flat keyed collection (tables: a name exists only in the target -> create, only in the source -> drop; no in-place change).

  • "diff_nested" -- the create/change/drop walk for members nested under tables (columns, indexes, foreign keys), parameterized by a per-engine "did this member change?" predicate (e.g. the is_same_* functions from DBIO::Diff::Compare) and per-engine op-construction callbacks.

  • "summary_prefix" -- the +/-/~ action glyph used in summaries.

Subclasses keep as_sql (the real engine seam) and a thin summary, and call these helpers from their own diff class method. Drivers whose diff is orchestrated differently (e.g. PostgreSQL's registry-based dispatch, or its global definition-string index comparison) are free to ignore the walk helpers and use only "new"/"mk_diff_accessors"/"summary_prefix".

METHODS

new

my $op = $class->new(action => 'create', table_name => 'foo', ...);

Blesses the argument hash into the operation class.

mk_diff_accessors

__PACKAGE__->mk_diff_accessors(qw/table_name column_name old_info new_info/);

Declares simple hash-slot accessors for the operation class. action is already provided by this base. This is a thin alias over Class::Accessor::Grouped's simple group.

summary_prefix

my $glyph = $op->summary_prefix;            # by action, sensible defaults
my $glyph = $op->summary_prefix(add => '*'); # override per action

Returns the one-character glyph for this op's action: + for create/add, - for drop, ~ for alter/modify/change. Pass an action => glyph map to override. Unknown actions default to ~.

diff_toplevel

my @ops = $class->diff_toplevel($source, $target,
  create => sub { my ($name) = @_; $class->new(action => 'create', ...) },
  drop   => sub { my ($name) = @_; $class->new(action => 'drop',   ...) },
);

The create/drop walk for a flat collection keyed by name (the table-level diff). $source and $target are hashrefs { $name => $info }. For each name present only in $target the create callback is invoked; for each name present only in $source the drop callback is invoked. Names present in both are left untouched (table identity has no in-place change here; column/index/fk changes are handled by "diff_nested"). Names are walked in sorted order. Each callback may return zero or more ops; all are collected.

diff_nested

my @ops = $class->diff_nested($source, $target,
  index_by      => 'column_name',     # omit if members are already a hash
  scope         => 'both',            # or 'all'
  source_tables => $src_model->{tables},
  target_tables => $tgt_model->{tables},
  skip          => sub { my ($m) = @_; ... },   # ignore a member (e.g. auto index)
  changed_when  => sub { my ($old, $new) = @_; scalar changed_column_fields($old, $new) },
  on_new     => sub { my ($table, $name, $new) = @_; ... },
  on_changed => sub { my ($table, $name, $old, $new) = @_; ... },
  on_gone    => sub { my ($table, $name, $old) = @_; ... },
);

The create/change/drop walk for members nested one level under a table (columns, indexes, foreign keys). $source and $target are hashrefs { $table => $members }, where $members is either a hashref keyed by member name or an arrayref of member hashes (in which case pass index_by -- the field to key them by).

For each retained table, every member in the target is matched against the source by name: present only in the target -> on_new; present in both and changed_when returns true -> on_changed; present only in the source -> on_gone. changed_when is the per-engine seam -- pass one of the is_same_* predicates from DBIO::Diff::Compare (they return the list of changed fields, hence are true exactly when the member changed). skip, if given, drops a member from consideration on both sides (used to ignore auto-generated primary-key / unique indexes).

scope selects which tables are walked:

  • both (default) -- only tables present in both source_tables and target_tables. Members of brand-new tables are emitted inline by the table-create op, and members of dropped tables vanish with the table, so they are skipped here. Pass the model tables sections as source_tables / target_tables (they default to $source / $target if omitted).

  • all -- every target table (regardless of source), plus a trailing pass that emits on_gone for members of source-only tables. Use this when the member's drop must be emitted even as its table is dropped (e.g. indexes).

Tables and members are both walked in sorted order. Each callback may return zero or more ops; all are collected.

AUTHOR

DBIO & DBIx::Class Authors

COPYRIGHT AND LICENSE

Copyright (C) 2026 DBIO Authors Portions Copyright (C) 2005-2025 DBIx::Class Authors Based on DBIx::Class, heavily modified.

This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself.