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 (/$group_end_re/) { # end of an explicit group
507 warn "Encountered explicit group end\n" if $self->debug;
513 } elsif ($self->filter_count && /$filter_re/) { # found a filter
514 warn "Encountered search filter: $1 set to $2\n" if $self->debug;
517 $struct->new_filter( $1 => [ split '[, ]+', $2 ] );
520 } elsif ($self->filter_count && /$filter_as_class_re/) { # found a filter
521 warn "Encountered search filter: $1 set to $2\n" if $self->debug;
524 $struct->new_filter( $1 => [ split '[, ]+', $2 ] );
527 } elsif ($self->modifier_count && /$modifier_re/) { # found a modifier
528 warn "Encountered search modifier: $1\n" if $self->debug;
531 if (!$struct->top_plan) {
532 warn " Search modifiers only allowed at the top level of the query\n" if $self->debug;
534 $struct->new_modifier($1);
538 } elsif ($self->modifier_count && /$modifier_as_class_re/) { # found a modifier
539 warn "Encountered search modifier: $1\n" if $self->debug;
544 if (!$struct->top_plan) {
545 warn " Search modifiers only allowed at the top level of the query\n" if $self->debug;
546 } elsif ($2 =~ /^[ty1]/i) {
547 $struct->new_modifier($mod);
551 } elsif (/$group_start_re/) { # start of an explicit group
552 warn "Encountered explicit group start\n" if $self->debug;
554 my ($substruct, $subremainder) = $self->decompose( $', $current_class, $recursing + 1 );
555 $struct->add_node( $substruct );
559 } elsif (/$and_re/) { # ANDed expression
561 next if ($last_type eq 'AND');
562 next if ($last_type eq 'OR');
563 warn "Encountered AND\n" if $self->debug;
565 $struct->joiner( '&' );
568 } elsif (/$or_re/) { # ORed expression
570 next if ($last_type eq 'AND');
571 next if ($last_type eq 'OR');
572 warn "Encountered OR\n" if $self->debug;
574 $struct->joiner( '|' );
577 } elsif ($self->facet_class_count && /$facet_re/) { # changing current class
578 warn "Encountered facet: $1 => $2\n" if $self->debug;
581 my $facet_value = [ split '\s*#\s*', $2 ];
582 $struct->new_facet( $facet => $facet_value );
586 } elsif ($self->search_class_count && /$search_class_re/) { # changing current class
587 warn "Encountered class change: $1\n" if $self->debug;
590 $struct->classed_node( $current_class );
594 } elsif (/^\s*"([^"]+)"/) { # phrase, always anded
595 warn "Encountered phrase: $1\n" if $self->debug;
597 $struct->joiner( '&' );
600 my $class_node = $struct->classed_node($current_class);
601 $class_node->add_phrase( $phrase );
605 } elsif (/$required_re([^\s)]+)/) { # phrase, always anded
606 warn "Encountered required atom (mini phrase): $1\n" if $self->debug;
610 my $class_node = $struct->classed_node($current_class);
611 $class_node->add_phrase( $phrase );
613 $struct->joiner( '&' );
616 } elsif (/^\s*([^$group_end\s]+)/o) { # atom
617 warn "Encountered atom: $1\n" if $self->debug;
618 warn "Remainder: $'\n" if $self->debug;
623 my $class_node = $struct->classed_node($current_class);
624 my $negator = ($atom =~ s/^-//o) ? '!' : '';
625 my $truncate = ($atom =~ s/\*$//o) ? '*' : '';
627 $class_node->add_fts_atom( $atom, suffix => $truncate, prefix => $negator, node => $class_node );
628 $struct->joiner( '&' );
638 return $struct if !wantarray;
639 return ($struct, $remainder);
642 sub find_class_index {
646 my ($class_part, @field_parts) = split '\|', $class;
647 $class_part ||= $class;
649 for my $idx ( 0 .. scalar(@$query) - 1 ) {
650 next unless ref($$query[$idx]);
651 return $idx if ( $$query[$idx]{requested_class} && $class eq $$query[$idx]{requested_class} );
654 push(@$query, { classname => $class_part, (@field_parts ? (fields => \@field_parts) : ()), requested_class => $class, ftsquery => [], phrases => [] });
661 $self->{core_limit} = $l if ($l);
662 return $self->{core_limit};
668 $self->{superpage} = $l if ($l);
669 return $self->{superpage};
675 $self->{superpage_size} = $l if ($l);
676 return $self->{superpage_size};
680 #-------------------------------
681 package QueryParser::query_plan;
685 return undef unless ref($self);
686 return $self->{QueryParser};
691 $pkg = ref($pkg) || $pkg;
692 my %args = (joiner => '&', @_);
694 return bless \%args => $pkg;
699 my $pkg = ref($self) || $self;
700 my $node = do{$pkg.'::node'}->new( plan => $self, @_ );
701 $self->add_node( $node );
707 my $pkg = ref($self) || $self;
711 my $node = do{$pkg.'::facet'}->new( plan => $self, name => $name, 'values' => $args );
712 $self->add_node( $node );
719 my $pkg = ref($self) || $self;
723 my $node = do{$pkg.'::filter'}->new( plan => $self, name => $name, args => $args );
724 $self->add_filter( $node );
732 return undef unless ($needle);
733 return grep { $_->name eq $needle } @{ $self->filters };
739 return undef unless ($needle);
740 return grep { $_->name eq $needle } @{ $self->modifiers };
745 my $pkg = ref($self) || $self;
748 my $node = do{$pkg.'::modifier'}->new( $name );
749 $self->add_modifier( $node );
756 my $requested_class = shift;
759 for my $n (@{$self->{query}}) {
760 next unless (ref($n) && $n->isa( 'QueryParser::query_plan::node' ));
761 if ($n->requested_class eq $requested_class) {
768 $node = $self->new_node;
769 $node->requested_class( $requested_class );
777 return $self->{query};
784 $self->{query} ||= [];
785 push(@{$self->{query}}, $self->joiner) if (@{$self->{query}});
786 push(@{$self->{query}}, $node);
794 return $self->{level} ? 0 : 1;
799 return $self->{level};
806 $self->{joiner} = $joiner if ($joiner);
807 return $self->{joiner};
812 $self->{modifiers} ||= [];
813 return $self->{modifiers};
818 my $modifier = shift;
820 $self->{modifiers} ||= [];
821 return $self if (grep {$$_ eq $$modifier} @{$self->{modifiers}});
823 push(@{$self->{modifiers}}, $modifier);
830 $self->{facets} ||= [];
831 return $self->{facets};
838 $self->{facets} ||= [];
839 return $self if (grep {$_->name eq $facet->name} @{$self->{facets}});
841 push(@{$self->{facets}}, $facet);
848 $self->{filters} ||= [];
849 return $self->{filters};
856 $self->{filters} ||= [];
857 return $self if (grep {$_->name eq $filter->name} @{$self->{filters}});
859 push(@{$self->{filters}}, $filter);
865 #-------------------------------
866 package QueryParser::query_plan::node;
870 $pkg = ref($pkg) || $pkg;
873 return bless \%args => $pkg;
878 my $pkg = ref($self) || $self;
879 return do{$pkg.'::atom'}->new( @_ );
882 sub requested_class { # also split into classname and fields
887 my ($class_part, @field_parts) = split '\|', $class;
888 $class_part ||= $class;
890 $self->{requested_class} = $class;
891 $self->{classname} = $class_part;
892 $self->{fields} = \@field_parts;
895 return $self->{requested_class};
902 $self->{plan} = $plan if ($plan);
903 return $self->{plan};
910 $self->{classname} = $class if ($class);
911 return $self->{classname};
918 $self->{fields} ||= [];
919 $self->{fields} = \@fields if (@fields);
920 return $self->{fields};
927 $self->{phrases} ||= [];
928 $self->{phrases} = \@phrases if (@phrases);
929 return $self->{phrases};
936 push(@{$self->phrases}, $phrase);
943 my @query_atoms = @_;
945 $self->{query_atoms} ||= [];
946 $self->{query_atoms} = \@query_atoms if (@query_atoms);
947 return $self->{query_atoms};
958 $atom = $self->new_atom( content => $content, @parts );
961 push(@{$self->query_atoms}, $self->plan->joiner) if (@{$self->query_atoms});
962 push(@{$self->query_atoms}, $atom);
967 #-------------------------------
968 package QueryParser::query_plan::node::atom;
972 $pkg = ref($pkg) || $pkg;
975 return bless \%args => $pkg;
980 return undef unless (ref $self);
981 return $self->{node};
986 return undef unless (ref $self);
987 return $self->{content};
992 return undef unless (ref $self);
993 return $self->{prefix};
998 return undef unless (ref $self);
999 return $self->{suffix};
1002 #-------------------------------
1003 package QueryParser::query_plan::filter;
1007 $pkg = ref($pkg) || $pkg;
1010 return bless \%args => $pkg;
1015 return $self->{plan};
1020 return $self->{name};
1025 return $self->{args};
1028 #-------------------------------
1029 package QueryParser::query_plan::facet;
1033 $pkg = ref($pkg) || $pkg;
1036 return bless \%args => $pkg;
1041 return $self->{plan};
1046 return $self->{name};
1051 return $self->{'values'};
1054 #-------------------------------
1055 package QueryParser::query_plan::modifier;
1059 $pkg = ref($pkg) || $pkg;
1060 my $modifier = shift;
1062 return bless \$modifier => $pkg;