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/\b$alias[:=]/$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
589 warn "Encountered class change: $1\n" if $self->debug;
592 $struct->classed_node( $current_class );
596 } elsif (/^\s*"([^"]+)"/) { # phrase, always anded
597 warn "Encountered phrase: $1\n" if $self->debug;
599 $struct->joiner( '&' );
602 my $class_node = $struct->classed_node($current_class);
603 $class_node->add_phrase( $phrase );
607 } elsif (/$required_re([^\s)]+)/) { # phrase, always anded
608 warn "Encountered required atom (mini phrase): $1\n" if $self->debug;
612 my $class_node = $struct->classed_node($current_class);
613 $class_node->add_phrase( $phrase );
615 $struct->joiner( '&' );
618 } elsif (/^\s*([^$group_end\s]+)/o) { # atom
619 warn "Encountered atom: $1\n" if $self->debug;
620 warn "Remainder: $'\n" if $self->debug;
625 my $class_node = $struct->classed_node($current_class);
626 my $negator = ($atom =~ s/^-//o) ? '!' : '';
627 my $truncate = ($atom =~ s/\*$//o) ? '*' : '';
629 $class_node->add_fts_atom( $atom, suffix => $truncate, prefix => $negator, node => $class_node );
630 $struct->joiner( '&' );
640 return $struct if !wantarray;
641 return ($struct, $remainder);
644 sub find_class_index {
648 my ($class_part, @field_parts) = split '\|', $class;
649 $class_part ||= $class;
651 for my $idx ( 0 .. scalar(@$query) - 1 ) {
652 next unless ref($$query[$idx]);
653 return $idx if ( $$query[$idx]{requested_class} && $class eq $$query[$idx]{requested_class} );
656 push(@$query, { classname => $class_part, (@field_parts ? (fields => \@field_parts) : ()), requested_class => $class, ftsquery => [], phrases => [] });
663 $self->{core_limit} = $l if ($l);
664 return $self->{core_limit};
670 $self->{superpage} = $l if ($l);
671 return $self->{superpage};
677 $self->{superpage_size} = $l if ($l);
678 return $self->{superpage_size};
682 #-------------------------------
683 package QueryParser::query_plan;
687 return undef unless ref($self);
688 return $self->{QueryParser};
693 $pkg = ref($pkg) || $pkg;
694 my %args = (query => [], joiner => '&', @_);
696 return bless \%args => $pkg;
701 my $pkg = ref($self) || $self;
702 my $node = do{$pkg.'::node'}->new( plan => $self, @_ );
703 $self->add_node( $node );
709 my $pkg = ref($self) || $self;
713 my $node = do{$pkg.'::facet'}->new( plan => $self, name => $name, 'values' => $args );
714 $self->add_node( $node );
721 my $pkg = ref($self) || $self;
725 my $node = do{$pkg.'::filter'}->new( plan => $self, name => $name, args => $args );
726 $self->add_filter( $node );
734 return undef unless ($needle);
735 return grep { $_->name eq $needle } @{ $self->filters };
741 return undef unless ($needle);
742 return grep { $_->name eq $needle } @{ $self->modifiers };
747 my $pkg = ref($self) || $self;
750 my $node = do{$pkg.'::modifier'}->new( $name );
751 $self->add_modifier( $node );
758 my $requested_class = shift;
761 for my $n (@{$self->{query}}) {
762 next unless (ref($n) && $n->isa( 'QueryParser::query_plan::node' ));
763 if ($n->requested_class eq $requested_class) {
770 $node = $self->new_node;
771 $node->requested_class( $requested_class );
779 return $self->{query};
786 $self->{query} ||= [];
787 push(@{$self->{query}}, $self->joiner) if (@{$self->{query}});
788 push(@{$self->{query}}, $node);
796 return $self->{level} ? 0 : 1;
801 return $self->{level};
808 $self->{joiner} = $joiner if ($joiner);
809 return $self->{joiner};
814 $self->{modifiers} ||= [];
815 return $self->{modifiers};
820 my $modifier = shift;
822 $self->{modifiers} ||= [];
823 return $self if (grep {$$_ eq $$modifier} @{$self->{modifiers}});
825 push(@{$self->{modifiers}}, $modifier);
832 $self->{facets} ||= [];
833 return $self->{facets};
840 $self->{facets} ||= [];
841 return $self if (grep {$_->name eq $facet->name} @{$self->{facets}});
843 push(@{$self->{facets}}, $facet);
850 $self->{filters} ||= [];
851 return $self->{filters};
858 $self->{filters} ||= [];
859 return $self if (grep {$_->name eq $filter->name} @{$self->{filters}});
861 push(@{$self->{filters}}, $filter);
867 #-------------------------------
868 package QueryParser::query_plan::node;
872 $pkg = ref($pkg) || $pkg;
875 return bless \%args => $pkg;
880 my $pkg = ref($self) || $self;
881 return do{$pkg.'::atom'}->new( @_ );
884 sub requested_class { # also split into classname and fields
889 my ($class_part, @field_parts) = split '\|', $class;
890 $class_part ||= $class;
892 $self->{requested_class} = $class;
893 $self->{classname} = $class_part;
894 $self->{fields} = \@field_parts;
897 return $self->{requested_class};
904 $self->{plan} = $plan if ($plan);
905 return $self->{plan};
912 $self->{classname} = $class if ($class);
913 return $self->{classname};
920 $self->{fields} ||= [];
921 $self->{fields} = \@fields if (@fields);
922 return $self->{fields};
929 $self->{phrases} ||= [];
930 $self->{phrases} = \@phrases if (@phrases);
931 return $self->{phrases};
938 push(@{$self->phrases}, $phrase);
945 my @query_atoms = @_;
947 $self->{query_atoms} ||= [];
948 $self->{query_atoms} = \@query_atoms if (@query_atoms);
949 return $self->{query_atoms};
960 $atom = $self->new_atom( content => $content, @parts );
963 push(@{$self->query_atoms}, $self->plan->joiner) if (@{$self->query_atoms});
964 push(@{$self->query_atoms}, $atom);
969 #-------------------------------
970 package QueryParser::query_plan::node::atom;
974 $pkg = ref($pkg) || $pkg;
977 return bless \%args => $pkg;
982 return undef unless (ref $self);
983 return $self->{node};
988 return undef unless (ref $self);
989 return $self->{content};
994 return undef unless (ref $self);
995 return $self->{prefix};
1000 return undef unless (ref $self);
1001 return $self->{suffix};
1004 #-------------------------------
1005 package QueryParser::query_plan::filter;
1009 $pkg = ref($pkg) || $pkg;
1012 return bless \%args => $pkg;
1017 return $self->{plan};
1022 return $self->{name};
1027 return $self->{args};
1030 #-------------------------------
1031 package QueryParser::query_plan::facet;
1035 $pkg = ref($pkg) || $pkg;
1038 return bless \%args => $pkg;
1043 return $self->{plan};
1048 return $self->{name};
1053 return $self->{'values'};
1056 #-------------------------------
1057 package QueryParser::query_plan::modifier;
1061 $pkg = ref($pkg) || $pkg;
1062 my $modifier = shift;
1064 return bless \$modifier => $pkg;