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 default_search_class {
212 $pkg = ref($pkg) || $pkg;
214 $QueryParser::parser_config{$pkg}{default_class} = $pkg->add_search_class( $class ) if $class;
216 return $QueryParser::parser_config{$pkg}{default_class};
219 sub remove_facet_class {
221 $pkg = ref($pkg) || $pkg;
224 return $class if (!grep { $_ eq $class } @{$pkg->facet_classes});
226 $pkg->facet_classes( [ grep { $_ ne $class } @{$pkg->facet_classes} ] );
227 delete $QueryParser::parser_config{$pkg}{facet_fields}{$class};
232 sub remove_search_class {
234 $pkg = ref($pkg) || $pkg;
237 return $class if (!grep { $_ eq $class } @{$pkg->search_classes});
239 $pkg->search_classes( [ grep { $_ ne $class } @{$pkg->search_classes} ] );
240 delete $QueryParser::parser_config{$pkg}{fields}{$class};
245 sub add_facet_field {
247 $pkg = ref($pkg) || $pkg;
251 $pkg->add_facet_class( $class );
253 return { $class => $field } if (grep { $_ eq $field } @{$pkg->facet_fields->{$class}});
255 push @{$pkg->facet_fields->{$class}}, $field;
257 return { $class => $field };
262 $class = ref($class) || $class;
264 $parser_config{$class}{facet_fields} ||= {};
265 return $parser_config{$class}{facet_fields};
268 sub add_search_field {
270 $pkg = ref($pkg) || $pkg;
274 $pkg->add_search_class( $class );
276 return { $class => $field } if (grep { $_ eq $field } @{$pkg->search_fields->{$class}});
278 push @{$pkg->search_fields->{$class}}, $field;
280 return { $class => $field };
285 $class = ref($class) || $class;
287 $parser_config{$class}{fields} ||= {};
288 return $parser_config{$class}{fields};
291 sub add_search_class_alias {
293 $pkg = ref($pkg) || $pkg;
297 $pkg->add_search_class( $class );
299 return { $class => $alias } if (grep { $_ eq $alias } @{$pkg->search_class_aliases->{$class}});
301 push @{$pkg->search_class_aliases->{$class}}, $alias;
303 return { $class => $alias };
306 sub search_class_aliases {
308 $class = ref($class) || $class;
310 $parser_config{$class}{class_map} ||= {};
311 return $parser_config{$class}{class_map};
314 sub add_search_field_alias {
316 $pkg = ref($pkg) || $pkg;
321 return { $class => { $field => $alias } } if (grep { $_ eq $alias } @{$pkg->search_field_aliases->{$class}{$field}});
323 push @{$pkg->search_field_aliases->{$class}{$field}}, $alias;
325 return { $class => { $field => $alias } };
328 sub search_field_aliases {
330 $class = ref($class) || $class;
332 $parser_config{$class}{field_alias_map} ||= {};
333 return $parser_config{$class}{field_alias_map};
336 sub remove_facet_field {
338 $pkg = ref($pkg) || $pkg;
342 return { $class => $field } if (!$pkg->facet_fields->{$class} || !grep { $_ eq $field } @{$pkg->facet_fields->{$class}});
344 $pkg->facet_fields->{$class} = [ grep { $_ ne $field } @{$pkg->facet_fields->{$class}} ];
346 return { $class => $field };
349 sub remove_search_field {
351 $pkg = ref($pkg) || $pkg;
355 return { $class => $field } if (!$pkg->search_fields->{$class} || !grep { $_ eq $field } @{$pkg->search_fields->{$class}});
357 $pkg->search_fields->{$class} = [ grep { $_ ne $field } @{$pkg->search_fields->{$class}} ];
359 return { $class => $field };
362 sub remove_search_field_alias {
364 $pkg = ref($pkg) || $pkg;
369 return { $class => { $field => $alias } } if (!$pkg->search_field_aliases->{$class}{$field} || !grep { $_ eq $alias } @{$pkg->search_field_aliases->{$class}{$field}});
371 $pkg->search_field_aliases->{$class}{$field} = [ grep { $_ ne $alias } @{$pkg->search_field_aliases->{$class}{$field}} ];
373 return { $class => { $field => $alias } };
376 sub remove_search_class_alias {
378 $pkg = ref($pkg) || $pkg;
382 return { $class => $alias } if (!$pkg->search_class_aliases->{$class} || !grep { $_ eq $alias } @{$pkg->search_class_aliases->{$class}});
384 $pkg->search_class_aliases->{$class} = [ grep { $_ ne $alias } @{$pkg->search_class_aliases->{$class}} ];
386 return { $class => $alias };
392 $self->{_debug} = $q if (defined $q);
393 return $self->{_debug};
399 $self->{_query} = $q if (defined $q);
400 return $self->{_query};
406 $self->{_parse_tree} = $q if (defined $q);
407 return $self->{_parse_tree};
412 my $pkg = ref($self) || $self;
413 warn " ** parse package is $pkg\n" if $self->debug;
416 $self->query( shift() )
425 my $pkg = ref($self) || $self;
427 warn " ** decompose package is $pkg\n" if $self->debug;
430 my $current_class = shift || $self->default_search_class;
432 my $recursing = shift || 0;
434 # Build the search class+field uber-regexp
435 my $search_class_re = '^\s*(';
439 for my $class ( keys %{$pkg->search_fields} ) {
441 for my $field ( @{$pkg->search_fields->{$class}} ) {
443 for my $alias ( @{$pkg->search_field_aliases->{$class}{$field}} ) {
445 s/(^|\s+)$alias[:=]/$1$class\|$field:/g;
449 $search_class_re .= '|' unless ($first_class);
451 $search_class_re .= $class . '(?:\|\w+)*';
452 $seen_classes{$class} = 1;
455 for my $class ( keys %{$pkg->search_class_aliases} ) {
457 for my $alias ( @{$pkg->search_class_aliases->{$class}} ) {
459 s/(^|[^|])\b$alias\|/$1$class\|/g;
460 s/(^|[^|])\b$alias[:=]/$1$class:/g;
463 $search_class_re .= '|' unless ($first_class);
466 $search_class_re .= $class . '(?:\|\w+)*' if (!$seen_classes{$class});
467 $seen_classes{$class} = 1;
469 $search_class_re .= '):';
471 warn " ** Search class RE: $search_class_re\n" if $self->debug;
473 my $required_re = $pkg->operator('required');
474 $required_re = qr/^\s*\Q$required_re\E/;
475 my $and_re = $pkg->operator('and');
476 $and_re = qr/^\s*\Q$and_re\E/;
478 my $or_re = $pkg->operator('or');
479 $or_re = qr/^\s*\Q$or_re\E/;
481 my $group_start_re = $pkg->operator('group_start');
482 $group_start_re = qr/^\s*\Q$group_start_re\E/;
484 my $group_end = $pkg->operator('group_end');
485 my $group_end_re = qr/^\s*\Q$group_end\E/;
487 my $modifier_tag_re = $pkg->operator('modifier');
488 $modifier_tag_re = qr/^\s*\Q$modifier_tag_re\E/;
491 # Build the filter and modifier uber-regexps
492 my $facet_re = '^\s*((?:' . join( '|', @{$pkg->facet_classes}) . ')(?:\|\w+)*)\[(.+?)\]';
493 warn " Facet RE: $facet_re\n" if $self->debug;
495 my $filter_re = '^\s*(' . join( '|', @{$pkg->filters}) . ')\(([^()]+)\)';
496 my $filter_as_class_re = '^\s*(' . join( '|', @{$pkg->filters}) . '):\s*(\S+)';
498 my $modifier_re = '^\s*'.$modifier_tag_re.'(' . join( '|', @{$pkg->modifiers}) . ')\b';
499 my $modifier_as_class_re = '^\s*(' . join( '|', @{$pkg->modifiers}) . '):\s*(\S+)';
501 my $struct = $self->new_plan( level => $recursing );
505 while (!$remainder) {
506 if (/^\s*$/) { # end of an explicit group
508 } elsif (/$group_end_re/) { # end of an explicit group
509 warn "Encountered explicit group end\n" if $self->debug;
515 } elsif ($self->filter_count && /$filter_re/) { # found a filter
516 warn "Encountered search filter: $1 set to $2\n" if $self->debug;
519 $struct->new_filter( $1 => [ split '[, ]+', $2 ] );
522 } elsif ($self->filter_count && /$filter_as_class_re/) { # found a filter
523 warn "Encountered search filter: $1 set to $2\n" if $self->debug;
526 $struct->new_filter( $1 => [ split '[, ]+', $2 ] );
529 } elsif ($self->modifier_count && /$modifier_re/) { # found a modifier
530 warn "Encountered search modifier: $1\n" if $self->debug;
533 if (!$struct->top_plan) {
534 warn " Search modifiers only allowed at the top level of the query\n" if $self->debug;
536 $struct->new_modifier($1);
540 } elsif ($self->modifier_count && /$modifier_as_class_re/) { # found a modifier
541 warn "Encountered search modifier: $1\n" if $self->debug;
546 if (!$struct->top_plan) {
547 warn " Search modifiers only allowed at the top level of the query\n" if $self->debug;
548 } elsif ($2 =~ /^[ty1]/i) {
549 $struct->new_modifier($mod);
553 } elsif (/$group_start_re/) { # start of an explicit group
554 warn "Encountered explicit group start\n" if $self->debug;
556 my ($substruct, $subremainder) = $self->decompose( $', $current_class, $recursing + 1 );
557 $struct->add_node( $substruct );
561 } elsif (/$and_re/) { # ANDed expression
563 next if ($last_type eq 'AND');
564 next if ($last_type eq 'OR');
565 warn "Encountered AND\n" if $self->debug;
567 $struct->joiner( '&' );
570 } elsif (/$or_re/) { # ORed expression
572 next if ($last_type eq 'AND');
573 next if ($last_type eq 'OR');
574 warn "Encountered OR\n" if $self->debug;
576 $struct->joiner( '|' );
579 } elsif ($self->facet_class_count && /$facet_re/) { # changing current class
580 warn "Encountered facet: $1 => $2\n" if $self->debug;
583 my $facet_value = [ split '\s*#\s*', $2 ];
584 $struct->new_facet( $facet => $facet_value );
588 } elsif ($self->search_class_count && /$search_class_re/) { # changing current class
590 if ($last_type eq 'CLASS') {
591 $struct->remove_last_node( $current_class );
592 warn "Encountered class change with no searches!\n" if $self->debug;
595 warn "Encountered class change: $1\n" if $self->debug;
598 $struct->classed_node( $current_class );
601 $last_type = 'CLASS';
602 } elsif (/^\s*"([^"]+)"/) { # phrase, always anded
603 warn "Encountered phrase: $1\n" if $self->debug;
605 $struct->joiner( '&' );
608 my $class_node = $struct->classed_node($current_class);
609 $class_node->add_phrase( $phrase );
613 } elsif (/$required_re([^\s)]+)/) { # phrase, always anded
614 warn "Encountered required atom (mini phrase): $1\n" if $self->debug;
618 my $class_node = $struct->classed_node($current_class);
619 $class_node->add_phrase( $phrase );
621 $struct->joiner( '&' );
624 } elsif (/^\s*([^$group_end\s]+)/o) { # atom
625 warn "Encountered atom: $1\n" if $self->debug;
626 warn "Remainder: $'\n" if $self->debug;
634 my $negator = ($atom =~ s/^-//o) ? '!' : '';
635 my $truncate = ($atom =~ s/\*$//o) ? '*' : '';
637 if (!grep { $atom eq $_ } ('&','|')) { # throw away & and |, not allowed in tsquery, and not really useful anyway
638 my $class_node = $struct->classed_node($current_class);
639 $class_node->add_fts_atom( $atom, suffix => $truncate, prefix => $negator, node => $class_node );
640 $struct->joiner( '&' );
648 return $struct if !wantarray;
649 return ($struct, $remainder);
652 sub find_class_index {
656 my ($class_part, @field_parts) = split '\|', $class;
657 $class_part ||= $class;
659 for my $idx ( 0 .. scalar(@$query) - 1 ) {
660 next unless ref($$query[$idx]);
661 return $idx if ( $$query[$idx]{requested_class} && $class eq $$query[$idx]{requested_class} );
664 push(@$query, { classname => $class_part, (@field_parts ? (fields => \@field_parts) : ()), requested_class => $class, ftsquery => [], phrases => [] });
671 $self->{core_limit} = $l if ($l);
672 return $self->{core_limit};
678 $self->{superpage} = $l if ($l);
679 return $self->{superpage};
685 $self->{superpage_size} = $l if ($l);
686 return $self->{superpage_size};
690 #-------------------------------
691 package QueryParser::query_plan;
695 return undef unless ref($self);
696 return $self->{QueryParser};
701 $pkg = ref($pkg) || $pkg;
702 my %args = (query => [], joiner => '&', @_);
704 return bless \%args => $pkg;
709 my $pkg = ref($self) || $self;
710 my $node = do{$pkg.'::node'}->new( plan => $self, @_ );
711 $self->add_node( $node );
717 my $pkg = ref($self) || $self;
721 my $node = do{$pkg.'::facet'}->new( plan => $self, name => $name, 'values' => $args );
722 $self->add_node( $node );
729 my $pkg = ref($self) || $self;
733 my $node = do{$pkg.'::filter'}->new( plan => $self, name => $name, args => $args );
734 $self->add_filter( $node );
742 return undef unless ($needle);
743 return grep { $_->name eq $needle } @{ $self->filters };
749 return undef unless ($needle);
750 return grep { $_->name eq $needle } @{ $self->modifiers };
755 my $pkg = ref($self) || $self;
758 my $node = do{$pkg.'::modifier'}->new( $name );
759 $self->add_modifier( $node );
766 my $requested_class = shift;
769 for my $n (@{$self->{query}}) {
770 next unless (ref($n) && $n->isa( 'QueryParser::query_plan::node' ));
771 if ($n->requested_class eq $requested_class) {
778 $node = $self->new_node;
779 $node->requested_class( $requested_class );
785 sub remove_last_node {
787 my $requested_class = shift;
789 my $old = pop(@{$self->query_nodes});
790 pop(@{$self->query_nodes}) if (@{$self->query_nodes});
797 return $self->{query};
804 $self->{query} ||= [];
805 push(@{$self->{query}}, $self->joiner) if (@{$self->{query}});
806 push(@{$self->{query}}, $node);
814 return $self->{level} ? 0 : 1;
819 return $self->{level};
826 $self->{joiner} = $joiner if ($joiner);
827 return $self->{joiner};
832 $self->{modifiers} ||= [];
833 return $self->{modifiers};
838 my $modifier = shift;
840 $self->{modifiers} ||= [];
841 return $self if (grep {$$_ eq $$modifier} @{$self->{modifiers}});
843 push(@{$self->{modifiers}}, $modifier);
850 $self->{facets} ||= [];
851 return $self->{facets};
858 $self->{facets} ||= [];
859 return $self if (grep {$_->name eq $facet->name} @{$self->{facets}});
861 push(@{$self->{facets}}, $facet);
868 $self->{filters} ||= [];
869 return $self->{filters};
876 $self->{filters} ||= [];
877 return $self if (grep {$_->name eq $filter->name} @{$self->{filters}});
879 push(@{$self->{filters}}, $filter);
885 #-------------------------------
886 package QueryParser::query_plan::node;
890 $pkg = ref($pkg) || $pkg;
893 return bless \%args => $pkg;
898 my $pkg = ref($self) || $self;
899 return do{$pkg.'::atom'}->new( @_ );
902 sub requested_class { # also split into classname and fields
907 my ($class_part, @field_parts) = split '\|', $class;
908 $class_part ||= $class;
910 $self->{requested_class} = $class;
911 $self->{classname} = $class_part;
912 $self->{fields} = \@field_parts;
915 return $self->{requested_class};
922 $self->{plan} = $plan if ($plan);
923 return $self->{plan};
930 $self->{classname} = $class if ($class);
931 return $self->{classname};
938 $self->{fields} ||= [];
939 $self->{fields} = \@fields if (@fields);
940 return $self->{fields};
947 $self->{phrases} ||= [];
948 $self->{phrases} = \@phrases if (@phrases);
949 return $self->{phrases};
956 push(@{$self->phrases}, $phrase);
963 my @query_atoms = @_;
965 $self->{query_atoms} ||= [];
966 $self->{query_atoms} = \@query_atoms if (@query_atoms);
967 return $self->{query_atoms};
978 $atom = $self->new_atom( content => $content, @parts );
981 push(@{$self->query_atoms}, $self->plan->joiner) if (@{$self->query_atoms});
982 push(@{$self->query_atoms}, $atom);
987 #-------------------------------
988 package QueryParser::query_plan::node::atom;
992 $pkg = ref($pkg) || $pkg;
995 return bless \%args => $pkg;
1000 return undef unless (ref $self);
1001 return $self->{node};
1006 return undef unless (ref $self);
1007 return $self->{content};
1012 return undef unless (ref $self);
1013 return $self->{prefix};
1018 return undef unless (ref $self);
1019 return $self->{suffix};
1022 #-------------------------------
1023 package QueryParser::query_plan::filter;
1027 $pkg = ref($pkg) || $pkg;
1030 return bless \%args => $pkg;
1035 return $self->{plan};
1040 return $self->{name};
1045 return $self->{args};
1048 #-------------------------------
1049 package QueryParser::query_plan::facet;
1053 $pkg = ref($pkg) || $pkg;
1056 return bless \%args => $pkg;
1061 return $self->{plan};
1066 return $self->{name};
1071 return $self->{'values'};
1074 #-------------------------------
1075 package QueryParser::query_plan::modifier;
1079 $pkg = ref($pkg) || $pkg;
1080 my $modifier = shift;
1082 return bless \$modifier => $pkg;