rewrite GenericSubquery to handle multiple order columns
[dbsrgits/Data-Query.git] / lib / Data / Query / Renderer / SQL / Slice / GenericSubquery.pm
CommitLineData
01d7143b 1package Data::Query::Renderer::SQL::Slice::GenericSubquery;
2
3use Data::Query::ExprHelpers;
4use Moo::Role;
5
6with 'Data::Query::Renderer::SQL::Slice::SubqueryRemap';
7
fbf38b8e 8sub slice_subquery {
9 (limit => 1, offset => 1);
10}
11
8b2c306b 12sub slice_stability {
13 (limit => 'requires', offset => 'requires');
14}
15
01d7143b 16sub _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};
5a058623 22 # Should we simply strip until we reach a join/alias/etc. here?
9c778d0f 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;
01d7143b 35 }
9c778d0f 36 die "WHAT" unless $first_from;
01d7143b 37 $first_from = $first_from->{from} if is_Alias($first_from);
9c778d0f 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
01d7143b 56 my $count_alias = 'rownum__emulation';
9c778d0f 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;
01d7143b 84 my $count_sel = Select(
85 [ Operator({ 'SQL.Naive' => 'apply' }, [ Identifier('COUNT'), Identifier('*') ]) ],
86 Where(
9c778d0f 87 $count_cond,
01d7143b 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},
e3335558 109 (compose { no warnings 'once'; Order($b->{by}, $b->{reverse}, $b->{nulls}, $a) }
01d7143b 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
1261;