Raw content of BioMart::DatasetI # $Id: DatasetI.pm,v 1.30 2008/04/09 12:52:33 syed Exp $ # BioMart module for BioMart::DatasetI # # You may distribute this module under the same terms as perl itself # POD documentation - main docs before the code =head1 NAME BioMart::DatasetI =head1 SYNOPSIS BioMart::DatasetI objects provide a flexible framework to allow almost any data source to be made into a BioMart dataset. This is done by mapping the various attributes and filters into a ConfigurationTree object, and creating any exportables and importables that Link a given dataset to other datasets. =head1 DESCRIPTION The BioMart::DatasetI interface allows any data source with attributes and filters to be made into a BioMart dataset, regardless of its underlying access mode (RDBMS, file system, etc). Each implementation must provide access to a BioMart::Configuration::ConfigurationTree object describing its attributes and filters (see perlpod of BioMart::Configuration::ConfigurationTree object for more details). Implementations can also export attributes to other datasets to be used as filters in those systems, using the Links API. This provides a way of functionally linking two datasets together using a common name. Exporting datasets should link this name to a BioMart::Configuration::AttributeList object containing one or more BioMart::Configuration::Attribute objects representing the 'Exportable' for this dataset. The AttributeList is added to the query targeted for the exporting subsysytem by the QueryRunner to satisfy the Link requirements. Importing datasets should link this name to a BioMart::Configuration::FilterList object representing the 'Importable' for the importing dataset. The QueryRunner will set the FilterList Table to the ResultTable from the exporting dataset, and add it to the query targeted to the importing dataset. This allows two datasets to implicitly define ways to chain queries between them. =head1 AUTHOR - Arek Kasprzyk, Syed Haider, Richard Holland, Darin London, Damian Smedley =head1 CONTACT This module is part of the BioMart project http://www.biomart.org Questions can be posted to the mart-dev mailing list: mart-dev@ebi.ac.uk =head1 METHODS =cut package BioMart::DatasetI; use strict; use warnings; use Digest::MD5; use Data::Dumper; use XML::Simple qw(:strict); use Storable qw(store retrieve freeze nfreeze thaw); local $Storable::Deparse = 1; $Storable::forgive_me = 1; use Cwd; use DBI; use Log::Log4perl; my $logger=Log::Log4perl->get_logger(__PACKAGE__); use base qw(BioMart::Root); use BioMart::ResultTable; use constant NAME => "name"; use constant DISPLAYNAME => "display_name"; use constant CONFIGURATOR => "configurator"; use constant INIT_BATCHSIZE => "initial_batchsize"; use constant MAX_BATCHSIZE => "max_batchsize"; use constant VISIBLE => "visible"; use constant VERSION => "version"; use constant LOCATIONDISPLAY => "locationDisplayName"; use constant LOCATIONNAME => "locationName"; use constant VIRTSCHEMA => "virtualSchema"; use constant SERVERTYPE => "serverType"; use constant INTERFACES => "interfaces"; use constant MODIFIED => "modified"; use constant TITLES => [ NAME, DISPLAYNAME, CONFIGURATOR, INIT_BATCHSIZE, MAX_BATCHSIZE, VISIBLE, VERSION, LOCATIONDISPLAY, LOCATIONNAME, VIRTSCHEMA, INTERFACES, MODIFIED, ]; =head2 _new Usage : my $dataset = BioMart::Dataset::ImpName->new( 'name' => $name, 'display_name' => $display_name, 'configurator' => $configurator, 'initial_batchsize' => 100, 'max_batchsize' => 100000, 'visible' => $visible_boolean, 'version' => $version, 'database' => $instanceName, 'schema' => $schema, 'virtualSchema' => $virtualSchema, 'serverType' => $serverType, 'interfaces' => $interfaces, 'modified' => $modified ); Description: Creates a new instance of a BioMart::Dataset implementation object named by ImpName. Requires a name used as the key to this dataset in the BioMart::Registry, and a diplay_name used in any User Interfaces. Also requires a reference to a BioMart::Configurator object. Initial_batchsize must be set to determine the number of rows to return in all initial batches unless explicitly overridden with a batch_size on getResultTable. Max_batchsize must be set to limit the number of rows returned in any batch of a batched query. This should be large enough so that the system can initiate a query with a small batch_size, allowing quick response, while ResultTable steps its subsequent request batch_size up after each call to get a batch. The visible flag instructs Graphical Interfaces to present (or not) the dataset as a choice to users. Non visible datasets are loaded in the background, transparent to the user. The version is used to present visible datasets to the user (not required for non-visible datasets). Dataset creation and management is done by the BioMart::Configurator object. Users should not need to create DatasetI implementing objects manually. Returntype : new BioMart::Dataset implementing object. Exceptions : Missing or invalid Required Parameters. Implementation specific Exceptions. Caller : BioMart::Configurator =cut sub _new { my ($self, @param) = @_; $self->SUPER::_new(@param); $self->addParams(TITLES, @param); $self->checkRequiredParams(TITLES); $self->_checkValidParams; $self->attr('supportedSchemas', undef); $self->attr('exportables', {}); #hash of linkName -> attributeList objects $self->attr('importables', {}); #hash of linkName -> filterList objects $self->attr('configurationTrees', {}); $self->attr('exhausted', undef); # Dataset determines when it is exhausted # explicit_batching set to true if first call to getResultTable # includes a batch_size $self->attr('explicit_batching', undef); $self->attr('links', []); $self->attr('cluster', undef); $self->attr('pathHash', {}); $self->attr('schema',undef); $self->attr('serverType', undef); $self->attr('attributeHash', {}); $self->attr('forceHash', undef); $self->attr('mode', 'MEMORY'); ## defaults to MEMORY, other option is LAZYLOAD, set by Registry::setMode $self->attr('dirPath', undef); ## absolute path to registry file directory, set by Registry::setDirPath $self->attr('LastDS', undef); ## set in QueryRunner when last DS is despatched for results ## used to track for last GS object to apply strtucture att merging on only last GS $self->attr('GenomicMAlignHack', undef); ## just for Compara Mart, follows the old hashing/merging } sub _checkValidParams { my $self = shift; my $conf = $self->getParam(CONFIGURATOR); unless ($conf->isa("BioMart::Configurator")) { BioMart::Exception::Configuration->throw("Dataset objects require a BioMart::Configurator object parameter\nReceived object ".$conf."\n"); } } sub getConfigurator { my $self = shift; return $self->getParam(CONFIGURATOR); } sub _getDBH { my $self = shift; my $location=$self->getParam('configurator')->get('location'); $location->openConnection(); return $location->dbh() || BioMart::Exception::Database->throw("no dbh database handle available from mart location $location"); } sub _setExhausted { my ($self, $exhausted) = @_; $self->set('exhausted', $exhausted); } =head2 _processNewQuery Description : Private interface method to reset any inter-batch state variables to their initial settings. Also sets the exhausted flag to false. Called at the beginning of every new Query recieved (eg, when no BioMart::ResultTable object has been passed into the call to_getResultTable). This method is only required for those DatasetI implementations which maintain state over the extent of a batched query. Implementations needing to reset their state should implement a '__processNewQuery()' method. If it is not implemeted, it does not throw an unimplemented method exception. =cut sub _processNewQuery { my ($self, $query) = @_; $self->set('exhausted', undef); $self->set('explicit_batching', undef); if ($self->can('__processNewQuery')) { $self->__processNewQuery($query); } } =head2 serverType Usage : set the serverType: $ds->serverType($name); get the serverType: $ds->serverType; Description: sets or gets the serverType of the given dataset. Returntype : scalar $server_type Exceptions : none Caller : caller =cut sub serverType { my ($self, $newname) = @_; if ($newname) { $self->set(SERVERTYPE, $newname); } return $self->get(SERVERTYPE); } =head2 interfaces Usage : set the interfaces: $ds->interfaces($name); get the interfaces: $ds->interfaces; Description: sets or gets the interfaces of the given dataset. Returntype : scalar $interfaces Exceptions : none Caller : caller =cut sub interfaces { my ($self, $interfaces) = @_; if ($interfaces) { $self->set(INTERFACES, $interfaces); } return $self->getParam(INTERFACES); } =head2 schema Usage : set the database schema of a Dataset: $ds->schema($name); get the database schema of a Dataset: $ds->schema(); Description: sets or gets the database schema of the given dataset. Returntype : scalar $schema Exceptions : none Caller : caller =cut sub schema { my ($self, $schema) = @_; if ($schema) { $self->set('schema', $schema); } return $self->get('schema'); } =head2 forceHash Usage : set the forceHash setting: $ds->forceHash($name); get the forceHash setting: $ds->forceHash; Description: sets or gets the forceHash of the given dataset. This is used when linking placeholder attributes from a first visible dataset into a second visible dataset. As a natural link does not exist the hashing and merging of attributes has to be forced Returntype : scalar $forceHash Exceptions : none Caller : caller =cut sub forceHash { my ($self, $forceHash) = @_; if ($forceHash) { $self->set('forceHash', $forceHash); } return $self->get('forceHash'); } =head2 name Usage : set the name: $subsys->name($name); get the name: $subsys->name; Description: sets or gets the name of the given dataset. Returntype : scalar $name Exceptions : none Caller : caller =cut sub name { my ($self, $newname) = @_; if ($newname) { $self->setParam(NAME, $newname); } return $self->getParam(NAME); } =head2 modified Usage : set the modified: $ds->modified($name); get the modified: $ds->modified; Description: sets or gets the modified date time of the given dataset. Returntype : scalar $modified TIME DATE STAMP Exceptions : none Caller : caller =cut sub modified { my ($self, $modified) = @_; if ($modified) { $self->setParam(MODIFIED, $modified); } return $self->getParam(MODIFIED); } =head2 displayName Usage : set the displayName: $ds->displayName($name); get the displayName: $ds->displayName; Description: sets or gets the displayName of the given dataset. Returntype : scalar $dispname Exceptions : none Caller : caller =cut sub displayName { my ($self, $newname) = @_; if ($newname) { $self->setParam(DISPLAYNAME, $newname); } return $self->getParam(DISPLAYNAME); } =head2 virtualSchema Usage : set the virtualSchema: $ds->virtualSchema($name); get the virtualSchema: $ds->virtualSchema; Description: sets or gets the virtualSchema of the given dataset. Returntype : scalar $virtualSchema or undef Exceptions : none Caller : caller =cut sub virtualSchema { my ($self, $newname) = @_; if ($newname) { $self->setParam(VIRTSCHEMA, $newname); } return $self->getParam(VIRTSCHEMA); } =head2 visible Usage : if ($dset->visible) { .. } Description : Sets/gets visible for a dataset Returntype : boolean, true if visible, false otherwise Exceptions : na Caller : BioMart::QueryRunner =cut sub visible { my ($self, $visible) = @_; if ($visible) { $self->setParam(VISIBLE, $visible); } return $self->getParam(VISIBLE); } =head2 version Usage : if ($dset->version) { .. } Description : Sets/gets version for a dataset Returntype : version Exceptions : na Caller : BioMart::QueryRunner =cut sub version { my ($self, $version) = @_; if ($version) { $self->setParam(VERSION, $version); } return $self->getParam(VERSION); } =head2 initialBatchSize Usage : my $init_batchsize = $dset->initialBatchSize Description : Sets/gets the initialBatchSize on the Dataset Returntype : initialBatchSize or null Exceptions : na Caller : BioMart::QueryRunner =cut sub initialBatchSize { my ($self, $initialBatchSize) = @_; if ($initialBatchSize) { $self->setParam(INIT_BATCHSIZE, $initialBatchSize); } return $self->getParam(INIT_BATCHSIZE); } =head2 maxBatchSize Usage : my $max_batchsize = $dset->maxBatchSize Description : Sets/gets the maxBatchSize on the Dataset Returntype : maxBatchSize or null Exceptions : na Caller : BioMart::QueryRunner =cut sub maxBatchSize { my ($self, $maxBatchSize) = @_; if ($maxBatchSize) { $self->setParam(MAX_BATCHSIZE, $maxBatchSize); } return $self->getParam(MAX_BATCHSIZE); } =head2 locationDisplayName Usage : if ($dset->locationDisplayName) { .. } Description : Sets/gets BioMart::Location's display name for a dataset Returntype : scalar $locationDisplayName Exceptions : na Caller : BioMart::QueryRunner =cut sub locationDisplayName { my ($self, $database) = @_; if ($database) { $self->setParam(LOCATIONDISPLAY, $database); } return $self->getParam(LOCATIONDISPLAY); } =head2 locationName Usage : if ($dset->locationName) { .. } Description : Sets/gets BioMart::Location's name for a dataset Returntype : scalar $locationName Exceptions : na Caller : BioMart::QueryRunner =cut sub locationName { my ($self, $schema) = @_; if ($schema) { $self->setParam(LOCATIONNAME, $schema); } return $self->getParam(LOCATIONNAME); } #------------------------------------------------------------------------ =head2 getMode Usage : $DS->getMode() $DS->setMode('LAZYLOAD'); To set Description: get the mode, default to MEMORY Return type: A string Exceptions : none Caller : caller =cut sub getMode { my ($self) = @_; return $self->get('mode'); } #------------------------------------------------------------------------ =head2 setMode Usage : $DS->getMode() $DS->setMode('LAZYLOAD'); To set Description: get the mode, default to MEMORY Return type: none Exceptions : none Caller : caller =cut sub setMode { my ($self, $val) = @_; if ($val) { $self->set('mode', $val); } } #------------------------------------------------------------------------ =head2 getDirPath Usage : $self->getDirPath() $self->setDirPath('/abc/def/'); To set Description: get the path to the folder taht contains registry file, where confTrees, _portables, XML directories live Return type: A string Exceptions : none Caller : caller =cut sub getDirPath { my ($self) = @_; return $self->get('dirPath'); } #------------------------------------------------------------------------- =head2 setDirPath Usage : $self->getDirPath() $self->setDirPath('/abc/def/'); To set Description: get the path to the folder taht contains registry file, where confTrees, _portables, XML directories live Return type: none Exceptions : none Caller : caller =cut sub setDirPath { my ($self, $val) = @_; if ($val) { $self->set('dirPath', $val); } } #------------------------------------------------------------------------ =head2 getConfigurationTree Usage : $confTree = $subsys->getConfigurationTree($interface, $dsCounter); Description: Returns a BioMart::Configuration::ConfigurationTree object with all attributes and filters that are supported by the given dataset for the given interface type. Returntype : BioMart::Configuration::ConfigurationTree Exceptions : none Caller : caller =cut sub getConfigurationTree { my ($self,$interface,$dsCounter)=@_; my %configurationTrees = %{$self->get('configurationTrees')}; my $cacheFile = $self->getDirPath(); $cacheFile .= $self->virtualSchema()."/"; $cacheFile .= "confTrees/"; my $createLinks = undef; if(($dsCounter) && ($dsCounter eq 'CREATE_ALL_LINKS')) { $dsCounter = undef; # so its not misused by _getConfiguration Tree $createLinks = 1; } if ($configurationTrees{$interface}) { if ($configurationTrees{$interface} eq 'LAZYLOAD') # means exists but on the disk { $cacheFile .= $self->locationName().".".$self->name().".".$interface; #$cacheFile .= $self->getParam('configurator')->get('location')->database().".".$self->name().".".$interface; if(-e $cacheFile) # if file exists { my $configurationTreeFromFile; eval{$configurationTreeFromFile = retrieve($cacheFile)}; if ($createLinks) # means the call is from Registry::createAllLinks, so need to keep tree in memory. { $configurationTrees{$interface} = $configurationTreeFromFile; $self->set('configurationTrees', \%configurationTrees); } return $configurationTreeFromFile; } } return $configurationTrees{$interface}; } if ($self->can('_getConfigurationTree')) { my $configurationTree = $self->_getConfigurationTree($interface, $dsCounter); $configurationTrees{$interface} = $configurationTree; $self->set('configurationTrees', \%configurationTrees); ## Store the configurationTree to DISK if LAZYLOAD is set if($self->getMode() eq 'LAZYLOAD') { $self->setConfigurationTree($interface, 'LAZYLOAD'); } return $configurationTree; } $self->unimplemented_method(); } #------------------------------------------------------------------------ =head2 getAllConfigurationTrees Usage : $confTree = $subsys->getAllConfigurationTrees($interface OPTIONAL) Description: Returns a BioMart::Configuration::ConfigurationTree object with all attributes and filters that are supported by the given dataset for the given interface type. Returntype : BioMart::Configuration::ConfigurationTree Exceptions : none Caller : caller =cut sub getAllConfigurationTrees { my ($self, @params) = @_; my $allConfigTrees; my (%params) = @params; my $martUser = $params{'martUser'} || 'default'; my $required_interface = $params{'interface'} || 'default'; my $interfacesList = $self->interfaces(); # should return a comma separated list of interfaces my @interfacesArray = split /,/,$interfacesList; foreach my $interface(@interfacesArray) { if ($required_interface eq $interface) ## interfaces checking done { my $confTree = $self->getConfigurationTree($interface); my $martUsersList = $confTree->mart_Users(); my @allusers = split /,/,$martUsersList; foreach my $user (@allusers) { if ($user eq $martUser) { push @{$allConfigTrees}, $confTree; } } } } return $allConfigTrees; } #------------------------------------------------------------------------ =head2 setConfigurationTree Usage : $confTree = $subsys->setConfigurationTree($interface, $dsCounter); Description: Stores to Disk BioMart::Configuration::ConfigurationTree object with all attributes and filters that are supported by the given dataset for the given interface type. Returntype : none Exceptions : none Caller : caller =cut sub setConfigurationTree { my ($self, $interface, $flag) = @_; my %configurationTrees = %{$self->get('configurationTrees')}; my $cacheFile = $self->getDirPath(); $cacheFile .= $self->virtualSchema()."/"; $cacheFile .= "confTrees/"; $cacheFile .= $self->locationName().".".$self->name().".".$interface; #$cacheFile .= $self->getParam('configurator')->get('location')->database().".".$self->name().".".$interface; if(-e $cacheFile) # if file exists { unlink $cacheFile; } store($configurationTrees{$interface},$cacheFile); if ($flag eq 'LAZYLOAD') { $configurationTrees{$interface} = 'LAZYLOAD'; $self->set('configurationTrees', \%configurationTrees); } } =head2 setExportables Usage : $DS->setExportables($interface, 'LAZYLOAD') Description: Stores the exportables associated with datasets to disk, and sets its value to LAZYLOAD Returntype : none Exceptions : none Caller : caller =cut sub setExportables { my ($self, $interface ,$val) = @_; my $cacheFile = $self->getDirPath(); $cacheFile .= $self->virtualSchema()."/"; $cacheFile .= "_portables/"; $cacheFile .= $self->locationName().".".$self->name().".".$interface.".exportables"; #$cacheFile .= $self->getParam('configurator')->get('location')->database().".".$self->name().".".$interface.".exportables"; if(-e $cacheFile) # if file exists { unlink $cacheFile; } store($self->get('exportables'),$cacheFile); $self->set('exportables', $val); ### $val is 'LAZYLOAD' } =head2 setExportables Usage : $DS->setImportables($interface, 'LAZYLOAD') Description: Stores the importables associated with datasets to disk, and sets its value to LAZYLOAD Returntype : none Exceptions : none Caller : caller =cut sub setImportables { my ($self, $interface, $val) = @_; my $cacheFile = $self->getDirPath(); $cacheFile .= $self->virtualSchema()."/"; $cacheFile .= "_portables/"; $cacheFile .= $self->locationName().".".$self->name().".".$interface.".importables"; #$cacheFile .= $self->getParam('configurator')->get('location')->database().".".$self->name().".".$interface.".importables"; if(-e $cacheFile) # if file exists { unlink $cacheFile; } store($self->get('importables'),$cacheFile); $self->set('importables', $val); ### $val is 'LAZYLOAD' } =head2 getExportables Usage : my $exportables = $subsys->getExportables; my $exportables = $subsys->getExportables($linkName); Description: Returns an array_ref of BioMart::Configuration::AttributeList objects representing the exportables for the given dataset, either across all Links, or for a given $LinkName. Returntype : array_ref of BioMart::Configuration::AttributeList Exceptions : none Caller : caller =cut sub getExportables { my ($self, $linkName, $interface) = @_; my $exportables = $self->get('exportables'); # --------- if ($exportables eq 'LAZYLOAD') { $interface ||='default'; my $cacheFile = $self->getDirPath(); $cacheFile .= $self->virtualSchema()."/"; $cacheFile .= "_portables/"; $cacheFile .= $self->locationName().".".$self->name().".".$interface.".exportables"; #$cacheFile .= $self->getParam('configurator')->get('location')->database().".".$self->name().".".$interface.".exportables"; $exportables = retrieve($cacheFile); } # --------- if ($linkName && $interface) {# just used in QueryRunner return $exportables->{$linkName}->{$interface}; } my $ref = []; # need loop as now 2 levels deep foreach (values %{$exportables}){ push @{$ref}, values %{$_}; } return $ref; } =head2 getImportables Usage : my $importables = $subsys->getImportables; my $importables = $subsys->getImportables($linkName); Description: Returns an array_ref of BioMart::Configuration::FilterList objects representing the importables of the given dataset, either across all Links, or for a given $linkName. Returntype : array_ref of BioMart::Configuration::FilterList objects Exceptions : none Caller : caller =cut sub getImportables { my ($self, $linkName, $interface) = @_; my $importables = $self->get('importables'); # --------- if ($importables eq 'LAZYLOAD') { $interface ||='default'; my $cacheFile = $self->getDirPath(); $cacheFile .= $self->virtualSchema()."/"; $cacheFile .= "_portables/"; $cacheFile .= $self->locationName().".".$self->name().".".$interface.".importables"; #$cacheFile .= $self->getParam('configurator')->get('location')->database().".".$self->name().".".$interface.".importables"; $importables = retrieve($cacheFile); } # --------- if ($linkName && $interface) {# just used in QueryRunner return $importables->{$linkName}->{$interface}; } my $ref = []; foreach (values %{$importables}){ push @{$ref}, values %{$_}; } return $ref; } =head2 importableTo Usage : if ($dset->importableTo) { ... } Description : Determine if this Dataset can import from other Datasets Returntype : boolean Exception : na Caller : BioMart::QueryRunner =cut sub importableTo { my $self = shift; my $importableTo = ( @{$self->getImportables} > 0 ); return $importableTo; } =head2 exportableFrom Usage : if ($dset->exportableFrom) { ... } Description : Determine if this Dataset can export to other Datasets Returntype : boolean Exception : na Caller : BioMart::QueryRunner =cut sub exportableFrom { my $self = shift; my $exportableFrom = ( @{$self->getExportables} > 0 ); return $exportableFrom; } =head2 getResultTable Usage : fully specified for retrieving a batch of 1000 records, starting with record 100 (eg. 100 - 999) into an existing ResultTable: my $rTable = $subsys->getResultTable( 'query' => $query, 'batch_start' => 100, 'batch_size' => 1000, 'table' => $rtable ); minimal, returns all results for a given query as a BioMart::ResultTable object: my $rTable = $subsys->getResultTable( 'query' => $query ); get all rows, starting from record 150 as a BioMart::ResultTable: my $rTable = $subsys->getResultTable( 'query' => $query, 'batch_start' => 150 ); get only the first 1000 rows (same as batch_start = 0, batch_size = 1000) into an existing BioMart::AttributeTable: my $rTable = $subsys->getResultTable( 'query' => $query, 'batch_size' => 1000, 'table' => $atable ); Description: Executes a BioMart::Query and returns a BioMart::ResultTable object. If a reference to an existing BioMart::ResultTable (or BioMart::AttributeTable) is passed in the 'table' parameter, it is modified and returned, otherwise a new BioMart::ResultTable object is created and returned. Each Implementation must implement a _getResultTable method. It must take the same parameters as DatasetI->getResultTable itself, although it will always recieve a 'table' parameter, so it will never need to create a new ResultTable object. Returntype : BioMart::ResultTable, or undef if Dataset is exhausted for a batched query Exceptions : Missing or invalid Query object. Unsupported Attribute/Filter request, invalid Link requests, Implementation specific Exceptions. Caller : caller =cut sub getResultTable { my ($self, @param) = @_; local($^W) = 0; # prevent "odd number of elements" warning with -w. my(%param) = @param; my $query = $param{'query'}; unless ($query->isa("BioMart::Query")) { BioMart::Exception::Query->throw("getResultTable requires a valid BioMart::Query object\nRecieved object ".$query."\n"); } my $table = $param{'table'}; # print "<BR>_getResultTable ", $self->name(); my $firstbatch; unless ($table) { $firstbatch = 1; $self->_processNewQuery($query); # resets for all new queries if ($param{'batch_size'}) { $self->set('explicit_batching',1); } else { #set the batch_size to the initial_batchsize. Some queries not involving #importables, or not explicitly batched, will ignore this setting. $param{'batch_size'} = $self->getParam(INIT_BATCHSIZE); } $table = BioMart::ResultTable->new('query' => $query, 'target_dataset' => $self, 'max_batchsize' => $self->getParam(MAX_BATCHSIZE), 'initial_batchsize' => $param{'batch_size'}); if ($param{'web_origin'} && $param{'web_origin'} == 1){ $table->webOrigin(1); } $param{'table'} = $table; } return undef if ($self->get('exhausted')); # Call the DatasetI implementing object's getResultTable method if ($self->can('_getResultTable')) { my ($importable_size,$exportable_size,$linkName,$to_hash,$importable); # IDENTIFY IF HAVE AN IMPORTABLE FILTERLIST AND RECOVER PREVIOUS # DATASET'S HASHED RESULTS AND ADD EXTRA ATTS FOR MERGING IF REQUIRED my $filters = $query->getAllFilters; foreach my $filter (@$filters) { if ($filter->isa("BioMart::Configuration::FilterList") && $filter->batching) { $importable = $filter; $importable_size = @{$importable->getAllFilters}; $linkName = $importable->linkName; # set initial hash so below extra att handling happens even # before 1st batch my $attribute_table = $importable->getTable; my $attributeHash = $self->get('attributeHash'); $attributeHash->{$linkName} = $attribute_table->hashedResults; $self->set('attributeHash',$attributeHash); if ($self->get('attributeHash')->{$linkName} && !$query->getAttributeListByName($linkName)) { # add an attribute at the beginning of the result table for # each filter in the importable for attribute merging my $alist = BioMart::Configuration::AttributeList->new( 'name' => $linkName, 'dataSetName' => $self->name , 'interface' => $query->getInterfaceForDataset($self->name)); my $attribute_string = ''; my $comma = ''; my $list_filters = $importable->getAllFilters; foreach (@$list_filters){ $attribute_string = $attribute_string.$comma. $_->attribute->name; $comma = ','; $alist->addAttribute($_->attribute); } # add as an AttributeList so gets to start of SQL select $alist->attributeString($attribute_string); $query->addAttributeListFirst($alist); } last; } } # GET RESULTTABLE FROM DATASETI IMPLEMENTING OBJECT my $has_data = $self->_getResultTable(%param); #print ": YES DATA " if $has_data; $logger->debug("Got results") if $has_data; $logger->debug("Got no results") unless $has_data; # if ($self->isa("BioMart::Dataset::GenomicSequence")) # { # print "<BR>RS RETURNS: <BR>", Dumper($has_data->getRows) if ($has_data); # } # DO MERGING OF ATTRIBUTES IF REQUIRED if ($importable){ # these lines are necessary to repopulate attributeHash as the # getResultTable call may have resulted in a new batch of the # previous dataset and hence new hashed results to be merged my $attribute_table = $importable->getTable; my $attributeHash = $self->get('attributeHash'); $attributeHash->{$linkName} = $attribute_table->hashedResults; $self->set('attributeHash',$attributeHash); } # print "\n<BR>++++++++++ GOT RESULTS (Before Merging) +++++++++++++++ <BR>"; # if($table && $self->isa("BioMart::Dataset::GenomicSequence") ) { # print Dumper($table->getRows), if ($table->getRows); # } if ($has_data && $has_data > 0 && $linkName && $self->get('attributeHash')->{$linkName}){ $logger->debug("Attribute merge using linkName: $linkName"); $logger->debug("Before merge: ".scalar(@{$has_data->get('columns')})); $table = $self->_attributeMerge($table,$importable_size,$linkName, $query); # print "\n<BR>++++++++++ AFTER MERGING TABLE +++++++++++++++ <BR>", Dumper($table->getRows); $logger->debug("After merge: ".scalar(@{$has_data->get('columns')})); } # DO HASHING OF ATTRIBUTES IF REQUIRED if ($self->forceHash){ # Dataset is being used for placeholder attributes on the first # visible dataset of a two visible dataset query. Need to force # attribute hashing and merging $to_hash = 1; $exportable_size = $self->forceHash; } elsif ($query->getAllAttributeLists && $query->getAllAttributes){ # have user chosen atts and an exportable - going to want to do # attribute hashing and merging to perform a join foreach my $exportable(@{$query->getAllAttributeLists}){ if ($exportable->linkName){ $to_hash = 1; $exportable_size = @{$exportable->getAllAttributes}; last; } } } else { # if table is bigger than exportable_size after merging then should # be hashed - fixes problem of getting no seq, gene dataset merging # if no structure atts chosen for example foreach my $exportable(@{$query->getAllAttributeLists}){ if ($exportable->linkName){ $exportable_size = @{$exportable->getAllAttributes}; last; } } my $first_row = ${$table->getRows()}[0]; my $col_number = @{$first_row} if ($first_row); if ($col_number && $exportable_size && $col_number > $exportable_size){ $to_hash = 1; } # another special case, say Human (seq)/msd, and u donot choose human main table att(deselect: biotype) # and this will stop the hashing call, even for empty DS, which will break the intermediate logic # of prKeys being passed over from one DS to another one. causing complete chaos. # try removing this IF and request Sequence for gene: ENSG00000188170 with MSD as second DS. if (!$col_number && $exportable_size) { $to_hash = 1; } } if ($to_hash){ $logger->debug("Attribute hash"); $logger->debug("Before hash: ".scalar(@{$table->get('columns')})); # print "\n<BR>++++++++++ BEFORE HASHING +++++++++++++++ <BR>",Dumper($table->getRows()); $table = $self->_hashAttributes($table,$exportable_size); # print "\n<BR>++++++++++ AFTER HASHING TABLE - ROWS +++++++++++++++ <BR>",Dumper($table->getRows()); # print "\n<BR>++++++++++ AFTER HASHING TABLE - HASHEDRESULTS +++++++++++++++ <BR>",Dumper($table->hashedResults); $logger->debug("After hash: ".scalar(@{$table->get('columns')})); } # RETURN FULL TABLE, EMPTY TABLE (1st batch), UNDEF (last batch) $logger->debug("Returning defined has_data") if $has_data; return $has_data if ($has_data); #always return defined result $logger->debug("Returning table") if $firstbatch; return $table if ($firstbatch); #returns empty table for first call. #Next call will be exhausted $logger->debug("Returning undefined has_data") if $has_data; return $has_data; #subsequent batches must return undef } $self->unimplemented_method(); } sub _attributeMerge { my ($self,$rtable,$importable_size,$linkName, $query) = @_; $logger->debug("Importable size: $importable_size"); $logger->debug("Link name: $linkName"); my $sequenceType = 'none'; my %prev_dset_hash = %{$self->get('attributeHash')->{$linkName}}; my %this_dset_hash; my $rows = $rtable->getRows(); HASHROW:foreach my $row(@{$rows}){ my $key_string = ''; my $pKey = ''; for (my $i = 0; $i < $importable_size; $i++) { next if (!$$row[$i]); $logger->debug("Appending ".$$row[$i]); $key_string .= $$row[$i]; $pKey = $$row[$i] if (!$pKey); } $logger->debug("Final key string is: ".$key_string); next if ($key_string eq "" ); # store hash element; my $hashed_rows; $hashed_rows = $this_dset_hash{$pKey}{$key_string} if (!$self->GenomicMAlignHack()); $hashed_rows = $this_dset_hash{$key_string}{$key_string} if ($self->GenomicMAlignHack()); my $row_to_add = [@{$row}[$importable_size..@{$row}-1]]; push @$hashed_rows, $row_to_add; $this_dset_hash{$pKey}{$key_string} = $hashed_rows if (!$self->GenomicMAlignHack()); $this_dset_hash{$key_string}{$key_string} = $hashed_rows if ($self->GenomicMAlignHack()); } if ($self->isa("BioMart::Dataset::GenomicSequence") && ($self->lastDS() == 1 || $self->lastDS() == 2) && ($query->getAllAttributes($self->name)->[0]->name()) ) { $sequenceType = $query->getAllAttributes($self->name)->[0]->name(); $sequenceType = 'ok'; #if ($this_dset_hash{'52041'}) #{ # print "<BR>=================== PREV DS ================== <BR>", Dumper (\%prev_dset_hash); #} # The last set of rows, against a pkey in GS is ignored due to batching as # the remaining rows could possibly come in next batch. # so for merging them back again to corresponding structure Atts, we store # structure atts in GS and add them to the new %prev_dset_hash, so when the # sequences is returned in next batch, it available for merging. if ($self->get('rowsFromLastBatch')) { my $lastBatchRows = $self->get('rowsFromLastBatch'); my $doWriteBack =0; foreach my $firstkey( keys %$lastBatchRows) { if (exists $this_dset_hash{$firstkey}) ####### only bring into prev_hash if required and delete immediately { foreach my $secondkey (keys %{$lastBatchRows->{$firstkey}}) { #$prev_dset_hash{$firstkey}{$secondkey} = $lastBatchRows->{$firstkey}{$secondkey}; foreach my $rows (@{$lastBatchRows->{$firstkey}{$secondkey}}) { push @{$prev_dset_hash{$firstkey}{$secondkey}}, $rows; } } delete $lastBatchRows->{$firstkey}; $doWriteBack = 1; } } $self->set('rowsFromLastBatch', $lastBatchRows) if ($doWriteBack); # print "<BR>=================== THIS_DATASET ================ <BR>", Dumper(\%this_dset_hash); # print "<BR>=================== RECOVERED ================== <BR>", Dumper (\%prev_dset_hash); } my $saveForNextBatch = undef; foreach my $firstkey (keys %prev_dset_hash) { if (!exists $this_dset_hash{$firstkey}) { $saveForNextBatch ||= $self->get('rowsFromLastBatch'); foreach my $secondkey (keys %{$prev_dset_hash{$firstkey}}) { # print "<BR>$firstkey : $secondkey"; # print "<BR>Already EXISTS :", Dumper($saveForNextBatch) if(exists $saveForNextBatch->{$firstkey}{$secondkey}); #$saveForNextBatch->{$firstkey}{$secondkey} = $prev_dset_hash{$firstkey}{$secondkey}; foreach my $rows (@{$prev_dset_hash{$firstkey}{$secondkey}}) { push @{$saveForNextBatch->{$firstkey}{$secondkey}}, $rows; } } } } $self->set('rowsFromLastBatch', $saveForNextBatch); } my @new_rows; # loop over both hashes and produce new table # print "<BR>=================== PREV_DATASET ================ <BR>", Dumper(\%prev_dset_hash); # print "<BR>=================== THIS_DATASET ================ <BR>", Dumper(\%this_dset_hash); foreach my $prkey(keys %this_dset_hash) { foreach my $key(keys %{$this_dset_hash{$prkey}}) { my $this_dset_rows = $this_dset_hash{$prkey}{$key}; my $pKey = $prkey; $logger->warn("Processing key: ".$key); $logger->warn("This previous rows: ".scalar(@$this_dset_rows)); foreach my $this_dset_row(@$this_dset_rows) { #print "<BR> ++ THIS DS: [ $pKey ] <BR>", Dumper($this_dset_row) ; my $prev_dset_rows = $prev_dset_hash{$prkey}; #{$key}; ## this matching of both lower and upper case of keys if(!$prev_dset_rows) ## is introduced ever since ensembl 41 has made the { ## pdb for e.g in UPPER case and in MSD its in LOWER case $prev_dset_rows = $prev_dset_hash{lc($prkey)}; #{lc($key)}; ## so its safe to test both the scenarios } if(!$prev_dset_rows) ## is introduced ever since ensembl 41 has made the { ## pdb for e.g in UPPER case and in MSD its in UPPER case $prev_dset_rows = $prev_dset_hash{uc($prkey)}; #{uc($key)}; ## so its safe to test both the scenarios } if ($prev_dset_rows) { #if ($sequenceType eq 'ok') #{ # print "<BR> ++ THIS DS: [ $pKey ] <BR>", Dumper($this_dset_row) ; # print "<BR>=================== GS: PREV_ROWS ================ <BR>", Dumper(\%prev_dset_hash); #} my @allRows; if (defined $prev_dset_hash{$pKey}) { foreach my $key_string (keys %{$prev_dset_hash{$pKey}}) { push @allRows, $prev_dset_hash{$pKey}{$key_string} ; } } if (!@allRows && exists $prev_dset_hash{lc($pKey)}) { foreach my $key_string (keys %{$prev_dset_hash{lc($pKey)}}) { push @allRows, $prev_dset_hash{lc($pKey)}{$key_string} ; } } if (!@allRows && exists $prev_dset_hash{uc($pKey)}) { foreach my $key_string (keys %{$prev_dset_hash{uc($pKey)}}) { push @allRows, $prev_dset_hash{uc($pKey)}{$key_string} ; } } # print "<BR> READY FOR MERGING -1"; # $logger->debug("There were previous rows: ".scalar(@$prev_dset_rows)); ## GS semi colon separated list for structure attributes if ($sequenceType eq 'ok') { # print "<BR>, Merging Here"; #e.g pKey=267929 AND key=26792911522636522755-15 my ($finalRow, $avoidRepeats) = (); # print "<BR>=================== ALL_ROWS ================ <BR>", Dumper(\@allRows); foreach my $subrow (@allRows) { if($subrow) { foreach my $row (@$subrow) { for (my $i = 0; $i < scalar(@{$row}); $i++) { if ( $row->[$i] ) { if (!$finalRow->[$i]) { $finalRow->[$i] = $row->[$i]; } else { $finalRow->[$i] .= ';'.$row->[$i] if (!$avoidRepeats->{$i}->{$row->[$i]}); } $avoidRepeats->{$i}->{$row->[$i]} = 1; } } } } } push @new_rows, [@$this_dset_row,@$finalRow] if ($finalRow); push @new_rows, [@$this_dset_row,""] if (!$finalRow); } else { my %avoidDuplication = (); # print "<BR> ++ ALL ROWS: [ $pKey ] ", Dumper(\@allRows) ; # print "<BR> ++ THIS DS: [ $pKey ] ", Dumper($this_dset_row) ; foreach my $subrow (@allRows) { if($subrow) { NEXTROW: foreach my $row (@$subrow) { #print "<BR>HERE: SUB_ROW: ", Dumper($row); # avoid using "@A" eq "@B" type comparison, it floods error log my $rowAsString = $self->toString($row); next NEXTROW if (!$rowAsString || exists $avoidDuplication{$rowAsString} ) ; $avoidDuplication{$rowAsString} = ''; # print "<BR>going be merged with seq: ",Dumper($row); push @new_rows, [@$this_dset_row,@$row]; } } } } } else { $logger->debug("There were NO previous rows"); } } } } $logger->debug("Finished with rows: ".scalar(@new_rows)); $rtable->setRows(\@new_rows); return $rtable; } sub _hashAttributes { my ($self,$tempTable,$exportable_size) = @_; my %datasetAttributeHash; my @new_rows; my @order_of_rows; my %groupSameKeyRows; my $rows = $tempTable->getRows(); if ($self->forceHash){# keys atts are at the end HASHROW1:foreach my $row(@{$rows}){ my $new_row = [@{$row}[@{$row}-$exportable_size..@{$row}-1]]; #push @new_rows, $new_row;# do first so even empty rows get added so # batching behaviour not confused no warnings 'uninitialized'; if (! exists $groupSameKeyRows{$new_row->[0]}) { push @order_of_rows, $new_row->[0]; } push @{$groupSameKeyRows{$new_row->[0]}}, $new_row; my $key_string = ''; my $pKey = ''; for (my $i = @{$row} - $exportable_size; $i < @{$row}; $i++){ next if (!$$row[$i]); $key_string .= $$row[$i]; $pKey = $$row[$i] if (!$pKey); } next if ($key_string eq ""); # store hash element; my $hashed_rows; $hashed_rows = $datasetAttributeHash{$pKey}{$key_string} if (!$self->GenomicMAlignHack()); $hashed_rows = $datasetAttributeHash{$key_string}{$key_string} if ($self->GenomicMAlignHack()); my $row_to_add = [@{$row}[0..@{$row}-1-$exportable_size]]; #next HASHROW1 if ($self->rowExists($row_to_add, $hashed_rows)); if ($hashed_rows){ foreach my $prev_row (@{$hashed_rows}){ # avoid using "@A" eq "@B" type comparison, it floods error log next HASHROW1 if ( (($prev_row && $row_to_add) && ($self->toString($prev_row) eq $self->toString($row_to_add))) || (!$prev_row && !$row_to_add) ); } } push @$hashed_rows,$row_to_add; $datasetAttributeHash{$pKey}{$key_string} = $hashed_rows if (!$self->GenomicMAlignHack()); $datasetAttributeHash{$key_string}{$key_string} = $hashed_rows if ($self->GenomicMAlignHack()); } } else{ HASHROW:foreach my $row(@{$rows}){ my $new_row = [@{$row}[0..$exportable_size-1]]; #push @new_rows, $new_row;# do first so even empty rows get added so # batching behaviour not confused # if($new_row->[0]) # just to avoid warnings # { no warnings 'uninitialized'; if (! exists $groupSameKeyRows{$new_row->[0]}) { push @order_of_rows, $new_row->[0]; } push @{$groupSameKeyRows{$new_row->[0]}}, $new_row; # } my $key_string = ''; my $pKey = ''; for (my $i = 0; $i < $exportable_size; $i++){ next if (!$$row[$i]); $key_string .= $$row[$i]; $pKey = $$row[$i] if (!$pKey); } next if ($key_string eq ""); # store hash element; my $hashed_rows; $hashed_rows = $datasetAttributeHash{$pKey}{$key_string} if (!$self->GenomicMAlignHack()); $hashed_rows = $datasetAttributeHash{$key_string}{$key_string} if ($self->GenomicMAlignHack()); my $row_to_add = [@{$row}[$exportable_size..@{$row}-1]]; #next HASHROW if ($self->rowExists($row_to_add, $hashed_rows)); if ($hashed_rows){# make sure unique before add, error_log flooding foreach my $prev_row (@{$hashed_rows}){ # avoid using "@A" eq "@B" type comparison, it floods error log next HASHROW if ( (($prev_row && $row_to_add) && ($self->toString($prev_row) eq $self->toString($row_to_add))) || (!$prev_row && !$row_to_add) ); } } #warn($key_string." => ".join ",",@$row_to_add); push @$hashed_rows,$row_to_add; $datasetAttributeHash{$pKey}{$key_string} = $hashed_rows if (!$self->GenomicMAlignHack()); $datasetAttributeHash{$key_string}{$key_string} = $hashed_rows if ($self->GenomicMAlignHack()); } } foreach my $rowKey (@order_of_rows) { foreach my $row (@{$groupSameKeyRows{$rowKey}}) { push @new_rows, $row; } } $tempTable->setRows(\@new_rows); #if (!%datasetAttributeHash) #{ # print "<BR>BIG CLEVER FINDING :", Dumper(\%datasetAttributeHash), "<BR>and old ones" , Dumper($tempTable->hashedResults) ; #} $tempTable->hashedResults(\%datasetAttributeHash) if (%datasetAttributeHash); # this is a very clever line # if (!%datasetAttributeHash) = {}, while if there are previous results then they should be kept # however, if previous results are undef, then hashedResults should be assigne to {} instead of undef if (!%datasetAttributeHash && !$tempTable->hashedResults) { $tempTable->hashedResults(\%datasetAttributeHash); } return $tempTable; } =head2 getCount Usage : my $count = $subsys->getCount( 'query' => $query, ); Description: Executes a BioMart::Query and returns a count. Each Implementation must implement a _getCount method. It must take the same parameters as DatasetI->getCount itself. Currently, queries involving importables from other visible datasets which are batched are not countable. These will return -1 as the count. Returntype : String $count Exceptions : Missing or invalid Query object. Unsupported Attribute/Filter request, invalid Link requests, Implementation specific Exceptions. Caller : caller =cut sub getCount{ my ($self, @param) = @_; local($^W) = 0; # prevent "odd number of elements" warning with -w. my(%param) = @param; my $query = $param{'query'}; unless ($query->isa("BioMart::Query")) { BioMart::Exception::Query->throw("getResultTable requires a valid BioMart::Query object\nRecieved object ".$query."\n"); } if ($self->can('_getCount')) { return $self->_getCount(%param); } $self->unimplemented_method(); } =head2 toString Usage : Description: Helper rouinte for Array comparison by converting it to string first. Comparing arrays as "@A" eq "@B" brings flood of warning Returntype : String Exceptions : none Caller : caller =cut sub toString { my ($self, $curRow) = @_; my $string; foreach (@{$curRow}) { $string .= $_ if ($_); } return $string; } sub lastDS { my ($self, $val) = @_; if ($val) { $self->set('LastDS', $val); } return $self->get('LastDS'); } sub GenomicMAlignHack { my ($self, $val) = @_; if ($val) { $self->set('GenomicMAlignHack', $val); } return $self->get('GenomicMAlignHack'); } 1;