17 sub facet_class_count {
19 return @{$self->facet_classes};
22 sub search_class_count {
24 return @{$self->search_classes};
29 return @{$self->filters};
34 return @{$self->modifiers};
39 $class = ref($class) || $class;
41 $parser_config{$class}{custom_data} ||= {};
42 return $parser_config{$class}{custom_data};
47 $class = ref($class) || $class;
49 $parser_config{$class}{operators} ||= {};
50 return $parser_config{$class}{operators};
55 $class = ref($class) || $class;
57 $parser_config{$class}{filters} ||= [];
58 return $parser_config{$class}{filters};
63 $class = ref($class) || $class;
65 $parser_config{$class}{modifiers} ||= [];
66 return $parser_config{$class}{modifiers};
71 $class = ref($class) || $class;
75 my $self = bless {} => $class;
77 for my $o (keys %{QueryParser->operators}) {
78 $class->operator($o => QueryParser->operator($o)) unless ($class->operator($o));
81 for my $opt ( keys %opts) {
82 $self->$opt( $opts{$opt} ) if ($self->can($opt));
90 my $pkg = ref($self) || $self;
91 return do{$pkg.'::query_plan'}->new( QueryParser => $self, @_ );
94 sub add_search_filter {
96 $pkg = ref($pkg) || $pkg;
99 return $filter if (grep { $_ eq $filter } @{$pkg->filters});
100 push @{$pkg->filters}, $filter;
104 sub add_search_modifier {
106 $pkg = ref($pkg) || $pkg;
107 my $modifier = shift;
109 return $modifier if (grep { $_ eq $modifier } @{$pkg->modifiers});
110 push @{$pkg->modifiers}, $modifier;
114 sub add_facet_class {
116 $pkg = ref($pkg) || $pkg;
119 return $class if (grep { $_ eq $class } @{$pkg->facet_classes});
121 push @{$pkg->facet_classes}, $class;
122 $pkg->facet_fields->{$class} = [];
127 sub add_search_class {
129 $pkg = ref($pkg) || $pkg;
132 return $class if (grep { $_ eq $class } @{$pkg->search_classes});
134 push @{$pkg->search_classes}, $class;
135 $pkg->search_fields->{$class} = [];
136 $pkg->default_search_class( $pkg->search_classes->[0] ) if (@{$pkg->search_classes} == 1);
143 $class = ref($class) || $class;
147 return undef unless ($opname);
149 $parser_config{$class}{operators} ||= {};
150 $parser_config{$class}{operators}{$opname} = $op if ($op);
152 return $parser_config{$class}{operators}{$opname};
157 $class = ref($class) || $class;
160 $parser_config{$class}{facet_classes} ||= [];
161 $parser_config{$class}{facet_classes} = $classes if (ref($classes) && @$classes);
162 return $parser_config{$class}{facet_classes};
167 $class = ref($class) || $class;
170 $parser_config{$class}{classes} ||= [];
171 $parser_config{$class}{classes} = $classes if (ref($classes) && @$classes);
172 return $parser_config{$class}{classes};
175 sub add_query_normalizer {
177 $pkg = ref($pkg) || $pkg;
181 my $params = shift || [];
183 return $func if (grep { $_ eq $func } @{$pkg->query_normalizers->{$class}->{$field}});
185 push(@{$pkg->query_normalizers->{$class}->{$field}}, { function => $func, params => $params });
190 sub query_normalizers {
192 $pkg = ref($pkg) || $pkg;
197 $parser_config{$pkg}{normalizers} ||= {};
200 $parser_config{$pkg}{normalizers}{$class}{$field} ||= [];
201 return $parser_config{$pkg}{normalizers}{$class}{$field};
203 return $parser_config{$pkg}{normalizers}{$class};
207 return $parser_config{$pkg}{normalizers};
210 sub add_filter_normalizer {
212 $pkg = ref($pkg) || $pkg;
215 my $params = shift || [];
217 return $func if (grep { $_ eq $func } @{$pkg->filter_normalizers->{$filter}});
219 push(@{$pkg->filter_normalizers->{$filter}}, { function => $func, params => $params });
224 sub filter_normalizers {
226 $pkg = ref($pkg) || $pkg;
230 $parser_config{$pkg}{filter_normalizers} ||= {};
232 $parser_config{$pkg}{filter_normalizers}{$filter} ||= [];
233 return $parser_config{$pkg}{filter_normalizers}{$filter};
236 return $parser_config{$pkg}{filter_normalizers};
239 sub default_search_class {
241 $pkg = ref($pkg) || $pkg;
243 $QueryParser::parser_config{$pkg}{default_class} = $pkg->add_search_class( $class ) if $class;
245 return $QueryParser::parser_config{$pkg}{default_class};
248 sub remove_facet_class {
250 $pkg = ref($pkg) || $pkg;
253 return $class if (!grep { $_ eq $class } @{$pkg->facet_classes});
255 $pkg->facet_classes( [ grep { $_ ne $class } @{$pkg->facet_classes} ] );
256 delete $QueryParser::parser_config{$pkg}{facet_fields}{$class};
261 sub remove_search_class {
263 $pkg = ref($pkg) || $pkg;
266 return $class if (!grep { $_ eq $class } @{$pkg->search_classes});
268 $pkg->search_classes( [ grep { $_ ne $class } @{$pkg->search_classes} ] );
269 delete $QueryParser::parser_config{$pkg}{fields}{$class};
274 sub add_facet_field {
276 $pkg = ref($pkg) || $pkg;
280 $pkg->add_facet_class( $class );
282 return { $class => $field } if (grep { $_ eq $field } @{$pkg->facet_fields->{$class}});
284 push @{$pkg->facet_fields->{$class}}, $field;
286 return { $class => $field };
291 $class = ref($class) || $class;
293 $parser_config{$class}{facet_fields} ||= {};
294 return $parser_config{$class}{facet_fields};
297 sub add_search_field {
299 $pkg = ref($pkg) || $pkg;
303 $pkg->add_search_class( $class );
305 return { $class => $field } if (grep { $_ eq $field } @{$pkg->search_fields->{$class}});
307 push @{$pkg->search_fields->{$class}}, $field;
309 return { $class => $field };
314 $class = ref($class) || $class;
316 $parser_config{$class}{fields} ||= {};
317 return $parser_config{$class}{fields};
320 sub add_search_class_alias {
322 $pkg = ref($pkg) || $pkg;
326 $pkg->add_search_class( $class );
328 return { $class => $alias } if (grep { $_ eq $alias } @{$pkg->search_class_aliases->{$class}});
330 push @{$pkg->search_class_aliases->{$class}}, $alias;
332 return { $class => $alias };
335 sub search_class_aliases {
337 $class = ref($class) || $class;
339 $parser_config{$class}{class_map} ||= {};
340 return $parser_config{$class}{class_map};
343 sub add_search_field_alias {
345 $pkg = ref($pkg) || $pkg;
350 return { $class => { $field => $alias } } if (grep { $_ eq $alias } @{$pkg->search_field_aliases->{$class}{$field}});
352 push @{$pkg->search_field_aliases->{$class}{$field}}, $alias;
354 return { $class => { $field => $alias } };
357 sub search_field_aliases {
359 $class = ref($class) || $class;
361 $parser_config{$class}{field_alias_map} ||= {};
362 return $parser_config{$class}{field_alias_map};
365 sub remove_facet_field {
367 $pkg = ref($pkg) || $pkg;
371 return { $class => $field } if (!$pkg->facet_fields->{$class} || !grep { $_ eq $field } @{$pkg->facet_fields->{$class}});
373 $pkg->facet_fields->{$class} = [ grep { $_ ne $field } @{$pkg->facet_fields->{$class}} ];
375 return { $class => $field };
378 sub remove_search_field {
380 $pkg = ref($pkg) || $pkg;
384 return { $class => $field } if (!$pkg->search_fields->{$class} || !grep { $_ eq $field } @{$pkg->search_fields->{$class}});
386 $pkg->search_fields->{$class} = [ grep { $_ ne $field } @{$pkg->search_fields->{$class}} ];
388 return { $class => $field };
391 sub remove_search_field_alias {
393 $pkg = ref($pkg) || $pkg;
398 return { $class => { $field => $alias } } if (!$pkg->search_field_aliases->{$class}{$field} || !grep { $_ eq $alias } @{$pkg->search_field_aliases->{$class}{$field}});
400 $pkg->search_field_aliases->{$class}{$field} = [ grep { $_ ne $alias } @{$pkg->search_field_aliases->{$class}{$field}} ];
402 return { $class => { $field => $alias } };
405 sub remove_search_class_alias {
407 $pkg = ref($pkg) || $pkg;
411 return { $class => $alias } if (!$pkg->search_class_aliases->{$class} || !grep { $_ eq $alias } @{$pkg->search_class_aliases->{$class}});
413 $pkg->search_class_aliases->{$class} = [ grep { $_ ne $alias } @{$pkg->search_class_aliases->{$class}} ];
415 return { $class => $alias };
421 $self->{_debug} = $q if (defined $q);
422 return $self->{_debug};
428 $self->{_query} = $q if (defined $q);
429 return $self->{_query};
435 $self->{_parse_tree} = $q if (defined $q);
436 return $self->{_parse_tree};
441 my $pkg = ref($self) || $self;
442 warn " ** parse package is $pkg\n" if $self->debug;
445 $self->query( shift() )
454 my $pkg = ref($self) || $self;
456 warn " ** decompose package is $pkg\n" if $self->debug;
459 my $current_class = shift || $self->default_search_class;
461 my $recursing = shift || 0;
463 # Build the search class+field uber-regexp
464 my $search_class_re = '^\s*(';
468 for my $class ( keys %{$pkg->search_fields} ) {
470 for my $field ( @{$pkg->search_fields->{$class}} ) {
472 for my $alias ( @{$pkg->search_field_aliases->{$class}{$field}} ) {
474 s/(^|\s+)$alias[:=]/$1$class\|$field:/g;
478 $search_class_re .= '|' unless ($first_class);
480 $search_class_re .= $class . '(?:\|\w+)*';
481 $seen_classes{$class} = 1;
484 for my $class ( keys %{$pkg->search_class_aliases} ) {
486 for my $alias ( @{$pkg->search_class_aliases->{$class}} ) {
488 s/(^|[^|])\b$alias\|/$1$class\|/g;
489 s/(^|[^|])\b$alias[:=]/$1$class:/g;
492 $search_class_re .= '|' unless ($first_class);
495 $search_class_re .= $class . '(?:\|\w+)*' if (!$seen_classes{$class});
496 $seen_classes{$class} = 1;
498 $search_class_re .= '):';
500 warn " ** Search class RE: $search_class_re\n" if $self->debug;
502 my $required_re = $pkg->operator('required');
503 $required_re = qr/^\s*\Q$required_re\E/;
504 my $and_re = $pkg->operator('and');
505 $and_re = qr/^\s*\Q$and_re\E/;
507 my $or_re = $pkg->operator('or');
508 $or_re = qr/^\s*\Q$or_re\E/;
510 my $group_start_re = $pkg->operator('group_start');
511 $group_start_re = qr/^\s*\Q$group_start_re\E/;
513 my $group_end = $pkg->operator('group_end');
514 my $group_end_re = qr/^\s*\Q$group_end\E/;
516 my $modifier_tag_re = $pkg->operator('modifier');
517 $modifier_tag_re = qr/^\s*\Q$modifier_tag_re\E/;
520 # Build the filter and modifier uber-regexps
521 my $facet_re = '^\s*((?:' . join( '|', @{$pkg->facet_classes}) . ')(?:\|\w+)*)\[(.+?)\]';
522 warn " Facet RE: $facet_re\n" if $self->debug;
524 my $filter_re = '^\s*(' . join( '|', @{$pkg->filters}) . ')\(([^()]+)\)';
525 my $filter_as_class_re = '^\s*(' . join( '|', @{$pkg->filters}) . '):\s*(\S+)';
527 my $modifier_re = '^\s*'.$modifier_tag_re.'(' . join( '|', @{$pkg->modifiers}) . ')\b';
528 my $modifier_as_class_re = '^\s*(' . join( '|', @{$pkg->modifiers}) . '):\s*(\S+)';
530 my $struct = $self->new_plan( level => $recursing );
534 while (!$remainder) {
535 if (/^\s*$/) { # end of an explicit group
537 } elsif (/$group_end_re/) { # end of an explicit group
538 warn "Encountered explicit group end\n" if $self->debug;
544 } elsif ($self->filter_count && /$filter_re/) { # found a filter
545 warn "Encountered search filter: $1 set to $2\n" if $self->debug;
548 $struct->new_filter( $1 => [ split '[, ]+', $2 ] );
551 } elsif ($self->filter_count && /$filter_as_class_re/) { # found a filter
552 warn "Encountered search filter: $1 set to $2\n" if $self->debug;
555 $struct->new_filter( $1 => [ split '[, ]+', $2 ] );
558 } elsif ($self->modifier_count && /$modifier_re/) { # found a modifier
559 warn "Encountered search modifier: $1\n" if $self->debug;
562 if (!$struct->top_plan) {
563 warn " Search modifiers only allowed at the top level of the query\n" if $self->debug;
565 $struct->new_modifier($1);
569 } elsif ($self->modifier_count && /$modifier_as_class_re/) { # found a modifier
570 warn "Encountered search modifier: $1\n" if $self->debug;
575 if (!$struct->top_plan) {
576 warn " Search modifiers only allowed at the top level of the query\n" if $self->debug;
577 } elsif ($2 =~ /^[ty1]/i) {
578 $struct->new_modifier($mod);
582 } elsif (/$group_start_re/) { # start of an explicit group
583 warn "Encountered explicit group start\n" if $self->debug;
585 my ($substruct, $subremainder) = $self->decompose( $', $current_class, $recursing + 1 );
586 $struct->add_node( $substruct );
590 } elsif (/$and_re/) { # ANDed expression
592 next if ($last_type eq 'AND');
593 next if ($last_type eq 'OR');
594 warn "Encountered AND\n" if $self->debug;
596 $struct->joiner( '&' );
599 } elsif (/$or_re/) { # ORed expression
601 next if ($last_type eq 'AND');
602 next if ($last_type eq 'OR');
603 warn "Encountered OR\n" if $self->debug;
605 $struct->joiner( '|' );
608 } elsif ($self->facet_class_count && /$facet_re/) { # changing current class
609 warn "Encountered facet: $1 => $2\n" if $self->debug;
612 my $facet_value = [ split '\s*#\s*', $2 ];
613 $struct->new_facet( $facet => $facet_value );
617 } elsif ($self->search_class_count && /$search_class_re/) { # changing current class
619 if ($last_type eq 'CLASS') {
620 $struct->remove_last_node( $current_class );
621 warn "Encountered class change with no searches!\n" if $self->debug;
624 warn "Encountered class change: $1\n" if $self->debug;
627 $struct->classed_node( $current_class );
630 $last_type = 'CLASS';
631 } elsif (/^\s*"([^"]+)"/) { # phrase, always anded
632 warn "Encountered phrase: $1\n" if $self->debug;
634 $struct->joiner( '&' );
637 my $class_node = $struct->classed_node($current_class);
638 $class_node->add_phrase( $phrase );
642 } elsif (/$required_re([^\s)]+)/) { # phrase, always anded
643 warn "Encountered required atom (mini phrase): $1\n" if $self->debug;
647 my $class_node = $struct->classed_node($current_class);
648 $class_node->add_phrase( $phrase );
650 $struct->joiner( '&' );
653 } elsif (/^\s*([^$group_end\s]+)/o) { # atom
654 warn "Encountered atom: $1\n" if $self->debug;
655 warn "Remainder: $'\n" if $self->debug;
663 my $negator = ($atom =~ s/^-//o) ? '!' : '';
664 my $truncate = ($atom =~ s/\*$//o) ? '*' : '';
666 if (!grep { $atom eq $_ } ('&','|')) { # throw away & and |, not allowed in tsquery, and not really useful anyway
667 my $class_node = $struct->classed_node($current_class);
668 $class_node->add_fts_atom( $atom, suffix => $truncate, prefix => $negator, node => $class_node );
669 $struct->joiner( '&' );
677 return $struct if !wantarray;
678 return ($struct, $remainder);
681 sub find_class_index {
685 my ($class_part, @field_parts) = split '\|', $class;
686 $class_part ||= $class;
688 for my $idx ( 0 .. scalar(@$query) - 1 ) {
689 next unless ref($$query[$idx]);
690 return $idx if ( $$query[$idx]{requested_class} && $class eq $$query[$idx]{requested_class} );
693 push(@$query, { classname => $class_part, (@field_parts ? (fields => \@field_parts) : ()), requested_class => $class, ftsquery => [], phrases => [] });
700 $self->{core_limit} = $l if ($l);
701 return $self->{core_limit};
707 $self->{superpage} = $l if ($l);
708 return $self->{superpage};
714 $self->{superpage_size} = $l if ($l);
715 return $self->{superpage_size};
719 #-------------------------------
720 package QueryParser::query_plan;
724 return undef unless ref($self);
725 return $self->{QueryParser};
730 $pkg = ref($pkg) || $pkg;
731 my %args = (query => [], joiner => '&', @_);
733 return bless \%args => $pkg;
738 my $pkg = ref($self) || $self;
739 my $node = do{$pkg.'::node'}->new( plan => $self, @_ );
740 $self->add_node( $node );
746 my $pkg = ref($self) || $self;
750 my $node = do{$pkg.'::facet'}->new( plan => $self, name => $name, 'values' => $args );
751 $self->add_node( $node );
758 my $pkg = ref($self) || $self;
762 my $node = do{$pkg.'::filter'}->new( plan => $self, name => $name, args => $args );
763 $self->add_filter( $node );
771 return undef unless ($needle);
772 return grep { $_->name eq $needle } @{ $self->filters };
778 return undef unless ($needle);
779 return grep { $_->name eq $needle } @{ $self->modifiers };
784 my $pkg = ref($self) || $self;
787 my $node = do{$pkg.'::modifier'}->new( $name );
788 $self->add_modifier( $node );
795 my $requested_class = shift;
798 for my $n (@{$self->{query}}) {
799 next unless (ref($n) && $n->isa( 'QueryParser::query_plan::node' ));
800 if ($n->requested_class eq $requested_class) {
807 $node = $self->new_node;
808 $node->requested_class( $requested_class );
814 sub remove_last_node {
816 my $requested_class = shift;
818 my $old = pop(@{$self->query_nodes});
819 pop(@{$self->query_nodes}) if (@{$self->query_nodes});
826 return $self->{query};
833 $self->{query} ||= [];
834 push(@{$self->{query}}, $self->joiner) if (@{$self->{query}});
835 push(@{$self->{query}}, $node);
843 return $self->{level} ? 0 : 1;
848 return $self->{level};
855 $self->{joiner} = $joiner if ($joiner);
856 return $self->{joiner};
861 $self->{modifiers} ||= [];
862 return $self->{modifiers};
867 my $modifier = shift;
869 $self->{modifiers} ||= [];
870 return $self if (grep {$$_ eq $$modifier} @{$self->{modifiers}});
872 push(@{$self->{modifiers}}, $modifier);
879 $self->{facets} ||= [];
880 return $self->{facets};
887 $self->{facets} ||= [];
888 return $self if (grep {$_->name eq $facet->name} @{$self->{facets}});
890 push(@{$self->{facets}}, $facet);
897 $self->{filters} ||= [];
898 return $self->{filters};
905 $self->{filters} ||= [];
906 return $self if (grep {$_->name eq $filter->name} @{$self->{filters}});
908 push(@{$self->{filters}}, $filter);
914 #-------------------------------
915 package QueryParser::query_plan::node;
919 $pkg = ref($pkg) || $pkg;
922 return bless \%args => $pkg;
927 my $pkg = ref($self) || $self;
928 return do{$pkg.'::atom'}->new( @_ );
931 sub requested_class { # also split into classname and fields
936 my ($class_part, @field_parts) = split '\|', $class;
937 $class_part ||= $class;
939 $self->{requested_class} = $class;
940 $self->{classname} = $class_part;
941 $self->{fields} = \@field_parts;
944 return $self->{requested_class};
951 $self->{plan} = $plan if ($plan);
952 return $self->{plan};
959 $self->{classname} = $class if ($class);
960 return $self->{classname};
967 $self->{fields} ||= [];
968 $self->{fields} = \@fields if (@fields);
969 return $self->{fields};
976 $self->{phrases} ||= [];
977 $self->{phrases} = \@phrases if (@phrases);
978 return $self->{phrases};
985 push(@{$self->phrases}, $phrase);
992 my @query_atoms = @_;
994 $self->{query_atoms} ||= [];
995 $self->{query_atoms} = \@query_atoms if (@query_atoms);
996 return $self->{query_atoms};
1004 my $content = $atom;
1007 $atom = $self->new_atom( content => $content, @parts );
1010 push(@{$self->query_atoms}, $self->plan->joiner) if (@{$self->query_atoms});
1011 push(@{$self->query_atoms}, $atom);
1016 #-------------------------------
1017 package QueryParser::query_plan::node::atom;
1021 $pkg = ref($pkg) || $pkg;
1024 return bless \%args => $pkg;
1029 return undef unless (ref $self);
1030 return $self->{node};
1035 return undef unless (ref $self);
1036 return $self->{content};
1041 return undef unless (ref $self);
1042 return $self->{prefix};
1047 return undef unless (ref $self);
1048 return $self->{suffix};
1051 #-------------------------------
1052 package QueryParser::query_plan::filter;
1056 $pkg = ref($pkg) || $pkg;
1059 return bless \%args => $pkg;
1064 return $self->{plan};
1069 return $self->{name};
1074 return $self->{args};
1077 #-------------------------------
1078 package QueryParser::query_plan::facet;
1082 $pkg = ref($pkg) || $pkg;
1085 return bless \%args => $pkg;
1090 return $self->{plan};
1095 return $self->{name};
1100 return $self->{'values'};
1103 #-------------------------------
1104 package QueryParser::query_plan::modifier;
1108 $pkg = ref($pkg) || $pkg;
1109 my $modifier = shift;
1111 return bless \$modifier => $pkg;