use List::Util 'first';
use Scalar::Util 'blessed';
use Sub::Name 'subname';
+use Data::Query::Constants;
+use Data::Query::ExprHelpers;
use namespace::clean;
#
# join collapse *will not work* on heavy data types.
my $connecting_aliastypes = $self->_resolve_aliastypes_from_select_args({
%$inner_attrs,
- select => [],
+ select => undef,
});
for (sort map { keys %{$_->{-seen_columns}||{}} } map { values %$_ } values %$connecting_aliastypes) {
$sql_maker->{name_sep} = '';
}
+ # delete local is 5.12+
+ local @{$sql_maker}{qw(renderer converter)};
+ delete @{$sql_maker}{qw(renderer converter)};
+
my ($lquote, $rquote, $sep) = map { quotemeta $_ } ($sql_maker->_quote_chars, $sql_maker->name_sep);
# generate sql chunks
my $to_scan = {
restricting => [
- $sql_maker->_recurse_where ($attrs->{where}),
- $sql_maker->_parse_rs_attrs ({ having => $attrs->{having} }),
+ ($attrs->{where}
+ ? ($sql_maker->_recurse_where($attrs->{where}))[0]
+ : ()
+ ),
+ ($attrs->{having}
+ ? ($sql_maker->_recurse_where($attrs->{having}))[0]
+ : ()
+ ),
],
grouping => [
- $sql_maker->_parse_rs_attrs ({ group_by => $attrs->{group_by} }),
+ ($attrs->{group_by}
+ ? ($sql_maker->_render_sqla(group_by => $attrs->{group_by}))[0]
+ : (),
+ )
],
joining => [
$sql_maker->_recurse_from (
),
],
selecting => [
- map { $sql_maker->_recurse_fields($_) } @{$attrs->{select}},
+ map { $sql_maker->_render_sqla(select_select => $_) =~ /^SELECT\s+(.+)/ } @{$attrs->{select}||[]},
],
ordering => [
map { $_->[0] } $self->_extract_order_criteria ($attrs->{order_by}, $sql_maker),
}
}
- my @order_by = $self->_extract_order_criteria($attrs->{order_by})
+ my $sql_maker = $self->sql_maker;
+ my @order_by = $self->_extract_order_criteria($attrs->{order_by}, $sql_maker)
or return (\@group_by, $attrs->{order_by});
# add any order_by parts that are not already present in the group_by
# the proper overall order without polluting the group criteria (and
# possibly changing the outcome entirely)
- my ($leftovers, $sql_maker, @new_order_by, $order_chunks, $aliastypes);
+ my ($leftovers, @new_order_by, $order_chunks, $aliastypes);
my $group_already_unique = $self->_columns_comprise_identifying_set($colinfos, \@group_by);
# pesky tests won't pass
# wrap any part of the order_by that "responds" to an ordering alias
# into a MIN/MAX
- $sql_maker ||= $self->sql_maker;
- $order_chunks ||= [
- map { ref $_ eq 'ARRAY' ? $_ : [ $_ ] } $sql_maker->_order_by_chunks($attrs->{order_by})
- ];
- my ($chunk, $is_desc) = $sql_maker->_split_order_chunk($order_chunks->[$o_idx][0]);
+ $order_chunks ||= do {
+ my @c;
+ my $dq_node = $sql_maker->converter->_order_by_to_dq($attrs->{order_by});
- $new_order_by[$o_idx] = \[
- sprintf( '%s( %s )%s',
- ($is_desc ? 'MAX' : 'MIN'),
- $chunk,
- ($is_desc ? ' DESC' : ''),
- ),
- @ {$order_chunks->[$o_idx]} [ 1 .. $#{$order_chunks->[$o_idx]} ]
- ];
+ while (is_Order($dq_node)) {
+ push @c, {
+ is_desc => $dq_node->{reverse},
+ dq_node => $dq_node->{by},
+ };
+
+ @{$c[-1]}{qw(sql bind)} = $sql_maker->_render_dq($dq_node->{by});
+
+ $dq_node = $dq_node->{from};
+ }
+
+ \@c;
+ };
+
+ $new_order_by[$o_idx] = {
+ ($order_chunks->[$o_idx]{is_desc} ? '-desc' : '-asc') => \[
+ sprintf ( '%s( %s )',
+ ($order_chunks->[$o_idx]{is_desc} ? 'MAX' : 'MIN'),
+ $order_chunks->[$o_idx]{sql},
+ ),
+ @{ $order_chunks->[$o_idx]{bind} || [] }
+ ]
+ };
}
}
# recreate the untouched order parts
if (@new_order_by) {
- $new_order_by[$_] ||= \ $order_chunks->[$_] for ( 0 .. $#$order_chunks );
+ $new_order_by[$_] ||= {
+ ( $order_chunks->[$_]{is_desc} ? '-desc' : '-asc' )
+ => \ $order_chunks->[$_]{dq_node}
+ } for ( 0 .. $#$order_chunks );
}
return (
}
sub _extract_order_criteria {
- my ($self, $order_by, $sql_maker) = @_;
-
- my $parser = sub {
- my ($sql_maker, $order_by, $orig_quote_chars) = @_;
+ my ($self, $order_by, $sql_maker, $ident_only) = @_;
- return scalar $sql_maker->_order_by_chunks ($order_by)
- unless wantarray;
+ $sql_maker ||= $self->sql_maker;
- my ($lq, $rq, $sep) = map { quotemeta($_) } (
- ($orig_quote_chars ? @$orig_quote_chars : $sql_maker->_quote_chars),
- $sql_maker->name_sep
- );
-
- my @chunks;
- for ($sql_maker->_order_by_chunks ($order_by) ) {
- my $chunk = ref $_ ? [ @$_ ] : [ $_ ];
- ($chunk->[0]) = $sql_maker->_split_order_chunk($chunk->[0]);
+ my $order_dq = $sql_maker->converter->_order_by_to_dq($order_by);
- # order criteria may have come back pre-quoted (literals and whatnot)
- # this is fragile, but the best we can currently do
- $chunk->[0] =~ s/^ $lq (.+?) $rq $sep $lq (.+?) $rq $/"$1.$2"/xe
- or $chunk->[0] =~ s/^ $lq (.+) $rq $/$1/x;
+ my @by;
+ while (is_Order($order_dq)) {
+ push @by, $order_dq->{by};
+ $order_dq = $order_dq->{from};
+ }
- push @chunks, $chunk;
+ # delete local is 5.12+
+ local @{$sql_maker}{qw(quote_char renderer converter)};
+ delete @{$sql_maker}{qw(quote_char renderer converter)};
+
+ return map { [ $sql_maker->_render_dq($_) ] } do {
+ if ($ident_only) {
+ my @by_ident;
+ scan_dq_nodes({ DQ_IDENTIFIER ,=> sub { push @by_ident, $_[0] } }, @by);
+ @by_ident
+ } else {
+ @by
}
-
- return @chunks;
};
-
- if ($sql_maker) {
- return $parser->($sql_maker, $order_by);
- }
- else {
- $sql_maker = $self->sql_maker;
-
- # pass these in to deal with literals coming from
- # the user or the deep guts of prefetch
- my $orig_quote_chars = [$sql_maker->_quote_chars];
-
- local $sql_maker->{quote_char};
- return $parser->($sql_maker, $order_by, $orig_quote_chars);
- }
}
sub _order_by_is_stable {
my ($self, $ident, $order_by, $where) = @_;
my @cols = (
- (map { $_->[0] } $self->_extract_order_criteria($order_by)),
+ (map { $_->[0] } $self->_extract_order_criteria($order_by, undef, 1)),
$where ? @{$self->_extract_fixed_condition_columns($where)} :(),
) or return undef;
sub _extract_fixed_condition_columns {
my ($self, $where) = @_;
+ if (ref($where) eq 'REF' and ref($$where) eq 'HASH') {
+ # Yes. I know.
+ my $fixed = DBIx::Class::ResultSource->_extract_fixed_values_for($$where);
+ return [ keys %$fixed ];
+ }
+
return unless ref $where eq 'HASH';
my @cols;