/[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.5 - (hide annotations)
Thu Dec 5 08:06:05 2002 UTC (21 years, 7 months ago) by joko
Branch: MAIN
Changes since 1.4: +63 -16 lines
+ bugfix with determining empty fields (Null) with DBD::CSV
+ debugging
+ updated comments

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

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