The London Perl and Raku Workshop takes place on 26th Oct 2024. If your company depends on Perl, please consider sponsoring and/or attending.
package Data::Query::Renderer::SQL::Slice::GenericSubquery;

use Data::Query::ExprHelpers;
use Moo::Role;

with 'Data::Query::Renderer::SQL::Slice::SubqueryRemap';

sub slice_subquery {
  (limit => 1, offset => 1);
}

sub slice_stability {
  (limit => 'requires', offset => 'requires');
}

sub _render_slice {
  my ($self, $dq) = @_;
  die "Slice's inner is not a Select"
    unless is_Select my $orig_select = $dq->{from};
  my %remapped = $self->_subquery_remap($orig_select);
  my $first_from = $remapped{inner_body};
  # Should we simply strip until we reach a join/alias/etc. here?
  STRIP: while ($first_from) {
    if (is_Group($first_from)) {
      $first_from = $first_from->{from};
      next STRIP;
    } elsif (is_Where($first_from)) {
      $first_from = $first_from->{from};
      next STRIP;
    } elsif (is_Join($first_from)) {
      $first_from = $first_from->{left};
      next STRIP;
    }
    last STRIP;
  }
  die "WHAT" unless $first_from;
  $first_from = $first_from->{from} if is_Alias($first_from);
  my @main_order;
  foreach my $i (0..$#{$remapped{inside_order}}) {
    my $order = $remapped{inside_order}[$i];
    my $outside = $remapped{outside_order}[$i];
    if (is_Identifier($order->{by})
        and (
          (@{$order->{by}{elements}} == 2
          and $order->{by}{elements}[0] eq $remapped{default_inside_alias})
        or (@{$order->{by}{elements}} == 1))
    ) {
      push @main_order, [
        $outside->{by}, $order->{by}{elements}[-1], $order->{reverse},
        $order->{nulls}
      ];
    } else {
      last;
    }
  }

  my $count_alias = 'rownum__emulation';
  my ($op_and, $op_or) = map +{ 'SQL.Naive' => $_ }, qw(AND OR);
  my $count_cond = compose {
    my $lhs = $b->[0];
    my $rhs = Identifier($count_alias, $b->[1]);
    ($lhs, $rhs) = ($rhs, $lhs) if $b->[2];
    my $no_nulls = ($b->[3]||'') eq 'none';
    my ($this) = map {
      $no_nulls
        ? $_
        : Operator($op_or, [
            Operator($op_and, [
              Operator({ 'SQL.Naive' => 'IS NOT NULL' }, [ $lhs ]),
              Operator({ 'SQL.Naive' => 'IS NULL' }, [ $rhs ]),
            ]),
            $_
          ])
    } Operator({ 'SQL.Naive' => '>' }, [ $lhs, $rhs ]);
    my $final = (
      $a
        ? Operator($op_or, [
            $this,
            Operator($op_and, [
              (map {
                $no_nulls
                  ? $_
                  : Operator($op_or, [
                      Operator($op_and, [
                        map Operator({ 'SQL.Naive' => 'IS NULL' }, [ $_ ]),
                          $lhs, $rhs
                      ]),
                      $_,
                    ])
              } Operator({ 'SQL.Naive' => '=' }, [ $lhs, $rhs ])),
              $a
            ])
          ])
        : $this
    );
    $final;
  } @main_order, undef;
  my $count_sel = Select(
    [ Operator({ 'SQL.Naive' => 'apply' }, [ Identifier('COUNT'), Identifier('*') ]) ],
    Where(
      $count_cond,
      Alias($count_alias, $first_from)
    )
  );
  my $count_where = Operator(
    { 'SQL.Naive' => ($dq->{offset} ? 'BETWEEN' : '<') },
    [ $count_sel, (
        $dq->{offset}
          ? (
              $dq->{offset},
              {
                %{$dq->{limit}},
                value => $dq->{limit}{value}+$dq->{offset}{value}-1
              }
            )
          : ($dq->{limit})
      )
    ]
  );
  return $self->render(
    Select(
      $remapped{outside_select_list},
      (compose { no warnings 'once'; Order($b->{by}, $b->{reverse}, $b->{nulls}, $a) }
        @{$remapped{outside_order}},
        Where(
          $count_where,
          Alias(
            $remapped{default_inside_alias},
            Select(
              $remapped{inside_select_list},
              $remapped{inner_body},
            )
          )
        )
      )
    )
  );
}

1;