cleaner plugin registration
[scpubgit/Q-Branch.git] / lib / SQL / Abstract / ExtraClauses.pm
1 package SQL::Abstract::ExtraClauses;
2
3 use Moo;
4
5 has sqla => (
6   is => 'ro', init_arg => undef,
7   handles => [ qw(
8     expand_expr render_aqt
9     format_keyword join_query_parts
10   ) ],
11 );
12
13 BEGIN { *puke = \&SQL::Abstract::puke }
14
15 sub cb {
16   my ($self, $method) = @_;
17   return sub { local $self->{sqla} = shift; $self->$method(@_) };
18 }
19
20 sub apply_to {
21   my ($self, $sqla) = @_;
22   $self = $self->new unless ref($self);
23   my @clauses = $sqla->clauses_of('select');
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;
33   $sqla->clauses_of(select => 'with', @clauses);
34   $sqla->clause_expanders(
35     'select.group_by', $self->cb(sub {
36       $_[0]->sqla->_expand_maybe_list_expr($_[2], -ident)
37     }),
38     'select.having', $self->cb(sub { $_[0]->expand_expr($_[2]) }),
39   );
40   foreach my $thing (qw(join from_list)) {
41     $sqla->expander($thing => $self->cb("_expand_${thing}"))
42          ->renderer($thing => $self->cb("_render_${thing}"))
43   }
44   $sqla->op_expander(as => $self->cb('_expand_op_as'));
45   $sqla->expander(as => $self->cb('_expand_op_as'));
46   $sqla->renderer(as => $self->cb('_render_as'));
47   $sqla->expander(alias => $self->cb(sub {
48     my ($self, undef, $args) = @_;
49     if (ref($args) eq 'HASH' and my $alias = $args->{-alias}) {
50       $args = $alias;
51     }
52     +{ -alias => [
53         map $self->expand_expr($_, -ident),
54         ref($args) eq 'ARRAY' ? @{$args} : $args
55       ]
56     }
57   }));
58   $sqla->renderer(alias => $self->cb('_render_alias'));
59
60   $sqla->clauses_of(update => sub {
61     my ($self, @clauses) = @_;
62     splice(@clauses, 2, 0, 'from');
63     @clauses;
64   });
65
66   $sqla->clauses_of(delete => sub {
67     my ($self, @clauses) = @_;
68     splice(@clauses, 1, 0, 'using');
69     @clauses;
70   });
71
72   $sqla->clause_expanders(
73     'update.from' => $self->cb('_expand_select_clause_from'),
74     'delete.using' => $self->cb(sub {
75       +(using => $_[0]->_expand_from_list(undef, $_[2]));
76     }),
77     'insert.rowvalues' => $self->cb(sub {
78       +(from => $_[0]->expand_expr({ -values => $_[2] }));
79     }),
80     'insert.select' => $self->cb(sub {
81       +(from => $_[0]->expand_expr({ -select => $_[2] }));
82     }),
83   );
84
85   # set ops
86   $sqla->wrap_expander(select => sub {
87     my $orig = shift;
88     $self->cb(sub {
89       my $self = shift;
90       my $exp = $self->sqla->$orig(@_);
91       return $exp unless my $setop = (my $sel = $exp->{-select})->{setop};
92       if (my @keys = grep $sel->{$_}, @before_setop) {
93         my %inner; @inner{@keys} = delete @{$sel}{@keys};
94         unshift @{(values(%$setop))[0]{queries}},
95           { -select => \%inner };
96       }
97       return $exp;
98     });
99   });
100   my $expand_setop = $self->cb(sub {
101     my ($self, $setop, $args) = @_;
102     +{ "-${setop}" => {
103          %$args,
104          queries => [ map $self->expand_expr($_), @{$args->{queries}} ],
105     } };
106   });
107   $sqla->expanders(map +($_ => $expand_setop), qw(union intersect except));
108
109   $sqla->clause_renderer('select.setop' => $self->cb(sub {
110     my ($self, undef, $setop) = @_;
111     $self->render_aqt($setop);
112   }));
113
114   $sqla->renderer($_ => $self->cb(sub {
115     my ($self, $setop, $args) = @_;
116     $self->join_query_parts(
117       ' '.$self->format_keyword(join '_', $setop, ($args->{type}||())).' ',
118       @{$args->{queries}}
119     );
120   })) for qw(union intersect except);
121
122   my $setop_expander = $self->cb(sub {
123     my ($self, $setop, $args) = @_;
124     my ($op, $type) = split '_', $setop;
125     +(setop => $self->expand_expr({
126       "-${op}" => {
127         ($type ? (type => $type) : ()),
128         queries => (ref($args) eq 'ARRAY' ? $args : [ $args ])
129       }
130     }));
131   });
132
133   $sqla->clause_expanders(
134     map +($_ => $setop_expander),
135       map "select.${_}",
136         map +($_, "${_}_all", "${_}_distinct"),
137           qw(union intersect except)
138   );
139
140   $sqla->clause_expander('select.with' => my $with_expander = $self->cb(sub {
141     my ($self, $name, $with) = @_;
142     my (undef, $type) = split '_', $name;
143     if (ref($with) eq 'HASH') {
144       return +{
145         %$with,
146         queries => [
147           map +[
148             $self->expand_expr({ -alias => $_->[0] }, -ident),
149             $self->expand_expr($_->[1]),
150           ], @{$with->{queries}}
151         ]
152       }
153     }
154     my @with = @$with;
155     my @exp;
156     while (my ($alias, $query) = splice @with, 0, 2) {
157       push @exp, [
158         $self->expand_expr({ -alias => $alias }, -ident),
159         $self->expand_expr($query)
160       ];
161     }
162     return +(with => { ($type ? (type => $type) : ()), queries => \@exp });
163   }));
164   $sqla->clause_expander('select.with_recursive', $with_expander);
165   $sqla->clause_renderer('select.with' => my $with_renderer = $self->cb(sub {
166     my ($self, undef, $with) = @_;
167     my $q_part = $self->join_query_parts(', ',
168       map {
169         my ($alias, $query) = @$_;
170         $self->join_query_parts(' ',
171             $alias,
172             $self->format_keyword('as'),
173             $query,
174         )
175       } @{$with->{queries}}
176     );
177     return $self->join_query_parts(' ',
178       $self->format_keyword(join '_', 'with', ($with->{type}||'')),
179       $q_part,
180     );
181   }));
182   foreach my $stmt (qw(insert update delete)) {
183     $sqla->clauses_of($stmt => 'with', $sqla->clauses_of($stmt));
184     $sqla->clause_expander("${stmt}.$_", $with_expander)
185       for qw(with with_recursive);
186     $sqla->clause_renderer("${stmt}.with", $with_renderer);
187   }
188   $sqla->expander(cast => $self->cb(sub {
189     return { -func => [ cast => $_[2] ] } if ref($_[2]) eq 'HASH';
190     my ($cast, $to) = @{$_[2]};
191     +{ -func => [ cast => { -as => [
192       $self->expand_expr($cast),
193       $self->expand_expr($to, -ident),
194     ] } ] };
195   }));
196
197   $sqla->clause_expanders(
198     "select.from", $self->cb('_expand_select_clause_from'),
199     "update.target", $self->cb('_expand_update_clause_target'),
200     "update.update", $self->cb('_expand_update_clause_target'),
201   );
202
203   return $sqla;
204 }
205
206 sub _expand_select_clause_from {
207   my ($self, undef, $from) = @_;
208   +(from => $self->_expand_from_list(undef, $from));
209 }
210
211 sub _expand_from_list {
212   my ($self, undef, $args) = @_;
213   if (ref($args) eq 'HASH') {
214     return $args if $args->{-from_list};
215     return { -from_list => [ $self->expand_expr($args) ] };
216   }
217   my @list;
218   my @args = ref($args) eq 'ARRAY' ? @$args : ($args);
219   while (my $entry = shift @args) {
220     if (!ref($entry) and $entry =~ /^-(.*)/) {
221       if ($1 eq 'as') {
222         $list[-1] = $self->expand_expr({ -as => [
223           $list[-1], map +(ref($_) eq 'ARRAY' ? @$_ : $_), shift(@args)
224         ]});
225         next;
226       }
227       $entry = { $entry => shift @args };
228     }
229     my $aqt = $self->expand_expr($entry, -ident);
230     if ($aqt->{-join} and not $aqt->{-join}{from}) {
231       $aqt->{-join}{from} = pop @list;
232     }
233     push @list, $aqt;
234   }
235   return { -from_list => \@list };
236 }
237
238 sub _expand_join {
239   my ($self, undef, $args) = @_;
240   my %proto = (
241     ref($args) eq 'HASH'
242       ? %$args
243       : (to => $args->[0], @{$args}[1..$#$args])
244   );
245   if (my $as = delete $proto{as}) {
246     $proto{to} = $self->expand_expr({ -as => [ $proto{to}, $as ] });
247   }
248   if (defined($proto{using}) and ref(my $using = $proto{using}) ne 'HASH') {
249     $proto{using} = [
250       map [ $self->expand_expr($_, -ident) ],
251         ref($using) eq 'ARRAY' ? @$using: $using
252     ];
253   }
254   my %ret = map +($_ => $self->expand_expr($proto{$_}, -ident)),
255               sort keys %proto;
256   return +{ -join => \%ret };
257 }
258
259 sub _render_from_list {
260   my ($self, undef, $list) = @_;
261   return $self->join_query_parts(', ', @$list);
262 }
263
264 sub _render_join {
265   my ($self, undef, $args) = @_;
266
267   my @parts = (
268     $args->{from},
269     $self->format_keyword(join '_', ($args->{type}||()), 'join'),
270     (map +($_->{-ident} || $_->{-as} ? $_ : ('(', $_, ')')), $args->{to}),
271     ($args->{on} ? (
272       $self->format_keyword('on') ,
273       $args->{on},
274     ) : ()),
275     ($args->{using} ? (
276       $self->format_keyword('using'),
277       '(', $args->{using}, ')',
278     ) : ()),
279   );
280   return $self->join_query_parts(' ', @parts);
281 }
282
283 sub _expand_op_as {
284   my ($self, undef, $vv, $k) = @_;
285   my @vv = (ref($vv) eq 'ARRAY' ? @$vv : $vv);
286   $k ||= shift @vv;
287   my $ik = $self->expand_expr($k, -ident);
288   return +{ -as => [ $ik, $self->expand_expr($vv[0], -alias) ] }
289     if @vv == 1 and ref($vv[0]) eq 'HASH';
290
291   my @as = map $self->expand_expr($_, -ident), @vv;
292   return { -as => [ $ik, { -alias => \@as } ] };
293 }
294
295 sub _render_as {
296   my ($self, undef, $args) = @_;
297   my ($thing, $alias) = @$args;
298   return $self->join_query_parts(
299     ' ',
300     $thing,
301     $self->format_keyword('as'),
302     $alias,
303   );
304 }
305
306 sub _render_alias {
307   my ($self, undef, $args) = @_;
308   my ($as, @cols) = @$args;
309   return (@cols
310     ? $self->join_query_parts('',
311          $as,
312          '(',
313          $self->join_query_parts(
314            ', ',
315            @cols
316          ),
317          ')',
318       )
319     : $self->render_aqt($as)
320   );
321 }
322
323 sub _expand_update_clause_target {
324   my ($self, undef, $target) = @_;
325   +(target => $self->_expand_from_list(undef, $target));
326 }
327
328 1;