]>
Commit | Line | Data |
---|---|---|
84fb5b46 MKG |
1 | # BEGIN BPS TAGGED BLOCK {{{ |
2 | # | |
3 | # COPYRIGHT: | |
4 | # | |
3ffc5f4f | 5 | # This software is Copyright (c) 1996-2014 Best Practical Solutions, LLC |
84fb5b46 MKG |
6 | # <sales@bestpractical.com> |
7 | # | |
8 | # (Except where explicitly superseded by other copyright notices) | |
9 | # | |
10 | # | |
11 | # LICENSE: | |
12 | # | |
13 | # This work is made available to you under the terms of Version 2 of | |
14 | # the GNU General Public License. A copy of that license should have | |
15 | # been provided with this software, but in any event can be snarfed | |
16 | # from www.gnu.org. | |
17 | # | |
18 | # This work is distributed in the hope that it will be useful, but | |
19 | # WITHOUT ANY WARRANTY; without even the implied warranty of | |
20 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
21 | # General Public License for more details. | |
22 | # | |
23 | # You should have received a copy of the GNU General Public License | |
24 | # along with this program; if not, write to the Free Software | |
25 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA | |
26 | # 02110-1301 or visit their web page on the internet at | |
27 | # http://www.gnu.org/licenses/old-licenses/gpl-2.0.html. | |
28 | # | |
29 | # | |
30 | # CONTRIBUTION SUBMISSION POLICY: | |
31 | # | |
32 | # (The following paragraph is not intended to limit the rights granted | |
33 | # to you to modify and distribute this software under the terms of | |
34 | # the GNU General Public License and is only of importance to you if | |
35 | # you choose to contribute your changes and enhancements to the | |
36 | # community by submitting them to Best Practical Solutions, LLC.) | |
37 | # | |
38 | # By intentionally submitting any modifications, corrections or | |
39 | # derivatives to this work, or any other work intended for use with | |
40 | # Request Tracker, to Best Practical Solutions, LLC, you confirm that | |
41 | # you are the copyright holder for those contributions and you grant | |
42 | # Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable, | |
43 | # royalty-free, perpetual, license to use, copy, create derivative | |
44 | # works based on those contributions, and sublicense and distribute | |
45 | # those contributions and any derivatives thereof. | |
46 | # | |
47 | # END BPS TAGGED BLOCK }}} | |
48 | ||
49 | package RT::SQL; | |
50 | ||
51 | use strict; | |
52 | use warnings; | |
53 | ||
54 | ||
84fb5b46 MKG |
55 | # States |
56 | use constant VALUE => 1; | |
57 | use constant AGGREG => 2; | |
58 | use constant OP => 4; | |
59 | use constant OPEN_PAREN => 8; | |
60 | use constant CLOSE_PAREN => 16; | |
61 | use constant KEYWORD => 32; | |
62 | my @tokens = qw[VALUE AGGREGATOR OPERATOR OPEN_PAREN CLOSE_PAREN KEYWORD]; | |
63 | ||
64 | use Regexp::Common qw /delimited/; | |
65 | my $re_aggreg = qr[(?i:AND|OR)]; | |
66 | my $re_delim = qr[$RE{delimited}{-delim=>qq{\'\"}}]; | |
403d7b0b | 67 | my $re_value = qr[[+-]?\d+|(?i:NULL)|$re_delim]; |
84fb5b46 MKG |
68 | my $re_keyword = qr[[{}\w\.]+|$re_delim]; |
69 | my $re_op = qr[=|!=|>=|<=|>|<|(?i:IS NOT)|(?i:IS)|(?i:NOT LIKE)|(?i:LIKE)|(?i:NOT STARTSWITH)|(?i:STARTSWITH)|(?i:NOT ENDSWITH)|(?i:ENDSWITH)]; # long to short | |
70 | my $re_open_paren = qr[\(]; | |
71 | my $re_close_paren = qr[\)]; | |
72 | ||
73 | sub ParseToArray { | |
74 | my ($string) = shift; | |
75 | ||
76 | my ($tree, $node, @pnodes); | |
77 | $node = $tree = []; | |
78 | ||
79 | my %callback; | |
80 | $callback{'OpenParen'} = sub { push @pnodes, $node; $node = []; push @{ $pnodes[-1] }, $node }; | |
81 | $callback{'CloseParen'} = sub { $node = pop @pnodes }; | |
82 | $callback{'EntryAggregator'} = sub { push @$node, $_[0] }; | |
83 | $callback{'Condition'} = sub { push @$node, { key => $_[0], op => $_[1], value => $_[2] } }; | |
84 | ||
85 | Parse($string, \%callback); | |
86 | return $tree; | |
87 | } | |
88 | ||
89 | sub Parse { | |
90 | my ($string, $cb) = @_; | |
91 | my $loc = sub {HTML::Mason::Commands::loc(@_)}; | |
92 | $string = '' unless defined $string; | |
93 | ||
94 | my $want = KEYWORD | OPEN_PAREN; | |
95 | my $last = 0; | |
96 | ||
97 | my $depth = 0; | |
98 | my ($key,$op,$value) = ("","",""); | |
99 | ||
100 | # order of matches in the RE is important.. op should come early, | |
101 | # because it has spaces in it. otherwise "NOT LIKE" might be parsed | |
102 | # as a keyword or value. | |
103 | ||
104 | while ($string =~ /( | |
105 | $re_aggreg | |
106 | |$re_op | |
107 | |$re_keyword | |
108 | |$re_value | |
109 | |$re_open_paren | |
110 | |$re_close_paren | |
111 | )/iogx ) | |
112 | { | |
113 | my $match = $1; | |
114 | ||
115 | # Highest priority is last | |
116 | my $current = 0; | |
117 | $current = OP if ($want & OP) && $match =~ /^$re_op$/io; | |
118 | $current = VALUE if ($want & VALUE) && $match =~ /^$re_value$/io; | |
119 | $current = KEYWORD if ($want & KEYWORD) && $match =~ /^$re_keyword$/io; | |
120 | $current = AGGREG if ($want & AGGREG) && $match =~ /^$re_aggreg$/io; | |
121 | $current = OPEN_PAREN if ($want & OPEN_PAREN) && $match =~ /^$re_open_paren$/io; | |
122 | $current = CLOSE_PAREN if ($want & CLOSE_PAREN) && $match =~ /^$re_close_paren$/io; | |
123 | ||
124 | ||
125 | unless ($current && $want & $current) { | |
126 | my $tmp = substr($string, 0, pos($string)- length($match)); | |
127 | $tmp .= '>'. $match .'<--here'. substr($string, pos($string)); | |
128 | my $msg = $loc->("Wrong query, expecting a [_1] in '[_2]'", _BitmaskToString($want), $tmp); | |
129 | return $cb->{'Error'}->( $msg ) if $cb->{'Error'}; | |
130 | die $msg; | |
131 | } | |
132 | ||
133 | # State Machine: | |
134 | ||
135 | # Parens are highest priority | |
136 | if ( $current & OPEN_PAREN ) { | |
137 | $cb->{'OpenParen'}->(); | |
138 | $depth++; | |
139 | $want = KEYWORD | OPEN_PAREN; | |
140 | } | |
141 | elsif ( $current & CLOSE_PAREN ) { | |
142 | $cb->{'CloseParen'}->(); | |
143 | $depth--; | |
144 | $want = AGGREG; | |
145 | $want |= CLOSE_PAREN if $depth; | |
146 | } | |
147 | elsif ( $current & AGGREG ) { | |
148 | $cb->{'EntryAggregator'}->( $match ); | |
149 | $want = KEYWORD | OPEN_PAREN; | |
150 | } | |
151 | elsif ( $current & KEYWORD ) { | |
152 | $key = $match; | |
153 | $want = OP; | |
154 | } | |
155 | elsif ( $current & OP ) { | |
156 | $op = $match; | |
157 | $want = VALUE; | |
158 | } | |
159 | elsif ( $current & VALUE ) { | |
160 | $value = $match; | |
161 | ||
162 | # Remove surrounding quotes and unescape escaped | |
163 | # characters from $key, $match | |
164 | for ( $key, $value ) { | |
165 | if ( /$re_delim/o ) { | |
166 | substr($_,0,1) = ""; | |
167 | substr($_,-1,1) = ""; | |
168 | } | |
169 | s!\\(.)!$1!g; | |
170 | } | |
171 | ||
172 | $cb->{'Condition'}->( $key, $op, $value ); | |
173 | ||
174 | ($key,$op,$value) = ("","",""); | |
175 | $want = AGGREG; | |
176 | $want |= CLOSE_PAREN if $depth; | |
177 | } else { | |
178 | my $msg = $loc->("Query parser is lost"); | |
179 | return $cb->{'Error'}->( $msg ) if $cb->{'Error'}; | |
180 | die $msg; | |
181 | } | |
182 | ||
183 | $last = $current; | |
184 | } # while | |
185 | ||
186 | unless( !$last || $last & (CLOSE_PAREN | VALUE) ) { | |
187 | my $msg = $loc->("Incomplete query, last element ([_1]) is not close paren or value in '[_2]'", | |
188 | _BitmaskToString($last), | |
189 | $string); | |
190 | return $cb->{'Error'}->( $msg ) if $cb->{'Error'}; | |
191 | die $msg; | |
192 | } | |
193 | ||
194 | if( $depth ) { | |
3ffc5f4f | 195 | my $msg = $loc->("Incomplete query, [quant,_1,unclosed paren,unclosed parens] in '[_2]'", $depth, $string); |
84fb5b46 MKG |
196 | return $cb->{'Error'}->( $msg ) if $cb->{'Error'}; |
197 | die $msg; | |
198 | } | |
199 | } | |
200 | ||
201 | sub _BitmaskToString { | |
202 | my $mask = shift; | |
203 | ||
204 | my @res; | |
205 | for( my $i = 0; $i<@tokens; $i++ ) { | |
206 | next unless $mask & (1<<$i); | |
207 | push @res, $tokens[$i]; | |
208 | } | |
209 | ||
210 | my $tmp = join ', ', splice @res, 0, -1; | |
211 | unshift @res, $tmp if $tmp; | |
212 | return join ' or ', @res; | |
213 | } | |
214 | ||
84fb5b46 MKG |
215 | RT::Base->_ImportOverlays(); |
216 | ||
217 | 1; |