1DBIx::Class::Schema::DiUfsfe(r3)Contributed Perl DocumenDtBaItxi:o:nClass::Schema::Diff(3)
2
3
4

NAME

6       DBIx::Class::Schema::Diff - Identify differences between two
7       DBIx::Class schemas
8

SYNOPSIS

10        use DBIx::Class::Schema::Diff;
11
12        # Create new diff object using schema class names:
13        my $D = DBIx::Class::Schema::Diff->new(
14          old_schema => 'My::Schema1',
15          new_schema => 'My::Schema2'
16        );
17
18        # Create new diff object using schema objects:
19        $D = DBIx::Class::Schema::Diff->new(
20          old_schema => $schema1,
21          new_schema => $schema2
22        );
23
24        # Dump current schema data to a json file for later use:
25        $D->old_schema->dump_json_file('/tmp/my_schema1_data.json');
26
27        # Or
28        DBIx::Class::Schema::Diff::SchemaData->new(
29          schema => 'My::Schema1'
30        )->dump_json_file('/tmp/my_schema1_data.json');
31
32        # Create new diff object using previously saved
33        # schema data + current schema class:
34        $D = DBIx::Class::Schema::Diff->new(
35          old_schema => '/tmp/my_schema1_data.json',
36          new_schema => 'My::Schema1'
37        );
38
39       Filtering the diff:
40
41        # Get all differences (hash structure):
42        my $hash = $D->diff;
43
44        # Only column differences:
45        $hash = $D->filter('columns')->diff;
46
47        # Only things named 'Artist' or 'CD':
48        $hash = $D->filter(qw/Artist CD/)->diff;
49
50        # Things named 'Artist', *columns* named 'CD' and *relationships* named 'columns':
51        $hash = $D->filter(qw(Artist columns/CD relationships/columns))->diff;
52
53        # Sources named 'Artist', excluding column changes:
54        $hash = $D->filter('Artist:')->filter_out('columns')->diff;
55
56        if( $D->filter('Artist:columns/name.size')->diff ) {
57         # Do something only if there has been a change in 'size' (i.e. in column_info)
58         # to the 'name' column in the 'Artist' source
59         # ...
60        }
61
62        # Names of all sources which exist in new_schema but not in old_schema:
63        my @sources = keys %{
64          $D->filter({ source_events => 'added' })->diff || {}
65        };
66
67        # All changes to existing unique_constraints (ignoring added or deleted)
68        # excluding those named or within sources named Album or Genre:
69        $hash = $D->filter_out({ events => [qw(added deleted)] })
70                  ->filter_out('Album','Genre')
71                  ->filter('constraints')
72                  ->diff;
73
74        # All changes to relationship attrs except for 'cascade_delete' in
75        # relationships named 'artists':
76        $hash = $D->filter_out('relationships/artists.attrs.cascade_delete')
77                  ->filter('relationships/*.attrs')
78                  ->diff;
79

DESCRIPTION

81       General-purpose schema differ for DBIx::Class to identify changes
82       between two DBIC Schemas.  Currently tracks added/deleted/changed
83       events and deep diffing across 5 named types of source data:
84
85       ·   columns
86
87       ·   relationships
88
89       ·   constraints
90
91       ·   table_name
92
93       ·   isa
94
95       The changes which are detected are stored in a HashRef which can be
96       accessed by calling diff. This data packet, which has a format that is
97       specific to this module, can either be inspected directly, or filtered
98       to be able to check for specific changes as boolean test(s), making it
99       unnecessary to know the internal diff structure for many use-cases
100       (since if there are no changes, or no changes left after being
101       filtered, "diff" returns false/undef - see the FILTERING section for
102       more info).
103
104       This tool attempts to be simple and flexible with a straightforward,
105       "DWIM" API. It is meant to be used programmatically in dynamic
106       scenarios where schema changes are occurring but are not well suited
107       for DBIx::Class::Migration or DBIx::Class::DeploymentHandler for
108       whatever reasons, or some other event/action needs to take place based
109       on certain types of changes (note that this tool is NOT meant to be a
110       replacement for Migrations/DH).
111
112       It is also useful as a general debugging/development tool, and was
113       designed with this in mind to be "handy" and not need a lot of
114       setup/RTFM to use.
115
116       This tool is different from SQL::Translator::Diff in that it compares
117       DBIC schemas at the class/code level, not the underlying DDL, nor does
118       it attempt to modify one schema to match the other (although, it could
119       certainly be used to write a tool that did).
120

