File Coverage

blib/lib/SQL/Amazon/Parser.pm
Criterion Covered Total %
statement 4 6 66.6
branch n/a
condition n/a
subroutine 2 2 100.0
pod n/a
total 6 8 75.0


line stmt bran cond sub pod time code
1             #
2             # Copyright (c) 2005, Presicient Corp., USA
3             #
4             # Permission is granted to use this software according to the terms of the
5             # Artistic License, as specified in the Perl README file,
6             # with the exception that commercial redistribution, either
7             # electronic or via physical media, as either a standalone package,
8             # or incorporated into a third party product, requires prior
9             # written approval of the author.
10             #
11             # This software is distributed in the hope that it will be useful,
12             # but WITHOUT ANY WARRANTY; without even the implied warranty of
13             # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
14             #
15             # Presicient Corp. reserves the right to provide support for this software
16             # to individual sites under a separate (possibly fee-based)
17             # agreement.
18             #
19             # History:
20             #
21             # 2005-Jan-27 D. Arnold
22             # Coded.
23             #
24             package SQL::Amazon::Parser;
25              
26 1     1   5 use Exporter;
  1         2  
  1         39  
27 1     1   1738 use SQL::Parser;
  0            
  0            
28             use Data::Dumper 'Dumper';
29             use Clone qw(clone);
30             use DBI;
31              
32             BEGIN {
33              
34             our @ISA = qw(Exporter SQL::Parser);
35              
36             use constant SQL_TREE_OP => 0;
37             use constant SQL_TREE_ARG1 => 1;
38             use constant SQL_TREE_ARG2 => 2;
39             use constant SQL_TREE_NEG => 3;
40             use constant SQL_TREE_TABLES => 4;
41              
42             use constant SQL_TREE_TYPE => 0;
43             use constant SQL_TREE_VALUE => 1;
44              
45             use constant SQL_PRED_CONJOIN => 0;
46             use constant SQL_PRED_TABLES => 1;
47              
48             our @EXPORT = ();
49             our @EXPORT_OK = ();
50              
51             our %EXPORT_TAGS = (
52             pred_node_codes => [
53             qw/SQL_TREE_OP SQL_TREE_ARG1 SQL_TREE_ARG2 SQL_TREE_NEG/
54             ]
55             );
56              
57             Exporter::export_tags(keys %EXPORT_TAGS);
58              
59             };
60              
61             use SQL::Amazon::StorageEngine;
62             use SQL::Amazon::ReqFactory;
63              
64             use strict;
65              
66             our $VERSION = '0.10';
67             my %neg_ops = (
68             '<', '>=',
69             '>', '<=',
70             '=', '<>',
71             '<>', '=',
72             '<=', '>',
73             '>=', '<'
74             );
75             my %transpose_ops = (
76             '<', '>',
77             '>', '<',
78             '=', '=',
79             '<>', '<>',
80             '<=', '>=',
81             '>=', '<='
82             );
83              
84             sub new {
85             my ($class, $flags) = @_;
86            
87             my $obj = $class->SUPER::new('Amazon', $flags);
88             return undef unless $obj;
89             $obj->LOAD('LOAD SQL::Amazon::Functions');
90             return $obj;
91             }
92             sub get_in {
93             my ($obj, $str) = @_;
94              
95             my $in_inside_parens = 0;
96              
97             my $strpos = 0;
98             my $replpos = 0;
99             while ($str =~ /\G(.+?)\b(NOT\s+)?IN \((.+)$/igcs ) {
100             my ($col, $contents);
101             my $front = $1;
102             my $back = $3;
103             my $not = $2 ? 1 : 0;
104             $strpos = $-[3];
105             $replpos = $-[1];
106             my $pos = ($front=~/^.+\b(AND|NOT|OR)\b(.+)$/igcs) ? $-[2] : 0;
107             pos($front) = $pos;
108             $in_inside_parens += ($1 eq '(') ? 1 : -1
109             while ($front=~/\G.*?([\(\)])/gcs);
110              
111             $obj->{struct}{errstr} = "Unmatched right parentheses during IN processing!",
112             return undef
113             if ($in_inside_parens < 0);
114             pos($front) = $pos;
115             $in_inside_parens--,
116             $pos = $+[0]
117             while ($in_inside_parens && ($front=~/\G.*?\(/gcs));
118             $col = substr($front, $pos);
119             $replpos += $pos;
120             my $funcstr = ($not ? ' AMZN_NOT_IN_ANY (' : ' AMZN_IN_ANY (') .
121             $col . ', ';
122              
123             substr($str, $replpos, $strpos - $replpos) = $funcstr;
124             pos($str) = $replpos + length($funcstr);
125             }
126              
127             $str =~ s/^\s+//;
128             $str =~ s/\s+$//;
129             $str =~ s/\(\s+/(/;
130             $str =~ s/\s+\)/)/;
131              
132             return $str;
133             }
134             sub transform_syntax {
135             my ($obj, $str) = @_;
136              
137             my $repl;
138             while ($str =~/\bMATCHES(\s+(ANY|ALL|TEXT))?\s*\(/i ) {
139             $repl = $2 ? 'AMZN_MATCH_' . uc $2 . '(' : 'AMZN_MATCH_ANY(';
140             $str=~s/\bMATCHES(\s+(ANY|ALL|TEXT))?\s*\(/$repl/i;
141             }
142             $str=~s/\bPOWER_SEARCH(\s*\()/AMZN_POWER_SEARCH$1/g;
143             return $str;
144             }
145             sub arrayify {
146             my $tree = shift;
147              
148             return (defined($tree) && ($tree ne '')) ?
149             ((ref $tree ne 'HASH') || (! $tree->{op})) ?
150             clone($tree) :
151             [ $tree->{op},
152             arrayify($tree->{arg1}),
153             arrayify($tree->{arg2}),
154             $tree->{neg} ] :
155             undef;
156             }
157              
158             sub hashify {
159             my $tree = shift;
160            
161             return (ref $tree eq 'ARRAY') ?
162             {
163             op => $tree->[SQL_TREE_OP],
164             arg1 => $tree->[SQL_TREE_ARG1],
165             arg2 => $tree->[SQL_TREE_ARG2],
166             neg => $tree->[SQL_TREE_NEG],
167             } : $tree;
168             }
169              
170             sub decomment {
171             my $sql = shift;
172             my $out = '';
173             my $spos = 0;
174             while ($sql=~/\G.*?(['"]|\/\*|--)/gcs) {
175             if ($1 eq "'") {
176             return ''
177             unless ($sql=~/\G.*?'/gcs);
178             }
179             elsif ($1 eq '"') {
180             return ''
181             unless ($sql=~/\G.*?"/gcs);
182             }
183             elsif ($1 eq '/*') {
184             $out .= substr($sql, $spos, $-[1] - $spos) . ' ';
185             return ''
186             unless ($sql=~/\G.*?\*\//gcs);
187             $spos = pos($sql);
188             }
189             elsif ($1 eq '--') {
190             $out .= substr($sql, $spos, $-[1] - $spos);
191             return $out
192             unless ($sql=~/\G.*?([\r\n])/gcs);
193             $spos = pos($sql) - 1;
194             }
195             }
196             $out .= substr($sql, $spos);
197             return $out;
198             }
199              
200             sub parse {
201             my ($obj, $sql) = @_;
202              
203             DBI->trace_msg("[SQL::Amazon::Parser::parse] Parsing query\n$sql", 3)
204             if $ENV{DBD_AMZN_DEBUG};
205             $sql = decomment($sql);
206             return undef
207             unless $obj->SUPER::parse($sql);
208             my $predary = $obj->{struct}{where_clause} ?
209             dnf_flatten(
210             dnf_recurse(
211             dnf_negate(
212             arrayify($obj->{struct}{where_clause}))), []) :
213             [ ];
214             $obj->{struct}{amzn_predicate} = $predary,
215             $obj->{struct}{amzn_requests} = [],
216             return $obj
217             if ($obj->{struct}{table_names} &&
218             ($#{$obj->{struct}{table_names}} == 0) &&
219             (uc $obj->{struct}{table_names}[0] eq 'SYSSCHEMA'));
220              
221             my $cachecnt = 0;
222             $cachecnt += (/^CACHED/i) ? 1 : 0
223             foreach (@{$obj->{struct}{table_names}});
224             $obj->{struct}{amzn_predicate} = $predary,
225             $obj->{struct}{amzn_requests} = [],
226             return $obj
227             if ($cachecnt == scalar @{$obj->{struct}{table_names}});
228             $cachecnt = 0;
229             my @amznreqs = ();
230             my @finalpreds = ();
231             my $reqobj;
232             my $single_table = $obj->{struct}{table_names}[0];
233             foreach my $pred (@$predary) {
234             my ($table, $reqclass);
235             my $requests = [];
236             $pred->[SQL_PRED_TABLES] = { $single_table => 1}
237             unless ($pred->[SQL_PRED_TABLES] &&
238             keys %{$pred->[SQL_PRED_TABLES]});
239              
240             my $cached = 1;
241              
242             foreach (keys %{$pred->[SQL_PRED_TABLES]}) {
243             ($table, $reqclass) =
244             SQL::Amazon::StorageEngine::has_table($_);
245             $obj->{struct}{errstr} = "Unknown table $_.",
246             return undef
247             unless $table;
248              
249             next if /^CACHED/i;
250              
251             $cached = undef;
252             next unless $reqclass;
253             push @$requests, [ $reqclass, $table ]
254             unless ($table=~/^CACHED/i);
255             }
256              
257             if ($cached) {
258             $cachecnt++;
259             push @finalpreds, $pred
260             if $pred;
261             next;
262             }
263              
264             $pred->[SQL_PRED_TABLES] =
265             SQL::Amazon::ReqFactory->cleanup_requests($requests);
266             $obj->{struct}{errstr} = SQL::Amazon::ReqFactory->errstr,
267             return undef
268             unless (scalar @{$pred->[SQL_PRED_TABLES]});
269             $obj->{struct}{errstr} =
270             'Invalid predicate: insufficient qualifiers to issue service request.',
271             return undef
272             unless scalar @$requests;
273             foreach (@$requests) {
274             ($pred, $reqobj) =
275             SQL::Amazon::ReqFactory->create_request(
276             $_->[0], $_->[1], $pred, $obj);
277             $obj->{struct}{errstr} = SQL::Amazon::ReqFactory->errstr,
278             return undef
279             unless (defined($pred) || defined($reqobj));
280              
281             push @finalpreds, $pred
282             if $pred;
283             push @amznreqs, $reqobj
284             if $reqobj;
285             }
286             }
287              
288             $obj->{struct}{errstr} =
289             'Invalid predicate: insufficient qualifiers to issue service request.',
290             return undef
291             unless (scalar @amznreqs ||
292             ($cachecnt == scalar @$predary));
293             $obj->{struct}{amzn_predicate} = \@finalpreds;
294             $obj->{struct}{amzn_requests} = \@amznreqs;
295             return $obj;
296             }
297              
298             sub negate_node {
299             my $node = shift;
300             $node->[SQL_TREE_OP] = $neg_ops{$node->[SQL_TREE_OP]},
301             delete $node->[SQL_TREE_NEG],
302             return $node
303             if $neg_ops{$node->[SQL_TREE_OP]};
304             $node->[SQL_TREE_NEG] = (! $node->[SQL_TREE_NEG]);
305             return $node;
306             }
307             sub dnf_negate {
308             my $node = shift;
309            
310             if ($node->[SQL_TREE_NEG]) {
311             if (($node->[SQL_TREE_OP] eq 'AND') ||
312             ($node->[SQL_TREE_OP] eq 'OR')) {
313             $node->[SQL_TREE_OP] = ($node->[SQL_TREE_OP] eq 'AND') ? 'OR' : 'AND';
314             negate_node($node->[SQL_TREE_ARG1]);
315             negate_node($node->[SQL_TREE_ARG2]);
316             }
317             else {
318             negate_node($node);
319             }
320             }
321             dnf_negate($node->[SQL_TREE_ARG1]),
322             dnf_negate($node->[SQL_TREE_ARG2])
323             if (($node->[SQL_TREE_OP] eq 'AND') ||
324             ($node->[SQL_TREE_OP] eq 'OR'));
325             $node;
326             }
327             sub dnf_find_tables {
328             my ($node, $tables) = @_;
329              
330             return undef
331             unless ((ref $node eq 'HASH') &&
332             ($node->{type} ne 'null'));
333              
334             if ($node->{type} eq 'column') {
335             $tables->{uc $1} = 1
336             if ($node->{value}=~/^([A-Z]\w*)\..+$/i);
337             return $tables;
338             }
339             elsif ($node->{value} eq 'multiple values') {
340             }
341             return undef;
342             }
343             sub dnf_recurse {
344             my ($node, $optimize) = shift;
345             return $node
346             if ($optimize &&
347             ($node->[SQL_TREE_ARG1][SQL_TREE_OP] ne 'OR') &&
348             ($node->[SQL_TREE_ARG2][SQL_TREE_OP] ne 'OR'));
349             if (($node->[SQL_TREE_OP] ne 'OR') &&
350             ($node->[SQL_TREE_OP] ne 'AND')) {
351             return $node
352             if $node->[SQL_TREE_TABLES];
353              
354             my $tables = {};
355             if (dnf_find_tables($node->[SQL_TREE_ARG1], $tables)) {
356             $node->[SQL_TREE_TABLES] = $tables
357             unless ($node->[SQL_TREE_ARG2] &&
358             dnf_find_tables($node->[SQL_TREE_ARG2], $tables));
359             return $node;
360             }
361              
362             $node->[SQL_TREE_TABLES] = $tables
363             if ($node->[SQL_TREE_ARG2] &&
364             dnf_find_tables($node->[SQL_TREE_ARG2], $tables));
365             return $node;
366             }
367             dnf_recurse($node->[SQL_TREE_ARG1], $optimize);
368             dnf_recurse($node->[SQL_TREE_ARG2], $optimize);
369             my ($temp, $newnode);
370             if ($node->[SQL_TREE_OP] eq 'AND') {
371              
372             if ($node->[SQL_TREE_ARG1][SQL_TREE_OP] eq 'OR') {
373             $temp = $node->[SQL_TREE_ARG1][SQL_TREE_ARG2];
374             $node->[SQL_TREE_ARG1][SQL_TREE_ARG2] = clone($node->[SQL_TREE_ARG2]);
375             $node->[SQL_TREE_ARG1][SQL_TREE_OP] = 'AND';
376             $newnode = [ 'AND', $temp, $node->[SQL_TREE_ARG2] ];
377             $node->[SQL_TREE_OP] = 'OR';
378             $node->[SQL_TREE_ARG2] = $newnode;
379             dnf_recurse($node->[SQL_TREE_ARG1], 1);
380             dnf_recurse($node->[SQL_TREE_ARG2], 1);
381             }
382             elsif ($node->[SQL_TREE_ARG2][SQL_TREE_OP] eq 'OR') {
383             $temp = $node->[SQL_TREE_ARG2][SQL_TREE_ARG2];
384             $node->[SQL_TREE_ARG2][SQL_TREE_ARG2] = clone($node->[SQL_TREE_ARG1]);
385             $node->[SQL_TREE_ARG2][SQL_TREE_OP] = 'AND';
386             $newnode = [ 'AND', $node->[SQL_TREE_ARG1], $temp ];
387             $node->[SQL_TREE_OP] = 'OR';
388             $node->[SQL_TREE_ARG1] = $newnode;
389             dnf_recurse($node->[SQL_TREE_ARG1], 1);
390             dnf_recurse($node->[SQL_TREE_ARG2], 1);
391             }
392             }
393             return $node;
394             }
395             sub dnf_flatten {
396             my ($tree, $dnfary) = @_;
397             dnf_flatten($tree->[SQL_TREE_ARG1], $dnfary),
398             dnf_flatten($tree->[SQL_TREE_ARG2], $dnfary),
399             $tree->[SQL_TREE_ARG1] = undef,
400             $tree->[SQL_TREE_ARG2] = undef,
401             return $dnfary
402             if ($tree->[SQL_TREE_OP] eq 'OR');
403             my $conjoins = [];
404             my $tables = {};
405             dnf_flatten_ANDs($tree, $conjoins, $tables);
406             push(@$dnfary, [ $conjoins, $tables ]);
407             return $dnfary;
408             }
409             sub dnf_flatten_ANDs {
410             my ($tree, $conjoins, $tables) = @_;
411             dnf_flatten_ANDs($tree->[SQL_TREE_ARG1], $conjoins, $tables),
412             dnf_flatten_ANDs($tree->[SQL_TREE_ARG2], $conjoins, $tables),
413             $tree->[SQL_TREE_ARG1] = undef,
414             $tree->[SQL_TREE_ARG2] = undef,
415             return $conjoins
416             if ($tree->[SQL_TREE_OP] eq 'AND');
417             my $t;
418             $t = $tree->[SQL_TREE_ARG1],
419             $tree->[SQL_TREE_ARG1] = $tree->[SQL_TREE_ARG2],
420             $tree->[SQL_TREE_ARG2] = $t,
421             $tree->[SQL_TREE_OP] = $transpose_ops{$tree->[SQL_TREE_OP]}
422             if ($transpose_ops{$tree->[SQL_TREE_OP]} &&
423             ((ref $tree->[SQL_TREE_ARG1] ne 'HASH') ||
424             ($tree->[SQL_TREE_ARG1]{type} ne 'column')) &&
425             (ref $tree->[SQL_TREE_ARG2] eq 'HASH') &&
426             ($tree->[SQL_TREE_ARG2]{type} eq 'column'));
427              
428             $tables->{$_} = 1,
429             $tree->[SQL_TREE_TABLES] = undef
430             foreach (keys %{$tree->[SQL_TREE_TABLES]});
431             push(@$conjoins, $tree);
432             return $conjoins;
433             }
434             sub dnf_test {
435             my $tree = shift;
436             print print_node($tree), "\n";
437             dnf_negate($tree);
438             dnf_recurse($tree);
439             print print_node($tree), "\n";
440              
441             return $tree;
442             }
443              
444             sub print_node {
445             my $tree = shift;
446            
447             return (($tree->[SQL_TREE_OP] eq 'AND') || ($tree->[SQL_TREE_OP] eq 'OR')) ?
448             '(' . $tree->[SQL_TREE_ARG1]->print_node . ') ' .
449             $tree->[SQL_TREE_OP] . ' (' .
450             $tree->[SQL_TREE_ARG2]->print_node . ')' :
451             '(' . $tree->[SQL_TREE_ARG1] . ' ' . $tree->[SQL_TREE_OP] . ' ' .
452             $tree->[SQL_TREE_ARG2] . ')';
453             }
454              
455              
456             1;
457