$opt{sqlfalse} ||= '0=1';
# special operators
- $opt{user_special_ops} = [ @{$opt{special_ops} ||= []} ];
+ $opt{special_ops} ||= [];
+
# regexes are applied in order, thus push after user-defines
push @{$opt{special_ops}}, @BUILTIN_SPECIAL_OPS;
+ if ($class->isa('DBIx::Class::SQLMaker')) {
+ push @{$opt{special_ops}}, our $DBIC_Compat_Op ||= {
+ regex => qr/^(?:ident|value)$/i, handler => sub { die "NOPE" }
+ };
+ $opt{is_dbic_sqlmaker} = 1;
+ }
+
# unary operators
$opt{unary_ops} ||= [];
$opt{node_types} = +{
map +("-$_" => '_render_'.$_),
- qw(op func value bind ident literal)
+ qw(op func bind ident literal list)
};
$opt{expand_unary} = {};
$self->_expand_maybe_list_expr( [
map {
my ($k, $set) = @$_;
+ $set = { -bind => $_ } unless defined $set;
+{ -op => [ '=', { -ident => $k }, $set ] };
}
map {
sub _select_fields {
my ($self, $fields) = @_;
+ return $fields unless ref($fields);
return $self->_render_expr(
$self->_expand_maybe_list_expr($fields, undef, '-ident')
);
sub _expand_expr {
my ($self, $expr, $logic, $default_scalar_to) = @_;
local our $Default_Scalar_To = $default_scalar_to if $default_scalar_to;
+ our $Expand_Depth ||= 0; local $Expand_Depth = $Expand_Depth + 1;
return undef unless defined($expr);
if (ref($expr) eq 'HASH') {
if (keys %$expr > 1) {
sort keys %$expr
] };
}
- return unless %$expr;
+ return { -literal => [ '' ] } unless keys %$expr;
return $self->_expand_expr_hashpair(%$expr, $logic);
}
if (ref($expr) eq 'ARRAY') {
my $logic = lc($logic || $self->{logic});
$logic eq 'and' or $logic eq 'or' or puke "unknown logic: $logic";
- my @expr = @$expr;
+ #my @expr = @$expr;
+ my @expr = grep {
+ (ref($_) eq 'ARRAY' and @$_)
+ or (ref($_) eq 'HASH' and %$_)
+ or 1
+ } @$expr;
my @res;
unless defined($el) and length($el);
my $elref = ref($el);
if (!$elref) {
+ local $Expand_Depth = 0;
push(@res, $self->_expand_expr({ $el, shift(@expr) }));
} elsif ($elref eq 'ARRAY') {
push(@res, $self->_expand_expr($el)) if @$el;
} elsif (my $l = is_literal_value($el)) {
push @res, { -literal => $l };
} elsif ($elref eq 'HASH') {
- push @res, $self->_expand_expr($el);
+ local $Expand_Depth = 0;
+ push @res, $self->_expand_expr($el) if %$el;
} else {
die "notreached";
}
}
if (!ref($expr) or Scalar::Util::blessed($expr)) {
if (my $d = $Default_Scalar_To) {
- return +{ $d => $expr };
+ return $self->_expand_expr({ $d => $expr });
}
if (my $m = our $Cur_Col_Meta) {
return +{ -bind => [ $m, $expr ] };
}
- return +{ -value => $expr };
+ return +{ -bind => [ undef, $expr ] };
}
die "notreached";
}
. "You probably wanted ...-and => [ $k => COND1, $k => COND2 ... ]";
}
if ($k eq '-nest') {
+ # DBIx::Class requires a nest warning to be emitted once but the private
+ # method it overrode to do so no longer exists
+ if ($self->{is_dbic_sqlmaker}) {
+ unless (our $Nest_Warned) {
+ belch(
+ "-nest in search conditions is deprecated, you most probably wanted:\n"
+ .q|{..., -and => [ \%cond0, \@cond1, \'cond2', \[ 'cond3', [ col => bind ] ], etc. ], ... }|
+ );
+ $Nest_Warned = 1;
+ }
+ }
return $self->_expand_expr($v);
}
if ($k eq '-bool') {
$op =~ s/^-// if length($op) > 1;
# top level special ops are illegal in general
- puke "Illegal use of top-level '-$op'"
- if List::Util::first { $op =~ $_->{regex} } @{$self->{special_ops}};
+ # note that, arguably, if it makes no sense at top level, it also
+ # makes no sense on the other side of an = sign or similar but DBIC
+ # gets disappointingly upset if I disallow it
+ if (
+ (our $Expand_Depth) == 1
+ and List::Util::first { $op =~ $_->{regex} } @{$self->{special_ops}}
+ ) {
+ puke "Illegal use of top-level '-$op'"
+ }
if (my $us = List::Util::first { $op =~ $_->{regex} } @{$self->{unary_ops}}) {
return { -op => [ $op, $v ] };
}
}
- if ($k eq '-value' and my $m = our $Cur_Col_Meta) {
- return +{ -bind => [ $m, $v ] };
+ if ($k eq '-value') {
+ return +{ -bind => [ our $Cur_Col_Meta, $v ] };
}
if (my $custom = $self->{expand_unary}{$k}) {
return $self->$custom($v);
and (keys %$v)[0] =~ /^-/
) {
my ($func) = $k =~ /^-(.*)$/;
+ if (List::Util::first { $func =~ $_->{regex} } @{$self->{special_ops}}) {
+ return +{ -op => [ $func, $self->_expand_expr($v) ] };
+ }
return +{ -func => [ $func, $self->_expand_expr($v) ] };
}
if (!ref($v) or is_literal_value($v)) {
sort keys %$v
] };
}
+ return { -literal => [ '' ] } unless keys %$v;
my ($vk, $vv) = %$v;
$vk =~ s/^-//;
$vk = lc($vk);
] };
}
}
- if (my $us = List::Util::first { $vk =~ $_->{regex} } @{$self->{user_special_ops}}) {
+ if (my $us = List::Util::first { $vk =~ $_->{regex} } @{$self->{special_ops}}) {
return { -op => [ $vk, { -ident => $k }, $vv ] };
}
if (my $us = List::Util::first { $vk =~ $_->{regex} } @{$self->{unary_ops}}) {
my ($sql, @bind) = @$literal;
if ($self->{bindtype} eq 'columns') {
for (@bind) {
- if (!defined $_ || ref($_) ne 'ARRAY' || @$_ != 2) {
- puke "bindtype 'columns' selected, you need to pass: [column_name => bind_value]"
- }
+ $self->_assert_bindval_matches_bindtype($_);
}
}
return +{ -literal => [ $self->_quote($k).' '.$sql, @bind ] };
sub _recurse_where {
my ($self, $where, $logic) = @_;
-#print STDERR Data::Dumper::Concise::Dumper([ $where, $logic ]);
+ # Special case: top level simple string treated as literal
- my $where_exp = $self->_expand_expr($where, $logic);
+ my $where_exp = (ref($where)
+ ? $self->_expand_expr($where, $logic)
+ : { -literal => [ $where ] });
-#print STDERR Data::Dumper::Concise::Dumper([ EXP => $where_exp ]);
-
- # dispatch on appropriate method according to refkind of $where
-# my $method = $self->_METHOD_FOR_refkind("_where", $where_exp);
-
-# my ($sql, @bind) = $self->$method($where_exp, $logic);
+ # dispatch expanded expression
my ($sql, @bind) = defined($where_exp) ? $self->_render_expr($where_exp) : (undef);
-
# DBIx::Class used to call _recurse_where in scalar context
# something else might too...
if (wantarray) {
return $self->_convert($self->_quote($ident));
}
-sub _render_value {
- my ($self, $value) = @_;
-
- return ($self->_convert('?'), $self->_bindtype(undef, $value));
-}
-
my %unop_postfix = map +($_ => 1),
'is null', 'is not null',
'asc', 'desc',
if (my $h = $special{$op}) {
return $self->$h(\@args);
}
- if (my $us = List::Util::first { $op =~ $_->{regex} } @{$self->{user_special_ops}}) {
+ my $us = List::Util::first { $op =~ $_->{regex} } @{$self->{special_ops}};
+ if ($us and @args > 1) {
puke "Special op '${op}' requires first value to be identifier"
unless my ($k) = map $_->{-ident}, grep ref($_) eq 'HASH', $args[0];
+ local our $Expand_Depth = 1;
return $self->${\($us->{handler})}($k, $op, $args[1]);
}
if (my $us = List::Util::first { $op =~ $_->{regex} } @{$self->{unary_ops}}) {
? "${expr_sql} ${op_sql}"
: "${op_sql} ${expr_sql}"
);
- return (($op eq 'not' ? '('.$final_sql.')' : $final_sql), @bind);
+ return (($op eq 'not' || $us ? '('.$final_sql.')' : $final_sql), @bind);
+ #} elsif (@args == 0) {
+ # return '';
} else {
- my @parts = map [ $self->_render_expr($_) ], @args;
- my ($final_sql) = map +($op =~ /^(and|or)$/ ? "(${_})" : $_), join(
- ($final_op eq ',' ? '' : ' ').$self->_sqlcase($final_op).' ',
+ my @parts = grep length($_->[0]), map [ $self->_render_expr($_) ], @args;
+ return '' unless @parts;
+ my $is_andor = !!($op =~ /^(and|or)$/);
+ return @{$parts[0]} if $is_andor and @parts == 1;
+ my ($final_sql) = map +($is_andor ? "( ${_} )" : $_), join(
+ ' '.$self->_sqlcase($final_op).' ',
map $_->[0], @parts
);
return (
die "unhandled";
}
+sub _render_list {
+ my ($self, $list) = @_;
+ my @parts = grep length($_->[0]), map [ $self->_render_expr($_) ], @$list;
+ return join(', ', map $_->[0], @parts), map @{$_}[1..$#$_], @parts;
+}
+
sub _render_func {
my ($self, $rest) = @_;
my ($func, @args) = @$rest;
# ORDER BY
#======================================================================
-sub _order_by {
+sub _expand_order_by {
my ($self, $arg) = @_;
- return '' unless defined($arg) and not (ref($arg) eq 'ARRAY' and !@$arg);
+ return unless defined($arg) and not (ref($arg) eq 'ARRAY' and !@$arg);
my $expander = sub {
my ($self, $dir, $expr) = @_;
+ my @to_expand = ref($expr) eq 'ARRAY' ? @$expr : $expr;
+ foreach my $arg (@to_expand) {
+ if (
+ ref($arg) eq 'HASH'
+ and keys %$arg > 1
+ and grep /^-(asc|desc)$/, keys %$arg
+ ) {
+ puke "ordering direction hash passed to order by must have exactly one key (-asc or -desc)";
+ }
+ }
my @exp = map +(defined($dir) ? { -op => [ $dir => $_ ] } : $_),
map $self->_expand_expr($_, undef, -ident),
- ref($expr) eq 'ARRAY' ? @$expr : $expr;
- return (@exp > 1 ? { -op => [ ',', @exp ] } : $exp[0]);
+ map ref($_) eq 'ARRAY' ? @$_ : $_, @to_expand;
+ return (@exp > 1 ? { -list => \@exp } : $exp[0]);
};
local @{$self->{expand_unary}}{qw(-asc -desc)} = (
sub { shift->$expander(desc => @_) },
);
- my $expanded = $self->$expander(undef, $arg);
+ return $self->$expander(undef, $arg);
+}
+
+sub _order_by {
+ my ($self, $arg) = @_;
+
+ return '' unless defined(my $expanded = $self->_expand_order_by($arg));
my ($sql, @bind) = $self->_render_expr($expanded);
+ return '' unless length($sql);
+
my $final_sql = $self->_sqlcase(' order by ').$sql;
return wantarray ? ($final_sql, @bind) : $final_sql;
}
+# _order_by no longer needs to call this so doesn't but DBIC uses it.
+
+sub _order_by_chunks {
+ my ($self, $arg) = @_;
+
+ return () unless defined(my $expanded = $self->_expand_order_by($arg));
+
+ return $self->_chunkify_order_by($expanded);
+}
+
+sub _chunkify_order_by {
+ my ($self, $expanded) = @_;
+
+ return grep length, $self->_render_expr($expanded)
+ if $expanded->{-ident} or @{$expanded->{-literal}||[]} == 1;
+
+ for ($expanded) {
+ if (ref() eq 'HASH' and my $l = $_->{-list}) {
+ return map $self->_chunkify_order_by($_), @$l;
+ }
+ return [ $self->_render_expr($_) ];
+ }
+}
+
#======================================================================
# DATASOURCE (FOR NOW, JUST PLAIN TABLE OR LIST OF TABLES)
#======================================================================
my ($self, $expr, $logic, $default) = @_;
my $e = do {
if (ref($expr) eq 'ARRAY') {
- return { -op => [
- ',', map $self->_expand_expr($_, $logic, $default), @$expr
+ return { -list => [
+ map $self->_expand_expr($_, $logic, $default), @$expr
] } if @$expr > 1;
$expr->[0]
} else {