Give implicit operators higher precedence in QP
authorJared Camins-Esakov <jcamins@cpbibliography.com>
Tue, 21 Aug 2012 17:53:49 +0000 (13:53 -0400)
committerJared Camins-Esakov <jcamins@cpbibliography.com>
Sun, 9 Sep 2012 19:16:03 +0000 (15:16 -0400)
When the implicit ANDs added by QueryParser between terms are combined
with explicit ORs without any explicit grouping, the results can be
unexpected and undesirable. For example, the following query:

  harry potter and the chamber of secrets || sorcerer's stone

Is translated into a query with two branches:
1. harry potter and the chamber of secrets stone
2. sorcerer's stone

This is of course nothing like what the user was expecting, which were
the following two branches:
1. harry potter and the chamber of secrets
2. sorcerer's stone

(Note: of course the user probably wanted to search for "harry potter
and the chamber of secrets" or "harry potter and the sorcerer's stone"
but we have to draw the line on implicit grouping somewhere, and where
it requires reading minds seems like a good place)

Signed-off-by: Jared Camins-Esakov <jcamins@cpbibliography.com>
Open-ILS/src/perlmods/lib/OpenILS/Application/Storage/QueryParser.pm

index 3afb9d7..630925b 100644 (file)
@@ -3,6 +3,7 @@ use warnings;
 
 package QueryParser;
 use OpenSRF::Utils::JSON;
+# Note that the first key must match the name of the package.
 our %parser_config = (
     QueryParser => {
         filters => [],
@@ -642,20 +643,48 @@ sub decompose {
             $last_type = '';
         } elsif (/$and_re/) { # ANDed expression
             $_ = $';
-            next if ($last_type eq 'AND');
-            next if ($last_type eq 'OR');
+            next unless (defined($struct->query_nodes) and scalar(@{$struct->query_nodes}));
             warn "Encountered AND\n" if $self->debug;
 
+            my $lhs = $self->new_plan(level =>
+                    $struct->plan_level + 1, query => $struct->query_nodes);
+            $struct->{query} = [];
+            my ($rhs, $rest) = $self->decompose( $', $current_class, $recursing + 1 );
+            $struct->add_node($lhs);
             $struct->joiner( '&' );
+            $struct->add_node($rhs);
+            foreach (@{$rhs->modifiers}) {
+                $struct->add_modifier($_)
+            }
+            $rhs->{modifiers} = [];
+            foreach (@{$rhs->filters}) {
+                $struct->add_filter($_);
+            }
+            $rhs->{filters} = [];
+            $_ = $rest;
 
             $last_type = 'AND';
         } elsif (/$or_re/) { # ORed expression
             $_ = $';
-            next if ($last_type eq 'AND');
-            next if ($last_type eq 'OR');
+            next unless (defined($struct->query_nodes) and scalar(@{$struct->query_nodes}));
             warn "Encountered OR\n" if $self->debug;
 
+            my $lhs = $self->new_plan(level =>
+                    $struct->plan_level + 1, query => $struct->query_nodes);
+            $struct->{query} = [];
+            my ($rhs, $rest) = $self->decompose( $', $current_class, $recursing + 1 );
+            $struct->add_node($lhs);
             $struct->joiner( '|' );
+            $struct->add_node($rhs);
+            foreach (@{$rhs->modifiers}) {
+                $struct->add_modifier($_)
+            }
+            $rhs->{modifiers} = [];
+            foreach (@{$rhs->filters}) {
+                $struct->add_filter($_);
+            }
+            $rhs->{filters} = [];
+            $_ = $rest;
 
             $last_type = 'OR';
         } elsif ($self->facet_class_count && /$facet_re/) { # changing current class