17 sub search_class_count {
19 return @{$self->search_classes};
24 return @{$self->filters};
29 return @{$self->modifiers};
34 $class = ref($class) || $class;
36 $parser_config{$class}{custom_data} ||= {};
37 return $parser_config{$class}{custom_data};
42 $class = ref($class) || $class;
44 $parser_config{$class}{operators} ||= {};
45 return $parser_config{$class}{operators};
50 $class = ref($class) || $class;
52 $parser_config{$class}{filters} ||= [];
53 return $parser_config{$class}{filters};
58 $class = ref($class) || $class;
60 $parser_config{$class}{modifiers} ||= [];
61 return $parser_config{$class}{modifiers};
66 $class = ref($class) || $class;
70 my $self = bless {} => $class;
72 for my $o (keys %{QueryParser->operators}) {
73 $class->operator($o => QueryParser->operator($o)) unless ($class->operator($o));
76 for my $opt ( keys %opts) {
77 $self->$opt( $opts{$opt} ) if ($self->can($opt));
85 my $pkg = ref($self) || $self;
86 return do{$pkg.'::query_plan'}->new( QueryParser => $self, @_ );
89 sub add_search_filter {
91 $pkg = ref($pkg) || $pkg;
94 return $filter if (grep { $_ eq $filter } @{$pkg->filters});
95 push @{$pkg->filters}, $filter;
99 sub add_search_modifier {
101 $pkg = ref($pkg) || $pkg;
102 my $modifier = shift;
104 return $modifier if (grep { $_ eq $modifier } @{$pkg->modifiers});
105 push @{$pkg->modifiers}, $modifier;
109 sub add_search_class {
111 $pkg = ref($pkg) || $pkg;
114 return $class if (grep { $_ eq $class } @{$pkg->search_classes});
116 push @{$pkg->search_classes}, $class;
117 $pkg->search_fields->{$class} = [];
118 $pkg->default_search_class( $pkg->search_classes->[0] ) if (@{$pkg->search_classes} == 1);
125 $class = ref($class) || $class;
129 return undef unless ($opname);
131 $parser_config{$class}{operators} ||= {};
132 $parser_config{$class}{operators}{$opname} = $op if ($op);
134 return $parser_config{$class}{operators}{$opname};
139 $class = ref($class) || $class;
142 $parser_config{$class}{classes} ||= [];
143 $parser_config{$class}{classes} = $classes if (ref($classes) && @$classes);
144 return $parser_config{$class}{classes};
147 sub add_query_normalizer {
149 $pkg = ref($pkg) || $pkg;
153 my $params = shift || [];
155 return $func if (grep { $_ eq $func } @{$pkg->query_normalizers->{$class}->{$field}});
157 push(@{$pkg->query_normalizers->{$class}->{$field}}, { function => $func, params => $params });
162 sub query_normalizers {
164 $pkg = ref($pkg) || $pkg;
169 $parser_config{$pkg}{normalizers} ||= {};
172 $parser_config{$pkg}{normalizers}{$class}{$field} ||= [];
173 return $parser_config{$pkg}{normalizers}{$class}{$field};
175 return $parser_config{$pkg}{normalizers}{$class};
179 return $parser_config{$pkg}{normalizers};
182 sub default_search_class {
184 $pkg = ref($pkg) || $pkg;
186 $QueryParser::parser_config{$pkg}{default_class} = $pkg->add_search_class( $class ) if $class;
188 return $QueryParser::parser_config{$pkg}{default_class};
191 sub remove_search_class {
193 $pkg = ref($pkg) || $pkg;
196 return $class if (!grep { $_ eq $class } @{$pkg->search_classes});
198 $pkg->search_classes( [ grep { $_ ne $class } @{$pkg->search_classes} ] );
199 delete $QueryParser::parser_config{$pkg}{fields}{$class};
204 sub add_search_field {
206 $pkg = ref($pkg) || $pkg;
210 $pkg->add_search_class( $class );
212 return { $class => $field } if (grep { $_ eq $field } @{$pkg->search_fields->{$class}});
214 push @{$pkg->search_fields->{$class}}, $field;
216 return { $class => $field };
221 $class = ref($class) || $class;
223 $parser_config{$class}{fields} ||= {};
224 return $parser_config{$class}{fields};
227 sub add_search_class_alias {
229 $pkg = ref($pkg) || $pkg;
233 $pkg->add_search_class( $class );
235 return { $class => $alias } if (grep { $_ eq $alias } @{$pkg->search_class_aliases->{$class}});
237 push @{$pkg->search_class_aliases->{$class}}, $alias;
239 return { $class => $alias };
242 sub search_class_aliases {
244 $class = ref($class) || $class;
246 $parser_config{$class}{class_map} ||= {};
247 return $parser_config{$class}{class_map};
250 sub add_search_field_alias {
252 $pkg = ref($pkg) || $pkg;
257 return { $class => { $field => $alias } } if (grep { $_ eq $alias } @{$pkg->search_field_aliases->{$class}{$field}});
259 push @{$pkg->search_field_aliases->{$class}{$field}}, $alias;
261 return { $class => { $field => $alias } };
264 sub search_field_aliases {
266 $class = ref($class) || $class;
268 $parser_config{$class}{field_alias_map} ||= {};
269 return $parser_config{$class}{field_alias_map};
272 sub remove_search_field {
274 $pkg = ref($pkg) || $pkg;
278 return { $class => $field } if (!$pkg->search_fields->{$class} || !grep { $_ eq $field } @{$pkg->search_fields->{$class}});
280 $pkg->search_fields->{$class} = [ grep { $_ ne $field } @{$pkg->search_fields->{$class}} ];
282 return { $class => $field };
285 sub remove_search_field_alias {
287 $pkg = ref($pkg) || $pkg;
292 return { $class => { $field => $alias } } if (!$pkg->search_field_aliases->{$class}{$field} || !grep { $_ eq $alias } @{$pkg->search_field_aliases->{$class}{$field}});
294 $pkg->search_field_aliases->{$class}{$field} = [ grep { $_ ne $alias } @{$pkg->search_field_aliases->{$class}{$field}} ];
296 return { $class => { $field => $alias } };
299 sub remove_search_class_alias {
301 $pkg = ref($pkg) || $pkg;
305 return { $class => $alias } if (!$pkg->search_class_aliases->{$class} || !grep { $_ eq $alias } @{$pkg->search_class_aliases->{$class}});
307 $pkg->search_class_aliases->{$class} = [ grep { $_ ne $alias } @{$pkg->search_class_aliases->{$class}} ];
309 return { $class => $alias };
315 $self->{_debug} = $q if (defined $q);
316 return $self->{_debug};
322 $self->{_query} = $q if (defined $q);
323 return $self->{_query};
329 $self->{_parse_tree} = $q if (defined $q);
330 return $self->{_parse_tree};
337 $self->query( shift() )
346 my $pkg = ref($self) || $self;;
349 my $current_class = shift || $self->default_search_class;
351 my $recursing = shift || 0;
353 # Build the search class+field uber-regexp
354 my $search_class_re = '^\s*(';
357 for my $class ( keys %{$pkg->search_field_aliases} ) {
359 for my $field ( keys %{$pkg->search_field_aliases->{$class}} ) {
361 for my $alias ( @{$pkg->search_field_aliases->{$class}{$field}} ) {
363 s/\b$alias[:=]/$class\|$field:/g;
366 $search_class_re .= '|' unless ($first_class);
369 $search_class_re .= $class;
373 for my $class ( keys %{$pkg->search_class_aliases} ) {
375 for my $alias ( @{$pkg->search_class_aliases->{$class}} ) {
377 s/(^|[^|])\b$alias\|/$1$class\|/g;
378 s/(^|[^|])\b$alias[:=]/$1$class:/g;
381 $search_class_re .= '|' unless ($first_class);
384 $search_class_re .= $class . '(?:\|\w+)*';
386 $search_class_re .= '):';
388 my $required_re = $pkg->operator('required');
389 $required_re = qr/^\s*\Q$required_re\E/;
390 my $and_re = $pkg->operator('and');
391 $and_re = qr/^\s*\Q$and_re\E/;
393 my $or_re = $pkg->operator('or');
394 $or_re = qr/^\s*\Q$or_re\E/;
396 my $group_start_re = $pkg->operator('group_start');
397 $group_start_re = qr/^\s*\Q$group_start_re\E/;
399 my $group_end = $pkg->operator('group_end');
400 my $group_end_re = qr/^\s*\Q$group_end\E/;
402 my $modifier_tag_re = $pkg->operator('modifier');
403 $modifier_tag_re = qr/^\s*\Q$modifier_tag_re\E/;
406 # Build the filter and modifier uber-regexps
407 my $filter_re = '^\s*(' . join( '|', @{$pkg->filters}) . ')\(([^()]+)\)';
408 my $filter_as_class_re = '^\s*(' . join( '|', @{$pkg->filters}) . '):\s*(\S+)';
410 my $modifier_re = '^\s*'.$modifier_tag_re.'(' . join( '|', @{$pkg->modifiers}) . ')\b';
411 my $modifier_as_class_re = '^\s*(' . join( '|', @{$pkg->modifiers}) . '):\s*(\S+)';
413 my $struct = $self->new_plan( level => $recursing );
417 while (!$remainder) {
418 if (/$group_end_re/) { # end of an explicit group
419 warn "Encountered explicit group end\n" if $self->debug;
425 } elsif ($self->filter_count && /$filter_re/) { # found a filter
426 warn "Encountered search filter: $1 set to $2\n" if $self->debug;
429 $struct->new_filter( $1 => [ split '[, ]+', $2 ] );
432 } elsif ($self->filter_count && /$filter_as_class_re/) { # found a filter
433 warn "Encountered search filter: $1 set to $2\n" if $self->debug;
436 $struct->new_filter( $1 => [ split '[, ]+', $2 ] );
439 } elsif ($self->modifier_count && /$modifier_re/) { # found a modifier
440 warn "Encountered search modifier: $1\n" if $self->debug;
443 if (!$struct->top_plan) {
444 warn " Search modifiers only allowed at the top level of the query\n" if $self->debug;
446 $struct->new_modifier($1);
450 } elsif ($self->modifier_count && /$modifier_as_class_re/) { # found a modifier
451 warn "Encountered search modifier: $1\n" if $self->debug;
456 if (!$struct->top_plan) {
457 warn " Search modifiers only allowed at the top level of the query\n" if $self->debug;
458 } elsif ($2 =~ /^[ty1]/i) {
459 $struct->new_modifier($mod);
463 } elsif (/$group_start_re/) { # start of an explicit group
464 warn "Encountered explicit group start\n" if $self->debug;
466 my ($substruct, $subremainder) = $self->decompose( $', $current_class, $recursing + 1 );
467 $struct->add_node( $substruct );
471 } elsif (/$and_re/) { # ANDed expression
473 next if ($last_type eq 'AND');
474 next if ($last_type eq 'OR');
475 warn "Encountered AND\n" if $self->debug;
477 $struct->joiner( '&' );
480 } elsif (/$or_re/) { # ORed expression
482 next if ($last_type eq 'AND');
483 next if ($last_type eq 'OR');
484 warn "Encountered OR\n" if $self->debug;
486 $struct->joiner( '|' );
489 } elsif ($self->search_class_count && /$search_class_re/) { # changing current class
490 warn "Encountered class change: $1\n" if $self->debug;
493 $struct->classed_node( $current_class );
497 } elsif (/^\s*"([^"]+)"/) { # phrase, always anded
498 warn "Encountered phrase: $1\n" if $self->debug;
500 $struct->joiner( '&' );
503 my $class_node = $struct->classed_node($current_class);
504 $class_node->add_phrase( $phrase );
508 } elsif (/$required_re([^\s)]+)/) { # phrase, always anded
509 warn "Encountered required atom (mini phrase): $1\n" if $self->debug;
513 my $class_node = $struct->classed_node($current_class);
514 $class_node->add_phrase( $phrase );
516 $struct->joiner( '&' );
519 } elsif (/^\s*([^$group_end\s]+)/o) { # atom
520 warn "Encountered atom: $1\n" if $self->debug;
521 warn "Remainder: $'\n" if $self->debug;
526 my $class_node = $struct->classed_node($current_class);
527 my $negator = ($atom =~ s/^-//o) ? '!' : '';
529 $class_node->add_fts_atom( $atom, prefix => $negator, node => $class_node );
530 $struct->joiner( '&' );
540 return $struct if !wantarray;
541 return ($struct, $remainder);
544 sub find_class_index {
548 my ($class_part, @field_parts) = split '\|', $class;
549 $class_part ||= $class;
551 for my $idx ( 0 .. scalar(@$query) - 1 ) {
552 next unless ref($$query[$idx]);
553 return $idx if ( $$query[$idx]{requested_class} && $class eq $$query[$idx]{requested_class} );
556 push(@$query, { classname => $class_part, (@field_parts ? (fields => \@field_parts) : ()), requested_class => $class, ftsquery => [], phrases => [] });
563 $self->{core_limit} = $l if ($l);
564 return $self->{core_limit};
570 $self->{superpage} = $l if ($l);
571 return $self->{superpage};
577 $self->{superpage_size} = $l if ($l);
578 return $self->{superpage_size};
582 #-------------------------------
583 package QueryParser::query_plan;
587 return undef unless ref($self);
588 return $self->{QueryParser};
593 $pkg = ref($pkg) || $pkg;
594 my %args = (joiner => '&', @_);
596 return bless \%args => $pkg;
601 my $pkg = ref($self) || $self;
602 my $node = do{$pkg.'::node'}->new( plan => $self, @_ );
603 $self->add_node( $node );
609 my $pkg = ref($self) || $self;
613 my $node = do{$pkg.'::filter'}->new( plan => $self, name => $name, args => $args );
614 $self->add_filter( $node );
622 return undef unless ($needle);
623 return grep { $_->name eq $needle } @{ $self->filters };
629 return undef unless ($needle);
630 return grep { $_->name eq $needle } @{ $self->modifiers };
635 my $pkg = ref($self) || $self;
638 my $node = do{$pkg.'::modifier'}->new( $name );
639 $self->add_modifier( $node );
646 my $requested_class = shift;
649 for my $n (@{$self->{query}}) {
650 next unless (ref($n) && $n->isa( 'QueryParser::query_plan::node' ));
651 if ($n->requested_class eq $requested_class) {
658 $node = $self->new_node;
659 $node->requested_class( $requested_class );
667 return $self->{query};
674 $self->{query} ||= [];
675 push(@{$self->{query}}, $self->joiner) if (@{$self->{query}});
676 push(@{$self->{query}}, $node);
684 return $self->{level} ? 0 : 1;
689 return $self->{level};
696 $self->{joiner} = $joiner if ($joiner);
697 return $self->{joiner};
702 $self->{modifiers} ||= [];
703 return $self->{modifiers};
708 my $modifier = shift;
710 $self->{modifiers} ||= [];
711 return $self if (grep {$$_ eq $$modifier} @{$self->{modifiers}});
713 push(@{$self->{modifiers}}, $modifier);
720 $self->{filters} ||= [];
721 return $self->{filters};
728 $self->{filters} ||= [];
729 return $self if (grep {$_->name eq $filter->name} @{$self->{filters}});
731 push(@{$self->{filters}}, $filter);
737 #-------------------------------
738 package QueryParser::query_plan::node;
742 $pkg = ref($pkg) || $pkg;
745 return bless \%args => $pkg;
750 my $pkg = ref($self) || $self;
751 return do{$pkg.'::atom'}->new( @_ );
754 sub requested_class { # also split into classname and fields
759 my ($class_part, @field_parts) = split '\|', $class;
760 $class_part ||= $class;
762 $self->{requested_class} = $class;
763 $self->{classname} = $class_part;
764 $self->{fields} = \@field_parts;
767 return $self->{requested_class};
774 $self->{plan} = $plan if ($plan);
775 return $self->{plan};
782 $self->{classname} = $class if ($class);
783 return $self->{classname};
790 $self->{fields} ||= [];
791 $self->{fields} = \@fields if (@fields);
792 return $self->{fields};
799 $self->{phrases} ||= [];
800 $self->{phrases} = \@phrases if (@phrases);
801 return $self->{phrases};
808 push(@{$self->phrases}, $phrase);
815 my @query_atoms = @_;
817 $self->{query_atoms} ||= [];
818 $self->{query_atoms} = \@query_atoms if (@query_atoms);
819 return $self->{query_atoms};
830 $atom = $self->new_atom( content => $content, @parts );
833 push(@{$self->query_atoms}, $self->plan->joiner) if (@{$self->query_atoms});
834 push(@{$self->query_atoms}, $atom);
839 #-------------------------------
840 package QueryParser::query_plan::node::atom;
844 $pkg = ref($pkg) || $pkg;
847 return bless \%args => $pkg;
852 return undef unless (ref $self);
853 return $self->{node};
858 return undef unless (ref $self);
859 return $self->{content};
864 return undef unless (ref $self);
865 return $self->{prefix};
868 #-------------------------------
869 package QueryParser::query_plan::filter;
873 $pkg = ref($pkg) || $pkg;
876 return bless \%args => $pkg;
881 return $self->{plan};
886 return $self->{name};
891 return $self->{args};
894 #-------------------------------
895 package QueryParser::query_plan::modifier;
899 $pkg = ref($pkg) || $pkg;
900 my $modifier = shift;
902 return bless \$modifier => $pkg;