Fix trailing whitespace
[dbsrgits/Data-Query.git] / lib / Data / Query / Renderer / SQL / Slice / GenericSubquery.pm
1 package Data::Query::Renderer::SQL::Slice::GenericSubquery;
2
3 use Data::Query::ExprHelpers;
4 use Moo::Role;
5
6 with 'Data::Query::Renderer::SQL::Slice::SubqueryRemap';
7
8 sub slice_subquery {
9   (limit => 1, offset => 1);
10 }
11
12 sub slice_stability {
13   (limit => 'requires', offset => 'requires');
14 }
15
16 sub _render_slice {
17   my ($self, $dq) = @_;
18   die "Slice's inner is not a Select"
19     unless is_Select my $orig_select = $dq->{from};
20   my %remapped = $self->_subquery_remap($orig_select);
21   my $first_from = $remapped{inner_body};
22   # Should we simply strip until we reach a join/alias/etc. here?
23   STRIP: while ($first_from) {
24     if (is_Group($first_from)) {
25       $first_from = $first_from->{from};
26       next STRIP;
27     } elsif (is_Where($first_from)) {
28       $first_from = $first_from->{from};
29       next STRIP;
30     } elsif (is_Join($first_from)) {
31       $first_from = $first_from->{left};
32       next STRIP;
33     }
34     last STRIP;
35   }
36   die "WHAT" unless $first_from;
37   $first_from = $first_from->{from} if is_Alias($first_from);
38   my @main_order;
39   foreach my $i (0..$#{$remapped{inside_order}}) {
40     my $order = $remapped{inside_order}[$i];
41     my $outside = $remapped{outside_order}[$i];
42     if (is_Identifier($order->{by})
43         and (
44           (@{$order->{by}{elements}} == 2
45           and $order->{by}{elements}[0] eq $remapped{default_inside_alias})
46         or (@{$order->{by}{elements}} == 1))
47     ) {
48       push @main_order, [
49         $outside->{by}, $order->{by}{elements}[-1], $order->{reverse},
50         $order->{nulls}
51       ];
52     } else {
53       last;
54     }
55   }
56
57   my $count_alias = 'rownum__emulation';
58   my ($op_and, $op_or) = map +{ 'SQL.Naive' => $_ }, qw(AND OR);
59   my $count_cond = compose {
60     my $lhs = $b->[0];
61     my $rhs = Identifier($count_alias, $b->[1]);
62     ($lhs, $rhs) = ($rhs, $lhs) if $b->[2];
63     my $no_nulls = ($b->[3]||'') eq 'none';
64     my ($this) = map {
65       $no_nulls
66         ? $_
67         : Operator($op_or, [
68             Operator($op_and, [
69               Operator({ 'SQL.Naive' => 'IS NOT NULL' }, [ $lhs ]),
70               Operator({ 'SQL.Naive' => 'IS NULL' }, [ $rhs ]),
71             ]),
72             $_
73           ])
74     } Operator({ 'SQL.Naive' => '>' }, [ $lhs, $rhs ]);
75     my $final = (
76       $a
77         ? Operator($op_or, [
78             $this,
79             Operator($op_and, [
80               (map {
81                 $no_nulls
82                   ? $_
83                   : Operator($op_or, [
84                       Operator($op_and, [
85                         map Operator({ 'SQL.Naive' => 'IS NULL' }, [ $_ ]),
86                           $lhs, $rhs
87                       ]),
88                       $_,
89                     ])
90               } Operator({ 'SQL.Naive' => '=' }, [ $lhs, $rhs ])),
91               $a
92             ])
93           ])
94         : $this
95     );
96     $final;
97   } @main_order, undef;
98   my $count_sel = Select(
99     [ Operator({ 'SQL.Naive' => 'apply' }, [ Identifier('COUNT'), Identifier('*') ]) ],
100     Where(
101       $count_cond,
102       Alias($count_alias, $first_from)
103     )
104   );
105   my $count_where = Operator(
106     { 'SQL.Naive' => ($dq->{offset} ? 'BETWEEN' : '<') },
107     [ $count_sel, (
108         $dq->{offset}
109           ? (
110               $dq->{offset},
111               {
112                 %{$dq->{limit}},
113                 value => $dq->{limit}{value}+$dq->{offset}{value}-1
114               }
115             )
116           : ($dq->{limit})
117       )
118     ]
119   );
120   return $self->render(
121     Select(
122       $remapped{outside_select_list},
123       (compose { no warnings 'once'; Order($b->{by}, $b->{reverse}, $b->{nulls}, $a) }
124         @{$remapped{outside_order}},
125         Where(
126           $count_where,
127           Alias(
128             $remapped{default_inside_alias},
129             Select(
130               $remapped{inside_select_list},
131               $remapped{inner_body},
132             )
133           )
134         )
135       )
136     )
137   );
138 }
139
140 1;