X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?a=blobdiff_plain;f=lib%2FSQL%2FAbstract.pm;h=5dc55c7648321b886cd2c65dd4bf857ed1458520;hb=f0724630077f85e0127fc4cb51e108c484feac61;hp=08f6011689c6822ce84a6120263e035c21d51e7a;hpb=843a94b5a440baaea0b5cd7d751f8995a1657421;p=dbsrgits%2FSQL-Abstract.git diff --git a/lib/SQL/Abstract.pm b/lib/SQL/Abstract.pm index 08f6011..5dc55c7 100644 --- a/lib/SQL/Abstract.pm +++ b/lib/SQL/Abstract.pm @@ -27,7 +27,7 @@ BEGIN { # GLOBALS #====================================================================== -our $VERSION = '1.78'; +our $VERSION = '1.81_01'; # This would confuse some packagers $VERSION = eval $VERSION if $VERSION =~ /_/; # numify for warning-free dev releases @@ -78,11 +78,6 @@ sub puke (@) { 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; } @@ -232,7 +227,10 @@ sub insert { return wantarray ? ($sql, @bind) : $sql; } -sub _insert_returning { +# Used by DBIx::Class::SQLMaker->insert +sub _insert_returning { shift->_returning(@_) } + +sub _returning { my ($self, $options) = @_; my $f = $options->{returning}; @@ -266,13 +264,14 @@ sub _insert_ARRAYREF { # just generate values(?,?) part (no list of fields) $self->{bindtype} ne 'columns' or belch "can't do 'columns' bindtype when called with arrayref"; - # fold the list of values into a hash of column name - value pairs - # (where the column names are artificially generated, and their - # lexicographical ordering keep the ordering of the original list) - my $i = "a"; # incremented values will be in lexicographical order - my $data_in_hash = { map { ($i++ => $_) } @$data }; - - return $self->_insert_values($data_in_hash); + my (@values, @all_bind); + foreach my $value (@$data) { + my ($values, @bind) = $self->_insert_value(undef, $value); + push @values, $values; + push @all_bind, @bind; + } + my $sql = $self->_sqlcase('values')." ( ".join(", ", @values)." )"; + return ($sql, @all_bind); } sub _insert_ARRAYREFREF { # literal SQL with bind @@ -296,52 +295,60 @@ sub _insert_values { my (@values, @all_bind); foreach my $column (sort keys %$data) { - my $v = $data->{$column}; + my ($values, @bind) = $self->_insert_value($column, $data->{$column}); + push @values, $values; + push @all_bind, @bind; + } + my $sql = $self->_sqlcase('values')." ( ".join(", ", @values)." )"; + return ($sql, @all_bind); +} - $self->_SWITCH_refkind($v, { +sub _insert_value { + my ($self, $column, $v) = @_; - ARRAYREF => sub { - if ($self->{array_datatypes}) { # if array datatype are activated - push @values, '?'; - push @all_bind, $self->_bindtype($column, $v); - } - else { # else literal SQL with bind - my ($sql, @bind) = @$v; - $self->_assert_bindval_matches_bindtype(@bind); - push @values, $sql; - push @all_bind, @bind; - } - }, + my (@values, @all_bind); + $self->_SWITCH_refkind($v, { - ARRAYREFREF => sub { # literal SQL with bind - my ($sql, @bind) = @${$v}; + ARRAYREF => sub { + if ($self->{array_datatypes}) { # if array datatype are activated + push @values, '?'; + push @all_bind, $self->_bindtype($column, $v); + } + else { # else literal SQL with bind + my ($sql, @bind) = @$v; $self->_assert_bindval_matches_bindtype(@bind); push @values, $sql; push @all_bind, @bind; - }, + } + }, - # THINK : anything useful to do with a HASHREF ? - HASHREF => sub { # (nothing, but old SQLA passed it through) - #TODO in SQLA >= 2.0 it will die instead - belch "HASH ref as bind value in insert is not supported"; - push @values, '?'; - push @all_bind, $self->_bindtype($column, $v); - }, + ARRAYREFREF => sub { # literal SQL with bind + my ($sql, @bind) = @${$v}; + $self->_assert_bindval_matches_bindtype(@bind); + push @values, $sql; + push @all_bind, @bind; + }, - SCALARREF => sub { # literal SQL without bind - push @values, $$v; - }, + # THINK : anything useful to do with a HASHREF ? + HASHREF => sub { # (nothing, but old SQLA passed it through) + #TODO in SQLA >= 2.0 it will die instead + belch "HASH ref as bind value in insert is not supported"; + push @values, '?'; + push @all_bind, $self->_bindtype($column, $v); + }, - SCALAR_or_UNDEF => sub { - push @values, '?'; - push @all_bind, $self->_bindtype($column, $v); - }, + SCALARREF => sub { # literal SQL without bind + push @values, $$v; + }, - }); + SCALAR_or_UNDEF => sub { + push @values, '?'; + push @all_bind, $self->_bindtype($column, $v); + }, - } + }); - my $sql = $self->_sqlcase('values')." ( ".join(", ", @values)." )"; + my $sql = join(", ", @values); return ($sql, @all_bind); } @@ -353,10 +360,11 @@ sub _insert_values { sub update { - my $self = shift; - my $table = $self->_table(shift); - my $data = shift || return; - my $where = shift; + my $self = shift; + my $table = $self->_table(shift); + my $data = shift || return; + my $where = shift; + my $options = shift; # first build the 'SET' part of the sql statement my (@set, @all_bind); @@ -419,9 +427,16 @@ sub update { push @all_bind, @where_bind; } + if ($options->{returning}) { + my ($returning_sql, @returning_bind) = $self->_update_returning ($options); + $sql .= $returning_sql; + push @all_bind, @returning_bind; + } + return wantarray ? ($sql, @all_bind) : $sql; } +sub _update_returning { shift->_returning(@_) } @@ -482,7 +497,9 @@ sub where { # order by? if ($order) { - $sql .= $self->_order_by($order); + my ($order_sql, @order_bind) = $self->_order_by($order); + $sql .= $order_sql; + push @bind, @order_bind; } return wantarray ? ($sql, @bind) : $sql; @@ -497,9 +514,15 @@ sub _recurse_where { my ($sql, @bind) = $self->$method($where, $logic); - # DBIx::Class directly calls _recurse_where in scalar context, so - # we must implement it, even if not in the official API - return wantarray ? ($sql, @bind) : $sql; + # DBIx::Class used to call _recurse_where in scalar context + # something else might too... + if (wantarray) { + return ($sql, @bind); + } + else { + belch "Calling _recurse_where in scalar context is deprecated and will go away before 2.0"; + return $sql; + } } @@ -519,7 +542,10 @@ sub _where_ARRAYREF { my (@sql_clauses, @all_bind); # need to use while() so can shift() for pairs - while (my $el = shift @clauses) { + while (@clauses) { + my $el = shift @clauses; + + $el = undef if (defined $el and ! length $el); # switch according to kind of $el and get corresponding ($sql, @bind) my ($sql, @bind) = $self->_SWITCH_refkind($el, { @@ -537,10 +563,12 @@ sub _where_ARRAYREF { SCALARREF => sub { ($$el); }, - SCALAR => sub {# top-level arrayref with scalars, recurse in pairs - $self->_recurse_where({$el => shift(@clauses)})}, + SCALAR => sub { + # top-level arrayref with scalars, recurse in pairs + $self->_recurse_where({$el => shift(@clauses)}) + }, - UNDEF => sub {puke "not supported : UNDEF in arrayref" }, + UNDEF => sub {puke "Supplying an empty left hand side argument is not supported in array-pairs" }, }); if ($sql) { @@ -594,11 +622,20 @@ sub _where_HASHREF { $s = "($s)" unless ( List::Util::first {$op =~ $_->{regex}} @{$self->{unary_ops}} or - defined($self->{_nested_func_lhs}) && ($self->{_nested_func_lhs} eq $k) + ( defined $self->{_nested_func_lhs} and $self->{_nested_func_lhs} eq $k ) ); ($s, @b); } else { + if (! length $k) { + if (is_literal_value ($v) ) { + belch 'Hash-pairs consisting of an empty string with a literal are deprecated, and will be removed in 2.0: use -and => [ $literal ] instead'; + } + else { + puke "Supplying an empty left hand side argument is not supported in hash-pairs"; + } + } + my $method = $self->_METHOD_FOR_refkind("_where_hashpair", $v); $self->$method($k, $v); } @@ -614,6 +651,11 @@ sub _where_HASHREF { sub _where_unary_op { my ($self, $op, $rhs) = @_; + # top level special ops are illegal in general + # this includes the -ident/-value ops (dual purpose unary and special) + puke "Illegal use of top-level '-$op'" + if ! defined $self->{_nested_func_lhs} and List::Util::first {$op =~ $_->{regex}} @{$self->{special_ops}}; + if (my $op_entry = List::Util::first {$op =~ $_->{regex}} @{$self->{unary_ops}}) { my $handler = $op_entry->{handler}; @@ -638,8 +680,8 @@ sub _where_unary_op { my ($sql, @bind) = $self->_SWITCH_refkind ($rhs, { SCALAR => sub { - puke "Illegal use of top-level '$op'" - unless $self->{_nested_func_lhs}; + puke "Illegal use of top-level '-$op'" + unless defined $self->{_nested_func_lhs}; return ( $self->_convert('?'), @@ -771,7 +813,7 @@ sub _where_op_VALUE { # special-case NULL if (! defined $rhs) { - return $lhs + return defined $lhs ? $self->_convert($self->_quote($lhs)) . ' IS NULL' : undef ; @@ -779,7 +821,7 @@ sub _where_op_VALUE { my @bind = $self->_bindtype ( - ($lhs || $self->{_nested_func_lhs}), + ( defined $lhs ? $lhs : $self->{_nested_func_lhs} ), $rhs, ) ; @@ -830,7 +872,10 @@ sub _where_hashpair_HASHREF { my ($self, $k, $v, $logic) = @_; $logic ||= 'and'; - local $self->{_nested_func_lhs} = $self->{_nested_func_lhs}; + local $self->{_nested_func_lhs} = defined $self->{_nested_func_lhs} + ? $self->{_nested_func_lhs} + : $k + ; my ($all_sql, @all_bind); @@ -909,10 +954,6 @@ sub _where_hashpair_HASHREF { }, FALLBACK => sub { # CASE: col => {op/func => $stuff} - - # retain for proper column type bind - $self->{_nested_func_lhs} ||= $k; - ($sql, @bind) = $self->_where_unary_op ($op, $val); $sql = join (' ', @@ -1108,7 +1149,6 @@ sub _where_field_BETWEEN { my ($func, $arg, @rest) = %$val; puke ("Only simple { -func => arg } functions accepted as sub-arguments to BETWEEN") if (@rest or $func !~ /^ \- (.+)/x); - local $self->{_nested_func_lhs} = $k; $self->_where_unary_op ($1 => $arg); }, FALLBACK => sub { @@ -1166,7 +1206,6 @@ sub _where_field_IN { my ($func, $arg, @rest) = %$val; puke ("Only simple { -func => arg } functions accepted as sub-arguments to IN") if (@rest or $func !~ /^ \- (.+)/x); - local $self->{_nested_func_lhs} = $k; $self->_where_unary_op ($1 => $arg); }, UNDEF => sub { @@ -1225,8 +1264,29 @@ sub _where_field_IN { # adding them back in the corresponding method sub _open_outer_paren { my ($self, $sql) = @_; - $sql = $1 while $sql =~ /^ \s* \( (.*) \) \s* $/xs; - return $sql; + + while ( my ($inner) = $sql =~ /^ \s* \( (.*) \) \s* $/xs ) { + + # there are closing parens inside, need the heavy duty machinery + # to reevaluate the extraction starting from $sql (full reevaluation) + if ( $inner =~ /\)/ ) { + require Text::Balanced; + + my (undef, $remainder) = do { + # idiotic design - writes to $@ but *DOES NOT* throw exceptions + local $@; + Text::Balanced::extract_bracketed( $sql, '()', qr/\s*/ ); + }; + + # the entire expression needs to be a balanced bracketed thing + # (after an extract no remainder sans trailing space) + last if defined $remainder and $remainder =~ /\S/; + } + + $sql = $inner; + } + + $sql; } @@ -1339,34 +1399,27 @@ sub _quote { return '' unless defined $_[1]; return ${$_[1]} if ref($_[1]) eq 'SCALAR'; - unless ($_[0]->{quote_char}) { - $_[0]->_assert_pass_injection_guard($_[1]); - return $_[1]; - } + $_[0]->{quote_char} or + ($_[0]->_assert_pass_injection_guard($_[1]), return $_[1]); my $qref = ref $_[0]->{quote_char}; - my ($l, $r); - if (!$qref) { - ($l, $r) = ( $_[0]->{quote_char}, $_[0]->{quote_char} ); - } - elsif ($qref eq 'ARRAY') { - ($l, $r) = @{$_[0]->{quote_char}}; - } - else { - puke "Unsupported quote_char format: $_[0]->{quote_char}"; - } + my ($l, $r) = + !$qref ? ($_[0]->{quote_char}, $_[0]->{quote_char}) + : ($qref eq 'ARRAY') ? @{$_[0]->{quote_char}} + : 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 '*' ? $_ : do { (my $n = $_) =~ s/(\Q$esc\E|\Q$r\E)/$esc$1/g; $l . $n . $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] ) ); } # Conversion, if applicable -sub _convert ($) { +sub _convert { #my ($self, $arg) = @_; if ($_[0]->{convert}) { return $_[0]->_sqlcase($_[0]->{convert}) .'(' . $_[1] . ')'; @@ -1375,7 +1428,7 @@ sub _convert ($) { } # And bindtype -sub _bindtype (@) { +sub _bindtype { #my ($self, $col, @vals) = @_; # called often - tighten code return $_[0]->{bindtype} eq 'columns' @@ -1624,7 +1677,7 @@ SQL::Abstract - Generate SQL from Perl data structures my $sql = SQL::Abstract->new; - my($stmt, @bind) = $sql->select($source, \@fields, \%where, \@order); + my($stmt, @bind) = $sql->select($source, \@fields, \%where, $order); my($stmt, @bind) = $sql->insert($table, \%fieldvals || \@values); @@ -1637,7 +1690,7 @@ SQL::Abstract - Generate SQL from Perl data structures $sth->execute(@bind); # Just generate the WHERE clause - my($stmt, @bind) = $sql->where(\%where, \@order); + my($stmt, @bind) = $sql->where(\%where, $order); # Return values in the same order, for hashed queries # See PERFORMANCE section for more details @@ -1718,7 +1771,7 @@ say something like this: my %data = ( name => 'Bill', - date_entered => \["to_date(?,'MM/DD/YYYY')", "03/02/2003"], + date_entered => \[ "to_date(?,'MM/DD/YYYY')", "03/02/2003" ], ); The first value in the array is the actual SQL. Any other values are @@ -1772,9 +1825,9 @@ Easy, eh? =head1 METHODS -The methods are simple. There's one for each major SQL operation, +The methods are simple. There's one for every major SQL operation, and a constructor you use first. The arguments are specified in a -similar order to each method (table, then fields, then a where +similar order for each method (table, then fields, then a where clause) to try and simplify things. =head2 new(option => 'value') @@ -1919,7 +1972,7 @@ are or are not included. You could wrap that above C loop in a simple sub called C or something and reuse it repeatedly. You still get a layer of abstraction over manual SQL specification. -Note that if you set L to C, the C<\[$sql, @bind]> +Note that if you set L to C, the C<\[ $sql, @bind ]> construct (see L) will expect the bind values in this format. @@ -1946,7 +1999,7 @@ words in your database's SQL dialect. 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 +The parameter default in case of a single L character is the quote character itself. When opening-closing-style quoting is used (L is an arrayref) @@ -2032,7 +2085,7 @@ be supported by all database engines. =back -=head2 update($table, \%fieldvals, \%where) +=head2 update($table, \%fieldvals, \%where, \%options) This takes a table, hashref of field/value pairs, and an optional hashref L. It returns an SQL UPDATE function and a list @@ -2041,6 +2094,19 @@ See the sections on L and L for information on how to insert with those data types. +The optional C<\%options> hash reference may contain additional +options to generate the update SQL. Currently supported options +are: + +=over 4 + +=item returning + +See the C option to +L. + +=back + =head2 select($source, $fields, $where, $order) This returns a SQL SELECT statement and associated list of bind values, as @@ -2089,7 +2155,7 @@ for details. This takes a table name and optional hashref L. It returns an SQL DELETE statement and list of bind values. -=head2 where(\%where, \@order) +=head2 where(\%where, $order) This is used to generate just the WHERE clause. For example, if you have an arbitrary data structure and know what the @@ -2190,7 +2256,7 @@ or perhaps even If you fall victim to the above - please attempt to reduce the problem to something that could be sent to the L +|DBIx::Class/GETTING HELP/SUPPORT> (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 @@ -2213,8 +2279,6 @@ module: =item * C<\[ $sql_string, @bind_values ]> -=item * C<< { -ident => $plain_defined_string } >> - =back On failure returns C, on sucess returns an B reference @@ -2344,7 +2408,7 @@ Which would generate: @bind = ('2', '5', 'nwiger'); If you want to include literal SQL (with or without bind values), just use a -scalar reference or array reference as the value: +scalar reference or reference to an arrayref as the value: my %where = ( date_entered => { '>' => \["to_date(?, 'MM/DD/YYYY')", "11/26/2008"] }, @@ -2353,7 +2417,7 @@ scalar reference or array reference as the value: Which would generate: - $stmt = "WHERE date_entered > "to_date(?, 'MM/DD/YYYY') AND date_expires < now()"; + $stmt = "WHERE date_entered > to_date(?, 'MM/DD/YYYY') AND date_expires < now()"; @bind = ('11/26/2008'); @@ -2367,7 +2431,7 @@ this (notice the C): Because, in Perl you I do this: - priority => { '!=', 2, '!=', 1 } + priority => { '!=' => 2, '!=' => 1 } As the second C key will obliterate the first. The solution is to use the special C<-modifier> form inside an arrayref: @@ -2559,10 +2623,10 @@ to change the logic inside : That would yield: - WHERE ( user = ? AND ( - ( workhrs > ? AND geo = ? ) - OR ( workhrs < ? OR geo = ? ) - ) ) + $stmt = "WHERE ( user = ? + AND ( ( workhrs > ? AND geo = ? ) + OR ( workhrs < ? OR geo = ? ) ) )"; + @bind = ('nwiger', '20', 'ASIA', '50', 'EURO'); =head3 Algebraic inconsistency, for historical reasons @@ -2587,10 +2651,16 @@ 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'}]} + { col => [ -and => + { -like => 'foo%' }, + { -like => '%bar' }, + ] } # yields : WHERE ( ( col LIKE ? AND col LIKE ? ) ) - [-and => {col => {-like => 'foo%'}, {col => {-like => '%bar'}}]] + [ -and => + { col => { -like => 'foo%' } }, + { col => { -like => '%bar' } }, + ] # yields : WHERE ( ( col LIKE ? OR col LIKE ? ) ) @@ -2683,7 +2753,7 @@ not so common, but perfectly legal Perl). For example, to find a date in Postgres you can use something like this: my %where = ( - date_column => \[q/= date '2008-09-30' - ?::integer/, 10/] + date_column => \[ "= date '2008-09-30' - ?::integer", 10 ] ) This would create: @@ -2692,15 +2762,16 @@ This would create: @bind = ('10'); Note that you must pass the bind values in the same format as they are returned -by L. That means that if you set L to C, you must -provide the bind values in the C<< [ column_meta => value ] >> format, where -C is an opaque scalar value; most commonly the column name, but -you can use any scalar value (including references and blessed references), -L will simply pass it through intact. So if C is set -to C the above example will look like: +by L. This means that if you set L +to C, you must provide the bind values in the +C<< [ column_meta => value ] >> format, where C is an opaque +scalar value; most commonly the column name, but you can use any scalar value +(including references and blessed references), L will simply +pass it through intact. So if C is set to C the above +example will look like: my %where = ( - date_column => \[q/= date '2008-09-30' - ?::integer/, [ dummy => 10 ]/] + date_column => \[ "= date '2008-09-30' - ?::integer", [ {} => 10 ] ] ) Literal SQL is especially useful for nesting parenthesized clauses in the @@ -2833,13 +2904,12 @@ script. =head1 ORDER BY CLAUSES 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: +column name), a hashref of C<< { -desc => 'col' } >> or C<< { -asc => 'col' } +>>, a scalarref, an arrayref-ref, or an arrayref of any of the previous +forms. Examples: Given | Will Generate - ---------------------------------------------------------- - | - \'colA DESC' | ORDER BY colA DESC + --------------------------------------------------------------- | 'colA' | ORDER BY colA | @@ -2853,12 +2923,19 @@ or an array of either of the two previous forms. Examples: | { -asc => [qw/colA colB/] } | ORDER BY colA ASC, colB ASC | + \'colA DESC' | ORDER BY colA DESC + | + \[ 'FUNC(colA, ?)', $x ] | ORDER BY FUNC(colA, ?) + | /* ...with $x bound to ? */ + | [ | { -asc => 'colA' }, | ORDER BY colA ASC, colB DESC, - { -desc => [qw/colB/], | colC ASC, colD ASC - { -asc => [qw/colC colD/],| + { -desc => [qw/colB/], | colC ASC, colD ASC, + { -asc => [qw/colC colD/],| colE DESC, FUNC(colF, ?) + \'colE DESC', | /* ...with $x bound to ? */ + \[ 'FUNC(colF, ?)', $x ], | ] | - =========================================================== + =============================================================== @@ -2907,14 +2984,14 @@ 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: +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 + $op is the part that matched the handler regex $arg is the RHS When supplied with a coderef, it is called as: @@ -2983,7 +3060,7 @@ Either a coderef or a plain scalar method name. In both cases the expected return is C<< $sql >>. When supplied with a method name, it is simply called on the -L object as: +L object as: $self->$method_name ($op, $arg) @@ -3064,13 +3141,27 @@ a fast interface to returning and formatting data. I frequently use these three modules together to write complex database query apps in under 50 lines. -=head1 REPO +=head1 HOW TO CONTRIBUTE + +Contributions are always welcome, in all usable forms (we especially +welcome documentation improvements). The delivery methods include git- +or unified-diff formatted patches, GitHub pull requests, or plain bug +reports either via RT or the Mailing list. Contributors are generally +granted full access to the official repository after their first several +patches pass successful review. + +This project is maintained in a git repository. The code and related tools are +accessible at the following locations: =over -=item * gitweb: L +=item * Official repo: L + +=item * Official gitweb: L + +=item * GitHub mirror: L -=item * git: L +=item * Authorized committers: L =back @@ -3091,7 +3182,7 @@ The main changes are : =item * -support for literal SQL through the C<< \ [$sql, bind] >> syntax. +support for literal SQL through the C<< \ [ $sql, @bind ] >> syntax. =item *