3 See the NOTICE file distributed with
this work
for additional information
4 regarding copyright ownership.
6 Licensed under the Apache License, Version 2.0 (the
"License");
7 you may not use
this file except in compliance with the License.
8 You may obtain a copy of the License at
12 Unless required by applicable law or agreed to in writing, software
13 distributed under the License is distributed on an
"AS IS" BASIS,
14 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 See the License
for the specific language governing permissions and
16 limitations under the License.
24 Please email comments or questions to the
public Ensembl
25 developers list at <http:
27 Questions may also be sent to the Ensembl help desk at
40 $operon_adaptor->store($operon);
41 my $operon2 = $operon_adaptor->fetch_by_dbID( $operon->dbID() );
45 This is a database aware adaptor
for the retrieval and storage of operon
52 package Bio::EnsEMBL::DBSQL::OperonAdaptor;
68 # Description: PROTECTED implementation of superclass abstract method.
69 # Returns the names, aliases of the tables to use for queries.
70 # Returntype : list of listrefs of strings
76 return ( [
'operon',
'o' ] );
82 # Description: PROTECTED implementation of superclass abstract method.
83 # Returns a list of columns to use for queries.
84 # Returntype : list of strings
93 $self->db()->dbc()->from_date_to_seconds(
"o.created_date");
95 $self->db()->dbc()->from_date_to_seconds(
"o.modified_date");
97 return (
'o.operon_id',
'o.seq_region_id',
'o.seq_region_start',
98 'o.seq_region_end',
'o.seq_region_strand',
'o.display_label',
99 'o.analysis_id',
'o.stable_id',
'o.version',
100 $created_date, $modified_date );
105 Example : @operon_ids = @{$operon_adaptor->list_dbIDs()};
106 Description: Gets an array of
internal ids
for all operons in the current db
107 Arg[1] : <optional>
int. not 0
for the ids to be sorted by the seq_region.
108 Returntype : Listref of Ints
116 my ( $self, $ordered ) = @_;
118 return $self->_list_dbIDs(
"operon", undef, $ordered );
121 =head2 list_stable_ids
123 Example : @stable_operon_ids = @{$operon_adaptor->list_stable_ids()};
124 Description: Gets an listref of stable ids
for all operons in the current db
125 Returntype : reference to a list of strings
132 sub list_stable_ids {
135 return $self->_list_dbIDs(
"operon",
"stable_id" );
138 sub list_seq_region_ids {
141 return $self->_list_seq_region_ids(
'operon');
146 Arg [1] : String $label - name of operon to fetch
147 Example : my $operon = $operonAdaptor->fetch_by_name(
"accBC");
148 Description: Returns the operon which has the given display label or undef
if
149 there is none. If there are more than 1, only the first is
162 my $constraint =
"o.display_label = ?";
163 $self->bind_param_generic_fetch( $label, SQL_VARCHAR );
164 my ($operon) = @{ $self->generic_fetch($constraint) };
169 =head2 fetch_by_stable_id
172 The stable ID of the operon to retrieve
173 Example : $operon = $operon_adaptor->fetch_by_stable_id(
'ENSG00000148944');
174 Description: Retrieves a operon
object from the database via its stable
id.
175 The operon will be retrieved in its native coordinate system (i.e.
176 in the coordinate system it is stored in the database). It may
177 be converted to a different coordinate system through a call to
178 transform() or transfer(). If the operon or
exon is not found
179 undef is returned instead.
181 Exceptions :
if we cant get the operon in given coord system
187 sub fetch_by_stable_id {
188 my ( $self, $stable_id ) = @_;
190 my $constraint =
"o.stable_id = ?";
191 $self->bind_param_generic_fetch( $stable_id, SQL_VARCHAR );
192 my ($operon) = @{ $self->generic_fetch($constraint) };
194 # If we didn't get anything back, desperately try to see if there's
195 # a version number in the stable_id
196 if(!defined($operon) && (my $vindex = rindex($stable_id,
'.'))) {
197 $operon = $self->fetch_by_stable_id_version(substr($stable_id,0,$vindex),
198 substr($stable_id,$vindex+1));
206 Example : $operons = $operon_adaptor->fetch_all();
207 Description : Similar to fetch_by_stable_id, but retrieves all
208 operons stored in the database.
215 =head2 fetch_by_stable_id_version
218 The stable ID of the operon to retrieve
219 Arg [2] : Integer $version
220 The version of the stable_id to retrieve
221 Example : $operon = $operon_adaptor->fetch_by_stable_id(
'16152-16153-4840', 2);
222 Description: Retrieves an operon
object from the database via its stable
id and version.
223 The operon will be retrieved in its native coordinate system (i.e.
224 in the coordinate system it is stored in the database). It may
225 be converted to a different coordinate system through a call to
226 transform() or transfer(). If the operon is not found
227 undef is returned instead.
229 Exceptions :
if we cant get the operon in given coord system
235 sub fetch_by_stable_id_version {
236 my ($self, $stable_id, $version) = @_;
238 # Enforce that version be numeric
239 return unless($version =~ /^\d+$/);
241 my $constraint =
"o.stable_id = ? AND o.version = ?";
242 $self->bind_param_generic_fetch($stable_id, SQL_VARCHAR);
243 $self->bind_param_generic_fetch($version, SQL_INTEGER);
244 my ($operon) = @{$self->generic_fetch($constraint)};
253 my @operons = @{ $self->generic_fetch($constraint) };
257 =head2 fetch_all_versions_by_stable_id
259 Arg [1] : String $stable_id
260 The stable ID of the operon to retrieve
261 Example : $operon = $operon_adaptor->fetch_all_versions_by_stable_id
263 Description : Similar to fetch_by_stable_id, but retrieves all versions of a
264 operon stored in the database.
266 Exceptions :
if we cant get the operon in given coord system
272 sub fetch_all_versions_by_stable_id {
273 my ( $self, $stable_id ) = @_;
275 my $constraint =
"o.stable_id = ?";
276 $self->bind_param_generic_fetch( $stable_id, SQL_VARCHAR );
277 return $self->generic_fetch($constraint);
280 =head2 fetch_all_by_Slice
283 The slice to fetch operons on.
284 Arg [2] : (optional)
string $logic_name
285 the logic name of the type of features to obtain
286 Arg [3] : (optional)
boolean $load_transcripts
287 if true, transcripts will be loaded immediately rather than
289 Arg [4] : (optional)
string $source
290 the source name of the features to obtain.
291 Arg [5] : (optional)
string biotype
292 the biotype of the features to obtain.
293 Example : @operons = @{$operon_adaptor->fetch_all_by_Slice()};
294 Description: Overrides superclass method to optionally load transcripts
295 immediately rather than lazy-loading them later. This
296 is more efficient when there are a lot of operons whose
297 transcripts are going to be used.
298 Returntype : reference to list of operons
300 Caller : Slice::get_all_operons
305 sub fetch_all_by_Slice {
306 my ( $self, $slice, $logic_name, $load_transcripts ) = @_;
310 $self->SUPER::fetch_all_by_Slice_constraint( $slice, $constraint,
313 # If there are less than two operons, still do lazy-loading.
314 if ( !$load_transcripts || @$operons < 2 ) {
318 # Preload all of the transcripts now, instead of lazy loading later,
319 # faster than one query per transcript.
321 # First check if transcripts are already preloaded.
322 # FIXME: Should check all transcripts.
323 if ( exists( $operons->[0]->{
'_operon_transcript_array'} ) ) {
327 # Get extent of region spanned by transcripts.
328 my ($min_start, $max_end);
331 unless ($slice->is_circular()) {
332 foreach my $o (@$operons) {
333 if (!defined($min_start) || $o->seq_region_start() < $min_start) {
334 $min_start = $o->seq_region_start();
336 if (!defined($max_end) || $o->seq_region_end() > $max_end) {
337 $max_end = $o->seq_region_end();
341 if ($min_start >= $slice->start() && $max_end <= $slice->end()) {
344 my $sa = $self->db()->get_SliceAdaptor();
345 $ext_slice = $sa->fetch_by_region($slice->coord_system->name(), $slice->seq_region_name(), $min_start, $max_end, $slice->strand(), $slice->coord_system->version());
349 # feature might be crossing the origin of replication (i.e. seq_region_start > seq_region_end)
350 # the computation of min_start|end based on seq_region_start|end is not safe
351 # use feature start/end relative to the slice instead
352 my ($min_start_feature, $max_end_feature);
353 foreach my $o (@$operons) {
354 if (!defined($min_start) || ($o->start() >= 0 && $o->start() < $min_start)) {
355 $min_start = $o->start();
356 $min_start_feature = $o;
358 if (!defined($max_end) || ($o->end() >= 0 && $o->end() > $max_end)) {
359 $max_end = $o->end();
360 $max_end_feature = $o;
364 # now we can reassign min_start|end to seq_region_start|end of
365 # the feature which spans the largest region
366 $min_start = $min_start_feature->seq_region_start();
367 $max_end = $max_end_feature->seq_region_end();
369 my $sa = $self->db()->get_SliceAdaptor();
371 $sa->fetch_by_region($slice->coord_system->name(),
372 $slice->seq_region_name(),
376 $slice->coord_system->version());
380 # Associate transcript identifiers with operons.
382 my %o_hash =
map { $_->dbID => $_ } @{$operons};
384 my $o_id_str = join(
',', keys(%o_hash) );
387 $self->prepare(
"SELECT operon_id, operon_transcript_id "
388 .
"FROM operon_transcript "
389 .
"WHERE operon_id IN ($o_id_str)" );
393 my ( $o_id, $tr_id );
394 $sth->bind_columns( \( $o_id, $tr_id ) );
398 while ( $sth->fetch() ) {
399 $tr_o_hash{$tr_id} = $o_hash{$o_id};
402 my $ta = $self->db()->get_OperonTranscriptAdaptor();
404 $ta->fetch_all_by_Slice( $ext_slice,
406 sprintf(
"ot.operon_transcript_id IN (%s)",
409 keys(%tr_o_hash) ) ) );
411 # Move transcripts onto operon slice, and add them to operons.
412 foreach my $tr ( @{$transcripts} ) {
413 if ( !exists( $tr_o_hash{ $tr->dbID() } ) ) {
418 if ( $slice != $ext_slice ) {
419 $new_tr = $tr->transfer($slice);
420 if ( !defined($new_tr) ) {
421 throw(
"Unexpected. "
422 .
"Transcript could not be transfered onto operon slice."
429 $tr_o_hash{ $tr->dbID() }->add_OperonTranscript($new_tr);
433 } ## end sub fetch_all_by_Slice
435 =head2 fetch_by_transcript_id
437 Arg [1] : Int $trans_id
438 Unique database identifier
for the
transcript whose operon should
439 be retrieved. The operon is returned in its native coord
440 system (i.e. the coord_system it is stored in). If the coord
441 system needs to be changed, then tranform or transfer should
442 be called on the returned
object. undef is returned
if the
443 operon or
transcript is not found in the database.
444 Example : $operon = $operon_adaptor->fetch_by_transcript_id(1241);
445 Description: Retrieves a operon from the database via the database identifier
446 of one of its transcripts.
454 sub fetch_by_operon_transcript_id {
455 my ( $self, $trans_id ) = @_;
457 # this is a cheap SQL call
458 my $sth = $self->prepare(
461 FROM operon_transcript tr
462 WHERE tr.operon_transcript_id = ?
465 $sth->bind_param( 1, $trans_id, SQL_INTEGER );
468 my ($operonid) = $sth->fetchrow_array();
472 return undef
if ( !defined $operonid );
474 my $operon = $self->fetch_by_dbID($operonid);
478 =head2 fetch_by_operon_transcript_stable_id
480 Arg [1] :
string $trans_stable_id
481 transcript stable ID whose operon should be retrieved
482 Example : my $operon = $operon_adaptor->fetch_by_operon_transcript_stable_id
484 Description: Retrieves a operon from the database via the stable ID of one of
493 sub fetch_by_operon_transcript_stable_id {
494 my ( $self, $trans_stable_id ) = @_;
496 my $sth = $self->prepare(
499 FROM operon_transcript
503 $sth->bind_param( 1, $trans_stable_id, SQL_VARCHAR );
506 my ($operonid) = $sth->fetchrow_array();
509 return undef
if ( !defined $operonid );
511 my $operon = $self->fetch_by_dbID($operonid);
515 sub fetch_by_operon_transcript {
516 my ( $self, $trans ) = @_;
517 assert_ref( $trans,
'Bio::EnsEMBL::OperonTranscript' );
518 $self->fetch_by_operon_transcript_id( $trans->dbID() );
524 The operon to store in the database
525 Arg [2] : ignore_release in xrefs [
default 1] set to 0 to use release
info
526 in external database references
527 Example : $operon_adaptor->store($operon);
528 Description: Stores a operon in the database.
529 Returntype : the database identifier (dbID) of the newly stored operon
531 $operon does not have an analysis
object
538 my ( $self, $operon, $ignore_release ) = @_;
540 if ( !ref $operon || !$operon->isa(
'Bio::EnsEMBL::Operon') ) {
541 throw(
"Must store a operon object, not a $operon");
544 my $db = $self->db();
546 if ( $operon->is_stored($db) ) {
547 return $operon->
dbID();
549 my $analysis = $operon->analysis();
550 throw(
"Operons must have an analysis object.")
if(!defined($analysis));
552 if ( $analysis->is_stored($db) ) {
553 $analysis_id = $analysis->dbID();
555 $analysis_id = $db->get_AnalysisAdaptor->store( $analysis );
557 # ensure coords are correct before storing
558 #$operon->recalculate_coordinates();
562 ( $operon, $seq_region_id ) = $self->_pre_store($operon);
575 if ( defined($operon->stable_id()) ) {
580 my $created = $self->db->dbc->from_seconds_to_date($operon->created_date());
581 my $modified = $self->db->dbc->from_seconds_to_date($operon->modified_date());
584 push @canned_columns,
'created_date';
585 push @canned_values, $created;
588 push @canned_columns,
'modified_date';
589 push @canned_values, $modified;
593 my $i_columns = join(
', ', @columns, @canned_columns);
594 my $i_values = join(
', ', ((
'?') x scalar(@columns)), @canned_values);
595 my $store_operon_sql = qq(
596 INSERT INTO operon ( ${i_columns} ) VALUES ( $i_values )
599 my $sth = $self->prepare($store_operon_sql);
600 $sth->bind_param( 1, $seq_region_id, SQL_INTEGER );
601 $sth->bind_param( 2, $operon->start(), SQL_INTEGER );
602 $sth->bind_param( 3, $operon->end(), SQL_INTEGER );
603 $sth->bind_param( 4, $operon->strand(), SQL_TINYINT );
604 $sth->bind_param( 5, $operon->display_label(), SQL_VARCHAR );
605 $sth->bind_param( 6, $analysis_id, SQL_INTEGER );
607 if ( defined($operon->stable_id()) ) {
608 $sth->bind_param( 7, $operon->stable_id(), SQL_VARCHAR );
609 my $version = ($operon->version()) ? $operon->version() : 1;
610 $sth->bind_param( 8, $version, SQL_INTEGER );
615 my $operon_dbID = $self->last_insert_id(
'operon_id', undef,
'operon');
617 my $transcripts = $operon->get_all_OperonTranscripts();
619 if ( $transcripts && scalar @$transcripts ) {
620 my $transcript_adaptor = $db->get_OperonTranscriptAdaptor();
621 for my $transcript (@$transcripts) {
622 $transcript_adaptor->store( $transcript, $operon_dbID );
626 # store the dbentries associated with this operon
627 my $dbEntryAdaptor = $db->get_DBEntryAdaptor();
629 foreach my $dbe ( @{ $operon->get_all_DBEntries } ) {
630 $dbEntryAdaptor->store( $dbe, $operon_dbID,
"Operon", $ignore_release );
633 # store operon attributes if there are any
634 my $attrs = $operon->get_all_Attributes();
635 if ( $attrs && scalar @$attrs ) {
636 my $attr_adaptor = $db->get_AttributeAdaptor();
637 $attr_adaptor->store_on_Operon( $operon, $attrs );
640 # set the adaptor and dbID on the original passed in operon not the
642 $operon->adaptor($self);
643 $operon->dbID($operon_dbID);
651 the operon to remove from the database
652 Example : $operon_adaptor->remove($operon);
653 Description: Removes a operon completely from the database. All associated
654 transcripts, exons, stable_identifiers, descriptions, etc.
655 are removed as well. Use with caution!
657 Exceptions :
throw on incorrect arguments
658 warning
if operon is not stored in
this database
668 if ( !ref($operon) || !$operon->isa(
'Bio::EnsEMBL::Operon') ) {
669 throw(
"Bio::EnsEMBL::Operon argument expected.");
672 if ( !$operon->is_stored( $self->db() ) ) {
673 warning(
"Cannot remove operon "
675 .
". Is not stored in "
676 .
"this database." );
680 # remove all object xrefs associated with this operon
682 my $dbe_adaptor = $self->db()->get_DBEntryAdaptor();
683 foreach my $dbe ( @{ $operon->get_all_DBEntries() } ) {
684 $dbe_adaptor->remove_from_object( $dbe, $operon,
'Operon' );
687 # remove all of the transcripts associated with this operon
688 my $transcriptAdaptor = $self->db->get_OperonTranscriptAdaptor();
689 foreach my $trans ( @{ $operon->get_all_OperonTranscripts() } ) {
690 $transcriptAdaptor->remove($trans);
693 # remove this operon from the database
695 my $sth = $self->prepare(
"DELETE FROM operon WHERE operon_id = ? ");
696 $sth->bind_param( 1, $operon->dbID, SQL_INTEGER );
700 # unset the operon identifier and adaptor thereby flagging it as unstored
702 $operon->dbID(undef);
703 $operon->adaptor(undef);
710 # Arg [1] : StatementHandle $sth
711 # Arg [2] : Bio::EnsEMBL::AssemblyMapper $mapper
712 # Arg [3] : Bio::EnsEMBL::Slice $dest_slice
713 # Description: PROTECTED implementation of abstract superclass method.
714 # responsible for the creation of Operons
715 # Returntype : listref of Bio::EnsEMBL::Operon in target coordinate system
721 my ($self, $sth, $mapper, $dest_slice) = @_;
724 # This code is ugly because an attempt has been made to remove as many
725 # function calls as possible for speed purposes. Thus many caches and
726 # a fair bit of gymnastics is used.
729 my $sa = $self->db()->get_SliceAdaptor();
730 my $aa = $self->db()->get_AnalysisAdaptor();
739 $operon_id, $seq_region_id, $seq_region_start,
740 $seq_region_end, $seq_region_strand, $display_label,
741 $analysis_id, $stable_id, $version,
742 $created_date, $modified_date
745 $sth->bind_columns( \(
746 $operon_id, $seq_region_id, $seq_region_start,
747 $seq_region_end, $seq_region_strand, $display_label,
748 $analysis_id, $stable_id, $version,
749 $created_date, $modified_date ));
751 my $dest_slice_start;
753 my $dest_slice_strand;
754 my $dest_slice_length;
756 my $dest_slice_sr_name;
757 my $dest_slice_sr_id;
761 $dest_slice_start = $dest_slice->start();
762 $dest_slice_end = $dest_slice->end();
763 $dest_slice_strand = $dest_slice->strand();
764 $dest_slice_length = $dest_slice->length();
765 $dest_slice_cs = $dest_slice->coord_system();
766 $dest_slice_sr_name = $dest_slice->seq_region_name();
767 $dest_slice_sr_id = $dest_slice->get_seq_region_id();
768 $asma = $self->db->get_AssemblyMapperAdaptor();
771 OPERON:
while ( $sth->fetch() ) {
773 #get the analysis object
774 my $analysis = $analysis_hash{$analysis_id} ||= $aa->fetch_by_dbID($analysis_id);
775 $analysis_hash{$analysis_id} = $analysis;
777 #need to get the internal_seq_region, if present
778 $seq_region_id = $self->get_seq_region_id_internal($seq_region_id);
779 my $slice = $slice_hash{
"ID:".$seq_region_id};
782 $slice = $sa->fetch_by_seq_region_id($seq_region_id);
783 $slice_hash{
"ID:".$seq_region_id} = $slice;
784 $sr_name_hash{$seq_region_id} = $slice->seq_region_name();
785 $sr_cs_hash{$seq_region_id} = $slice->coord_system();
788 #obtain a mapper if none was defined, but a dest_seq_region was
789 if(!$mapper && $dest_slice && !$dest_slice_cs->equals($slice->coord_system)) {
790 $mapper = $asma->fetch_by_CoordSystems($dest_slice_cs, $slice->coord_system);
793 my $sr_name = $sr_name_hash{$seq_region_id};
794 my $sr_cs = $sr_cs_hash{$seq_region_id};
797 # remap the feature coordinates to another coord system
798 # if a mapper was provided
803 if (defined $dest_slice && $mapper->isa(
'Bio::EnsEMBL::ChainedAssemblyMapper') ) {
804 ($seq_region_id, $seq_region_start, $seq_region_end, $seq_region_strand) =
805 $mapper->map($sr_name, $seq_region_start, $seq_region_end, $seq_region_strand, $sr_cs, 1, $dest_slice);
808 ($seq_region_id, $seq_region_start, $seq_region_end, $seq_region_strand) =
809 $mapper->fastmap($sr_name, $seq_region_start, $seq_region_end, $seq_region_strand, $sr_cs);
812 #skip features that map to gaps or coord system boundaries
813 next OPERON
if (!defined($seq_region_id));
815 #get a slice in the coord system we just mapped to
816 $slice = $slice_hash{
"ID:".$seq_region_id} ||= $sa->fetch_by_seq_region_id($seq_region_id);
820 # If a destination slice was provided convert the coords.
822 if (defined($dest_slice)) {
823 my $seq_region_len = $dest_slice->seq_region_length();
825 if ( $dest_slice_strand == 1 ) {
826 $seq_region_start = $seq_region_start - $dest_slice_start + 1;
827 $seq_region_end = $seq_region_end - $dest_slice_start + 1;
829 if ( $dest_slice->is_circular ) {
830 # Handle circular chromosomes.
832 if ( $seq_region_start > $seq_region_end ) {
833 # Looking at a feature overlapping the chromosome origin.
835 if ( $seq_region_end > $dest_slice_start ) {
836 # Looking at the region in the beginning of the chromosome
837 $seq_region_start -= $seq_region_len;
839 if ( $seq_region_end < 0 ) {
840 $seq_region_end += $seq_region_len;
843 if ($dest_slice_start > $dest_slice_end && $seq_region_end < 0) {
844 # Looking at the region overlapping the chromosome
845 # origin and a feature which is at the beginning of the
847 $seq_region_start += $seq_region_len;
848 $seq_region_end += $seq_region_len;
854 my $start = $dest_slice_end - $seq_region_end + 1;
855 my $end = $dest_slice_end - $seq_region_start + 1;
857 if ($dest_slice->is_circular()) {
859 if ($dest_slice_start > $dest_slice_end) {
860 # slice spans origin or replication
862 if ($seq_region_start >= $dest_slice_start) {
863 $end += $seq_region_len;
864 $start += $seq_region_len
if $seq_region_end > $dest_slice_start;
866 } elsif ($seq_region_start <= $dest_slice_end) {
868 } elsif ($seq_region_end >= $dest_slice_start) {
869 $start += $seq_region_len;
870 $end += $seq_region_len;
872 } elsif ($seq_region_end <= $dest_slice_end) {
873 $end += $seq_region_len
if $end < 0;
875 } elsif ($seq_region_start > $seq_region_end) {
876 $end += $seq_region_len;
881 if ($seq_region_start <= $dest_slice_end and $seq_region_end >= $dest_slice_start) {
883 } elsif ($seq_region_start > $seq_region_end) {
884 if ($seq_region_start <= $dest_slice_end) {
885 $start -= $seq_region_len;
886 } elsif ($seq_region_end >= $dest_slice_start) {
887 $end += $seq_region_len;
893 $seq_region_start = $start;
894 $seq_region_end = $end;
895 $seq_region_strand *= -1;
897 } ## end
else [
if ( $dest_slice_strand...)]
899 # Throw away features off the end of the requested slice or on
900 # different seq_region.
901 if ($seq_region_end < 1
902 || $seq_region_start > $dest_slice_length
903 || ($dest_slice_sr_id != $seq_region_id)) {
906 $slice = $dest_slice;
907 } ## end
if ($dest_slice)
911 -START => $seq_region_start,
912 -END => $seq_region_end,
913 -STRAND => $seq_region_strand,
915 -DISPLAY_LABEL => $display_label,
918 -STABLE_ID => $stable_id,
919 -VERSION => $version,
920 -CREATED_DATE => $created_date || undef,
921 -MODIFIED_DATE => $modified_date || undef,
922 -ANALYSIS => $analysis ) );
924 } ## end
while ( $sth->fetch() )
927 } ## end sub _objs_from_sth