X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?a=blobdiff_plain;f=lib%2FSQL%2FAbstract.pm;h=90434b8e083780de145b6b0db20de91c6630630a;hb=474e33354b67e58e0bb4fa532adb3c01b148f36e;hp=eb660db4ce7c3b4cbd9da7315b469eb7d9c70044;hpb=44b9e5020c2bf5ae465aa7ddb760d9fd5a67f638;p=scpubgit%2FQ-Branch.git diff --git a/lib/SQL/Abstract.pm b/lib/SQL/Abstract.pm index eb660db..90434b8 100644 --- a/lib/SQL/Abstract.pm +++ b/lib/SQL/Abstract.pm @@ -15,7 +15,7 @@ use Scalar::Util qw/blessed/; # GLOBALS #====================================================================== -our $VERSION = '1.49_01'; +our $VERSION = '1.49_04'; $VERSION = eval $VERSION; # numify for warning-free dev releases @@ -110,18 +110,7 @@ sub _insert_HASHREF { # explicit list of fields and then values my @fields = sort keys %$data; - my ($sql, @bind); - { # get values (need temporary override of bindtype to avoid an error) - local $self->{bindtype} = 'normal'; - ($sql, @bind) = $self->_insert_ARRAYREF([@{$data}{@fields}]); - } - - # if necessary, transform values according to 'bindtype' - if ($self->{bindtype} eq 'columns') { - for my $i (0 .. $#fields) { - ($bind[$i]) = $self->_bindtype($fields[$i], $bind[$i]); - } - } + my ($sql, @bind) = $self->_insert_values($data); # assemble SQL $_ = $self->_quote($_) foreach @fields; @@ -137,18 +126,48 @@ 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); +} + +sub _insert_ARRAYREFREF { # literal SQL with bind + my ($self, $data) = @_; + + my ($sql, @bind) = @${$data}; + $self->_assert_bindval_matches_bindtype(@bind); + + return ($sql, @bind); +} + + +sub _insert_SCALARREF { # literal SQL without bind + my ($self, $data) = @_; + + return ($$data); +} + +sub _insert_values { + my ($self, $data) = @_; + my (@values, @all_bind); - for my $v (@$data) { + foreach my $column (sort keys %$data) { + my $v = $data->{$column}; $self->_SWITCH_refkind($v, { ARRAYREF => sub { if ($self->{array_datatypes}) { # if array datatype are activated push @values, '?'; - push @all_bind, $v; + 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; } @@ -156,11 +175,18 @@ sub _insert_ARRAYREF { # just generate values(?,?) part (no list of fields) ARRAYREFREF => sub { # 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); + }, SCALARREF => sub { # literal SQL without bind push @values, $$v; @@ -168,7 +194,7 @@ sub _insert_ARRAYREF { # just generate values(?,?) part (no list of fields) SCALAR_or_UNDEF => sub { push @values, '?'; - push @all_bind, $v; + push @all_bind, $self->_bindtype($column, $v); }, }); @@ -180,19 +206,6 @@ sub _insert_ARRAYREF { # just generate values(?,?) part (no list of fields) } -sub _insert_ARRAYREFREF { # literal SQL with bind - my ($self, $data) = @_; - return @${$data}; -} - - -sub _insert_SCALARREF { # literal SQL without bind - my ($self, $data) = @_; - - return ($$data); -} - - #====================================================================== # UPDATE methods @@ -223,14 +236,16 @@ sub update { } else { # literal SQL with bind my ($sql, @bind) = @$v; + $self->_assert_bindval_matches_bindtype(@bind); push @set, "$label = $sql"; - push @all_bind, $self->_bindtype($k, @bind); + push @all_bind, @bind; } }, ARRAYREFREF => sub { # literal SQL with bind my ($sql, @bind) = @${$v}; + $self->_assert_bindval_matches_bindtype(@bind); push @set, "$label = $sql"; - push @all_bind, $self->_bindtype($k, @bind); + push @all_bind, @bind; }, SCALARREF => sub { # literal SQL without bind push @set, "$label = $$v"; @@ -371,6 +386,8 @@ sub _where_ARRAYREF { # skip empty elements, otherwise get invalid trailing AND stuff ARRAYREF => sub {$self->_recurse_where($el) if @$el}, + ARRAYREFREF => sub { @{${$el}} if @{${$el}}}, + HASHREF => sub {$self->_recurse_where($el, 'and') if %$el}, # LDNOTE : previous SQLA code for hashrefs was creating a dirty # side-effect: the first hashref within an array would change @@ -395,7 +412,16 @@ sub _where_ARRAYREF { return $self->_join_sql_clauses($logic, \@sql_clauses, \@all_bind); } +#====================================================================== +# WHERE: top-level ARRAYREFREF +#====================================================================== + +sub _where_ARRAYREFREF { + my ($self, $where) = @_; + my ($sql, @bind) = @{${$where}}; + return ($sql, @bind); +} #====================================================================== # WHERE: top-level HASHREF @@ -530,14 +556,15 @@ sub _where_hashpair_HASHREF { ($sql, @bind) = $self->_where_field_op_ARRAYREF($k, $op, $val); }, - SCALARREF => sub { # CASE: col => {op => \$scalar} + SCALARREF => sub { # CASE: col => {op => \$scalar} (literal SQL without bind) $sql = join ' ', $self->_convert($self->_quote($k)), $self->_sqlcase($op), $$val; }, - ARRAYREFREF => sub { # CASE: col => {op => \[$sql, @bind]} + ARRAYREFREF => sub { # CASE: col => {op => \[$sql, @bind]} (literal SQL with bind) my ($sub_sql, @sub_bind) = @$$val; + $self->_assert_bindval_matches_bindtype(@sub_bind); $sql = join ' ', $self->_convert($self->_quote($k)), $self->_sqlcase($op), $sub_sql; @@ -607,15 +634,17 @@ sub _where_hashpair_SCALARREF { return ($sql); } +# literal SQL with bind sub _where_hashpair_ARRAYREFREF { my ($self, $k, $v) = @_; $self->_debug("REF($k) means literal SQL: @${$v}"); my ($sql, @bind) = @${$v}; + $self->_assert_bindval_matches_bindtype(@bind); $sql = $self->_quote($k) . " " . $sql; - @bind = $self->_bindtype($k, @bind); return ($sql, @bind ); } +# literal SQL without bind sub _where_hashpair_SCALAR { my ($self, $k, $v) = @_; $self->_debug("NOREF($k) means simple key=val: $k $self->{cmp} $v"); @@ -691,25 +720,37 @@ sub _where_field_IN { # backwards compatibility : if scalar, force into an arrayref $vals = [$vals] if defined $vals && ! ref $vals; - ref $vals eq 'ARRAY' - or puke "special op 'in' requires an arrayref"; - my ($label) = $self->_convert($self->_quote($k)); my ($placeholder) = $self->_convert('?'); - my $and = $self->_sqlcase('and'); $op = $self->_sqlcase($op); - if (@$vals) { # nonempty list - my $placeholders = join ", ", (($placeholder) x @$vals); - my $sql = "$label $op ( $placeholders )"; - my @bind = $self->_bindtype($k, @$vals); + my ($sql, @bind) = $self->_SWITCH_refkind($vals, { + ARRAYREF => sub { # list of choices + if (@$vals) { # nonempty list + my $placeholders = join ", ", (($placeholder) x @$vals); + my $sql = "$label $op ( $placeholders )"; + my @bind = $self->_bindtype($k, @$vals); - return ($sql, @bind); - } - else { # empty list : some databases won't understand "IN ()", so DWIM - my $sql = ($op =~ /\bnot\b/i) ? $self->{sqltrue} : $self->{sqlfalse}; - return ($sql); - } + return ($sql, @bind); + } + else { # empty list : some databases won't understand "IN ()", so DWIM + my $sql = ($op =~ /\bnot\b/i) ? $self->{sqltrue} : $self->{sqlfalse}; + return ($sql); + } + }, + + ARRAYREFREF => sub { # literal SQL with bind + my ($sql, @bind) = @$$vals; + $self->_assert_bindval_matches_bindtype(@bind); + return ("$label $op ( $sql )", @bind); + }, + + FALLBACK => sub { + puke "special op 'in' requires an arrayref (or arrayref-ref)"; + }, + }); + + return ($sql, @bind); } @@ -852,6 +893,20 @@ sub _bindtype (@) { return $self->{bindtype} eq 'columns' ? map {[$col, $_]} @vals : @vals; } +# Dies if any element of @bind is not in [colname => value] format +# if bindtype is 'columns'. +sub _assert_bindval_matches_bindtype { + my ($self, @bind) = @_; + + if ($self->{bindtype} eq 'columns') { + foreach my $val (@bind) { + if (!defined $val || ref($val) ne 'ARRAY' || @$val != 2) { + die "bindtype 'columns' selected, you need to pass: [column_name => bind_value]" + } + } + } +} + sub _join_sql_clauses { my ($self, $logic, $clauses_aref, $bind_aref) = @_; @@ -946,7 +1001,35 @@ sub values { my $data = shift || return; puke "Argument to ", __PACKAGE__, "->values must be a \\%hash" unless ref $data eq 'HASH'; - return map { $self->_bindtype($_, $data->{$_}) } sort keys %$data; + + my @all_bind; + foreach my $k ( sort keys %$data ) { + my $v = $data->{$k}; + $self->_SWITCH_refkind($v, { + ARRAYREF => sub { + if ($self->{array_datatypes}) { # array datatype + push @all_bind, $self->_bindtype($k, $v); + } + else { # literal SQL with bind + my ($sql, @bind) = @$v; + $self->_assert_bindval_matches_bindtype(@bind); + push @all_bind, @bind; + } + }, + ARRAYREFREF => sub { # literal SQL with bind + my ($sql, @bind) = @${$v}; + $self->_assert_bindval_matches_bindtype(@bind); + push @all_bind, @bind; + }, + SCALARREF => sub { # literal SQL without bind + }, + SCALAR_or_UNDEF => sub { + push @all_bind, $self->_bindtype($k, $v); + }, + }); + } + + return @all_bind; } sub generate { @@ -962,13 +1045,13 @@ sub generate { my $r = ref $v; my $label = $self->_quote($k); if ($r eq 'ARRAY') { - # SQL included for values - my @bind = @$v; - my $sql = shift @bind; + # literal SQL with bind + my ($sql, @bind) = @$v; + $self->_assert_bindval_matches_bindtype(@bind); push @sqlq, "$label = $sql"; - push @sqlv, $self->_bindtype($k, @bind); + push @sqlv, @bind; } elsif ($r eq 'SCALAR') { - # embedded literal SQL + # literal SQL without bind push @sqlq, "$label = $$v"; } else { push @sqlq, "$label = ?"; @@ -980,11 +1063,12 @@ sub generate { # unlike insert(), assume these are ONLY the column names, i.e. for SQL for my $v (@$_) { my $r = ref $v; - if ($r eq 'ARRAY') { - my @val = @$v; - push @sqlq, shift @val; - push @sqlv, @val; - } elsif ($r eq 'SCALAR') { + if ($r eq 'ARRAY') { # literal SQL with bind + my ($sql, @bind) = @$v; + $self->_assert_bindval_matches_bindtype(@bind); + push @sqlq, $sql; + push @sqlv, @bind; + } elsif ($r eq 'SCALAR') { # literal SQL without bind # embedded literal SQL push @sqlq, $$v; } else { @@ -1337,6 +1421,10 @@ 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]> +construct (see L) +will expect the bind values in this format. + =item quote_char This is the character that a table or column name will be quoted @@ -1830,6 +1918,17 @@ This would create: $stmt = "WHERE ( date_column = date '2008-09-30' - ?::integer )" @bind = ('10'); +Note that you must pass the bind values in the same format as they are returned +by C. 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 scalar value (including references and blessed +references), L will simply pass it through intact. So eg. the +above example will look like: + + my %where = ( + date_column => \[q/= date '2008-09-30' - ?::integer/, [ dummy => 10 ]/] + ) Literal SQL is especially useful for nesting parenthesized clauses in the main SQL query. Here is a first example : @@ -2098,6 +2197,10 @@ support for the { operator => \"..." } construct (to embed literal SQL) =item * +support for the { operator => \["...", @bind] } construct (to embed literal SQL with bind values) + +=item * + added -nest1, -nest2 or -nest_1, -nest_2, ... =item * @@ -2160,6 +2263,7 @@ so I have no idea who they are! But the people I do know are: Dan Kubb (support for "quote_char" and "name_sep") 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) Thanks!