X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?a=blobdiff_plain;f=lib%2FSQL%2FAbstract.pm;h=c6b2e2461db79d2f5ea7a6e0b8c9737a4de0f1c6;hb=97a920ef62ad5bf8177531935a67e4ce4d02f260;hp=127e6ad30558bb7724ed4f96a6c8a7149be290f7;hpb=01a01e578da5a9743771d793d81dcbbb7f965183;p=scpubgit%2FQ-Branch.git diff --git a/lib/SQL/Abstract.pm b/lib/SQL/Abstract.pm index 127e6ad..c6b2e24 100644 --- a/lib/SQL/Abstract.pm +++ b/lib/SQL/Abstract.pm @@ -15,7 +15,7 @@ use Scalar::Util qw/blessed/; # GLOBALS #====================================================================== -our $VERSION = '1.53'; +our $VERSION = '1.54'; # This would confuse some packagers #$VERSION = eval $VERSION; # numify for warning-free dev releases @@ -25,10 +25,19 @@ 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'}, ); +# unaryish operators - key maps to handler +my $BUILTIN_UNARY_OPS = { + 'AND' => '_where_op_ANDOR', + 'OR' => '_where_op_ANDOR', + 'NEST' => '_where_op_NEST', + 'BOOL' => '_where_op_BOOL', + 'NOT_BOOL' => '_where_op_BOOL', +}; + #====================================================================== # DEBUGGING AND ERROR REPORTING #====================================================================== @@ -443,8 +452,8 @@ sub _where_HASHREF { sub _where_op_in_hash { my ($self, $op_str, $v) = @_; - $op_str =~ /^ (AND|OR|NEST) ( \_? \d* ) $/xi - or puke "unknown operator: -$op_str"; + $op_str =~ /^ ([A-Z_]+[A-Z]) ( \_? \d* ) $/xi + or puke "unknown or malstructured operator: -$op_str"; my $op = uc($1); # uppercase, remove trailing digits if ($2) { @@ -454,36 +463,75 @@ sub _where_op_in_hash { $self->_debug("OP(-$op) within hashref, recursing..."); + my $handler = $BUILTIN_UNARY_OPS->{$op}; + if (! $handler) { + puke "unknown operator: -$op_str"; + } + elsif (not ref $handler) { + 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"; + } +} + +sub _where_op_ANDOR { + my ($self, $op, $v) = @_; + + $self->_SWITCH_refkind($v, { + ARRAYREF => sub { + return $self->_where_ARRAYREF($v, $op); + }, + + HASHREF => sub { + return ( $op eq 'OR' ) + ? $self->_where_ARRAYREF( [ map { $_ => $v->{$_} } ( sort keys %$v ) ], $op ) + : $self->_where_HASHREF($v); + }, + + SCALARREF => sub { + puke "-$op => \\\$scalar not supported, use -nest => ..."; + }, + + ARRAYREFREF => sub { + puke "-$op => \\[..] not supported, use -nest => ..."; + }, + + SCALAR => sub { # permissively interpreted as SQL + puke "-$op => 'scalar' not supported, use -nest => \\'scalar'"; + }, + + UNDEF => sub { + puke "-$op => undef not supported"; + }, + }); +} + +sub _where_op_NEST { + my ($self, $op, $v) = @_; + $self->_SWITCH_refkind($v, { ARRAYREF => sub { - return $self->_where_ARRAYREF($v, $op eq 'NEST' ? '' : $op); + return $self->_where_ARRAYREF($v, ''); }, HASHREF => sub { - if ($op eq 'OR') { - return $self->_where_ARRAYREF([ map { $_ => $v->{$_} } (sort keys %$v) ], 'OR'); - } - else { # NEST | AND - return $self->_where_HASHREF($v); - } + return $self->_where_HASHREF($v); }, SCALARREF => sub { # literal SQL - $op eq 'NEST' - or puke "-$op => \\\$scalar not supported, use -nest => ..."; return ($$v); }, ARRAYREFREF => sub { # literal SQL - $op eq 'NEST' - or puke "-$op => \\[..] not supported, use -nest => ..."; return @{${$v}}; }, SCALAR => sub { # permissively interpreted as SQL - $op eq 'NEST' - or puke "-$op => 'scalar' not supported, use -nest => \\'scalar'"; belch "literal SQL should be -nest => \\'scalar' " . "instead of -nest => 'scalar' "; return ($v); @@ -496,6 +544,22 @@ sub _where_op_in_hash { } +sub _where_op_BOOL { + my ($self, $op, $v) = @_; + + my $prefix = $op eq 'BOOL' ? '' : 'NOT '; + $self->_SWITCH_refkind($v, { + SCALARREF => sub { # literal SQL + return ($prefix . $$v); + }, + + SCALAR => sub { # interpreted as SQL column + return ($prefix . $self->_convert($self->_quote($v))); + }, + }); +} + + sub _where_hashpair_ARRAYREF { my ($self, $k, $v) = @_; @@ -548,7 +612,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, { @@ -790,8 +866,6 @@ sub _where_field_IN { - - #====================================================================== # ORDER BY #====================================================================== @@ -1827,6 +1901,24 @@ Would give you: These are the two builtin "special operators"; but the list can be expanded : see section L below. +=head2 Boolean operators + +If you wish to test against boolean columns or functions within your +database you can use the C<-bool> and C<-not_bool> operators. For +example to test the column C being true and the column + being false you would use:- + + my %where = ( + -bool => 'is_user', + -not_bool => 'is_enabled', + ); + +Would give you: + + WHERE is_user AND NOT is_enabledmv + + + =head2 Nested conditions, -and/-or prefixes So far, we've seen how multiple conditions are joined with a top-level @@ -2123,11 +2215,16 @@ or an array of either of the two previous forms. Examples: =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', }, ]); @@ -2140,12 +2237,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 @@ -2155,10 +2253,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