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        # Git a checksum/fingerprint of the diff data:
40        my $checksum = $D->fingerprint;
41
42       Filtering the diff:
43
44        # Get all differences (hash structure):
45        my $hash = $D->diff;
46
47        # Only column differences:
48        $hash = $D->filter('columns')->diff;
49
50        # Only things named 'Artist' or 'CD':
51        $hash = $D->filter(qw/Artist CD/)->diff;
52
53        # Things named 'Artist', *columns* named 'CD' and *relationships* named 'columns':
54        $hash = $D->filter(qw(Artist columns/CD relationships/columns))->diff;
55
56        # Sources named 'Artist', excluding column changes:
57        $hash = $D->filter('Artist:')->filter_out('columns')->diff;
58
59        if( $D->filter('Artist:columns/name.size')->diff ) {
60         # Do something only if there has been a change in 'size' (i.e. in column_info)
61         # to the 'name' column in the 'Artist' source
62         # ...
63        }
64
65        # Names of all sources which exist in new_schema but not in old_schema:
66        my @sources = keys %{
67          $D->filter({ source_events => 'added' })->diff || {}
68        };
69
70        # All changes to existing unique_constraints (ignoring added or deleted)
71        # excluding those named or within sources named Album or Genre:
72        $hash = $D->filter_out({ events => [qw(added deleted)] })
73                  ->filter_out('Album','Genre')
74                  ->filter('constraints')
75                  ->diff;
76
77        # All changes to relationship attrs except for 'cascade_delete' in
78        # relationships named 'artists':
79        $hash = $D->filter_out('relationships/artists.attrs.cascade_delete')
80                  ->filter('relationships/*.attrs')
81                  ->diff;
82

DESCRIPTION

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

METHODS

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

FILTERING

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

EXAMPLES

366       For examples, see the SYNOPSIS and also the unit tests in "t/" which
367       has lots of working examples.
368

BUGS/LIMITATIONS

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

SEE ALSO

431       •   DBIx::Class
432
433       •   SQL::Translator::Diff
434
435       •   DBIx::Class::Migration
436
437       •   DBIx::Class::DeploymentHandler
438

AUTHOR

440       Henry Van Styn <vanstyn@cpan.org>
441
443       This software is copyright (c) 2014 by IntelliTree Solutions llc.
444
445       This is free software; you can redistribute it and/or modify it under
446       the same terms as the Perl 5 programming language system itself.
447
448
449
450perl v5.34.0                      2022-01-21      DBIx::Class::Schema::Diff(3)
Impressum