File Coverage

blib/lib/LucyX/Search/WildcardQuery.pm
Criterion Covered Total %
statement 9 9 100.0
branch n/a
condition n/a
subroutine 3 3 100.0
pod n/a
total 12 12 100.0


line stmt bran cond sub pod time code
1             package LucyX::Search::WildcardQuery;
2 1     1   46917 use strict;
  1         2  
  1         34  
3 1     1   6 use warnings;
  1         2  
  1         27  
4 1     1   6 use base qw( Lucy::Search::Query );
  1         6  
  1         819  
5             use Carp;
6             use Scalar::Util qw( blessed );
7             use LucyX::Search::WildcardCompiler;
8              
9             our $VERSION = '0.06';
10              
11             =head1 NAME
12              
13             LucyX::Search::WildcardQuery - Lucy query extension
14              
15             =head1 SYNOPSIS
16              
17             my $query = LucyX::Search::WildcardQuery->new(
18             term => 'green*',
19             field => 'color',
20             );
21             my $hits = $searcher->hits( query => $query );
22            
23              
24             =head1 DESCRIPTION
25              
26             LucyX::Search::WildcardQuery extends the
27             Lucy::QueryParser syntax
28             to support wildcards. This code is similar to the sample PrefixQuery
29             code in the Lucy distribution and the KinoSearch::Search::WildCardQuery
30             and Search::Query::Dialect::KSx::WildcardQuery module on CPAN.
31              
32             =head1 METHODS
33              
34             This class is a subclass of Lucy::Search::Query. Only new or overridden
35             methods are documented here.
36              
37             =cut
38              
39             # Inside-out member vars
40             my %term;
41             my %field;
42             my %regex;
43             my %prefix;
44             my %suffix;
45             my %lex_terms;
46              
47             =head2 new( I )
48              
49             Create a new WildcardQuery object. I must contain key/value pairs
50             for C and C.
51              
52             =cut
53              
54             sub new {
55             my ( $class, %args ) = @_;
56             my $term = delete $args{term};
57             my $field = delete $args{field};
58             my $self = $class->SUPER::new(%args);
59             confess("'term' param is required")
60             unless defined $term;
61             confess("Invalid term: '$term'")
62             unless $term =~ /[\*\?]/;
63             confess("'field' param is required")
64             unless defined $field;
65             $term{$$self} = $term;
66             $field{$$self} = $field;
67             $self->_build_regex($term);
68             return $self;
69             }
70              
71             sub _build_regex {
72             my ( $self, $term ) = @_;
73             $term = quotemeta($term); # turn into a regexp that matches a literal str
74             $term =~ s/\\\*/.*/g; # convert wildcards into regex
75             $term =~ s/\\\?/.?/g; # convert wildcards into regex
76             $term =~ s/(?:\.\*){2,}/.*/g; # eliminate multiple consecutive wild cards
77             $term =~ s/(?:\.\?){2,}/.?/g; # eliminate multiple consecutive wild cards
78             $term =~ s/^/^/; # unless $term =~ s/^\.\*//; # anchor the regexp to
79             $term
80             =~ s/\z/\\z/; # unless $term =~ s/\.\*\z//; # the ends of the term
81             $regex{$$self} = qr/$term/;
82              
83             # get the literal prefix of the regexp, if any.
84             if ($regex{$$self} =~ m<^
85             (?: # prefix for qr//'s, without allowing /i :
86             \(\? ([a-hj-z]*) (?:-[a-z]*)?:
87             )?
88             (\\[GA]|\^) # anchor
89             ([^#\$()*+.?[\]\\^]+) # literal pat (no metachars or comments)
90             >x
91             )
92             {
93             {
94             my ( $mod, $anchor, $prefix ) = ( $1 || '', $2, $3 );
95             $anchor eq '^' and $mod =~ /m/ and last;
96             for ($prefix) {
97             $mod =~ /x/ and s/\s+//g;
98             }
99             $prefix{$$self} = $prefix;
100             }
101             }
102              
103             if ( $term =~ m/\.[\?\*](\w+)/ ) {
104             my $suffix = $1;
105             $suffix{$$self} = $suffix;
106             }
107              
108             }
109              
110             =head2 get_term
111              
112             =head2 get_field
113              
114             Retrieve the value set in new().
115              
116             =head2 get_regex
117              
118             Retrieve the qr// object representing I.
119              
120             =head2 get_prefix
121              
122             Retrieve the literal string (if any) that precedes the wildcards
123             in I.
124              
125             =head2 get_suffix
126              
127             Retrieve the literal string (if any) that follows the wildcards
128             in I.
129              
130             =cut
131              
132             sub get_term { my $self = shift; return $term{$$self} }
133             sub get_field { my $self = shift; return $field{$$self} }
134             sub get_regex { my $self = shift; return $regex{$$self} }
135             sub get_prefix { my $self = shift; return $prefix{$$self} }
136             sub get_suffix { my $self = shift; return $suffix{$$self} }
137              
138             =head2 add_lex_term( I )
139              
140             Push I onto the stack of lexicon terms that this Query matches.
141              
142             =cut
143              
144             sub add_lex_term {
145             my $self = shift;
146             my $t = shift;
147             croak "term required" unless defined $t;
148             $lex_terms{$$self}->{$t}++;
149             }
150              
151             =head2 get_lex_terms
152              
153             Returns array ref of terms in the lexicons that this
154             query matches.
155              
156             =cut
157              
158             sub get_lex_terms {
159             my $self = shift;
160             return [ keys %{ $lex_terms{$$self} } ];
161             }
162              
163             sub DESTROY {
164             my $self = shift;
165             delete $term{$$self};
166             delete $field{$$self};
167             delete $prefix{$$self};
168             delete $suffix{$$self};
169             delete $regex{$$self};
170             delete $lex_terms{$$self};
171             $self->SUPER::DESTROY;
172             }
173              
174             =head2 equals
175              
176             Returns true (1) if the object represents the same kind of query
177             clause as another WildcardQuery.
178              
179             NOTE: Currently a NOTWildcardQuery and a WildcardQuery object will
180             evaluate as equal if they have the same terma and field. This is a bug.
181              
182             =cut
183              
184             sub equals {
185             my ( $self, $other ) = @_;
186             return 0 unless blessed($other);
187             return 0
188             unless $other->isa( blessed($self) );
189             return 0 unless $self->get_field eq $other->get_field;
190             return 0 unless $self->get_term eq $other->get_term;
191             return 1;
192             }
193              
194             =head2 to_string
195              
196             Returns the query clause the object represents.
197              
198             =cut
199              
200             sub to_string {
201             my $self = shift;
202             return "$field{$$self}:$term{$$self}";
203             }
204              
205             =head2 make_compiler
206              
207             Returns a LucyX::Search::WildcardCompiler object.
208              
209             =cut
210              
211             sub make_compiler {
212             my $self = shift;
213             my %args = @_;
214             my $subordinate = delete $args{subordinate}; # new in Lucy 0.2.2
215             $args{parent} = $self;
216             my $compiler = LucyX::Search::WildcardCompiler->new(%args);
217              
218             # unlike Search::Query synopsis, normalize()
219             # is called internally in $compiler.
220             # This should be fixed in a C re-write.
221             #$compiler->normalize unless $subordinate;
222              
223             return $compiler;
224             }
225              
226             1;
227              
228             __END__