2 use OpenSRF::Utils::JSON;
18 sub facet_class_count {
20 return @{$self->facet_classes};
23 sub search_class_count {
25 return @{$self->search_classes};
30 return @{$self->filters};
35 return @{$self->modifiers};
40 $class = ref($class) || $class;
42 $parser_config{$class}{custom_data} ||= {};
43 return $parser_config{$class}{custom_data};
48 $class = ref($class) || $class;
50 $parser_config{$class}{operators} ||= {};
51 return $parser_config{$class}{operators};
56 $class = ref($class) || $class;
58 $parser_config{$class}{filters} ||= [];
59 return $parser_config{$class}{filters};
64 $class = ref($class) || $class;
66 $parser_config{$class}{modifiers} ||= [];
67 return $parser_config{$class}{modifiers};
72 $class = ref($class) || $class;
76 my $self = bless {} => $class;
78 for my $o (keys %{QueryParser->operators}) {
79 $class->operator($o => QueryParser->operator($o)) unless ($class->operator($o));
82 for my $opt ( keys %opts) {
83 $self->$opt( $opts{$opt} ) if ($self->can($opt));
91 my $pkg = ref($self) || $self;
92 return do{$pkg.'::query_plan'}->new( QueryParser => $self, @_ );
95 sub add_search_filter {
97 $pkg = ref($pkg) || $pkg;
100 return $filter if (grep { $_ eq $filter } @{$pkg->filters});
101 push @{$pkg->filters}, $filter;
105 sub add_search_modifier {
107 $pkg = ref($pkg) || $pkg;
108 my $modifier = shift;
110 return $modifier if (grep { $_ eq $modifier } @{$pkg->modifiers});
111 push @{$pkg->modifiers}, $modifier;
115 sub add_facet_class {
117 $pkg = ref($pkg) || $pkg;
120 return $class if (grep { $_ eq $class } @{$pkg->facet_classes});
122 push @{$pkg->facet_classes}, $class;
123 $pkg->facet_fields->{$class} = [];
128 sub add_search_class {
130 $pkg = ref($pkg) || $pkg;
133 return $class if (grep { $_ eq $class } @{$pkg->search_classes});
135 push @{$pkg->search_classes}, $class;
136 $pkg->search_fields->{$class} = [];
137 $pkg->default_search_class( $pkg->search_classes->[0] ) if (@{$pkg->search_classes} == 1);
144 $class = ref($class) || $class;
148 return undef unless ($opname);
150 $parser_config{$class}{operators} ||= {};
151 $parser_config{$class}{operators}{$opname} = $op if ($op);
153 return $parser_config{$class}{operators}{$opname};
158 $class = ref($class) || $class;
161 $parser_config{$class}{facet_classes} ||= [];
162 $parser_config{$class}{facet_classes} = $classes if (ref($classes) && @$classes);
163 return $parser_config{$class}{facet_classes};
168 $class = ref($class) || $class;
171 $parser_config{$class}{classes} ||= [];
172 $parser_config{$class}{classes} = $classes if (ref($classes) && @$classes);
173 return $parser_config{$class}{classes};
176 sub add_query_normalizer {
178 $pkg = ref($pkg) || $pkg;
182 my $params = shift || [];
184 # do not add if function AND params are identical to existing member
185 return $func if (grep {
186 $_->{function} eq $func and
187 OpenSRF::Utils::JSON->perl2JSON($_->{params}) eq OpenSRF::Utils::JSON->perl2JSON($params)
188 } @{$pkg->query_normalizers->{$class}->{$field}});
190 push(@{$pkg->query_normalizers->{$class}->{$field}}, { function => $func, params => $params });
195 sub query_normalizers {
197 $pkg = ref($pkg) || $pkg;
202 $parser_config{$pkg}{normalizers} ||= {};
205 $parser_config{$pkg}{normalizers}{$class}{$field} ||= [];
206 return $parser_config{$pkg}{normalizers}{$class}{$field};
208 return $parser_config{$pkg}{normalizers}{$class};
212 return $parser_config{$pkg}{normalizers};
215 sub add_filter_normalizer {
217 $pkg = ref($pkg) || $pkg;
220 my $params = shift || [];
222 return $func if (grep { $_ eq $func } @{$pkg->filter_normalizers->{$filter}});
224 push(@{$pkg->filter_normalizers->{$filter}}, { function => $func, params => $params });
229 sub filter_normalizers {
231 $pkg = ref($pkg) || $pkg;
235 $parser_config{$pkg}{filter_normalizers} ||= {};
237 $parser_config{$pkg}{filter_normalizers}{$filter} ||= [];
238 return $parser_config{$pkg}{filter_normalizers}{$filter};
241 return $parser_config{$pkg}{filter_normalizers};
244 sub default_search_class {
246 $pkg = ref($pkg) || $pkg;
248 $QueryParser::parser_config{$pkg}{default_class} = $pkg->add_search_class( $class ) if $class;
250 return $QueryParser::parser_config{$pkg}{default_class};
253 sub remove_facet_class {
255 $pkg = ref($pkg) || $pkg;
258 return $class if (!grep { $_ eq $class } @{$pkg->facet_classes});
260 $pkg->facet_classes( [ grep { $_ ne $class } @{$pkg->facet_classes} ] );
261 delete $QueryParser::parser_config{$pkg}{facet_fields}{$class};
266 sub remove_search_class {
268 $pkg = ref($pkg) || $pkg;
271 return $class if (!grep { $_ eq $class } @{$pkg->search_classes});
273 $pkg->search_classes( [ grep { $_ ne $class } @{$pkg->search_classes} ] );
274 delete $QueryParser::parser_config{$pkg}{fields}{$class};
279 sub add_facet_field {
281 $pkg = ref($pkg) || $pkg;
285 $pkg->add_facet_class( $class );
287 return { $class => $field } if (grep { $_ eq $field } @{$pkg->facet_fields->{$class}});
289 push @{$pkg->facet_fields->{$class}}, $field;
291 return { $class => $field };
296 $class = ref($class) || $class;
298 $parser_config{$class}{facet_fields} ||= {};
299 return $parser_config{$class}{facet_fields};
302 sub add_search_field {
304 $pkg = ref($pkg) || $pkg;
308 $pkg->add_search_class( $class );
310 return { $class => $field } if (grep { $_ eq $field } @{$pkg->search_fields->{$class}});
312 push @{$pkg->search_fields->{$class}}, $field;
314 return { $class => $field };
319 $class = ref($class) || $class;
321 $parser_config{$class}{fields} ||= {};
322 return $parser_config{$class}{fields};
325 sub add_search_class_alias {
327 $pkg = ref($pkg) || $pkg;
331 $pkg->add_search_class( $class );
333 return { $class => $alias } if (grep { $_ eq $alias } @{$pkg->search_class_aliases->{$class}});
335 push @{$pkg->search_class_aliases->{$class}}, $alias;
337 return { $class => $alias };
340 sub search_class_aliases {
342 $class = ref($class) || $class;
344 $parser_config{$class}{class_map} ||= {};
345 return $parser_config{$class}{class_map};
348 sub add_search_field_alias {
350 $pkg = ref($pkg) || $pkg;
355 return { $class => { $field => $alias } } if (grep { $_ eq $alias } @{$pkg->search_field_aliases->{$class}{$field}});
357 push @{$pkg->search_field_aliases->{$class}{$field}}, $alias;
359 return { $class => { $field => $alias } };
362 sub search_field_aliases {
364 $class = ref($class) || $class;
366 $parser_config{$class}{field_alias_map} ||= {};
367 return $parser_config{$class}{field_alias_map};
370 sub remove_facet_field {
372 $pkg = ref($pkg) || $pkg;
376 return { $class => $field } if (!$pkg->facet_fields->{$class} || !grep { $_ eq $field } @{$pkg->facet_fields->{$class}});
378 $pkg->facet_fields->{$class} = [ grep { $_ ne $field } @{$pkg->facet_fields->{$class}} ];
380 return { $class => $field };
383 sub remove_search_field {
385 $pkg = ref($pkg) || $pkg;
389 return { $class => $field } if (!$pkg->search_fields->{$class} || !grep { $_ eq $field } @{$pkg->search_fields->{$class}});
391 $pkg->search_fields->{$class} = [ grep { $_ ne $field } @{$pkg->search_fields->{$class}} ];
393 return { $class => $field };
396 sub remove_search_field_alias {
398 $pkg = ref($pkg) || $pkg;
403 return { $class => { $field => $alias } } if (!$pkg->search_field_aliases->{$class}{$field} || !grep { $_ eq $alias } @{$pkg->search_field_aliases->{$class}{$field}});
405 $pkg->search_field_aliases->{$class}{$field} = [ grep { $_ ne $alias } @{$pkg->search_field_aliases->{$class}{$field}} ];
407 return { $class => { $field => $alias } };
410 sub remove_search_class_alias {
412 $pkg = ref($pkg) || $pkg;
416 return { $class => $alias } if (!$pkg->search_class_aliases->{$class} || !grep { $_ eq $alias } @{$pkg->search_class_aliases->{$class}});
418 $pkg->search_class_aliases->{$class} = [ grep { $_ ne $alias } @{$pkg->search_class_aliases->{$class}} ];
420 return { $class => $alias };
426 $self->{_debug} = $q if (defined $q);
427 return $self->{_debug};
433 $self->{_query} = $q if (defined $q);
434 return $self->{_query};
440 $self->{_parse_tree} = $q if (defined $q);
441 return $self->{_parse_tree};
446 my $pkg = ref($self) || $self;
447 warn " ** parse package is $pkg\n" if $self->debug;
450 $self->query( shift() )
459 my $pkg = ref($self) || $self;
461 warn " ** decompose package is $pkg\n" if $self->debug;
464 my $current_class = shift || $self->default_search_class;
466 my $recursing = shift || 0;
468 # Build the search class+field uber-regexp
469 my $search_class_re = '^\s*(';
473 for my $class ( keys %{$pkg->search_fields} ) {
475 for my $field ( @{$pkg->search_fields->{$class}} ) {
477 for my $alias ( @{$pkg->search_field_aliases->{$class}{$field}} ) {
479 s/(^|\s+)$alias[:=]/$1$class\|$field:/g;
483 $search_class_re .= '|' unless ($first_class);
485 $search_class_re .= $class . '(?:\|\w+)*';
486 $seen_classes{$class} = 1;
489 for my $class ( keys %{$pkg->search_class_aliases} ) {
491 for my $alias ( @{$pkg->search_class_aliases->{$class}} ) {
493 s/(^|[^|])\b$alias\|/$1$class\|/g;
494 s/(^|[^|])\b$alias[:=]/$1$class:/g;
497 $search_class_re .= '|' unless ($first_class);
500 $search_class_re .= $class . '(?:\|\w+)*' if (!$seen_classes{$class});
501 $seen_classes{$class} = 1;
503 $search_class_re .= '):';
505 warn " ** Search class RE: $search_class_re\n" if $self->debug;
507 my $required_re = $pkg->operator('required');
508 $required_re = qr/^\s*\Q$required_re\E/;
509 my $and_re = $pkg->operator('and');
510 $and_re = qr/^\s*\Q$and_re\E/;
512 my $or_re = $pkg->operator('or');
513 $or_re = qr/^\s*\Q$or_re\E/;
515 my $group_start_re = $pkg->operator('group_start');
516 $group_start_re = qr/^\s*\Q$group_start_re\E/;
518 my $group_end = $pkg->operator('group_end');
519 my $group_end_re = qr/^\s*\Q$group_end\E/;
521 my $modifier_tag_re = $pkg->operator('modifier');
522 $modifier_tag_re = qr/^\s*\Q$modifier_tag_re\E/;
525 # Build the filter and modifier uber-regexps
526 my $facet_re = '^\s*((?:' . join( '|', @{$pkg->facet_classes}) . ')(?:\|\w+)*)\[(.+?)\]';
527 warn " Facet RE: $facet_re\n" if $self->debug;
529 my $filter_re = '^\s*(' . join( '|', @{$pkg->filters}) . ')\(([^()]+)\)';
530 my $filter_as_class_re = '^\s*(' . join( '|', @{$pkg->filters}) . '):\s*(\S+)';
532 my $modifier_re = '^\s*'.$modifier_tag_re.'(' . join( '|', @{$pkg->modifiers}) . ')\b';
533 my $modifier_as_class_re = '^\s*(' . join( '|', @{$pkg->modifiers}) . '):\s*(\S+)';
535 my $struct = $self->new_plan( level => $recursing );
539 while (!$remainder) {
540 if (/^\s*$/) { # end of an explicit group
542 } elsif (/$group_end_re/) { # end of an explicit group
543 warn "Encountered explicit group end\n" if $self->debug;
549 } elsif ($self->filter_count && /$filter_re/) { # found a filter
550 warn "Encountered search filter: $1 set to $2\n" if $self->debug;
553 $struct->new_filter( $1 => [ split '[, ]+', $2 ] );
556 } elsif ($self->filter_count && /$filter_as_class_re/) { # found a filter
557 warn "Encountered search filter: $1 set to $2\n" if $self->debug;
560 $struct->new_filter( $1 => [ split '[, ]+', $2 ] );
563 } elsif ($self->modifier_count && /$modifier_re/) { # found a modifier
564 warn "Encountered search modifier: $1\n" if $self->debug;
567 if (!$struct->top_plan) {
568 warn " Search modifiers only allowed at the top level of the query\n" if $self->debug;
570 $struct->new_modifier($1);
574 } elsif ($self->modifier_count && /$modifier_as_class_re/) { # found a modifier
575 warn "Encountered search modifier: $1\n" if $self->debug;
580 if (!$struct->top_plan) {
581 warn " Search modifiers only allowed at the top level of the query\n" if $self->debug;
582 } elsif ($2 =~ /^[ty1]/i) {
583 $struct->new_modifier($mod);
587 } elsif (/$group_start_re/) { # start of an explicit group
588 warn "Encountered explicit group start\n" if $self->debug;
590 my ($substruct, $subremainder) = $self->decompose( $', $current_class, $recursing + 1 );
591 $struct->add_node( $substruct );
595 } elsif (/$and_re/) { # ANDed expression
597 next if ($last_type eq 'AND');
598 next if ($last_type eq 'OR');
599 warn "Encountered AND\n" if $self->debug;
601 $struct->joiner( '&' );
604 } elsif (/$or_re/) { # ORed expression
606 next if ($last_type eq 'AND');
607 next if ($last_type eq 'OR');
608 warn "Encountered OR\n" if $self->debug;
610 $struct->joiner( '|' );
613 } elsif ($self->facet_class_count && /$facet_re/) { # changing current class
614 warn "Encountered facet: $1 => $2\n" if $self->debug;
617 my $facet_value = [ split '\s*#\s*', $2 ];
618 $struct->new_facet( $facet => $facet_value );
622 } elsif ($self->search_class_count && /$search_class_re/) { # changing current class
624 if ($last_type eq 'CLASS') {
625 $struct->remove_last_node( $current_class );
626 warn "Encountered class change with no searches!\n" if $self->debug;
629 warn "Encountered class change: $1\n" if $self->debug;
632 $struct->classed_node( $current_class );
635 $last_type = 'CLASS';
636 } elsif (/^\s*"([^"]+)"/) { # phrase, always anded
637 warn "Encountered phrase: $1\n" if $self->debug;
639 $struct->joiner( '&' );
642 my $class_node = $struct->classed_node($current_class);
643 $class_node->add_phrase( $phrase );
647 } elsif (/$required_re([^\s)]+)/) { # phrase, always anded
648 warn "Encountered required atom (mini phrase): $1\n" if $self->debug;
652 my $class_node = $struct->classed_node($current_class);
653 $class_node->add_phrase( $phrase );
655 $struct->joiner( '&' );
658 } elsif (/^\s*([^$group_end\s]+)/o) { # atom
659 warn "Encountered atom: $1\n" if $self->debug;
660 warn "Remainder: $'\n" if $self->debug;
668 my $negator = ($atom =~ s/^-//o) ? '!' : '';
669 my $truncate = ($atom =~ s/\*$//o) ? '*' : '';
671 if (!grep { $atom eq $_ } ('&','|')) { # throw away & and |, not allowed in tsquery, and not really useful anyway
672 my $class_node = $struct->classed_node($current_class);
673 $class_node->add_fts_atom( $atom, suffix => $truncate, prefix => $negator, node => $class_node );
674 $struct->joiner( '&' );
682 return $struct if !wantarray;
683 return ($struct, $remainder);
686 sub find_class_index {
690 my ($class_part, @field_parts) = split '\|', $class;
691 $class_part ||= $class;
693 for my $idx ( 0 .. scalar(@$query) - 1 ) {
694 next unless ref($$query[$idx]);
695 return $idx if ( $$query[$idx]{requested_class} && $class eq $$query[$idx]{requested_class} );
698 push(@$query, { classname => $class_part, (@field_parts ? (fields => \@field_parts) : ()), requested_class => $class, ftsquery => [], phrases => [] });
705 $self->{core_limit} = $l if ($l);
706 return $self->{core_limit};
712 $self->{superpage} = $l if ($l);
713 return $self->{superpage};
719 $self->{superpage_size} = $l if ($l);
720 return $self->{superpage_size};
724 #-------------------------------
725 package QueryParser::query_plan;
729 return undef unless ref($self);
730 return $self->{QueryParser};
735 $pkg = ref($pkg) || $pkg;
736 my %args = (query => [], joiner => '&', @_);
738 return bless \%args => $pkg;
743 my $pkg = ref($self) || $self;
744 my $node = do{$pkg.'::node'}->new( plan => $self, @_ );
745 $self->add_node( $node );
751 my $pkg = ref($self) || $self;
755 my $node = do{$pkg.'::facet'}->new( plan => $self, name => $name, 'values' => $args );
756 $self->add_node( $node );
763 my $pkg = ref($self) || $self;
767 my $node = do{$pkg.'::filter'}->new( plan => $self, name => $name, args => $args );
768 $self->add_filter( $node );
776 return undef unless ($needle);
777 return grep { $_->name eq $needle } @{ $self->filters };
783 return undef unless ($needle);
784 return grep { $_->name eq $needle } @{ $self->modifiers };
789 my $pkg = ref($self) || $self;
792 my $node = do{$pkg.'::modifier'}->new( $name );
793 $self->add_modifier( $node );
800 my $requested_class = shift;
803 for my $n (@{$self->{query}}) {
804 next unless (ref($n) && $n->isa( 'QueryParser::query_plan::node' ));
805 if ($n->requested_class eq $requested_class) {
812 $node = $self->new_node;
813 $node->requested_class( $requested_class );
819 sub remove_last_node {
821 my $requested_class = shift;
823 my $old = pop(@{$self->query_nodes});
824 pop(@{$self->query_nodes}) if (@{$self->query_nodes});
831 return $self->{query};
838 $self->{query} ||= [];
839 push(@{$self->{query}}, $self->joiner) if (@{$self->{query}});
840 push(@{$self->{query}}, $node);
848 return $self->{level} ? 0 : 1;
853 return $self->{level};
860 $self->{joiner} = $joiner if ($joiner);
861 return $self->{joiner};
866 $self->{modifiers} ||= [];
867 return $self->{modifiers};
872 my $modifier = shift;
874 $self->{modifiers} ||= [];
875 return $self if (grep {$$_ eq $$modifier} @{$self->{modifiers}});
877 push(@{$self->{modifiers}}, $modifier);
884 $self->{facets} ||= [];
885 return $self->{facets};
892 $self->{facets} ||= [];
893 return $self if (grep {$_->name eq $facet->name} @{$self->{facets}});
895 push(@{$self->{facets}}, $facet);
902 $self->{filters} ||= [];
903 return $self->{filters};
910 $self->{filters} ||= [];
911 return $self if (grep {$_->name eq $filter->name} @{$self->{filters}});
913 push(@{$self->{filters}}, $filter);
919 #-------------------------------
920 package QueryParser::query_plan::node;
924 $pkg = ref($pkg) || $pkg;
927 return bless \%args => $pkg;
932 my $pkg = ref($self) || $self;
933 return do{$pkg.'::atom'}->new( @_ );
936 sub requested_class { # also split into classname and fields
941 my ($class_part, @field_parts) = split '\|', $class;
942 $class_part ||= $class;
944 $self->{requested_class} = $class;
945 $self->{classname} = $class_part;
946 $self->{fields} = \@field_parts;
949 return $self->{requested_class};
956 $self->{plan} = $plan if ($plan);
957 return $self->{plan};
964 $self->{classname} = $class if ($class);
965 return $self->{classname};
972 $self->{fields} ||= [];
973 $self->{fields} = \@fields if (@fields);
974 return $self->{fields};
981 $self->{phrases} ||= [];
982 $self->{phrases} = \@phrases if (@phrases);
983 return $self->{phrases};
990 push(@{$self->phrases}, $phrase);
997 my @query_atoms = @_;
999 $self->{query_atoms} ||= [];
1000 $self->{query_atoms} = \@query_atoms if (@query_atoms);
1001 return $self->{query_atoms};
1009 my $content = $atom;
1012 $atom = $self->new_atom( content => $content, @parts );
1015 push(@{$self->query_atoms}, $self->plan->joiner) if (@{$self->query_atoms});
1016 push(@{$self->query_atoms}, $atom);
1021 #-------------------------------
1022 package QueryParser::query_plan::node::atom;
1026 $pkg = ref($pkg) || $pkg;
1029 return bless \%args => $pkg;
1034 return undef unless (ref $self);
1035 return $self->{node};
1040 return undef unless (ref $self);
1041 return $self->{content};
1046 return undef unless (ref $self);
1047 return $self->{prefix};
1052 return undef unless (ref $self);
1053 return $self->{suffix};
1056 #-------------------------------
1057 package QueryParser::query_plan::filter;
1061 $pkg = ref($pkg) || $pkg;
1064 return bless \%args => $pkg;
1069 return $self->{plan};
1074 return $self->{name};
1079 return $self->{args};
1082 #-------------------------------
1083 package QueryParser::query_plan::facet;
1087 $pkg = ref($pkg) || $pkg;
1090 return bless \%args => $pkg;
1095 return $self->{plan};
1100 return $self->{name};
1105 return $self->{'values'};
1108 #-------------------------------
1109 package QueryParser::query_plan::modifier;
1113 $pkg = ref($pkg) || $pkg;
1114 my $modifier = shift;
1116 return bless \$modifier => $pkg;