plugin registration
[scpubgit/Q-Branch.git] / lib / SQL / Abstract / Plugin / ExtraClauses.pm
CommitLineData
d10d5b94 1package SQL::Abstract::Plugin::ExtraClauses;
1ff9018c 2
df5d0507 3use Moo;
4
d10d5b94 5with 'SQL::Abstract::Role::Plugin';
ec49a2e1 6
7sub register_extensions {
8 my ($self, $sqla) = @_;
f1c52c0c 9
ad078c71 10 my @clauses = $sqla->clauses_of('select');
2b0b3d43 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 }
f1c52c0c 19
2b0b3d43 20 die "Huh?" unless @before_setop;
4be5ded3 21 $sqla->clauses_of(select => @clauses);
58ebc7fa 22
ad078c71 23 $sqla->clauses_of(update => sub {
ee706e89 24 my ($self, @clauses) = @_;
25 splice(@clauses, 2, 0, 'from');
26 @clauses;
27 });
28
ad078c71 29 $sqla->clauses_of(delete => sub {
ee706e89 30 my ($self, @clauses) = @_;
31 splice(@clauses, 1, 0, 'using');
32 @clauses;
33 });
34
4be5ded3 35 $self->register(
f1c52c0c 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' ],
4be5ded3 47 clause_expanders => [
f1c52c0c 48 "select.from", '_expand_from_list',
49 'select.group_by'
8480fb63 50 => sub { $_[0]->expand_expr({ -list => $_[2] }, -ident) },
f1c52c0c 51 'select.having'
52 => sub { $_[0]->expand_expr($_[2]) },
4be5ded3 53 'update.from' => '_expand_from_list',
f1c52c0c 54 "update.target", '_expand_update_clause_target',
55 "update.update", '_expand_update_clause_target',
4be5ded3 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 ],
58ebc7fa 64 );
26994fdd 65
2b0b3d43 66 # set ops
ad078c71 67 $sqla->wrap_expander(select => sub {
c671eba6 68 $self->cb('_expand_select', $_[0], \@before_setop);
3f9899e5 69 });
2b0b3d43 70
4be5ded3 71 $self->register(
72 clause_renderer => [
73 'select.setop' => sub { $_[0]->render_aqt($_[2]) }
74 ],
de3eeaea 75 expander => [
76 map +($_ => '_expand_setop', "${_}_all" => '_expand_setop'), qw(union intersect except) ],
4be5ded3 77 renderer => [ map +($_ => '_render_setop'), qw(union intersect except) ],
e2db8228 78 );
2b0b3d43 79
41086177 80 my $setop_expander = $self->cb('_expand_clause_setop');
f61bfd7b 81
ad078c71 82 $sqla->clause_expanders(
f61bfd7b 83 map +($_ => $setop_expander),
84 map "select.${_}",
85 map +($_, "${_}_all", "${_}_distinct"),
86 qw(union intersect except)
87 );
d175037f 88
4be5ded3 89 foreach my $stmt (qw(select insert update delete)) {
ad078c71 90 $sqla->clauses_of($stmt => 'with', $sqla->clauses_of($stmt));
4be5ded3 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 );
a97ecd95 98 }
2f4717ad 99
ad078c71 100 return $sqla;
1ff9018c 101}
102
c671eba6 103sub _expand_select {
093442c8 104 my ($self, $orig, $before_setop, @args) = @_;
105 my $exp = $self->sqla->$orig(@args);
c671eba6 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
7741b7ad 115sub _expand_from_list {
116 my ($self, undef, $args) = @_;
117 if (ref($args) eq 'HASH') {
38e67490 118 return $args if $args->{-from_list};
7741b7ad 119 return { -from_list => [ $self->expand_expr($args) ] };
120 }
121 my @list;
86a6ebf4 122 my @args = ref($args) eq 'ARRAY' ? @$args : ($args);
7741b7ad 123 while (my $entry = shift @args) {
b99e9a14 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 }
7741b7ad 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 }
09742461 139 return $list[0] if @list == 1;
7741b7ad 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
d36cb439 148 : (to => @$args)
7741b7ad 149 );
b99e9a14 150 if (my $as = delete $proto{as}) {
ca97398a 151 $proto{to} = $self->expand_expr(
152 { -as => [ { -from_list => $proto{to} }, $as ] }
153 );
b99e9a14 154 }
e0eb8d26 155 if (defined($proto{using}) and ref(my $using = $proto{using}) ne 'HASH') {
13c99dad 156 $proto{using} = [
e0eb8d26 157 map [ $self->expand_expr($_, -ident) ],
158 ref($using) eq 'ARRAY' ? @$using: $using
13c99dad 159 ];
e0eb8d26 160 }
ca97398a 161 my %ret = (
162 type => delete $proto{type},
163 to => $self->expand_expr({ -from_list => delete $proto{to} }, -ident)
ca97398a 164 );
165 %ret = (%ret,
166 map +($_ => $self->expand_expr($proto{$_}, -ident)),
167 sort keys %proto
168 );
7741b7ad 169 return +{ -join => \%ret };
170}
171
172sub _render_from_list {
a01911a2 173 my ($self, undef, $list) = @_;
a1f8b6ef 174 return $self->join_query_parts(', ', @$list);
7741b7ad 175}
176
177sub _render_join {
a01911a2 178 my ($self, undef, $args) = @_;
7741b7ad 179
180 my @parts = (
412f9efe 181 $args->{from},
1601bb47 182 { -keyword => join '_', ($args->{type}||()), 'join' },
ca97398a 183 (map +($_->{-ident} || $_->{-as}
184 ? $_
185 : ('(', $self->render_aqt($_, 1), ')')),
09742461 186 map +(@{$_->{-from_list}||[]} == 1 ? $_->{-from_list}[0] : $_),
187 $args->{to}
ca97398a 188 ),
7741b7ad 189 ($args->{on} ? (
1601bb47 190 { -keyword => 'on' },
412f9efe 191 $args->{on},
7741b7ad 192 ) : ()),
193 ($args->{using} ? (
1601bb47 194 { -keyword => 'using' },
13c99dad 195 '(', $args->{using}, ')',
7741b7ad 196 ) : ()),
197 );
0236f122 198 return $self->join_query_parts(' ', @parts);
7741b7ad 199}
200
b99e9a14 201sub _expand_op_as {
202 my ($self, undef, $vv, $k) = @_;
984db0d7 203 my @vv = (ref($vv) eq 'ARRAY' ? @$vv : $vv);
984db0d7 204 my $ik = $self->expand_expr($k, -ident);
4979c509 205 return +{ -as => [ $ik, $self->expand_expr($vv[0], -ident) ] }
984db0d7 206 if @vv == 1 and ref($vv[0]) eq 'HASH';
207
208 my @as = map $self->expand_expr($_, -ident), @vv;
4979c509 209 return { -as => [ $ik, $self->expand_expr({ -alias => \@as }) ] };
b99e9a14 210}
211
212sub _render_as {
a01911a2 213 my ($self, undef, $args) = @_;
984db0d7 214 my ($thing, $alias) = @$args;
0236f122 215 return $self->join_query_parts(
ac7992be 216 ' ',
984db0d7 217 $thing,
1601bb47 218 { -keyword => 'as' },
984db0d7 219 $alias,
1ba47f38 220 );
221}
222
223sub _render_alias {
984db0d7 224 my ($self, undef, $args) = @_;
1ba47f38 225 my ($as, @cols) = @$args;
226 return (@cols
0236f122 227 ? $self->join_query_parts('',
a1f8b6ef 228 $as,
3f312d2e 229 '(',
230 $self->join_query_parts(
231 ', ',
232 @cols
233 ),
234 ')',
1ba47f38 235 )
236 : $self->render_aqt($as)
b99e9a14 237 );
238}
239
f9f1fdcd 240sub _expand_update_clause_target {
fe8b493f 241 my ($self, undef, $target) = @_;
f9f1fdcd 242 +(target => $self->_expand_from_list(undef, $target));
243}
244
2f4717ad 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 }
4979c509 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 };
2f4717ad 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,
1601bb47 298 { -keyword => 'as' },
2f4717ad 299 $query,
300 )
301 } @{$with->{queries}}
302 );
303 return $self->join_query_parts(' ',
1601bb47 304 { -keyword => join '_', 'with', ($with->{type}||'') },
2f4717ad 305 $q_part,
306 );
307}
308
51046d0e 309sub _expand_setop {
310 my ($self, $setop, $args) = @_;
de3eeaea 311 my $is_all = $setop =~ s/_all$//;
51046d0e 312 +{ "-${setop}" => {
de3eeaea 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 ),
51046d0e 321 } };
322}
323
b5f4a869 324sub _render_setop {
325 my ($self, $setop, $args) = @_;
326 $self->join_query_parts(
1601bb47 327 { -keyword => ' '.join('_', $setop, ($args->{type}||())).' ' },
b5f4a869 328 @{$args->{queries}}
329 );
330}
331
41086177 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
1ff9018c 3431;
3001f097 344
583c7957 345__END__
346
3001f097 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
6aac9987 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
3001f097 361=head1 METHODS
362
363=head2 apply_to
364
365Applies the plugin to an L<SQL::Abstract> object.
366
f1c52c0c 367=head2 register_extensions
368
369Registers the extensions described below
370
3001f097 371=head2 cb
372
373For plugin authors, creates a callback to call a method on the plugin.
374
f1c52c0c 375=head2 register
376
377For plugin authors, registers callbacks more easily.
378
3001f097 379=head2 sqla
380
381Available only during plugin callback executions, contains the currently
382active L<SQL::Abstract> object.
383
583c7957 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
4979c509 412 { -as => [ { -ident => [ 'foo' ] }, { -ident => [ 'bar' ] } ] }
583c7957 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
d36cb439 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
09742461 496 { -join => {
4979c509 497 from =>
498 {
499 -as => [ { -ident => [ 't1' ] }, { -ident => [ 'table_one' ] } ]
500 },
09742461 501 on => { -op => [
502 '=', { -ident => [ 'table_one', 'x' ] },
503 { -ident => [ 't2', 'x' ] },
504 ] },
505 to => { -ident => [ 't2' ] },
506 type => undef,
507 } }
d36cb439 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
09742461 521 { -join => {
4979c509 522 from =>
523 {
524 -as => [ { -ident => [ 't1' ] }, { -ident => [ 'table_one' ] } ]
525 },
09742461 526 to => { -ident => [ 't2' ] },
527 type => undef,
528 using =>
529 { -op => [ 'or', { -op => [ 'or', { -ident => [ 'x' ] } ] } ] },
530 } }
d36cb439 531
532 # query
533 t1 AS table_one JOIN t2 USING ( x )
534 []
535
536With oddities:
537
538 # expr
539 { -from_list => [
ca97398a 540 'x', -join =>
541 [ [ 'y', -join => [ 'z', 'type', 'left' ] ], 'type', 'left' ],
d36cb439 542 ] }
543
544 # aqt
09742461 545 { -join => {
546 from => { -ident => [ 'x' ] },
547 to => { -join => {
548 from => { -ident => [ 'y' ] },
549 to => { -ident => [ 'z' ] },
d36cb439 550 type => 'left',
09742461 551 } },
552 type => 'left',
553 } }
d36cb439 554
555 # query
556 x LEFT JOIN ( y LEFT JOIN z )
557 []
558
de3eeaea 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
6aac9987 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
de3eeaea 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
49dba492 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 )
49dba492 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
e376aa80 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
3001f097 927=cut