X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?a=blobdiff_plain;f=lib%2FSQL%2FAbstract.pm;h=532d70aa65730b4350978a1dc60bd2a6b27d82d3;hb=bd6a65cac4599702fc4a717772934ff28421c6be;hp=8e0364bb56db61836f5b42902a1e6e343cf1c06a;hpb=59f23b3d42833c2f990c26f96997be819ebe3a26;p=dbsrgits%2FSQL-Abstract.git diff --git a/lib/SQL/Abstract.pm b/lib/SQL/Abstract.pm index 8e0364b..532d70a 100644 --- a/lib/SQL/Abstract.pm +++ b/lib/SQL/Abstract.pm @@ -15,7 +15,7 @@ use Scalar::Util qw/blessed/; # GLOBALS #====================================================================== -our $VERSION = '1.54'; +our $VERSION = '1.58'; # This would confuse some packagers #$VERSION = eval $VERSION; # numify for warning-free dev releases @@ -31,10 +31,11 @@ my @BUILTIN_SPECIAL_OPS = ( # unaryish operators - key maps to handler my @BUILTIN_UNARY_OPS = ( - { regex => qr/^and (\s? \d+)?$/xi, handler => '_where_op_ANDOR' }, - { regex => qr/^or (\s? \d+)?$/xi, handler => '_where_op_ANDOR' }, - { regex => qr/^nest (\s? \d+)?$/xi, handler => '_where_op_NEST' }, - { regex => qr/^(not \s?)? bool$/xi, handler => '_where_op_BOOL' }, + # the digits are backcompat stuff + { regex => qr/^and (?: \s? \d+ )? $/xi, handler => '_where_op_ANDOR' }, + { regex => qr/^or (?: \s? \d+ )? $/xi, handler => '_where_op_ANDOR' }, + { regex => qr/^nest (?: \s? \d+ )? $/xi, handler => '_where_op_NEST' }, + { regex => qr/^ (?: not \s )? bool $/xi, handler => '_where_op_BOOL' }, ); #====================================================================== @@ -438,7 +439,7 @@ sub _where_HASHREF { my $v = $where->{$k}; # ($k => $v) is either a special op or a regular hashpair - my ($sql, @bind) = ($k =~ /^-(.+)/) ? $self->_where_op_in_hash($1, $v) + my ($sql, @bind) = ($k =~ /^(-.+)/) ? $self->_where_op_in_hash($1, $v) : do { my $method = $self->_METHOD_FOR_refkind("_where_hashpair", $v); $self->$method($k, $v); @@ -453,41 +454,39 @@ sub _where_HASHREF { sub _where_op_in_hash { - my ($self, $op, $v) = @_; + my ($self, $orig_op, $v) = @_; # put the operator in canonical form - $op =~ s/^-//; # remove initial dash - $op =~ tr/_/ /; # underscores become spaces - $op =~ s/^\s+//; # no initial space - $op =~ s/\s+$//; # no final space - $op =~ s/\s+/ /; # multiple spaces become one + my $op = $orig_op; + $op =~ s/^-//; # remove initial dash + $op =~ s/[_\t ]+/ /g; # underscores and whitespace become single spaces + $op =~ s/^\s+|\s+$//g;# remove leading/trailing space $self->_debug("OP(-$op) within hashref, recursing..."); my $op_entry = first {$op =~ $_->{regex}} @{$self->{unary_ops}}; my $handler = $op_entry->{handler}; if (! $handler) { - puke "unknown operator: -$op"; + puke "unknown operator: $orig_op"; } elsif (not ref $handler) { + if ($op =~ s/\s?\d+$//) { + belch 'Use of [and|or|nest]_N modifiers is deprecated and will be removed in SQLA v2.0. ' + . "You probably wanted ...-and => [ -$op => COND1, -$op => COND2 ... ]"; + } return $self->$handler ($op, $v); } elsif (ref $handler eq 'CODE') { return $handler->($self, $op, $v); } else { - puke "Illegal handler for operator $op - expecting a method name or a coderef"; + puke "Illegal handler for operator $orig_op - expecting a method name or a coderef"; } } sub _where_op_ANDOR { my ($self, $op, $v) = @_; - if ($op =~ s/\s?\d+$//) { - belch 'Use of [and|or|nest]_N modifiers is deprecated and will be removed in SQLA v2.0. ' - . "You probably wanted ...-and => [ $op => COND1, $op => COND2 ... ]"; - } - $self->_SWITCH_refkind($v, { ARRAYREF => sub { return $self->_where_ARRAYREF($v, $op); @@ -520,12 +519,6 @@ sub _where_op_ANDOR { sub _where_op_NEST { my ($self, $op, $v) = @_; - if ($op =~ s/\s?\d+$//) { - belch 'Use of [and|or|nest]_N modifiers is deprecated and will be removed in SQLA v2.0. ' - . "You probably wanted ...-and => [ $op => COND1, $op => COND2 ... ]"; - } - - $self->_SWITCH_refkind($v, { ARRAYREF => sub { @@ -560,14 +553,35 @@ sub _where_op_NEST { sub _where_op_BOOL { my ($self, $op, $v) = @_; - my $prefix = ($op =~ /\bnot\b/i) ? 'NOT ' : ''; + my ( $prefix, $suffix ) = ( $op =~ /\bnot\b/i ) + ? ( '(NOT ', ')' ) + : ( '', '' ); $self->_SWITCH_refkind($v, { + ARRAYREF => sub { + my ( $sql, @bind ) = $self->_where_ARRAYREF($v, ''); + return ( ($prefix . $sql . $suffix), @bind ); + }, + + ARRAYREFREF => sub { + my ( $sql, @bind ) = @{ ${$v} }; + return ( ($prefix . $sql . $suffix), @bind ); + }, + + HASHREF => sub { + my ( $sql, @bind ) = $self->_where_HASHREF($v); + return ( ($prefix . $sql . $suffix), @bind ); + }, + SCALARREF => sub { # literal SQL - return ($prefix . $$v); + return ($prefix . $$v . $suffix); }, SCALAR => sub { # interpreted as SQL column - return ($prefix . $self->_convert($self->_quote($v))); + return ($prefix . $self->_convert($self->_quote($v)) . $suffix); + }, + + UNDEF => sub { + puke "-$op => undef not supported"; }, }); } @@ -610,15 +624,14 @@ sub _where_hashpair_HASHREF { my ($all_sql, @all_bind); - for my $op (sort keys %$v) { - my $val = $v->{$op}; + for my $orig_op (sort keys %$v) { + my $val = $v->{$orig_op}; # put the operator in canonical form - $op =~ s/^-//; # remove initial dash - $op =~ tr/_/ /; # underscores become spaces - $op =~ s/^\s+//; # no initial space - $op =~ s/\s+$//; # no final space - $op =~ s/\s+/ /; # multiple spaces become one + my $op = $orig_op; + $op =~ s/^-//; # remove initial dash + $op =~ s/[_\t ]+/ /g; # underscores and whitespace become single spaces + $op =~ s/^\s+|\s+$//g;# remove leading/trailing space my ($sql, @bind); @@ -627,7 +640,7 @@ sub _where_hashpair_HASHREF { if ($special_op) { my $handler = $special_op->{handler}; if (! $handler) { - puke "No handler supplied for special operator matching $special_op->{regex}"; + puke "No handler supplied for special operator $orig_op"; } elsif (not ref $handler) { ($sql, @bind) = $self->$handler ($k, $op, $val); @@ -636,7 +649,7 @@ sub _where_hashpair_HASHREF { ($sql, @bind) = $handler->($self, $k, $op, $val); } else { - puke "Illegal handler for special operator matching $special_op->{regex} - expecting a method name or a coderef"; + puke "Illegal handler for special operator $orig_op - expecting a method name or a coderef"; } } else { @@ -668,10 +681,10 @@ sub _where_hashpair_HASHREF { UNDEF => sub { # CASE: col => {op => undef} : sql "IS (NOT)? NULL" my $is = ($op =~ $self->{equality_op}) ? 'is' : ($op =~ $self->{inequality_op}) ? 'is not' : - puke "unexpected operator '$op' with undef operand"; + puke "unexpected operator '$orig_op' with undef operand"; $sql = $self->_quote($k) . $self->_sqlcase(" $is null"); }, - + FALLBACK => sub { # CASE: col => {op => $scalar} $sql = join ' ', $self->_convert($self->_quote($k)), $self->_sqlcase($op), @@ -692,18 +705,23 @@ sub _where_hashpair_HASHREF { sub _where_field_op_ARRAYREF { my ($self, $k, $op, $vals) = @_; - if(@$vals) { - $self->_debug("ARRAY($vals) means multiple elements: [ @$vals ]"); + my @vals = @$vals; #always work on a copy + + if(@vals) { + $self->_debug(sprintf '%s means multiple elements: [ %s ]', + $vals, + join (', ', map { defined $_ ? "'$_'" : 'NULL' } @vals ), + ); # see if the first element is an -and/-or op my $logic; - if ($vals->[0] =~ /^ - ( AND|OR ) $/ix) { + if (defined $vals[0] && $vals[0] =~ /^ - ( AND|OR ) $/ix) { $logic = uc $1; - shift @$vals; + shift @vals; } - # distribute $op over each remaining member of @$vals, append logic if exists - return $self->_recurse_where([map { {$k => {$op, $_}} } @$vals], $logic); + # distribute $op over each remaining member of @vals, append logic if exists + return $self->_recurse_where([map { {$k => {$op, $_}} } @vals], $logic); # LDNOTE : had planned to change the distribution logic when # $op =~ $self->{inequality_op}, because of Morgan laws : @@ -712,7 +730,7 @@ sub _where_field_op_ARRAYREF { # WHERE field != 22 AND field != 33. # To do this, replace the above to roughly : # my $logic = ($op =~ $self->{inequality_op}) ? 'AND' : 'OR'; - # return $self->_recurse_where([map { {$k => {$op, $_}} } @$vals], $logic); + # return $self->_recurse_where([map { {$k => {$op, $_}} } @vals], $logic); } else { @@ -886,48 +904,76 @@ sub _where_field_IN { sub _order_by { my ($self, $arg) = @_; - # construct list of ordering instructions - my @order = $self->_SWITCH_refkind($arg, { + my (@sql, @bind); + for my $c ($self->_order_by_chunks ($arg) ) { + $self->_SWITCH_refkind ($c, { + SCALAR => sub { push @sql, $c }, + ARRAYREF => sub { push @sql, shift @$c; push @bind, @$c }, + }); + } + + my $sql = @sql + ? sprintf ('%s %s', + $self->_sqlcase(' order by'), + join (', ', @sql) + ) + : '' + ; + + return wantarray ? ($sql, @bind) : $sql; +} + +sub _order_by_chunks { + my ($self, $arg) = @_; + + return $self->_SWITCH_refkind($arg, { ARRAYREF => sub { - map {$self->_SWITCH_refkind($_, { - SCALAR => sub {$self->_quote($_)}, - UNDEF => sub {}, - SCALARREF => sub {$$_}, # literal SQL, no quoting - HASHREF => sub {$self->_order_by_hash($_)} - }) } @$arg; + map { $self->_order_by_chunks ($_ ) } @$arg; }, + ARRAYREFREF => sub { [ @$$arg ] }, + SCALAR => sub {$self->_quote($arg)}, - UNDEF => sub {}, + + UNDEF => sub {return () }, + SCALARREF => sub {$$arg}, # literal SQL, no quoting - HASHREF => sub {$self->_order_by_hash($arg)}, - }); + HASHREF => sub { + # get first pair in hash + my ($key, $val) = each %$arg; - # build SQL - my $order = join ', ', @order; - return $order ? $self->_sqlcase(' order by')." $order" : ''; -} + return () unless $key; + if ( (keys %$arg) > 1 or not $key =~ /^-(desc|asc)/i ) { + puke "hash passed to _order_by must have exactly one key (-desc or -asc)"; + } -sub _order_by_hash { - my ($self, $hash) = @_; + my $direction = $1; - # get first pair in hash - my ($key, $val) = each %$hash; + my @ret; + for my $c ($self->_order_by_chunks ($val)) { + my ($sql, @bind); - # check if one pair was found and no other pair in hash - $key && !(each %$hash) - or puke "hash passed to _order_by must have exactly one key (-desc or -asc)"; + $self->_SWITCH_refkind ($c, { + SCALAR => sub { + $sql = $c; + }, + ARRAYREF => sub { + ($sql, @bind) = @$c; + }, + }); - my ($order) = ($key =~ /^-(desc|asc)/i) - or puke "invalid key in _order_by hash : $key"; + $sql = $sql . ' ' . $self->_sqlcase($direction); - $val = ref $val eq 'ARRAY' ? $val : [$val]; - return join ', ', map { $self->_quote($_) . ' ' . $self->_sqlcase($order) } @$val; -} + push @ret, [ $sql, @bind]; + } + return @ret; + }, + }); +} #====================================================================== @@ -1934,8 +1980,23 @@ example to test the column C being true and the column Would give you: - WHERE is_user AND NOT is_enabledmv + WHERE is_user AND NOT is_enabled +If a more complex combination is required, testing more conditions, +then you should use the and/or operators:- + + my %where = ( + -and => [ + -bool => 'one', + -bool => 'two', + -bool => 'three', + -not_bool => 'four', + ], + ); + +Would give you: + + WHERE one AND two AND three AND NOT four =head2 Nested conditions, -and/-or prefixes @@ -2058,8 +2119,10 @@ with this: TMTOWTDI. -Conditions on boolean columns can be expressed in the -same way, passing a reference to an empty string : +Conditions on boolean columns can be expressed in the same way, passing +a reference to an empty string, however using liternal SQL in this way +is deprecated - the preferred method is to use the boolean operators - +see L : my %where = ( priority => { '<', 2 }, @@ -2318,7 +2381,7 @@ of the MATCH .. AGAINST syntax for MySQL =head1 UNARY OPERATORS - my $sqlmaker = SQL::Abstract->new(special_ops => [ + my $sqlmaker = SQL::Abstract->new(unary_ops => [ { regex => qr/.../, handler => sub {