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; |