METHODS

122   new
123       Create a new DBIx::Class::Schema::Diff instance. The following build
124       options are supported:
125
126       old_schema
127           The "old" (or left-side) schema to be compared.
128
129           Can be supplied as a DBIx::Class::Schema class name, connected
130           schema object instance, or previously saved SchemaData which can be
131           supplied as an object, HashRef, or a path to a file containing
132           serialized JSON data (as produced by
133           DBIx::Class::Schema::Diff::SchemaData#dump_json_file)
134
135           See the SYNOPSIS and DBIx::Class::Schema::Diff::SchemaData for more
136           info.
137
138       new_schema
139           The "new" (or right-side) schema to be compared. Accepts the same
140           dynamic type options as "old_schema".
141
142   diff
143       Returns the differences between the two schemas as a HashRef structure,
144       or "undef" if there are none.
145
146       The HashRef is divided first by source name, then by type, with the
147       special "_event" key identifying the kind of modification (added,
148       deleted or changed) at both the source and the type level. For
149       'changed' events within types, a deeper, type-specific diff HashRef is
150       provided (with column_info/relationship_info diffs generated using
151       Hash::Diff).
152
153       Here is an example of what a diff packet (with a sampling of lots of
154       different kinds of changes) might look like:
155
156        # Example diff with sample of all 3 kinds of events and all 5 types:
157        {
158          Address => {
159            _event => "changed",
160            isa => [
161              "-Some::Removed::Component",
162              "+Test::DummyClass"
163            ],
164            relationships => {
165              customers2 => {
166                _event => "added"
167              },
168              staffs => {
169                _event => "changed",
170                diff => {
171                  attrs => {
172                    cascade_delete => 1
173                  }
174                }
175              }
176            }
177          },
178          City => {
179            _event => "changed",
180            table_name => "city1"
181          },
182          FilmCategory => {
183            _event => "changed",
184            columns => {
185              last_update => {
186                _event => "changed",
187                diff => {
188                  is_nullable => 1
189                }
190              }
191            }
192          },
193          FooBar => {
194            _event => "added"
195          },
196          FooBaz => {
197            _event => "deleted"
198          },
199          Store => {
200            _event => "changed",
201            constraints => {
202              idx_unique_store_manager => {
203                _event => "added"
204              }
205            }
206          }
207        }
208
209   filter
210       Accepts filter argument(s) to restrict the differences to consider and
211       returns a new "Schema::Diff" instance, making it chainable (much like
212       ResultSets).
213
214       See FILTERING for filter argument syntax.
215
216   filter_out
217       Works like "filter()" but the arguments exclude differences rather than
218       restrict/limit to them.
219
220       See FILTERING for filter argument syntax.
221

FILTERING

223       The filter (and inverse filter_out) method is analogous to ResultSet's
224       search_rs in that it is chainable (i.e. returns a new object instance)
225       and each call further restricts the data considered. But, instead of
226       building up an SQL query, it filters the data in the HashRef returned
227       by diff.
228
229       The filter argument(s) define an expression which matches specific
230       parts of the "diff" packet. In the case of "filter()", all data that
231       does not match the expression is removed from the diff HashRef (of the
232       returned, new object), while in the case of "filter_out()", all data
233       that does match the expression is removed.
234
235       The filter expression is designed to be simple and declarative. It can
236       be supplied as a list of strings which match schema data either broadly
237       or narrowly. A filter string argument follows this general pattern:
238
239        '<source>:<type>/<id>'
240
241       Where "source" is the name of a specific source in the schema (either
242       side), "type" is the type of data, which is currently one of five (5)
243       supported, predefined types: 'columns', 'relationships', 'constraints',
244       'isa' and 'table_name', and "id" is the name of an item, specific to
245       that type, if applicable.
246
247       For instance, this expression would match only the column named
248       'timestamp' in the source named 'Artist':
249
250        'Artist:columns/timestamp'
251
252       Not all types have sub-items (only columns, relationships and
253       constraints). The isa and table_name types are source-global. So, for
254       example, to see changes to isa (i.e. differences in inheritance and/or
255       loaded components in the result class) you could use the following:
256
257        'Artist:isa'
258
259       On the other hand, not only are there multiple columns and
260       relationships within each source, but each can have specific changes to
261       their attributes (column_info/relationship_info) which can also be
262       targeted selectively. For instance, to match only changes in "size" of
263       a specific column:
264
265        'Artist:columns/timestamp.size'
266
267       Attributes with sub hashes can be matched as well. For example, to
268       match only changes in "list" within "extra" (which is where DBIC puts
269       the list of possible values for enum columns):
270
271        'Artist:columns/my_enum.extra.list'
272
273       The structure is specific to the type. The dot-separated path applies
274       to the data returned by column_info for columns and relationship_info
275       for relationships. For instance, the following matches changes to
276       "cascade_delete" of a specific relationship named 'some_rel' in the
277       'Artist' source:
278
279        'Artist:relationships/some_rel.attrs.cascade_delete'
280
281       Filter arguments can also match broadly using the wildcard asterisk
282       character ("*"). For instance, to match 'isa' changes in any source:
283
284        '*:isa'
285
286       The system also accepts ambiguous/partial match strings and tries to
287       "DWIM". So, the above can also be written simply as:
288
289        'isa'
290
291       This is possible because 'isa' is understood/known as a type keyword.
292       Additionally, the system knows the names of all the sources in advance,
293       so the following filter string argument would match everything in the
294       'Artist' source:
295
296        'Artist'
297
298       Sub-item names are automatically resolved, too. The following would
299       match any column, relationship, or constraint named 'code' in any
300       source:
301
302        'code'
303
304       When you have schemas with overlapping names, such as a column named
305       'isa', you simply need to supply more specific match strings, as
306       ambiguous names are resolved with left-precedence. So, to match any
307       column, relationship, or constraint named 'isa', you could use the
308       following:
309
310        # Matches column, relationship, or constraints named 'isa':
311        '*:*/isa'
312
313       Different delimiter characters are used for the source level (':') and
314       the type level ('/') so you can do things like match any
315       column/relationship/constraint of a specific source, such as:
316
317        Artist:code
318
319       The above is equivalent to:
320
321        Artist:*/code
322
323       You can also supply a delimiter character to match a specific level
324       explicitly. So, if you wanted to match all changes to a source named
325       'isa':
326
327        # Matches a source (poorly) named 'isa'
328        'isa:'
329
330       The same works at the type level. The following are all equivalent
331
332        # Each of the following 3 filter strings are equivalent:
333        'columns/'
334        '*:columns/*'
335        'columns'
336
337       Internally, Hash::Layout is used to process the filter arguments.
338
339   event filtering
340       Besides matching specific parts of the schema, you can also filter by
341       event, which is either 'added', 'deleted' or 'changed' at both the
342       source and type level (i.e. the event of a new column is 'added' at the
343       type level, but 'changed' at the source level).
344
345       Filtering by event requires passing a HashRef argument to
346       filter/filter_out, with the special 'events' key matching 'type'
347       events, and 'source_events' matching 'source' events. Both accept
348       either a string (when specifying only one event) or an ArrayRef:
349
350        # Limit type (i.e. columns, relationships, etc) events to 'added'
351        $D = $D->filter({ events => 'added' });
352
353        # Exclude added and deleted sources:
354        $D = $D->filter_out({ source_events => ['added','deleted'] });
355
356        # Also excludes added and deleted sources:
357        $D = $D->filter({ source_events => ['changed'] });
358

EXAMPLES

360       For examples, see the SYNOPSIS and also the unit tests in "t/" which
361       has lots of working examples.
362

BUGS/LIMITATIONS

364       I'm not aware of any bugs at this point (although I'm sure there are
365       some), but there are several things to be aware of in general when
366       using this tool that are worth mentioning:
367
368       ·   Firstly, the diff packet is informational only; it does not contain
369           the information needed to "patch" anything, or see the previous and
370           new values. It assumes you already/still have access to the old and
371           new schemas to look up this info yourself. Its main purpose is
372           simply to flag which items are changed.
373
374       ·   Also, there is no deeper "diff" for 'added' and 'deleted' events
375           because it is redundant. For an added source, for example, you
376           already know that every column, relationship, etc., that it
377           contains is also "added" (depending on your definition of "added"),
378           so these are not included in the diff for the purpose of reducing
379           clutter. But, one side effect of this that you have to keep in mind
380           is that when filtering for all changes to 'columns', for example,
381           this will not include columns in added sources. This is just a
382           design decision made early on (and it can't be both ways). It just
383           means if you want to check for the expanded definition of
384           'modified' columns, which include added/deleted via a source, you
385           must also test for added/deleted sources.
386
387           In a later version, an additional layer of sugar methods could be
388           added to provide convenient access to some of these concepts.
389
390       ·   Filter string arguments are NOT glob patterns, so you can't do
391           things like 'Arti*' to match sub-strings (this may be a worthwhile
392           feature to add in a later version). The wildcard "*" applies to
393           whole items only.
394
395       ·   Also, the special "*" character can only be used in place of the
396           predefined first 3 levels ('*:*/*') and not within deeper
397           column_info/relationship_info sub-hashes (so you can't match
398           'Artist:columns/foo.*.list'). We're really splitting hairs at this
399           point, but it is still worth noting. (Internally, Hash::Layout is
400           used to process the filter arguments, so these limitations have to
401           do with the design of that package which provides more-useful
402           flexibility in other areas)
403
404       ·   In many practical cases, differences in loaded components will
405           produce many more changes than just 'isa'. It depends on whether or
406           not the components in question change the column/relationship
407           infos.  One common example is InflateColumn::DateTime which sets
408           inflator/deflators on all date columns. This is more of a feature
409           than it is a limitation, but it is something to keep in mind. If
410           one side loads a component(s) like this but the other doesn't,
411           you'll have lots of differences to contend with that you might not
412           actually care about. And, in order to filter out these differences,
413           you have to filter out a lot more than 'isa', which is trivial.
414           This is more about how DBIC works than anything else.
415
416           One thing that I did to overcome this when there were lots of
417           different loaded components that I couldn't do anything about was
418           to deploy both sides to a temp SQLite file, then create new schemas
419           (in memory) from those files with Schema::Loader, using the same
420           options (and thus the same loaded components), and then run the
421           diff on the two new schemas.  This type of approach may not work or
422           be appropriate in all scenarios; it obviously depends on what
423           exactly you are trying to accomplish.
424

SEE ALSO

426       ·   DBIx::Class
427
428       ·   SQL::Translator::Diff
429
430       ·   DBIx::Class::Migration
431
432       ·   DBIx::Class::DeploymentHandler
433

AUTHOR

435       Henry Van Styn <vanstyn@cpan.org>
436
438       This software is copyright (c) 2014 by IntelliTree Solutions llc.
439
440       This is free software; you can redistribute it and/or modify it under
441       the same terms as the Perl 5 programming language system itself.
442
443
444
445perl v5.30.1                      2020-01-29      DBIx::Class::Schema::Diff(3)
Impressum