make pod tests happy
[dbsrgits/SQL-Abstract.git] / lib / SQL / Abstract / ExtraClauses.pm
CommitLineData
f8392f56 1package SQL::Abstract::ExtraClauses;
2
b40d30dc 3use Moo;
4
5has sqla => (
6 is => 'ro', init_arg => undef,
7 handles => [ qw(
312abf42 8 expand_expr expand_maybe_list_expr render_aqt join_query_parts
b40d30dc 9 ) ],
10);
f8392f56 11
b40d30dc 12sub cb {
22f5ed9a 13 my ($self, $method, @args) = @_;
14 return sub {
15 local $self->{sqla} = shift;
16 $self->$method(@args, @_)
17 };
b40d30dc 18}
19
20sub apply_to {
21 my ($self, $sqla) = @_;
22 $self = $self->new unless ref($self);
ac3616e8 23 my @clauses = $sqla->clauses_of('select');
f7a20100 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;
ac3616e8 33 $sqla->clauses_of(select => 'with', @clauses);
34 $sqla->clause_expanders(
b40d30dc 35 'select.group_by', $self->cb(sub {
035449c2 36 $_[0]->expand_maybe_list_expr($_[2], -ident)
b40d30dc 37 }),
38 'select.having', $self->cb(sub { $_[0]->expand_expr($_[2]) }),
71c1b4d5 39 );
01dfea60 40 foreach my $thing (qw(join from_list)) {
ac3616e8 41 $sqla->expander($thing => $self->cb("_expand_${thing}"))
b40d30dc 42 ->renderer($thing => $self->cb("_render_${thing}"))
01dfea60 43 }
e1c7c0b5 44 $sqla->binop_expander(as => $self->cb('_expand_op_as'));
ac3616e8 45 $sqla->renderer(as => $self->cb('_render_as'));
6d42f0b9 46 $sqla->expander(alias => $self->cb('_expand_alias'));
ac3616e8 47 $sqla->renderer(alias => $self->cb('_render_alias'));
71c1b4d5 48
ac3616e8 49 $sqla->clauses_of(update => sub {
e6b86bee 50 my ($self, @clauses) = @_;
51 splice(@clauses, 2, 0, 'from');
52 @clauses;
53 });
54
ac3616e8 55 $sqla->clauses_of(delete => sub {
e6b86bee 56 my ($self, @clauses) = @_;
57 splice(@clauses, 1, 0, 'using');
58 @clauses;
59 });
60
ac3616e8 61 $sqla->clause_expanders(
c319683e 62 'update.from' => $self->cb('_expand_from_list'),
adda3bd8 63 'delete.using' => $self->cb('_expand_from_list'),
b40d30dc 64 'insert.rowvalues' => $self->cb(sub {
1107714b 65 +(from => $_[0]->expand_expr({ -values => $_[2] }));
b40d30dc 66 }),
67 'insert.select' => $self->cb(sub {
1107714b 68 +(from => $_[0]->expand_expr({ -select => $_[2] }));
b40d30dc 69 }),
71c1b4d5 70 );
37b399a8 71
f7a20100 72 # set ops
ac3616e8 73 $sqla->wrap_expander(select => sub {
22f5ed9a 74 $self->cb('_expand_select', $_[0], \@before_setop);
95ab9342 75 });
f7a20100 76
5824607d 77 $sqla->clause_renderer(
78 'select.setop' => $self->cb(sub { $_[0]->render_aqt($_[2]); })
79 );
f7a20100 80
4b5f7259 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 }
b42692a6 85
0f4de029 86 my $setop_expander = $self->cb('_expand_clause_setop');
bb36c26d 87
ac3616e8 88 $sqla->clause_expanders(
bb36c26d 89 map +($_ => $setop_expander),
90 map "select.${_}",
91 map +($_, "${_}_all", "${_}_distinct"),
92 qw(union intersect except)
93 );
f7fd09f7 94
6d42f0b9 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
44a6affb 101 foreach my $stmt (qw(insert update delete)) {
ac3616e8 102 $sqla->clauses_of($stmt => 'with', $sqla->clauses_of($stmt));
6d42f0b9 103 $sqla->clause_expander("${stmt}.$_" => $w_exp)
44a6affb 104 for qw(with with_recursive);
6d42f0b9 105 $sqla->clause_renderer("${stmt}.with" => $w_rdr);
44a6affb 106 }
6d42f0b9 107
108 $sqla->expander(cast => $self->cb('_expand_cast'));
b40d30dc 109
ac3616e8 110 $sqla->clause_expanders(
c319683e 111 "select.from", $self->cb('_expand_from_list'),
b40d30dc 112 "update.target", $self->cb('_expand_update_clause_target'),
113 "update.update", $self->cb('_expand_update_clause_target'),
114 );
f7a20100 115
ac3616e8 116 return $sqla;
f8392f56 117}
118
22f5ed9a 119sub _expand_select {
b5c13e0a 120 my ($self, $orig, $before_setop, @args) = @_;
121 my $exp = $self->sqla->$orig(@args);
22f5ed9a 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
f79455dc 131sub _expand_from_list {
132 my ($self, undef, $args) = @_;
133 if (ref($args) eq 'HASH') {
09ceda11 134 return $args if $args->{-from_list};
f79455dc 135 return { -from_list => [ $self->expand_expr($args) ] };
136 }
137 my @list;
b9e35873 138 my @args = ref($args) eq 'ARRAY' ? @$args : ($args);
f79455dc 139 while (my $entry = shift @args) {
6990b2aa 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 }
f79455dc 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 );
6990b2aa 165 if (my $as = delete $proto{as}) {
01e9b916 166 $proto{to} = $self->expand_expr({ -as => [ $proto{to}, $as ] });
6990b2aa 167 }
0891ae97 168 if (defined($proto{using}) and ref(my $using = $proto{using}) ne 'HASH') {
60757815 169 $proto{using} = [
0891ae97 170 map [ $self->expand_expr($_, -ident) ],
171 ref($using) eq 'ARRAY' ? @$using: $using
60757815 172 ];
0891ae97 173 }
f79455dc 174 my %ret = map +($_ => $self->expand_expr($proto{$_}, -ident)),
175 sort keys %proto;
176 return +{ -join => \%ret };
177}
178
179sub _render_from_list {
6c39a2f7 180 my ($self, undef, $list) = @_;
3e230b71 181 return $self->join_query_parts(', ', @$list);
f79455dc 182}
183
184sub _render_join {
6c39a2f7 185 my ($self, undef, $args) = @_;
f79455dc 186
187 my @parts = (
e48c4b9a 188 $args->{from},
312abf42 189 { -keyword => join '_', ($args->{type}||()), 'join' },
cbe3b5a9 190 (map +($_->{-ident} || $_->{-as} ? $_ : ('(', $_, ')')), $args->{to}),
f79455dc 191 ($args->{on} ? (
312abf42 192 { -keyword => 'on' },
e48c4b9a 193 $args->{on},
f79455dc 194 ) : ()),
195 ($args->{using} ? (
312abf42 196 { -keyword => 'using' },
60757815 197 '(', $args->{using}, ')',
f79455dc 198 ) : ()),
199 );
59c7f80e 200 return $self->join_query_parts(' ', @parts);
f79455dc 201}
202
6990b2aa 203sub _expand_op_as {
204 my ($self, undef, $vv, $k) = @_;
01e9b916 205 my @vv = (ref($vv) eq 'ARRAY' ? @$vv : $vv);
01e9b916 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 } ] };
6990b2aa 212}
213
214sub _render_as {
6c39a2f7 215 my ($self, undef, $args) = @_;
01e9b916 216 my ($thing, $alias) = @$args;
59c7f80e 217 return $self->join_query_parts(
8d1295c3 218 ' ',
01e9b916 219 $thing,
312abf42 220 { -keyword => 'as' },
01e9b916 221 $alias,
369e7844 222 );
223}
224
225sub _render_alias {
01e9b916 226 my ($self, undef, $args) = @_;
369e7844 227 my ($as, @cols) = @$args;
228 return (@cols
59c7f80e 229 ? $self->join_query_parts('',
3e230b71 230 $as,
68a92d22 231 '(',
232 $self->join_query_parts(
233 ', ',
234 @cols
235 ),
236 ')',
369e7844 237 )
238 : $self->render_aqt($as)
6990b2aa 239 );
240}
241
af407e9a 242sub _expand_update_clause_target {
1107714b 243 my ($self, undef, $target) = @_;
af407e9a 244 +(target => $self->_expand_from_list(undef, $target));
245}
246
6d42f0b9 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,
312abf42 301 { -keyword => 'as' },
6d42f0b9 302 $query,
303 )
304 } @{$with->{queries}}
305 );
306 return $self->join_query_parts(' ',
312abf42 307 { -keyword => join '_', 'with', ($with->{type}||'') },
6d42f0b9 308 $q_part,
309 );
310}
311
4b5f7259 312sub _expand_setop {
313 my ($self, $setop, $args) = @_;
314 +{ "-${setop}" => {
315 %$args,
316 queries => [ map $self->expand_expr($_), @{$args->{queries}} ],
317 } };
318}
319
acfbc601 320sub _render_setop {
321 my ($self, $setop, $args) = @_;
322 $self->join_query_parts(
312abf42 323 { -keyword => ' '.join('_', $setop, ($args->{type}||())).' ' },
acfbc601 324 @{$args->{queries}}
325 );
326}
327
0f4de029 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
f8392f56 3391;
99fe0bf3 340
341=head1 NAME
342
343SQL::Abstract::ExtraClauses - new/experimental additions to L<SQL::Abstract>
344
345=head1 SYNOPSIS
346
347 my $sqla = SQL::Abstract->new;
348 SQL::Abstract::ExtraClauses->apply_to($sqla);
349
350=head1 METHODS
351
352=head2 apply_to
353
354Applies the plugin to an L<SQL::Abstract> object.
355
356=head2 cb
357
358For plugin authors, creates a callback to call a method on the plugin.
359
360=head2 sqla
361
362Available only during plugin callback executions, contains the currently
363active L<SQL::Abstract> object.
364
365=cut