# See section WHERE: BUILTIN SPECIAL OPERATORS below for implementation
my @BUILTIN_SPECIAL_OPS = (
{regex => qr/^ (?: not \s )? between $/ix, handler => sub { die "NOPE" }},
- {regex => qr/^ (?: not \s )? in $/ix, handler => sub { die "NOPE" }},
{regex => qr/^ is (?: \s+ not )? $/ix, handler => sub { die "NOPE" }},
+ {regex => qr/^ (?: not \s )? in $/ix, handler => sub { die "NOPE" }},
);
#======================================================================
$opt{equality_op} = qr/^( \Q$opt{cmp}\E | \= )$/ix;
$opt{inequality_op} = qr/^( != | <> )$/ix;
- $opt{like_op} = qr/^ (is\s+)? r?like $/xi;
- $opt{not_like_op} = qr/^ (is\s+)? not \s+ r?like $/xi;
+ $opt{like_op} = qr/^ (is_)?r?like $/xi;
+ $opt{not_like_op} = qr/^ (is_)?not_r?like $/xi;
# SQL booleans
$opt{sqltrue} ||= '1=1';
if ($class->isa('DBIx::Class::SQLMaker')) {
push @{$opt{special_ops}}, our $DBIC_Compat_Op ||= {
- regex => qr/^(?:ident|value)$/i, handler => sub { die "NOPE" }
+ regex => qr/^(?:ident|value|(?:not\s)?in)$/i, handler => sub { die "NOPE" }
};
$opt{is_dbic_sqlmaker} = 1;
}
-and => '_expand_op_andor',
-or => '_expand_op_andor',
-nest => '_expand_nest',
+ -bind => sub { shift; +{ @_ } },
+ -in => '_expand_in',
+ -not_in => '_expand_in',
};
$opt{expand_op} = {
'between' => '_expand_between',
- 'not between' => '_expand_between',
+ 'not_between' => '_expand_between',
'in' => '_expand_in',
- 'not in' => '_expand_in',
+ 'not_in' => '_expand_in',
'nest' => '_expand_nest',
(map +($_ => '_expand_op_andor'), ('and', 'or')),
- (map +($_ => '_expand_op_is'), ('is', 'is not')),
+ (map +($_ => '_expand_op_is'), ('is', 'is_not')),
};
# placeholder for _expand_unop system
};
$opt{render_op} = {
- (map +($_ => '_render_op_between'), 'between', 'not between'),
- (map +($_ => '_render_op_in'), 'in', 'not in'),
+ (map +($_ => '_render_op_between'), 'between', 'not_between'),
+ (map +($_ => '_render_op_in'), 'in', 'not_in'),
(map +($_ => '_render_unop_postfix'),
- 'is null', 'is not null', 'asc', 'desc',
+ 'is_null', 'is_not_null', 'asc', 'desc',
),
(not => '_render_op_not'),
(map +($_ => '_render_op_andor'), qw(and or)),
return wantarray ? ($sql, @bind) : $sql;
}
+{ our $Default_Scalar_To = -value }
+
sub expand_expr {
my ($self, $expr, $default_scalar_to) = @_;
local our $Default_Scalar_To = $default_scalar_to if $default_scalar_to;
$self->render_aqt($self->expand_expr($expr));
}
+sub _normalize_op {
+ my ($self, $raw) = @_;
+ s/^-(?=.)//, s/\s+/_/g for my $op = lc $raw;
+ $op;
+}
+
sub _expand_expr {
my ($self, $expr) = @_;
our $Expand_Depth ||= 0; local $Expand_Depth = $Expand_Depth + 1;
return +{ -literal => $literal };
}
if (!ref($expr) or Scalar::Util::blessed($expr)) {
- if (my $d = our $Default_Scalar_To) {
- return $self->_expand_expr({ $d => $expr });
- }
- return $self->_expand_value(-value => $expr);
+ return $self->_expand_expr_scalar($expr);
}
die "notreached";
}
sub _expand_expr_hashpair_ident {
my ($self, $k, $v) = @_;
+ local our $Cur_Col_Meta = $k;
+
# hash with multiple or no elements is andor
if (ref($v) eq 'HASH' and keys %$v != 1) {
# scalars and objects get expanded as whatever requested or values
if (!ref($v) or Scalar::Util::blessed($v)) {
- my $d = our $Default_Scalar_To;
- local our $Cur_Col_Meta = $k;
- return $self->_expand_expr_hashpair_ident(
- $k,
- ($d
- ? $self->_expand_expr($d => $v)
- : { -value => $v }
- )
- );
+ return $self->_expand_expr_hashpair_scalar($k, $v);
}
+
+ # single key hashref is a hashtriple
+
if (ref($v) eq 'HASH') {
return $self->_expand_expr_hashtriple($k, %$v);
}
+
+ # arrayref needs re-engineering over the elements
+
if (ref($v) eq 'ARRAY') {
return $self->sqlfalse unless @$v;
$self->_debug("ARRAY($k) means distribute over elements");
$logic => $v, $k
);
}
+
if (my $literal = is_literal_value($v)) {
unless (length $k) {
belch 'Hash-pairs consisting of an empty string with a literal are deprecated, and will be removed in 2.0: use -and => [ $literal ] instead';
die "notreached";
}
+sub _expand_expr_scalar {
+ my ($self, $expr) = @_;
+
+ return $self->_expand_expr({ (our $Default_Scalar_To) => $expr });
+}
+
+sub _expand_expr_hashpair_scalar {
+ my ($self, $k, $v) = @_;
+
+ return $self->_expand_expr_hashpair_cmp(
+ $k, $self->_expand_expr_scalar($v),
+ );
+}
+
sub _expand_expr_hashpair_op {
my ($self, $k, $v) = @_;
- my $op = $k;
- $op =~ s/^-// if length($op) > 1;
- $self->_assert_pass_injection_guard($op);
+ $self->_assert_pass_injection_guard($k =~ /\A-(.*)\Z/s);
+
+ my $op = $self->_normalize_op($k);
# Ops prefixed with -not_ get converted
- if (my ($rest) = $op =~/^not[_ ](.*)$/) {
+ if (my ($rest) = $op =~/^not_(.*)$/) {
return +{ -op => [
'not',
$self->_expand_expr({ "-${rest}", $v })
- ] };
+ ] };
}
-
{ # Old SQLA compat
+ my $op = join(' ', split '_', $op);
+
# the old special op system requires illegality for top-level use
if (
my $ik = $self->_expand_ident(-ident => $k);
- my $op = join ' ', split '_', (map lc, $vk =~ /^-?(.*)$/)[0];
+ my $op = $self->_normalize_op($vk);
$self->_assert_pass_injection_guard($op);
- if ($op =~ s/ [_\s]? \d+ $//x ) {
+
+ if ($op =~ s/ _? \d+ $//x ) {
return $self->_expand_expr($k, { $vk, $vv });
}
if (my $x = $self->{expand_op}{$op}) {
return $self->$x($op, $vv, $k);
}
{ # Old SQLA compat
+
+ my $op = join(' ', split '_', $op);
+
if (my $us = List::Util::first { $op =~ $_->{regex} } @{$self->{special_ops}}) {
return { -op => [ $op, $ik, $vv ] };
}
or $op =~ $self->{not_like_op}
) {
if (lc($logic) eq '-or' and @values > 1) {
- belch "A multi-element arrayref as an argument to the inequality op '${\uc($op)}' "
+ belch "A multi-element arrayref as an argument to the inequality op '${\uc(join ' ', split '_', $op)}' "
. 'is technically equivalent to an always-true 1=1 (you probably wanted '
. "to say ...{ \$inequality_op => [ -and => \@values ] }... instead)"
;
}
sub _dwim_op_to_is {
- my ($self, $op, $empty, $fail) = @_;
+ my ($self, $raw, $empty, $fail) = @_;
+
+ my $op = $self->_normalize_op($raw);
+
if ($op =~ /^not$/i) {
return 0;
}
return 1;
}
if ($op =~ $self->{like_op}) {
- belch(sprintf $empty, uc($op));
+ belch(sprintf $empty, uc(join ' ', split '_', $op));
return 1;
}
if ($op =~ $self->{inequality_op}) {
return 0;
}
if ($op =~ $self->{not_like_op}) {
- belch(sprintf $empty, uc($op));
+ belch(sprintf $empty, uc(join ' ', split '_', $op));
return 0;
}
puke(sprintf $fail, $op);
and exists($vv->{-value})
and !defined($vv->{-value})
);
- return +{ -op => [ $op.' null', $self->_expand_ident(-ident => $k) ] };
+ return +{ -op => [ $op.'_null', $self->_expand_ident(-ident => $k) ] };
}
sub _expand_between {
}
sub _expand_in {
- my ($self, $op, $vv, $k) = @_;
+ my ($self, $raw, $vv, $k) = @_;
+ $k = shift @{$vv = [ @$vv ]} unless defined $k;
+ local our $Cur_Col_Meta = $k;
+ my $op = $self->_normalize_op($raw);
if (my $literal = is_literal_value($vv)) {
my ($sql, @bind) = @$literal;
my $opened_sql = $self->_open_outer_paren($sql);
return @$literal;
}
+sub _render_op {
+ my ($self, $v) = @_;
+ my ($op, @args) = @$v;
+ if (my $r = $self->{render_op}{$op}) {
+ return $self->$r($op, \@args);
+ }
+
+ { # Old SQLA compat
+
+ my $op = join(' ', split '_', $op);
+
+ 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 ($ident) = map $_->{-ident}, grep ref($_) eq 'HASH', $args[0];
+ my $k = join(($self->{name_sep}||'.'), @$ident);
+ local our $Expand_Depth = 1;
+ return $self->${\($us->{handler})}($k, $op, $args[1]);
+ }
+ if (my $us = List::Util::first { $op =~ $_->{regex} } @{$self->{unary_ops}}) {
+ return $self->${\($us->{handler})}($op, $args[0]);
+ }
+
+ }
+ if (@args == 1) {
+ return $self->_render_unop_prefix($op, \@args);
+ } else {
+ return $self->_render_op_multop($op, \@args);
+ }
+ die "notreached";
+}
+
+
sub _render_op_between {
my ($self, $op, $args) = @_;
my ($left, $low, $high) = @$args;
};
my ($lhsql, @lhbind) = $self->render_aqt($left);
return (
- join(' ', '(', $lhsql, $self->_sqlcase($op), $rhsql, ')'),
+ join(' ',
+ '(', $lhsql,
+ $self->_sqlcase(join ' ', split '_', $op),
+ $rhsql, ')'
+ ),
@lhbind, @rhbind
);
}
} @$rhs;
my ($lhsql, @lbind) = $self->render_aqt($lhs);
return (
- $lhsql.' '.$self->_sqlcase($op).' ( '
+ $lhsql.' '.$self->_sqlcase(join ' ', split '_', $op).' ( '
.join(', ', @in_sql)
.' )',
@lbind, @in_bind
return '' unless @parts;
return @{$parts[0]} if @parts == 1;
my ($final_sql) = join(
- ' '.$self->_sqlcase($op).' ',
+ ' '.$self->_sqlcase(join ' ', split '_', $op).' ',
map $_->[0], @parts
);
return (
map @{$_}[1..$#$_], @parts
);
}
-
-sub _render_op {
- my ($self, $v) = @_;
- my ($op, @args) = @$v;
- if (my $r = $self->{render_op}{$op}) {
- return $self->$r($op, \@args);
- }
-
- { # Old SQLA compat
-
- 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 ($ident) = map $_->{-ident}, grep ref($_) eq 'HASH', $args[0];
- my $k = join(($self->{name_sep}||'.'), @$ident);
- local our $Expand_Depth = 1;
- return $self->${\($us->{handler})}($k, $op, $args[1]);
- }
- if (my $us = List::Util::first { $op =~ $_->{regex} } @{$self->{unary_ops}}) {
- return $self->${\($us->{handler})}($op, $args[0]);
- }
-
- }
- if (@args == 1) {
- return $self->_render_unop_prefix($op, \@args);
- } else {
- return $self->_render_op_multop($op, \@args);
- }
- die "notreached";
-}
-
sub _render_op_not {
my ($self, $op, $v) = @_;
my ($sql, @bind) = $self->_render_unop_prefix($op, $v);
sub _render_unop_prefix {
my ($self, $op, $v) = @_;
my ($expr_sql, @bind) = $self->render_aqt($v->[0]);
- my $op_sql = $self->_sqlcase($op);
+
+ my $op_sql = $self->_sqlcase($op); # join ' ', split '_', $op);
return ("${op_sql} ${expr_sql}", @bind);
}
sub _render_unop_postfix {
my ($self, $op, $v) = @_;
my ($expr_sql, @bind) = $self->render_aqt($v->[0]);
- my $op_sql = $self->_sqlcase($op);
+ my $op_sql = $self->_sqlcase(join ' ', split '_', $op);
return ($expr_sql.' '.$op_sql, @bind);
}