5320cbbf6e756ef678b8f13a0f222b6af86e099e
[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       ];
51     } else {
52       last;
53     }
54   }
55   
56   my $count_alias = 'rownum__emulation';
57   my ($op_and, $op_or) = map +{ 'SQL.Naive' => $_ }, qw(AND OR);
58   my $count_cond = compose {
59     my $lhs = $b->[0];
60     my $rhs = Identifier($count_alias, $b->[1]);
61     ($lhs, $rhs) = ($rhs, $lhs) if $b->[2];
62     my $this = Operator($op_or, [
63       Operator($op_and, [
64         Operator({ 'SQL.Naive' => 'IS NOT NULL' }, [ $lhs ]),
65         Operator({ 'SQL.Naive' => 'IS NULL' }, [ $rhs ]),
66       ]),
67       Operator({ 'SQL.Naive' => '>' }, [ $lhs, $rhs ]),
68     ]);
69     ($a
70       ? Operator($op_or, [
71           $this,
72           Operator($op_and, [
73             Operator($op_or, [
74               Operator($op_and, [
75                 map Operator({ 'SQL.Naive' => 'IS NULL' }, [ $_ ]), $lhs, $rhs
76               ]),
77               Operator({ 'SQL.Naive' => '=' }, [ $lhs, $rhs ])
78             ]),
79             $a
80           ])
81         ])
82       : $this)
83   } @main_order, undef;
84   my $count_sel = Select(
85     [ Operator({ 'SQL.Naive' => 'apply' }, [ Identifier('COUNT'), Identifier('*') ]) ],
86     Where(
87       $count_cond,
88       Alias($count_alias, $first_from)
89     )
90   );
91   my $count_where = Operator(
92     { 'SQL.Naive' => ($dq->{offset} ? 'BETWEEN' : '<') },
93     [ $count_sel, (
94         $dq->{offset}
95           ? (
96               $dq->{offset},
97               {
98                 %{$dq->{limit}},
99                 value => $dq->{limit}{value}+$dq->{offset}{value}-1
100               }
101             )
102           : ($dq->{limit})
103       )
104     ]
105   );
106   return $self->render(
107     Select(
108       $remapped{outside_select_list},
109       (compose { no warnings 'once'; Order($b->{by}, $b->{reverse}, $b->{nulls}, $a) }
110         @{$remapped{outside_order}},
111         Where(
112           $count_where,
113           Alias(
114             $remapped{default_inside_alias},
115             Select(
116               $remapped{inside_select_list},
117               $remapped{inner_body},
118             )
119           )
120         )
121       )
122     )
123   );
124 }
125
126 1;