fixup! first cut of rno sketch
[dbsrgits/Data-Query.git] / lib / Data / Query / Renderer / SQL / Slice / RowNumberOver.pm
1 package Data::Query::Renderer::SQL::Slice::RowNumberOver;
2
3 use Data::Query::Constants qw(
4   DQ_SELECT DQ_ALIAS DQ_IDENTIFIER DQ_ORDER DQ_SLICE DQ_WHERE DQ_OPERATOR
5   DQ_LITERAL
6 );
7 use Moo::Role;
8
9 sub _render_slice {
10   my ($self, $dq) = @_;
11   die "Slice's inner is not a Select"
12     unless (my $orig_select = $dq->{from})->{type} eq DQ_SELECT;
13   my %alias_map;
14   my $gensym_count;
15   my (@inside_select_list, @outside_select_list);
16   my $default_inside_alias;
17   SELECT: foreach my $s (@{$orig_select->{select}}) {
18     my $name;
19     if ($s->{type} eq DQ_ALIAS) {
20       $name = $s->{to};
21       $s = $s->{from};
22     }
23     my $key;
24     if ($s->{type} eq DQ_IDENTIFIER) {
25       if (!$name and @{$s->{elements}} == 2) {
26         $default_inside_alias ||= $s->{elements}[0];
27         if ($s->{elements}[0] eq $default_inside_alias) {
28           $alias_map{join('.',@{$s->{elements}})} = $s;
29           push @inside_select_list, $s;
30           push @outside_select_list, $s;
31           next SELECT;
32         }
33       }
34       $name ||= join('__', @{$s->{elements}});
35       $key = join('.', @{$s->{elements}});
36     } else {
37       die "XXX not implemented yet" unless $name;
38       $key = "$s";
39     }
40     $name ||= sprintf("GENSYM__%03i",++$gensym_count);
41     push @inside_select_list, +{
42       type => DQ_ALIAS,
43       from => $s,
44       to => $name,
45     };
46     push @outside_select_list, $alias_map{$key} = +{
47       type => DQ_IDENTIFIER,
48       elements => [ $name ]
49     };
50   }
51   my $order = $orig_select->{from};
52   my $order_gensym_count;
53   my (@order_nodes, %order_map);
54   while ($order->{type} eq DQ_ORDER) {
55     my $by = $order->{by};
56     if ($by->{type} eq DQ_IDENTIFIER) {
57       $default_inside_alias ||= $by->{elements}[0]
58         if @{$by->{elements}} == 2;
59       $order_map{$by}
60         = $alias_map{join('.', @{$by->{elements}})}
61           ||= do {
62                 if (
63                   @{$by->{elements}} == 2
64                   and $by->{elements}[0] eq $default_inside_alias
65                 ) {
66                   $by;
67                 } else {
68                   my $name = sprintf("ORDER__BY__%03i",++$order_gensym_count);
69                   push @inside_select_list, +{
70                     type => DQ_ALIAS,
71                     from => $by,
72                     to => $name
73                   };
74                   +{
75                     type => DQ_IDENTIFIER,
76                     elements => [ $name ],
77                   };
78                 }
79               };
80     } else {
81       die "XXX not implemented yet";
82     }
83     push @order_nodes, $order;
84     $order = $order->{from};
85   }
86   my $inside_order = $order;
87   $inside_order = +{
88     type => DQ_ORDER,
89     by => $_->{by},
90     reverse => $_->{reverse},
91     from => $inside_order
92   } for reverse @order_nodes;
93   my $inside_select = +{
94     type => DQ_SELECT,
95     select => \@inside_select_list,
96     from => $inside_order,
97   };
98   $default_inside_alias ||= 'me';
99   my $bridge_from = +{
100     type => DQ_ALIAS,
101     from => $inside_select,
102     to => $default_inside_alias,
103   };
104   my $outside_order;
105   $outside_order = +{
106     type => DQ_ORDER,
107     by => $order_map{$_->{by}},
108     reverse => !$_->{reverse},
109     from => $outside_order
110   } for reverse @order_nodes;
111   my $outside_select = +{
112     type => DQ_SELECT,
113     select => [
114       @outside_select_list,
115       {
116         type => DQ_ALIAS,
117         from => $self->_rno_literal($outside_order),
118         to => 'rno__row__index',
119       }
120     ],
121     from => $bridge_from,
122   };
123   my $idx_name = +{
124     type => DQ_IDENTIFIER,
125     elements => [ 'rno__row__index' ],
126   };
127   my $offset_value = $dq->{offset} ? $dq->{offset}{value} : 0;
128   my $final = +{
129     type => DQ_WHERE,
130     where => {
131       type => DQ_OPERATOR,
132       operator => { 'SQL.Naive' => 'AND' },
133       args => [
134         {
135           type => DQ_OPERATOR,
136           operator => { 'SQL.Naive' => '>=' },
137           args => [
138             $idx_name,
139             { %{$dq->{limit}}, value => $offset_value + 1 },
140           ]
141         },
142         {
143           type => DQ_OPERATOR,
144           operator => { 'SQL.Naive' => '<=' },
145           args => [
146             $idx_name,
147             { %{$dq->{limit}}, value => $dq->{limit}{value} + $offset_value },
148           ]
149         },
150       ],
151     },
152     from => {
153       type => DQ_SELECT,
154       select => \@outside_select_list,
155       from => {
156         type => DQ_ALIAS,
157         from => $outside_select,
158         to => $default_inside_alias,
159       },
160     }
161   };
162   return $self->_render($final);
163 }
164
165 sub _rno_literal {
166   my ($self, $order) = @_;
167   my ($order_str, @order_bind) = (
168     $order
169       ? @{$self->render($order)}
170       : ('')
171   );
172   return +{
173     type => DQ_LITERAL,
174     subtype => 'SQL',
175     literal => "ROW_NUMBER() OVER( $order_str )",
176     values => \@order_bind
177   };
178 }
179
180 1;