Class-DBI-ConceptSearch

 view release on metacpan or  search on metacpan

lib/Class/DBI/ConceptSearch.pm  view on Meta::CPAN

           this instance.  See CONFIGURATION


=cut

sub new {
  my($class,%arg) = @_;

  my $self = bless {}, $class;
  $self->_init(%arg);

  die(__PACKAGE__.' requires an "xml" argument.') unless $self->xml;

  return $self;
}

=head2 _init

 Title   : _init
 Usage   : $obj->_init(%arg);
 Function: internal method.  initializes a new Class::DBI::ConceptSearch object
 Returns : true on success
 Args    : args passed to new()


=cut

sub _init {
  my($self,%arg) = @_;

  foreach my $arg (keys %arg){
    $self->$arg($arg{$arg}) if $self->can($arg);
  }

  *Class::DBI::_do_search = sub {
	my ($proto, $search_type, @args) = @_;
	my $class = ref $proto || $proto;

	@args = %{ $args[0] } if ref $args[0] eq "HASH";
	my (@cols, @vals);
	my $search_opts = @args % 2 ? pop @args : {};
	while (my ($col, $val) = splice @args, 0, 2) {
		#this regex allows the field being searched to be transformed,
		#which can be useful for certain indexes, eg, in postgres:
		#  SELECT * FROM book WHERE lower(title) LIKE 'symbolic logic'
		#can use a functional index defined as:
		#  CREATE INDEX ON book(lower(title))
		#which performs much better than the ILIKE version of the same query:
		#  SELECT * FROM book WHERE title ILIKE 'symbolic logic';

		my($x,$y,$z) = $col =~ /^(.+\()(.+)(\))$/;
		$col = $y if $y;

		my $column = $class->find_column($col)
			|| (List::Util::first { $_->accessor eq $col } $class->columns)
			|| $class->_croak("$col is not a column of $class");
                push @cols, $y ? "$x$col$z" : $col;
		push @vals, $class->_deflated_column($column, $val);
	}

	my $frag = join " AND ",
		map defined($vals[$_]) ? "$cols[$_] $search_type ?" : "$cols[$_] IS NULL",
		0 .. $#cols;
	$frag .= " ORDER BY $search_opts->{order_by}"
		if $search_opts->{order_by};
	return $class->sth_to_objects($class->sql_Retrieve($frag),
		[ grep defined, @vals ]);
  };

  return 1;
}

=head2 search

  Title   : search
  Usage   : $cs->search(concept => 'gene', pattern => 'GH1');
  Function:
  Returns : a (possibly heterogenous) list of objects inheriting from
            Class::DBI.
  Args    : concept (required): conceptual domain to be searched
            pattern (required): pattern to match in each source
            table.field of concept search, as configured.  See CONFIGURATION


=cut

sub search {
  #FIXME: the pod doc for this sub says args should come in as a hash but here they are used as an array.
  my($self,$category,$pattern,$page_num) = @_;

  $page_num = 1 unless defined($page_num);

  return () unless defined($category) and defined($pattern);

  my $search_strategy;

  if(($pattern =~ /\*/s and $self->use_wildcards) or $self->use_implicit_wildcards){
    $pattern =~ s/\*/%/gs;

    $pattern = '%'.$pattern.'%' if $self->use_implicit_wildcards;
  }

  if($self->use_search_ilike){
    $search_strategy = 'search_ilike';
  } elsif($self->use_search_lower){
    $search_strategy = 'search_lower';
  } elsif($pattern =~ /%/) {
    $search_strategy = 'search_like';
  } else {
    $search_strategy = 'search';
  }

  my $config = XML::XPath->new( xml => $self->xml ) or die "couldn't instantiate XML::XPath: $!";

  my @concepts;
  my @hits;
  my @concept_hits =();
  my $page_size = 20;

  #find the page_size for Class::DBI objects that support paging
  foreach my $conceptsearch ($config->find('/conceptsearch')->get_nodelist){
    if(defined($conceptsearch->getAttribute('page_size'))) { $page_size = $conceptsearch->getAttribute('page_size'); }
  }

  #a driver to test the search
  warn "iterate over concepts using $search_strategy" if DEBUG;
  foreach my $concept ($config->find('/conceptsearch/concept')->get_nodelist){
    warn "concept: $category" if DEBUG;
    next unless $category eq $concept->getAttribute('name');
    warn "  searching..." if DEBUG;

    foreach my $source ($concept->find('source')->get_nodelist){
      my $sourceclass = $source->getAttribute('class');
      my $sourcefield = $source->getAttribute('field');

      warn "searching: $sourceclass.$sourcefield for '$pattern' with $search_strategy" if DEBUG;

      my @source_matches;
      # check if the targetclass is able to use the Class::DBI::Pager API
      if ($sourceclass->can("pager")) {
        my $pager = $sourceclass->pager($page_size,$page_num);
        $self->pager($pager);
        (@source_matches) = $pager->$search_strategy($sourcefield => $pattern);
      } else {
        (@source_matches) = $sourceclass->$search_strategy($sourcefield => $pattern);
      } 

      #my(@source_matches) = $sourceclass->$search_strategy( $sourcefield => $pattern );

      if(@source_matches){
        warn "xforms start" if DEBUG;

        foreach my $transform ($source->find('transform')->get_nodelist){
          warn "xform" if DEBUG;

          my $t_sourceclass = $transform->getAttribute('sourceclass'); #unused;
          my $t_sourcefield = $transform->getAttribute('sourcefield');
          my $t_targetclass = $transform->getAttribute('targetclass');
          my $t_targetfield = $transform->getAttribute('targetfield');

          my @t = ();

          foreach my $source_match (@source_matches){
            warn Data::Dumper::Dumper($source_match) if DEBUG;
            warn "$t_targetclass->search( $t_targetfield => ".$source_match->$t_sourcefield." );" if DEBUG;

            my $v =  ref($source_match->$t_sourcefield)
              ? $source_match->$t_sourcefield->id
              : scalar($source_match->$t_sourcefield);

            warn $v if DEBUG;

            # this call is fragile, handle it with care
            #
            # it would add power to allow search_like, search_ilike, or fuzzy searches (eg soundex) here
            # but requires extension of the xml format and *a lot* more code
            my @u = $t_targetclass->search( $t_targetfield => $v );
            push @t, @u;
          }
          @source_matches = @t;
        }

        push @concept_hits, @source_matches;
      }
      warn "xforms end" if DEBUG;
    }

    my %unique_hits = ();
    $unique_hits{ref($_).'_'.$_->id} = $_ foreach @concept_hits;
    push @hits, values %unique_hits;
  }
  # FIXME: should I close the db connection here???
  return @hits;
}

=head2 pager

  Title   : pager
  Usage   : $obj->pager($newval)
  Function: sets/returns the pager object, useful for getting information
            about the complete set of results
  Returns : value of pager
  Args    : on set, new value (a scalar or undef, optional)


=cut

sub pager {
  my $self = shift;

  return $self->{'pager'} = shift if @_;
  return $self->{'pager'};
}

=head2 use_wildcards

  Title   : use_wildcards
  Usage   : $obj->use_wildcards($newval)
  Function: when true, enables search_like/search_ilike from
            search()
  Returns : value of use_wildcards (a scalar)
  Args    : on set, new value (a scalar or undef, optional)


=cut

sub use_wildcards {
  my $self = shift;

  return $self->{'use_wildcards'} = shift if @_;
  return $self->{'use_wildcards'};
}



( run in 2.212 seconds using v1.01-cache-2.11-cpan-364913b4093 )