EC easy node types
[scpubgit/Q-Branch.git] / lib / SQL / Abstract / ExtraClauses.pm
CommitLineData
1ff9018c 1package SQL::Abstract::ExtraClauses;
2
df5d0507 3use Moo;
4
5has sqla => (
6 is => 'ro', init_arg => undef,
7 handles => [ qw(
1601bb47 8 expand_expr expand_maybe_list_expr render_aqt join_query_parts
df5d0507 9 ) ],
10);
1ff9018c 11
df5d0507 12sub cb {
c671eba6 13 my ($self, $method, @args) = @_;
14 return sub {
15 local $self->{sqla} = shift;
16 $self->$method(@args, @_)
17 };
df5d0507 18}
19
20sub apply_to {
21 my ($self, $sqla) = @_;
22 $self = $self->new unless ref($self);
ad078c71 23 my @clauses = $sqla->clauses_of('select');
2b0b3d43 24 my @before_setop;
25 CLAUSE: foreach my $idx (0..$#clauses) {
26 if ($clauses[$idx] eq 'order_by') {
27 @before_setop = @clauses[0..$idx-1];
28 splice(@clauses, $idx, 0, qw(setop group_by having));
29 last CLAUSE;
30 }
31 }
32 die "Huh?" unless @before_setop;
ad078c71 33 $sqla->clauses_of(select => 'with', @clauses);
34 $sqla->clause_expanders(
df5d0507 35 'select.group_by', $self->cb(sub {
b99e393b 36 $_[0]->expand_maybe_list_expr($_[2], -ident)
df5d0507 37 }),
38 'select.having', $self->cb(sub { $_[0]->expand_expr($_[2]) }),
58ebc7fa 39 );
a6227174 40 foreach my $thing (qw(join from_list)) {
ad078c71 41 $sqla->expander($thing => $self->cb("_expand_${thing}"))
df5d0507 42 ->renderer($thing => $self->cb("_render_${thing}"))
a6227174 43 }
e90782e3 44 $sqla->binop_expander(as => $self->cb('_expand_op_as'));
ad078c71 45 $sqla->renderer(as => $self->cb('_render_as'));
2f4717ad 46 $sqla->expander(alias => $self->cb('_expand_alias'));
ad078c71 47 $sqla->renderer(alias => $self->cb('_render_alias'));
58ebc7fa 48
ad078c71 49 $sqla->clauses_of(update => sub {
ee706e89 50 my ($self, @clauses) = @_;
51 splice(@clauses, 2, 0, 'from');
52 @clauses;
53 });
54
ad078c71 55 $sqla->clauses_of(delete => sub {
ee706e89 56 my ($self, @clauses) = @_;
57 splice(@clauses, 1, 0, 'using');
58 @clauses;
59 });
60
ad078c71 61 $sqla->clause_expanders(
805c64f1 62 'update.from' => $self->cb('_expand_from_list'),
9a20a32d 63 'delete.using' => $self->cb('_expand_from_list'),
df5d0507 64 'insert.rowvalues' => $self->cb(sub {
fe8b493f 65 +(from => $_[0]->expand_expr({ -values => $_[2] }));
df5d0507 66 }),
67 'insert.select' => $self->cb(sub {
fe8b493f 68 +(from => $_[0]->expand_expr({ -select => $_[2] }));
df5d0507 69 }),
58ebc7fa 70 );
26994fdd 71
2b0b3d43 72 # set ops
ad078c71 73 $sqla->wrap_expander(select => sub {
c671eba6 74 $self->cb('_expand_select', $_[0], \@before_setop);
3f9899e5 75 });
2b0b3d43 76
e2db8228 77 $sqla->clause_renderer(
78 'select.setop' => $self->cb(sub { $_[0]->render_aqt($_[2]); })
79 );
2b0b3d43 80
51046d0e 81 foreach my $setop (qw(union intersect except)) {
82 $sqla->expander($setop => $self->cb('_expand_setop'));
83 $sqla->renderer($setop => $self->cb('_render_setop'));
84 }
260e0f29 85
41086177 86 my $setop_expander = $self->cb('_expand_clause_setop');
f61bfd7b 87
ad078c71 88 $sqla->clause_expanders(
f61bfd7b 89 map +($_ => $setop_expander),
90 map "select.${_}",
91 map +($_, "${_}_all", "${_}_distinct"),
92 qw(union intersect except)
93 );
d175037f 94
2f4717ad 95 my $w_exp = $self->cb('_expand_with');
96 my $w_rdr = $self->cb('_render_with');
97 $sqla->clause_expander('select.with' => $w_exp);
98 $sqla->clause_expander('select.with_recursive' => $w_exp);
99 $sqla->clause_renderer('select.with' => $w_rdr);
100
a97ecd95 101 foreach my $stmt (qw(insert update delete)) {
ad078c71 102 $sqla->clauses_of($stmt => 'with', $sqla->clauses_of($stmt));
2f4717ad 103 $sqla->clause_expander("${stmt}.$_" => $w_exp)
a97ecd95 104 for qw(with with_recursive);
2f4717ad 105 $sqla->clause_renderer("${stmt}.with" => $w_rdr);
a97ecd95 106 }
2f4717ad 107
108 $sqla->expander(cast => $self->cb('_expand_cast'));
df5d0507 109
ad078c71 110 $sqla->clause_expanders(
805c64f1 111 "select.from", $self->cb('_expand_from_list'),
df5d0507 112 "update.target", $self->cb('_expand_update_clause_target'),
113 "update.update", $self->cb('_expand_update_clause_target'),
114 );
2b0b3d43 115
ad078c71 116 return $sqla;
1ff9018c 117}
118
c671eba6 119sub _expand_select {
093442c8 120 my ($self, $orig, $before_setop, @args) = @_;
121 my $exp = $self->sqla->$orig(@args);
c671eba6 122 return $exp unless my $setop = (my $sel = $exp->{-select})->{setop};
123 if (my @keys = grep $sel->{$_}, @$before_setop) {
124 my %inner; @inner{@keys} = delete @{$sel}{@keys};
125 unshift @{(values(%$setop))[0]{queries}},
126 { -select => \%inner };
127 }
128 return $exp;
129}
130
7741b7ad 131sub _expand_from_list {
132 my ($self, undef, $args) = @_;
133 if (ref($args) eq 'HASH') {
38e67490 134 return $args if $args->{-from_list};
7741b7ad 135 return { -from_list => [ $self->expand_expr($args) ] };
136 }
137 my @list;
86a6ebf4 138 my @args = ref($args) eq 'ARRAY' ? @$args : ($args);
7741b7ad 139 while (my $entry = shift @args) {
b99e9a14 140 if (!ref($entry) and $entry =~ /^-(.*)/) {
141 if ($1 eq 'as') {
142 $list[-1] = $self->expand_expr({ -as => [
143 $list[-1], map +(ref($_) eq 'ARRAY' ? @$_ : $_), shift(@args)
144 ]});
145 next;
146 }
7741b7ad 147 $entry = { $entry => shift @args };
148 }
149 my $aqt = $self->expand_expr($entry, -ident);
150 if ($aqt->{-join} and not $aqt->{-join}{from}) {
151 $aqt->{-join}{from} = pop @list;
152 }
153 push @list, $aqt;
154 }
155 return { -from_list => \@list };
156}
157
158sub _expand_join {
159 my ($self, undef, $args) = @_;
160 my %proto = (
161 ref($args) eq 'HASH'
162 ? %$args
163 : (to => $args->[0], @{$args}[1..$#$args])
164 );
b99e9a14 165 if (my $as = delete $proto{as}) {
984db0d7 166 $proto{to} = $self->expand_expr({ -as => [ $proto{to}, $as ] });
b99e9a14 167 }
e0eb8d26 168 if (defined($proto{using}) and ref(my $using = $proto{using}) ne 'HASH') {
13c99dad 169 $proto{using} = [
e0eb8d26 170 map [ $self->expand_expr($_, -ident) ],
171 ref($using) eq 'ARRAY' ? @$using: $using
13c99dad 172 ];
e0eb8d26 173 }
7741b7ad 174 my %ret = map +($_ => $self->expand_expr($proto{$_}, -ident)),
175 sort keys %proto;
176 return +{ -join => \%ret };
177}
178
179sub _render_from_list {
a01911a2 180 my ($self, undef, $list) = @_;
a1f8b6ef 181 return $self->join_query_parts(', ', @$list);
7741b7ad 182}
183
184sub _render_join {
a01911a2 185 my ($self, undef, $args) = @_;
7741b7ad 186
187 my @parts = (
412f9efe 188 $args->{from},
1601bb47 189 { -keyword => join '_', ($args->{type}||()), 'join' },
51ccda04 190 (map +($_->{-ident} || $_->{-as} ? $_ : ('(', $_, ')')), $args->{to}),
7741b7ad 191 ($args->{on} ? (
1601bb47 192 { -keyword => 'on' },
412f9efe 193 $args->{on},
7741b7ad 194 ) : ()),
195 ($args->{using} ? (
1601bb47 196 { -keyword => 'using' },
13c99dad 197 '(', $args->{using}, ')',
7741b7ad 198 ) : ()),
199 );
0236f122 200 return $self->join_query_parts(' ', @parts);
7741b7ad 201}
202
b99e9a14 203sub _expand_op_as {
204 my ($self, undef, $vv, $k) = @_;
984db0d7 205 my @vv = (ref($vv) eq 'ARRAY' ? @$vv : $vv);
984db0d7 206 my $ik = $self->expand_expr($k, -ident);
207 return +{ -as => [ $ik, $self->expand_expr($vv[0], -alias) ] }
208 if @vv == 1 and ref($vv[0]) eq 'HASH';
209
210 my @as = map $self->expand_expr($_, -ident), @vv;
211 return { -as => [ $ik, { -alias => \@as } ] };
b99e9a14 212}
213
214sub _render_as {
a01911a2 215 my ($self, undef, $args) = @_;
984db0d7 216 my ($thing, $alias) = @$args;
0236f122 217 return $self->join_query_parts(
ac7992be 218 ' ',
984db0d7 219 $thing,
1601bb47 220 { -keyword => 'as' },
984db0d7 221 $alias,
1ba47f38 222 );
223}
224
225sub _render_alias {
984db0d7 226 my ($self, undef, $args) = @_;
1ba47f38 227 my ($as, @cols) = @$args;
228 return (@cols
0236f122 229 ? $self->join_query_parts('',
a1f8b6ef 230 $as,
3f312d2e 231 '(',
232 $self->join_query_parts(
233 ', ',
234 @cols
235 ),
236 ')',
1ba47f38 237 )
238 : $self->render_aqt($as)
b99e9a14 239 );
240}
241
f9f1fdcd 242sub _expand_update_clause_target {
fe8b493f 243 my ($self, undef, $target) = @_;
f9f1fdcd 244 +(target => $self->_expand_from_list(undef, $target));
245}
246
2f4717ad 247sub _expand_cast {
248 my ($self, undef, $thing) = @_;
249 return { -func => [ cast => $thing ] } if ref($thing) eq 'HASH';
250 my ($cast, $to) = @{$thing};
251 +{ -func => [ cast => { -as => [
252 $self->expand_expr($cast),
253 $self->expand_expr($to, -ident),
254 ] } ] };
255}
256
257sub _expand_alias {
258 my ($self, undef, $args) = @_;
259 if (ref($args) eq 'HASH' and my $alias = $args->{-alias}) {
260 $args = $alias;
261 }
262 +{ -alias => [
263 map $self->expand_expr($_, -ident),
264 ref($args) eq 'ARRAY' ? @{$args} : $args
265 ]
266 }
267}
268
269sub _expand_with {
270 my ($self, $name, $with) = @_;
271 my (undef, $type) = split '_', $name;
272 if (ref($with) eq 'HASH') {
273 return +{
274 %$with,
275 queries => [
276 map +[
277 $self->expand_expr({ -alias => $_->[0] }, -ident),
278 $self->expand_expr($_->[1]),
279 ], @{$with->{queries}}
280 ]
281 }
282 }
283 my @with = @$with;
284 my @exp;
285 while (my ($alias, $query) = splice @with, 0, 2) {
286 push @exp, [
287 $self->expand_expr({ -alias => $alias }, -ident),
288 $self->expand_expr($query)
289 ];
290 }
291 return +(with => { ($type ? (type => $type) : ()), queries => \@exp });
292}
293
294sub _render_with {
295 my ($self, undef, $with) = @_;
296 my $q_part = $self->join_query_parts(', ',
297 map {
298 my ($alias, $query) = @$_;
299 $self->join_query_parts(' ',
300 $alias,
1601bb47 301 { -keyword => 'as' },
2f4717ad 302 $query,
303 )
304 } @{$with->{queries}}
305 );
306 return $self->join_query_parts(' ',
1601bb47 307 { -keyword => join '_', 'with', ($with->{type}||'') },
2f4717ad 308 $q_part,
309 );
310}
311
51046d0e 312sub _expand_setop {
313 my ($self, $setop, $args) = @_;
314 +{ "-${setop}" => {
315 %$args,
316 queries => [ map $self->expand_expr($_), @{$args->{queries}} ],
317 } };
318}
319
b5f4a869 320sub _render_setop {
321 my ($self, $setop, $args) = @_;
322 $self->join_query_parts(
1601bb47 323 { -keyword => ' '.join('_', $setop, ($args->{type}||())).' ' },
b5f4a869 324 @{$args->{queries}}
325 );
326}
327
41086177 328sub _expand_clause_setop {
329 my ($self, $setop, $args) = @_;
330 my ($op, $type) = split '_', $setop;
331 +(setop => $self->expand_expr({
332 "-${op}" => {
333 ($type ? (type => $type) : ()),
334 queries => (ref($args) eq 'ARRAY' ? $args : [ $args ])
335 }
336 }));
337}
338
1ff9018c 3391;
3001f097 340
583c7957 341__END__
342
3001f097 343=head1 NAME
344
345SQL::Abstract::ExtraClauses - new/experimental additions to L<SQL::Abstract>
346
347=head1 SYNOPSIS
348
349 my $sqla = SQL::Abstract->new;
350 SQL::Abstract::ExtraClauses->apply_to($sqla);
351
352=head1 METHODS
353
354=head2 apply_to
355
356Applies the plugin to an L<SQL::Abstract> object.
357
358=head2 cb
359
360For plugin authors, creates a callback to call a method on the plugin.
361
362=head2 sqla
363
364Available only during plugin callback executions, contains the currently
365active L<SQL::Abstract> object.
366
583c7957 367=head1 NODE TYPES
368
369=head2 alias
370
371Represents a table alias. Expands name and column names with ident as default.
372
373 # expr
374 { -alias => [ 't', 'x', 'y', 'z' ] }
375
376 # aqt
377 { -alias => [
378 { -ident => [ 't' ] }, { -ident => [ 'x' ] },
379 { -ident => [ 'y' ] }, { -ident => [ 'z' ] },
380 ] }
381
382 # query
383 t(x, y, z)
384 []
385
386=head2 as
387
388Represents an sql AS. LHS is expanded with ident as default, RHS is treated
389as a list of arguments for the alias node.
390
391 # expr
392 { foo => { -as => 'bar' } }
393
394 # aqt
395 { -as =>
396 [
397 { -ident => [ 'foo' ] },
398 { -alias => [ { -ident => [ 'bar' ] } ] },
399 ]
400 }
401
402 # query
403 foo AS bar
404 []
405
406 # expr
407 { -as => [ { -select => { _ => 'blah' } }, 't', 'blah' ] }
408
409 # aqt
410 { -as => [
411 { -select =>
412 { select => { -op => [ ',', { -ident => [ 'blah' ] } ] } }
413 },
414 { -alias => [ { -ident => [ 't' ] }, { -ident => [ 'blah' ] } ] },
415 ] }
416
417 # query
418 (SELECT blah) AS t(blah)
419 []
420
421=head2 cast
422
423 # expr
424 { -cast => [ { -ident => 'birthday' }, 'date' ] }
425
426 # aqt
427 { -func => [
428 'cast', {
429 -as => [ { -ident => [ 'birthday' ] }, { -ident => [ 'date' ] } ]
430 },
431 ] }
432
433 # query
434 CAST(birthday AS date)
435 []
436
3001f097 437=cut