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 # 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
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
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
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 does
237 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 that
239 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
366 For examples, see the SYNOPSIS and also the unit tests in "t/" which
367 has lots of working examples.
368
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
431 • DBIx::Class
432
433 • SQL::Translator::Diff
434
435 • DBIx::Class::Migration
436
437 • DBIx::Class::DeploymentHandler
438
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.38.0 2023-07-20 DBIx::Class::Schema::Diff(3)