clean up join call
[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   $self->wrap_expander(select => sub {
66     my $orig = shift;
67     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_query_parts(
96       ' '.$self->format_keyword(join '_', $setop, ($args->{type}||())).' ',
97       @{$args->{queries}}
98     );
99   }) for qw(union intersect except);
100
101   my $setop_expander = sub {
102     my ($self, $setop, $args) = @_;
103     my ($op, $type) = split '_', $setop;
104     +(setop => $self->expand_expr({
105       "-${op}" => {
106         ($type ? (type => $type) : ()),
107         queries => (ref($args) eq 'ARRAY' ? $args : [ $args ])
108       }
109     }));
110   };
111
112   $self->clause_expanders(
113     map +($_ => $setop_expander),
114       map "select.${_}",
115         map +($_, "${_}_all", "${_}_distinct"),
116           qw(union intersect except)
117   );
118
119   $self->clause_expander('select.with' => my $with_expander = sub {
120     my ($self, $name, $with) = @_;
121     my (undef, $type) = split '_', $name;
122     if (ref($with) eq 'HASH') {
123       return +{
124         %$with,
125         queries => [ map $self->expand_expr($_), @{$with->{queries}} ]
126       }
127     }
128     my @with = @$with;
129     my @exp;
130     while (my ($name, $query) = splice @with, 0, 2) {
131       my @n = map $self->expand_expr($_, -ident),
132                 ref($name) eq 'ARRAY' ? @$name : $name;
133       push @exp, [
134         \@n,
135         $self->expand_expr($query)
136       ];
137     }
138     return +(with => { ($type ? (type => $type) : ()), queries => \@exp });
139   });
140   $self->clause_expander('select.with_recursive', $with_expander);
141   $self->clause_renderer('select.with' => sub {
142     my ($self, undef, $with) = @_;
143     my $q_part = [ $self->join_query_parts(', ',
144       map {
145         my ($alias, $query) = @$_;
146         [ $self->join_query_parts(' ',
147             [ $self->_render_alias($alias) ],
148             [ $self->format_keyword('as') ],
149             $query,
150         ) ]
151       } @{$with->{queries}}
152     ) ];
153     return $self->join_query_parts(' ',
154       [ $self->format_keyword(join '_', 'with', ($with->{type}||'')) ],
155       $q_part,
156     );
157   });
158
159   return $self;
160 }
161
162 sub _expand_select_clause_from {
163   my ($self, undef, $from) = @_;
164   +(from => $self->_expand_from_list(undef, $from));
165 }
166
167 sub _expand_from_list {
168   my ($self, undef, $args) = @_;
169   if (ref($args) eq 'HASH') {
170     return { -from_list => [ $self->expand_expr($args) ] };
171   }
172   my @list;
173   my @args = ref($args) eq 'ARRAY' ? @$args : ($args);
174   while (my $entry = shift @args) {
175     if (!ref($entry) and $entry =~ /^-(.*)/) {
176       if ($1 eq 'as') {
177         $list[-1] = $self->expand_expr({ -as => [
178           $list[-1], map +(ref($_) eq 'ARRAY' ? @$_ : $_), shift(@args)
179         ]});
180         next;
181       }
182       $entry = { $entry => shift @args };
183     }
184     my $aqt = $self->expand_expr($entry, -ident);
185     if ($aqt->{-join} and not $aqt->{-join}{from}) {
186       $aqt->{-join}{from} = pop @list;
187     }
188     push @list, $aqt;
189   }
190   return { -from_list => \@list };
191 }
192
193 sub _expand_join {
194   my ($self, undef, $args) = @_;
195   my %proto = (
196     ref($args) eq 'HASH'
197       ? %$args
198       : (to => $args->[0], @{$args}[1..$#$args])
199   );
200   if (my $as = delete $proto{as}) {
201     $proto{to} = { -as => [ $proto{to}, ref($as) eq 'ARRAY' ? @$as : $as ] };
202   }
203   if (defined($proto{using}) and ref(my $using = $proto{using}) ne 'HASH') {
204     $proto{using} = { -row => [
205       map [ $self->expand_expr($_, -ident) ],
206         ref($using) eq 'ARRAY' ? @$using: $using
207     ] };
208   }
209   my %ret = map +($_ => $self->expand_expr($proto{$_}, -ident)),
210               sort keys %proto;
211   return +{ -join => \%ret };
212 }
213
214 sub _render_from_list {
215   my ($self, undef, $list) = @_;
216   return $self->join_query_parts(', ', @$list);
217 }
218
219 sub _render_join {
220   my ($self, undef, $args) = @_;
221
222   my @parts = (
223     $args->{from},
224     $self->format_keyword(join '_', ($args->{type}||()), 'join'),
225     (map +($_->{-ident} || $_->{-as} ? $_ : { -row => [ $_ ] }), $args->{to}),
226     ($args->{on} ? (
227       $self->format_keyword('on') ,
228       $args->{on},
229     ) : ()),
230     ($args->{using} ? (
231       $self->format_keyword('using'),
232       $args->{using},
233     ) : ()),
234   );
235   return $self->join_query_parts(' ', @parts);
236 }
237
238 sub _expand_op_as {
239   my ($self, undef, $vv, $k) = @_;
240   my @as = map $self->expand_expr($_, -ident),
241              (defined($k) ? ($k) : ()), ref($vv) eq 'ARRAY' ? @$vv : $vv;
242   return { -as => \@as };
243 }
244
245 sub _render_as {
246   my ($self, undef, $args) = @_;
247   my ($thing, @alias) = @$args;
248   return $self->join_query_parts(
249     ' ',
250     [ $self->render_aqt($thing) ],
251     [ $self->format_keyword('as') ],
252     [ $self->_render_alias(\@alias) ],
253   );
254 }
255
256 sub _render_alias {
257   my ($self, $args) = @_;
258   my ($as, @cols) = @$args;
259   return (@cols
260     ? $self->join_query_parts('',
261          $as,
262          [ '(' ],
263          [ $self->join_query_parts(
264              ', ',
265              @cols
266          ) ],
267          [ ')' ],
268       )
269     : $self->render_aqt($as)
270   );
271 }
272
273 sub _expand_update_clause_target {
274   my ($self, undef, $target) = @_;
275   +(target => $self->_expand_from_list(undef, $target));
276 }
277
278 1;