delete from, update using, join using
[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 new {
12   my ($proto, @args) = @_;
13   my $new = $proto->next::method(@args);
14   $new->{clauses_of}{select} = [
15     @{$new->{clauses_of}{select}}, qw(group_by having)
16   ];
17   $new->{expand_clause}{'select.group_by'} = sub {
18     $_[0]->_expand_maybe_list_expr($_[1], -ident)
19   };
20   $new->{expand_clause}{'select.having'} = sub {
21     $_[0]->expand_expr($_[1])
22   };
23   $new->{expand}{from_list} = '_expand_from_list';
24   $new->{render}{from_list} = '_render_from_list';
25   $new->{expand}{join} = '_expand_join';
26   $new->{render}{join} = '_render_join';
27   $new->{expand_op}{as} = '_expand_op_as';
28   $new->{expand}{as} = '_expand_op_as';
29   $new->{render}{as} = '_render_as';
30   splice(@{$new->{clauses_of}{update}}, 2, 0, 'from');
31   splice(@{$new->{clauses_of}{delete}}, 1, 0, 'using');
32   $new->{expand_clause}{'update.from'} = '_expand_select_clause_from';
33   $new->{expand_clause}{'delete.using'} = sub {
34     +(using => $_[0]->_expand_from_list(undef, $_[1]));
35   };
36   return $new;
37 }
38
39 sub _expand_select_clause_from {
40   my ($self, $from) = @_;
41   +(from => $self->_expand_from_list(undef, $from));
42 }
43
44 sub _expand_from_list {
45   my ($self, undef, $args) = @_;
46   if (ref($args) eq 'HASH') {
47     return { -from_list => [ $self->expand_expr($args) ] };
48   }
49   my @list;
50   my @args = ref($args) ? @$args : ($args);
51   while (my $entry = shift @args) {
52     if (!ref($entry) and $entry =~ /^-(.*)/) {
53       if ($1 eq 'as') {
54         $list[-1] = $self->expand_expr({ -as => [
55           $list[-1], map +(ref($_) eq 'ARRAY' ? @$_ : $_), shift(@args)
56         ]});
57         next;
58       }
59       $entry = { $entry => shift @args };
60     }
61     my $aqt = $self->expand_expr($entry, -ident);
62     if ($aqt->{-join} and not $aqt->{-join}{from}) {
63       $aqt->{-join}{from} = pop @list;
64     }
65     push @list, $aqt;
66   }
67   return { -from_list => \@list };
68 }
69
70 sub _expand_join {
71   my ($self, undef, $args) = @_;
72   my %proto = (
73     ref($args) eq 'HASH'
74       ? %$args
75       : (to => $args->[0], @{$args}[1..$#$args])
76   );
77   if (my $as = delete $proto{as}) {
78     $proto{to} = { -as => [ $proto{to}, ref($as) eq 'ARRAY' ? @$as : $as ] };
79   }
80   if (defined($proto{using}) and ref(my $using = $proto{using}) ne 'HASH') {
81     $proto{using} = { -row => [
82       map [ $self->expand_expr($_, -ident) ],
83         ref($using) eq 'ARRAY' ? @$using: $using
84     ] };
85   }
86   my %ret = map +($_ => $self->expand_expr($proto{$_}, -ident)),
87               sort keys %proto;
88   return +{ -join => \%ret };
89 }
90
91 sub _render_from_list {
92   my ($self, $list) = @_;
93   return $self->_join_parts(', ', map [ $self->render_aqt($_) ], @$list);
94 }
95
96 sub _render_join {
97   my ($self, $args) = @_;
98
99   my @parts = (
100     [ $self->render_aqt($args->{from}) ],
101     [ $self->_sqlcase(
102         ($args->{type}
103           ? join(' ', split '_', $args->{type}).' '
104           : ''
105         )
106         .'join'
107       )
108     ],
109     [ $self->render_aqt(
110         map +($_->{-ident} || $_->{-as} ? $_ : { -row => [ $_ ] }), $args->{to}
111     ) ],
112     ($args->{on} ? (
113       [ $self->_sqlcase('on') ],
114       [ $self->render_aqt($args->{on}) ],
115     ) : ()),
116     ($args->{using} ? (
117       [ $self->_sqlcase('using') ],
118       [ $self->render_aqt($args->{using}) ],
119     ) : ()),
120   );
121   return $self->_join_parts(' ', @parts);
122 }
123
124 sub _expand_op_as {
125   my ($self, undef, $vv, $k) = @_;
126   my @as = map $self->expand_expr($_, -ident),
127              (defined($k) ? ($k) : ()), ref($vv) eq 'ARRAY' ? @$vv : $vv;
128   return { -as => \@as };
129 }
130
131 sub _render_as {
132   my ($self, $args) = @_;
133   my ($thing, $as, @cols) = @$args;
134   return $self->_join_parts(
135     ' ',
136     [ $self->render_aqt(
137         map +($_->{-ident} ? $_ : { -row => [ $_ ] }), $thing
138     ) ],
139     [ $self->_sqlcase('as') ],
140     (@cols
141       ? [ $self->_join_parts('',
142             [ $self->render_aqt($as) ],
143             [ '(' ],
144             [ $self->_join_parts(
145                 ', ',
146                 map [ $self->render_aqt($_) ], @cols
147             ) ],
148             [ ')' ],
149         ) ]
150       : [ $self->render_aqt($as) ]
151     ),
152   );
153 }
154
155 1;