1 |
################################# |
2 |
# |
3 |
# $Id$ |
4 |
# |
5 |
# $Log$ |
6 |
# |
7 |
################################# |
8 |
|
9 |
package Data::Transfer::SyncStep; |
10 |
|
11 |
#require Exporter; |
12 |
#@ISA = qw( Exporter ); |
13 |
#@EXPORT = qw( syncSet2Table ); |
14 |
|
15 |
use strict; |
16 |
use warnings; |
17 |
|
18 |
use Data::Dumper; |
19 |
use misc::HashExt; |
20 |
|
21 |
# get logger instance |
22 |
my $logger = Log::Dispatch::Config->instance; |
23 |
|
24 |
sub new { |
25 |
my $invocant = shift; |
26 |
my $class = ref($invocant) || $invocant; |
27 |
my $self = { @_ }; |
28 |
$logger->debug( __PACKAGE__ . "->new(@_)" ); |
29 |
return bless $self, $class; |
30 |
} |
31 |
|
32 |
sub _dumpCompact { |
33 |
$Data::Dumper::Indent = 0; |
34 |
my $result = Dumper(@_); |
35 |
$Data::Dumper::Indent = 2; |
36 |
return $result; |
37 |
} |
38 |
|
39 |
sub syncSet2Table { |
40 |
|
41 |
my $self = shift; |
42 |
my $args = shift; |
43 |
|
44 |
my $tc = OneLineDumpHash->new( {} ); |
45 |
|
46 |
|
47 |
my @results; |
48 |
if ($args->{objectSet}) { |
49 |
# set of objects is already in $args |
50 |
$logger->debug( __PACKAGE__ . "->syncSet2Table( objectSet => '" . $args->{objectSet} . "' )" ); |
51 |
@results = @{$args->{objectSet}}; |
52 |
} else { |
53 |
# get set of objects from odbms by object name |
54 |
$logger->debug( __PACKAGE__ . "->syncSet2Table( objectName => '" . $args->{objectName} . "' )" ); |
55 |
my $objects_set = $self->{L}->remote($args->{objectName}); |
56 |
@results = $self->{L}->select($objects_set); |
57 |
} |
58 |
|
59 |
# iterate through set |
60 |
foreach my $obj (@results) { |
61 |
|
62 |
# clone object (in case we have to modify it here) |
63 |
my $objClone = $obj; |
64 |
|
65 |
# exclude defined fields (simply delete from object) |
66 |
foreach my $exField ( @{$args->{exclude}} ) { |
67 |
delete $objClone->{$exField}; |
68 |
} |
69 |
|
70 |
# calculate checksum for current object |
71 |
my $oid = $self->{L}->id($objClone); |
72 |
|
73 |
#my $objdump = $oid . "\n" . $objClone->quickdump(); |
74 |
my $objdump = $oid . "\n" . Dumper($objClone); |
75 |
|
76 |
# TODO: $logger->dump( ... ); |
77 |
#$logger->debug( __PACKAGE__ . ": " . $objdump ); |
78 |
#$logger->dump( __PACKAGE__ . ": " . $objdump ); |
79 |
|
80 |
# build checksum for current object-dump |
81 |
# md5-based fingerprint, base64 encoded (from Digest::MD5) |
82 |
#my $checksum_cur = md5_base64($objdump) . '=='; |
83 |
# 32-bit integer "hash" value (maybe faster?) (from DBI) |
84 |
my $checksum_cur = DBI::hash($objdump, 1); |
85 |
|
86 |
# get checksum for current entry from rdbms |
87 |
my $sql = "SELECT cs FROM $args->{sqlTable} WHERE $args->{sqlIdField}='$oid';"; |
88 |
#my $result = sendSql($sql); |
89 |
my $result = $self->{R}->sendCommand($sql); |
90 |
my $row = $result->getNextEntry(); |
91 |
my $checksum_remote = $row->{cs}; |
92 |
|
93 |
# determine if entry is "new" or "dirty" |
94 |
my $bool_new = !$checksum_remote; |
95 |
my $bool_dirty = $bool_new || $checksum_cur ne $checksum_remote || $args->{force}; |
96 |
print "c" if $main::args->{verbose}; |
97 |
if (!$bool_dirty) { |
98 |
$tc->{in_sync}++; |
99 |
next; |
100 |
} |
101 |
|
102 |
# field-structure for building sql |
103 |
# mapping of sql-fieldnames to object-attributes |
104 |
my $sqlmap; |
105 |
|
106 |
# manually set ... |
107 |
# ... object-id |
108 |
$sqlmap->{$args->{sqlIdField}} = $self->{L}->id($objClone); |
109 |
# ... checksum |
110 |
$sqlmap->{cs} = $checksum_cur; |
111 |
|
112 |
|
113 |
if ($args->{mapping}) { |
114 |
# apply mapping from $args->{mapping} to $sqlmap |
115 |
foreach my $key (keys %{$args->{mapping}}) { |
116 |
my $map_right = $args->{mapping}->{$key}; |
117 |
#print "map: $map_right", "\n"; |
118 |
my $value; |
119 |
# detect for callback |
120 |
if (ref($map_right) eq 'CODE') { |
121 |
$value = &$map_right($objClone); |
122 |
} else { |
123 |
$value = $objClone->{$map_right}; |
124 |
} |
125 |
#$sqlmap->{$key} = $value; |
126 |
$sqlmap->{$key} = $self->{R}->quoteSql($value); |
127 |
} |
128 |
} |
129 |
|
130 |
if ($args->{mappingV2}) { |
131 |
|
132 |
# apply mapping from $args->{mappingV2} to $sqlmap |
133 |
foreach my $mapStep (@{$args->{mappingV2}}) { |
134 |
|
135 |
# prepare left/right keys/values |
136 |
my $left_key = $mapStep->{left}; |
137 |
my $left_val = _resolveMapStepExpr( $objClone, $mapStep->{left} ); |
138 |
my $right_key = $mapStep->{right}; |
139 |
my $right_val = ( $mapStep->{right} ); |
140 |
#print "map: $map_right", "\n"; |
141 |
|
142 |
if ($mapStep->{method}) { |
143 |
if ($mapStep->{method} eq 'v:1') { |
144 |
$left_val = $left_key; |
145 |
} |
146 |
} |
147 |
|
148 |
#$sqlmap->{$key} = $value; |
149 |
#if ( grep(!/$right_key/, @{$args->{exclude}}) ) { |
150 |
$sqlmap->{$right_key} = $self->{R}->quoteSql($left_val); |
151 |
#} |
152 |
} |
153 |
} |
154 |
|
155 |
# TODO: $logger->dump( ... ); |
156 |
#$logger->debug( "sqlmap:" . "\n" . Dumper($sqlmap) ); |
157 |
|
158 |
my $sql_main; |
159 |
if ($bool_new) { |
160 |
$tc->{attempt_new}++; |
161 |
$sql_main = $self->{R}->hash2Sql($args->{sqlTable}, $sqlmap, 'SQL_INSERT'); |
162 |
} else { |
163 |
$tc->{attempt_modify}++; |
164 |
$sql_main = $self->{R}->hash2Sql($args->{sqlTable}, $sqlmap, 'SQL_UPDATE', "$args->{sqlIdField}='$sqlmap->{$args->{sqlIdField}}'"); |
165 |
} |
166 |
|
167 |
my $sqlHandle = $self->{R}->sendCommand($sql_main); |
168 |
if (!$sqlHandle->err) { |
169 |
$tc->{ok}++; |
170 |
print "t" if $main::args->{verbose}; |
171 |
} else { |
172 |
$tc->{error}++; |
173 |
push( @{$tc->{error_per_row}}, { |
174 |
statement => $sql_main, |
175 |
state => $sqlHandle->state, |
176 |
err => $sqlHandle->err, |
177 |
errstr => $sqlHandle->errstr, |
178 |
} ); |
179 |
#if ($args->{debug}) { print "sql-error with statement: $sql_main", "\n"; } |
180 |
print "e" if $main::args->{verbose}; |
181 |
} |
182 |
|
183 |
} |
184 |
print "\n" if $main::args->{verbose}; |
185 |
|
186 |
|
187 |
# build user-message from some stats and log it via "sysevent" |
188 |
# change logging level in case of an error |
189 |
|
190 |
my $level = 'LEVEL_INFO'; |
191 |
|
192 |
my $msg; |
193 |
|
194 |
# $msg .= "stats: "; |
195 |
# $tc->{in_sync} && ($msg .= "in-sync: $tc->{in_sync}"); |
196 |
# $tc->{attempt_new} && ($msg .= " / " . "attempt-new: $tc->{attempt_new}"); |
197 |
# $tc->{attempt_modify} && ($msg .= " / " . "attempt-modify: $tc->{attempt_modify}"); |
198 |
# $tc->{ok} && ($msg .= " / " . "result-ok: $tc->{ok}"); |
199 |
# $tc->{error} && ($msg .= " / " . "result-error: $tc->{error}"); |
200 |
|
201 |
$msg .= "stats: $tc"; |
202 |
|
203 |
if ($tc->{error_per_row}) { |
204 |
$msg .= "\n"; |
205 |
$level = 'LEVEL_NOTIFY'; |
206 |
$msg .= "errors:" . "\n"; |
207 |
$msg .= Dumper($tc->{error_per_row}); |
208 |
} |
209 |
|
210 |
# todo!!! |
211 |
#sysevent( { usermsg => $msg, level => $level }, $taskEvent ); |
212 |
$logger->info($msg); |
213 |
|
214 |
return $tc; |
215 |
|
216 |
} |
217 |
|
218 |
sub _resolveMapStepExpr { |
219 |
|
220 |
my $obj = shift; |
221 |
my $expr = shift; |
222 |
|
223 |
my $value; |
224 |
# detect for callback (code-reference) |
225 |
if (ref($expr) eq 'CODE') { |
226 |
$value = &$expr($obj); |
227 |
} elsif ($expr =~ m/->/) { |
228 |
# use expr as complex object reference declaration (obj->subObj->subSubObj->0->attribute) |
229 |
my $objPerlRefString = _mapStepExprToPerlRefString($expr); |
230 |
#print "\n", "expr: $expr"; |
231 |
#print "\n", "objPerlRefString: $objPerlRefString"; |
232 |
$value = eval('$obj' . '->' . $objPerlRefString); |
233 |
} else { |
234 |
# use expr as simple scalar key (attributename) |
235 |
$value = $obj->{$expr}; |
236 |
} |
237 |
|
238 |
return $value; |
239 |
|
240 |
} |
241 |
|
242 |
sub _mapStepExprToPerlRefString { |
243 |
my $expr = shift; |
244 |
|
245 |
# split expression by dereference operators first |
246 |
my @parts = split(/->/, $expr); |
247 |
|
248 |
# wrap []'s around each part, if it consists of numeric characters only (=> numeric array-index), |
249 |
# use {}'s, if there are word-characters in it (=> alphanumeric hash-key) |
250 |
map { |
251 |
m/^\d+$/ && ($_ = "[$_]") || ($_ = "{$_}"); |
252 |
} @parts; |
253 |
|
254 |
# join parts with dereference operators together again and return built string |
255 |
return join('->', @parts); |
256 |
} |
257 |
|
258 |
|
259 |
1; |