From 287a3e2052f5699daacddb7c536b6a15631fcf16 Mon Sep 17 00:00:00 2001 From: Bill Erickson Date: Mon, 21 May 2012 10:32:56 -0400 Subject: [PATCH] Query Parser merged copy / non-dynamic filters When more than one copy-level filter (e.g. locations()) is used in a query-parser query, all but the first are ignored. The goal of this work is to compress multiple copy-level filters into a single filter that represents the full collection. Wherever possible, boolean structures will be honored, to the extent possible for post-search copy-level filters. Examples: concerto locations(1,2) locations(3,4) ==> concerto locations(1,2,3,4) ( concerto locations(3,4,5) ) || (piano locations(3,5,7) ) ==> ( concerto || piano ) locations(3,4,5,6,7) ( concerto locations(3,4,5) ) && ( piano locations(3,5,7) ) ==> concerto piano locations(3,5) Note, in the last 2 examples, the final query does not exactly match the original. This is because copy-level filters are applied after the initial search and cannot be executed as part of the nested query. The best we can do is create a representation of the final copy-level filter, based on the nesting, and apply it to the final result of the nested search. Signed-off-by: Bill Erickson Signed-off-by: Mike Rylander --- .../lib/OpenILS/Application/Storage/QueryParser.pm | 72 +++++++++++++++++++++- 1 file changed, 70 insertions(+), 2 deletions(-) diff --git a/Open-ILS/src/perlmods/lib/OpenILS/Application/Storage/QueryParser.pm b/Open-ILS/src/perlmods/lib/OpenILS/Application/Storage/QueryParser.pm index d5e12a46fa..4cb51ed3bc 100644 --- a/Open-ILS/src/perlmods/lib/OpenILS/Application/Storage/QueryParser.pm +++ b/Open-ILS/src/perlmods/lib/OpenILS/Application/Storage/QueryParser.pm @@ -971,11 +971,80 @@ sub new_filter { return $node; } + +sub _merge_filters { + my $left_filter = shift; + my $right_filter = shift; + my $join = shift; + + return undef unless $left_filter or $right_filter; + return $right_filter unless $left_filter; + return $left_filter unless $right_filter; + + my $args = $left_filter->{args} || []; + + if ($join eq '|') { + push(@$args, @{$right_filter->{args}}); + + } else { + # find the intersect values + my %new_vals; + map { $new_vals{$_} = 1 } @{$right_filter->{args} || []}; + $args = [ grep { $new_vals{$_} } @$args ]; + } + + $left_filter->{args} = $args; + return $left_filter; +} + +sub collapse_filters { + my $self = shift; + my $name = shift; + + # start by merging any filters at this level. + # like-level filters are always ORed together + + my $cur_filter; + my @cur_filters = grep {$_->name eq $name } @{ $self->filters }; + if (@cur_filters) { + $cur_filter = shift @cur_filters; + my $args = $cur_filter->{args} || []; + $cur_filter = _merge_filters($cur_filter, $_, '|') for @cur_filters; + } + + # next gather the collapsed filters from sub-plans and + # merge them with our own + + my @subquery = @{$self->{query}}; + + while (@subquery) { + my $blob = shift @subquery; + shift @subquery; # joiner + next unless $blob->isa('QueryParser::query_plan'); + my $sub_filter = $blob->collapse_filters($name); + $cur_filter = _merge_filters($cur_filter, $sub_filter, $self->joiner); + } + + if ($self->QueryParser->debug) { + my @args = ($cur_filter and $cur_filter->{args}) ? @{$cur_filter->{args}} : (); + warn "collapse_filters($name) => [@args]\n"; + } + + return $cur_filter; +} + sub find_filter { my $self = shift; my $needle = shift;; return undef unless ($needle); - return grep { $_->name eq $needle } @{ $self->filters }; + + my $filter = $self->collapse_filters($needle); + + warn "find_filter($needle) => " . + (($filter and $filter->{args}) ? "@{$filter->{args}}" : '[]') . "\n" + if $self->QueryParser->debug; + + return $filter ? ($filter) : (); } sub find_modifier { @@ -1109,7 +1178,6 @@ sub add_filter { my $filter = shift; $self->{filters} ||= []; - $self->{filters} = [ grep {$_->name ne $filter->name} @{$self->{filters}} ]; push(@{$self->{filters}}, $filter); -- 2.11.0