tests pass
[sgn.git] / lib / CXGN / Stock.pm
blob29bdee3a3e2aa029b47b8f56dc785828834b866e
1 =head1 NAME
3 CXGN::Stock - a second-level object for Stock
5 Version: 2.0
7 =head1 DESCRIPTION
9 This object was re-factored from CXGN::Chado::Stock and moosified.
11 Functions such as 'get_obsolete' , 'store' , and 'exists_in_database' are required , and do not use standard DBIC syntax.
13 =head1 AUTHOR
15 Naama Menda <nm249@cornell.edu>
16 Lukas Mueller <lam87@cornell.edu>
18 =cut
20 package CXGN::Stock ;
22 use Moose;
24 use Carp;
25 use Data::Dumper;
26 use Bio::Chado::Schema;
27 use CXGN::Metadata::Schema;
28 use SGN::Model::Cvterm;
29 use Bio::GeneticRelationships::Pedigree;
30 use Bio::GeneticRelationships::Individual;
31 use base qw / CXGN::DB::Object / ;
32 use CXGN::Stock::StockLookup;
33 use Try::Tiny;
35 has 'schema' => (
36 isa => 'Bio::Chado::Schema',
37 is => 'rw',
38 required => 1
41 has 'check_name_exists' => (
42 isa => 'Bool',
43 is => 'rw',
44 default => 1
47 has 'stock' => (
48 isa => 'Bio::Chado::Schema::Result::Stock::Stock',
49 is => 'rw',
52 has 'stock_id' => (
53 isa => 'Maybe[Int]',
54 is => 'rw',
57 has 'organism' => (
58 isa => 'Bio::Chado::Schema::Result::Organism::Organism',
59 is => 'rw',
62 has 'organism_id' => (
63 isa => 'Maybe[Int]',
64 is => 'rw',
67 has 'species' => (
68 isa => 'Maybe[Str]',
69 is => 'rw',
72 has 'genus' => (
73 isa => 'Maybe[Str]',
74 is => 'rw',
77 has 'organism_common_name' => (
78 isa => 'Maybe[Str]',
79 is => 'rw',
82 has 'organism_abbreviation' => (
83 isa => 'Maybe[Str]',
84 is => 'rw',
87 has 'organism_comment' => (
88 isa => 'Maybe[Str]',
89 is => 'rw',
92 has 'type' => (
93 isa => 'Str',
94 is => 'rw',
95 default => 'accession',
98 has 'type_id' => (
99 isa => 'Int',
100 is => 'rw',
103 has 'name' => (
104 isa => 'Str',
105 is => 'rw',
108 has 'uniquename' => (
109 isa => 'Str',
110 is => 'rw',
113 has 'description' => (
114 isa => 'Maybe[Str]',
115 is => 'rw',
116 default => '',
119 has 'is_obsolete' => (
120 isa => 'Bool',
121 is => 'rw',
122 default => 0,
125 has 'organization_name' => (
126 isa => 'Maybe[Str]',
127 is => 'rw',
130 has 'population_name' => (
131 isa => 'Maybe[Str]',
132 is => 'rw',
135 has 'populations' => (
136 isa => 'Maybe[ArrayRef[Str]]',
137 is => 'rw'
141 sub BUILD {
142 my $self = shift;
144 print STDERR "RUNNING BUILD FOR STOCK.PM...\n";
145 my $stock;
146 if ($self->stock_id){
147 $stock = $self->schema()->resultset("Stock::Stock")->find({ stock_id => $self->stock_id() });
149 if (defined $stock) {
150 $self->stock($stock);
151 $self->stock_id($stock->stock_id);
152 $self->name($stock->name);
153 $self->uniquename($stock->uniquename);
154 $self->description($stock->description() || '');
155 $self->type_id($stock->type_id);
156 $self->type($self->schema()->resultset("Cv::Cvterm")->find({ cvterm_id=>$self->type_id() })->name());
157 $self->is_obsolete($stock->is_obsolete);
158 $self->organization_name($self->_retrieve_stockprop('organization'));
159 $self->_retrieve_populations();
162 return $self;
167 =head2 store
169 Usage: $self->store
170 Desc: store a new stock or update an existing stock
171 Ret: a database id
172 Args: none
173 Side Effects: checks if the stock exists in the database (if a stock_id is provided), and if does, will attempt to update
174 Example:
176 =cut
178 sub store {
179 my $self = shift;
180 my %return;
182 my $stock = $self->stock;
183 my $schema = $self->schema();
185 #no stock id . Check first if the name exists in te database
186 my $exists;
187 if ($self->check_name_exists){
188 $exists= $self->exists_in_database();
191 if (!$self->type_id) {
192 my $type_id = SGN::Model::Cvterm->get_cvterm_row($self->schema(), $self->type(), 'stock_type')->cvterm_id();
193 $self->type_id($type_id);
196 if (!$self->organism_id){
197 if ($self->species){
199 my $organism_rs = $self->schema->resultset("Organism::Organism")->search({ species=>$self->species });
200 if ($organism_rs->count > 1){
201 return $return{error} = "More than one organism returned for species: ".$self->species;
203 if ($organism_rs->count == 0){
204 return $return{error} = "NO ORGANISM FOUND OF SPECIES: ".$self->species;
206 if ($organism_rs->count == 1){
207 my $organism = $organism_rs->first();
208 $self->organism($organism);
209 $self->organism_id($organism->organism_id);
210 $self->organism_abbreviation($organism->abbreviation);
211 $self->genus($organism->genus);
212 $self->species($organism->species);
213 $self->organism_common_name($organism->common_name);
214 $self->organism_comment($organism->comment);
219 if (!$stock) { #Trying to create a new stock
220 if (!$exists) {
222 my $new_row = $self->schema()->resultset("Stock::Stock")->create({
223 name => $self->name(),
224 uniquename => $self->uniquename(),
225 description => $self->description(),
226 type_id => $self->type_id(),
227 organism_id => $self->organism_id(),
228 is_obsolete => $self->is_obsolete(),
230 $new_row->insert();
232 my $id = $new_row->stock_id();
233 $self->stock_id($id);
234 $self->stock($new_row);
236 if ($self->organization_name){
237 $self->_store_stockprop('organization', $self->organization_name());
239 if ($self->population_name){
240 $self->_store_population_relationship();
244 else {
245 die "The entry ".$self->uniquename()." already exists in the database. Error: $exists\n";
248 else { # entry exists, so update
249 print STDERR "EXISTS: $exists\n";
250 my $row = $self->schema()->resultset("Stock::Stock")->find({ stock_id => $self->stock_id() });
251 $row->name($self->name());
252 $row->uniquename($self->uniquename());
253 $row->description($self->description());
254 $row->type_id($self->type_id());
255 $row->organism_id($self->organism_id());
256 $row->is_obsolete($self->is_obsolete());
257 $row->update();
259 return $self->stock_id();
262 ########################
265 =head2 exists_in_database
267 Usage: $self->exists_in_database()
268 Desc: check if the uniquename exists in the stock table
269 Ret:
270 Args:
271 Side Effects:
272 Example:
274 =cut
276 sub exists_in_database {
277 my $self = shift;
278 my $schema = $self->schema;
279 my $stock = $self->stock;
280 my $stock_id = $self->stock_id;
281 my $uniquename = $self->uniquename || '' ;
282 my $stock_lookup = CXGN::Stock::StockLookup->new({
283 schema => $schema,
284 stock_name => $uniquename
286 my $s = $stock_lookup->get_stock();
288 # loading new stock - $stock_id is undef
290 if (defined($s) && !$stock ) {
291 return "Uniquename already exists in database with stock_id: ".$s->stock_id;
294 # updating an existing stock
296 elsif ($stock && defined($s) ) {
297 if ( ($s->stock_id == $stock_id) ) {
298 return 0;
299 #trying to update the uniquename
301 elsif ( $s->stock_id != $stock_id ) {
302 return " Can't update an existing stock $stock_id uniquename:$uniquename.";
303 # if the new name we're trying to update/insert does not exist
304 # in the stock table..
307 elsif ($stock && !$s->stock_id) {
308 return 0;
311 return undef;
314 =head2 get_organism
316 Usage: $self->get_organism
317 Desc: find the organism object of this stock
318 Ret: L<Bio::Chado::Schema::Organism::Organism> object
319 Args: none
320 Side Effects: none
321 Example:
323 =cut
325 sub get_organism {
326 my $self = shift;
327 my $bcs_stock = $self->schema()->resultset("Stock::Stock")->find( { stock_id => $self->stock_id() });
328 if ($bcs_stock) {
329 return $bcs_stock->organism;
331 return undef;
335 =head2 get_species
337 Usage: $self->get_species
338 Desc: find the species name of this stock , if one exists
339 Ret: string
340 Args: none
341 Side Effects: none
342 Example:
344 =cut
346 sub get_species {
347 my $self = shift;
348 my $organism = $self->get_organism;
349 if ($organism) {
350 return $organism->species;
352 else {
353 return undef;
357 =head2 get_genus
359 Usage: $self->get_genus
360 Desc: find the genus name of this stock , if one exists
361 Ret: string
362 Args: none
363 Side Effects: none
364 Example:
366 =cut
368 sub get_genus {
369 my $self = shift;
370 my $organism = $self->get_organism;
371 if ($organism) {
372 return $organism->genus;
374 else {
375 return undef;
379 =head2 get_species_authority
381 Usage: $self->get_species_authority
382 Desc: find the species_authority of this stock , if one exists
383 Ret: string
384 Args: none
385 Side Effects: none
386 Example:
388 =cut
390 sub get_species_authority {
391 my $self = shift;
392 return $self->_retrieve_organismprop('species authority');
395 =head2 get_subtaxa
397 Usage: $self->get_subtaxa
398 Desc: find the subtaxa of this stock , if one exists
399 Ret: string
400 Args: none
401 Side Effects: none
402 Example:
404 =cut
406 sub get_subtaxa {
407 my $self = shift;
408 return $self->_retrieve_organismprop('subtaxa');
411 =head2 get_subtaxa_authority
413 Usage: $self->get_subtaxa_authority
414 Desc: find the subtaxa_authority of this stock , if one exists
415 Ret: string
416 Args: none
417 Side Effects: none
418 Example:
420 =cut
422 sub get_subtaxa_authority {
423 my $self = shift;
424 return $self->_retrieve_organismprop('subtaxa authority');
427 =head2 set_species
429 Usage: $self->set_species
430 Desc: set organism_id for the stock using organism.species name
431 Ret: nothing
432 Args: species name (case insensitive)
433 Side Effects: sets the organism_id for the stock
434 Example:
436 =cut
438 sub set_species {
439 my $self = shift;
440 my $species_name = shift; # this has to be EXACTLY as stored in the organism table
441 my $organism = $self->get_schema->resultset('Organism::Organism')->search(
442 { 'lower(species)' => { like => lc($species_name) } } )->single ; #should be 1 result
443 if ($organism) {
444 $self->organism_id($organism->organism_id);
446 else {
447 warn "NO organism found for species name $species_name!!\n";
451 =head2 function get_image_ids
453 Synopsis: my @images = $self->get_image_ids()
454 Arguments: none
455 Returns: a list of image ids
456 Side effects: none
457 Description: a method for fetching all images associated with a stock
459 =cut
461 sub get_image_ids {
462 my $self = shift;
463 my $ids = $self->schema()->storage->dbh->selectcol_arrayref
464 ( "SELECT image_id FROM phenome.stock_image WHERE stock_id=? ",
465 undef,
466 $self->stock_id
468 return @$ids;
471 =head2 associate_allele
473 Usage: $self->associate_allele($allele_id, $sp_person_id)
474 Desc: store a stock-allele link in phenome.stock_allele
475 Ret: a database id
476 Args: allele_id, sp_person_id
477 Side Effects: store a metadata row
478 Example:
480 =cut
482 sub associate_allele {
483 my $self = shift;
484 my $allele_id = shift;
485 my $sp_person_id = shift;
486 if (!$allele_id || !$sp_person_id) {
487 warn "Need both allele_id and person_id for linking the stock with an allele!";
488 return
490 my $metadata_id = $self->_new_metadata_id($sp_person_id);
491 #check if the allele is already linked
492 my $ids = $self->schema()->storage()->dbh()->selectcol_arrayref
493 ( "SELECT stock_allele_id FROM phenome.stock_allele WHERE stock_id = ? AND allele_id = ?",
494 undef,
495 $self->stock_id,
496 $allele_id
498 if ($ids) { warn "Allele $allele_id is already linked with stock " . $self->stock_id ; }
499 #store the allele_id - stock_id link
500 my $q = "INSERT INTO phenome.stock_allele (stock_id, allele_id, metadata_id) VALUES (?,?,?) RETURNING stock_allele_id";
501 my $sth = $self->schema()->storage()->dbh()->prepare($q);
502 $sth->execute($self->stock_id, $allele_id, $metadata_id);
503 my ($id) = $sth->fetchrow_array;
504 return $id;
507 =head2 associate_owner
509 Usage: $self->associate_owner($owner_sp_person_id, $sp_person_id)
510 Desc: store a stock-owner link in phenome.stock_owner
511 Ret: a database id
512 Args: owner_id, sp_person_id
513 Side Effects: store a metadata row
514 Example:
516 =cut
518 sub associate_owner {
519 my $self = shift;
520 my $owner_id = shift;
521 my $sp_person_id = shift;
522 if (!$owner_id || !$sp_person_id) {
523 warn "Need both owner_id and person_id for linking the stock with an owner!";
524 return;
526 my $metadata_id = $self->_new_metadata_id($sp_person_id);
527 #check if the owner is already linked
528 my $ids = $self->schema()->storage()->dbh()->selectcol_arrayref
529 ( "SELECT stock_owner_id FROM phenome.stock_owner WHERE stock_id = ? AND owner_id = ?",
530 undef,
531 $self->stock_id,
532 $owner_id
534 if ($ids) { warn "Owner $owner_id is already linked with stock " . $self->stock_id ; }
535 #store the owner_id - stock_id link
536 my $q = "INSERT INTO phenome.stock_owner (stock_id, owner_id, metadata_id) VALUES (?,?,?) RETURNING stock_owner_id";
537 my $sth = $self->schema()->storage()->dbh()->prepare($q);
538 $sth->execute($self->stock_id, $owner_id, $metadata_id);
539 my ($id) = $sth->fetchrow_array;
540 return $id;
543 =head2 get_trait_list
545 Usage:
546 Desc: gets the list of traits that have been measured
547 on this stock
548 Ret: a list of lists ( [ cvterm_id, cvterm_name] , ...)
549 Args:
550 Side Effects:
551 Example:
553 =cut
555 sub get_trait_list {
556 my $self = shift;
558 my $q = "select distinct(cvterm.cvterm_id), db.name || ':' || dbxref.accession, cvterm.name, avg(phenotype.value::Real), stddev(phenotype.value::Real) from stock as accession join stock_relationship on (accession.stock_id=stock_relationship.object_id) JOIN stock as plot on (plot.stock_id=stock_relationship.subject_id) JOIN nd_experiment_stock ON (plot.stock_id=nd_experiment_stock.stock_id) JOIN nd_experiment_phenotype USING(nd_experiment_id) JOIN phenotype USING (phenotype_id) JOIN cvterm ON (phenotype.cvalue_id = cvterm.cvterm_id) JOIN dbxref ON(cvterm.dbxref_id = dbxref.dbxref_id) JOIN db USING(db_id) where accession.stock_id=? and phenotype.value~? group by cvterm.cvterm_id, db.name || ':' || dbxref.accession, cvterm.name";
559 my $h = $self->schema()->storage()->dbh()->prepare($q);
560 my $numeric_regex = '^[0-9]+([,.][0-9]+)?$';
561 $h->execute($self->stock_id(), $numeric_regex);
562 my @traits;
563 while (my ($cvterm_id, $cvterm_accession, $cvterm_name, $avg, $stddev) = $h->fetchrow_array()) {
564 push @traits, [ $cvterm_id, $cvterm_accession, $cvterm_name, $avg, $stddev ];
567 # get directly associated traits
569 $q = "select distinct(cvterm.cvterm_id), db.name || ':' || dbxref.accession, cvterm.name, avg(phenotype.value::Real), stddev(phenotype.value::Real) from stock JOIN nd_experiment_stock ON (stock.stock_id=nd_experiment_stock.stock_id) JOIN nd_experiment_phenotype USING(nd_experiment_id) JOIN phenotype USING (phenotype_id) JOIN cvterm ON (phenotype.cvalue_id = cvterm.cvterm_id) JOIN dbxref ON(cvterm.dbxref_id = dbxref.dbxref_id) JOIN db USING(db_id) where stock.stock_id=? and phenotype.value~? group by cvterm.cvterm_id, db.name || ':' || dbxref.accession, cvterm.name";
571 $h = $self->schema()->storage()->dbh()->prepare($q);
572 $numeric_regex = '^[0-9]+([,.][0-9]+)?$';
573 $h->execute($self->stock_id(), $numeric_regex);
575 while (my ($cvterm_id, $cvterm_accession, $cvterm_name, $avg, $stddev) = $h->fetchrow_array()) {
576 push @traits, [ $cvterm_id, $cvterm_accession, $cvterm_name, $avg, $stddev ];
579 return @traits;
582 =head2 get_trials
584 Usage:
585 Desc: gets the list of trails this stock was used in
586 Ret:
587 Args:
588 Side Effects:
589 Example:
591 =cut
593 sub get_trials {
594 my $self = shift;
595 my $dbh = $self->schema()->storage()->dbh();
597 my $geolocation_q = "SELECT nd_geolocation_id, description FROM nd_geolocation;";
598 my $geolocation_h = $dbh->prepare($geolocation_q);
599 $geolocation_h->execute();
600 my %geolocations;
602 while (my ($nd_geolocation_id, $description) = $geolocation_h->fetchrow_array()) {
603 $geolocations{$nd_geolocation_id} = $description;
606 my $geolocation_type_id = SGN::Model::Cvterm->get_cvterm_row($self->schema(), 'project location', 'project_property')->cvterm_id();
607 my $q = "select distinct(project.project_id), project.name, projectprop.value from stock as accession join stock_relationship on (accession.stock_id=stock_relationship.object_id) JOIN stock as plot on (plot.stock_id=stock_relationship.subject_id) JOIN nd_experiment_stock ON (plot.stock_id=nd_experiment_stock.stock_id) JOIN nd_experiment_project USING(nd_experiment_id) JOIN project USING (project_id) LEFT JOIN projectprop ON (project.project_id=projectprop.project_id) where projectprop.type_id=$geolocation_type_id AND accession.stock_id=?;";
609 my $h = $dbh->prepare($q);
610 $h->execute($self->stock_id());
612 my @trials;
613 while (my ($project_id, $project_name, $nd_geolocation_id) = $h->fetchrow_array()) {
614 push @trials, [ $project_id, $project_name, $nd_geolocation_id, $geolocations{$nd_geolocation_id} ];
616 return @trials;
619 =head2 get_ancestor_hash
621 Usage:
622 Desc: gets a multi-dimensional hash of this stock's ancestors
623 Ret:
624 Args:
625 Side Effects:
626 Example:
628 =cut
630 sub get_ancestor_hash {
631 my ($self, $stock_id, $direct_descendant_ids) = @_;
633 if (!$stock_id) { $stock_id = $self->stock_id(); }
634 push @$direct_descendant_ids, $stock_id; #excluded in parent retrieval to prevent loops
636 my $stock = $self->schema->resultset("Stock::Stock")->find({stock_id => $stock_id});
637 #print STDERR "Stock ".$stock->uniquename()." decendants are: ".Dumper($direct_descendant_ids)."\n";
638 my %pedigree;
639 $pedigree{'id'} = $stock_id;
640 $pedigree{'name'} = $stock->uniquename();
641 $pedigree{'female_parent'} = undef;
642 $pedigree{'male_parent'} = undef;
643 $pedigree{'link'} = "/stock/$stock_id/view";
645 #get cvterms for parent relationships
646 my $cvterm_female_parent = SGN::Model::Cvterm->get_cvterm_row($self->schema, 'female_parent', 'stock_relationship');
647 my $cvterm_male_parent = SGN::Model::Cvterm->get_cvterm_row($self->schema, 'male_parent', 'stock_relationship');
649 #get the stock relationships for the stock, find stock relationships for types "female_parent" and "male_parent", and get the corresponding subject stock IDs and stocks.
650 my $stock_relationships = $stock->search_related("stock_relationship_objects",undef,{ prefetch => ['type','subject'] });
651 my $female_parent_relationship = $stock_relationships->find({type_id => $cvterm_female_parent->cvterm_id(), subject_id => {'not_in' => $direct_descendant_ids}});
652 if ($female_parent_relationship) {
653 my $female_parent_stock_id = $female_parent_relationship->subject_id();
654 $pedigree{'cross_type'} = $female_parent_relationship->value();
655 $pedigree{'female_parent'} = get_ancestor_hash( $self, $female_parent_stock_id, $direct_descendant_ids );
657 my $male_parent_relationship = $stock_relationships->find({type_id => $cvterm_male_parent->cvterm_id(), subject_id => {'not_in' => $direct_descendant_ids}});
658 if ($male_parent_relationship) {
659 my $male_parent_stock_id = $male_parent_relationship->subject_id();
660 $pedigree{'male_parent'} = get_ancestor_hash( $self, $male_parent_stock_id, $direct_descendant_ids );
662 pop @$direct_descendant_ids; # falling back a level while recursing pedigree tree
663 return \%pedigree;
666 =head2 get_descendant_hash
668 Usage:
669 Desc: gets a multi-dimensional hash of this stock's descendants
670 Ret:
671 Args:
672 Side Effects:
673 Example:
675 =cut
677 sub get_descendant_hash {
678 my ($self, $stock_id, $direct_ancestor_ids) = @_;
680 if (!$stock_id) { $stock_id = $self->stock_id(); }
681 push @$direct_ancestor_ids, $stock_id; #excluded in child retrieval to prevent loops
683 my $stock = $self->schema->resultset("Stock::Stock")->find({stock_id => $stock_id});
684 #print STDERR "Stock ".$stock->uniquename()." ancestors are: ".Dumper($direct_ancestor_ids)."\n";
685 my %descendants;
686 my %progeny;
687 $descendants{'id'} = $stock_id;
688 $descendants{'name'} = $stock->uniquename();
689 $descendants{'link'} = "/stock/$stock_id/view";
690 #get cvterms for parent relationships
691 my $cvterm_female_parent = SGN::Model::Cvterm->get_cvterm_row($self->schema, 'female_parent','stock_relationship');
692 my $cvterm_male_parent = SGN::Model::Cvterm->get_cvterm_row($self->schema, 'male_parent', 'stock_relationship');
694 #get the stock relationships for the stock, find stock relationships for types "female_parent" and "male_parent", and get the corresponding subject stock IDs and stocks.
695 my $descendant_relationships = $stock->search_related("stock_relationship_subjects",{ object_id => {'not_in' => $direct_ancestor_ids}},{ prefetch => ['type','object'] });
696 if ($descendant_relationships) {
697 while (my $descendant_relationship = $descendant_relationships->next) {
698 my $descendant_stock_id = $descendant_relationship->object_id();
699 if (($descendant_relationship->type_id() == $cvterm_female_parent->cvterm_id()) || ($descendant_relationship->type_id() == $cvterm_male_parent->cvterm_id())) {
700 $progeny{$descendant_stock_id} = get_descendant_hash($self, $descendant_stock_id, $direct_ancestor_ids);
703 $descendants{'descendants'} = \%progeny;
704 pop @$direct_ancestor_ids; # falling back a level while recursing descendant tree
705 return \%descendants;
709 =head2 get_pedigree_rows
711 Usage:
712 Desc: get an array of pedigree rows from an array of stock ids, conatining female parent, male parent, and cross type if defined
713 Ret:
714 Args: $accession_ids, $format (either 'parents_only' or 'full')
715 Side Effects:
716 Example:
718 =cut
720 sub get_pedigree_rows {
721 my ($self, $accession_ids, $format) = @_;
722 #print STDERR "Accession ids are: ".Dumper(@$accession_ids)."\n";
724 my $placeholders = join ( ',', ('?') x @$accession_ids );
725 my ($query, $pedigree_rows);
727 if ($format eq 'parents_only') {
728 $query = "
729 SELECT child.uniquename AS Accession,
730 mother.uniquename AS Female_Parent,
731 father.uniquename AS Male_Parent,
732 m_rel.value AS cross_type
733 FROM stock child
734 LEFT JOIN stock_relationship m_rel ON(child.stock_id = m_rel.object_id and m_rel.type_id = (SELECT cvterm_id FROM cvterm WHERE name = 'female_parent'))
735 LEFT JOIN stock mother ON(m_rel.subject_id = mother.stock_id)
736 LEFT JOIN stock_relationship f_rel ON(child.stock_id = f_rel.object_id and f_rel.type_id = (SELECT cvterm_id FROM cvterm WHERE name = 'male_parent'))
737 LEFT JOIN stock father ON(f_rel.subject_id = father.stock_id)
738 WHERE child.stock_id IN ($placeholders)
739 GROUP BY 1,2,3,4
740 ORDER BY 1";
742 elsif ($format eq 'full') {
743 $query = "
744 WITH RECURSIVE included_rows(child, child_id, mother, mother_id, father, father_id, type, depth, path, cycle) AS (
745 SELECT c.uniquename AS child,
746 c.stock_id AS child_id,
747 m.uniquename AS mother,
748 m.stock_id AS mother_id,
749 f.uniquename AS father,
750 f.stock_id AS father_id,
751 m_rel.value AS type,
753 ARRAY[c.stock_id],
754 false
755 FROM stock c
756 LEFT JOIN stock_relationship m_rel ON(c.stock_id = m_rel.object_id and m_rel.type_id = (SELECT cvterm_id FROM cvterm WHERE name = 'female_parent'))
757 LEFT JOIN stock m ON(m_rel.subject_id = m.stock_id)
758 LEFT JOIN stock_relationship f_rel ON(c.stock_id = f_rel.object_id and f_rel.type_id = (SELECT cvterm_id FROM cvterm WHERE name = 'male_parent'))
759 LEFT JOIN stock f ON(f_rel.subject_id = f.stock_id)
760 WHERE c.stock_id IN ($placeholders)
761 GROUP BY 1,2,3,4,5,6,7,8,9,10
762 UNION
763 SELECT c.uniquename AS child,
764 c.stock_id AS child_id,
765 m.uniquename AS mother,
766 m.stock_id AS mother_id,
767 f.uniquename AS father,
768 f.stock_id AS father_id,
769 m_rel.value AS type,
770 included_rows.depth + 1,
771 path || c.stock_id,
772 c.stock_id = ANY(path)
773 FROM included_rows, stock c
774 LEFT JOIN stock_relationship m_rel ON(c.stock_id = m_rel.object_id and m_rel.type_id = (SELECT cvterm_id FROM cvterm WHERE name = 'female_parent'))
775 LEFT JOIN stock m ON(m_rel.subject_id = m.stock_id)
776 LEFT JOIN stock_relationship f_rel ON(c.stock_id = f_rel.object_id and f_rel.type_id = (SELECT cvterm_id FROM cvterm WHERE name = 'male_parent'))
777 LEFT JOIN stock f ON(f_rel.subject_id = f.stock_id)
778 WHERE c.stock_id IN (included_rows.mother_id, included_rows.father_id) AND NOT cycle
779 GROUP BY 1,2,3,4,5,6,7,8,9,10
781 SELECT child, mother, father, type, depth
782 FROM included_rows
783 GROUP BY 1,2,3,4,5
784 ORDER BY 5,1;";
787 my $sth = $self->schema()->storage()->dbh()->prepare($query);
788 $sth->execute(@$accession_ids);
790 no warnings 'uninitialized';
791 while (my ($name, $mother, $father, $cross_type, $depth) = $sth->fetchrow_array()) {
792 #print STDERR "For child $name:\n\tMother:$mother\n\tFather:$father\n\tCross Type:$cross_type\n\tDepth:$depth\n\n";
793 push @$pedigree_rows, "$name\t$mother\t$father\t$cross_type\n";
795 return $pedigree_rows;
798 =head2 get_pedigree_string
800 Usage:
801 Desc: get the properly formatted pedigree string of the given level (Parents, Grandparents, or Great-Grandparents) for this stock
802 Ret:
803 Args:
804 Side Effects:
805 Example:
807 =cut
809 sub get_pedigree_string {
810 my ($self, $level) = @_;
812 my $pedigree_hashref = $self->get_ancestor_hash();
814 #print STDERR "Getting string of level $level from pedigree hashref ".Dumper($pedigree_hashref)."\n";
815 if ($level eq "Parents") {
816 return $self->_get_parent_string($pedigree_hashref);
818 elsif ($level eq "Grandparents") {
819 my $maternal_parent_string = $self->_get_parent_string($pedigree_hashref->{'female_parent'});
820 my $paternal_parent_string = $self->_get_parent_string($pedigree_hashref->{'male_parent'});
821 return "$maternal_parent_string//$paternal_parent_string";
823 elsif ($level eq "Great-Grandparents") {
824 my $mm_parent_string = $self->_get_parent_string($pedigree_hashref->{'female_parent'}->{'female_parent'});
825 my $mf_parent_string = $self->_get_parent_string($pedigree_hashref->{'female_parent'}->{'male_parent'});
826 my $pm_parent_string = $self->_get_parent_string($pedigree_hashref->{'male_parent'}->{'female_parent'});
827 my $pf_parent_string = $self->_get_parent_string($pedigree_hashref->{'male_parent'}->{'male_parent'});
828 return "$mm_parent_string//$mf_parent_string///$pm_parent_string//$pf_parent_string";
832 sub _get_parent_string {
833 my ($self, $pedigree_hashref) = @_;
834 my $mother = $pedigree_hashref->{'female_parent'}->{'name'} || 'NA';
835 my $father = $pedigree_hashref->{'male_parent'}->{'name'} || 'NA';
836 return "$mother/$father";
839 sub get_parents {
840 my $self = shift;
841 my $pedigree_hashref = $self->get_ancestor_hash();
842 my %parents;
843 $parents{'mother'} = $pedigree_hashref->{'female_parent'}->{'name'};
844 $parents{'mother_id'} = $pedigree_hashref->{'female_parent'}->{'id'};
845 $parents{'father'} = $pedigree_hashref->{'male_parent'}->{'name'};
846 $parents{'father_id'} = $pedigree_hashref->{'male_parent'}->{'id'};
847 return \%parents;
850 sub _store_stockprop {
851 my $self = shift;
852 my $type = shift;
853 my $value = shift;
854 my $stockprop = SGN::Model::Cvterm->get_cvterm_row($self->schema, $type, 'stock_property')->name();
855 my $stored_stockprop = $self->stock->create_stockprops({ $stockprop => $value});
858 sub _retrieve_stockprop {
859 my $self = shift;
860 my $type = shift;
861 my @results;
863 try {
864 my $stockprop_type_id = SGN::Model::Cvterm->get_cvterm_row($self->schema, $type, 'stock_property')->cvterm_id();
865 my $rs = $self->schema()->resultset("Stock::Stockprop")->search({ stock_id => $self->stock_id(), type_id => $stockprop_type_id }, { order_by => {-asc => 'stockprop_id'} });
867 while (my $r = $rs->next()){
868 push @results, $r->value;
870 } catch {
871 #print STDERR "Cvterm $type does not exist in this database\n";
874 my $res = join ',', @results;
875 return $res;
878 sub _remove_stockprop {
879 my $self = shift;
880 my $type = shift;
881 my $value = shift;
882 my $type_id = SGN::Model::Cvterm->get_cvterm_row($self->schema, $type, 'stock_property')->cvterm_id();
883 my $rs = $self->schema()->resultset("Stock::Stockprop")->search( { type_id=>$type_id, stock_id => $self->stock_id(), value=>$value } );
885 if ($rs->count() == 1) {
886 $rs->first->delete();
887 return 1;
889 elsif ($rs->count() == 0) {
890 return 0;
892 else {
893 print STDERR "Error removing stockprop from stock ".$self->stock_id().". Please check this manually.\n";
894 return 0;
899 sub _retrieve_organismprop {
900 my $self = shift;
901 my $type = shift;
902 my @results;
904 try {
905 my $organismprop_type_id = SGN::Model::Cvterm->get_cvterm_row($self->schema, $type, 'organism_property')->cvterm_id();
906 my $rs = $self->schema()->resultset("Organism::Organismprop")->search({ organism_id => $self->stock->organism_id, type_id => $organismprop_type_id }, { order_by => {-asc => 'organismprop_id'} });
908 while (my $r = $rs->next()){
909 push @results, $r->value;
911 } catch {
912 #print STDERR "Cvterm $type does not exist in this database\n";
915 my $res = join ',', @results;
916 return $res;
919 sub _store_population_relationship {
920 my $self = shift;
921 my $schema = $self->schema;
922 my $population_cvterm_id = SGN::Model::Cvterm->get_cvterm_row($schema, 'population','stock_type')->cvterm_id();
923 my $population_member_cvterm_id = SGN::Model::Cvterm->get_cvterm_row($schema, 'member_of','stock_relationship')->cvterm_id();
925 my $population = $schema->resultset("Stock::Stock")->find_or_create({
926 uniquename => $self->population_name(),
927 name => $self->population_name(),
928 organism_id => $self->organism_id(),
929 type_id => $population_cvterm_id,
931 $self->stock->find_or_create_related('stock_relationship_objects', {
932 type_id => $population_member_cvterm_id,
933 object_id => $population->stock_id(),
934 subject_id => $self->stock_id(),
938 sub _retrieve_populations {
939 my $self = shift;
940 my $schema = $self->schema;
941 my $population_member_cvterm_id = SGN::Model::Cvterm->get_cvterm_row($schema, 'member_of','stock_relationship')->cvterm_id();
943 my $rs = $schema->resultset("Stock::StockRelationship")->search({
944 type_id => $population_member_cvterm_id,
945 subject_id => $self->stock_id(),
947 if ($rs->count == 0) {
948 #print STDERR "No population saved for this stock!\n";
950 else {
951 my @population_names;
952 while (my $row = $rs->next) {
953 my $population = $row->object;
954 push @population_names, $population->uniquename();
956 $self->populations(\@population_names);
957 #print STDERR "This stock is a member of the following populations: ".Dumper($self->populations())."\n";
961 =head2 _new_metadata_id
963 Usage: my $md_id = $self->_new_metatada_id($sp_person_id)
964 Desc: Store a new md_metadata row with a $sp_person_id
965 Ret: a database id
966 Args: sp_person_id
968 =cut
970 sub _new_metadata_id {
971 my $self = shift;
972 my $sp_person_id = shift;
973 my $metadata_schema = CXGN::Metadata::Schema->connect(
974 sub { $self->schema()->storage()->dbh() },
976 my $metadata = CXGN::Metadata::Metadbdata->new($metadata_schema);
977 $metadata->set_create_person_id($sp_person_id);
978 my $metadata_id = $metadata->store()->get_metadata_id();
979 return $metadata_id;
982 =head2 merge
984 Usage: $s->merge(221, 1);
985 Desc: merges stock $s with stock_id 221. Optional delete boolean
986 parameter indicates whether other stock should be deleted.
987 Ret:
988 Args:
989 Side Effects:
990 Example:
992 =cut
994 sub merge {
995 my $self = shift;
996 my $other_stock_id = shift;
997 my $delete_other_stock = shift;
999 if ($other_stock_id == $self->stock_id()) {
1000 print STDERR "Trying to merge stock into itself ($other_stock_id) Skipping...\n";
1001 return;
1006 my $stockprop_count=0;
1007 my $subject_rel_count=0;
1008 my $object_rel_count=0;
1009 my $stock_allele_count=0;
1010 my $image_count=0;
1011 my $experiment_stock_count=0;
1012 my $stock_dbxref_count=0;
1013 my $stock_owner_count=0;
1014 my $parent_1_count=0;
1015 my $parent_2_count=0;
1016 my $other_stock_deleted = 'NO';
1019 my $schema = $self->schema();
1021 # move stockprops
1023 my $sprs = $schema->resultset("Stock::Stockprop")->search( { stock_id => $other_stock_id });
1024 while (my $row = $sprs->next()) {
1026 # check if this stockprop already exists for this stock; save only if not
1028 my $thissprs = $schema->resultset("Stock::Stockprop")->search(
1030 stock_id => $self->stock_id(),
1031 type_id => $row->type_id(),
1032 value => $row->value()
1035 if ($thissprs->count() == 0) {
1036 my $value = $row->value();
1037 my $type_id = $row->type_id();
1039 my $rank_rs = $schema->resultset("Stock::Stockprop")->search( { stock_id => $self->stock_id(), type_id => $type_id });
1041 my $rank;
1042 if ($rank_rs->count() > 0) {
1043 $rank = $rank_rs->get_column("rank")->max();
1046 $rank++;
1047 $row->rank($rank);
1048 $row->stock_id($self->stock_id());
1050 $row->update();
1052 print STDERR "MERGED stockprop_id ".$row->stockprop_id." for stock $other_stock_id type_id $type_id value $value into stock ".$self->stock_id()."\n";
1053 $stockprop_count++;
1057 # move subject relationships
1059 my $ssrs = $schema->resultset("Stock::StockRelationship")->search( { subject_id => $other_stock_id });
1061 while (my $row = $ssrs->next()) {
1063 my $this_subject_rel_rs = $schema->resultset("Stock::StockRelationship")->search( { subject_id => $self->stock_id(), object_id => $row->object_id, type_id => $row->type_id() });
1065 if ($this_subject_rel_rs->count() == 0) { # this stock does not have the relationship
1066 # get the max rank
1067 my $rank_rs = $schema->resultset("Stock::StockRelationship")->search( { subject_id => $self->stock_id(), type_id => $row->type_id() });
1068 my $rank = 0;
1069 if ($rank_rs->count() > 0) {
1070 $rank = $rank_rs->get_column("rank")->max();
1072 $rank++;
1073 $row->rank($rank);
1074 $row->subject_id($self->stock_id());
1075 $row->update();
1076 print STDERR "Moving subject relationships from stock $other_stock_id to stock ".$self->stock_id()."\n";
1077 $subject_rel_count++;
1081 # move object relationships
1083 my $osrs = $schema->resultset("Stock::StockRelationship")->search( { object_id => $other_stock_id });
1084 while (my $row = $osrs->next()) {
1085 my $this_object_rel_rs = $schema->resultset("Stock::StockRelationship")->search( { object_id => $self->stock_id, subject_id => $row->subject_id(), type_id => $row->type_id() });
1087 if ($this_object_rel_rs->count() == 0) {
1088 my $rank_rs = $schema->resultset("Stock::StockRelationship")->search( { object_id => $self->stock_id(), type_id => $row->type_id() });
1089 my $rank = 0;
1090 if ($rank_rs->count() > 0) {
1091 $rank = $rank_rs->get_column("rank")->max();
1093 $rank++;
1094 $row->rank($rank);
1095 $row->object_id($self->stock_id());
1096 $row->update();
1097 print STDERR "Moving object relationships from stock $other_stock_id to stock ".$self->stock_id()."\n";
1098 $object_rel_count++;
1102 # move experiment_stock
1104 my $esrs = $schema->resultset("NaturalDiversity::NdExperimentStock")->search( { stock_id => $other_stock_id });
1105 while (my $row = $esrs->next()) {
1106 $row->stock_id($self->stock_id());
1107 $row->update();
1108 print STDERR "Moving experiments for stock $other_stock_id to stock ".$self->stock_id()."\n";
1109 $experiment_stock_count++;
1112 # move stock_cvterm relationships
1116 # move stock_dbxref
1118 my $sdrs = $schema->resultset("Stock::StockDbxref")->search( { stock_id => $other_stock_id });
1119 while (my $row = $sdrs->next()) {
1120 $row->stock_id($self->stock_id());
1121 $row->update();
1122 $stock_dbxref_count++;
1125 # move sgn.pcr_exp_accession relationships
1129 # move sgn.pcr_experiment relationships
1134 # move stock_genotype relationships
1138 my $phenome_schema = CXGN::Phenome::Schema->connect(
1139 sub { $self->schema()->storage()->dbh() }, { on_connect_do => [ 'SET search_path TO phenome, public, sgn'], limit_dialect => 'LimitOffset' }
1142 # move phenome.stock_allele relationships
1144 my $sars = $phenome_schema->resultset("StockAllele")->search( { stock_id => $other_stock_id });
1145 while (my $row = $sars->next()) {
1146 $row->stock_id($self->stock_id());
1147 $row->udate();
1148 print STDERR "Moving stock alleles from stock $other_stock_id to stock ".$self->stock_id()."\n";
1149 $stock_allele_count++;
1152 # move image relationships
1154 my $irs = $phenome_schema->resultset("StockImage")->search( { stock_id => $other_stock_id });
1155 while (my $row = $irs->next()) {
1157 my $this_rs = $phenome_schema->resultset("StockImage")->search( { stock_id => $self->stock_id(), image_id => $row->image_id() } );
1158 if ($this_rs->count() == 0) {
1159 $row->stock_id($self->stock_id());
1160 $row->update();
1161 print STDERR "Moving image ".$row->image_id()." from stock $other_stock_id to stock ".$self->stock_id()."\n";
1162 $image_count++;
1164 else {
1165 print STDERR "Removing stock_image entry...\n";
1166 $row->delete(); # there is no cascade delete on image relationships, so we need to remove dangling relationships.
1170 # move stock owners
1172 my $sors = $phenome_schema->resultset("StockOwner")->search( { stock_id => $other_stock_id });
1173 while (my $row = $sors->next()) {
1175 my $this_rs = $phenome_schema->resultset("StockOwner")->search( { stock_id => $self->stock_id(), sp_person_id => $row->sp_person_id() });
1176 if ($this_rs->count() == 0) {
1177 $row->stock_id($self->stock_id());
1178 $row->update();
1179 print STDERR "Moved stock_owner ".$row->sp_person_id()." of stock $other_stock_id to stock ".$self->stock_id()."\n";
1180 $stock_owner_count++;
1182 else {
1183 print STDERR "(Deleting stock owner entry for stock $other_stock_id, owner ".$row->sp_person_id()."\n";
1184 $row->delete(); # see comment for move image relationships
1188 # move map parents
1190 my $sgn_schema = SGN::Schema->connect(
1191 sub { $self->schema()->storage()->dbh() }, { limit_dialect => 'LimitOffset' }
1194 my $mrs1 = $sgn_schema->resultset("Map")->search( { parent_1 => $other_stock_id });
1195 while (my $row = $mrs1->next()) {
1196 $row->parent_1($self->stock_id());
1197 $row->update();
1198 print STDERR "Move map parent_1 $other_stock_id to ".$self->stock_id()."\n";
1199 $parent_1_count++;
1202 my $mrs2 = $sgn_schema->resultset("Map")->search( { parent_2 => $other_stock_id });
1203 while (my $row = $mrs2->next()) {
1204 $row->parent_2($self->stock_id());
1205 $row->update();
1206 print STDERR "Move map parent_2 $other_stock_id to ".$self->stock_id()."\n";
1207 $parent_2_count++;
1210 if ($delete_other_stock) {
1211 my $row = $self->schema()->resultset("Stock::Stock")->find( { stock_id => $other_stock_id });
1212 $row->delete();
1213 $other_stock_deleted = 'YES';
1217 print STDERR "Done with merge of stock_id $other_stock_id into ".$self->stock_id()."\n";
1218 print STDERR "Relationships moved: \n";
1219 print STDERR <<COUNTS;
1220 Stock props: $stockprop_count
1221 Subject rels: $subject_rel_count
1222 Object rels: $object_rel_count
1223 Alleles: $stock_allele_count
1224 Images: $image_count
1225 Experiments: $experiment_stock_count
1226 Dbxrefs: $stock_dbxref_count
1227 Stock owners: $stock_owner_count
1228 Map parents: $parent_1_count
1229 Map parents: $parent_2_count
1230 Other stock deleted: $other_stock_deleted.
1231 COUNTS
1235 __PACKAGE__->meta->make_immutable;
1237 ##########
1238 1;########
1239 ##########