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
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);
}
}
+sub _sth_bind_param {
+ my ($self, $sth, $placeholder_index, $data, $attributes) = @_;
+
+ $sth->bind_param($placeholder_index, $data, $attributes);
+}
+
sub _dbh_execute {
my ($self, $dbh, $op, $extra_bind, $ident, $bind_attributes, @args) = @_;
my $ref = ref $data;
$data = $ref && $ref ne 'ARRAY' ? ''.$data : $data; # stringify args (except arrayrefs)
- $sth->bind_param($placeholder_index, $data, $attributes);
+ $self->_sth_bind_param($sth, $placeholder_index, $data, $attributes);
$placeholder_index++;
}
}
}
sub update {
- my ($self, $source, $data, $where, @args) = @_;
+ my ($self, $source, @args) = @_;
my $bind_attrs = $self->source_bind_attributes($source);
- $where = $self->_strip_cond_qualifiers ($where);
- return $self->_execute('update' => [], $source, $bind_attrs, $data, $where, @args);
+ return $self->_execute('update' => [], $source, $bind_attrs, @args);
}
sub delete {
- my ($self, $source, $where, @args) = @_;
+ my ($self, $source, @args) = @_;
my $bind_attrs = $self->source_bind_attributes($source);
- $where = $self->_strip_cond_qualifiers ($where);
- return $self->_execute('delete' => [], $source, $bind_attrs, $where, @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. Since we employ subqueries when multiple tables are involved
-# (joins), it is relatively safe to strip all column qualifiers. Worst
-# case scenario the error message will be a bit misleading, if the
-# user supplies a foreign qualifier without a join (the message would
-# be "can't find column X", when in fact the user shoud join T containing
-# T.X)
-sub _strip_cond_qualifiers {
- my ($self, $where) = @_;
-
- my $sqlmaker = $self->sql_maker;
- my ($sql, @bind) = $sqlmaker->_recurse_where($where);
- return undef unless $sql;
-
- my ($qquot, $qsep) = map { quotemeta $_ } ( ($sqlmaker->quote_char||''), ($sqlmaker->name_sep||'.') );
- $sql =~ s/ (?: $qquot [\w\-]+ $qquot | [\w\-]+ ) $qsep //gx;
-
- return \[$sql, @bind];
+ return $self->_execute('delete' => [], $source, $bind_attrs, @args);
}
# 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)
+# (i.e. joined or limited resultsets, or non-introspectable conditions)
#
# Generating a single PK column subquery is trivial and supported
# by all RDBMS. However if we have a multicolumn PK, things get ugly.
my $rsrc = $rs->result_source;
- # we already check this, but double check naively just in case. Should be removed soon
+ # 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';
- my @pcols = $rsrc->primary_columns;
- if (@$sel != @pcols) {
+
+ if (
+ join ("\x00", map { join '.', $rs->{attrs}{alias}, $_ } sort @pcols)
+ ne
+ join ("\x00", sort @$sel )
+ ) {
$self->throw_exception (
- 'Subquery update/delete can not be called on resultsets selecting a'
- .' number of columns different than the number of primary keys'
+ '_subq_update_delete can not be called on resultsets selecting columns other than the primary keys'
);
}
This API is B<EXPERIMENTAL>, 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<undef>, implement in your Storage driver if
you need this functionality.