functionalise order mapping
[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   die "Slice's Select not followed by Order but order_is_stable set"
29     unless is_Order $orig_select->{from};
30
31   my $gensym_count;
32   my $default_inside_alias;
33
34   my @inside_select_list = map {
35     if (is_Alias) {
36       $_;
37     } elsif (is_Identifier) {
38       my @el = @{$_->{elements}};
39       if (@el == 2 and $el[0] eq ($default_inside_alias ||= $el[0])) {
40         $_;
41       } else {
42         Alias(join('__', @el), $_);
43       }
44     } else {
45       Alias(sprintf("GENSYM__%03i",++$gensym_count), $_);
46     }
47   } @{$orig_select->{select}};
48
49   my %alias_map = map {
50     if (is_Alias and is_Identifier $_->{from}) {
51       +(join('.',@{$_->{from}{elements}}) => Identifier($_->{to}))
52     } elsif (is_Identifier) {
53       +(join('.',@{$_->{elements}}) => $_)
54     } else {
55       +()
56     }
57   } @inside_select_list;
58
59   my @outside_select_list = map {
60     if (is_Alias) {
61       Identifier($_->{to});
62     } else {
63       $_;
64     }
65   } @inside_select_list;
66
67   my @order_nodes;
68   my $inner_body = do {
69     my $order = $orig_select->{from};
70     while (is_Order $order) {
71       push @order_nodes, $order;
72       $order = $order->{from};
73     }
74     $order;
75   };
76
77   my $order_gensym_count;
78   my @mapped_order = map {
79     my $by = $_->{by};
80     if (is_Identifier $by) {
81       $default_inside_alias ||= $by->{elements}[0]
82         if @{$by->{elements}} == 2;
83       my $mapped_by
84         = $alias_map{join('.', @{$by->{elements}})}
85           ||= do {
86                 if (
87                   @{$by->{elements}} == 2
88                   and $by->{elements}[0] eq $default_inside_alias
89                 ) {
90                   $by;
91                 } else {
92                   my $name = sprintf("ORDER__BY__%03i",++$order_gensym_count);
93                   push @inside_select_list, Alias($name, $by);
94                   Identifier($name);
95                 }
96               };
97       Order($mapped_by, $_->{reverse});
98     } else {
99       die "XXX not implemented yet";
100     }
101   } @order_nodes;
102
103   $default_inside_alias ||= 'me';
104
105   my $limit_plus_offset = +{
106     %{$dq->{limit}}, value => $dq->{limit}{value} + $dq->{offset}{value}
107   };
108
109   return $self->_render(
110     map {
111       $dq->{preserve_order}
112         ? Select(
113           \@outside_select_list,
114           compose {
115             Order($b->{by}, $b->{reverse}, $a)
116           } (
117             @mapped_order,
118             Alias($default_inside_alias, $_)
119           )
120         )
121         : $_
122     } (
123       Slice(
124         undef, $dq->{limit},
125         Select(
126           [
127             @outside_select_list,
128             $dq->{preserve_order}
129               ? (grep @{$_->{elements}} == 1,
130                   map $_->{by}, @mapped_order)
131               : (),
132           ],
133           compose {
134             Order($b->{by}, !$b->{reverse}, $a)
135           } (
136             @mapped_order,
137             Alias(
138               $default_inside_alias,
139               Slice(
140                 undef, $limit_plus_offset,
141                 Select(
142                   \@inside_select_list,
143                   compose {
144                     Order($b->{by}, $b->{reverse}, $a)
145                   } @order_nodes, $inner_body
146                 )
147               )
148             )
149           )
150         )
151       )
152     )
153   );
154 }
155
156 1;