File Coverage

File:grammar/Select.yp
Coverage:88.4%

linestmtbrancondsubpodtimecode
1#
2# Select.yp
3#
4#
5%{
6
7my (
8    @Models, @Columns, @OutVars,
9    $InVals, %Defaults, $Quote, $QuoteIdent,
10    @Unbound,
11);
12
13%}
14
15%left 'and'
16
88
0
625
%left 'or'
17
18%%
19
20miniSQL: statement
21       ;
22
23statement: select_stmt ';'
24         | select_stmt
25         ;
26
27select_stmt: 'select' pattern_list postfix_clause_list
28
79
20030
             { join(' ', @_[1..$#_]) }
29           | 'select' pattern_list
30
1
363
             { join(' ', @_[1..$#_]) }
31           ;
32
33models: model ',' models
34
11
3377
             { join(' ', @_[1..$#_]) }
35      | model
36      ;
37
38
91
91
26933
765
model: symbol { push @Models, $_[1]; $QuoteIdent->($_[1]) }
39     ;
40
41pattern_list: pattern ',' pattern_list
42
0
0
                { join(' ', @_[1..$#_]) }
43            | pattern
44            ;
45
46pattern: aggregate alias
47       | aggregate
48       | proc_call
49       | column
50       | '*'
51       ;
52
53aggregate: func '(' column ')'
54
0
0
             { join(' ', @_[1..$#_]) }
55         | func '(' '*' ')'
56
3
743
             { join(' ', @_[1..$#_]) }
57         ;
58
59func: 'max'
60    | 'min'
61    | 'count'
62    | 'sum'
63    ;
64
65proc_call: IDENT '(' parameter_list ')'
66
6
2182
             { join(' ', @_[1..$#_]) }
67         ;
68
69parameter_list: parameter ',' parameter_list
70
5
1761
                { join(' ', @_[1..$#_]) }
71              | parameter
72              ;
73
74parameter: string
75         | NUM
76         ;
77
78constant: STRING | NUM;
79
80
29
6127
string: STRING { $Quote->(parse_string($_[1])) }
81      | VAR '|' constant
82
6
2503
          { push @OutVars, $_[1];
83
6
37
            my $val = $InVals->{$_[1]};
84
6
45
            if (!defined $val) {
85
5
15
                my $default;
86
5
36
                $Defaults{$_[1]} = $default = parse_string($_[3]);
87
5
39
                return $Quote->($default);
88            }
89
1
8
            $Quote->($val);
90          }
91      | VAR
92
8
970
          { push @OutVars, $_[1];
93
8
60
            my $val = $InVals->{$_[1]};
94
8
63
            if (!defined $val) {
95
4
21
                push @Unbound, $_[1];
96
4
29
                return $Quote->("");
97            }
98
4
30
            $Quote->($val);
99          }
100      ;
101
102column: qualified_symbol
103
99
99
16808
872
      | symbol { push @Columns, $_[1]; $QuoteIdent->($_[1]) }
104      ;
105
106qualified_symbol: symbol '.' symbol
107                    {
108
24
7990
                      push @Models, $_[1];
109
24
96
                      push @Columns, $_[3];
110
24
147
                      $QuoteIdent->($_[1]).'.'.$QuoteIdent->($_[3]);
111                    }
112                ;
113
114symbol: IDENT
115      | VAR '|' IDENT
116
26
6167
          { push @OutVars, $_[1];
117
26
173
            my $val = $InVals->{$_[1]};
118
26
192
            if (!defined $val) {
119
19
58
                my $default;
120
19
148
                $Defaults{$_[1]} = $default = $_[3];
121
19
116
                _IDENT($default) or die "Bad symbol: $default\n";
122
19
170
                return $default;
123            }
124
7
37
            _IDENT($val) or die "Bad symbol: $val\n";
125
6
39
            $val;
126          }
127      | VAR
128
44
5793
          { push @OutVars, $_[1];
129
44
329
            my $val = $InVals->{$_[1]};
130
44
315
            if (!defined $val) {
131
15
81
                push @Unbound, $_[1];
132
15
118
                return '';
133            }
134            #warn _IDENT($val);
135
29
183
            _IDENT($val) or die "Bad symbol: $val\n";
136
29
220
            $val;
137          }
138      ;
139
140alias: symbol
141     ;
142
143postfix_clause_list: postfix_clause postfix_clause_list
144
94
46410
                        { join(' ', @_[1..$#_]) }
145                   | postfix_clause
146                   ;
147
148postfix_clause: where_clause
149              | group_by_clause
150              | order_by_clause
151              | limit_clause
152              | offset_clause
153              | from_clause
154              ;
155
156from_clause: 'from' models
157
80
33744
                { join(' ', @_[1..$#_]) }
158           | 'from' proc_call
159
2
453
                { join(' ', @_[1..$#_]) }
160           ;
161
162where_clause: 'where' condition
163
45
29718
                { join(' ', @_[1..$#_]) }
164            ;
165
166condition: disjunction
167         ;
168
169disjunction: conjunction 'or' conjunction
170
4
1391
            { join(' ', @_[1..$#_]) }
171           | conjunction
172           ;
173
174conjunction: comparison 'and' comparison
175
9
2261
                { join(' ', @_[1..$#_]) }
176           | comparison
177           ;
178
179comparison: column operator literal
180
51
22007
                { join(' ', @_[1..$#_]) }
181          | column operator column
182
8
3430
                { join(' ', @_[1..$#_]) }
183          | '(' condition ')'
184
0
0
                { join(' ', @_[1..$#_]) }
185          ;
186
187operator: '>'
188        | '>='
189        | '<='
190        | '<'
191        | '<>'
192        | '='
193        | 'like'
194        ;
195
196literal: string
197       | NUM
198       ;
199
200group_by_clause: 'group by' column_list
201
1
429
                    { join(' ', @_[1..$#_]) }
202               ;
203
204column_list: column ',' column_list
205
0
0
                { join(' ', @_[1..$#_]) }
206           | column
207           ;
208
209order_by_clause: 'order by' column_list
210
34
15189
                    { join(' ', @_[1..$#_]) }
211               ;
212
213limit_clause: 'limit' NUM
214
7
7
1527
297
                { delete $_[0]->YYData->{limit}; join(' ', @_[1..$#_]) }
215            ;
216
217offset_clause: 'offset' NUM {
218
7
7
1536
292
                 delete $_[0]->YYData->{offset}; join(' ', @_[1..$#_]) }
219
88
96687
             ;
220
221%%
222
223#use Smart::Comments;
224
225sub _Error {
226
10
2011
    my ($value) = $_[0]->YYCurval;
227
228
10
331
    my $token = 1;
229    ## $value
230
10
100
    my @expect = $_[0]->YYExpect;
231    ### expect: @expect
232
10
558
    my ($what) = $value ? "input: \"$value\"" : "end of input";
233
234
10
16
53
361
    map { $_ = "'$_'" if $_ ne '' and !/^\w+$/ } @expect;
235
10
81
    my $expected = join " or ", @expect;
236
10
160
    _SyntaxError(1, "Unexpected $what".($expected?" ($expected expected)":''), $.);
237}
238
239sub _SyntaxError {
240
10
80
    my ($level, $message, $lineno) = @_;
241
242
10
87
    $message= "line $lineno: error: $message";
243
10
33
    die $message, ".\n";
244}
245
246sub _Lexer {
247
947
246611
    my ($parser) = shift;
248
249
947
5993
    my $yydata = $parser->YYData;
250
947
26270
    my $source = $yydata->{source};
251    #local $" = "\n";
252
947
11329
    defined $yydata->{input} && $yydata->{input} =~ s/^\s+//s;
253
254
947
16276
    if (!defined $yydata->{input} || $yydata->{input} eq '') {
255        ### HERE!!!
256
196
2060
        $yydata->{input} = <$source>;
257    }
258
947
6638
    if (!defined $yydata->{input}) {
259
77
906
        return ('', undef);
260    }
261
262    ## other data: <$source>
263    ### data: $yydata->{input}
264    ### lineno: $.
265
266
870
6338
    for ($yydata->{input}) {
267
870
7222
        s/^\s*(\d+(?:\.\d+)?)\b//s
268                and return ('NUM', $1);
269
836
5656
        s/^\s*('(?:\\.|''|[^'])*')//
270                and return ('STRING', $1);
271
806
4319
        s/^\s*"(\w*)"//
272                and return ('IDENT', $1);
273
804
4654
        s/^\s*(\$(\w*)\$.*?\$\2\$)//
274                and return ('STRING', $1);
275
799
10840
        s/^\s*(\*|count|sum|max|min|select|and|or|from|where|delete|update|set|order by|group by|limit|offset)\b//is
276                and return (lc($1), lc($1));
277
510
3087
        s/^\s*(<=|>=|<>)//s
278                and return ($1, $1);
279
505
5840
        s/^\s*([A-Za-z][A-Za-z0-9_]*)\b//s
280                and return ('IDENT', $1);
281
304
3000
        s/^\$(\w+)//s
282                and return ('VAR', $1);
283
219
4130
        s/^\s*(\S)//s
284                and return ($1, $1);
285    }
286}
287
288sub parse_string {
289
34
0
194
    my $s = $_[0];
290
34
381
    if ($s =~ /^'(.*)'$/) {
291
28
152
        $s = $1;
292
28
148
        $s =~ s/''/'/g;
293
28
124
        $s =~ s/\\n/\n/g;
294
28
110
        $s =~ s/\\t/\t/g;
295
28
110
        $s =~ s/\\r/\r/g;
296
28
136
        $s =~ s/\\(.)/$1/g;
297
28
273
        return $s;
298    } elsif ($s =~ /^\$(\w*)\$(.*)\$\1\$$/) {
299
5
35
        $s = $2;
300
5
56
        return $s;
301    } elsif ($s =~ /^[\d\.]*$/) {
302
1
16
        return $s;
303    } else {
304
0
0
        die "Unknown string literal: $s";
305    }
306}
307
308sub parse {
309
87
0
683
    my ($self, $sql, $params) = @_;
310
87
0
0
0
2866
0
0
0
    open my $source, '<', \$sql;
311
87
1059
    my $yydata = $self->YYData;
312
87
3573
    $yydata->{source} = $source;
313
87
662
    $yydata->{limit} = $params->{limit};
314
87
632
    $yydata->{offset} = $params->{offset};
315
316
87
0
761
0
    $Quote = $params->{quote} || sub { "''" };
317
87
0
664
0
    $QuoteIdent = $params->{quote_ident} || sub { '""' };
318
87
829
    $InVals = $params->{vars} || {};
319    #$QuoteIdent = $params->{quote_ident};
320
321    #$self->YYData->{INPUT} = ;
322    ### $sql
323
87
508
    @Unbound = ();
324
87
445
    @Models = ();
325
87
402
    @Columns = ();
326
87
386
    @OutVars = ();
327
87
427
    %Defaults = ();
328
87
1365
    my $sql = $self->YYParse( yydebug => 0 & 0x1F, yylex => \&_Lexer, yyerror => \&_Error );
329
76
51873
    close $source;
330    return {
331
76
2904
        limit => $yydata->{limit},
332        offset => $yydata->{offset},
333        models => [@Models],
334        columns => [@Columns],
335        sql => $sql,
336        vars => [@OutVars],
337        defaults => {%Defaults},
338        unbound => [@Unbound],
339    };
340}
341
342sub _IDENT {
343
55
1339
    (defined $_[0] && $_[0] =~ /^[A-Za-z]\w*$/) ? $_[0] : undef;
344}
345
346#my ($select) =new Select;
347#my $var = $select->Run;
348
3491;
350