1DBIx::Class::Schema::DiUfsfe(r3)Contributed Perl DocumenDtBaItxi:o:nClass::Schema::Diff(3)
2
3
4
6 DBIx::Class::Schema::Diff - Identify differences between two
7 DBIx::Class schemas
8
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
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
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
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
360 For examples, see the SYNOPSIS and also the unit tests in "t/" which
361 has lots of working examples.
362
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
426 · DBIx::Class
427
428 · SQL::Translator::Diff
429
430 · DBIx::Class::Migration
431
432 · DBIx::Class::DeploymentHandler
433
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.32.0 2020-07-28 DBIx::Class::Schema::Diff(3)