DBIx-Class-Schema-Diff

 view release on metacpan or  search on metacpan

README.md  view on Meta::CPAN

      new_schema => 'My::Schema2'
    );
    
    # Create new diff object using schema objects:
    $D = DBIx::Class::Schema::Diff->new(
      old_schema => $schema1,
      new_schema => $schema2
    );
    
    # Dump current schema data to a json file for later use:
    $D->old_schema->dump_json_file('/tmp/my_schema1_data.json');
    
    # Or
    DBIx::Class::Schema::Diff::SchemaData->new(
      schema => 'My::Schema1'
    )->dump_json_file('/tmp/my_schema1_data.json');
    
    # Create new diff object using previously saved 
    # schema data + current schema class:
    $D = DBIx::Class::Schema::Diff->new(
      old_schema => '/tmp/my_schema1_data.json',
      new_schema => 'My::Schema1'
    );
    

Filtering the diff:

    # Get all differences (hash structure):
    my $hash = $D->diff;
    
    # Only column differences:
    $hash = $D->filter('columns')->diff;
    
    # Only things named 'Artist' or 'CD':
    $hash = $D->filter(qw/Artist CD/)->diff;
    
    # Things named 'Artist', *columns* named 'CD' and *relationships* named 'columns':
    $hash = $D->filter(qw(Artist columns/CD relationships/columns))->diff;
    
    # Sources named 'Artist', excluding column changes:
    $hash = $D->filter('Artist:')->filter_out('columns')->diff;
    
    if( $D->filter('Artist:columns/name.size')->diff ) {
     # Do something only if there has been a change in 'size' (i.e. in column_info)
     # to the 'name' column in the 'Artist' source
     # ...
    }
    
    # Names of all sources which exist in new_schema but not in old_schema:
    my @sources = keys %{ 
      $D->filter({ source_events => 'added' })->diff || {}
    };
    
    # All changes to existing unique_constraints (ignoring added or deleted)
    # excluding those named or within sources named Album or Genre:
    $hash = $D->filter_out({ events => [qw(added deleted)] })
              ->filter_out('Album','Genre')
              ->filter('constraints')
              ->diff;
    
    # All changes to relationship attrs except for 'cascade_delete' in 
    # relationships named 'artists':
    $hash = $D->filter_out('relationships/artists.attrs.cascade_delete')
              ->filter('relationships/*.attrs')
              ->diff;

## DESCRIPTION

