File Coverage

blib/lib/Search/Query/Dialect/KSx/WildcardQuery.pm
Criterion Covered Total %
statement 66 78 84.6
branch 6 22 27.2
condition 0 5 0.0
subroutine 16 17 94.1
pod 9 9 100.0
total 97 131 74.0


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