X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?a=blobdiff_plain;f=lib%2FDBIx%2FClass%2FStorage%2FDBI.pm;h=846deccaaa28663f824c4e011458410a69f7e2d5;hb=0ce4ab92f5cb558e0f145eee2e52b8e213092045;hp=e11a8e969d4e21760fc0ee0bf334ef02a87696e6;hpb=8de09adb1fed7f0e15890cfa423a7e2a5643478e;p=dbsrgits%2FDBIx-Class-Historic.git diff --git a/lib/DBIx/Class/Storage/DBI.pm b/lib/DBIx/Class/Storage/DBI.pm index e11a8e9..846decc 100644 --- a/lib/DBIx/Class/Storage/DBI.pm +++ b/lib/DBIx/Class/Storage/DBI.pm @@ -14,6 +14,7 @@ use DBIx::Class::Storage::Statistics; use Scalar::Util(); use List::Util(); use Data::Dumper::Concise(); +use Sub::Name (); # what version of sqlt do we require if deploy() without a ddl_dir is invoked # when changing also adjust the corresponding author_require in Makefile.PL @@ -63,7 +64,7 @@ for my $meth (@rdbms_specific_methods) { no strict qw/refs/; no warnings qw/redefine/; - *{__PACKAGE__ ."::$meth"} = sub { + *{__PACKAGE__ ."::$meth"} = Sub::Name::subname $meth => sub { if (not $_[0]->_driver_determined) { $_[0]->_determine_driver; goto $_[0]->can($meth); @@ -450,13 +451,50 @@ L =cut sub connect_info { - my ($self, $info_arg) = @_; + my ($self, $info) = @_; - return $self->_connect_info if !$info_arg; + return $self->_connect_info if !$info; - my @args = @$info_arg; # take a shallow copy for further mutilation - $self->_connect_info([@args]); # copy for _connect_info + $self->_connect_info($info); # copy for _connect_info + + $info = $self->_normalize_connect_info($info) + if ref $info eq 'ARRAY'; + + for my $storage_opt (keys %{ $info->{storage_options} }) { + my $value = $info->{storage_options}{$storage_opt}; + + $self->$storage_opt($value); + } + + # Kill sql_maker/_sql_maker_opts, so we get a fresh one with only + # the new set of options + $self->_sql_maker(undef); + $self->_sql_maker_opts({}); + + for my $sql_maker_opt (keys %{ $info->{sql_maker_options} }) { + my $value = $info->{sql_maker_options}{$sql_maker_opt}; + $self->_sql_maker_opts->{$sql_maker_opt} = $value; + } + + my %attrs = ( + %{ $self->_default_dbi_connect_attributes || {} }, + %{ $info->{attributes} || {} }, + ); + + my @args = @{ $info->{arguments} }; + + $self->_dbi_connect_info([@args, + %attrs && !(ref $args[0] eq 'CODE') ? \%attrs : ()]); + + return $self->_connect_info; +} + +sub _normalize_connect_info { + my ($self, $info_arg) = @_; + my %info; + + my @args = @$info_arg; # take a shallow copy for further mutilation # combine/pre-parse arguments depending on invocation style @@ -493,36 +531,23 @@ sub connect_info { @args = @args[0,1,2]; } - # Kill sql_maker/_sql_maker_opts, so we get a fresh one with only - # the new set of options - $self->_sql_maker(undef); - $self->_sql_maker_opts({}); + $info{arguments} = \@args; - if(keys %attrs) { - for my $storage_opt (@storage_options, 'cursor_class') { # @storage_options is declared at the top of the module - if(my $value = delete $attrs{$storage_opt}) { - $self->$storage_opt($value); - } - } - for my $sql_maker_opt (qw/limit_dialect quote_char name_sep/) { - if(my $opt_val = delete $attrs{$sql_maker_opt}) { - $self->_sql_maker_opts->{$sql_maker_opt} = $opt_val; - } - } - } + my @storage_opts = grep exists $attrs{$_}, + @storage_options, 'cursor_class'; - if (ref $args[0] eq 'CODE') { - # _connect() never looks past $args[0] in this case - %attrs = () - } else { - %attrs = ( - %{ $self->_default_dbi_connect_attributes || {} }, - %attrs, - ); - } + @{ $info{storage_options} }{@storage_opts} = + delete @attrs{@storage_opts} if @storage_opts; + + my @sql_maker_opts = grep exists $attrs{$_}, + qw/limit_dialect quote_char name_sep/; - $self->_dbi_connect_info([@args, keys %attrs ? \%attrs : ()]); - $self->_connect_info; + @{ $info{sql_maker_options} }{@sql_maker_opts} = + delete @attrs{@sql_maker_opts} if @sql_maker_opts; + + $info{attributes} = \%attrs if %attrs; + + return \%info; } sub _default_dbi_connect_attributes { @@ -1359,7 +1384,6 @@ sub insert { return $updated_cols; } -## Still not quite perfect, and EXPERIMENTAL ## Currently it is assumed that all values passed will be "normal", i.e. not ## scalar refs, or at least, all the same type as the first set, the statement is ## only prepped once. @@ -1565,62 +1589,6 @@ sub delete { return $self->_execute('delete' => [], $source, $bind_attrs, @args); } -# Most databases do not allow aliasing of tables in UPDATE/DELETE. Thus -# a condition containing 'me' or other table prefixes will not work -# at all. What this code tries to do (badly) is introspect the condition -# and remove all column qualifiers. If it bails out early (returns undef) -# the calling code should try another approach (e.g. a subquery) -sub _strip_cond_qualifiers { - my ($self, $where) = @_; - - my $cond = {}; - - # No-op. No condition, we're updating/deleting everything - return $cond unless $where; - - if (ref $where eq 'ARRAY') { - $cond = [ - map { - my %hash; - foreach my $key (keys %{$_}) { - $key =~ /([^.]+)$/; - $hash{$1} = $_->{$key}; - } - \%hash; - } @$where - ]; - } - elsif (ref $where eq 'HASH') { - if ( (keys %$where) == 1 && ( (keys %{$where})[0] eq '-and' )) { - $cond->{-and} = []; - my @cond = @{$where->{-and}}; - for (my $i = 0; $i < @cond; $i++) { - my $entry = $cond[$i]; - my $hash; - if (ref $entry eq 'HASH') { - $hash = $self->_strip_cond_qualifiers($entry); - } - else { - $entry =~ /([^.]+)$/; - $hash->{$1} = $cond[++$i]; - } - push @{$cond->{-and}}, $hash; - } - } - else { - foreach my $key (keys %$where) { - $key =~ /([^.]+)$/; - $cond->{$1} = $where->{$key}; - } - } - } - else { - return undef; - } - - return $cond; -} - # We were sent here because the $rs contains a complex search # which will require a subquery to select the correct rows # (i.e. joined or limited resultsets, or non-introspectable conditions) @@ -1634,7 +1602,29 @@ sub _subq_update_delete { my $rsrc = $rs->result_source; + # quick check if we got a sane rs on our hands my @pcols = $rsrc->primary_columns; + unless (@pcols) { + $self->throw_exception ( + sprintf ( + "You must declare primary key(s) on source '%s' (via set_primary_key) in order to update or delete complex resultsets", + $rsrc->source_name || $rsrc->from + ) + ); + } + + my $sel = $rs->_resolved_attrs->{select}; + $sel = [ $sel ] unless ref $sel eq 'ARRAY'; + + if ( + join ("\x00", map { join '.', $rs->{attrs}{alias}, $_ } sort @pcols) + ne + join ("\x00", sort @$sel ) + ) { + $self->throw_exception ( + '_subq_update_delete can not be called on resultsets selecting columns other than the primary keys' + ); + } if (@pcols == 1) { return $self->$op ( @@ -1801,17 +1791,69 @@ sub _select_args { my @limit; - # see if we need to tear the prefetch apart (either limited has_many or grouped prefetch) - # otherwise delegate the limiting to the storage, unless software limit was requested + # see if we need to tear the prefetch apart otherwise delegate the limiting to the + # storage, unless software limit was requested if ( + #limited has_many ( $attrs->{rows} && keys %{$attrs->{collapse}} ) || - ( $attrs->{group_by} && @{$attrs->{group_by}} && - $attrs->{_prefetch_select} && @{$attrs->{_prefetch_select}} ) + # limited prefetch with RNO subqueries + ( + $attrs->{rows} + && + $sql_maker->limit_dialect eq 'RowNumberOver' + && + $attrs->{_prefetch_select} + && + @{$attrs->{_prefetch_select}} + ) + || + # grouped prefetch + ( $attrs->{group_by} + && + @{$attrs->{group_by}} + && + $attrs->{_prefetch_select} + && + @{$attrs->{_prefetch_select}} + ) ) { ($ident, $select, $where, $attrs) = $self->_adjust_select_args_for_complex_prefetch ($ident, $select, $where, $attrs); } + + elsif ( + ($attrs->{rows} || $attrs->{offset}) + && + $sql_maker->limit_dialect eq 'RowNumberOver' + && + (ref $ident eq 'ARRAY' && @$ident > 1) # indicates a join + && + scalar $sql_maker->_order_by_chunks ($attrs->{order_by}) + ) { + # the RNO limit dialect above mangles the SQL such that the join gets lost + # wrap a subquery here + + push @limit, delete @{$attrs}{qw/rows offset/}; + + my $subq = $self->_select_args_to_query ( + $ident, + $select, + $where, + $attrs, + ); + + $ident = { + -alias => $attrs->{alias}, + -source_handle => $ident->[0]{-source_handle}, + $attrs->{alias} => $subq, + }; + + # all part of the subquery now + delete @{$attrs}{qw/order_by group_by having/}; + $where = undef; + } + elsif (! $attrs->{software_limit} ) { push @limit, $attrs->{rows}, $attrs->{offset}; } @@ -2033,7 +2075,7 @@ sub last_insert_id { This API is B, will almost definitely change in the future, and currently only used by L<::AutoCast|DBIx::Class::Storage::DBI::AutoCast> and -L<::Sybase|DBIx::Class::Storage::DBI::Sybase>. +L<::Sybase::ASE|DBIx::Class::Storage::DBI::Sybase::ASE>. The default implementation returns C, implement in your Storage driver if you need this functionality. @@ -2129,7 +2171,7 @@ sub is_datatype_numeric { } -=head2 create_ddl_dir (EXPERIMENTAL) +=head2 create_ddl_dir =over 4 @@ -2181,10 +2223,8 @@ hashref like the following { ignore_constraint_names => 0, # ... other options } -Note that this feature is currently EXPERIMENTAL and may not work correctly -across all databases, or fully handle complex relationships. - -WARNING: Please check all SQL files created, before applying them. +WARNING: You are strongly advised to check all SQL files created, before applying +them. =cut @@ -2366,10 +2406,19 @@ sub deployment_statements { data => $schema, ); - my $ret = $tr->translate - or $self->throw_exception( 'Unable to produce deployment statements: ' . $tr->error); + my @ret; + my $wa = wantarray; + if ($wa) { + @ret = $tr->translate; + } + else { + $ret[0] = $tr->translate; + } + + $self->throw_exception( 'Unable to produce deployment statements: ' . $tr->error) + unless (@ret && defined $ret[0]); - return $ret; + return $wa ? @ret : $ret[0]; } sub deploy { @@ -2494,6 +2543,34 @@ sub lag_behind_master { sub _sqlt_minimum_version { $minimum_sqlt_version }; } +=head2 relname_to_table_alias + +=over 4 + +=item Arguments: $relname, $join_count + +=back + +L uses L names as table aliases in +queries. + +This hook is to allow specific L drivers to change the +way these aliases are named. + +The default behavior is C<"$relname_$join_count" if $join_count > 1>, otherwise +C<"$relname">. + +=cut + +sub relname_to_table_alias { + my ($self, $relname, $join_count) = @_; + + my $alias = ($join_count && $join_count > 1 ? + join('_', $relname, $join_count) : $relname); + + return $alias; +} + sub DESTROY { my $self = shift;