X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?a=blobdiff_plain;f=lib%2FSQL%2FAbstract.pm;h=ed4bb33ca2299b4d69cbae8c12bca09eeb7267c9;hb=bbde1e88e7a995cd59c7d2e568555c6c716860c6;hp=24a1c8763ad4db85e24bc4a5139ea17d87d24c3e;hpb=eb49170d9524af0a98dbe02cba213d82be6f0cc3;p=scpubgit%2FQ-Branch.git diff --git a/lib/SQL/Abstract.pm b/lib/SQL/Abstract.pm index 24a1c87..ed4bb33 100644 --- a/lib/SQL/Abstract.pm +++ b/lib/SQL/Abstract.pm @@ -15,7 +15,7 @@ use Scalar::Util qw/blessed/; # GLOBALS #====================================================================== -our $VERSION = '1.51'; +our $VERSION = '1.54'; # This would confuse some packagers #$VERSION = eval $VERSION; # numify for warning-free dev releases @@ -25,8 +25,8 @@ our $AUTOLOAD; # special operators (-in, -between). May be extended/overridden by user. # See section WHERE: BUILTIN SPECIAL OPERATORS below for implementation my @BUILTIN_SPECIAL_OPS = ( - {regex => qr/^(not )?between$/i, handler => \&_where_field_BETWEEN}, - {regex => qr/^(not )?in$/i, handler => \&_where_field_IN}, + {regex => qr/^(not )?between$/i, handler => '_where_field_BETWEEN'}, + {regex => qr/^(not )?in$/i, handler => '_where_field_IN'}, ); #====================================================================== @@ -63,7 +63,7 @@ sub new { delete $opt{case} if $opt{case} && $opt{case} ne 'lower'; # default logic for interpreting arrayrefs - $opt{logic} = uc $opt{logic} || 'OR'; + $opt{logic} = $opt{logic} ? uc $opt{logic} : 'OR'; # how to return bind vars # LDNOTE: changed nwiger code : why this 'delete' ?? @@ -422,7 +422,6 @@ sub _where_HASHREF { my ($self, $where) = @_; my (@sql_clauses, @all_bind); - # LDNOTE : don't really know why we need to sort keys for my $k (sort keys %$where) { my $v = $where->{$k}; @@ -463,7 +462,7 @@ sub _where_op_in_hash { HASHREF => sub { if ($op eq 'OR') { - return $self->_where_ARRAYREF([%$v], 'OR'); + return $self->_where_ARRAYREF([ map { $_ => $v->{$_} } (sort keys %$v) ], 'OR'); } else { # NEST | AND return $self->_where_HASHREF($v); @@ -505,9 +504,10 @@ sub _where_hashpair_ARRAYREF { $self->_debug("ARRAY($k) means distribute over elements"); # put apart first element if it is an operator (-and, -or) - my $op = ($v[0] =~ /^ - (?: AND|OR ) $/ix - ? shift @v - : '' + my $op = ( + (defined $v[0] && $v[0] =~ /^ - (?: AND|OR ) $/ix) + ? shift @v + : '' ); my @distributed = map { {$k => $_} } @v; @@ -548,7 +548,19 @@ sub _where_hashpair_HASHREF { # CASE: special operators like -in or -between my $special_op = first {$op =~ $_->{regex}} @{$self->{special_ops}}; if ($special_op) { - ($sql, @bind) = $special_op->{handler}->($self, $k, $op, $val); + my $handler = $special_op->{handler}; + if (! $handler) { + puke "No handler supplied for special operator matching $special_op->{regex}"; + } + elsif (not ref $handler) { + ($sql, @bind) = $self->$handler ($k, $op, $val); + } + elsif (ref $handler eq 'CODE') { + ($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"; + } } else { $self->_SWITCH_refkind($val, { @@ -606,17 +618,25 @@ sub _where_field_op_ARRAYREF { if(@$vals) { $self->_debug("ARRAY($vals) means multiple elements: [ @$vals ]"); + # see if the first element is an -and/-or op + my $logic; + if ($vals->[0] =~ /^ - ( AND|OR ) $/ix) { + $logic = uc $1; + shift @$vals; + } + + # 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 : # with {field => {'!=' => [22, 33]}}, it would be ridiculous to generate # WHERE field != 22 OR field != 33 : the user probably means # WHERE field != 22 AND field != 33. - # To do this, replace the line below by : + # 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); - # distribute $op over each member of @$vals - return $self->_recurse_where([map { {$k => {$op, $_}} } @$vals]); } else { # try to DWIM on equality operators @@ -703,16 +723,39 @@ sub _where_UNDEF { sub _where_field_BETWEEN { my ($self, $k, $op, $vals) = @_; - ref $vals eq 'ARRAY' && @$vals == 2 - or puke "special op 'between' requires an arrayref of two values"; + (ref $vals eq 'ARRAY' && @$vals == 2) or + (ref $vals eq 'REF' && (@$$vals == 1 || @$$vals == 2 || @$$vals == 3)) + or puke "special op 'between' requires an arrayref of two values (or a scalarref or arrayrefref for literal SQL)"; - my ($label) = $self->_convert($self->_quote($k)); - my ($placeholder) = $self->_convert('?'); - my $and = $self->_sqlcase('and'); + my ($clause, @bind, $label, $and, $placeholder); + $label = $self->_convert($self->_quote($k)); + $and = ' ' . $self->_sqlcase('and') . ' '; + $placeholder = $self->_convert('?'); $op = $self->_sqlcase($op); - my $sql = "( $label $op $placeholder $and $placeholder )"; - my @bind = $self->_bindtype($k, @$vals); + if (ref $vals eq 'REF') { + ($clause, @bind) = @$$vals; + } + else { + my (@all_sql, @all_bind); + + foreach my $val (@$vals) { + my ($sql, @bind) = $self->_SWITCH_refkind($val, { + SCALAR => sub { + return ($placeholder, ($val)); + }, + SCALARREF => sub { + return ($self->_convert($$val), ()); + }, + }); + push @all_sql, $sql; + push @all_bind, @bind; + } + + $clause = (join $and, @all_sql); + @bind = $self->_bindtype($k, @all_bind); + } + my $sql = "( $label $op $clause )"; return ($sql, @bind) } @@ -806,7 +849,8 @@ sub _order_by_hash { my ($order) = ($key =~ /^-(desc|asc)/i) or puke "invalid key in _order_by hash : $key"; - return $self->_quote($val) ." ". $self->_sqlcase($order); + $val = ref $val eq 'ARRAY' ? $val : [$val]; + return join ', ', map { $self->_quote($_) . ' ' . $self->_sqlcase($order) } @$val; } @@ -2062,30 +2106,45 @@ Some functions take an order by clause. This can either be a scalar (just a column name,) a hash of C<< { -desc => 'col' } >> or C<< { -asc => 'col' } >>, or an array of either of the two previous forms. Examples: - Given | Will Generate + Given | Will Generate ---------------------------------------------------------- - \'colA DESC' | ORDER BY colA DESC - 'colA' | ORDER BY colA - [qw/colA colB/] | ORDER BY colA, colB - {-asc => 'colA'} | ORDER BY colA ASC - {-desc => 'colB'} | ORDER BY colB DESC - [ | - {-asc => 'colA'}, | ORDER BY colA ASC, colB DESC - {-desc => 'colB'} | - ] | - [colA => {-asc => 'colB'}] | ORDER BY colA, colB ASC - ========================================================== + | + \'colA DESC' | ORDER BY colA DESC + | + 'colA' | ORDER BY colA + | + [qw/colA colB/] | ORDER BY colA, colB + | + {-asc => 'colA'} | ORDER BY colA ASC + | + {-desc => 'colB'} | ORDER BY colB DESC + | + ['colA', {-asc => 'colB'}] | ORDER BY colA, colB ASC + | + { -asc => [qw/colA colB] } | ORDER BY colA ASC, colB ASC + | + [ | + { -asc => 'colA' }, | ORDER BY colA ASC, colB DESC, + { -desc => [qw/colB/], | colC ASC, colD ASC + { -asc => [qw/colC colD/],| + ] | + =========================================================== =head1 SPECIAL OPERATORS my $sqlmaker = SQL::Abstract->new(special_ops => [ - {regex => qr/.../, + { + regex => qr/.../, handler => sub { my ($self, $field, $op, $arg) = @_; ... - }, + }, + }, + { + regex => qr/.../, + handler => 'method_name', }, ]); @@ -2098,12 +2157,13 @@ For example : WHERE MATCH(field) AGAINST (?, ?) Special operators IN and BETWEEN are fairly standard and therefore -are builtin within C. For other operators, -like the MATCH .. AGAINST example above which is -specific to MySQL, you can write your own operator handlers : -supply a C argument to the C method. -That argument takes an arrayref of operator definitions; -each operator definition is a hashref with two entries +are builtin within C (as the overridable methods +C<_where_field_IN> and C<_where_field_BETWEEN>). For other operators, +like the MATCH .. AGAINST example above which is specific to MySQL, +you can write your own operator handlers - supply a C +argument to the C method. That argument takes an arrayref of +operator definitions; each operator definition is a hashref with two +entries: =over @@ -2113,10 +2173,24 @@ the regular expression to match the operator =item handler -coderef that will be called when meeting that operator -in the input tree. The coderef will be called with -arguments C<< ($self, $field, $op, $arg) >>, and -should return a C<< ($sql, @bind) >> structure. +Either a coderef or a plain scalar method name. In both cases +the expected return is C<< ($sql, @bind) >>. + +When supplied with a method name, it is simply called on the +L object as: + + $self->$method_name ($field, $op, $arg) + + Where: + + $op is the part that matched the handler regex + $field is the LHS of the operator + $arg is the RHS + +When supplied with a coderef, it is called as: + + $coderef->($self, $field, $op, $arg) + =back @@ -2278,6 +2352,7 @@ so I have no idea who they are! But the people I do know are: Guillermo Roditi (patch to cleanup "IN" and "BETWEEN", fix and tests for _order_by) Laurent Dami (internal refactoring, multiple -nest, extensible list of special operators, literal SQL) Norbert Buchmuller (support for literal SQL in hashpair, misc. fixes & tests) + Peter Rabbitson (rewrite of SQLA::Test, misc. fixes & tests) Thanks! @@ -2296,6 +2371,8 @@ While not an official support venue, C makes heavy use of C, and as such list members there are very familiar with how to create queries. +=head1 LICENSE + This module is free software; you may copy this under the terms of the GNU General Public License, or the Artistic License, copies of which should have accompanied your Perl kit.