working RowNumberOver
Matt S Trout [Thu, 16 Aug 2012 16:33:08 +0000 (17:33 +0100)]
lib/Data/Query/Renderer/SQL/Naive.pm
lib/Data/Query/Renderer/SQL/Slice/FetchFirst.pm
lib/Data/Query/Renderer/SQL/Slice/RowNumberOver.pm
lib/Data/Query/Renderer/SQL/Slice/SubqueryRemap.pm [new file with mode: 0644]

index 6e16fb8..7b3a941 100644 (file)
@@ -79,6 +79,7 @@ sub _flatten_structure {
 sub _format_keyword { $_[0]->lc_keywords ? lc($_[1]) : $_[1] }
 
 sub _render {
+  die "Expected hashref, got $_[1]" unless ref($_[1]) eq 'HASH';
   $_[0]->${\"_render_${\(lc($_[1]->{type})||'broken')}"}($_[1]);
 }
 
index d885fb4..1af7f78 100644 (file)
@@ -3,6 +3,8 @@ package Data::Query::Renderer::SQL::Slice::FetchFirst;
 use Data::Query::ExprHelpers;
 use Moo::Role;
 
+with 'Data::Query::Renderer::SQL::Slice::SubqueryRemap';
+
 sub _render_slice_limit {
   my ($self, $dq) = @_;
   return [
@@ -28,79 +30,14 @@ sub _render_slice {
   die "Slice's Select not followed by Order but order_is_stable set"
     unless is_Order $orig_select->{from};
 
-  my $gensym_count;
-  my $default_inside_alias;
-
-  my @inside_select_list = map {
-    if (is_Alias) {
-      $_;
-    } elsif (is_Identifier) {
-      my @el = @{$_->{elements}};
-      if (@el == 2 and $el[0] eq ($default_inside_alias ||= $el[0])) {
-        $_;
-      } else {
-        Alias(join('__', @el), $_);
-      }
-    } else {
-      Alias(sprintf("GENSYM__%03i",++$gensym_count), $_);
-    }
-  } @{$orig_select->{select}};
-
-  my %alias_map = map {
-    if (is_Alias and is_Identifier $_->{from}) {
-      +(join('.',@{$_->{from}{elements}}) => Identifier($_->{to}))
-    } elsif (is_Identifier) {
-      +(join('.',@{$_->{elements}}) => $_)
-    } else {
-      +()
-    }
-  } @inside_select_list;
-
-  my @outside_select_list = map {
-    if (is_Alias) {
-      Identifier($_->{to});
-    } else {
-      $_;
-    }
-  } @inside_select_list;
-
-  my @order_nodes;
-  my $inner_body = do {
-    my $order = $orig_select->{from};
-    while (is_Order $order) {
-      push @order_nodes, $order;
-      $order = $order->{from};
-    }
-    $order;
-  };
-
-  my $order_gensym_count;
-  my @mapped_order = map {
-    my $by = $_->{by};
-    if (is_Identifier $by) {
-      $default_inside_alias ||= $by->{elements}[0]
-        if @{$by->{elements}} == 2;
-      my $mapped_by
-        = $alias_map{join('.', @{$by->{elements}})}
-          ||= do {
-                if (
-                  @{$by->{elements}} == 2
-                  and $by->{elements}[0] eq $default_inside_alias
-                ) {
-                  $by;
-                } else {
-                  my $name = sprintf("ORDER__BY__%03i",++$order_gensym_count);
-                  push @inside_select_list, Alias($name, $by);
-                  Identifier($name);
-                }
-              };
-      Order($mapped_by, $_->{reverse});
-    } else {
-      die "XXX not implemented yet";
-    }
-  } @order_nodes;
+  my %remapped = $self->_subquery_remap($orig_select);
 
-  $default_inside_alias ||= 'me';
+  my @inside_select_list = @{$remapped{inside_select_list}};
+  my @outside_select_list = @{$remapped{outside_select_list}};
+  my @inside_order = @{$remapped{inside_order}};
+  my @outside_order = @{$remapped{outside_order}};
+  my $default_inside_alias = $remapped{default_inside_alias};
+  my $inner_body = $remapped{inner_body};
 
   my $limit_plus_offset = +{
     %{$dq->{limit}}, value => $dq->{limit}{value} + $dq->{offset}{value}
@@ -114,7 +51,7 @@ sub _render_slice {
           compose {
             Order($b->{by}, $b->{reverse}, $a)
           } (
-            @mapped_order,
+            @outside_order,
             Alias($default_inside_alias, $_)
           )
         )
@@ -127,13 +64,13 @@ sub _render_slice {
             @outside_select_list,
             $dq->{preserve_order}
               ? (grep @{$_->{elements}} == 1,
-                  map $_->{by}, @mapped_order)
+                  map $_->{by}, @outside_order)
               : (),
           ],
           compose {
             Order($b->{by}, !$b->{reverse}, $a)
           } (
-            @mapped_order,
+            @outside_order,
             Alias(
               $default_inside_alias,
               Slice(
@@ -142,7 +79,7 @@ sub _render_slice {
                   \@inside_select_list,
                   compose {
                     Order($b->{by}, $b->{reverse}, $a)
-                  } @order_nodes, $inner_body
+                  } @inside_order, $inner_body
                 )
               )
             )
index 53c3562..c3f6764 100644 (file)
 package Data::Query::Renderer::SQL::Slice::RowNumberOver;
 
-use Data::Query::Constants qw(
-  DQ_SELECT DQ_ALIAS DQ_IDENTIFIER DQ_ORDER DQ_SLICE DQ_WHERE DQ_OPERATOR
-  DQ_LITERAL
-);
+use Data::Query::Constants;
+use Data::Query::ExprHelpers;
 use Moo::Role;
 
+with 'Data::Query::Renderer::SQL::Slice::SubqueryRemap';
+
 sub _render_slice {
   my ($self, $dq) = @_;
   die "Slice's inner is not a Select"
     unless (my $orig_select = $dq->{from})->{type} eq DQ_SELECT;
-  my %alias_map;
-  my $gensym_count;
-  my (@inside_select_list, @outside_select_list);
-  my $default_inside_alias;
-  SELECT: foreach my $s (@{$orig_select->{select}}) {
-    my $name;
-    if ($s->{type} eq DQ_ALIAS) {
-      $name = $s->{to};
-      $s = $s->{from};
-    }
-    my $key;
-    if ($s->{type} eq DQ_IDENTIFIER) {
-      if (!$name and @{$s->{elements}} == 2) {
-        $default_inside_alias ||= $s->{elements}[0];
-        if ($s->{elements}[0] eq $default_inside_alias) {
-          $alias_map{join('.',@{$s->{elements}})} = $s;
-          push @inside_select_list, $s;
-          push @outside_select_list, $s;
-          next SELECT;
-        }
-      }
-      $name ||= join('__', @{$s->{elements}});
-      $key = join('.', @{$s->{elements}});
-    } else {
-      die "XXX not implemented yet" unless $name;
-      $key = "$s";
-    }
-    $name ||= sprintf("GENSYM__%03i",++$gensym_count);
-    push @inside_select_list, +{
-      type => DQ_ALIAS,
-      from => $s,
-      to => $name,
-    };
-    push @outside_select_list, $alias_map{$key} = +{
-      type => DQ_IDENTIFIER,
-      elements => [ $name ]
-    };
-  }
-  my $order = $orig_select->{from};
-  my $order_gensym_count;
-  my (@order_nodes, %order_map);
-  while ($order->{type} eq DQ_ORDER) {
-    my $by = $order->{by};
-    if ($by->{type} eq DQ_IDENTIFIER) {
-      $default_inside_alias ||= $by->{elements}[0]
-        if @{$by->{elements}} == 2;
-      $order_map{$by}
-        = $alias_map{join('.', @{$by->{elements}})}
-          ||= do {
-                if (
-                  @{$by->{elements}} == 2
-                  and $by->{elements}[0] eq $default_inside_alias
-                ) {
-                  $by;
-                } else {
-                  my $name = sprintf("ORDER__BY__%03i",++$order_gensym_count);
-                  push @inside_select_list, +{
-                    type => DQ_ALIAS,
-                    from => $by,
-                    to => $name
-                  };
-                  +{
-                    type => DQ_IDENTIFIER,
-                    elements => [ $name ],
-                  };
-                }
-              };
-    } else {
-      die "XXX not implemented yet";
-    }
-    push @order_nodes, $order;
-    $order = $order->{from};
-  }
-  my $inside_order = $order;
-  $inside_order = +{
-    type => DQ_ORDER,
-    by => $_->{by},
-    reverse => $_->{reverse},
-    from => $inside_order
-  } for reverse @order_nodes;
-  my $inside_select = +{
-    type => DQ_SELECT,
-    select => \@inside_select_list,
-    from => $inside_order,
-  };
-  $default_inside_alias ||= 'me';
-  my $bridge_from = +{
-    type => DQ_ALIAS,
-    from => $inside_select,
-    to => $default_inside_alias,
-  };
-  my $outside_order;
-  $outside_order = +{
-    type => DQ_ORDER,
-    by => $order_map{$_->{by}},
-    reverse => !$_->{reverse},
-    from => $outside_order
-  } for reverse @order_nodes;
-  my $outside_select = +{
-    type => DQ_SELECT,
-    select => [
-      @outside_select_list,
-      {
-        type => DQ_ALIAS,
-        from => $self->_rno_literal($outside_order),
-        to => 'rno__row__index',
-      }
-    ],
-    from => $bridge_from,
+
+  my %remapped = $self->_subquery_remap($orig_select);
+
+  my @inside_select_list = @{$remapped{inside_select_list}};
+  my @outside_select_list = @{$remapped{outside_select_list}};
+  my @inside_order = @{$remapped{inside_order}};
+  my @outside_order = @{$remapped{outside_order}};
+  my $default_inside_alias = $remapped{default_inside_alias};
+  my $inner_body = $remapped{inner_body};
+
+  my $rno_name = 'rno__row__index';
+
+  my $order = compose { Order($b->{by}, $b->{reverse}, $a) }
+                @outside_order, undef;
+
+  my $rno_node = Alias($rno_name, $self->_rno_literal($order));
+
+  my $limit_plus_offset = +{
+    %{$dq->{limit}}, value => ($dq->{limit}{value}||0) + ($dq->{offset}{value}||0)
   };
-  my $idx_name = +{
-    type => DQ_IDENTIFIER,
-    elements => [ 'rno__row__index' ],
+
+  my $offset_plus = +{
+    %{$dq->{limit}}, value => ($dq->{offset}{value}||0)+1
   };
-  my $offset_value = $dq->{offset} ? $dq->{offset}{value} : 0;
-  my $final = +{
-    type => DQ_WHERE,
-    where => {
-      type => DQ_OPERATOR,
-      operator => { 'SQL.Naive' => 'AND' },
-      args => [
-        {
-          type => DQ_OPERATOR,
-          operator => { 'SQL.Naive' => '>=' },
-          args => [
-            $idx_name,
-            { %{$dq->{limit}}, value => $offset_value + 1 },
-          ]
-        },
-        {
-          type => DQ_OPERATOR,
-          operator => { 'SQL.Naive' => '<=' },
-          args => [
-            $idx_name,
-            { %{$dq->{limit}}, value => $dq->{limit}{value} + $offset_value },
+
+  return $self->_render(
+    Select(
+      \@outside_select_list,
+      Where(
+        Operator(
+          { 'SQL.Naive' => 'AND' },
+          [
+            Operator(
+              { 'SQL.Naive' => '>=' },
+              [ Identifier($rno_name), $offset_plus ],
+            ),
+            Operator(
+              { 'SQL.Naive' => '<=' },
+              [ Identifier($rno_name), $limit_plus_offset ],
+            ),
           ]
-        },
-      ],
-    },
-    from => {
-      type => DQ_SELECT,
-      select => \@outside_select_list,
-      from => {
-        type => DQ_ALIAS,
-        from => $outside_select,
-        to => $default_inside_alias,
-      },
-    }
-  };
-  return $self->_render($final);
+        ),
+        Alias(
+          $default_inside_alias,
+          Select(
+            [ @outside_select_list, $rno_node ],
+            Alias(
+              $default_inside_alias,
+              Select(
+                \@inside_select_list,
+                $inner_body
+              ),
+            ),
+          ),
+        )
+      )
+    )
+  );
 }
 
 sub _rno_literal {
diff --git a/lib/Data/Query/Renderer/SQL/Slice/SubqueryRemap.pm b/lib/Data/Query/Renderer/SQL/Slice/SubqueryRemap.pm
new file mode 100644 (file)
index 0000000..092d726
--- /dev/null
@@ -0,0 +1,94 @@
+package Data::Query::Renderer::SQL::Slice::SubqueryRemap;
+
+use Data::Query::ExprHelpers;
+use Moo::Role;
+
+sub _subquery_remap {
+  my ($self, $orig_select) = @_;
+
+  my $gensym_count;
+  my $default_inside_alias;
+
+  my @inside_select_list = map {
+    if (is_Alias) {
+      $_;
+    } elsif (is_Identifier) {
+      my @el = @{$_->{elements}};
+      if (@el == 2 and $el[0] eq ($default_inside_alias ||= $el[0])) {
+        $_;
+      } else {
+        Alias(join('__', @el), $_);
+      }
+    } else {
+      Alias(sprintf("GENSYM__%03i",++$gensym_count), $_);
+    }
+  } @{$orig_select->{select}};
+
+  my %alias_map = map {
+    if (is_Alias and is_Identifier $_->{from}) {
+      +(join('.',@{$_->{from}{elements}}) => Identifier($_->{to}))
+    } elsif (is_Identifier) {
+      +(join('.',@{$_->{elements}}) => $_)
+    } else {
+      +()
+    }
+  } @inside_select_list;
+
+  my @outside_select_list = map {
+    if (is_Alias) {
+      Identifier($_->{to});
+    } else {
+      $_;
+    }
+  } @inside_select_list;
+
+  my @inside_order;
+  my $inner_body = do {
+    my $order = $orig_select->{from};
+    while (is_Order $order) {
+      push @inside_order, $order;
+      $order = $order->{from};
+    }
+    $order;
+  };
+
+  my $order_gensym_count;
+  my @outside_order = map {
+    my $by = $_->{by};
+    if (is_Identifier $by) {
+      $default_inside_alias ||= $by->{elements}[0]
+        if @{$by->{elements}} == 2;
+      my $mapped_by
+        = $alias_map{join('.', @{$by->{elements}})}
+          ||= do {
+                if (
+                  @{$by->{elements}} == 2
+                  and $by->{elements}[0] eq $default_inside_alias
+                ) {
+                  push @inside_select_list, $by;
+                  $by;
+                } else {
+                  my $name = sprintf("ORDER__BY__%03i",++$order_gensym_count);
+                  push @inside_select_list, Alias($name, $by);
+                  Identifier($name);
+                }
+              };
+      Order($mapped_by, $_->{reverse});
+    } else {
+      die "XXX not implemented yet";
+    }
+  } @inside_order;
+
+  $default_inside_alias ||= 'me';
+
+  return (
+    inside_select_list => \@inside_select_list,
+    outside_select_list => \@outside_select_list,
+    inside_order => \@inside_order,
+    outside_order => \@outside_order,
+    default_inside_alias => $default_inside_alias,
+    inner_body => $inner_body,
+  );
+}
+
+1;