General-purpose schema differ for [DBIx::Class](https://metacpan.org/pod/DBIx::Class) to identify changes between two DBIC Schemas. 
Currently tracks added/deleted/changed events and deep diffing across 5 named types of source data:

- columns
- relationships
- constraints
- table\_name
- isa

The changes which are detected are stored in a HashRef which can be accessed by calling 
[diff](https://metacpan.org/pod/DBIx::Class::Schema::Diff#diff). This data packet, which has a format that is specific to 
this module, can either be inspected directly, or _filtered_ to be able to check for specific 
changes as boolean test(s), making it unnecessary to know the internal diff structure for many 
use-cases (since if there are no changes, or no changes left after being filtered, `diff` returns 
false/undef - see the [FILTERING](https://metacpan.org/pod/DBIx::Class::Schema::Diff#FILTERING) section for more info).

This tool attempts to be simple and flexible with a straightforward, "DWIM" API. It is meant
to be used programmatically in dynamic scenarios where schema changes are occurring but are not well
suited for [DBIx::Class::Migration](https://metacpan.org/pod/DBIx::Class::Migration) or [DBIx::Class::DeploymentHandler](https://metacpan.org/pod/DBIx::Class::DeploymentHandler) for whatever reasons, or
some other event/action needs to take place based on certain types of changes (note that this tool 
is NOT meant to be a replacement for Migrations/DH). 

It is also useful as a general debugging/development tool, and was designed with this in mind to 
be "handy" and not need a lot of setup/RTFM to use.

This tool is different from [SQL::Translator::Diff](https://metacpan.org/pod/SQL::Translator::Diff) in that it compares DBIC schemas at the 
_class/code_ level, not the underlying DDL, nor does it attempt to modify one schema to match
the other (although, it could certainly be used to write a tool that did).

## METHODS

### new

Create a new DBIx::Class::Schema::Diff instance. The following build options are supported:

- old\_schema

    The "old" (or left-side) schema to be compared. 

    Can be supplied as a [DBIx::Class::Schema](https://metacpan.org/pod/DBIx::Class::Schema) class name, connected schema object instance, 
    or previously saved [SchemaData](https://metacpan.org/pod/DBIx::Class::Schema::Diff::SchemaData) which can be 
    supplied as an object, HashRef, or a path to a file containing serialized JSON data (as 
    produced by [DBIx::Class::Schema::Diff::SchemaData#dump\_json\_file](https://metacpan.org/pod/DBIx::Class::Schema::Diff::SchemaData#dump_json_file))

    See the SYNOPSIS and [DBIx::Class::Schema::Diff::SchemaData](https://metacpan.org/pod/DBIx::Class::Schema::Diff::SchemaData) for more info.

- new\_schema

    The "new" (or right-side) schema to be compared. Accepts the same dynamic type options 
    as `old_schema`.

### diff

Returns the differences between the two schemas as a HashRef structure, or `undef` if there are 
none.

The HashRef is divided first by source name, then by type, with the special `_event` key 
identifying the kind of modification (added, deleted or changed) at both the source and the type 
level. For 'changed' events within types, a deeper, type-specific diff HashRef is provided (with 
column\_info/relationship\_info diffs generated using [Hash::Diff](https://metacpan.org/pod/Hash::Diff)).

Here is an example of what a diff packet (with a sampling of lots of different kinds of changes) 
might look like:

    # Example diff with sample of all 3 kinds of events and all 5 types:
    {
      Address => {
        _event => "changed",
        isa => [
          "-Some::Removed::Component",
          "+Test::DummyClass"
        ],
        relationships => {
          customers2 => {
            _event => "added"
          },
          staffs => {
            _event => "changed",
            diff => {
              attrs => {
                cascade_delete => 1
              }
            }
          }
        }
      },
      City => {
        _event => "changed",
        table_name => "city1"
      },
      FilmCategory => {
        _event => "changed",
        columns => {
          last_update => {
            _event => "changed",
            diff => {
              is_nullable => 1
            }
          }
        }
      },
      FooBar => {
        _event => "added"
      },
      FooBaz => {
        _event => "deleted"
      },
      Store => {
        _event => "changed",
        constraints => {
          idx_unique_store_manager => {
            _event => "added"
          }
        }
      }
    }

### filter

Accepts filter argument(s) to restrict the differences to consider and returns a new `Schema::Diff` 
instance, making it chainable (much like [ResultSets](https://metacpan.org/pod/DBIx::Class::ResultSet#search_rs)).

See [FILTERING](https://metacpan.org/pod/DBIx::Class::Schema::Diff#FILTERING) for filter argument syntax.

### filter\_out

Works like `filter()` but the arguments exclude differences rather than restrict/limit to them.

See [FILTERING](https://metacpan.org/pod/DBIx::Class::Schema::Diff#FILTERING) for filter argument syntax.

## FILTERING

The [filter](https://metacpan.org/pod/DBIx::Class::Schema::Diff#filter) (and inverse 
[filter\_out](https://metacpan.org/pod/DBIx::Class::Schema::Diff#filter_out)) method is analogous to ResultSet's 
[search\_rs](https://metacpan.org/pod/DBIx::Class::ResultSet#search_rs) in that it is chainable (i.e. returns a new object 
instance) and each call further restricts the data considered. But, instead of building up an SQL 
query, it filters the data in the HashRef returned by [diff](https://metacpan.org/pod/DBIx::Class::Schema::Diff#diff). 

The filter argument(s) define an expression which matches specific parts of the `diff` packet. In 
the case of `filter()`, all data that **does not** match the expression is removed from the diff 
HashRef (of the returned, new object), while in the case of `filter_out()`, all data that **does** 
match the expression is removed.

The filter expression is designed to be simple and declarative. It can be supplied as a list of 
strings which match schema data either broadly or narrowly. A filter string argument follows this 
general pattern:

    '<source>:<type>/<id>'

Where `source` is the name of a specific source in the schema (either side), `type` is the 
_type_ of data, which is currently one of five (5) supported, predefined types: _'columns'_, 
_'relationships'_, _'constraints'_, _'isa'_ and _'table\_name'_, and `id` is the name of an 
item, specific to that type, if applicable. 

For instance, this expression would match only the _column_ named 'timestamp' in the source 
named 'Artist':

    'Artist:columns/timestamp'

Not all types have sub-items (only _columns_, _relationships_ and _constraints_). The _isa_ and 
_table\_name_ types are source-global. So, for example, to see changes to _isa_ (i.e. differences 
in inheritance and/or loaded components in the result class) you could use the following:

    'Artist:isa'

On the other hand, not only are there multiple _columns_ and _relationships_ within each source, 
but each can have specific changes to their attributes (column\_info/relationship\_info) which can 
also be targeted selectively. For instance, to match only changes in `size` of a specific column:

    'Artist:columns/timestamp.size'

Attributes with sub hashes can be matched as well. For example, to match only changes in `list` 
_within_ `extra` (which is where DBIC puts the list of possible values for enum columns):

    'Artist:columns/my_enum.extra.list'

The structure is specific to the type. The dot-separated path applies to the data returned by [column\_info](https://metacpan.org/pod/DBIx::Class::ResultSource#column_info) for columns and
[relationship\_info](https://metacpan.org/pod/DBIx::Class::ResultSource#relationship_info) for relationships. For instance, 
the following matches changes to `cascade_delete` of a specific relationship named 'some\_rel' 
in the 'Artist' source:

    'Artist:relationships/some_rel.attrs.cascade_delete'

Filter arguments can also match _broadly_ using the wildcard asterisk character (`*`). For 
instance, to match _'isa'_ changes in any source:

    '*:isa'

The system also accepts ambiguous/partial match strings and tries to "DWIM". So, the above can also 
be written simply as:

    'isa'

This is possible because 'isa' is understood/known as a _type_ keyword. Additionally, the system 
knows the names of all the sources in advance, so the following filter string argument would match 
everything in the 'Artist' source:

    'Artist'

Sub-item names are automatically resolved, too. The following would match any column, relationship, 
or constraint named `'code'` in any source:

    'code'

When you have schemas with overlapping names, such as a column named 'isa', you simply need to 
supply more specific match strings, as ambiguous names are resolved with left-precedence. So, to 
match any column, relationship, or constraint named 'isa', you could use the following:

    # Matches column, relationship, or constraints named 'isa':
    '*:*/isa'

Different delimiter characters are used for the source level (`':'`) and the type level (`'/'`) 
so you can do things like match any column/relationship/constraint of a specific source, such as:

    Artist:code

The above is equivalent to:

    Artist:*/code

You can also supply a delimiter character to match a specific level explicitly. So, if you wanted to
match all changes to a _source_ named 'isa':

    # Matches a source (poorly) named 'isa'
    'isa:'

The same works at the type level. The following are all equivalent

    # Each of the following 3 filter strings are equivalent:
    'columns/'
    '*:columns/*'
    'columns'

Internally, [Hash::Layout](https://metacpan.org/pod/Hash::Layout) is used to process the filter arguments.

### event filtering

Besides matching specific parts of the schema, you can also filter by _event_, which is either 
_'added'_, _'deleted'_ or _'changed'_ at both the source and type level (i.e. the event of a 
new column is 'added' at the type level, but 'changed' at the source level).

Filtering by event requires passing a HashRef argument to filter/filter\_out, with the special



( run in 0.499 second using v1.01-cache-2.11-cpan-39bf76dae61 )