diff --git a/lib/Foswiki/Contrib/DBIStoreContrib/HoistSQL.pm b/lib/Foswiki/Contrib/DBIStoreContrib/HoistSQL.pm index c2fe082..b033535 100644 --- a/lib/Foswiki/Contrib/DBIStoreContrib/HoistSQL.pm +++ b/lib/Foswiki/Contrib/DBIStoreContrib/HoistSQL.pm @@ -127,7 +127,7 @@ sub _hoistB { my $rhs = _hoistC( $node->{params}[1], "${indent}r", 0 ); if ( $lhs && $rhs ) { print STDERR "${indent}L&R\n" if MONITOR; - return "($lhs) OR ($rhs)"; + return "(($lhs) OR ($rhs))"; } } else { diff --git a/lib/Foswiki/Contrib/DBIStoreContrib/Listener.pm b/lib/Foswiki/Contrib/DBIStoreContrib/Listener.pm index 31ae4ff..516b14c 100644 --- a/lib/Foswiki/Contrib/DBIStoreContrib/Listener.pm +++ b/lib/Foswiki/Contrib/DBIStoreContrib/Listener.pm @@ -59,6 +59,16 @@ sub _connect { return 1 if $this->{handle}; + if ($Foswiki::inUnitTestMode) { + # Change the DSN to a SQLite test db, which is held in the data + # area; that way it will be ripped down by -clean + $Foswiki::cfg{Extensions}{DBIStoreContrib}{DSN} = + $Foswiki::cfg{Extensions}{DBIStoreContrib}{DSN} = + "dbi:SQLite:dbname=$Foswiki::cfg{DataDir}/TemporarySQLiteCache"; + $Foswiki::cfg{Extensions}{DBIStoreContrib}{Username} = ''; + $Foswiki::cfg{Extensions}{DBIStoreContrib}{Password} = ''; + } + print STDERR "CONNECT $Foswiki::cfg{Extensions}{DBIStoreContrib}{DSN}..." if MONITOR; $this->{handle} = DBI->connect( diff --git a/lib/Foswiki/Store/QueryAlgorithms/DBIStoreContrib.pm b/lib/Foswiki/Store/QueryAlgorithms/DBIStoreContrib.pm index e2479ff..7272f31 100644 --- a/lib/Foswiki/Store/QueryAlgorithms/DBIStoreContrib.pm +++ b/lib/Foswiki/Store/QueryAlgorithms/DBIStoreContrib.pm @@ -3,6 +3,7 @@ =begin TML ---+ package Foswiki::Store::QueryAlgorithms::DBIStoreContrib +Implements Foswiki::Store::Interfaces::QueryAlgorithm =cut @@ -11,9 +12,11 @@ package Foswiki::Store::QueryAlgorithms::DBIStoreContrib; use strict; use warnings; -#@ISA = ( 'Foswiki::Query::QueryAlgorithms' ); # interface +#use Foswiki::Store::Interfaces::QueryAlgorithm (); +#@ISA = ( 'Foswiki::Store::Interfaces::QueryAlgorithm' ); use Foswiki::Search::Node (); +use Foswiki::Store::Interfaces::SearchAlgorithm (); use Foswiki::Meta (); use Foswiki::Search::InfoCache (); use Foswiki::Search::ResultSet (); @@ -22,7 +25,7 @@ use Foswiki::Query::Node (); use Foswiki::Contrib::DBIStoreContrib::Listener (); # Debug prints -use constant MONITOR => 1; +use constant MONITOR => 0; # See Foswiki::Query::QueryAlgorithms.pm for details sub query { @@ -40,8 +43,8 @@ sub query { my $webNames = $options->{web} || ''; my $recurse = $options->{'recurse'} || ''; my $searchAllFlag = ( $webNames =~ /(^|[\,\s])(all|on)([\,\s]|$)/i ); - my @webs = Foswiki::Search::InfoCache::_getListOfWebs( $webNames, $recurse, - $searchAllFlag ); + my @webs = Foswiki::Store::Interfaces::SearchAlgorithm::getListOfWebs( + $webNames, $recurse, $searchAllFlag ); my @interestingWebs; foreach my $web (@webs) { @@ -71,31 +74,34 @@ sub query { # can use to refine the topic set require Foswiki::Contrib::DBIStoreContrib::HoistSQL; - my $hoistedSQL = Foswiki::Contrib::DBIStoreContrib::HoistSQL::hoist($query) || 1; + my $hoistedSQL = Foswiki::Contrib::DBIStoreContrib::HoistSQL::hoist( + $query) || 1; if ($hoistedSQL) { - print STDERR "Hoisted '$hoistedSQL', remaining query: " . $query->stringify . "\n" - if MONITOR; + print STDERR "Hoisted '$hoistedSQL', remaining query: " + . $query->stringify . "\n" if MONITOR; # Did hoisting eliminate the dynamic query? if ($query->evaluatesToConstant()) { print STDERR "\t...eliminated static query\n" if MONITOR; + $query = undef; } } - my $sql = 'SELECT tid FROM topic WHERE ' . ( $hoistedSQL ? "$hoistedSQL AND " : '' ) . "topic.web IN (" . join( ',', map { "'$_'" } @interestingWebs ) . ')'; - if ( $interestingTopics && scalar(@$interestingTopics) ) { + if ( $interestingTopics && $interestingTopics->hasNext() ) { $sql .= " AND topic.name IN (" - . join( ',', map { "'$_'" } @$interestingTopics ) . ')'; + . join( ',', map { "'$_'" } $interestingTopics->all() ) . ')'; } # otherwise there is no topic name filter $sql .= ' ORDER BY web,name'; + print STDERR "Generated SQL: $sql\n" if MONITOR; + my $topicSet = Foswiki::Contrib::DBIStoreContrib::Listener::query( $session, $sql ); my $filter = Foswiki::Search::InfoCache::getOptionFilter($options); @@ -119,7 +125,7 @@ sub query { new Foswiki::Search::InfoCache($Foswiki::Plugins::SESSION); if ($query) { - print STDERR "Processing " . $meta->getPath() . "\n" if MONITOR; + print STDERR "Evaluating " . $meta->getPath() . "\n" if MONITOR; # this 'lazy load' will become useful when @$topics becomes # an infoCache @@ -138,9 +144,14 @@ sub query { } } + # We have to pre-sort the result sets by web name to mimic the + # behaviour of default search. my $resultset = - new Foswiki::Search::ResultSet( [values %results], $options->{groupby}, - $options->{order}, Foswiki::isTrue( $options->{reverse} ) ); + new Foswiki::Search::ResultSet( + [ map { $results{$_} } sort( keys( %results )) ], + $options->{groupby}, + $options->{order}, + Foswiki::isTrue( $options->{reverse} ) ); #TODO: $options should become redundant $resultset->sortResults($options); @@ -286,7 +297,7 @@ sub getField { } # Get a referenced topic -# See Foswiki::Store::QueryAlgorithms.pm for details +# See Foswiki::Store::Interfaces::QueryAlgorithms.pm for details sub getRefTopic { my ( $this, $relativeTo, $w, $t ) = @_; return Foswiki::Meta->load( $relativeTo->session, $w, $t ); diff --git a/lib/Foswiki/Store/SearchAlgorithms/DBIStoreContrib.pm b/lib/Foswiki/Store/SearchAlgorithms/DBIStoreContrib.pm index 1469b30..7149a93 100644 --- a/lib/Foswiki/Store/SearchAlgorithms/DBIStoreContrib.pm +++ b/lib/Foswiki/Store/SearchAlgorithms/DBIStoreContrib.pm @@ -1,63 +1,85 @@ # See bottom of file for license and copyright information package Foswiki::Store::SearchAlgorithms::DBIStoreContrib; +=begin TML + +---+ package Foswiki::Store::SearchAlgorithms::DBIStoreContrib +Implements Foswiki::Store::Interfaces::SearchAlgorithm + +DBI implementation of search. + +=cut + use strict; use Assert; use Foswiki::Search::InfoCache (); use Foswiki::Query::Parser (); +use Foswiki::Store::QueryAlgorithms::DBIStoreContrib (); + +#@ISA = ( 'Foswiki::Store::Interfaces::SearchAlgorithm' ); # Analyse the requirements of the search, and redirect to the query # algorithm. This is kinda like the reverse of hoisting regexes :-) +# Implements Foswiki::Store::Interfaces::SearchAlgorithm sub query { my ( $query, $inputTopicSet, $session, $options ) = @_; - if (( @{ $query->{tokens} } ) == 0) { + if ( $query->isEmpty() ) { return new Foswiki::Search::InfoCache($session, ''); } + print STDERR "Search ".$query->stringify()."\n" + if Foswiki::Store::QueryAlgorithms::DBIStoreContrib::MONITOR; + # Convert the search to a query # AND search - search once for each token, ANDing result together my @ands; - foreach my $token ( @{ $query->{tokens} } ) { + foreach my $token ( @{ $query->tokens() } ) { my $tokenCopy = $token; # flag for AND NOT search - my $invert = ( $tokenCopy =~ s/^\!//o ) ? 'NOT ' : ''; + my $invert = ( $tokenCopy =~ s/^\!// ) ? 'NOT ' : ''; - # scope can be 'topic' (default), 'text' or "all" + # scope can be 'topic', 'text' or "all" # scope='topic', e.g. Perl search on topic name: + $options->{scope} = 'text' unless defined $options->{'scope'}; + $options->{type} ||= 'literal'; + $options->{casesensitive} ||= 0; + + $tokenCopy = "\\b$tokenCopy\\b" if $options->{wordboundaries}; + my %topicMatches; my @ors; - if ( $options->{'scope'} =~ /^(topic|all)$/ ) { + if ( $options->{scope} ne 'text' ) { # topic or all my $expr = $tokenCopy; - $expr = quotemeta($expr) unless ( $options->{'type'} eq 'regex' ); - $expr = "(?i:$expr)" unless $options->{'casesensitive'}; + $expr = quotemeta($expr) unless ( $options->{type} eq 'regex' ); + $expr = "(?i:$expr)" unless $options->{casesensitive}; push(@ors, "${invert}name =~ '$expr'"); } # scope='text', e.g. grep search on topic text: - if ( $options->{'scope'} =~ /^(text|all)$/ ) { + if ( $options->{scope} ne 'topic' ) { # text or all my $expr = $tokenCopy; - $expr = quotemeta($expr) unless ( $options->{'type'} eq 'regex' ); - $expr = "(?i:$expr)" unless $options->{'casesensitive'}; + $expr = quotemeta($expr) unless ( $options->{type} eq 'regex' ); + $expr = "(?i:$expr)" unless $options->{casesensitive}; push(@ors, "${invert}raw =~ '$expr'"); } - push(@ands, '(' . join(' OR ', @ors) . ')'); + push(@ands, '(' . join($invert ? ' AND ' : ' OR ', @ors) . ')'); } my $queryParser = Foswiki::Query::Parser->new(); - $query = $queryParser->parse(join(' AND ', @ands)); - - eval "require $Foswiki::cfg{Store}{QueryAlgorithm}"; - die $@ if $@; - my $fn = $Foswiki::cfg{Store}{QueryAlgorithm}.'::query'; - no strict 'refs'; - return &$fn($query, $inputTopicSet, $session, $options); - use strict 'refs'; + my $search = join(' AND ', @ands); + print STDERR "Search generated query $search\n" + if Foswiki::Store::QueryAlgorithms::DBIStoreContrib::MONITOR; + + $query = $queryParser->parse($search); + + return Foswiki::Store::QueryAlgorithms::DBIStoreContrib::query( + $query, $inputTopicSet, $session, $options); } 1;