Raw content of Bio::EnsEMBL::Compara::DBSQL::NestedSetAdaptor
=head1 NAME
NestedSetAdaptor - DESCRIPTION of Object
=head1 SYNOPSIS
=head1 DESCRIPTION
=head1 CONTACT
Contact Jessica Severin on implemetation/design detail: jessica@ebi.ac.uk
Contact Ewan Birney on EnsEMBL in general: birney@sanger.ac.uk
=head1 APPENDIX
The rest of the documentation details each of the object methods. Internal methods are usually preceded with a _
=cut
package Bio::EnsEMBL::Compara::DBSQL::NestedSetAdaptor;
use strict;
use Bio::EnsEMBL::Utils::Exception qw(throw warning deprecate);
use Bio::EnsEMBL::Compara::NestedSet;
use Bio::EnsEMBL::DBSQL::BaseAdaptor;
our @ISA = qw(Bio::EnsEMBL::DBSQL::BaseAdaptor);
###########################
# FETCH methods
###########################
sub fetch_node_by_node_id {
my ($self, $node_id) = @_;
my $table= $self->tables->[0]->[1];
my $constraint = "WHERE $table.node_id = $node_id";
my ($node) = @{$self->_generic_fetch($constraint)};
return $node;
}
sub fetch_parent_for_node {
my ($self, $node) = @_;
unless($node->isa('Bio::EnsEMBL::Compara::NestedSet')) {
throw("set arg must be a [Bio::EnsEMBL::Compara::NestedSet] not a $node");
}
my $table= $self->tables->[0]->[1];
my $constraint = "WHERE $table.node_id = " . $node->_parent_id;
my ($parent) = @{$self->_generic_fetch($constraint)};
return $parent;
}
sub fetch_all_children_for_node {
my ($self, $node) = @_;
unless($node->isa('Bio::EnsEMBL::Compara::NestedSet')) {
throw("set arg must be a [Bio::EnsEMBL::Compara::NestedSet] not a $node");
}
my $constraint = "WHERE parent_id = " . $node->node_id;
my $kids = $self->_generic_fetch($constraint);
foreach my $child (@{$kids}) { $node->add_child($child); }
return $node;
}
sub fetch_all_leaves_indexed {
my ($self, $node) = @_;
unless($node->isa('Bio::EnsEMBL::Compara::NestedSet')) {
throw("set arg must be a [Bio::EnsEMBL::Compara::NestedSet] not a $node");
}
my $table= $self->tables->[0]->[1];
my $left_index = $node->left_index;
my $right_index = $node->right_index;
my $constraint = "WHERE ($table.right_index - $table.left_index) = 1 AND $table.left_index > $left_index AND $table.right_index < $right_index";
my @leaves = @{$self->_generic_fetch($constraint)};
return \@leaves;
}
sub fetch_subtree_under_node {
my $self = shift;
my $node = shift;
unless($node->isa('Bio::EnsEMBL::Compara::NestedSet')) {
throw("set arg must be a [Bio::EnsEMBL::Compara::NestedSet] not a $node");
}
unless ($node->left_index && $node->right_index) {
warning("fetch_tree_at_node_id subroutine assumes that left and right index has been built and store in the database.\n This does not seem to be the case for node_id=".$node->node_id.". Returning node.\n");
return $node;
}
my $table = $self->tables->[0]->[0];
my $alias = $self->tables->[0]->[1];
my $constraint = ", $table AS root_node WHERE $alias.left_index
BETWEEN root_node.left_index AND root_node.right_index
AND root_node.node_id=". $node->node_id;
my $all_nodes = $self->_generic_fetch($constraint);
push @{$all_nodes}, $node;
$self->_build_tree_from_nodes($all_nodes);
return $node;
}
sub fetch_tree_at_node_id {
my $self = shift;
my $node_id = shift;
my $node = $self->fetch_node_by_node_id($node_id);
unless ($node->left_index && $node->right_index) {
warning("fetch_tree_at_node_id subroutine assumes that left and right index has been built and store in the database.\n This does not seem to be the case for node_id=$node_id. Using fetch_node_by_node_id instead, and returning node.\n");
return $node;
}
my $table = $self->tables->[0]->[0];
my $alias = $self->tables->[0]->[1];
my $constraint = ", $table AS root_node WHERE $alias.left_index
BETWEEN root_node.left_index AND root_node.right_index
AND root_node.node_id=". $node_id;
my $all_nodes = $self->_generic_fetch($constraint);
my $root = $self->_build_tree_from_nodes($all_nodes);
return $root;
}
sub fetch_all_roots {
my $self = shift;
my $constraint = "WHERE root_id = 0";
return $self->_generic_fetch($constraint);
}
sub fetch_subroot_by_left_right_index {
my ($self,$node) = @_;
unless ($node->left_index && $node->right_index) {
warning("fetch_subroot_by_left_right_index subroutine assumes that left and right index has been built and store in the database.\n This does not seem to be the case.\n");
}
my $left_index = $node->left_index;
my $right_index = $node->right_index;
my $constraint = "WHERE parent_id = root_id";
$constraint .= " AND left_index<=$left_index";
$constraint .= " AND right_index>=$right_index";
return $self->_generic_fetch($constraint)->[0];
}
sub fetch_first_shared_ancestor_indexed {
my $self = shift;
my $node1 = shift;
my $node2 = shift;
my $left_node_id1 = $node1->left_index;
my $left_node_id2 = $node2->left_index;
my $right_node_id1 = $node1->right_index;
my $right_node_id2 = $node2->right_index;
my $min_left;
$min_left = $left_node_id1 if ($left_node_id1 < $left_node_id2);
$min_left = $left_node_id2 if ($left_node_id2 < $left_node_id1);
my $max_right;
$max_right = $right_node_id1 if ($right_node_id1 > $right_node_id2);
$max_right = $right_node_id2 if ($right_node_id2 > $right_node_id1);
my $constraint = "WHERE left_index < $min_left";
$constraint .= " AND right_index > $max_right";
$constraint .= " ORDER BY (right_index-left_index) LIMIT 1";
my $ancestor = $self->_generic_fetch($constraint)->[0];
return $ancestor;
}
=head2 fetch_root_by_node
Arg [1] : Bio::EnsEMBL::Compara::NestedSet $node
Example : $root = $nested_set_adaptor->fetch_root_by_node($node);
Description: Returns the root of the tree for this node
with links to all the intermediate nodes. Sister nodes
are not included in the result. Use fetch_node_by_node_id()
method to get the whole tree (loaded on demand)
Returntype : Bio::EnsEMBL::Compara::NestedSet
Exceptions : thrown if $node is not defined
Status : At-risk
Caller : $nested_set->root
=cut
sub fetch_root_by_node {
my ($self, $node) = @_;
unless(UNIVERSAL::isa($node, 'Bio::EnsEMBL::Compara::NestedSet')) {
throw("set arg must be a [Bio::EnsEMBL::Compara::NestedSet] not a $node");
}
my $alias = $self->tables->[0]->[1];
my $left_index = $node->left_index;
my $right_index = $node->right_index;
my $constraint = "WHERE $alias.left_index <= $left_index AND $alias.right_index >= $right_index";
my $nodes = $self->_generic_fetch($constraint);
my $root = $self->_build_tree_from_nodes($nodes);
return $root;
}
###########################
# STORE methods
###########################
sub update {
my ($self, $node) = @_;
unless(UNIVERSAL::isa($node, 'Bio::EnsEMBL::Compara::NestedSet')) {
throw("set arg must be a [Bio::EnsEMBL::Compara::NestedSet] not a $node");
}
my $parent_id = 0;
if($node->parent) {
$parent_id = $node->parent->node_id ;
}
my $root_id = $node->root->node_id;
my $table= $self->tables->[0]->[0];
my $sql = "UPDATE $table SET ".
"parent_id=$parent_id".
",root_id=$root_id".
",left_index=" . $node->left_index .
",right_index=" . $node->right_index .
",distance_to_parent=" . $node->distance_to_parent .
" WHERE $table.node_id=". $node->node_id;
$self->dbc->do($sql);
}
sub update_subtree {
my $self = shift;
my $node = shift;
$self->update($node);
foreach my $child (@{$node->children}) {
$self->update_subtree($child);
}
}
=head2 store
Arg [1] :
Example :
Description:
Returntype :
Exceptions :
Caller :
=cut
sub store {
my ($self, $node) = @_;
throw("must subclass and provide correct table names");
unless($node->isa('Bio::EnsEMBL::Compara::NestedSet')) {
throw("set arg must be a [Bio::EnsEMBL::Compara::NestedSet] not a $node");
}
my $sth = $self->prepare("INSERT INTO tree_node (parent_id, name) VALUES (?,?)");
if(defined($node->parent_node)) {
$sth->execute($node->parent_node->dbID, $node->name);
} else {
$sth->execute(0, $node->name);
}
$node->dbID( $sth->{'mysql_insertid'} );
$node->adaptor($self);
$sth->finish;
#
#now recursively do all the children
#
my $children = $node->children_nodes;
foreach my $child_node (@$children) {
$self->store($child_node);
}
return $node->dbID;
}
sub sync_tree_leftright_index {
my $self= shift;
my $tree_root = shift;
my $table = $self->tables->[0]->[0];
my $dc = $self->dbc->disconnect_when_inactive;
$self->dbc->disconnect_when_inactive(0);
$self->dbc->do("LOCK TABLES $table WRITE");
my $sql = "SELECT max(right_index) FROM $table;";
my $sth = $self->dbc->prepare($sql);
$sth->execute();
my ($max_counter) = $sth->fetchrow_array();
$sth->finish;
$tree_root->build_leftright_indexing($max_counter+1);
$sql = "UPDATE $table SET ".
"left_index=" . $tree_root->left_index .
",right_index=" . $tree_root->right_index .
" WHERE $table.node_id=". $tree_root->node_id;
$self->dbc->do($sql);
$self->dbc->do("UNLOCK TABLES");
$self->dbc->disconnect_when_inactive($dc);
return undef;
}
##################################
#
# Database related methods, sublcass overrides/inherits
#
##################################
sub tables {
my $self = shift;
throw("must subclass and provide correct table names");
}
sub columns {
my $self = shift;
throw("must subclass and provide correct column names");
}
sub left_join_clause {
return "";
}
sub default_where_clause {
my $self = shift;
return '';
}
sub final_clause {
my $self = shift;
$self->{'final_clause'} = shift if(@_);
return $self->{'final_clause'};
}
sub create_instance_from_rowhash {
my $self = shift;
my $rowhash = shift;
#my $node = $self->cache_fetch_by_id($rowhash->{'node_id'});
#return $node if($node);
my $node = new Bio::EnsEMBL::Compara::NestedSet;
$self->init_instance_from_rowhash($node, $rowhash);
#$self->cache_add_object($node);
return $node;
}
sub init_instance_from_rowhash {
my $self = shift;
my $node = shift;
my $rowhash = shift;
$node->adaptor($self);
$node->node_id ($rowhash->{'node_id'});
$node->_parent_id ($rowhash->{'parent_id'});
$node->_root_id ($rowhash->{'root_id'});
$node->left_index ($rowhash->{'left_index'});
$node->right_index ($rowhash->{'right_index'});
$node->distance_to_parent ($rowhash->{'distance_to_parent'});
return $node;
}
##################################
#
# INTERNAL METHODS
#
##################################
sub new {
my $class = shift;
my $self = $class->SUPER::new(@_);
$self->{'_node_cache'} = [];
return $self;
}
sub DESTROY {
my $self = shift;
$self->clear_cache;
$self->SUPER::DESTROY if $self->can("SUPER::DESTROY");
}
sub cache_fetch_by_id {
my $self = shift;
my $node_id = shift;
for(my $index=0; $index{'_node_cache'}}); $index++) {
my $node = $self->{'_node_cache'}->[$index];
if($node->node_id == $node_id) {
splice(@{$self->{'_node_cache'}}, $index, 1); #removes from list
unshift @{$self->{'_node_cache'}}, $node; #put at front of list
return $node;
}
}
return undef;
}
sub cache_add_object
{
my $self = shift;
my $node = shift;
unshift @{$self->{'_node_cache'}}, $node; #put at front of list
while(scalar(@{$self->{'_node_cache'}}) > 3000) {
my $old = pop @{$self->{'_node_cache'}};
#print("shrinking cache : "); $old->print_node;
}
return undef;
}
sub clear_cache {
my $self = shift;
$self->{'_node_cache'} = [];
return undef;
}
sub _build_tree_from_nodes {
my $self = shift;
my $node_list = shift;
#first hash all the nodes by id for fast access
my %node_hash;
foreach my $node (@{$node_list}) {
$node->no_autoload_children;
$node_hash{$node->node_id} = $node;
}
#next add children to their parents
my $root = undef;
foreach my $node (@{$node_list}) {
my $parent = $node_hash{$node->_parent_id};
if($parent) { $parent->add_child($node); }
else { $root = $node; }
}
return $root;
}
###################################
#
# _generic_fetch system
#
#####################################
=head2 _generic_fetch
Arg [1] : (optional) string $constraint
An SQL query constraint (i.e. part of the WHERE clause)
Arg [2] : (optional) string $logic_name
the logic_name of the analysis of the features to obtain
Example : $fts = $a->_generic_fetch('WHERE contig_id in (1234, 1235)', 'Swall');
Description: Performs a database fetch and returns feature objects in
contig coordinates.
Returntype : listref of Bio::EnsEMBL::SeqFeature in contig coordinates
Exceptions : none
Caller : BaseFeatureAdaptor, ProxyDnaAlignFeatureAdaptor::_generic_fetch
=cut
sub _generic_fetch {
my ($self, $constraint, $join, $final_clause) = @_;
my $sql = $self->_construct_sql_query($constraint, $join, $final_clause);
#print STDERR $sql,"\n";
my $node_list = [];
my $sth = $self->prepare($sql);
$sth->execute;
$node_list = $self->_objs_from_sth($sth);
$sth->finish;
return $node_list;
}
sub _construct_sql_query {
my ($self, $constraint, $join, $final_clause) = @_;
my @tables = @{$self->tables};
my $columns = join(', ', @{$self->columns()});
my $default_where = $self->default_where_clause;
if($default_where) {
if($constraint) {
$constraint .= " AND $default_where ";
} else {
$constraint = " WHERE $default_where ";
}
}
if ($join) {
foreach my $single_join (@{$join}) {
my ($tablename, $condition, $extracolumns) = @{$single_join};
if ($tablename && $condition) {
push @tables, $tablename;
if($constraint) {
$constraint .= " AND $condition";
} else {
$constraint = " WHERE $condition";
}
}
if ($extracolumns) {
$columns .= ", " . join(', ', @{$extracolumns});
}
}
}
#construct a nice table string like 'table1 t1, table2 t2'
my $tablenames = join(', ', map({ join(' ', @$_) } @tables));
my $sql = "SELECT $columns FROM $tablenames";
$sql .= " ". $self->left_join_clause;
$sql .= " $constraint" if($constraint);
#append additional clauses which may have been defined
if (!$final_clause) {
$final_clause = $self->final_clause;
}
$sql .= " $final_clause" if($final_clause);
return $sql;
}
1;