| line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
|
1
|
|
|
|
|
|
|
#!/usr/bin/perl -w |
|
2
|
|
|
|
|
|
|
|
|
3
|
|
|
|
|
|
|
=head1 Name |
|
4
|
|
|
|
|
|
|
|
|
5
|
|
|
|
|
|
|
Class::DBI::DataMigration::Mapper - Abstract class for mapping a single row in |
|
6
|
|
|
|
|
|
|
the source database to a single row in the target database. |
|
7
|
|
|
|
|
|
|
|
|
8
|
|
|
|
|
|
|
=head1 Synopsis |
|
9
|
|
|
|
|
|
|
|
|
10
|
|
|
|
|
|
|
use Class::DBI::DataMigration::Mapper; |
|
11
|
|
|
|
|
|
|
|
|
12
|
|
|
|
|
|
|
# ... later ... |
|
13
|
|
|
|
|
|
|
# Assume we've retrieved a $source_object of class Class from the source |
|
14
|
|
|
|
|
|
|
# database, and have assembled $mappings, a ref to an appropriate hash of |
|
15
|
|
|
|
|
|
|
# Class::DBI::DataMigration::Mapping objects: |
|
16
|
|
|
|
|
|
|
|
|
17
|
|
|
|
|
|
|
my $mapper = new Class::DBI::DataMigration::Mapper({ |
|
18
|
|
|
|
|
|
|
target_cdbi_class => Class, |
|
19
|
|
|
|
|
|
|
mappings => $mappings, |
|
20
|
|
|
|
|
|
|
target_search_keys => \@search_keys |
|
21
|
|
|
|
|
|
|
}); |
|
22
|
|
|
|
|
|
|
|
|
23
|
|
|
|
|
|
|
my $new_db_object = $mapper->map($source_object); |
|
24
|
|
|
|
|
|
|
|
|
25
|
|
|
|
|
|
|
# ... now $new_db_object is in the new database ... |
|
26
|
|
|
|
|
|
|
|
|
27
|
|
|
|
|
|
|
=head1 Description |
|
28
|
|
|
|
|
|
|
|
|
29
|
|
|
|
|
|
|
Class::DBI::DataMigration::Mapper is an abstract parent class for objects that |
|
30
|
|
|
|
|
|
|
will map a single row at a time from the source database into a single row in |
|
31
|
|
|
|
|
|
|
the new one. This is accomplished via Class::DBI; it's assumed that appropriate |
|
32
|
|
|
|
|
|
|
classes exist representing the tables in the source and target databases. |
|
33
|
|
|
|
|
|
|
|
|
34
|
|
|
|
|
|
|
Mapping is accomplished using a hash of instances of |
|
35
|
|
|
|
|
|
|
Class::DBI::DataMigration::Mapping objects. |
|
36
|
|
|
|
|
|
|
|
|
37
|
|
|
|
|
|
|
=cut |
|
38
|
|
|
|
|
|
|
|
|
39
|
1
|
|
|
1
|
|
901
|
use strict; |
|
|
1
|
|
|
|
|
1
|
|
|
|
1
|
|
|
|
|
41
|
|
|
40
|
|
|
|
|
|
|
|
|
41
|
|
|
|
|
|
|
package Class::DBI::DataMigration::Mapper; |
|
42
|
|
|
|
|
|
|
|
|
43
|
1
|
|
|
1
|
|
5
|
use Carp; |
|
|
1
|
|
|
|
|
1
|
|
|
|
1
|
|
|
|
|
96
|
|
|
44
|
1
|
|
|
1
|
|
5
|
use base 'Class::Accessor'; |
|
|
1
|
|
|
|
|
1
|
|
|
|
1
|
|
|
|
|
811
|
|
|
45
|
|
|
|
|
|
|
|
|
46
|
|
|
|
|
|
|
__PACKAGE__->mk_accessors( |
|
47
|
|
|
|
|
|
|
qw/target_cdbi_class target_keys target_search_keys mappings/ |
|
48
|
|
|
|
|
|
|
); |
|
49
|
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
=head1 Methods |
|
51
|
|
|
|
|
|
|
|
|
52
|
|
|
|
|
|
|
=head2 mappings |
|
53
|
|
|
|
|
|
|
|
|
54
|
|
|
|
|
|
|
Gets/sets a ref to a hash of Class::DBI::DataMigration::Mapping objects, keyed |
|
55
|
|
|
|
|
|
|
on keys into the source class whose values will be used to produce values for |
|
56
|
|
|
|
|
|
|
the target class. |
|
57
|
|
|
|
|
|
|
|
|
58
|
|
|
|
|
|
|
=head2 target_cdbi_class |
|
59
|
|
|
|
|
|
|
|
|
60
|
|
|
|
|
|
|
Gets/sets the target class in which to build a new object (or edit an existing |
|
61
|
|
|
|
|
|
|
one) using the mappings and the source_object supplied to map() |
|
62
|
|
|
|
|
|
|
|
|
63
|
|
|
|
|
|
|
=head2 target_keys |
|
64
|
|
|
|
|
|
|
|
|
65
|
|
|
|
|
|
|
Gets/sets a ref to a hash that acts as a dictionary between the target and |
|
66
|
|
|
|
|
|
|
source classes; the keys in this hash are keys into the target class, and the |
|
67
|
|
|
|
|
|
|
values are the corresponding keys into the source class. |
|
68
|
|
|
|
|
|
|
|
|
69
|
|
|
|
|
|
|
=head2 target_search_keys |
|
70
|
|
|
|
|
|
|
|
|
71
|
|
|
|
|
|
|
Gets/sets a ref to a list of keys that will be used during mapping to search |
|
72
|
|
|
|
|
|
|
for a target class object; if found, data from the matching source db object will |
|
73
|
|
|
|
|
|
|
be used to edit the already-existing target db object. Otherwise, a new object will |
|
74
|
|
|
|
|
|
|
be created in the target db. If target_search_keys is left empty, no searching |
|
75
|
|
|
|
|
|
|
will be done, and all objects from the source db will be mirrored as new |
|
76
|
|
|
|
|
|
|
objects in the target db. |
|
77
|
|
|
|
|
|
|
|
|
78
|
|
|
|
|
|
|
=head2 map |
|
79
|
|
|
|
|
|
|
|
|
80
|
|
|
|
|
|
|
Expects one parameter: the source_object in the source database whose data is to |
|
81
|
|
|
|
|
|
|
be mapped into an object in the target_cdbi_class. |
|
82
|
|
|
|
|
|
|
|
|
83
|
|
|
|
|
|
|
This method causes the Mapper to iterate through its target_keys hash, calling |
|
84
|
|
|
|
|
|
|
map() on each mapping with the source object and the source key under which it |
|
85
|
|
|
|
|
|
|
was stored in the mappings hash. The returned values of each of these map() |
|
86
|
|
|
|
|
|
|
calls are collected into a hash and used to do one of the following: |
|
87
|
|
|
|
|
|
|
|
|
88
|
|
|
|
|
|
|
- if an object matching our target_search_keys in the data hash is found in |
|
89
|
|
|
|
|
|
|
the target_cdbi_class (we use the first one found), that object is synchronized |
|
90
|
|
|
|
|
|
|
using the rest of the data in the data hash and returned; and, |
|
91
|
|
|
|
|
|
|
|
|
92
|
|
|
|
|
|
|
- if our target_search_keys is empty, or if no object matching the |
|
93
|
|
|
|
|
|
|
those keys in the data hash exists in the target_cdbi_class, a new target |
|
94
|
|
|
|
|
|
|
class object is created and returned. |
|
95
|
|
|
|
|
|
|
|
|
96
|
|
|
|
|
|
|
If errors are encountered during this process, an error message is returned |
|
97
|
|
|
|
|
|
|
instead of the affected object. |
|
98
|
|
|
|
|
|
|
|
|
99
|
|
|
|
|
|
|
Subclasses may do something fancier. |
|
100
|
|
|
|
|
|
|
|
|
101
|
|
|
|
|
|
|
=cut |
|
102
|
|
|
|
|
|
|
|
|
103
|
|
|
|
|
|
|
sub map { |
|
104
|
0
|
|
|
0
|
1
|
|
my ($self, $source_object) = @_; |
|
105
|
0
|
|
|
|
|
|
my %newobj_data = (); |
|
106
|
0
|
|
|
|
|
|
while ((my $source_key, my $target_key) = each %{$self->target_keys}) { |
|
|
0
|
|
|
|
|
|
|
|
107
|
0
|
0
|
|
|
|
|
my $mapping = $self->mappings->{$source_key} |
|
108
|
|
|
|
|
|
|
or confess "Couldn't retrieve mapping for source key $source_key"; |
|
109
|
0
|
|
|
|
|
|
my $mapped = $mapping->map($source_key, $source_object); |
|
110
|
0
|
0
|
|
|
|
|
$newobj_data{$target_key} = $mapped if $target_key; |
|
111
|
|
|
|
|
|
|
} |
|
112
|
|
|
|
|
|
|
|
|
113
|
0
|
|
|
|
|
|
return $self->_create_or_edit_object($source_object, \%newobj_data); |
|
114
|
|
|
|
|
|
|
} |
|
115
|
|
|
|
|
|
|
|
|
116
|
|
|
|
|
|
|
sub _create_or_edit_object { |
|
117
|
|
|
|
|
|
|
# Useful for subclasses to override for post-mapping-processing |
|
118
|
|
|
|
|
|
|
# (in this version we don't use $source_object, but subclasses can). |
|
119
|
|
|
|
|
|
|
|
|
120
|
0
|
|
|
0
|
|
|
my ($self, $source_object, $newobj_data) = @_; |
|
121
|
|
|
|
|
|
|
|
|
122
|
0
|
0
|
|
|
|
|
eval "require " . $self->target_cdbi_class unless $self->target_cdbi_class->can('new'); |
|
123
|
0
|
0
|
|
|
|
|
confess $@ if $@; |
|
124
|
|
|
|
|
|
|
|
|
125
|
0
|
0
|
0
|
|
|
|
if (($self->target_search_keys) and |
|
|
0
|
|
|
|
|
|
|
|
126
|
|
|
|
|
|
|
(scalar(@{$self->target_search_keys}) > 0)) { |
|
127
|
0
|
|
|
|
|
|
my %search_criteria; |
|
128
|
0
|
|
|
|
|
|
foreach (@{$self->target_search_keys}) { |
|
|
0
|
|
|
|
|
|
|
|
129
|
0
|
|
|
|
|
|
$search_criteria{$_} = $newobj_data->{$_}; |
|
130
|
|
|
|
|
|
|
} |
|
131
|
0
|
|
|
|
|
|
my $search_results = $self->target_cdbi_class->search(%search_criteria); |
|
132
|
0
|
|
|
|
|
|
my $search_obj = $search_results->next; |
|
133
|
0
|
|
|
|
|
|
my $errstr = ''; |
|
134
|
0
|
0
|
|
|
|
|
if ($search_obj) { |
|
135
|
0
|
|
|
|
|
|
while (my ($key, $value) = each %$newobj_data) { |
|
136
|
0
|
0
|
|
|
|
|
if (ref $value) { |
|
137
|
0
|
|
|
|
|
|
eval qq{ \$search_obj->$key($value) }; |
|
138
|
|
|
|
|
|
|
} else { |
|
139
|
|
|
|
|
|
|
# quote $value if it's not a reference: |
|
140
|
0
|
0
|
|
|
|
|
eval qq{ \$search_obj->$key('$value') } if $value; |
|
141
|
|
|
|
|
|
|
} |
|
142
|
0
|
0
|
|
|
|
|
$errstr .= $@ if $@; |
|
143
|
|
|
|
|
|
|
} |
|
144
|
0
|
0
|
|
|
|
|
return $errstr if $errstr; |
|
145
|
0
|
|
|
|
|
|
return $search_obj; |
|
146
|
|
|
|
|
|
|
} |
|
147
|
|
|
|
|
|
|
} |
|
148
|
|
|
|
|
|
|
|
|
149
|
|
|
|
|
|
|
# If we've gotten this far, then either there were no target class search keys, |
|
150
|
|
|
|
|
|
|
# or no target class object matched the search keys. Either way, we create a new one. |
|
151
|
|
|
|
|
|
|
|
|
152
|
0
|
|
|
|
|
|
my $created_obj = eval { return $self->target_cdbi_class->create($newobj_data); }; |
|
|
0
|
|
|
|
|
|
|
|
153
|
0
|
0
|
|
|
|
|
$created_obj = $@ if $@; |
|
154
|
0
|
|
|
|
|
|
return $created_obj; |
|
155
|
|
|
|
|
|
|
} |
|
156
|
|
|
|
|
|
|
|
|
157
|
|
|
|
|
|
|
=begin testing |
|
158
|
|
|
|
|
|
|
|
|
159
|
|
|
|
|
|
|
use_ok('Class::DBI::DataMigration::Mapper'); |
|
160
|
|
|
|
|
|
|
can_ok('Class::DBI::DataMigration::Mapper', 'map'); |
|
161
|
|
|
|
|
|
|
can_ok('Class::DBI::DataMigration::Mapper', 'mappings'); |
|
162
|
|
|
|
|
|
|
can_ok('Class::DBI::DataMigration::Mapper', 'target_keys'); |
|
163
|
|
|
|
|
|
|
can_ok('Class::DBI::DataMigration::Mapper', 'target_cdbi_class'); |
|
164
|
|
|
|
|
|
|
|
|
165
|
|
|
|
|
|
|
=end testing |
|
166
|
|
|
|
|
|
|
|
|
167
|
|
|
|
|
|
|
=head1 See Also |
|
168
|
|
|
|
|
|
|
|
|
169
|
|
|
|
|
|
|
C |
|
170
|
|
|
|
|
|
|
|
|
171
|
|
|
|
|
|
|
=head1 Author |
|
172
|
|
|
|
|
|
|
|
|
173
|
|
|
|
|
|
|
Dan Friedman |
|
174
|
|
|
|
|
|
|
|
|
175
|
|
|
|
|
|
|
=head1 Copyright & License |
|
176
|
|
|
|
|
|
|
|
|
177
|
|
|
|
|
|
|
Copyright 2004 Dan Friedman, All Rights Reserved. |
|
178
|
|
|
|
|
|
|
|
|
179
|
|
|
|
|
|
|
This program is free software; you can redistribute it and/or modify it |
|
180
|
|
|
|
|
|
|
under the same terms as Perl itself. |
|
181
|
|
|
|
|
|
|
|
|
182
|
|
|
|
|
|
|
Please note that these modules are not products of or supported by the |
|
183
|
|
|
|
|
|
|
employers of the various contributors to the code. |
|
184
|
|
|
|
|
|
|
|
|
185
|
|
|
|
|
|
|
=cut |
|
186
|
|
|
|
|
|
|
|
|
187
|
|
|
|
|
|
|
1; |