switch keyword formatting to method, fix clause order, add setop infra
Matt S Trout [Mon, 15 Apr 2019 16:34:07 +0000 (16:34 +0000)]
lib/SQL/Abstract/Clauses.pm
lib/SQL/Abstract/ExtraClauses.pm
xt/clauses.t

index 972963c..fd98e73 100644 (file)
@@ -274,14 +274,14 @@ sub _render_values {
 
 sub _ext_rw {
   my ($self, $name, $key, $value) = @_;
-  return $self->{$name}{$key} unless @_ > 2;
+  return $self->{$name}{$key} unless @_ > 3;
   $self->{$name}{$key} = $value;
   return $self;
 }
 
 BEGIN {
   foreach my $type (qw(
-    expand op_expand render op_renderer clause_expand clause_render
+    expand op_expand render op_render clause_expand clause_render
   )) {
     my $key = join '_', reverse split '_', $type;
     my $singular = "${type}er";
index 24a9dbf..ae1ded5 100644 (file)
@@ -11,9 +11,17 @@ BEGIN { *puke = \&SQL::Abstract::puke }
 sub register_defaults {
   my $self = shift;
   $self->next::method(@_);
-  $self->clauses_of(
-    select => $self->clauses_of('select'), qw(group_by having)
-  );
+  my @clauses = $self->clauses_of('select');
+  my @before_setop;
+  CLAUSE: foreach my $idx (0..$#clauses) {
+    if ($clauses[$idx] eq 'order_by') {
+      @before_setop = @clauses[0..$idx-1];
+      splice(@clauses, $idx, 0, qw(setop group_by having));
+      last CLAUSE;
+    }
+  }
+  die "Huh?" unless @before_setop;
+  $self->clauses_of(select => @clauses);
   $self->clause_expanders(
     'select.group_by', sub {
       $_[0]->_expand_maybe_list_expr($_[1], -ident)
@@ -53,12 +61,63 @@ sub register_defaults {
     },
   );
 
-  $self->renderer(keyword => sub {
-    $_[0]->_sqlcase(join ' ', split '_', $_[1]);
+  # set ops
+  {
+    my $orig = $self->expander('select');
+    $self->expander(select => sub {
+      my $self = shift;
+      my $exp = $self->$orig(@_);
+      return $exp unless my $setop = (my $sel = $exp->{-select})->{setop};
+      if (my @keys = grep $sel->{$_}, @before_setop) {
+        my %inner; @inner{@keys} = delete @{$sel}{@keys};
+        unshift @{(values(%$setop))[0]{queries}},
+          { -select => \%inner };
+      }
+      return $exp;
+    });
+  }
+
+  $self->clause_expander('select.union' => sub {
+    +(setop => $_[0]->expand_expr({
+                 -union => {
+                   queries => (ref($_[1]) eq 'ARRAY' ? $_[1] : [ $_[1] ]),
+                 }
+               }));
+  });
+  $self->clause_expander('select.union_all' => sub {
+    +(setop => $_[0]->expand_expr({
+                 -union => {
+                   type => 'all',
+                   queries => (ref($_[1]) eq 'ARRAY' ? $_[1] : [ $_[1] ]),
+                 }
+               }));
+  });
+  $self->expander(union => sub {
+    my ($self, undef, $args) = @_;
+    +{ -union => {
+         %$args,
+         queries => [ map $self->expand_expr($_), @{$args->{queries}} ],
+    } };
+  });
+
+  $self->clause_renderer('select.setop' => sub {
+    my ($self, $setop) = @_;
+    $self->render_aqt($setop);
+  });
+
+  $self->renderer(union => sub {
+    my ($self, $args) = @_;
+    $self->join_clauses(
+      ' '.$self->format_keyword(join '_', 'union', ($args->{type}||())).' ',
+      map [ $self->render_aqt($_) ], @{$args->{queries}}
+    );
   });
+
   return $self;
 }
 
+sub format_keyword { $_[0]->_sqlcase(join ' ', split '_', $_[1]) }
+
 sub _expand_select_clause_from {
   my ($self, $from) = @_;
   +(from => $self->_expand_from_list(undef, $from));
@@ -121,18 +180,16 @@ sub _render_join {
 
   my @parts = (
     [ $self->render_aqt($args->{from}) ],
-    [ $self->render_aqt(
-        { -keyword => join '_', ($args->{type}||()), 'join' }
-    ) ],
+    [ $self->format_keyword(join '_', ($args->{type}||()), 'join') ],
     [ $self->render_aqt(
         map +($_->{-ident} || $_->{-as} ? $_ : { -row => [ $_ ] }), $args->{to}
     ) ],
     ($args->{on} ? (
-      [ $self->render_aqt({ -keyword => 'on' }) ],
+      [ $self->format_keyword('on') ],
       [ $self->render_aqt($args->{on}) ],
     ) : ()),
     ($args->{using} ? (
-      [ $self->render_aqt({ -keyword => 'using' }) ],
+      [ $self->format_keyword('using') ],
       [ $self->render_aqt($args->{using}) ],
     ) : ()),
   );
@@ -152,7 +209,7 @@ sub _render_as {
   return $self->join_clauses(
     ' ',
     [ $self->render_aqt($thing) ],
-    [ $self->render_aqt({ -keyword => 'as' }) ],
+    [ $self->format_keyword('as') ],
     (@cols
       ? [ $self->join_clauses('',
             [ $self->render_aqt($as) ],
index 7fed67f..ce75f17 100644 (file)
@@ -24,9 +24,9 @@ is_same_sql_bind(
     SELECT artist.id, artist.name, JSON_AGG(cd)
     FROM artists AS artist JOIN cds AS cd ON cd.artist_id = artist.id
     WHERE artist.genres @> ?
-    ORDER BY artist.name
     GROUP BY artist.id
     HAVING COUNT(cd.id) > ?
+    ORDER BY artist.name
   },
   [ [ 'Rock' ], 3 ]
 );
@@ -164,4 +164,15 @@ is_same_sql(
   );
 }
 
+($sql) = $sqlac->select({
+  select => { -as => [ 1, 'x' ] },
+  union => { -select => { select => { -as => [ 2, 'x' ] } } },
+  order_by => { -desc => 'x' },
+});
+
+is_same_sql(
+  $sql,
+  q{(SELECT 1 AS x) UNION (SELECT 2 AS x) ORDER BY x DESC},
+);
+
 done_testing;