# GLOBALS
#======================================================================
-our $VERSION = '1.50';
+our $VERSION = '1.51';
# This would confuse some packagers
#$VERSION = eval $VERSION; # numify for warning-free dev releases
sub _where_op_in_hash {
- my ($self, $op, $v) = @_;
+ my ($self, $op_str, $v) = @_;
+
+ $op_str =~ /^ (AND|OR|NEST) ( \_? \d* ) $/xi
+ or puke "unknown operator: -$op_str";
+
+ my $op = uc($1); # uppercase, remove trailing digits
+ if ($2) {
+ belch 'Use of [and|or|nest]_N modifiers is deprecated and will be removed in SQLA v2.0. '
+ . "You probably wanted ...-and => [ $op_str => COND1, $op_str => COND2 ... ]";
+ }
- $op =~ /^(AND|OR|NEST)[_\d]*/i
- or puke "unknown operator: -$op";
- $op = uc($1); # uppercase, remove trailing digits
$self->_debug("OP(-$op) within hashref, recursing...");
$self->_SWITCH_refkind($v, {
$self->_debug("ARRAY($k) means distribute over elements");
# put apart first element if it is an operator (-and, -or)
- my $op = $v[0] =~ /^-/ ? shift @v : undef;
- $self->_debug("OP($op) reinjected into the distributed array") if $op;
-
+ my $op = ($v[0] =~ /^ - (?: AND|OR ) $/ix
+ ? shift @v
+ : ''
+ );
my @distributed = map { {$k => $_} } @v;
- unshift @distributed, $op if $op;
+
+ if ($op) {
+ $self->_debug("OP($op) reinjected into the distributed array");
+ unshift @distributed, $op;
+ }
+
my $logic = $op ? substr($op, 1) : '';
return $self->_recurse_where(\@distributed, $logic);
}
sub _where_hashpair_HASHREF {
- my ($self, $k, $v) = @_;
+ my ($self, $k, $v, $logic) = @_;
+ $logic ||= 'and';
- my (@all_sql, @all_bind);
+ my ($all_sql, @all_bind);
for my $op (sort keys %$v) {
my $val = $v->{$op};
@bind = @sub_bind;
},
+ HASHREF => sub {
+ ($sql, @bind) = $self->_where_hashpair_HASHREF($k, $val, $op);
+ },
+
UNDEF => sub { # CASE: col => {op => undef} : sql "IS (NOT)? NULL"
my $is = ($op =~ $self->{equality_op}) ? 'is' :
($op =~ $self->{inequality_op}) ? 'is not' :
});
}
- push @all_sql, $sql;
+ ($all_sql) = (defined $all_sql and $all_sql) ? $self->_join_sql_clauses($logic, [$all_sql, $sql], []) : $sql;
push @all_bind, @bind;
}
-
- return $self->_join_sql_clauses('and', \@all_sql, \@all_bind);
+ return ($all_sql, @all_bind);
}
=item sqltrue, sqlfalse
Expressions for inserting boolean values within SQL statements.
-By default these are C<1=1> and C<1=0>.
+By default these are C<1=1> and C<1=0>. They are used
+by the special operators C<-in> and C<-not_in> for generating
+correct SQL even when the argument is an empty array (see below).
=item logic
A field associated to an empty arrayref will be considered a
logical false and will generate 0=1.
-=head2 Key-value pairs
+=head2 Specific comparison operators
If you want to specify a different type of operator for your comparison,
you can use a hashref for a given column:
status => [ -or => {'=', 'assigned'}, {'=', 'in-progress'}]
status => [ {'=', 'assigned'}, {'=', 'in-progress'} ]
-In addition to C<-and> and C<-or>, there is also a special C<-nest>
-operator which adds an additional set of parens, to create a subquery.
-For example, to get something like this:
-
- $stmt = "WHERE user = ? AND ( workhrs > ? OR geo = ? )";
- @bind = ('nwiger', '20', 'ASIA');
-
-You would do:
-
- my %where = (
- user => 'nwiger',
- -nest => [ workhrs => {'>', 20}, geo => 'ASIA' ],
- );
-
-If you need several nested subexpressions, you can number
-the C<-nest> branches :
-
- my %where = (
- user => 'nwiger',
- -nest1 => ...,
- -nest2 => ...,
- ...
- );
=head2 Special operators : IN, BETWEEN, etc.
The reverse operator C<-not_in> generates SQL C<NOT IN> and is used in
the same way.
+If the argument to C<-in> is an empty array, 'sqlfalse' is generated
+(by default : C<1=0>). Similarly, C<< -not_in => [] >> generates
+'sqltrue' (by default : C<1=1>).
+
+
+
Another pair of operators is C<-between> and C<-not_between>,
used with an arrayref of two values:
These are the two builtin "special operators"; but the
list can be expanded : see section L</"SPECIAL OPERATORS"> below.
-=head2 Nested conditions
+=head2 Nested conditions, -and/-or prefixes
So far, we've seen how multiple conditions are joined with a top-level
C<AND>. We can change this by putting the different conditions we want in
OR ( user = ? AND status = ? ) )";
@bind = ('nwiger', 'pending', 'dispatched', 'robot', 'unassigned');
-This can be combined with the C<-nest> operator to properly group
-SQL statements. Furthermore, hashrefs or arrayrefs can be
+
+There is also a special C<-nest>
+operator which adds an additional set of parens, to create a subquery.
+For example, to get something like this:
+
+ $stmt = "WHERE user = ? AND ( workhrs > ? OR geo = ? )";
+ @bind = ('nwiger', '20', 'ASIA');
+
+You would do:
+
+ my %where = (
+ user => 'nwiger',
+ -nest => [ workhrs => {'>', 20}, geo => 'ASIA' ],
+ );
+
+
+Finally, clauses in hashrefs or arrayrefs can be
prefixed with an C<-and> or C<-or> to change the logic
inside :
( ( workhrs > ? AND geo = ? )
OR ( workhrs < ? AND geo = ? ) ) )
+
+=head2 Algebraic inconsistency, for historical reasons
+
C<Important note>: when connecting several conditions, the C<-and->|C<-or>
operator goes C<outside> of the nested structure; whereas when connecting
several constraints on one column, the C<-and> operator goes
OR ( c = ? OR d = ? )
OR ( e LIKE ? AND e LIKE ? ) ) )
+This difference in syntax is unfortunate but must be preserved for
+historical reasons. So be careful : the two examples below would
+seem algebraically equivalent, but they are not
+
+ {col => [-and => {-like => 'foo%'}, {-like => '%bar'}]}
+ # yields : WHERE ( ( col LIKE ? AND col LIKE ? ) )
+
+ [-and => {col => {-like => 'foo%'}, {col => {-like => '%bar'}}]]
+ # yields : WHERE ( ( col LIKE ? OR col LIKE ? ) )
+
=head2 Literal SQL
=item *
-added official support for -nest1, -nest2 or -nest_1, -nest_2, ...
-(undocumented in previous versions)
-
-=item *
-
optional support for L<array datatypes|/"Inserting and Updating Arrays">
=item *