X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?a=blobdiff_plain;f=lib%2FSQL%2FAbstract.pm;h=08f6011689c6822ce84a6120263e035c21d51e7a;hb=843a94b5a440baaea0b5cd7d751f8995a1657421;hp=91de6a99c2be4d8107523a4659a62f2204761482;hpb=f0770d6b3c31d0c507d7c059b843a87ddef4b337;p=dbsrgits%2FSQL-Abstract.git diff --git a/lib/SQL/Abstract.pm b/lib/SQL/Abstract.pm index 91de6a9..08f6011 100644 --- a/lib/SQL/Abstract.pm +++ b/lib/SQL/Abstract.pm @@ -6,11 +6,28 @@ use Carp (); use List::Util (); use Scalar::Util (); +use Exporter 'import'; +our @EXPORT_OK = qw(is_plain_value is_literal_value); + +BEGIN { + if ($] < 5.009_005) { + require MRO::Compat; + } + else { + require mro; + } + + *SQL::Abstract::_ENV_::DETECT_AUTOGENERATED_STRINGIFICATION = $ENV{SQLA_ISVALUE_IGNORE_AUTOGENERATED_STRINGIFICATION} + ? sub () { 0 } + : sub () { 1 } + ; +} + #====================================================================== # GLOBALS #====================================================================== -our $VERSION = '1.75'; +our $VERSION = '1.78'; # This would confuse some packagers $VERSION = eval $VERSION if $VERSION =~ /_/; # numify for warning-free dev releases @@ -58,6 +75,69 @@ sub puke (@) { Carp::croak "[$func] Fatal: ", @_; } +sub is_literal_value ($) { + ref $_[0] eq 'SCALAR' ? [ ${$_[0]} ] + : ( ref $_[0] eq 'REF' and ref ${$_[0]} eq 'ARRAY' ) ? [ @${ $_[0] } ] + : ( + ref $_[0] eq 'HASH' and keys %{$_[0]} == 1 + and + defined $_[0]->{-ident} and ! length ref $_[0]->{-ident} + ) ? [ $_[0]->{-ident} ] + : undef; +} + +# FIXME XSify - this can be done so much more efficiently +sub is_plain_value ($) { + no strict 'refs'; + ! length ref $_[0] ? \($_[0]) + : ( + ref $_[0] eq 'HASH' and keys %{$_[0]} == 1 + and + exists $_[0]->{-value} + ) ? \($_[0]->{-value}) + : ( + # reuse @_ for even moar speedz + defined ( $_[1] = Scalar::Util::blessed $_[0] ) + and + # deliberately not using Devel::OverloadInfo - the checks we are + # intersted in are much more limited than the fullblown thing, and + # this is a very hot piece of code + ( + # simply using ->can('(""') can leave behind stub methods that + # break actually using the overload later (see L and the source of overload::mycan()) + # + # either has stringification which DBI SHOULD prefer out of the box + grep { *{ (qq[${_}::(""]) }{CODE} } @{ $_[2] = mro::get_linear_isa( $_[1] ) } + or + # has nummification or boolification, AND fallback is *not* disabled + ( + SQL::Abstract::_ENV_::DETECT_AUTOGENERATED_STRINGIFICATION + and + ( + grep { *{"${_}::(0+"}{CODE} } @{$_[2]} + or + grep { *{"${_}::(bool"}{CODE} } @{$_[2]} + ) + and + ( + # no fallback specified at all + ! ( ($_[3]) = grep { *{"${_}::()"}{CODE} } @{$_[2]} ) + or + # fallback explicitly undef + ! defined ${"$_[3]::()"} + or + # explicitly true + !! ${"$_[3]::()"} + ) + ) + ) + ) ? \($_[0]) + : undef; +} + + #====================================================================== # NEW @@ -667,8 +747,8 @@ sub _where_op_BOOL { sub _where_op_IDENT { my $self = shift; my ($op, $rhs) = splice @_, -2; - if (ref $rhs) { - puke "-$op takes a single scalar argument (a quotable identifier)"; + if (! defined $rhs or length ref $rhs) { + puke "-$op requires a single plain scalar argument (a quotable identifier)"; } # in case we are called as a top level special op (no '=') @@ -689,6 +769,14 @@ sub _where_op_VALUE { # in case we are called as a top level special op (no '=') my $lhs = shift; + # special-case NULL + if (! defined $rhs) { + return $lhs + ? $self->_convert($self->_quote($lhs)) . ' IS NULL' + : undef + ; + } + my @bind = $self->_bindtype ( ($lhs || $self->{_nested_func_lhs}), @@ -765,6 +853,11 @@ sub _where_hashpair_HASHREF { # so that -not_foo works correctly $op =~ s/^not_/NOT /i; + # another retarded special case: foo => { $op => { -value => undef } } + if (ref $val eq 'HASH' and keys %$val == 1 and exists $val->{-value} and ! defined $val->{-value} ) { + $val = undef; + } + my ($sql, @bind); # CASE: col-value logic modifiers @@ -805,7 +898,8 @@ sub _where_hashpair_HASHREF { UNDEF => sub { # CASE: col => {op => undef} : sql "IS (NOT)? NULL" my $is = - $op =~ $self->{equality_op} ? 'is' + $op =~ /^not$/i ? 'is not' # legacy + : $op =~ $self->{equality_op} ? 'is' : $op =~ $self->{like_op} ? belch("Supplying an undefined argument to '@{[ uc $op]}' is deprecated") && 'is' : $op =~ $self->{inequality_op} ? 'is not' : $op =~ $self->{not_like_op} ? belch("Supplying an undefined argument to '@{[ uc $op]}' is deprecated") && 'is not' @@ -1261,10 +1355,11 @@ sub _quote { else { puke "Unsupported quote_char format: $_[0]->{quote_char}"; } + my $esc = $_[0]->{escape_char} || $r; # parts containing * are naturally unquoted return join( $_[0]->{name_sep}||'', map - { $_ eq '*' ? $_ : $l . $_ . $r } + { $_ eq '*' ? $_ : do { (my $n = $_) =~ s/(\Q$esc\E|\Q$r\E)/$esc$1/g; $l . $n . $r } } ( $_[0]->{name_sep} ? split (/\Q$_[0]->{name_sep}\E/, $_[1] ) : $_[1] ) ); } @@ -1675,16 +1770,13 @@ Which you could then use in DBI code like so: Easy, eh? -=head1 FUNCTIONS +=head1 METHODS -The functions are simple. There's one for each major SQL operation, +The methods are simple. There's one for each major SQL operation, and a constructor you use first. The arguments are specified in a -similar order to each function (table, then fields, then a where +similar order to each method (table, then fields, then a where clause) to try and simplify things. - - - =head2 new(option => 'value') The C function takes a list of options and values, and returns @@ -1849,6 +1941,21 @@ that generates SQL like this: Quoting is useful if you have tables or columns names that are reserved words in your database's SQL dialect. +=item escape_char + +This is the character that will be used to escape Ls appearing +in an identifier before it has been quoted. + +The paramter default in case of a single L character is the quote +character itself. + +When opening-closing-style quoting is used (L is an arrayref) +this parameter defaults to the B L. Occurences +of the B L within the identifier are currently left +untouched. The default for opening-closing-style quotes may change in future +versions, thus you are B to specify the escape character +explicitly. + =item name_sep This is the character that separates a table and column name. It is @@ -2031,6 +2138,88 @@ Might give you: You get the idea. Strings get their case twiddled, but everything else remains verbatim. +=head1 EXPORTABLE FUNCTIONS + +=head2 is_plain_value + +Determines if the supplied argument is a plain value as understood by this +module: + +=over + +=item * The value is C + +=item * The value is a non-reference + +=item * The value is an object with stringification overloading + +=item * The value is of the form C<< { -value => $anything } >> + +=back + +On failure returns C, on sucess returns a B reference +to the original supplied argument. + +=over + +=item * Note + +The stringification overloading detection is rather advanced: it takes +into consideration not only the presence of a C<""> overload, but if that +fails also checks for enabled +L|overload/Magic Autogeneration>, based +on either C<0+> or C. + +Unfortunately testing in the field indicates that this +detection B<< may tickle a latent bug in perl versions before 5.018 >>, +but only when very large numbers of stringifying objects are involved. +At the time of writing ( Sep 2014 ) there is no clear explanation of +the direct cause, nor is there a manageably small test case that reliably +reproduces the problem. + +If you encounter any of the following exceptions in B - this module may be to blame: + + Operation "ne": no method found, + left argument in overloaded package , + right argument in overloaded package + +or perhaps even + + Stub found while resolving method "???" overloading """" in package + +If you fall victim to the above - please attempt to reduce the problem +to something that could be sent to the L +(either publicly or privately). As a workaround in the meantime you can +set C<$ENV{SQLA_ISVALUE_IGNORE_AUTOGENERATED_STRINGIFICATION}> to a true +value, which will most likely eliminate your problem (at the expense of +not being able to properly detect exotic forms of stringification). + +This notice and environment variable will be removed in a future version, +as soon as the underlying problem is found and a reliable workaround is +devised. + +=back + +=head2 is_literal_value + +Determines if the supplied argument is a literal value as understood by this +module: + +=over + +=item * C<\$sql_string> + +=item * C<\[ $sql_string, @bind_values ]> + +=item * C<< { -ident => $plain_defined_string } >> + +=back + +On failure returns C, on sucess returns an B reference +containing the unpacked version of the supplied literal SQL and bind values. + =head1 WHERE CLAUSES =head2 Introduction