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