/[cvs]/nfo/perl/libs/Data/Transfer/Sync.pm
ViewVC logotype

Annotation of /nfo/perl/libs/Data/Transfer/Sync.pm

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1.6 - (hide annotations)
Fri Dec 6 04:49:10 2002 UTC (21 years, 7 months ago) by jonen
Branch: MAIN
Changes since 1.5: +7 -1 lines
+ disabled output-puffer here

1 jonen 1.6 ## $Id: Sync.pm,v 1.5 2002/12/05 08:06:05 joko Exp $
2 joko 1.1 ##
3     ## Copyright (c) 2002 Andreas Motl <andreas.motl@ilo.de>
4     ##
5     ## See COPYRIGHT section in pod text below for usage and distribution rights.
6     ##
7     ## ----------------------------------------------------------------------------------------
8 joko 1.2 ## $Log: Sync.pm,v $
9 jonen 1.6 ## Revision 1.5 2002/12/05 08:06:05 joko
10     ## + bugfix with determining empty fields (Null) with DBD::CSV
11     ## + debugging
12     ## + updated comments
13     ##
14 joko 1.5 ## Revision 1.4 2002/12/03 15:54:07 joko
15     ## + {import}-flag is now {prepare}-flag
16     ##
17 joko 1.4 ## Revision 1.3 2002/12/01 22:26:59 joko
18     ## + minor cosmetics for logging
19     ##
20 joko 1.3 ## Revision 1.2 2002/12/01 04:43:25 joko
21     ## + mapping deatil entries may now be either an ARRAY or a HASH
22     ## + erase flag is used now (for export-operations)
23     ## + expressions to refer to values inside deep nested structures
24     ## - removed old mappingV2-code
25     ## + cosmetics
26     ## + sub _erase_all
27     ##
28 joko 1.2 ## Revision 1.1 2002/11/29 04:45:50 joko
29     ## + initial check in
30     ##
31 joko 1.1 ## Revision 1.1 2002/10/10 03:44:21 cvsjoko
32     ## + new
33     ## ----------------------------------------------------------------------------------------
34    
35    
36     package Data::Transfer::Sync;
37    
38     use strict;
39     use warnings;
40    
41     use Data::Dumper;
42     use misc::HashExt;
43     use libp qw( md5_base64 );
44     use libdb qw( quotesql hash2Sql );
45 joko 1.2 use Data::Transform::Deep qw( hash2object refexpr2perlref );
46 joko 1.1 use Data::Compare::Struct qw( getDifference isEmpty );
47    
48     # get logger instance
49     my $logger = Log::Dispatch::Config->instance;
50    
51 jonen 1.6 $| = 1;
52 joko 1.1
53     sub new {
54     my $invocant = shift;
55     my $class = ref($invocant) || $invocant;
56     my $self = { @_ };
57     $logger->debug( __PACKAGE__ . "->new(@_)" );
58     bless $self, $class;
59     $self->_init();
60     return $self;
61     }
62    
63    
64     sub _init {
65     my $self = shift;
66    
67     # build new container if necessary
68     $self->{container} = Data::Storage::Container->new() if !$self->{container};
69    
70     # add storages to container (optional)
71     foreach (keys %{$self->{storages}}) {
72     $self->{container}->addStorage($_, $self->{storages}->{$_});
73     }
74    
75     # tag storages with id-authority and checksum-provider information
76     # TODO: better store tag inside metadata to hold bits together!
77     map { $self->{container}->{storage}->{$_}->{isIdentAuthority} = 1 } @{$self->{id_authorities}};
78     map { $self->{container}->{storage}->{$_}->{isChecksumAuthority} = 1; } @{$self->{checksum_authorities}};
79     map { $self->{container}->{storage}->{$_}->{isWriteProtected} = 1; } @{$self->{write_protected}};
80    
81     }
82    
83    
84     # TODO: some feature to show off the progress of synchronization (cur/max * 100)
85     sub syncNodes {
86    
87     my $self = shift;
88     my $args = shift;
89    
90     # remember arguments through the whole processing
91     $self->{args} = $args;
92    
93     $logger->debug( __PACKAGE__ . "->syncNodes: starting" );
94    
95     # hash to hold and/or fill in metadata required for the processing
96     $self->{meta} = {};
97    
98     # hash to sum up results
99     my $direction_arrow = '';
100    
101     # detect synchronization method to determine which optical symbol (directed arrow) to use
102     if (lc $self->{args}->{direction} eq 'push') {
103     $direction_arrow = '->';
104     } elsif (lc $self->{args}->{direction} eq 'pull') {
105     $direction_arrow = '<-';
106     } elsif (lc $self->{args}->{direction} eq 'full') {
107     $direction_arrow = '<->';
108     } else {
109     }
110    
111     # decompose identifiers for each partner
112     # TODO: take this list from already established/given metadata
113     foreach ('source', 'target') {
114    
115     # get/set metadata for further processing
116    
117     # Partner and Node (e.g.: "L:Country" or "R:countries.csv")
118     if (my $item = $self->{args}->{$_}) {
119     my @item = split(':', $item);
120     $self->{meta}->{$_}->{dbkey} = $item[0];
121     $self->{meta}->{$_}->{node} = $item[1];
122     }
123    
124     # Filter
125     if (my $item_filter = $self->{args}->{$_ . '_filter'}) {
126     $self->{meta}->{$_}->{filter} = $item_filter;
127     }
128    
129     # IdentProvider
130     if (my $item_ident = $self->{args}->{$_ . '_ident'}) {
131     my @item_ident = split(':', $item_ident);
132     $self->{meta}->{$_}->{IdentProvider} = { method => $item_ident[0], arg => $item_ident[1] };
133     }
134    
135     # TODO: ChecksumProvider
136    
137     # exclude properties/subnodes
138     if (my $item_exclude = $self->{args}->{$_ . '_exclude'}) {
139     $self->{meta}->{$_}->{subnodes_exclude} = $item_exclude;
140     }
141    
142     # TypeProvider
143     if (my $item_type = $self->{args}->{$_ . '_type'}) {
144     my @item_type = split(':', $item_type);
145     $self->{meta}->{$_}->{TypeProvider} = { method => $item_type[0], arg => $item_type[1] };
146     }
147    
148     # Callbacks - writers (will be triggered _before_ writing to target)
149     if (my $item_writers = $self->{args}->{$_ . '_callbacks_write'}) {
150     my $descent = $_; # this is important since the following code inside the map wants to use its own context variables
151     map { $self->{meta}->{$descent}->{Callback}->{write}->{$_}++; } @$item_writers;
152     }
153    
154     # Callbacks - readers (will be triggered _after_ reading from source)
155     if (my $item_readers = $self->{args}->{$_ . '_callbacks_read'}) {
156     my $descent = $_;
157     map { $self->{meta}->{$descent}->{Callback}->{read}->{$_}++; } @$item_readers;
158     }
159    
160     # resolve storage objects
161     #$self->{$_} = $self->{container}->{storage}->{$self->{meta}->{$_}->{dbkey}};
162     # relink references to metainfo
163     $self->{meta}->{$_}->{storage} = $self->{container}->{storage}->{$self->{meta}->{$_}->{dbkey}};
164     #print "iiiiisprov: ", Dumper($self->{meta}->{$_}->{storage}), "\n";
165     }
166    
167     $logger->info( __PACKAGE__ . "->syncNodes: source=$self->{meta}->{source}->{dbkey}/$self->{meta}->{source}->{node} $direction_arrow target=$self->{meta}->{target}->{dbkey}/$self->{meta}->{target}->{node}" );
168    
169     # build mapping
170 joko 1.2 # incoming: and Array of node map entries (Array or Hash) - e.g.
171     # [ 'source:item_name' => 'target:class_val' ]
172     # { source => 'event->startDateTime', target => 'begindate' }
173 joko 1.1 foreach (@{$self->{args}->{mapping}}) {
174 joko 1.2 if (ref $_ eq 'ARRAY') {
175     my @entry1 = split(':', $_->[0]);
176     my @entry2 = split(':', $_->[1]);
177     my $descent = [];
178     my $node = [];
179     $descent->[0] = $entry1[0];
180     $descent->[1] = $entry2[0];
181     $node->[0] = $entry1[1];
182     $node->[1] = $entry2[1];
183     push @{$self->{meta}->{$descent->[0]}->{childnodes}}, $node->[0];
184     push @{$self->{meta}->{$descent->[1]}->{childnodes}}, $node->[1];
185     } elsif (ref $_ eq 'HASH') {
186     foreach my $entry_key (keys %$_) {
187     my $entry_val = $_->{$entry_key};
188     push @{$self->{meta}->{$entry_key}->{childnodes}}, $entry_val;
189     }
190     }
191    
192 joko 1.1 }
193    
194     # check partners/nodes: does partner exist / is node available?
195     foreach my $partner (keys %{$self->{meta}}) {
196     next if $self->{meta}->{$partner}->{storage}->{locator}->{type} eq 'DBI'; # for DBD::CSV - re-enable for others
197     my $node = $self->{meta}->{$partner}->{node};
198     if (!$self->{meta}->{$partner}->{storage}->existsChildNode($node)) {
199     $logger->critical( __PACKAGE__ . "->syncNodes: Could not reach \"$node\" at \"$partner\"." );
200     return;
201     }
202     }
203    
204     # TODO:
205     # + if action == PUSH: start processing
206     # -+ if action == PULL: swap metadata and start processing
207     # - if action == FULL: start processing, then swap metadata and (re-)start processing
208    
209     #print Dumper($self->{args});
210    
211     # manipulate metainfo according to direction of synchronization
212     if (lc $self->{args}->{direction} eq 'push') {
213     # just do it ...
214     } elsif (lc $self->{args}->{direction} eq 'pull') {
215     #print "=======SWAP", "\n";
216     # swap
217     ($self->{meta}->{source}, $self->{meta}->{target}) =
218     ($self->{meta}->{target}, $self->{meta}->{source});
219     } elsif (lc $self->{args}->{direction} eq 'full') {
220     } else {
221     }
222    
223     # import flag means: prepare the source node to be syncable
224     # this is useful if there are e.g. no "ident" or "checksum" columns yet inside a DBI like (row-based) storage
225 joko 1.4 if ($self->{args}->{prepare}) {
226 joko 1.1 $self->_prepareNode_MetaProperties('source');
227     $self->_prepareNode_DummyIdent('source');
228     #return;
229     #$self->_erase_all($opts->{source_node});
230     }
231    
232 joko 1.2 # erase flag means: erase the target
233     #if ($opts->{erase}) {
234     if ($self->{args}->{erase}) {
235     # TODO: move this method to the scope of the synchronization core and wrap it around different handlers
236     #print "ERASE", "\n";
237     $self->_erase_all('target');
238     }
239    
240 joko 1.1 $self->_syncNodes();
241    
242     }
243    
244    
245     # TODO: abstract the hardwired use of "source" and "target" in here somehow - hmmmm....... /(="§/%???
246     sub _syncNodes {
247    
248     my $self = shift;
249    
250     my $tc = OneLineDumpHash->new( {} );
251     my $results;
252    
253     # set of objects is already in $self->{args}
254     # TODO: make independent of the terminology "object..."
255     $results = $self->{args}->{objectSet} if $self->{args}->{objectSet};
256    
257     # apply filter
258     if (my $filter = $self->{meta}->{source}->{filter}) {
259     #print Dumper($filter);
260     #exit;
261     $results ||= $self->_getNodeList('source', $filter);
262     }
263    
264 joko 1.5 # get reference to node list from convenient method provided by CORE-HANDLE
265 joko 1.1 #$results ||= $self->{source}->getListUnfiltered($self->{meta}->{source}->{node});
266     #$results ||= $self->{meta}->{source}->{storage}->getListUnfiltered($self->{meta}->{source}->{node});
267     $results ||= $self->_getNodeList('source');
268    
269     # checkpoint: do we actually have a list to iterate through?
270     if (!$results || !@{$results}) {
271     $logger->notice( __PACKAGE__ . "->syncNodes: No nodes to synchronize." );
272     return;
273     }
274    
275     # dereference
276     my @results = @{$results};
277    
278     # iterate through set
279     foreach my $source_node_real (@results) {
280    
281     $tc->{total}++;
282    
283     #print "======================== iter", "\n";
284    
285     # clone object (in case we have to modify it here)
286     # TODO:
287     # - is a "deep_copy" needed here if occouring modifications take place?
288     # - puuhhhh, i guess a deep_copy would destroy tangram mechanisms?
289     # - after all, just take care for now that this object doesn't get updated!
290     my $source_node = $source_node_real;
291    
292     # modify entry - handle new style callbacks (the readers)
293     #print Dumper($source_node);
294     #exit;
295    
296     my $descent = 'source';
297    
298     # handle callbacks right now while scanning them (asymmetric to the writers)
299     my $map_callbacks = {};
300     if (my $callbacks = $self->{meta}->{$descent}->{Callback}) {
301    
302     my $error = 0;
303    
304     foreach my $node (keys %{$callbacks->{read}}) {
305    
306     my $object = $source_node;
307     my $value; # = $source_node->{$node};
308    
309     # ------------ half-redundant: make $self->callCallback($object, $value, $opts)
310     my $perl_callback = $self->{meta}->{$descent}->{node} . '::' . $node . '_read';
311     my $evalstring = 'return ' . $perl_callback . '( { object => $object, property => $node, value => $value, storage => $self->{meta}->{$descent}->{storage} } );';
312     #print $evalstring, "\n"; exit;
313     my $cb_result = eval($evalstring);
314     if ($@) {
315     die $@;
316     $error = 1;
317     print $@, "\n";
318     }
319     # ------------ half-redundant: make $self->callCallback($object, $value, $opts)
320    
321     $source_node->{$node} = $cb_result;
322    
323     }
324    
325     }
326    
327     #print Dumper($source_node);
328    
329     # exclude defined fields (simply delete from object)
330     map { delete $source_node->{$_} } @{$self->{meta}->{source}->{subnodes_exclude}};
331    
332     # here we accumulate information about the status of the current node (payload/row/object/item/entry)
333     $self->{node} = {};
334     $self->{node}->{source}->{payload} = $source_node;
335    
336     #print "res - ident", "\n";
337    
338     # determine ident of entry
339     my $identOK = $self->_resolveNodeIdent('source');
340     #if (!$identOK && lc $self->{args}->{direction} ne 'import') {
341     if (!$identOK) {
342 joko 1.5 #print Dumper($self->{meta}->{source});
343     $logger->critical( __PACKAGE__ . "->syncNodes: No ident found in source node \"$self->{meta}->{source}->{node}\", try to \"prepare\" this node first?" );
344 joko 1.1 return;
345     }
346    
347     #print "statload", "\n";
348     #print "ident: ", $self->{node}->{source}->{ident}, "\n";
349 joko 1.5 #print Dumper($self->{node});
350 joko 1.1
351     my $statOK = $self->_statloadNode('target', $self->{node}->{source}->{ident});
352 joko 1.5
353     #print Dumper($self->{node});
354 joko 1.1
355     # mark node as new either if there's no ident or if stat/load failed
356     if (!$statOK) {
357     $self->{node}->{status}->{new} = 1;
358     print "n" if $self->{verbose};
359     }
360    
361     #print "checksum", "\n";
362    
363     # determine status of entry by synchronization method
364     if ( (lc $self->{args}->{method} eq 'checksum') ) {
365     #if ( $statOK && (lc $self->{args}->{method} eq 'checksum') ) {
366     #if ( !$self->{node}->{status}->{new} && (lc $self->{args}->{method} eq 'checksum') ) {
367    
368     # TODO:
369     # is this really worth a "critical"???
370     # no - it should just be a debug appendix i believe
371    
372     #print "readcs", "\n";
373    
374     # calculate checksum of source node
375     #$self->_calcChecksum('source');
376     if (!$self->_readChecksum('source')) {
377     $logger->critical( __PACKAGE__ . "->_readChecksum: Could not find \"source\" entry with ident=\"$self->{node}->{source}->{ident}\"" );
378     $tc->{skip}++;
379     print "s" if $self->{verbose};
380     next;
381     }
382    
383     # get checksum from synchronization target
384     $self->_readChecksum('target');
385     #if (!$self->_readChecksum('target')) {
386     # $logger->critical( __PACKAGE__ . "->_readChecksum: Could not find \"target\" entry with ident=\"$self->{node}->{source}->{ident}\"" );
387     # next;
388     #}
389    
390     # pre flight check: do we actually have a checksum provided?
391     #if (!$self->{node}->{source}->{checksum}) {
392     # print "Source checksum for entry with ident \"$self->{node}->{source}->{ident}\" could not be calculated, maybe it's missing?.", "\n";
393     # return;
394     #}
395    
396     # determine if entry is "new" or "dirty"
397     # after all, this seems to be the point where the hammer falls.....
398     print "c" if $self->{verbose};
399     $self->{node}->{status}->{new} = !$self->{node}->{target}->{checksum};
400     if (!$self->{node}->{status}->{new}) {
401     $self->{node}->{status}->{dirty} =
402     $self->{node}->{status}->{new} ||
403     (!$self->{node}->{source}->{checksum} || !$self->{node}->{target}->{checksum}) ||
404     ($self->{node}->{source}->{checksum} ne $self->{node}->{target}->{checksum}) ||
405     $self->{args}->{force};
406     }
407    
408     }
409    
410     # first reaction on entry-status: continue with next entry if the current is already "in sync"
411     if (!$self->{node}->{status}->{new} && !$self->{node}->{status}->{dirty}) {
412     $tc->{in_sync}++;
413     next;
414     }
415    
416     # build map to actually transfer the data from source to target
417     $self->_buildMap();
418    
419    
420     #print Dumper($self->{node}); exit;
421    
422     #print "attempt", "\n";
423    
424     # additional (new) checks for feature "write-protection"
425     if ($self->{meta}->{target}->{storage}->{isWriteProtected}) {
426     $tc->{attempt_transfer}++;
427     print "\n" if $self->{verbose};
428     $logger->notice( __PACKAGE__ . "->syncNodes: Target is write-protected. Will not insert or modify node. " .
429     "(Ident: $self->{node}->{source}->{ident} " . "Dump:\n" . Dumper($self->{node}->{source}->{payload}) . ")" );
430     print "\n" if $self->{verbose};
431     $tc->{skip}++;
432     next;
433     }
434    
435     # transfer contents of map to target
436     if ($self->{node}->{status}->{new}) {
437     $tc->{attempt_new}++;
438     $self->_doTransferToTarget('insert');
439     # asymmetry: refetch node from target to re-calculate new ident and checksum (TODO: is IdentAuthority of relevance here?)
440 joko 1.5 #print Dumper($self->{node});
441 joko 1.1 $self->_statloadNode('target', $self->{node}->{target}->{ident}, 1);
442     $self->_readChecksum('target');
443    
444     } elsif ($self->{node}->{status}->{dirty}) {
445     $tc->{attempt_modify}++;
446     # asymmetry: get ident before updating (TODO: is IdentAuthority of relevance here?)
447     $self->{node}->{target}->{ident} = $self->{node}->{map}->{$self->{meta}->{target}->{IdentProvider}->{arg}};
448     $self->_doTransferToTarget('update');
449     $self->_readChecksum('target');
450     }
451    
452     if ($self->{node}->{status}->{ok}) {
453     $tc->{ok}++;
454     print "t" if $self->{verbose};
455     }
456    
457     if ($self->{node}->{status}->{error}) {
458     $tc->{error}++;
459     push( @{$tc->{error_per_row}}, $self->{node}->{status}->{error} );
460     print "e" if $self->{verbose};
461     }
462    
463     # change ident in source (take from target), if transfer was ok and target is an IdentAuthority
464     # this is (for now) called a "retransmit" indicated by a "r"-character when verbosing
465     if ($self->{node}->{status}->{ok} && $self->{meta}->{target}->{storage}->{isIdentAuthority}) {
466 joko 1.5 print "r" if $self->{verbose};
467 joko 1.1 #print Dumper($self->{meta});
468     #print Dumper($self->{node});
469     #exit;
470     $self->_doModifySource_IdentChecksum($self->{node}->{target}->{ident});
471     }
472    
473     print ":" if $self->{verbose};
474    
475     }
476    
477     print "\n" if $self->{verbose};
478    
479     # build user-message from some stats
480 joko 1.3 my $msg = "statistics: $tc";
481 joko 1.1
482     if ($tc->{error_per_row}) {
483     $msg .= "\n";
484 joko 1.2 $msg .= "errors from \"error_per_row\":" . "\n";
485 joko 1.1 $msg .= Dumper($tc->{error_per_row});
486     }
487    
488     # todo!!!
489     #sysevent( { usermsg => $msg, level => $level }, $taskEvent );
490     $logger->info( __PACKAGE__ . "->syncNodes: $msg" );
491    
492     return $tc;
493    
494     }
495    
496    
497     sub _dumpCompact {
498     my $self = shift;
499    
500     #my $vars = \@_;
501     my @data = ();
502    
503     my $count = 0;
504     foreach (@_) {
505     my $item = {};
506     foreach my $key (keys %$_) {
507     my $val = $_->{$key};
508 joko 1.5
509     #print Dumper($val);
510    
511 joko 1.1 if (ref $val eq 'Set::Object') {
512     #print "========================= SET", "\n";
513 joko 1.5 #print Dumper($val);
514 joko 1.1 #print Dumper($val->members());
515     #$val = $val->members();
516     #$vars->[$count]->{$key} = $val->members() if $val->can("members");
517     #$item->{$key} = $val->members() if $val->can("members");
518     $item->{$key} = $val->members();
519     #print Dumper($vars->[$count]->{$key});
520 joko 1.5
521 joko 1.1 } else {
522     $item->{$key} = $val;
523     }
524 joko 1.5
525 joko 1.1 }
526     push @data, $item;
527     $count++;
528     }
529    
530 joko 1.5 #print "Dump:", Dumper(@data), "\n";
531 joko 1.1
532     $Data::Dumper::Indent = 0;
533     my $result = Dumper(@data);
534     $Data::Dumper::Indent = 2;
535     return $result;
536 joko 1.5
537 joko 1.1 }
538    
539    
540     sub _calcChecksum {
541    
542     my $self = shift;
543     my $descent = shift;
544     my $specifier = shift;
545    
546     # calculate checksum for current object
547     my $ident = $self->{node}->{$descent}->{ident};
548    
549     # build dump of this node
550     my $payload = $self->{node}->{$descent}->{payload};
551     #my $dump = $ident . "\n" . $item->quickdump();
552     #my $dump = $ident . "\n" . Dumper($item);
553     my $dump = $ident . "\n" . $self->_dumpCompact($payload);
554    
555     # TODO: $logger->dump( ... );
556     #$logger->debug( __PACKAGE__ . ": " . $dump );
557     #$logger->dump( __PACKAGE__ . ": " . $dump );
558    
559     # calculate checksum from dump
560     # md5-based fingerprint, base64 encoded (from Digest::MD5)
561     #my $checksum_cur = md5_base64($objdump) . '==';
562     # 32-bit integer "hash" value (maybe faster?) (from DBI)
563     $self->{node}->{$descent}->{checksum} = DBI::hash($dump, 1);
564    
565     # signal good
566     return 1;
567    
568     }
569    
570    
571     sub _readChecksum {
572     my $self = shift;
573    
574     my $descent = shift;
575    
576     #print "getcheck:", "\n"; print Dumper($self->{node}->{$descent});
577    
578     if (!$self->{node}->{$descent}) {
579     # signal checksum bad
580     return;
581     }
582    
583     # get checksum for current entry
584     # TODO: don't have the checksum column/property hardcoded as "cs" here, make this configurable somehow
585    
586     if ($self->{meta}->{$descent}->{storage}->{isChecksumAuthority}) {
587     #$self->{node}->{$descent}->{checksum} = $entry->{cs};
588     #$self->{node}->{$descent}->{checksum} = $self->_calcChecksum($descent); # $entry->{cs};
589     #print "descent: $descent", "\n";
590     $self->_calcChecksum($descent);
591     #print "checksum: ", $self->{node}->{$descent}->{checksum}, "\n";
592     } else {
593    
594     #$self->{node}->{$descent}->{checksum} = $entry->{cs};
595     $self->{node}->{$descent}->{checksum} = $self->{node}->{$descent}->{payload}->{cs};
596     }
597    
598     # signal checksum good
599     return 1;
600    
601     }
602    
603    
604     sub _buildMap {
605    
606     my $self = shift;
607    
608     # field-structure for building sql
609     # mapping of sql-fieldnames to object-attributes
610     $self->{node}->{map} = {};
611    
612     # manually set ...
613     # ... object-id
614     $self->{node}->{map}->{$self->{meta}->{target}->{IdentProvider}->{arg}} = $self->{node}->{source}->{ident};
615     # ... checksum
616     $self->{node}->{map}->{cs} = $self->{node}->{source}->{checksum};
617    
618     #print "sqlmap: ", Dumper($self->{node}->{map}), "\n";
619    
620     # for transferring flat structures via simple (1:1) mapping
621     # TODO: diff per property / property value
622    
623     if ($self->{args}->{mapping}) {
624     # apply mapping from $self->{args}->{mapping} to $self->{node}->{map}
625     #foreach my $key (@{$self->{meta}->{source}->{childnodes}}) {
626     my @childnodes = @{$self->{meta}->{source}->{childnodes}};
627     for (my $mapidx = 0; $mapidx <= $#childnodes; $mapidx++) {
628     #my $map_right = $self->{args}->{mapping}->{$key};
629    
630 joko 1.2 $self->{node}->{source}->{propcache} = {};
631     $self->{node}->{target}->{propcache} = {};
632    
633 joko 1.1 # get property name
634     $self->{node}->{source}->{propcache}->{property} = $self->{meta}->{source}->{childnodes}->[$mapidx];
635     $self->{node}->{target}->{propcache}->{property} = $self->{meta}->{target}->{childnodes}->[$mapidx];
636     #print "map: $map_right", "\n";
637    
638     # get property value
639     my $value;
640    
641     # detect for callback - old style - (maybe the better???)
642     if (ref($self->{node}->{target}->{map}) eq 'CODE') {
643     #$value = &$map_right($objClone);
644     } else {
645     # plain (scalar?) value
646     #$value = $objClone->{$map_right};
647     $self->{node}->{source}->{propcache}->{value} = $self->{node}->{source}->{payload}->{$self->{node}->{source}->{propcache}->{property}};
648     }
649     #$self->{node}->{map}->{$key} = $value;
650 joko 1.2
651     # detect expression
652     # for transferring deeply nested structures described by expressions
653     #print "val: $self->{node}->{source}->{propcache}->{value}", "\n";
654     if ($self->{node}->{source}->{propcache}->{property} =~ s/^expr://) {
655    
656     # create an anonymous sub to act as callback target dispatcher
657     my $cb_dispatcher = sub {
658     #print "=============== CALLBACK DISPATCHER", "\n";
659     #print "ident: ", $self->{node}->{source}->{ident}, "\n";
660     #return $self->{node}->{source}->{ident};
661    
662     };
663    
664    
665     #print Dumper($self->{node});
666    
667     # build callback map for helper function
668     #my $cbmap = { $self->{meta}->{source}->{IdentProvider}->{arg} => $cb_dispatcher };
669     my $cbmap = {};
670     my $value = refexpr2perlref($self->{node}->{source}->{payload}, $self->{node}->{source}->{propcache}->{property}, $cbmap);
671     $self->{node}->{source}->{propcache}->{value} = $value;
672     }
673 joko 1.1
674     # encode values dependent on type of underlying storage here - expand cases...
675     my $storage_type = $self->{meta}->{target}->{storage}->{locator}->{type};
676     if ($storage_type eq 'DBI') {
677     # ...for sql
678     $self->{node}->{source}->{propcache}->{value} = quotesql($self->{node}->{source}->{propcache}->{value});
679 joko 1.2 }
680     elsif ($storage_type eq 'Tangram') {
681     # iso? utf8 already possible?
682    
683 joko 1.1 } elsif ($storage_type eq 'LDAP') {
684     # TODO: encode utf8 here?
685     }
686    
687     # store value to transfer map
688     $self->{node}->{map}->{$self->{node}->{target}->{propcache}->{property}} = $self->{node}->{source}->{propcache}->{value};
689    
690     }
691     }
692    
693    
694     # TODO: $logger->dump( ... );
695     #$logger->debug( "sqlmap:" . "\n" . Dumper($self->{node}->{map}) );
696     #print "sqlmap: ", Dumper($self->{node}->{map}), "\n";
697     #print "entrystatus: ", Dumper($self->{node}), "\n";
698    
699     }
700    
701     sub _resolveNodeIdent {
702     my $self = shift;
703     my $descent = shift;
704    
705     #print Dumper($self->{node}->{$descent});
706    
707     # get to the payload
708     #my $item = $specifier->{item};
709     my $payload = $self->{node}->{$descent}->{payload};
710    
711     # resolve method to get to the id of the given item
712     # we use global metadata and the given descent for this task
713     #my $ident = $self->{$descent}->id($item);
714     #my $ident = $self->{meta}->{$descent}->{storage}->id($item);
715    
716     my $ident;
717     my $provider_method = $self->{meta}->{$descent}->{IdentProvider}->{method};
718     my $provider_arg = $self->{meta}->{$descent}->{IdentProvider}->{arg};
719    
720     # resolve to ident
721     if ($provider_method eq 'property') {
722     $ident = $payload->{$provider_arg};
723    
724     } elsif ($provider_method eq 'storage_method') {
725     #$ident = $self->{meta}->{$descent}->{storage}->id($item);
726     $ident = $self->{meta}->{$descent}->{storage}->$provider_arg($payload);
727     }
728    
729     $self->{node}->{$descent}->{ident} = $ident;
730    
731     return 1 if $ident;
732    
733     }
734    
735    
736     sub _modifyNode {
737     my $self = shift;
738     my $descent = shift;
739     my $action = shift;
740     my $map = shift;
741     my $crit = shift;
742    
743     # map for new style callbacks
744     my $map_callbacks = {};
745    
746     # checks go first!
747    
748     # TODO: this should be reviewed first - before extending ;-)
749     # TODO: this should be extended:
750     # count this cases inside the caller to this sub and provide a better overall message
751     # if this counts still zero in the end:
752     # "No nodes have been touched for modify: Do you have column-headers in your csv file?"
753     if (not defined $self->{node}) {
754     #$logger->critical( __PACKAGE__ . "->_modifyNode failed: \"$descent\" node is empty." );
755     #return;
756     }
757    
758     # transfer callback nodes from value map to callback map - handle them afterwards! - (new style callbacks)
759     if (my $callbacks = $self->{meta}->{$descent}->{Callback}) {
760     foreach my $callback (keys %{$callbacks->{write}}) {
761     $map_callbacks->{write}->{$callback} = $map->{$callback};
762     delete $map->{$callback};
763     }
764     }
765    
766 joko 1.5
767     #print Dumper($self->{meta});
768 joko 1.1
769     # DBI speaks SQL
770     if ($self->{meta}->{$descent}->{storage}->{locator}->{type} eq 'DBI') {
771    
772     #print Dumper($self->{node});
773     my $sql_main;
774     # translate map to sql
775     #print $action, "\n"; exit;
776     #print $self->{meta}->{$descent}->{node}, "\n"; exit;
777     #print "action:";
778     #print $action, "\n";
779     #$action = "anc";
780     #print "yai", "\n";
781     if (lc($action) eq 'insert') {
782     $sql_main = hash2Sql($self->{meta}->{$descent}->{node}, $map, 'SQL_INSERT');
783     } elsif (lc $action eq 'update') {
784     $crit ||= "$self->{meta}->{$descent}->{IdentProvider}->{arg}='$self->{node}->{$descent}->{ident}'";
785     $sql_main = hash2Sql($self->{meta}->{$descent}->{node}, $map, 'SQL_UPDATE', $crit);
786     }
787    
788     #print "sql: ", $sql_main, "\n";
789     #exit;
790    
791     # transfer data
792     my $sqlHandle = $self->{meta}->{$descent}->{storage}->sendCommand($sql_main);
793    
794     # handle errors
795     if ($sqlHandle->err) {
796     #if ($self->{args}->{debug}) { print "sql-error with statement: $sql_main", "\n"; }
797     $self->{node}->{status}->{error} = {
798     statement => $sql_main,
799     state => $sqlHandle->state,
800     err => $sqlHandle->err,
801     errstr => $sqlHandle->errstr,
802     };
803     } else {
804     $self->{node}->{status}->{ok} = 1;
805     }
806    
807     # Tangram does it the oo-way (naturally)
808     } elsif ($self->{meta}->{$descent}->{storage}->{locator}->{type} eq 'Tangram') {
809     my $sql_main;
810     my $object;
811    
812     # determine classname
813     my $classname = $self->{meta}->{$descent}->{node};
814    
815     # properties to exclude
816     my @exclude = @{$self->{meta}->{$descent}->{subnodes_exclude}};
817    
818    
819     if (my $identProvider = $self->{meta}->{$descent}->{IdentProvider}) {
820     push @exclude, $identProvider->{arg};
821     }
822    
823     # new feature:
824     # - check TypeProvider metadata property from other side
825     # - use argument (arg) inside as a classname for object creation on this side
826     #my $otherSide = $self->_otherSide($descent);
827     if (my $typeProvider = $self->{meta}->{$descent}->{TypeProvider}) {
828     #print Dumper($map);
829     $classname = $map->{$typeProvider->{arg}};
830     # remove nodes from map also (push nodes to "subnodes_exclude" list)
831     push @exclude, $typeProvider->{arg};
832     }
833    
834     # exclude banned properties (remove from map)
835     #map { delete $self->{node}->{map}->{$_} } @{$self->{args}->{exclude}};
836     map { delete $map->{$_} } @exclude;
837    
838     # list of properties
839     my @props = keys %{$map};
840    
841     # transfer data
842     if (lc $action eq 'insert') {
843    
844     # build array to initialize object
845     #my @initarray = ();
846     #map { push @initarray, $_, undef; } @props;
847    
848     # make the object persistent in four steps:
849     # - raw create (perl / class tangram scope)
850     # - engine insert (tangram scope) ... this establishes inheritance - don't try to fill in inherited properties before!
851     # - raw fill-in from hash (perl scope)
852     # - engine update (tangram scope) ... this updates all properties just filled in
853    
854     # create new object ...
855     #my $object = $classname->new( @initarray );
856     $object = $classname->new();
857    
858     # ... pass to orm ...
859     $self->{meta}->{$descent}->{storage}->insert($object);
860    
861     # ... and initialize with empty (undef'd) properties.
862     #print Dumper(@props);
863     map { $object->{$_} = undef; } @props;
864    
865     # mix in values ...
866     hash2object($object, $map);
867    
868     # ... and re-update@orm.
869 joko 1.5 #print Dumper($object);
870 joko 1.1 $self->{meta}->{$descent}->{storage}->update($object);
871    
872     # asymmetry: get ident after insert
873     # TODO:
874     # - just do this if it is an IdentAuthority
875     # - use IdentProvider metadata here
876 joko 1.5 #print Dumper($self->{meta}->{$descent});
877     my $oid = $self->{meta}->{$descent}->{storage}->id($object);
878     #print "oid: $oid", "\n";
879     $self->{node}->{$descent}->{ident} = $oid;
880 joko 1.1
881    
882     } elsif (lc $action eq 'update') {
883    
884     # get fresh object from orm first
885     $object = $self->{meta}->{$descent}->{storage}->load($self->{node}->{$descent}->{ident});
886    
887     #print Dumper($self->{node});
888    
889     # mix in values
890     #print Dumper($object);
891     hash2object($object, $map);
892     #print Dumper($object);
893     #exit;
894     $self->{meta}->{$descent}->{storage}->update($object);
895     }
896    
897     my $error = 0;
898    
899     # handle new style callbacks - this is a HACK - do this without an eval!
900     #print Dumper($map);
901     #print "cb: ", Dumper($self->{meta}->{$descent}->{Callback});
902     #print Dumper($map_callbacks);
903     foreach my $node (keys %{$map_callbacks->{write}}) {
904     #print Dumper($node);
905     my $perl_callback = $self->{meta}->{$descent}->{node} . '::' . $node . '_write';
906     my $evalstring = $perl_callback . '( { object => $object, value => $map_callbacks->{write}->{$node}, storage => $self->{meta}->{$descent}->{storage} } );';
907     #print $evalstring, "\n"; exit;
908     eval($evalstring);
909     if ($@) {
910     $error = 1;
911     print $@, "\n";
912     }
913    
914     #print "after eval", "\n";
915    
916     if (!$error) {
917     # re-update@orm
918     $self->{meta}->{$descent}->{storage}->update($object);
919     }
920     }
921    
922     # handle errors
923     if ($error) {
924     #print "error", "\n";
925     =pod
926     my $sqlHandle;
927     #if ($self->{args}->{debug}) { print "sql-error with statement: $sql_main", "\n"; }
928     $self->{node}->{status}->{error} = {
929     statement => $sql_main,
930     state => $sqlHandle->state,
931     err => $sqlHandle->err,
932     errstr => $sqlHandle->errstr,
933     };
934     =cut
935     # rollback....
936     #print "rollback", "\n";
937     $self->{meta}->{$descent}->{storage}->erase($object);
938     #print "after rollback", "\n";
939     } else {
940     $self->{node}->{status}->{ok} = 1;
941     }
942    
943    
944     }
945    
946     }
947    
948     # TODO:
949     # this should be split up into...
950     # - a "_statNode" (should just touch the node to check for existance)
951     # - a "_loadNode" (should load node completely)
952     # - maybe additionally a "loadNodeProperty" (may specify properties to load)
953     # - introduce $self->{nodecache} for this purpose
954     # TODO:
955     # should we:
956     # - not pass ident in here but resolve it via "$descent"?
957     # - refactor this and stuff it with additional debug/error message
958     # - this = the way the implicit load mechanism works
959     sub _statloadNode {
960    
961     my $self = shift;
962     my $descent = shift;
963     my $ident = shift;
964     my $force = shift;
965    
966     # fetch entry to retrieve checksum from
967     # was:
968     if (!$self->{node}->{$descent} || $force) {
969     # is:
970     #if (!$self->{node}->{$descent}->{item} || $force) {
971    
972     if (!$ident) {
973     #print "\n", "Attempt to fetch entry implicitely by ident failed: no ident given! This may result in an insert if no write-protection is in the way.", "\n";
974     return;
975     }
976 joko 1.5
977     # patch for DBD::CSV
978     if ($ident && $ident eq 'Null') {
979     return;
980     }
981 joko 1.1
982     my $result = $self->{meta}->{$descent}->{storage}->sendQuery({
983     node => $self->{meta}->{$descent}->{node},
984     subnodes => [qw( cs )],
985     criterias => [
986     { key => $self->{meta}->{$descent}->{IdentProvider}->{arg},
987     op => 'eq',
988     val => $ident },
989     ]
990     });
991    
992     my $entry = $result->getNextEntry();
993     my $status = $result->getStatus();
994    
995 joko 1.5 #print Dumper($status);
996    
997 joko 1.1 # TODO: enhance error handling (store inside tc)
998     #if (!$row) {
999     # print "\n", "row error", "\n";
1000     # next;
1001     #}
1002 joko 1.5
1003     # these checks run before actually loading payload- and meta-data to node-container
1004    
1005     # 1st level - hard error
1006     if ($status && $status->{err}) {
1007     $logger->debug( __PACKAGE__ . "->_statloadNode (ident=\"$ident\") failed - hard error (that's ok)" );
1008     return;
1009     }
1010    
1011     # 2nd level - logical (empty/notfound) error
1012     if (($status && $status->{empty}) || !$entry) {
1013     $logger->debug( __PACKAGE__ . "->_statloadNode (ident=\"$ident\") failed - logical error (that's ok)" );
1014     #print "no entry (logical)", "\n";
1015     return;
1016     }
1017    
1018     #print Dumper($entry);
1019    
1020 joko 1.1 # was:
1021     # $self->{node}->{$descent}->{ident} = $ident;
1022     # is:
1023 joko 1.5 # TODO: re-resolve ident from entry via metadata "IdentProvider" here - like elsewhere
1024 joko 1.1 $self->{node}->{$descent}->{ident} = $ident;
1025     $self->{node}->{$descent}->{payload} = $entry;
1026 joko 1.5
1027 joko 1.1 }
1028    
1029     return 1;
1030    
1031     }
1032    
1033     sub _doTransferToTarget {
1034     my $self = shift;
1035     my $action = shift;
1036     $self->_modifyNode('target', $action, $self->{node}->{map});
1037     }
1038    
1039     sub _doModifySource_IdentChecksum {
1040     my $self = shift;
1041     my $ident_new = shift;
1042     # this changes an old node to a new one including ident and checksum
1043     # TODO:
1044     # - eventually introduce an external resource to store this data to
1045     # - we won't have to "re"-modify the source node here
1046     my $map = {
1047     $self->{meta}->{source}->{IdentProvider}->{arg} => $ident_new,
1048     cs => $self->{node}->{target}->{checksum},
1049     };
1050 joko 1.5
1051     #print Dumper($map);
1052     #print Dumper($self->{node});
1053     #exit;
1054    
1055 joko 1.1 $self->_modifyNode('source', 'update', $map);
1056     }
1057    
1058    
1059     # this is a shortcut method
1060     # ... let's try to avoid _any_ redundant code in here (ok... - at the cost of method lookups...)
1061     sub _getNodeList {
1062     my $self = shift;
1063     my $descent = shift;
1064     my $filter = shift;
1065     return $self->{meta}->{$descent}->{storage}->getListFiltered($self->{meta}->{$descent}->{node}, $filter);
1066     }
1067    
1068    
1069     sub _prepareNode_MetaProperties {
1070     my $self = shift;
1071     my $descent = shift;
1072    
1073     $logger->info( __PACKAGE__ . "->_prepareNode_MetaProperties( descent $descent )" );
1074    
1075     # TODO: this should (better) be: "my $firstnode = $self->_getFirstNode($descent);"
1076     my $list = $self->_getNodeList($descent);
1077    
1078     # get first node
1079     my $firstnode = $list->[0];
1080    
1081     # check if node contains meta properties/nodes
1082     # TODO: "cs" is hardcoded here!
1083     my @required = ( $self->{meta}->{$descent}->{IdentProvider}->{arg}, 'cs' );
1084     my @found = keys %$firstnode;
1085     #my @diff = getDifference(\@found, \@required);
1086     my $diff = getDifference(\@required, \@found);
1087     #print Dumper(@found);
1088     #print Dumper(@required);
1089     #print Dumper(@diff);
1090     #if (!$#diff || $#diff == -1) {
1091     if (isEmpty($diff)) {
1092     $logger->warning( __PACKAGE__ . "->_prepareNode_MetaProperties: node is lacking meta properties - will try to alter..." );
1093     foreach (@required) {
1094     my $sql = "ALTER TABLE $self->{meta}->{$descent}->{node} ADD COLUMN $_";
1095     #print "sql: $sql", "\n";
1096     my $res = $self->{meta}->{$descent}->{storage}->sendCommand($sql);
1097     #print Dumper($res->getStatus());
1098     }
1099     }
1100    
1101     }
1102    
1103     sub _prepareNode_DummyIdent {
1104     my $self = shift;
1105     my $descent = shift;
1106    
1107     $logger->info( __PACKAGE__ . "->_prepareNode_DummyIdent( descent $descent )" );
1108    
1109     my $list = $self->_getNodeList($descent);
1110     #print Dumper($list);
1111     my $i = 0;
1112     my $ident_base = 5678983;
1113     my $ident_appendix = '0001';
1114     foreach my $node (@$list) {
1115     my $ident_dummy = $i + $ident_base;
1116     $ident_dummy .= $ident_appendix;
1117     my $map = {
1118     $self->{meta}->{$descent}->{IdentProvider}->{arg} => $ident_dummy,
1119     cs => undef,
1120     };
1121    
1122     # diff lists and ...
1123     my $diff = getDifference([keys %$node], [keys %$map]);
1124     next if $#{$diff} == -1;
1125    
1126     # ... build criteria including all columns
1127     my @crits;
1128     foreach my $property (@$diff) {
1129     next if !$property;
1130     my $value = $node->{$property};
1131     next if !$value;
1132     push @crits, "$property='" . quotesql($value) . "'";
1133     }
1134     my $crit = join ' AND ', @crits;
1135     print "p" if $self->{verbose};
1136 joko 1.5
1137     #print Dumper($map);
1138     #print Dumper($crit);
1139    
1140 joko 1.1 $self->_modifyNode($descent, 'update', $map, $crit);
1141     $i++;
1142     }
1143    
1144     print "\n" if $self->{verbose};
1145    
1146     if (!$i) {
1147     $logger->warning( __PACKAGE__ . "->_prepareNode_DummyIdent: no nodes touched" );
1148     }
1149    
1150     }
1151    
1152     # TODO: handle this in an abstract way (wipe out use of 'source' and/or 'target' inside core)
1153     sub _otherSide {
1154     my $self = shift;
1155 joko 1.2 my $descent = shift;
1156     return 'source' if $descent eq 'target';
1157     return 'target' if $descent eq 'source';
1158 joko 1.1 return '';
1159 joko 1.2 }
1160    
1161     sub _erase_all {
1162     my $self = shift;
1163     my $descent = shift;
1164     #my $node = shift;
1165     my $node = $self->{meta}->{$descent}->{node};
1166     $self->{meta}->{$descent}->{storage}->eraseAll($node);
1167 joko 1.1 }
1168    
1169    
1170     =pod
1171    
1172    
1173     =head1 DESCRIPTION
1174    
1175     Data::Transfer::Sync is a module providing a generic synchronization process
1176     across arbitrary/multiple storages based on a ident/checksum mechanism.
1177     It sits on top of Data::Storage.
1178    
1179    
1180     =head1 REQUIREMENTS
1181    
1182     For full functionality:
1183     Data::Storage
1184     Data::Transform
1185     Data::Compare
1186     ... and all their dependencies
1187    
1188    
1189     =head1 AUTHORS / COPYRIGHT
1190    
1191     The Data::Storage module is Copyright (c) 2002 Andreas Motl.
1192     All rights reserved.
1193    
1194     You may distribute it under the terms of either the GNU General Public
1195     License or the Artistic License, as specified in the Perl README file.
1196    
1197    
1198     =head1 SUPPORT / WARRANTY
1199    
1200     Data::Storage is free software. IT COMES WITHOUT WARRANTY OF ANY KIND.
1201    
1202    
1203    
1204     =head1 BUGS
1205    
1206     When in "import" mode for windows file - DBD::AutoCSV may hang.
1207     Hint: Maybe the source node contains an ident-, but no checksum-column?
1208    
1209    
1210     =head1 USER LEVEL ERRORS
1211    
1212     =head4 Mapping
1213    
1214     - - - - - - - - - - - - - - - - - - - - - - - - - -
1215     info: BizWorks::Process::Setup->syncResource( source_node Currency mode PULL erase 0 import 0 )critical: BizWorks::Process::Setup->startSync: Can't access mapping for node "Currency" - please check BizWorks::ResourceMapping.
1216     - - - - - - - - - - - - - - - - - - - - - - - - - -
1217     You have to create a sub for each node used in synchronization inside named Perl module. The name of this sub _must_ match
1218     the name of the node you want to sync. This sub holds mapping metadata to give the engine hints about how
1219     to access the otherwise generic nodes.
1220     - - - - - - - - - - - - - - - - - - - - - - - - - -
1221    
1222    
1223     =head4 DBD::AutoCSV's rulebase
1224    
1225     - - - - - - - - - - - - - - - - - - - - - - - - - -
1226     info: BizWorks::Process::Setup->syncResource( source_node Currency mode PULL erase 0 import 0 )
1227     info: Data::Transfer::Sync->syncNodes: source=L/Currency <- target=R/currencies.csv
1228    
1229     Execution ERROR: Error while scanning: Missing first row or scanrule not applied. at C:/home/amo/develop/netfrag.org/nfo/perl/libs/DBD/CSV.p
1230     m line 165, <GEN9> line 1.
1231     called from C:/home/amo/develop/netfrag.org/nfo/perl/libs/Data/Storage/Handler/DBI.pm at 123.
1232    
1233     DBI-Error: DBD::AutoCSV::st fetchrow_hashref failed: Attempt to fetch row from a Non-SELECT statement
1234     notice: Data::Transfer::Sync->syncNodes: No nodes to synchronize.
1235     - - - - - - - - - - - - - - - - - - - - - - - - - -
1236     DBD::AutoCSV contains a rulebase which is spooled down while attempting to guess the style of the csv file regarding
1237     parameters like newline (eol), column-seperation-character (sep_char), quoting character (quote_char).
1238     If this spool runs out of entries and no style could be resolved, DBD::CSV dies causing this "Execution ERROR" which
1239     results in a "DBI-Error" afterwards.
1240     - - - - - - - - - - - - - - - - - - - - - - - - - -
1241    
1242    
1243     =head4 Check structure of source node
1244    
1245     - - - - - - - - - - - - - - - - - - - - - - - - - -
1246     info: Data::Transfer::Sync->syncNodes: source=L/Currency <- target=R/currencies.csv
1247     critical: Data::Transfer::Sync->syncNodes: Can not synchronize: No ident found in source node, maybe try to "import" this node first.
1248     - - - - - - - - - - - - - - - - - - - - - - - - - -
1249     If lowlevel detection succeeds, but no other required informations are found, this message is issued.
1250     "Other informations" might be:
1251     - column-header-row completely missing
1252     - ident column is empty
1253     - - - - - - - - - - - - - - - - - - - - - - - - - -
1254    
1255    
1256     =head4 Modify structure of source node
1257    
1258     - - - - - - - - - - - - - - - - - - - - - - - - - -
1259     info: Data::Transfer::Sync->syncNodes: source=L/Currency <- target=R/currencies.csv
1260     info: Data::Transfer::Sync->_prepareNode_MetaProperties( descent source )
1261     warning: Data::Transfer::Sync->_prepareNode_MetaProperties: node is lacking meta properties - will try to alter...
1262     SQL ERROR: Command 'ALTER' not recognized or not supported!
1263    
1264     SQL ERROR: Command 'ALTER' not recognized or not supported!
1265     - - - - - - - - - - - - - - - - - - - - - - - - - -
1266     The Engine found a node which structure does not match the required. It tries to alter this automatically - only when doing "import" -
1267     but the DBD driver (in this case DBD::CSV) gets in the way croaking not to be able to do this.
1268     This could also appear if your database connection has insufficient rights to modify the database structure.
1269     DBD::CSV croaks because it doesn't implement the ALTER command, so please edit your columns manually.
1270     Hint: Add columns with the names of your "ident" and "checksum" property specifications.
1271     - - - - - - - - - - - - - - - - - - - - - - - - - -
1272    
1273    
1274     =head4 Load source node by ident
1275    
1276     - - - - - - - - - - - - - - - - - - - - - - - - - -
1277     info: Data::Transfer::Sync->_prepareNode_DummyIdent( descent source )
1278     pcritical: Data::Transfer::Sync->_modifyNode failed: "source" node is empty.
1279     - - - - - - - - - - - - - - - - - - - - - - - - - -
1280     The source node could not be loaded. Maybe the ident is missing. Please check manually.
1281     Hint: Like above, the ident and/or checksum columns may be missing....
1282     - - - - - - - - - - - - - - - - - - - - - - - - - -
1283    
1284    
1285     =head1 TODO
1286    
1287     - sub _resolveIdentProvider
1288     - wrap _doModifySource and _doTransferTarget around a core function which can change virtually any type of node
1289     - split this module up into Sync.pm, Sync/Core.pm, Sync/Compare.pm and Sync/Compare/Checksum.pm
1290     - introduce _compareNodes as a core method and wrap it around methods in Sync/Compare/Checksum.pm
1291     - introduce Sync/Compare/MyComparisonImplementation.pm
1292     - some generic deferring method - e.g. "$self->defer(action)" - to be able to accumulate a bunch of actions for later processing
1293     - this implies everything done is _really_ split up into generic actions - how else would we defer them???
1294     - example uses:
1295     - fetch whole checksum list from node
1296     - remember source ident retransmits
1297     - remember: this is convenient - and maybe / of course faster - but we'll loose "per-node-atomic" operations
1298     - feature: mechanism to implicit inject checksum property to nodes (alter table / modify schema)
1299     - expand statistics / keep track of:
1300     - touched/untouched nodes
1301     - full sync
1302     - just do a push and a pull for now but use stats for touched nodes in between to speed up things
1303     - introduce some new metadata flags for a synchronization partner which is (e.g.) of "source" or "target":
1304     - isNewNodePropagator
1305     - isWriteProtected
1306    
1307    
1308     =cut
1309    
1310     1;

MailToCvsAdmin">MailToCvsAdmin
ViewVC Help
Powered by ViewVC 1.1.26 RSS 2.0 feed