package DBImage; use strict; #-----------------------------------------------# # REQUIRED MODULES # #-----------------------------------------------# # for sub perfprofile my $curtime; my %navhash; my %navhashopt; # Optimize queries when loading dbimage in loaddata() below my %navhashdefr; # Holds deferred queries my %ColHash; # Holds schemae for each table my $querycount; my $dealrecordid; #===================================# # CLASS METHODS # #===================================# sub new { my $proto = shift; my $class = ref($proto) || $proto; ### Create basic object: my $self = {}; bless $self, $class; ### Build, if needed: return (@_ ? $self->build(@_) : $self); } #===================================# # INSTANCE METHODS # #===================================# sub build { my ($self,$dealid) = @_; $dealrecordid = $dealid; if (length($dealid) < 15) # if dealrefno passed find dealrecordid { my $query = qq/ select recordid from deal where dealrefno = '$dealid'/; my @results = &::db_retrieve("mr",$query); return $self if !@results; $dealrecordid = shift(@results); } if (!keys %navhash) { $self->LoadNavHash(); } if (!keys %navhashopt) { $self->LoadNavHashOpt(); } # reset navhashopt to not done foreach my $table (keys %navhashopt) { $navhashopt{$table}->{done} = 0; } %navhashdefr = (); #Holds deferred queries $querycount = 0; my %dataimage; #-------------------------------------------------------# #Get the one site for this deal, WinRef specific Code # # NOTE: starting point here could be generalized # #-------------------------------------------------------# my $colstr = join(',s.',@{ $navhash{site}->{columns} }); my $query = qq/ select s.$colstr from site s,deal d where s.recordid = d.siterecordid and d.recordid = '$dealrecordid'/; my @results = &::db_retrieve("mr",$query); my $row = shift(@results); # assumes one site row my ($siteid,$rest) = split($::col_delim,$row,2); $dataimage{children}->{site} = {}; my $dhptr = $dataimage{children}->{site}; my %hash; $dhptr->{$siteid} = \%hash; $hash{attributes} = $row; $hash{children} = {}; $navhashopt{site}->{done} = 1; #...cycle through navhashopt until all tables are done my $completeIfEmptyBranch = 0; # Covers case where data tree has empty # branch which means leaf table never completes for (my $done=0; !$done ; ) { $self->LoadData( 'site', $siteid, $navhash{site}->{children}, # {children} below site in %navhash $hash{children}); # {Children} below site in %dataimage $done = 1; $completeIfEmptyBranch ++; foreach my $table (keys %navhashopt) { $done *= $navhashopt{$table}->{done}; if (exists($navhashopt{$table}->{processing})) { $navhashopt{$table}->{done} = 1; delete ($navhashopt{$table}->{processing}); $completeIfEmptyBranch = 0; # found non empty data branch this time } } if ( $completeIfEmptyBranch > 3 ) # empty branch exists so exit { $done = 1; } my $x; } $self->{dataimage} = \%dataimage; &perfprofile("Loaded data $dealid"); print "Root hash loaded. $querycount queries performed\n"; return $self; } #======================================================# sub LoadNavHash { my $self = shift; &perfprofile("Initializing...(ignore CPU seconds)"); &perfprofile("Really starting now..."); $navhash{site}->{children} = {}; # get column names by reading schema from informix my @cols = &SchemaFor('site'); $navhash{site}->{columns} = \@cols; $ColHash{site} = \@cols; my $count = $self->LoadFromGlobalDT('site',$navhash{site}->{children}); &perfprofile("Retrieve Global DT data"); print "NavHash created. $count queries performed\n"; } #======================================================# sub LoadNavHashOpt { # Load hash with query optimizer data my $self = shift; &perfprofile("Starting loadNavHashOpt...(ignore CPU seconds)"); $navhashopt{site}->{parents} = {}; $navhashopt{site}->{done} = 0; $self->LoadOptFromNavHash('site',$navhash{site}->{children}); &perfprofile("Completed LoadNavHashOpt"); } #======================================================# sub LoadOptFromNavHash { my ($self,$parent,$pnavhash) = @_; # pnavhash is pointer to $navhash at children of $parent foreach my $child (keys %$pnavhash) { $navhashopt{$child}->{parents}->{$parent} = 'here'; $navhashopt{$child}->{done} = 0; $self->LoadOptFromNavHash($child,$pnavhash->{$child}->{children}); } } #======================================================# sub LoadData { # pass in name of parent table, parent id, and spots in # %navhash, %dataimage corresponding to parent # Repeatedly called from the base of the %navhash tree via loop on %navhashopt # as a way to defer the recordIDs for a query (in %navhashdefr ) so # that only one query is run for any table to optimize query speed my ($self,$parent,$parentid,$pnavhash,$pdataimage) = @_; #.... $pnavhash points to {children} in %navhash, initially below site #.... $pdataimage points to {children} in %dataimage, initially below site #.... Recursive call for each new $parentid in %dataimage # for each child table, load in children data to %dataimage who are joined to parent # when one query can be constructed to return all records for a given table # accumulate parentIDs into %navhashopt until all IDs are available foreach my $childtable (keys (%$pnavhash )) { # Defer query if parent not yet fully loaded and don't recurse down. my $parentsdone = 1; foreach my $optparent (keys %{ $navhashopt{$childtable}->{parents} } ) { $parentsdone *= $navhashopt{$optparent}->{done}; } if ( !$parentsdone ) { # store $parentid, $pdataimage to allow re-try of loadData from this point. $navhashdefr{$childtable}->{$parentid} = $pdataimage; next; } # parents are done. If I'm done, just recurse downward. # Otherwise, load my rows and stuff them into the appropriate # places in %dataimage; # Processing flag in %navhashopt stops repeated loading of a table's data # for those tables that have multiple parents but are ready to load # as the table will be hit multiple time each pass through %dataimage if (!$navhashopt{$childtable}->{done} && !exists($navhashopt{$childtable}->{processing})) { # get all parentids for query. Get from %navhashdefr # and from current $parentid and $pdataimage $navhashdefr{$childtable}->{$parentid} = $pdataimage; my $idlist = join("','",keys %{ $navhashdefr{$childtable} } ); my $navptr = $pnavhash->{$childtable}; my $colstr = join(',c.',@{ $navptr->{columns} }); my $childlink = $navptr->{childlink}; my $parentlink = $navptr->{parentlink}; my $query; my $winrefExceptionForDeal = ''; # force one deal, otherwise would get all # deals for a site since data model has deal as child of site # /mc may be duplicate code below attempting to do the same thing if (exists($navptr->{link})) # exception case where many to many table is removed in dataimage { my $jointable = $navptr->{link}->{jointable}; my $joinfield = $navptr->{link}->{joinfield}; $query = qq/ select j.$joinfield,c.$colstr from $childtable c, $jointable j where j.$joinfield in ('$idlist') and j.$parentlink = c.$childlink/; } # exception case where many to many table is not removed in dataimage elsif (exists($navptr->{uplink})) { my $jointable = $navptr->{uplink}->{jointable}; my $parentidfield = $navptr->{uplink}->{parentidfield}; $query = qq/ select j.$parentidfield,c.$colstr from $childtable c, $jointable j where j.$parentidfield in ('$idlist') and j.$parentlink = c.$childlink/; } else # normal process { # Next query this puts $childlink id first in results # NOTE: $childlink is returned twice in data if ($childtable eq 'deal') { $winrefExceptionForDeal = qq/ and c.recordid = '$dealrecordid'/; } $query = qq/ select c.$childlink,c.$colstr from $childtable c where c.$childlink in ('$idlist') $winrefExceptionForDeal/; } my @results = &::db_retrieve("mrnc",$query); $querycount++; #-----------------------------------# # bring in deal memos specially # #-----------------------------------# if ($childtable eq 'dealDONTDO') { my $row = shift(@results); foreach my $memcol (qw(businessdriver solutiondesc benefits whyhpwon comments mergememo geographicscope)) { $query = qq/ select c.$memcol from deal c where 1=1 $winrefExceptionForDeal/; $querycount++; my @results2 = &::db_retrieve("mrnc",$query); # push(@{ $navptr->{columns} },$memcol); $row .= $::col_delim . shift(@results2); } @results = ($row); } # park the rows returned for this table in the correct place in %dataimage foreach my $row (@results) { my ($parentrecid,$childid,$rest) = split($::col_delim,$row,3); my $pdataimage2 = $navhashdefr{$childtable}->{$parentrecid}; my %hash; $pdataimage2->{$childtable}->{$childid} = \%hash; $hash{attributes} = join($::col_delim,$childid,$rest); $hash{children} = {}; } $navhashopt{$childtable}->{processing} = 1; } # else #/mc & jh think this comes out { foreach my $childid (keys %{ $pdataimage->{$childtable} }) { my $navptr = $pnavhash->{$childtable}->{children}; $self->LoadData($childtable,$childid,$navptr, $pdataimage->{$childtable}->{$childid}->{children}); } } } } #======================================================# sub LoadFromGlobalDT { my ($self,$parent,$pnavhash) = @_; #pnavhash points to children of navhash #..return all children for a parent from globaldt.db my $query = qq/ select ChildTableName, ParentLinkField, ChildLinkFieldName, ChildTableType, ParentTableName from globaldt where loaddbimage = 'Y' and ParentTableName = '$parent'/; my @results = &::db_retrieve("mr",$query,'config'); my $count = 1; #..for each child of the parent foreach my $row (@results) { my ($childtable,$parentlink,$childlink,$childtype,$parenttable) = split($::col_delim,$row); #..Special cases, # 1. if childtype eq Link, get rid of link table and make direct one to many if ($childtype eq 'Link') { my $query = qq/ select ChildTableName, ParentLinkField, ChildLinkFieldName, ChildTableType from globaldt where loaddbimage = 'Y' and ParentTableName = '$childtable'/; my @results2 = &::db_retrieve("mr",$query,'config'); $count++; #!!!! FOR WINREF, WE KNOW WE'LL JUST GET BACK ONE ROW for a link table "child"!!! my $row2 = shift(@results2); my ($childtable2,$parentlink2,$childlink2,$childtype2) = split($::col_delim,$row2); #..save info on like table in case we need it later $pnavhash->{$childtable2}->{link}->{jointable} = $childtable; $pnavhash->{$childtable2}->{link}->{joinfield} = $childlink; $childtable = $childtable2; $parentlink = $parentlink2; $childlink = $childlink2; } # 2. if childtype eq UpLink, process the join information only. # Don't throw away any table. elsif ($childtype eq 'UpLink') { #..save info on like table in case we need it later $pnavhash->{$childtable}->{uplink}->{jointable} = $parenttable; $pnavhash->{$childtable}->{uplink}->{parentidfield} = 'recordid'; # JH: FIXME } $pnavhash->{$childtable}->{childlink} = $childtable eq 'flag' ? 'dealrecordid' : $childlink; # informix db uses old dealrecordID, #paradox tables has LinkRecordID. # DISCUSS WITH JEFF, put into globaldt.db??mc $pnavhash->{$childtable}->{parentlink} = $parentlink; $pnavhash->{$childtable}->{children} = {}; # get column names by reading schema from informix my @cols = &SchemaFor($childtable); # remove memo cols from deal to try to prevent Querier crash if ($childtable eq 'dealDONTDO') { @cols = grep { $_ !~ /businessdriver|solutiondesc|benefits|whyhpwon|comments|mergememo|geographicscope/} @cols; } $pnavhash->{$childtable}->{columns} = \@cols; # ASK Jeff $ColHash{$childtable} = \@cols; $count += $self->LoadFromGlobalDT($childtable,$pnavhash->{$childtable}->{children}); } return $count; } #======================================================# # Assumption: All data loaded to {dataimage} is to be # translated / transferred to {DTDdata}. Initially this # is to be the data for one site and one deal #======================================================# # Create session instance corresponding to the passed-in DTD. # Strategy: # Parse self, and refer to the DTD as necessary, not the # other way around. Reason is that self has the specific # data, whereas the DTD is comprehensive for all all deals, # and may contain structures for which we have no data. # sub DTDdataUsingDTD { my ($self,$rootEntity,$rootID) = @_; my %DTDdata; print STDERR "DBImage Loading $rootID...\n"; # find rootEntity in our dataimage. # FIXME FIXME really need recursive routine to find $rootEntity # in $self->{dataimage}(delay until we're trying to load different types # of data (other than winref deals)) my $dbihash = $self->{dataimage}; # special Winref Case: load from point of view of deal if ($rootEntity eq 'Deal') { my $sitehash = $dbihash->{children}->{site}; foreach my $siteid ( keys %$sitehash) { &LoadIntoSession( lc($rootEntity), # image instance database type $rootID, # image instance (deal id) $sitehash->{$siteid}, # image parent '', # dtd path \%DTDdata ); #---------------------------------------------------# # We need to load the site data as a child of deal # # even though 'tis other way 'round in dbimage. # # To accomplish this, load from site after the above# # is done. It will load the site data, but fail to # # recurse because there's no DTD path that continues# # under /Deal/Site... That failure happens to be # # what we want, since the data is already loaded. # #---------------------------------------------------# &LoadIntoSession( 'site', # dbimage instance database type $siteid, # dbimage instance id $dbihash, # dbimage parent '/Deal', # dtd path $DTDdata{children}->{Deal}->{$rootID} # pointer to dest. storage ); return \%DTDdata; # this is haque to return just first site # good assumption for the winref deal } } return {}; } #===========================================================# # load the given instance, and all that instance's children # #===========================================================# sub LoadIntoSession { my ($dbiTable, # = database table name eg. deal, $dbiId, # = RecordID being processed eg '197122012414894TRTR', $dbiParentHash, # = (source) pointer in dbimage at the parent to current table $dtdPath, # = DTD index key, eg. /Deal/ModelGroup/Model $dtddataParentHash # = (dest) pointer to current loc. in dtdData (of WinRef deal) ) = @_; my @dbmapIndexKeys = ucfirst($dbiTable); my $dbmapIndexKey; my %attr; my $dbobj = $dbiParentHash->{children}->{$dbiTable}->{$dbiId}; my @vals = split($::col_delim,$dbobj->{attributes}); my $cols = $ColHash{$dbiTable}; #$ColHash contains array of column names of table foreach my $col (@$cols) #for each column name in table { $attr{$col} = shift(@vals); #$attr is hash keyed by table column name, value = field value } # if database type is 'contlink' or 'prodline', get business key # (dbmapIndexKey) from linktable if ($dbiTable =~ /^(contlink|prodline)$/) { @dbmapIndexKeys = (ucfirst($dbiTable),$attr{linktable}); } # if database type is 'flag', get business key (dbmapIndexKey) from # flagtype/datatype/flagpurpose # also handle special cases where flagvalue2, 3 are # part of the business key elsif ($dbiTable eq 'flag') { @dbmapIndexKeys = ('Flag',$attr{flagtype}); # add datatype if it exists push(@dbmapIndexKeys,$attr{datatype}) if length($attr{datatype}); # add flagpurpose if it exists push(@dbmapIndexKeys,$attr{flagpurpose}) if length($attr{flagpurpose}); # ReferenceRating and MktgRefRating use flagvalue2 if ($attr{flagtype} =~ /ReferenceRating|MktgRefRating/) { push(@dbmapIndexKeys,$attr{flagvalue2}); } # MktProg uses flagvalue3 if ($attr{flagtype} eq "MktProg") { push(@dbmapIndexKeys,$attr{flagvalue3}); } # Oracle uses flagvalue3 if flagpurpose is "Software" if ($attr{companyname} eq "Oracle Corp" && $attr{flagpurpose} eq "Software") { push(@dbmapIndexKeys,$attr{flagvalue3}) if length($attr{flagvalue3}); } # HPO uses flagvalue3 and sometimes flagvalue2 if ($attr{flagtype} =~ /^HPO/) { push(@dbmapIndexKeys,$attr{flagvalue3}) if length($attr{flagvalue3}); push(@dbmapIndexKeys,$attr{flagvalue2}) if length($attr{flagvalue2}); } } # if database type is 'extflag', get business type from # our parent's flagtype, datatype and flagpurpose # (+flagvalue3 if companyname is Oracle Corp) elsif ($dbiTable eq 'extflag') { my @pvals = split($::col_delim,$dbiParentHash->{attributes}); my $pcols = $ColHash{flag}; my %pattr; foreach my $col (@$pcols) { $pattr{$col} = shift(@pvals); } @dbmapIndexKeys = ('Flag',$pattr{flagtype}); # add datatype if exists push(@dbmapIndexKeys,$pattr{datatype}) if length($pattr{datatype}); # add flagpurpose if exists push(@dbmapIndexKeys,$pattr{flagpurpose}) if length($pattr{flagpurpose}); # Oracle uses flagvalue3 if flagpurpose is "Software" if ($pattr{companyname} eq "Oracle Corp" && $pattr{flagpurpose} eq "Software") { push(@dbmapIndexKeys,$pattr{flagvalue3}) if length($pattr{flagvalue3}); } # HPO uses flagvalue3 if ($pattr{flagtype} =~ /^HPO/) { push(@dbmapIndexKeys,$pattr{flagvalue3}) if length($pattr{flagvalue3}); } # add 'Extflag' push(@dbmapIndexKeys,'Extflag'); # HPO sometimes uses flagvalue2 if ($pattr{flagtype} =~ /^HPO/) { push(@dbmapIndexKeys,$pattr{flagvalue2}) if length($pattr{flagvalue2}); } } #-----------------------------------------------# # locate $dbmapIndexKey in DTD->{dbmapIndex} # #-----------------------------------------------# my $OriginaldbmapIndexKey = join('|',@dbmapIndexKeys); my $match = 0; while ((!$match) && @dbmapIndexKeys) { $dbmapIndexKey = join('|',@dbmapIndexKeys); $match = exists($::DTD->{dbmapIndex}->{$dbmapIndexKey}); if (!$match) { # try with trailing '|'. This is needed for cases e.g. where # some of the parents have a flagpurpose and others do not, # e.g. Role $dbmapIndexKey .= '|'; $match = exists($::DTD->{dbmapIndex}->{$dbmapIndexKey}); } if (!$match) { pop (@dbmapIndexKeys); } } if (!$match) { # if 'Extflag' wasn't part of key, add it. If it was, remove it # (Add at position 3 (replacing fv2) or at end) my @ary = split('\|',$OriginaldbmapIndexKey); if ($OriginaldbmapIndexKey =~ /Extflag/) { $OriginaldbmapIndexKey =~ s/\|Extflag//; @dbmapIndexKeys = split('\|',$OriginaldbmapIndexKey); } else { my $ix = @ary >= 3 ? 3 : @ary; splice(@ary,$ix,0,'Extflag'); @dbmapIndexKeys = @ary; } } while ((!$match) && @dbmapIndexKeys) { $dbmapIndexKey = join('|',@dbmapIndexKeys); $match = exists($::DTD->{dbmapIndex}->{$dbmapIndexKey}); if (!$match) { # try with trailing '|'. This is needed for cases e.g. where # some of the parents have a flagpurpose and others do not, # e.g. Role $dbmapIndexKey .= '|'; $match = exists($::DTD->{dbmapIndex}->{$dbmapIndexKey}); } if (!$match) { pop (@dbmapIndexKeys); } } if (!$match) { print STDERR qq/ Can't find ->$OriginaldbmapIndexKey<- in DTD dbmapIndex /; return; } #-----------------------------------------------# # find the startpath which starts with $dtdPath # #-----------------------------------------------# my @paths = grep /^$dtdPath/, keys %{ $::DTD->{dbmapIndex}->{$dbmapIndexKey}->{startpath} }; if (!@paths) { print STDERR qq/No $dtdPath as a startpath in dbmapIndex for $dbmapIndexKey (original: $OriginaldbmapIndexKey) / unless $dtdPath eq '/Deal/Site'; return; } if (@paths > 1) { my $pathstr = join("\n",@paths); print STDERR qq/Too many startpaths in $dtdPath dbmapIndex for $dbmapIndexKey (original: $OriginaldbmapIndexKey): $pathstr /; return; } my $newDtdPath = shift(@paths); #-------------------------------------------------------------------# # Because DTDData has actual recordids in its structure, whereas # # DTD has only path names, there is no way to navigate to a # # place in DTDData given only a path in DTD. Therefore, we # # need to get the recordids from DBImage as we parse it, # # and pass them on as needed. # #-------------------------------------------------------------------# #-------------------------------------------------------------------# # create an entry with our unique id # # and load the attribute data there # #-------------------------------------------------------------------# my $UCdbiTable = ucfirst($dbiTable); my @temp = split('/', $newDtdPath); my $element = pop @temp; my $aref = $::DTD->{$element}->{databasemap}->{$UCdbiTable}; foreach my $dbmap (@$aref) { my $elementIDName = $dbmap->{"[elementid]"}; my $elementID = $attr{$elementIDName}; if (!$elementID) { print STDERR " No [elementid] defined for $UCdbiTable in element $element\n"; return; } else { #-------------------------------------------------------------------# # compare old and new dtdPaths, and locate or create # # the difference, including intermediates e.g. # # /Deal <=> /Deal/ModelGroup/CompetitionOvercome # #-------------------------------------------------------------------# if ($newDtdPath !~ /^$dtdPath(.*)/) { print STDERR " New dtdPath does not contain the old: $newDtdPath !~ /^$dtdPath/"; return; } $dtdPath = $newDtdPath; # in case we have multiple tables at this level my $diff = $1; my @newelems = split("/",$diff); # first is blank, ignored below if (@newelems) # everything but the last element is an intermediate element # e.g. /deal/modelGroup/model # deal and model have RecordIDs, but modelGroup is # given a key of 'noDatabaseKey' { if (!exists($dtddataParentHash->{children})) { $dtddataParentHash->{children} = {}; } $dtddataParentHash = $dtddataParentHash->{children}; while (@newelems) { my $newelem = shift(@newelems); next if !length($newelem); # ignore blanks in @newelems, ie. first my $id = @newelems ? 'noDatabaseKey' : $elementID; if (!exists($dtddataParentHash->{$newelem}->{$id})) { $dtddataParentHash->{$newelem}->{$id}->{children} = {}; } $dtddataParentHash = @newelems ? $dtddataParentHash->{$newelem}->{$id}->{children} : $dtddataParentHash->{$newelem}->{$id}; } } foreach my $dbcol (keys %$dbmap) { next if $dbcol =~ /^\[|\'/; # Throw out [table] and literal values i.e. 'Industry' # we don't want these in %DTDImage my $dtdcol = $dbmap->{$dbcol}; next if $dtdcol =~ /'/; # eliminate literals (e.g. Model=>Model) next if ref($dtdcol) eq 'HASH'; # eliminate attributes # flagtype in dbmap must match dbmapIndexKey if we're dealing with a Flag record # (This fixes the HPOpenViewSection bug) # if ($dbmapIndexKey =~ /^Flag/) # { # my $pat = $dbmap->{flagtype}; # $pat =~ s/'//g; # last unless $dbmapIndexKey =~ /$pat/; # } #-----------------------------------------------------------------------# # store with regex characters backslashed. This prevents e.g. dollar # # values in memos ($200) from being interpreted as variables when # # the deal hash is brought back from the Cacher. # #-----------------------------------------------------------------------# $dtddataParentHash->{attributes}->{$dtdcol} = "eSpecial($attr{$dbcol}); } } } my $x = 1; # now recurse through all of our children foreach my $childtype (keys %{ $dbobj->{children} } ) { my $childhash = $dbobj->{children}->{$childtype}; my @ckeys = keys %$childhash; foreach my $childid (@ckeys) { LoadIntoSession( $childtype,$childid,$dbobj, $newDtdPath, $dtddataParentHash); my $y=1; } my $z=1; } my $w =1; } #===================================# # HELPER PROCEDURES # #===================================# sub quoteSpecial { my $val = shift; $val =~ s/([\@\$%])/\\$1/g; $val =~ s/&/&/g; $val =~ s/"/'/g; $val; } sub SchemaFor { my $table = shift; my @cols = (); my @results = &Querier::columnsFor($table); # rows are col,type,len,nulls foreach my $row (@results) { my ($col,$rest) = split($::col_delim,$row,2); push(@cols,$col); } @cols; } #======================================================# sub perfprofile { my $comment = shift; my $end = (POSIX::times())[0]; printf "%.2f CPU seconds %s\n", ($end - $curtime)/100, $comment if $curtime; $curtime = $end; } 1;