plugin registration
[dbsrgits/SQL-Abstract.git] / lib / SQL / Abstract / Plugin / ExtraClauses.pm
CommitLineData
ddc2d2e3 1package SQL::Abstract::Plugin::ExtraClauses;
f8392f56 2
b40d30dc 3use Moo;
4
ddc2d2e3 5with 'SQL::Abstract::Role::Plugin';
8346c47f 6
7sub register_extensions {
8 my ($self, $sqla) = @_;
ad993fa3 9
ac3616e8 10 my @clauses = $sqla->clauses_of('select');
f7a20100 11 my @before_setop;
12 CLAUSE: foreach my $idx (0..$#clauses) {
13 if ($clauses[$idx] eq 'order_by') {
14 @before_setop = @clauses[0..$idx-1];
15 splice(@clauses, $idx, 0, qw(setop group_by having));
16 last CLAUSE;
17 }
18 }
ad993fa3 19
f7a20100 20 die "Huh?" unless @before_setop;
d689c054 21 $sqla->clauses_of(select => @clauses);
71c1b4d5 22
ac3616e8 23 $sqla->clauses_of(update => sub {
e6b86bee 24 my ($self, @clauses) = @_;
25 splice(@clauses, 2, 0, 'from');
26 @clauses;
27 });
28
ac3616e8 29 $sqla->clauses_of(delete => sub {
e6b86bee 30 my ($self, @clauses) = @_;
31 splice(@clauses, 1, 0, 'using');
32 @clauses;
33 });
34
d689c054 35 $self->register(
ad993fa3 36 (map +(
37 "${_}er" => [
38 do {
39 my $x = $_;
40 (map +($_ => "_${x}_${_}"), qw(join from_list alias))
41 }
42 ]
43 ), qw(expand render)),
44 binop_expander => [ as => '_expand_op_as' ],
45 renderer => [ as => '_render_as' ],
46 expander => [ cast => '_expand_cast' ],
d689c054 47 clause_expanders => [
ad993fa3 48 "select.from", '_expand_from_list',
49 'select.group_by'
a5a96dce 50 => sub { $_[0]->expand_expr({ -list => $_[2] }, -ident) },
ad993fa3 51 'select.having'
52 => sub { $_[0]->expand_expr($_[2]) },
d689c054 53 'update.from' => '_expand_from_list',
ad993fa3 54 "update.target", '_expand_update_clause_target',
55 "update.update", '_expand_update_clause_target',
d689c054 56 'delete.using' => '_expand_from_list',
57 'insert.rowvalues' => sub {
58 +(from => $_[0]->expand_expr({ -values => $_[2] }));
59 },
60 'insert.select' => sub {
61 +(from => $_[0]->expand_expr({ -select => $_[2] }));
62 },
63 ],
71c1b4d5 64 );
37b399a8 65
f7a20100 66 # set ops
ac3616e8 67 $sqla->wrap_expander(select => sub {
22f5ed9a 68 $self->cb('_expand_select', $_[0], \@before_setop);
95ab9342 69 });
f7a20100 70
d689c054 71 $self->register(
72 clause_renderer => [
73 'select.setop' => sub { $_[0]->render_aqt($_[2]) }
74 ],
2a7aa8b6 75 expander => [
76 map +($_ => '_expand_setop', "${_}_all" => '_expand_setop'), qw(union intersect except) ],
d689c054 77 renderer => [ map +($_ => '_render_setop'), qw(union intersect except) ],
5824607d 78 );
f7a20100 79
0f4de029 80 my $setop_expander = $self->cb('_expand_clause_setop');
bb36c26d 81
ac3616e8 82 $sqla->clause_expanders(
bb36c26d 83 map +($_ => $setop_expander),
84 map "select.${_}",
85 map +($_, "${_}_all", "${_}_distinct"),
86 qw(union intersect except)
87 );
f7fd09f7 88
d689c054 89 foreach my $stmt (qw(select insert update delete)) {
ac3616e8 90 $sqla->clauses_of($stmt => 'with', $sqla->clauses_of($stmt));
d689c054 91 $self->register(
92 clause_expanders => [
93 "${stmt}.with" => '_expand_with',
94 "${stmt}.with_recursive" => '_expand_with',
95 ],
96 clause_renderer => [ "${stmt}.with" => '_render_with' ],
97 );
44a6affb 98 }
6d42f0b9 99
ac3616e8 100 return $sqla;
f8392f56 101}
102
22f5ed9a 103sub _expand_select {
b5c13e0a 104 my ($self, $orig, $before_setop, @args) = @_;
105 my $exp = $self->sqla->$orig(@args);
22f5ed9a 106 return $exp unless my $setop = (my $sel = $exp->{-select})->{setop};
107 if (my @keys = grep $sel->{$_}, @$before_setop) {
108 my %inner; @inner{@keys} = delete @{$sel}{@keys};
109 unshift @{(values(%$setop))[0]{queries}},
110 { -select => \%inner };
111 }
112 return $exp;
113}
114
f79455dc 115sub _expand_from_list {
116 my ($self, undef, $args) = @_;
117 if (ref($args) eq 'HASH') {
09ceda11 118 return $args if $args->{-from_list};
f79455dc 119 return { -from_list => [ $self->expand_expr($args) ] };
120 }
121 my @list;
b9e35873 122 my @args = ref($args) eq 'ARRAY' ? @$args : ($args);
f79455dc 123 while (my $entry = shift @args) {
6990b2aa 124 if (!ref($entry) and $entry =~ /^-(.*)/) {
125 if ($1 eq 'as') {
126 $list[-1] = $self->expand_expr({ -as => [
127 $list[-1], map +(ref($_) eq 'ARRAY' ? @$_ : $_), shift(@args)
128 ]});
129 next;
130 }
f79455dc 131 $entry = { $entry => shift @args };
132 }
133 my $aqt = $self->expand_expr($entry, -ident);
134 if ($aqt->{-join} and not $aqt->{-join}{from}) {
135 $aqt->{-join}{from} = pop @list;
136 }
137 push @list, $aqt;
138 }
6d0e9332 139 return $list[0] if @list == 1;
f79455dc 140 return { -from_list => \@list };
141}
142
143sub _expand_join {
144 my ($self, undef, $args) = @_;
145 my %proto = (
146 ref($args) eq 'HASH'
147 ? %$args
ad133cfd 148 : (to => @$args)
f79455dc 149 );
6990b2aa 150 if (my $as = delete $proto{as}) {
5823ddfe 151 $proto{to} = $self->expand_expr(
152 { -as => [ { -from_list => $proto{to} }, $as ] }
153 );
6990b2aa 154 }
0891ae97 155 if (defined($proto{using}) and ref(my $using = $proto{using}) ne 'HASH') {
60757815 156 $proto{using} = [
0891ae97 157 map [ $self->expand_expr($_, -ident) ],
158 ref($using) eq 'ARRAY' ? @$using: $using
60757815 159 ];
0891ae97 160 }
5823ddfe 161 my %ret = (
162 type => delete $proto{type},
163 to => $self->expand_expr({ -from_list => delete $proto{to} }, -ident)
5823ddfe 164 );
165 %ret = (%ret,
166 map +($_ => $self->expand_expr($proto{$_}, -ident)),
167 sort keys %proto
168 );
f79455dc 169 return +{ -join => \%ret };
170}
171
172sub _render_from_list {
6c39a2f7 173 my ($self, undef, $list) = @_;
3e230b71 174 return $self->join_query_parts(', ', @$list);
f79455dc 175}
176
177sub _render_join {
6c39a2f7 178 my ($self, undef, $args) = @_;
f79455dc 179
180 my @parts = (
e48c4b9a 181 $args->{from},
312abf42 182 { -keyword => join '_', ($args->{type}||()), 'join' },
5823ddfe 183 (map +($_->{-ident} || $_->{-as}
184 ? $_
185 : ('(', $self->render_aqt($_, 1), ')')),
6d0e9332 186 map +(@{$_->{-from_list}||[]} == 1 ? $_->{-from_list}[0] : $_),
187 $args->{to}
5823ddfe 188 ),
f79455dc 189 ($args->{on} ? (
312abf42 190 { -keyword => 'on' },
e48c4b9a 191 $args->{on},
f79455dc 192 ) : ()),
193 ($args->{using} ? (
312abf42 194 { -keyword => 'using' },
60757815 195 '(', $args->{using}, ')',
f79455dc 196 ) : ()),
197 );
59c7f80e 198 return $self->join_query_parts(' ', @parts);
f79455dc 199}
200
6990b2aa 201sub _expand_op_as {
202 my ($self, undef, $vv, $k) = @_;
01e9b916 203 my @vv = (ref($vv) eq 'ARRAY' ? @$vv : $vv);
01e9b916 204 my $ik = $self->expand_expr($k, -ident);
30085c53 205 return +{ -as => [ $ik, $self->expand_expr($vv[0], -ident) ] }
01e9b916 206 if @vv == 1 and ref($vv[0]) eq 'HASH';
207
208 my @as = map $self->expand_expr($_, -ident), @vv;
30085c53 209 return { -as => [ $ik, $self->expand_expr({ -alias => \@as }) ] };
6990b2aa 210}
211
212sub _render_as {
6c39a2f7 213 my ($self, undef, $args) = @_;
01e9b916 214 my ($thing, $alias) = @$args;
59c7f80e 215 return $self->join_query_parts(
8d1295c3 216 ' ',
01e9b916 217 $thing,
312abf42 218 { -keyword => 'as' },
01e9b916 219 $alias,
369e7844 220 );
221}
222
223sub _render_alias {
01e9b916 224 my ($self, undef, $args) = @_;
369e7844 225 my ($as, @cols) = @$args;
226 return (@cols
59c7f80e 227 ? $self->join_query_parts('',
3e230b71 228 $as,
68a92d22 229 '(',
230 $self->join_query_parts(
231 ', ',
232 @cols
233 ),
234 ')',
369e7844 235 )
236 : $self->render_aqt($as)
6990b2aa 237 );
238}
239
af407e9a 240sub _expand_update_clause_target {
1107714b 241 my ($self, undef, $target) = @_;
af407e9a 242 +(target => $self->_expand_from_list(undef, $target));
243}
244
6d42f0b9 245sub _expand_cast {
246 my ($self, undef, $thing) = @_;
247 return { -func => [ cast => $thing ] } if ref($thing) eq 'HASH';
248 my ($cast, $to) = @{$thing};
249 +{ -func => [ cast => { -as => [
250 $self->expand_expr($cast),
251 $self->expand_expr($to, -ident),
252 ] } ] };
253}
254
255sub _expand_alias {
256 my ($self, undef, $args) = @_;
257 if (ref($args) eq 'HASH' and my $alias = $args->{-alias}) {
258 $args = $alias;
259 }
30085c53 260 my @parts = map $self->expand_expr($_, -ident),
261 ref($args) eq 'ARRAY' ? @{$args} : $args;
262 return $parts[0] if @parts == 1;
263 return { -alias => \@parts };
6d42f0b9 264}
265
266sub _expand_with {
267 my ($self, $name, $with) = @_;
268 my (undef, $type) = split '_', $name;
269 if (ref($with) eq 'HASH') {
270 return +{
271 %$with,
272 queries => [
273 map +[
274 $self->expand_expr({ -alias => $_->[0] }, -ident),
275 $self->expand_expr($_->[1]),
276 ], @{$with->{queries}}
277 ]
278 }
279 }
280 my @with = @$with;
281 my @exp;
282 while (my ($alias, $query) = splice @with, 0, 2) {
283 push @exp, [
284 $self->expand_expr({ -alias => $alias }, -ident),
285 $self->expand_expr($query)
286 ];
287 }
288 return +(with => { ($type ? (type => $type) : ()), queries => \@exp });
289}
290
291sub _render_with {
292 my ($self, undef, $with) = @_;
293 my $q_part = $self->join_query_parts(', ',
294 map {
295 my ($alias, $query) = @$_;
296 $self->join_query_parts(' ',
297 $alias,
312abf42 298 { -keyword => 'as' },
6d42f0b9 299 $query,
300 )
301 } @{$with->{queries}}
302 );
303 return $self->join_query_parts(' ',
312abf42 304 { -keyword => join '_', 'with', ($with->{type}||'') },
6d42f0b9 305 $q_part,
306 );
307}
308
4b5f7259 309sub _expand_setop {
310 my ($self, $setop, $args) = @_;
2a7aa8b6 311 my $is_all = $setop =~ s/_all$//;
4b5f7259 312 +{ "-${setop}" => {
2a7aa8b6 313 ($is_all ? (type => 'all') : ()),
314 (ref($args) eq 'ARRAY'
315 ? (queries => [ map $self->expand_expr($_), @$args ])
316 : (
317 %$args,
318 queries => [ map $self->expand_expr($_), @{$args->{queries}} ]
319 )
320 ),
4b5f7259 321 } };
322}
323
acfbc601 324sub _render_setop {
325 my ($self, $setop, $args) = @_;
326 $self->join_query_parts(
312abf42 327 { -keyword => ' '.join('_', $setop, ($args->{type}||())).' ' },
acfbc601 328 @{$args->{queries}}
329 );
330}
331
0f4de029 332sub _expand_clause_setop {
333 my ($self, $setop, $args) = @_;
334 my ($op, $type) = split '_', $setop;
335 +(setop => $self->expand_expr({
336 "-${op}" => {
337 ($type ? (type => $type) : ()),
338 queries => (ref($args) eq 'ARRAY' ? $args : [ $args ])
339 }
340 }));
341}
342
f8392f56 3431;
99fe0bf3 344
eba4f083 345__END__
346
99fe0bf3 347=head1 NAME
348
349SQL::Abstract::ExtraClauses - new/experimental additions to L<SQL::Abstract>
350
351=head1 SYNOPSIS
352
353 my $sqla = SQL::Abstract->new;
354 SQL::Abstract::ExtraClauses->apply_to($sqla);
355
5a062255 356=head1 WARNING
357
358This module is basically a nursery for things that seem like a good idea
359to live in until we figure out if we were right about that.
360
99fe0bf3 361=head1 METHODS
362
363=head2 apply_to
364
365Applies the plugin to an L<SQL::Abstract> object.
366
ad993fa3 367=head2 register_extensions
368
369Registers the extensions described below
370
99fe0bf3 371=head2 cb
372
373For plugin authors, creates a callback to call a method on the plugin.
374
ad993fa3 375=head2 register
376
377For plugin authors, registers callbacks more easily.
378
99fe0bf3 379=head2 sqla
380
381Available only during plugin callback executions, contains the currently
382active L<SQL::Abstract> object.
383
eba4f083 384=head1 NODE TYPES
385
386=head2 alias
387
388Represents a table alias. Expands name and column names with ident as default.
389
390 # expr
391 { -alias => [ 't', 'x', 'y', 'z' ] }
392
393 # aqt
394 { -alias => [
395 { -ident => [ 't' ] }, { -ident => [ 'x' ] },
396 { -ident => [ 'y' ] }, { -ident => [ 'z' ] },
397 ] }
398
399 # query
400 t(x, y, z)
401 []
402
403=head2 as
404
405Represents an sql AS. LHS is expanded with ident as default, RHS is treated
406as a list of arguments for the alias node.
407
408 # expr
409 { foo => { -as => 'bar' } }
410
411 # aqt
30085c53 412 { -as => [ { -ident => [ 'foo' ] }, { -ident => [ 'bar' ] } ] }
eba4f083 413
414 # query
415 foo AS bar
416 []
417
418 # expr
419 { -as => [ { -select => { _ => 'blah' } }, 't', 'blah' ] }
420
421 # aqt
422 { -as => [
423 { -select =>
424 { select => { -op => [ ',', { -ident => [ 'blah' ] } ] } }
425 },
426 { -alias => [ { -ident => [ 't' ] }, { -ident => [ 'blah' ] } ] },
427 ] }
428
429 # query
430 (SELECT blah) AS t(blah)
431 []
432
433=head2 cast
434
435 # expr
436 { -cast => [ { -ident => 'birthday' }, 'date' ] }
437
438 # aqt
439 { -func => [
440 'cast', {
441 -as => [ { -ident => [ 'birthday' ] }, { -ident => [ 'date' ] } ]
442 },
443 ] }
444
445 # query
446 CAST(birthday AS date)
447 []
448
ad133cfd 449=head2 join
450
451If given an arrayref, pretends it was given a hashref with the first
452element of the arrayref as the value for 'to' and the remaining pairs copied.
453
454Given a hashref, the 'as' key is if presented expanded to wrap the 'to'.
455
456If present the 'using' key is expanded as a list of idents.
457
458Known keys are: 'from' (the left hand side), 'type' ('left', 'right', or
459nothing), 'to' (the right hand side), 'on' and 'using'.
460
461 # expr
462 { -join => {
463 from => 'lft',
464 on => { 'lft.bloo' => { '>' => 'rgt.blee' } },
465 to => 'rgt',
466 type => 'left',
467 } }
468
469 # aqt
470 { -join => {
471 from => { -ident => [ 'lft' ] },
472 on => { -op => [
473 '>', { -ident => [ 'lft', 'bloo' ] },
474 { -ident => [ 'rgt', 'blee' ] },
475 ] },
476 to => { -ident => [ 'rgt' ] },
477 type => 'left',
478 } }
479
480 # query
481 lft LEFT JOIN rgt ON lft.bloo > rgt.blee
482 []
483
484=head2 from_list
485
486List of components of the FROM clause; -foo type elements indicate a pair
487with the next element; this is easiest if I show you:
488
489 # expr
490 { -from_list => [
491 't1', -as => 'table_one', -join =>
492 [ 't2', 'on', { 'table_one.x' => 't2.x' } ],
493 ] }
494
495 # aqt
6d0e9332 496 { -join => {
30085c53 497 from =>
498 {
499 -as => [ { -ident => [ 't1' ] }, { -ident => [ 'table_one' ] } ]
500 },
6d0e9332 501 on => { -op => [
502 '=', { -ident => [ 'table_one', 'x' ] },
503 { -ident => [ 't2', 'x' ] },
504 ] },
505 to => { -ident => [ 't2' ] },
506 type => undef,
507 } }
ad133cfd 508
509 # query
510 t1 AS table_one JOIN t2 ON table_one.x = t2.x
511 []
512
513Or with using:
514
515 # expr
516 { -from_list =>
517 [ 't1', -as => 'table_one', -join => [ 't2', 'using', [ 'x' ] ] ]
518 }
519
520 # aqt
6d0e9332 521 { -join => {
30085c53 522 from =>
523 {
524 -as => [ { -ident => [ 't1' ] }, { -ident => [ 'table_one' ] } ]
525 },
6d0e9332 526 to => { -ident => [ 't2' ] },
527 type => undef,
528 using =>
529 { -op => [ 'or', { -op => [ 'or', { -ident => [ 'x' ] } ] } ] },
530 } }
ad133cfd 531
532 # query
533 t1 AS table_one JOIN t2 USING ( x )
534 []
535
536With oddities:
537
538 # expr
539 { -from_list => [
5823ddfe 540 'x', -join =>
541 [ [ 'y', -join => [ 'z', 'type', 'left' ] ], 'type', 'left' ],
ad133cfd 542 ] }
543
544 # aqt
6d0e9332 545 { -join => {
546 from => { -ident => [ 'x' ] },
547 to => { -join => {
548 from => { -ident => [ 'y' ] },
549 to => { -ident => [ 'z' ] },
ad133cfd 550 type => 'left',
6d0e9332 551 } },
552 type => 'left',
553 } }
ad133cfd 554
555 # query
556 x LEFT JOIN ( y LEFT JOIN z )
557 []
558
2a7aa8b6 559=head2 setops
560
561Expanders are provided for union, union_all, intersect, intersect_all,
562except and except_all, and each takes an arrayref of queries:
563
564 # expr
565 { -union => [
566 { -select => { _ => { -value => 1 } } },
567 { -select => { _ => { -value => 2 } } },
568 ] }
569
570 # aqt
571 { -union => { queries => [
572 { -select =>
573 { select => { -op => [ ',', { -bind => [ undef, 1 ] } ] } }
574 },
575 { -select =>
576 { select => { -op => [ ',', { -bind => [ undef, 2 ] } ] } }
577 },
578 ] } }
579
580 # query
581 (SELECT ?) UNION (SELECT ?)
582 [ 1, 2 ]
583
584 # expr
585 { -union_all => [
586 { -select => { _ => { -value => 1 } } },
587 { -select => { _ => { -value => 2 } } },
588 { -select => { _ => { -value => 1 } } },
589 ] }
590
591 # aqt
592 { -union => {
593 queries => [
594 { -select =>
595 { select => { -op => [ ',', { -bind => [ undef, 1 ] } ] } }
596 },
597 { -select =>
598 { select => { -op => [ ',', { -bind => [ undef, 2 ] } ] } }
599 },
600 { -select =>
601 { select => { -op => [ ',', { -bind => [ undef, 1 ] } ] } }
602 },
603 ],
604 type => 'all',
605 } }
606
607 # query
608 (SELECT ?) UNION ALL (SELECT ?) UNION ALL (SELECT ?)
609 [ 1, 2, 1 ]
610
5a062255 611=head1 STATEMENT EXTENSIONS
612
613=head2 group by clause for select
614
615Expanded as a list with an ident default:
616
617 # expr
618 { -select => { group_by => [ 'foo', 'bar' ] } }
619
620 # aqt
621 { -select => { group_by =>
622 {
623 -op => [ ',', { -ident => [ 'foo' ] }, { -ident => [ 'bar' ] } ]
624 }
625 } }
626
627 # query
628 GROUP BY foo, bar
629 []
630
631=head2 having clause for select
632
633Basic expr, just like where, given having is pretty much post-group-by
634where clause:
635
636 # expr
637 { -select =>
638 { having => { '>' => [ { -count => { -ident => 'foo' } }, 3 ] } }
639 }
640
641 # aqt
642 { -select => { having => { -op => [
643 '>', { -func => [ 'count', { -ident => [ 'foo' ] } ] },
644 { -bind => [ undef, 3 ] },
645 ] } } }
646
647 # query
648 HAVING COUNT(foo) > ?
649 [ 3 ]
650
2a7aa8b6 651=head2 setop clauses
652
653If a select query contains a clause matching any of the setop node types,
654clauses that appear before the setop would in the resulting query are
655gathered together and moved into an inner select node:
656
657 # expr
658 { -select => {
659 _ => '*',
660 from => 'foo',
661 order_by => 'baz',
662 union =>
663 {
664 -select => { _ => '*', from => 'bar', where => { thing => 1 } }
665 },
666 where => { thing => 1 },
667 } }
668
669 # aqt
670 { -select => {
671 order_by => { -op => [ ',', { -ident => [ 'baz' ] } ] },
672 setop => { -union => { queries => [
673 { -select => {
674 from => { -ident => [ 'foo' ] },
675 select => { -op => [ ',', { -ident => [ '*' ] } ] },
676 where => { -op => [
677 '=', { -ident => [ 'thing' ] },
678 { -bind => [ 'thing', 1 ] },
679 ] },
680 } }, ] },
681 { -select => {
682 from => { -ident => [ 'bar' ] },
683 select => { -op => [ ',', { -ident => [ '*' ] } ] },
684 where => { -op => [
685 '=', { -ident => [ 'thing' ] },
686 { -bind => [ 'thing', 1 ] },
687 } },
688 ] } },
689 } }
690
691 # query
692 (SELECT * FROM foo WHERE thing = ?) UNION (
693 SELECT * FROM bar WHERE thing = ?
694 )
695 ORDER BY baz
696 [ 1, 1 ]
697
82b00b2f 698=head2 update from clause
699
700Some databases allow an additional FROM clause to reference other tables
701for the data to update; this clause is expanded as a normal from list, check
702your database for what is and isn't allowed in practice.
703
704 # expr
705 { -update => {
706 _ => 'employees',
707 from => 'accounts',
708 set => { sales_count => { sales_count => { '+' => \1 } } },
709 where => {
710 'accounts.name' => { '=' => \"'Acme Corporation'" },
711 'employees.id' => { -ident => 'accounts.sales_person' },
712 },
713 } }
714
715 # aqt
716 { -update => {
717 from => { -ident => [ 'accounts' ] },
718 set => { -op => [
719 ',', { -op => [
720 '=', { -ident => [ 'sales_count' ] }, { -op => [
721 '+', { -ident => [ 'sales_count' ] },
722 { -literal => [ 1 ] },
723 ] },
724 ] },
725 ] },
726 target => { -ident => [ 'employees' ] },
727 where => { -op => [
728 'and', { -op => [
729 '=', { -ident => [ 'accounts', 'name' ] },
730 { -literal => [ "'Acme Corporation'" ] },
731 ] }, { -op => [
732 '=', { -ident => [ 'employees', 'id' ] },
733 { -ident => [ 'accounts', 'sales_person' ] },
734 ] },
735 ] },
736 } }
737
738 # query
739 UPDATE employees SET sales_count = sales_count + 1 FROM accounts
740 WHERE (
741 accounts.name = 'Acme Corporation'
742 AND employees.id = accounts.sales_person
743 )
82b00b2f 744 []
745
746=head2 delete using clause
747
748Some databases allow an additional USING clause to reference other tables
749for the data to update; this clause is expanded as a normal from list, check
750your database for what is and isn't allowed in practice.
751
752 # expr
753 { -delete => {
754 from => 'x',
755 using => 'y',
756 where => { 'x.id' => { -ident => 'y.x_id' } },
757 } }
758
759 # aqt
760 { -delete => {
761 target => { -op => [ ',', { -ident => [ 'x' ] } ] },
762 using => { -ident => [ 'y' ] },
763 where => { -op => [
764 '=', { -ident => [ 'x', 'id' ] },
765 { -ident => [ 'y', 'x_id' ] },
766 ] },
767 } }
768
769 # query
770 DELETE FROM x USING y WHERE x.id = y.x_id
771 []
772
23409fc2 773=head2 insert rowvalues and select clauses
774
775rowvalues and select are shorthand for
776
777 { from => { -select ... } }
778
779and
780
781 { from => { -values ... } }
782
783respectively:
784
785 # expr
786 { -insert =>
787 { into => 'numbers', rowvalues => [ [ 1, 2 ], [ 3, 4 ], [ 5, 6 ] ] }
788 }
789
790 # aqt
791 { -insert => {
792 from => { -values => [
793 { -row =>
794 [ { -bind => [ undef, 1 ] }, { -bind => [ undef, 2 ] } ]
795 },
796 { -row =>
797 [ { -bind => [ undef, 3 ] }, { -bind => [ undef, 4 ] } ]
798 },
799 { -row =>
800 [ { -bind => [ undef, 5 ] }, { -bind => [ undef, 6 ] } ]
801 },
802 ] },
803 target => { -ident => [ 'numbers' ] },
804 } }
805
806 # query
807 INSERT INTO numbers VALUES (?, ?), (?, ?), (?, ?)
808 [ 1, 2, 3, 4, 5, 6 ]
809
810 # expr
811 { -insert =>
812 { into => 'numbers', select => { _ => '*', from => 'old_numbers' } }
813 }
814
815 # aqt
816 { -insert => {
817 from => { -select => {
818 from => { -ident => [ 'old_numbers' ] },
819 select => { -op => [ ',', { -ident => [ '*' ] } ] },
820 } },
821 target => { -ident => [ 'numbers' ] },
822 } }
823
824 # query
825 INSERT INTO numbers SELECT * FROM old_numbers
826 []
827
828=head2 with and with_recursive clauses
829
830These clauses are available on select/insert/update/delete queries; check
831your database for applicability (e.g. mysql supports all four but mariadb
832only select).
833
834The value should be an arrayref of name/query pairs:
835
836 # expr
837 { -select => {
838 from => 'foo',
839 select => '*',
840 with => [ 'foo', { -select => { select => \1 } } ],
841 } }
842
843 # aqt
844 { -select => {
845 from => { -ident => [ 'foo' ] },
846 select => { -op => [ ',', { -ident => [ '*' ] } ] },
847 with => { queries => [ [
848 { -ident => [ 'foo' ] }, { -select =>
849 { select => { -op => [ ',', { -literal => [ 1 ] } ] } }
850 },
851 ] ] },
852 } }
853
854 # query
855 WITH foo AS (SELECT 1) SELECT * FROM foo
856 []
857
858A more complete example (designed for mariadb, (ab)using the fact that
859mysqloids materialise subselects in FROM into an unindexed temp table to
860circumvent the restriction that you can't select from the table you're
861currently updating:
862
863 # expr
864 { -update => {
865 _ => [
866 'tree_table', -join => {
867 as => 'tree',
868 on => { 'tree.id' => 'tree_with_path.id' },
869 to => { -select => {
870 from => 'tree_with_path',
871 select => '*',
872 with_recursive => [
873 [ 'tree_with_path', 'id', 'parent_id', 'path' ],
874 { -select => {
875 _ => [
876 'id', 'parent_id', { -as => [
877 { -cast => { -as => [ 'id', 'char', 255 ] } },
878 'path',
879 ] } ],
880 from => 'tree_table',
881 union_all => { -select => {
882 _ => [
883 't.id', 't.parent_id', { -as => [
884 { -concat => [ 'r.path', \"'/'", 't.id' ] },
885 'path',
886 ] },
887 ],
888 from => [
889 'tree_table', -as => 't', -join => {
890 as => 'r',
891 on => { 't.parent_id' => 'r.id' },
892 to => 'tree_with_path',
893 },
894 ],
895 } },
896 where => { parent_id => undef },
897 } },
898 ],
899 } },
900 },
901 ],
902 set => { path => { -ident => [ 'tree', 'path' ] } },
903 } }
904
905 # query
906 UPDATE
907 tree_table JOIN
908 (
909 WITH RECURSIVE
910 tree_with_path(id, parent_id, path) AS (
911 (
912 SELECT id, parent_id, CAST(id AS char(255)) AS path
913 FROM tree_table WHERE parent_id IS NULL
914 ) UNION ALL (
915 SELECT t.id, t.parent_id, CONCAT(r.path, '/', t.id) AS path
916 FROM
917 tree_table AS t JOIN tree_with_path AS r ON
918 t.parent_id = r.id
919 )
920 )
921 SELECT * FROM tree_with_path
922 ) AS tree
923 ON tree.id = tree_with_path.id
924 SET path = tree.path
925 []
926
99fe0bf3 927=cut