Updates to MX::Declare required changes
[dbsrgits/SQL-Abstract-2.0-ish.git] / lib / SQL / Abstract / AST / v1.pm
CommitLineData
14774be0 1use MooseX::Declare;
2
3class SQL::Abstract::AST::v1 extends SQL::Abstract {
4
5 use Carp qw/croak/;
6 use Data::Dump qw/pp/;
7
8 use Moose::Util::TypeConstraints;
cbcfedc1 9 use MooseX::Types::Moose qw/ArrayRef Str Int Ref HashRef/;
14774be0 10 use MooseX::AttributeHelpers;
bad761ba 11 use SQL::Abstract::Types qw/AST/;
ef0d6124 12 use Devel::PartialDump qw/dump/;
14774be0 13
0c371882 14 # set things that are valid in where clauses
ef0d6124 15 override _build_expr_dispatch_table {
0bf8a8c4 16 return {
17 %{super()},
1b85673a 18 in => $self->can('_in'),
19 not_in => $self->can('_in'),
1f4bd99c 20 between => $self->can('_between'),
21 not_between => $self->can('_between'),
a464be15 22 and => $self->can('_recurse_where'),
23 or => $self->can('_recurse_where'),
1b85673a 24 map { +"$_" => $self->can("_$_") } qw/
0c371882 25 value
627dcb62 26 identifier
0c371882 27 true
28 false
e7996b3a 29 expr
0c371882 30 /
0bf8a8c4 31 };
14774be0 32 }
33
bad761ba 34 method _select(AST $ast) {
747f7c21 35 # Default to requiring columns and from.
36 # DB specific ones (i.e. mysql/Pg) can not require the FROM part with a bit
37 # of refactoring
38
39 for (qw/columns tablespec/) {
40 confess "'$_' is required in select AST with " . dump ($ast)
41 unless exists $ast->{$_};
4ee32f41 42 }
43
44 # Check that columns is a -list
747f7c21 45 confess "'columns' should be an array ref, not " . dump($ast->{columns})
46 unless is_ArrayRef($ast->{columns});
47
64c32031 48 my $cols = $self->_list({-type => 'list', args => $ast->{columns} });
4ee32f41 49
50 my @output = (
747f7c21 51 SELECT => $cols
4ee32f41 52 );
53
747f7c21 54 push @output, FROM => $self->dispatch($ast->{tablespec})
55 if exists $ast->{tablespec};
56
924d940e 57 if (exists $ast->{where}) {
58 my $sub_ast = $ast->{where};
59
60 confess "$_ option is not an AST: " . dump($sub_ast)
61 unless is_AST($sub_ast);
62
63 push @output, "WHERE", $self->_expr($sub_ast);
64 }
65
66 for (qw/group_by having order_by/) {
4ee32f41 67 if (exists $ast->{$_}) {
68 my $sub_ast = $ast->{$_};
e68f980b 69
924d940e 70 confess "$_ option is not an AST or an ArrayRef: " . dump($sub_ast)
71 unless is_AST($sub_ast) || is_ArrayRef($sub_ast);;
4ee32f41 72
e68f980b 73 my $meth = "__$_";
74 push @output, $self->$meth($sub_ast);
4ee32f41 75 }
76 }
77
78 return join(' ', @output);
14774be0 79 }
80
d4656fcf 81 method _update(AST $ast) {
82
83 for (qw/columns values tablespec/) {
84 confess "'$_' is required in update AST with " . dump ($ast)
85 unless exists $ast->{$_};
86 }
fc20481d 87
88 my $table = $ast->{tablespec};
89 confess 'update: tablespec must be an ident or an alias in ' . dump($ast)
90 unless $table->{-type} =~ /^identifier|alias$/;
91
92 my @output = (
93 'UPDATE',
94 $self->dispatch($table),
95 'SET'
96 );
97
99124578 98 confess 'update: Number of values does not match columns: ' . dump($ast)
fc20481d 99 if @{$ast->{columns}} != @{$ast->{values}};
100
fc20481d 101 my $list = {
102 -type => 'list',
103 args => [ map {
104 { -type => 'expr',
105 op => '==', # This should really be '=' but hmmmmmmmm
106 args => [
107 $ast->{columns}[$_],
108 $ast->{values}[$_]
109 ]
110 }
111 } 0..$#{$ast->{columns}} ]
112 };
113
114 push @output, $self->dispatch($list);
115
116 return join(' ', @output);
d4656fcf 117
118 }
119
64c32031 120 method _join(HashRef $ast) {
d0ad3a92 121
122 # TODO: Validate join type
123 my $type = $ast->{join_type} || "";
14774be0 124
f7dc4536 125 my @output = $self->dispatch($ast->{lhs});
d0ad3a92 126
127 push @output, uc $type if $type;
f7dc4536 128 push @output, "JOIN", $self->dispatch($ast->{rhs});
64c32031 129
d0ad3a92 130 push @output,
131 exists $ast->{on}
132 ? ('ON', '(' . $self->_expr( $ast->{on} ) . ')' )
924d940e 133 : ('USING', '(' .$self->dispatch($ast->{using}
134 || croak "No 'on' or 'uinsg' clause passed to join cluase: " .
135 dump($ast)
136 ) .
d0ad3a92 137 ')' );
64c32031 138
d0ad3a92 139 return join(" ", @output);
64c32031 140
14774be0 141 }
142
924d940e 143 method _ordering(AST $ast) {
144
145 my $output = $self->_expr($ast->{expr});
146
147 $output .= " " . uc $1
148 if $ast->{direction} &&
149 ( $ast->{direction} =~ /^(asc|desc)$/i
150 || confess "Unknown ordering direction " . dump($ast)
151 );
14774be0 152
924d940e 153 return $output;
14774be0 154 }
155
627dcb62 156 method _identifier(AST $ast) {
157 my @names = @{$ast->{elements}};
14774be0 158
627dcb62 159 my $sep = $self->ident_separator;
4ee32f41 160 my $quote = $self->is_quoting
161 ? $self->quote_chars
162 : [ '' ];
163
164 my $join = $quote->[-1] . $sep . $quote->[0];
14774be0 165
4ee32f41 166 # We dont want to quote * in [qw/me */]: `me`.* is the desired output there
167 # This means you can't have a field called `*`. I am willing to accept this
168 # situation, cos thats a really stupid thing to want.
169 my $post;
170 $post = pop @names if $names[-1] eq '*';
14774be0 171
8b398780 172 my $ret;
173 $ret = $quote->[0] .
174 join( $join, @names ) .
175 $quote->[-1]
176 if @names;
177
178 $ret = $ret
179 ? $ret . $sep . $post
180 : $post
181 if defined $post;
182
4ee32f41 183
4ee32f41 184 return $ret;
14774be0 185 }
186
14774be0 187
7a56723e 188 method _list(AST $ast) {
e6a33ce3 189 return "" unless $ast->{args};
190
924d940e 191 my @items = is_ArrayRef($ast->{args})
192 ? @{$ast->{args}}
193 : $ast->{args};
14774be0 194
195 return join(
196 $self->list_separator,
197 map { $self->dispatch($_) } @items);
198 }
199
747f7c21 200 # TODO: I think i want to parameterized AST type to get better validation
7a56723e 201 method _alias(AST $ast) {
202
4ee32f41 203 # TODO: Maybe we want qq{ AS "$as"} here
7a56723e 204 return $self->dispatch($ast->{ident}) . " AS " . $ast->{as};
14774be0 205
206 }
207
bad761ba 208 method _value(AST $ast) {
14774be0 209
1b85673a 210 $self->add_bind($ast->{value});
14774be0 211 return "?";
212 }
213
e68f980b 214 # Not dispatchable to.
924d940e 215 method __having($args) {
216 return "HAVING " . $self->_list({-type => 'list', args => $args});
217 }
218
219 method __group_by($args) {
220 return "GROUP BY " . $self->_list({-type => 'list', args => $args});
e68f980b 221 }
222
924d940e 223 method __order_by($args) {
224 return "ORDER BY " . $self->_list({-type => 'list', args => $args});
225 }
226
227
ef0d6124 228 # Perhaps badly named. handles 'and' and 'or' clauses
bad761ba 229 method _recurse_where(AST $ast) {
14774be0 230
a464be15 231 my $op = $ast->{op};
14774be0 232
a464be15 233 my $OP = uc $op;
234 my $prio = $SQL::Abstract::PRIO{$op};
14774be0 235
ef0d6124 236 my $dispatch_table = $self->expr_dispatch_table;
0bf8a8c4 237
14774be0 238 my @output;
a464be15 239 foreach ( @{$ast->{args}} ) {
bad761ba 240 croak "invalid component in where clause: $_" unless is_AST($_);
14774be0 241
9d7d0694 242 if ($_->{-type} eq 'expr' && $_->{op} =~ /^(and|or)$/) {
14774be0 243 my $sub_prio = $SQL::Abstract::PRIO{$1};
244
39f7dc30 245 if ($sub_prio == $prio) {
246 # When the element below has same priority, i.e. 'or' as a child of
247 # 'or', dont produce extra brackets
14774be0 248 push @output, $self->_recurse_where($_);
249 } else {
250 push @output, '(' . $self->_recurse_where($_) . ')';
251 }
252 } else {
ef0d6124 253 push @output, $self->_expr($_);
14774be0 254 }
255 }
256
257 return join(" $OP ", @output);
258 }
259
bad761ba 260 method _expr(AST $ast) {
1b85673a 261 my $op = $ast->{-type};
0c371882 262
ef0d6124 263 $op = $ast->{op} if $op eq 'expr';
264
265 if (my $code = $self->lookup_expr_dispatch($op)) {
0c371882 266
267 return $code->($self, $ast);
268
269 }
ef0d6124 270 croak "'$op' is not a valid AST type in an expression with " . dump($ast)
271 if $ast->{-type} ne 'expr';
0c371882 272
e6a33ce3 273 # This is an attempt to do some form of validation on function names. This
274 # might end up being a bad thing.
275 croak "'$op' is not a valid operator in an expression with " . dump($ast)
276 if $op =~ /\W/;
277
278 return $self->_generic_function_op($ast);
1b85673a 279
1b85673a 280 }
0c371882 281
bad761ba 282 method _binop(AST $ast) {
1b85673a 283 my ($lhs, $rhs) = @{$ast->{args}};
284 my $op = $ast->{op};
0bf8a8c4 285
ec376489 286 # IS NOT? NULL
287 if ($rhs->{-type} eq 'value' && !defined $rhs->{value} &&
288 ($op eq '==' || $op eq '!='))
289 {
290 return $self->_expr($lhs) .
291 ($op eq '==' ? " IS " : " IS NOT ") .
292 "NULL";
293 }
294
ef0d6124 295 join (' ', $self->_expr($lhs),
0bf8a8c4 296 $self->binop_mapping($op) || croak("Unknown binary operator $op"),
ef0d6124 297 $self->_expr($rhs)
14774be0 298 );
299 }
300
e6a33ce3 301 method _generic_function_op(AST $ast) {
302 my $op = $ast->{op};
303
304 return "$op(" . $self->_list($ast) . ")";
305 }
306
bad761ba 307 method _in(AST $ast) {
a464be15 308
9d7d0694 309 my ($field,@values) = @{$ast->{args}};
a464be15 310
9d7d0694 311 my $not = ($ast->{op} =~ /^not_/) ? " NOT" : "";
0bf8a8c4 312
9d7d0694 313 return $self->_false unless @values;
14774be0 314
ef0d6124 315 return $self->_expr($field) .
9d7d0694 316 $not .
14774be0 317 " IN (" .
9d7d0694 318 join(", ", map { $self->dispatch($_) } @values ) .
14774be0 319 ")";
320 }
321
1f4bd99c 322 method _between(AST $ast) {
323
324 my ($field,@values) = @{$ast->{args}};
325
326 my $not = ($ast->{op} =~ /^not_/) ? " NOT" : "";
327 croak "between requires 3 arguments: " . dump($ast)
328 unless @values == 2;
329
39f7dc30 330 # The brackets are to work round an issue with SQL::A::Test
331 return "(" .
332 $self->_expr($field) .
1f4bd99c 333 $not .
334 " BETWEEN " .
39f7dc30 335 join(" AND ", map { $self->dispatch($_) } @values ) .
336 ")";
1f4bd99c 337 }
338
44cfd1f6 339 # 'constants' that are portable across DBs
340 method _false($ast?) { "0 = 1" }
341 method _true($ast?) { "1 = 1" }
342
14774be0 343}