35792157d43ed908296e07756b650722dddb0b28
[dbsrgits/Data-Query.git] / lib / Data / Query / Renderer / SQL / Slice / FetchFirst.pm
1 package Data::Query::Renderer::SQL::Slice::FetchFirst;
2
3 use Data::Query::ExprHelpers;
4 use Moo::Role;
5
6 sub _render_slice_limit {
7   my ($self, $dq) = @_;
8   return [
9     ($dq->{from} ? $self->_render($dq->{from}) : ()),
10     $self->_format_keyword('FETCH FIRST'),
11     sprintf("%i", $dq->{limit}{value}),
12     $self->_format_keyword('ROWS ONLY')
13   ];
14 }
15
16 sub _slice_type { 'FetchFirst' }
17
18 sub _render_slice {
19   my ($self, $dq) = @_;
20   unless ($dq->{offset}) {
21     return $self->_render_slice_limit($dq);
22   }
23   unless ($dq->{order_is_stable}) {
24     die $self->_slice_type." limit style requires a stable order";
25   }
26   die "Slice's inner is not a Select"
27     unless is_Select my $orig_select = $dq->{from};
28   my %alias_map;
29   my $gensym_count;
30   my (@inside_select_list, @outside_select_list);
31   my $default_inside_alias;
32   SELECT: foreach my $s (@{$orig_select->{select}}) {
33     my $name;
34     if (is_Alias $s) {
35       $name = $s->{to};
36       $s = $s->{from};
37     }
38     my $key;
39     if (is_Identifier $s) {
40       if (!$name and @{$s->{elements}} == 2) {
41         $default_inside_alias ||= $s->{elements}[0];
42         if ($s->{elements}[0] eq $default_inside_alias) {
43           $alias_map{join('.',@{$s->{elements}})} = $s;
44           push @inside_select_list, $s;
45           push @outside_select_list, $s;
46           next SELECT;
47         }
48       }
49       $name ||= join('__', @{$s->{elements}});
50       $key = join('.', @{$s->{elements}});
51     } else {
52       die "XXX not implemented yet" unless $name;
53       $key = "$s";
54     }
55     $name ||= sprintf("GENSYM__%03i",++$gensym_count);
56     push @inside_select_list, Alias($name, $s);
57     push @outside_select_list, $alias_map{$key} = Identifier($name);
58   }
59   my $order = $orig_select->{from};
60   my $order_gensym_count;
61   die "Slice's Select not followed by Order but order_is_stable set"
62     unless is_Order $order;
63   my (@order_nodes, %order_map);
64   while (is_Order $order) {
65     my $by = $order->{by};
66     if (is_Identifier $by) {
67       $default_inside_alias ||= $by->{elements}[0]
68         if @{$by->{elements}} == 2;
69       $order_map{$by}
70         = $alias_map{join('.', @{$by->{elements}})}
71           ||= do {
72                 if (
73                   @{$by->{elements}} == 2
74                   and $by->{elements}[0] eq $default_inside_alias
75                 ) {
76                   $by;
77                 } else {
78                   my $name = sprintf("ORDER__BY__%03i",++$order_gensym_count);
79                   push @inside_select_list, Alias($name, $by);
80                   Identifier($name);
81                 }
82               };
83     } else {
84       die "XXX not implemented yet";
85     }
86     push @order_nodes, $order;
87     $order = $order->{from};
88   }
89   $default_inside_alias ||= 'me';
90   my $limit_plus_offset = +{
91     %{$dq->{limit}}, value => $dq->{limit}{value} + $dq->{offset}{value}
92   };
93   my $inner_body = $order;
94   return $self->_render(
95     map {
96       $dq->{preserve_order}
97         ? Select(
98           \@outside_select_list,
99           compose {
100             Order($order_map{$b->{by}}, $b->{reverse}, $a)
101           } (
102             @order_nodes,
103             Alias($default_inside_alias, $_)
104           )
105         )
106         : $_
107     } (
108       Slice(
109         undef, $dq->{limit},
110         Select(
111           [
112             @outside_select_list,
113             $dq->{preserve_order}
114               ? (grep @{$_->{elements}} == 1,
115                   @order_map{map $_->{by}, @order_nodes})
116               : (),
117           ],
118           compose {
119             Order($order_map{$b->{by}}, !$b->{reverse}, $a)
120           } (
121             @order_nodes,
122             Alias(
123               $default_inside_alias,
124               Slice(
125                 undef, $limit_plus_offset,
126                 Select(
127                   \@inside_select_list,
128                   compose {
129                     Order($b->{by}, $b->{reverse}, $a)
130                   } @order_nodes, $inner_body
131                 )
132               )
133             )
134           )
135         )
136       )
137     )
138   );
139 }
140
141 1;