use base qw/DBIx::Class::Storage::DBIHacks DBIx::Class::Storage/;
use mro 'c3';
-use Carp::Clan qw/^DBIx::Class|^Try::Tiny/;
-use DBI;
-use DBIx::Class::Storage::DBI::Cursor;
+use DBIx::Class::Carp;
+use DBIx::Class::Exception;
use Scalar::Util qw/refaddr weaken reftype blessed/;
use List::Util qw/first/;
-use Data::Dumper::Concise 'Dumper';
use Sub::Name 'subname';
use Try::Tiny;
-use File::Path 'make_path';
use overload ();
use namespace::clean;
-
# default cursor class, overridable in connect_info attributes
__PACKAGE__->cursor_class('DBIx::Class::Storage::DBI::Cursor');
-__PACKAGE__->mk_group_accessors('inherited' => qw/sql_maker_class sql_limit_dialect/);
+__PACKAGE__->mk_group_accessors('inherited' => qw/
+ sql_limit_dialect sql_quote_char sql_name_sep
+/);
+
+__PACKAGE__->mk_group_accessors('component_class' => qw/sql_maker_class datetime_parser_type/);
+
__PACKAGE__->sql_maker_class('DBIx::Class::SQLMaker');
+__PACKAGE__->datetime_parser_type('DateTime::Format::MySQL'); # historic default
+
+__PACKAGE__->sql_name_sep('.');
__PACKAGE__->mk_group_accessors('simple' => qw/
_connect_info _dbi_connect_info _dbic_connect_attributes _driver_determined
no strict qw/refs/;
no warnings qw/redefine/;
*{__PACKAGE__ ."::$meth"} = subname $meth => sub {
- if (not $_[0]->_driver_determined and not $_[0]->{_in_determine_driver}) {
+ if (
+ # only fire when invoked on an instance, a valid class-based invocation
+ # would e.g. be setting a default for an inherited accessor
+ ref $_[0]
+ and
+ ! $_[0]->_driver_determined
+ and
+ ! $_[0]->{_in_determine_driver}
+ ) {
$_[0]->_determine_driver;
# This for some reason crashes and burns on perl 5.8.1
my $cref = $_[0]->can ($meth);
goto $cref;
}
+
goto $orig;
};
}
default L</sql_limit_dialect> setting of the storage (if any). For a list
of available limit dialects see L<DBIx::Class::SQLMaker::LimitDialects>.
+=item quote_names
+
+When true automatically sets L</quote_char> and L</name_sep> to the characters
+appropriate for your particular RDBMS. This option is preferred over specifying
+L</quote_char> directly.
+
=item quote_char
Specifies what characters to use to quote table and column names.
my @args = @{ $info->{arguments} };
- $self->_dbi_connect_info([@args,
- %attrs && !(ref $args[0] eq 'CODE') ? \%attrs : ()]);
+ if (keys %attrs and ref $args[0] ne 'CODE') {
+ carp
+ 'You provided explicit AutoCommit => 0 in your connection_info. '
+ . 'This is almost universally a bad idea (see the footnotes of '
+ . 'DBIx::Class::Storage::DBI for more info). If you still want to '
+ . 'do this you can set $ENV{DBIC_UNSAFE_AUTOCOMMIT_OK} to disable '
+ . 'this warning.'
+ if ! $attrs{AutoCommit} and ! $ENV{DBIC_UNSAFE_AUTOCOMMIT_OK};
+
+ push @args, \%attrs if keys %attrs;
+ }
+ $self->_dbi_connect_info(\@args);
# FIXME - dirty:
# save attributes them in a separate accessor so they are always
delete @attrs{@storage_opts} if @storage_opts;
my @sql_maker_opts = grep exists $attrs{$_},
- qw/limit_dialect quote_char name_sep/;
+ qw/limit_dialect quote_char name_sep quote_names/;
@{ $info{sql_maker_options} }{@sql_maker_opts} =
delete @attrs{@sql_maker_opts} if @sql_maker_opts;
return \%info;
}
-sub _default_dbi_connect_attributes {
- return {
+sub _default_dbi_connect_attributes () {
+ +{
AutoCommit => 1,
- RaiseError => 1,
PrintError => 0,
+ RaiseError => 1,
+ ShowErrorStatement => 1,
};
}
# This is basically a blend of dbh_do above and DBIx::Class::Storage::txn_do.
# It also informs dbh_do to bypass itself while under the direction of txn_do,
-# via $self->{_in_dbh_do} (this saves some redundant eval and errorcheck, etc)
+# via $self->{_in_dbh_do} (this saves some redundant eval and errorcheck, etc)
sub txn_do {
my $self = shift;
my $coderef = shift;
my ($self) = @_;
unless ($self->_sql_maker) {
my $sql_maker_class = $self->sql_maker_class;
- $self->ensure_class_loaded ($sql_maker_class);
my %opts = %{$self->_sql_maker_opts||{}};
my $dialect =
"Your storage class ($s_class) does not set sql_limit_dialect and you "
. 'have not supplied an explicit limit_dialect in your connection_info. '
. 'DBIC will attempt to use the GenericSubQ dialect, which works on most '
- . 'databases but can be (and often is) painfully slow.'
+ . 'databases but can be (and often is) painfully slow. '
+ . "Please file an RT ticket against '$s_class' ."
);
'GenericSubQ';
}
;
+ my ($quote_char, $name_sep);
+
+ if ($opts{quote_names}) {
+ $quote_char = (delete $opts{quote_char}) || $self->sql_quote_char || do {
+ my $s_class = (ref $self) || $self;
+ carp (
+ "You requested 'quote_names' but your storage class ($s_class) does "
+ . 'not explicitly define a default sql_quote_char and you have not '
+ . 'supplied a quote_char as part of your connection_info. DBIC will '
+ .q{default to the ANSI SQL standard quote '"', which works most of }
+ . "the time. Please file an RT ticket against '$s_class'."
+ );
+
+ '"'; # RV
+ };
+
+ $name_sep = (delete $opts{name_sep}) || $self->sql_name_sep;
+ }
+
$self->_sql_maker($sql_maker_class->new(
bindtype=>'columns',
array_datatypes => 1,
limit_dialect => $dialect,
- name_sep => '.',
+ ($quote_char ? (quote_char => $quote_char) : ()),
+ name_sep => ($name_sep || '.'),
%opts,
));
}
}
sub _get_server_version {
- shift->_get_dbh->get_info(18);
+ shift->_dbh_get_info(18);
+}
+
+sub _dbh_get_info {
+ my ($self, $info) = @_;
+
+ return try { $self->_get_dbh->get_info($info) } || undef;
}
sub _determine_driver {
$self->_driver_determined(1);
+ Class::C3->reinitialize() if DBIx::Class::_ENV_::OLD_MRO;
+
$self->_init; # run driver-specific initializations
$self->_run_connection_actions
my $attrs = shift @do_args;
my @bind = map { [ undef, $_ ] } @do_args;
- $self->_query_start($sql, @bind);
+ $self->_query_start($sql, \@bind);
$self->_get_dbh->do($sql, $attrs, @do_args);
- $self->_query_end($sql, @bind);
+ $self->_query_end($sql, \@bind);
}
return $self;
try {
if(ref $info[0] eq 'CODE') {
- $dbh = $info[0]->();
+ $dbh = $info[0]->();
}
else {
- $dbh = DBI->connect(@info);
+ require DBI;
+ $dbh = DBI->connect(@info);
}
if (!$dbh) {
unless ($self->unsafe) {
+ $self->throw_exception(
+ 'Refusing clobbering of {HandleError} installed on externally supplied '
+ ."DBI handle $dbh. Either remove the handler or use the 'unsafe' attribute."
+ ) if $dbh->{HandleError} and ref $dbh->{HandleError} ne '__DBIC__DBH__ERROR__HANDLER__';
+
+ # Default via _default_dbi_connect_attributes is 1, hence it was an explicit
+ # request, or an external handle. Complain and set anyway
+ unless ($dbh->{RaiseError}) {
+ carp( ref $info[0] eq 'CODE'
+
+ ? "The 'RaiseError' of the externally supplied DBI handle is set to false. "
+ ."DBIx::Class will toggle it back to true, unless the 'unsafe' connect "
+ .'attribute has been supplied'
+
+ : 'RaiseError => 0 supplied in your connection_info, without an explicit '
+ .'unsafe => 1. Toggling RaiseError back to true'
+ );
+
+ $dbh->{RaiseError} = 1;
+ }
+
# this odd anonymous coderef dereference is in fact really
# necessary to avoid the unwanted effect described in perl5
# RT#75792
my $weak_self = $_[0];
weaken $weak_self;
- $_[1]->{HandleError} = sub {
+ # the coderef is blessed so we can distinguish it from externally
+ # supplied handles (which must be preserved)
+ $_[1]->{HandleError} = bless sub {
if ($weak_self) {
$weak_self->throw_exception("DBI Exception: $_[0]");
}
else {
# the handler may be invoked by something totally out of
# the scope of DBIC
- croak ("DBI Exception (unhandled by DBIC, ::Schema GCed): $_[0]");
+ DBIx::Class::Exception->throw("DBI Exception (unhandled by DBIC, ::Schema GCed): $_[0]");
}
- };
+ }, '__DBIC__DBH__ERROR__HANDLER__';
}->($self, $dbh);
-
- $dbh->{ShowErrorStatement} = 1;
- $dbh->{RaiseError} = 1;
- $dbh->{PrintError} = 0;
}
}
catch {
sub txn_commit {
my $self = shift;
- if ($self->{transaction_depth} == 1) {
+ if (! $self->_dbh) {
+ $self->throw_exception('cannot COMMIT on a disconnected handle');
+ }
+ elsif ($self->{transaction_depth} == 1) {
$self->debugobj->txn_commit()
if ($self->debug);
$self->_dbh_commit;
$self->svp_release
if $self->auto_savepoint;
}
+ elsif (! $self->_dbh->FETCH('AutoCommit') ) {
+
+ carp "Storage transaction_depth $self->{transaction_depth} does not match "
+ ."false AutoCommit of $self->{_dbh}, attempting COMMIT anyway";
+
+ $self->debugobj->txn_commit()
+ if ($self->debug);
+ $self->_dbh_commit;
+ $self->{transaction_depth} = 0
+ if $self->_dbh_autocommit;
+ }
else {
$self->throw_exception( 'Refusing to commit without a started transaction' );
}
# easier to override in NoBindVars without duping the rest. It takes up
# all of _execute's args, and emits $sql, @bind.
sub _prep_for_execute {
- my ($self, $op, $extra_bind, $ident, $args) = @_;
+ my ($self, $op, $ident, $args) = @_;
- if( blessed $ident && $ident->isa("DBIx::Class::ResultSource") ) {
- $ident = $ident->from();
- }
+ my ($sql, @bind) = $self->sql_maker->$op(
+ blessed($ident) ? $ident->from : $ident,
+ @$args,
+ );
- my ($sql, @bind) = $self->sql_maker->$op($ident, @$args);
+ my (@final_bind, $colinfos);
+ my $resolve_bindinfo = sub {
+ $colinfos ||= $self->_resolve_column_info($ident);
+ if (my $col = $_[1]->{dbic_colname}) {
+ $_[1]->{sqlt_datatype} ||= $colinfos->{$col}{data_type}
+ if $colinfos->{$col}{data_type};
+ $_[1]->{sqlt_size} ||= $colinfos->{$col}{size}
+ if $colinfos->{$col}{size};
+ }
+ $_[1];
+ };
- unshift(@bind,
- map { ref $_ eq 'ARRAY' ? $_ : [ '!!dummy', $_ ] } @$extra_bind)
- if $extra_bind;
- return ($sql, \@bind);
+ for my $e (@{$args->[2]{bind}||[]}, @bind) {
+ push @final_bind, [ do {
+ if (ref $e ne 'ARRAY') {
+ ({}, $e)
+ }
+ elsif (! defined $e->[0]) {
+ ({}, $e->[1])
+ }
+ elsif (ref $e->[0] eq 'HASH') {
+ (
+ (first { $e->[0]{$_} } qw/dbd_attrs sqlt_datatype/) ? $e->[0] : $self->$resolve_bindinfo($e->[0]),
+ $e->[1]
+ )
+ }
+ elsif (ref $e->[0] eq 'SCALAR') {
+ ( { sqlt_datatype => ${$e->[0]} }, $e->[1] )
+ }
+ else {
+ ( $self->$resolve_bindinfo({ dbic_colname => $e->[0] }), $e->[1] )
+ }
+ }];
+ }
+
+ ($sql, \@final_bind);
}
+sub _format_for_trace {
+ #my ($self, $bind) = @_;
-sub _fix_bind_params {
- my ($self, @bind) = @_;
+ ### Turn @bind from something like this:
+ ### ( [ "artist", 1 ], [ \%attrs, 3 ] )
+ ### to this:
+ ### ( "'1'", "'3'" )
- ### Turn @bind from something like this:
- ### ( [ "artist", 1 ], [ "cdid", 1, 3 ] )
- ### to this:
- ### ( "'1'", "'1'", "'3'" )
- return
- map {
- if ( defined( $_ && $_->[1] ) ) {
- map { qq{'$_'}; } @{$_}[ 1 .. $#$_ ];
- }
- else { q{NULL}; }
- } @bind;
+ map {
+ defined( $_ && $_->[1] )
+ ? qq{'$_->[1]'}
+ : q{NULL}
+ } @{$_[1] || []};
}
sub _query_start {
- my ( $self, $sql, @bind ) = @_;
-
- if ( $self->debug ) {
- @bind = $self->_fix_bind_params(@bind);
+ my ( $self, $sql, $bind ) = @_;
- $self->debugobj->query_start( $sql, @bind );
- }
+ $self->debugobj->query_start( $sql, $self->_format_for_trace($bind) )
+ if $self->debug;
}
sub _query_end {
- my ( $self, $sql, @bind ) = @_;
+ my ( $self, $sql, $bind ) = @_;
- if ( $self->debug ) {
- @bind = $self->_fix_bind_params(@bind);
- $self->debugobj->query_end( $sql, @bind );
- }
+ $self->debugobj->query_end( $sql, $self->_format_for_trace($bind) )
+ if $self->debug;
}
-sub _dbh_execute {
- my ($self, $dbh, $op, $extra_bind, $ident, $bind_attributes, @args) = @_;
+my $sba_compat;
+sub _dbi_attrs_for_bind {
+ my ($self, $ident, $bind) = @_;
- my ($sql, $bind) = $self->_prep_for_execute($op, $extra_bind, $ident, \@args);
-
- $self->_query_start( $sql, @$bind );
+ if (! defined $sba_compat) {
+ $self->_determine_driver;
+ $sba_compat = $self->can('source_bind_attributes') == \&source_bind_attributes
+ ? 0
+ : 1
+ ;
+ }
- my $sth = $self->sth($sql,$op);
+ my $sba_attrs;
+ if ($sba_compat) {
+ my $class = ref $self;
+ carp_unique (
+ "The source_bind_attributes() override in $class relies on a deprecated codepath. "
+ .'You are strongly advised to switch your code to override bind_attribute_by_datatype() '
+ .'instead. This legacy compat shim will also disappear some time before DBIC 0.09'
+ );
- my $placeholder_index = 1;
+ my $sba_attrs = $self->source_bind_attributes
+ }
- foreach my $bound (@$bind) {
- my $attributes = {};
- my($column_name, @data) = @$bound;
+ my @attrs;
- if ($bind_attributes) {
- $attributes = $bind_attributes->{$column_name}
- if defined $bind_attributes->{$column_name};
+ for (map { $_->[0] } @$bind) {
+ push @attrs, do {
+ if ($_->{dbd_attrs}) {
+ $_->{dbd_attrs}
+ }
+ elsif($_->{sqlt_datatype}) {
+ $self->bind_attribute_by_data_type($_->{sqlt_datatype}) || undef;
+ }
+ elsif ($sba_attrs and $_->{dbic_colname}) {
+ $sba_attrs->{$_->{dbic_colname}} || undef;
+ }
+ else {
+ undef; # always push something at this position
+ }
}
+ }
- foreach my $data (@data) {
- my $ref = ref $data;
+ return \@attrs;
+}
- if ($ref and overload::Method($data, '""') ) {
- $data = "$data";
- }
- elsif ($ref eq 'SCALAR') { # any scalarrefs are assumed to be bind_inouts
- $sth->bind_param_inout(
- $placeholder_index++,
- $data,
- $self->_max_column_bytesize($ident, $column_name),
- $attributes
- );
- next;
- }
+sub _execute {
+ my ($self, $op, $ident, @args) = @_;
+
+ my ($sql, $bind) = $self->_prep_for_execute($op, $ident, \@args);
- $sth->bind_param($placeholder_index++, $data, $attributes);
+ shift->dbh_do( # retry over disconnects
+ '_dbh_execute',
+ $sql,
+ $bind,
+ $self->_dbi_attrs_for_bind($ident, $bind)
+ );
+}
+
+sub _dbh_execute {
+ my ($self, undef, $sql, $bind, $bind_attrs) = @_;
+
+ $self->_query_start( $sql, $bind );
+ my $sth = $self->_sth($sql);
+
+ for my $i (0 .. $#$bind) {
+ if (ref $bind->[$i][1] eq 'SCALAR') { # any scalarrefs are assumed to be bind_inouts
+ $sth->bind_param_inout(
+ $i + 1, # bind params counts are 1-based
+ $bind->[$i][1],
+ $bind->[$i][0]{dbd_size} || $self->_max_column_bytesize($bind->[$i][0]), # size
+ $bind_attrs->[$i],
+ );
+ }
+ else {
+ $sth->bind_param(
+ $i + 1,
+ (ref $bind->[$i][1] and overload::Method($bind->[$i][1], '""'))
+ ? "$bind->[$i][1]"
+ : $bind->[$i][1]
+ ,
+ $bind_attrs->[$i],
+ );
}
}
$sth->errstr || $sth->err || 'Unknown error: execute() returned false, but error flags were not set...'
) if !$rv;
- $self->_query_end( $sql, @$bind );
+ $self->_query_end( $sql, $bind );
return (wantarray ? ($rv, $sth, @$bind) : $rv);
}
-sub _execute {
- my $self = shift;
- $self->dbh_do('_dbh_execute', @_); # retry over disconnects
-}
-
sub _prefetch_autovalues {
my ($self, $source, $to_insert) = @_;
}
}
- my $bind_attributes = $self->source_bind_attributes($source);
-
- my ($rv, $sth) = $self->_execute('insert' => [], $source, $bind_attributes, $to_insert, $sqla_opts);
+ my ($rv, $sth) = $self->_execute('insert', $source, $to_insert, $sqla_opts);
my %returned_cols;
}
-## 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.
sub insert_bulk {
my ($self, $source, $cols, $data) = @_;
- my %colvalues;
- @colvalues{@$cols} = (0..$#$cols);
-
- for my $i (0..$#$cols) {
- my $first_val = $data->[0][$i];
- next unless ref $first_val eq 'SCALAR';
-
- $colvalues{ $cols->[$i] } = $first_val;
+ # FIXME - perhaps this is not even needed? does DBI stringify?
+ #
+ # forcibly stringify whatever is stringifiable
+ for my $r (0 .. $#$data) {
+ for my $c (0 .. $#{$data->[$r]}) {
+ $data->[$r][$c] = "$data->[$r][$c]"
+ if ( ref $data->[$r][$c] and overload::Method($data->[$r][$c], '""') );
+ }
}
- # check for bad data and stringify stringifiable objects
- my $bad_slice = sub {
- my ($msg, $col_idx, $slice_idx) = @_;
- $self->throw_exception(sprintf "%s for column '%s' in populate slice:\n%s",
- $msg,
- $cols->[$col_idx],
- do {
- local $Data::Dumper::Maxdepth = 1; # don't dump objects, if any
- Dumper {
- map { $cols->[$_] => $data->[$slice_idx][$_] } (0 .. $#$cols)
- },
- }
- );
- };
-
- for my $datum_idx (0..$#$data) {
- my $datum = $data->[$datum_idx];
+ # check the data for consistency
+ # report a sensible error on bad data
+ #
+ # also create a list of dynamic binds (ones that will be changing
+ # for each row)
+ my $dyn_bind_idx;
+ for my $col_idx (0..$#$cols) {
+
+ # the first "row" is used as a point of reference
+ my $reference_val = $data->[0][$col_idx];
+ my $is_literal = ref $reference_val eq 'SCALAR';
+ my $is_literal_bind = ( !$is_literal and (
+ ref $reference_val eq 'REF'
+ and
+ ref $$reference_val eq 'ARRAY'
+ ) );
+
+ $dyn_bind_idx->{$col_idx} = 1
+ if (!$is_literal and !$is_literal_bind);
+
+ # use a closure for convenience (less to pass)
+ my $bad_slice = sub {
+ my ($msg, $slice_idx) = @_;
+ $self->throw_exception(sprintf "%s for column '%s' in populate slice:\n%s",
+ $msg,
+ $cols->[$col_idx],
+ do {
+ require Data::Dumper::Concise;
+ local $Data::Dumper::Maxdepth = 2;
+ Data::Dumper::Concise::Dumper ({
+ map { $cols->[$_] =>
+ $data->[$slice_idx][$_]
+ } (0 .. $#$cols)
+ }),
+ }
+ );
+ };
- for my $col_idx (0..$#$cols) {
- my $val = $datum->[$col_idx];
- my $sqla_bind = $colvalues{ $cols->[$col_idx] };
- my $is_literal_sql = (ref $sqla_bind) eq 'SCALAR';
+ for my $row_idx (1..$#$data) { # we are comparing against what we got from [0] above, hence start from 1
+ my $val = $data->[$row_idx][$col_idx];
- if ($is_literal_sql) {
- if (not ref $val) {
- $bad_slice->('bind found where literal SQL expected', $col_idx, $datum_idx);
+ if ($is_literal) {
+ if (ref $val ne 'SCALAR') {
+ $bad_slice->(
+ "Incorrect value (expecting SCALAR-ref \\'$$reference_val')",
+ $row_idx
+ );
+ }
+ elsif ($$val ne $$reference_val) {
+ $bad_slice->(
+ "Inconsistent literal SQL value (expecting \\'$$reference_val')",
+ $row_idx
+ );
}
- elsif ((my $reftype = ref $val) ne 'SCALAR') {
- $bad_slice->("$reftype reference found where literal SQL expected",
- $col_idx, $datum_idx);
+ }
+ elsif ($is_literal_bind) {
+ if (ref $val ne 'REF' or ref $$val ne 'ARRAY') {
+ $bad_slice->(
+ "Incorrect value (expecting ARRAYREF-ref \\['${$reference_val}->[0]', ... ])",
+ $row_idx
+ );
}
- elsif ($$val ne $$sqla_bind){
- $bad_slice->("inconsistent literal SQL value, expecting: '$$sqla_bind'",
- $col_idx, $datum_idx);
+ elsif (${$val}->[0] ne ${$reference_val}->[0]) {
+ $bad_slice->(
+ "Inconsistent literal SQL-bind value (expecting \\['${$reference_val}->[0]', ... ])",
+ $row_idx
+ );
}
}
- elsif (my $reftype = ref $val) {
- require overload;
- if (overload::Method($val, '""')) {
- $datum->[$col_idx] = "".$val;
+ elsif (ref $val) {
+ if (ref $val eq 'SCALAR' or (ref $val eq 'REF' and ref $$val eq 'ARRAY') ) {
+ $bad_slice->("Literal SQL found where a plain bind value is expected", $row_idx);
}
else {
- $bad_slice->("$reftype reference found where bind expected",
- $col_idx, $datum_idx);
+ $bad_slice->("$val reference found where bind expected", $row_idx);
}
}
}
}
- my ($sql, $bind) = $self->_prep_for_execute (
- 'insert', undef, $source, [\%colvalues]
+ # Get the sql with bind values interpolated where necessary. For dynamic
+ # binds convert the values of the first row into a literal+bind combo, with
+ # extra positional info in the bind attr hashref. This will allow us to match
+ # the order properly, and is so contrived because a user-supplied literal
+ # bind (or something else specific to a resultsource and/or storage driver)
+ # can inject extra binds along the way, so one can't rely on "shift
+ # positions" ordering at all. Also we can't just hand SQLA a set of some
+ # known "values" (e.g. hashrefs that can be later matched up by address),
+ # because we want to supply a real value on which perhaps e.g. datatype
+ # checks will be performed
+ my ($sql, $proto_bind) = $self->_prep_for_execute (
+ 'insert',
+ $source,
+ [ { map { $cols->[$_] => $dyn_bind_idx->{$_}
+ ? \[ '?', [
+ { dbic_colname => $cols->[$_], _bind_data_slice_idx => $_ }
+ =>
+ $data->[0][$_]
+ ] ]
+ : $data->[0][$_]
+ } (0..$#$cols) } ],
);
- if (! @$bind) {
- # if the bindlist is empty - make sure all "values" are in fact
- # literal scalarrefs. If not the case this means the storage ate
- # them away (e.g. the NoBindVars component) and interpolated them
- # directly into the SQL. This obviosly can't be good for multi-inserts
-
- $self->throw_exception('Cannot insert_bulk without support for placeholders')
- if first { ref $_ ne 'SCALAR' } values %colvalues;
+ if (! @$proto_bind and keys %$dyn_bind_idx) {
+ # if the bindlist is empty and we had some dynamic binds, this means the
+ # storage ate them away (e.g. the NoBindVars component) and interpolated
+ # them directly into the SQL. This obviosly can't be good for multi-inserts
+ $self->throw_exception('Cannot insert_bulk without support for placeholders');
}
# neither _execute_array, nor _execute_inserts_with_no_binds are
# scope guard
my $guard = $self->txn_scope_guard;
- $self->_query_start( $sql, @$bind ? [ dummy => '__BULK_INSERT__' ] : () );
- my $sth = $self->sth($sql);
+ $self->_query_start( $sql, @$proto_bind ? [[undef => '__BULK_INSERT__' ]] : () );
+ my $sth = $self->_sth($sql);
my $rv = do {
- if (@$bind) {
- #@bind = map { ref $_ ? ''.$_ : $_ } @bind; # stringify args
- $self->_execute_array( $source, $sth, $bind, $cols, $data );
+ if (@$proto_bind) {
+ # proto bind contains the information on which pieces of $data to pull
+ # $cols is passed in only for prettier error-reporting
+ $self->_execute_array( $source, $sth, $proto_bind, $cols, $data );
}
else {
# bind_param_array doesn't work if there are no binds
}
};
- $self->_query_end( $sql, @$bind ? [ dummy => '__BULK_INSERT__' ] : () );
+ $self->_query_end( $sql, @$proto_bind ? [[ undef => '__BULK_INSERT__' ]] : () );
$guard->commit;
- return (wantarray ? ($rv, $sth, @$bind) : $rv);
+ return (wantarray ? ($rv, $sth, @$proto_bind) : $rv);
}
sub _execute_array {
- my ($self, $source, $sth, $bind, $cols, $data, @extra) = @_;
+ my ($self, $source, $sth, $proto_bind, $cols, $data, @extra) = @_;
## This must be an arrayref, else nothing works!
my $tuple_status = [];
- ## Get the bind_attributes, if any exist
- my $bind_attributes = $self->source_bind_attributes($source);
-
- ## Bind the values and execute
- my $placeholder_index = 1;
-
- foreach my $bound (@$bind) {
+ my $bind_attrs = $self->_dbi_attrs_for_bind($source, $proto_bind);
- my $attributes = {};
- my ($column_name, $data_index) = @$bound;
-
- if( $bind_attributes ) {
- $attributes = $bind_attributes->{$column_name}
- if defined $bind_attributes->{$column_name};
- }
-
- my @data = map { $_->[$data_index] } @$data;
+ # Bind the values by column slices
+ for my $i (0 .. $#$proto_bind) {
+ my $data_slice_idx = (
+ ref $proto_bind->[$i][0] eq 'HASH'
+ and
+ exists $proto_bind->[$i][0]{_bind_data_slice_idx}
+ ) ? $proto_bind->[$i][0]{_bind_data_slice_idx} : undef;
$sth->bind_param_array(
- $placeholder_index,
- [@data],
- (%$attributes ? $attributes : ()),
+ $i+1, # DBI bind indexes are 1-based
+ defined $data_slice_idx
+ # either get a "column" of dynamic values, or just repeat the same
+ # bind over and over
+ ? [ map { $_->[$data_slice_idx] } @$data ]
+ : [ ($proto_bind->[$i][1]) x @$data ]
+ ,
+ defined $bind_attrs->[$i] ? $bind_attrs->[$i] : (), # some DBDs throw up when given an undef
);
- $placeholder_index++;
}
my ($rv, $err);
$err = shift;
};
+ # Not all DBDs are create equal. Some throw on error, some return
+ # an undef $rv, and some set $sth->err - try whatever we can
+ $err = ($sth->errstr || 'UNKNOWN ERROR ($sth->errstr is unset)') if (
+ ! defined $err
+ and
+ ( !defined $rv or $sth->err )
+ );
+
# Statement must finish even if there was an exception.
try {
$sth->finish
$err = shift unless defined $err
};
- $err = $sth->errstr
- if (! defined $err and $sth->err);
-
if (defined $err) {
my $i = 0;
++$i while $i <= $#$tuple_status && !ref $tuple_status->[$i];
$self->throw_exception("Unexpected populate error: $err")
if ($i > $#$tuple_status);
- $self->throw_exception(sprintf "%s for populate slice:\n%s",
+ require Data::Dumper::Concise;
+ $self->throw_exception(sprintf "execute_array() aborted with '%s' at populate slice:\n%s",
($tuple_status->[$i][1] || $err),
- Dumper { map { $cols->[$_] => $data->[$i][$_] } (0 .. $#$cols) },
+ Data::Dumper::Concise::Dumper( { map { $cols->[$_] => $data->[$i][$_] } (0 .. $#$cols) } ),
);
}
}
sub _dbh_execute_array {
- my ($self, $sth, $tuple_status, @extra) = @_;
-
- return $sth->execute_array({ArrayTupleStatus => $tuple_status});
+ #my ($self, $sth, $tuple_status, @extra) = @_;
+ return $_[1]->execute_array({ArrayTupleStatus => $_[2]});
}
sub _dbh_execute_inserts_with_no_binds {
}
sub update {
- my ($self, $source, @args) = @_;
-
- my $bind_attrs = $self->source_bind_attributes($source);
-
- return $self->_execute('update' => [], $source, $bind_attrs, @args);
+ #my ($self, $source, @args) = @_;
+ shift->_execute('update', @_);
}
sub delete {
- my ($self, $source, @args) = @_;
-
- my $bind_attrs = $self->source_bind_attributes($source);
-
- return $self->_execute('delete' => [], $source, $bind_attrs, @args);
+ #my ($self, $source, @args) = @_;
+ shift->_execute('delete', @_);
}
# We were sent here because the $rs contains a complex search
sub _select_args_to_query {
my $self = shift;
- # my ($op, $bind, $ident, $bind_attrs, $select, $cond, $rs_attrs, $rows, $offset)
+ # my ($op, $ident, $select, $cond, $rs_attrs, $rows, $offset)
# = $self->_select_args($ident, $select, $cond, $attrs);
- my ($op, $bind, $ident, $bind_attrs, @args) =
+ my ($op, $ident, @args) =
$self->_select_args(@_);
- # my ($sql, $prepared_bind) = $self->_prep_for_execute($op, $bind, $ident, [ $select, $cond, $rs_attrs, $rows, $offset ]);
- my ($sql, $prepared_bind) = $self->_prep_for_execute($op, $bind, $ident, \@args);
+ # my ($sql, $prepared_bind) = $self->_prep_for_execute($op, $ident, [ $select, $cond, $rs_attrs, $rows, $offset ]);
+ my ($sql, $prepared_bind) = $self->_prep_for_execute($op, $ident, \@args);
$prepared_bind ||= [];
return wantarray
- ? ($sql, $prepared_bind, $bind_attrs)
+ ? ($sql, $prepared_bind)
: \[ "($sql)", @$prepared_bind ]
;
}
from => $ident,
where => $where,
$rs_alias && $alias2source->{$rs_alias}
- ? ( _rsroot_source_handle => $alias2source->{$rs_alias}->handle )
+ ? ( _rsroot_rsrc => $alias2source->{$rs_alias} )
: ()
,
};
- # calculate bind_attrs before possible $ident mangling
- my $bind_attrs = {};
- for my $alias (keys %$alias2source) {
- my $bindtypes = $self->source_bind_attributes ($alias2source->{$alias}) || {};
- for my $col (keys %$bindtypes) {
-
- my $fqcn = join ('.', $alias, $col);
- $bind_attrs->{$fqcn} = $bindtypes->{$col} if $bindtypes->{$col};
-
- # Unqialified column names are nice, but at the same time can be
- # rather ambiguous. What we do here is basically go along with
- # the loop, adding an unqualified column slot to $bind_attrs,
- # alongside the fully qualified name. As soon as we encounter
- # another column by that name (which would imply another table)
- # we unset the unqualified slot and never add any info to it
- # to avoid erroneous type binding. If this happens the users
- # only choice will be to fully qualify his column name
-
- if (exists $bind_attrs->{$col}) {
- $bind_attrs->{$col} = {};
- }
- else {
- $bind_attrs->{$col} = $bind_attrs->{$fqcn};
- }
- }
- }
-
# Sanity check the attributes (SQLMaker does it too, but
# in case of a software_limit we'll never reach there)
if (defined $attrs->{offset}) {
$self->throw_exception('A supplied offset attribute must be a non-negative integer')
if ( $attrs->{offset} =~ /\D/ or $attrs->{offset} < 0 );
}
- $attrs->{offset} ||= 0;
if (defined $attrs->{rows}) {
$self->throw_exception("The rows attribute must be a positive integer if present")
= $self->_adjust_select_args_for_complex_prefetch ($ident, $select, $where, $attrs);
}
elsif (! $attrs->{software_limit} ) {
- push @limit, $attrs->{rows}, $attrs->{offset};
+ push @limit, (
+ $attrs->{rows} || (),
+ $attrs->{offset} || (),
+ );
}
# try to simplify the joinmap further (prune unreferenced type-single joins)
# invoked, and that's just bad...
###
- return ('select', $attrs->{bind}, $ident, $bind_attrs, $select, $where, $attrs, @limit);
+ return ('select', $ident, $select, $where, $attrs, @limit);
}
# Returns a counting SELECT for a simple count
return { count => '*' };
}
-
sub source_bind_attributes {
- my ($self, $source) = @_;
-
- my $bind_attributes;
-
- my $colinfo = $source->columns_info;
-
- for my $col (keys %$colinfo) {
- if (my $dt = $colinfo->{$col}{data_type} ) {
- $bind_attributes->{$col} = $self->bind_attribute_by_data_type($dt)
- }
- }
-
- return $bind_attributes;
+ shift->throw_exception(
+ 'source_bind_attributes() was never meant to be a callable public method - '
+ .'please contact the DBIC dev-team and describe your use case so that a reasonable '
+ .'solution can be provided'
+ ."\nhttp://search.cpan.org/dist/DBIx-Class/lib/DBIx/Class.pm#GETTING_HELP/SUPPORT"
+ );
}
=head2 select
=head2 sql_limit_dialect
This is an accessor for the default SQL limit dialect used by a particular
-storage driver. Can be overriden by supplying an explicit L</limit_dialect>
+storage driver. Can be overridden by supplying an explicit L</limit_dialect>
to L<DBIx::Class::Schema/connect>. For a list of available limit dialects
see L<DBIx::Class::SQLMaker::LimitDialects>.
# XXX You would think RaiseError would make this impossible,
# but apparently that's not true :(
- $self->throw_exception($dbh->errstr) if !$sth;
+ $self->throw_exception(
+ $dbh->errstr
+ ||
+ sprintf( "\$dbh->prepare() of '%s' through %s failed *silently* without "
+ .'an exception and/or setting $dbh->errstr',
+ length ($sql) > 20
+ ? substr($sql, 0, 20) . '...'
+ : $sql
+ ,
+ 'DBD::' . $dbh->{Driver}{Name},
+ )
+ ) if !$sth;
$sth;
}
sub sth {
+ carp_unique 'sth was mistakenly marked/documented as public, stop calling it (will be removed before DBIC v0.09)';
+ shift->_sth(@_);
+}
+
+sub _sth {
my ($self, $sql) = @_;
$self->dbh_do('_dbh_sth', $sql); # retry over disconnects
}
=cut
sub is_datatype_numeric {
- my ($self, $dt) = @_;
+ #my ($self, $dt) = @_;
- return 0 unless $dt;
+ return 0 unless $_[1];
- return $dt =~ /^ (?:
+ $_[1] =~ /^ (?:
numeric | int(?:eger)? | (?:tiny|small|medium|big)int | dec(?:imal)? | real | float | double (?: \s+ precision)? | (?:big)?serial
) $/ix;
}
} else {
-d $dir
or
- make_path ("$dir") # make_path does not like objects (i.e. Path::Class::Dir)
+ (require File::Path and File::Path::make_path ("$dir")) # make_path does not like objects (i.e. Path::Class::Dir)
or
$self->throw_exception(
- "Failed to create '$dir': " . ($! || $@ || 'error unknow')
+ "Failed to create '$dir': " . ($! || $@ || 'error unknown')
);
}
my $filename = $schema->ddl_filename($type, $version, $dir);
if(-f $filename)
{
+ # FIXME replace this block when a proper sane sql parser is available
my $file;
open($file, "<$filename")
or $self->throw_exception("Can't open $filename ($!)");
return wantarray ? @ret : $ret[0];
}
+# FIXME deploy() currently does not accurately report sql errors
+# Will always return true while errors are warned
sub deploy {
my ($self, $schema, $type, $sqltargs, $dir) = @_;
my $deploy = sub {
my $line = shift;
- return if($line =~ /^--/);
return if(!$line);
+ return if($line =~ /^--/);
# next if($line =~ /^DROP/m);
return if($line =~ /^BEGIN TRANSACTION/m);
return if($line =~ /^COMMIT/m);
}
}
elsif (@statements == 1) {
- foreach my $line ( split(";\n", $statements[0])) {
+ # split on single line comments and end of statements
+ foreach my $line ( split(/\s*--.*\n|;\n/, $statements[0])) {
$deploy->( $line );
}
}
=head2 datetime_parser_type
-Defines (returns) the datetime parser class - currently hardwired to
-L<DateTime::Format::MySQL>
-
-=cut
-
-sub datetime_parser_type { "DateTime::Format::MySQL"; }
+Defines the datetime parser class - currently defaults to L<DateTime::Format::MySQL>
=head2 build_datetime_parser
sub build_datetime_parser {
my $self = shift;
my $type = $self->datetime_parser_type(@_);
- $self->ensure_class_loaded ($type);
return $type;
}
# version and it may be necessary to amend or override it for a specific storage
# if such binds are necessary.
sub _max_column_bytesize {
- my ($self, $source, $col) = @_;
+ my ($self, $attr) = @_;
- my $inf = $source->column_info($col);
- return $inf->{_max_bytesize} ||= do {
+ my $max_size;
- my $max_size;
+ if ($attr->{sqlt_datatype}) {
+ my $data_type = lc($attr->{sqlt_datatype});
- if (my $data_type = $inf->{data_type}) {
- $data_type = lc($data_type);
+ if ($attr->{sqlt_size}) {
# String/sized-binary types
- if ($data_type =~ /^(?:l?(?:var)?char(?:acter)?(?:\s*varying)?
- |(?:var)?binary(?:\s*varying)?|raw)\b/x
+ if ($data_type =~ /^(?:
+ l? (?:var)? char(?:acter)? (?:\s*varying)?
+ |
+ (?:var)? binary (?:\s*varying)?
+ |
+ raw
+ )\b/x
) {
- $max_size = $inf->{size};
+ $max_size = $attr->{sqlt_size};
}
# Other charset/unicode types, assume scale of 4
- elsif ($data_type =~ /^(?:national\s*character(?:\s*varying)?|nchar
- |univarchar
- |nvarchar)\b/x
+ elsif ($data_type =~ /^(?:
+ national \s* character (?:\s*varying)?
+ |
+ nchar
+ |
+ univarchar
+ |
+ nvarchar
+ )\b/x
) {
- $max_size = $inf->{size} * 4 if $inf->{size};
- }
- # Blob types
- elsif ($self->_is_lob_type($data_type)) {
- # default to longreadlen
- }
- else {
- $max_size = 100; # for all other (numeric?) datatypes
+ $max_size = $attr->{sqlt_size} * 4;
}
}
- $max_size ||= $self->_get_dbh->{LongReadLen} || 8000;
- };
+ if (!$max_size and !$self->_is_lob_type($data_type)) {
+ $max_size = 100 # for all other (numeric?) datatypes
+ }
+ }
+
+ $max_size || $self->_dbic_connect_attributes->{LongReadLen} || $self->_get_dbh->{LongReadLen} || 8000;
}
# Determine if a data_type is some type of BLOB
sub _is_lob_type {
my ($self, $data_type) = @_;
- $data_type && ($data_type =~ /(?:lob|bfile|text|image|bytea|memo)/i
- || $data_type =~ /^long(?:\s*(?:raw|bit\s*varying|varbit|binary
+ $data_type && ($data_type =~ /lob|bfile|text|image|bytea|memo/i
+ || $data_type =~ /^long(?:\s+(?:raw|bit\s*varying|varbit|binary
|varchar|character\s*varying|nvarchar
- |national\s*character\s*varying))?$/xi);
+ |national\s*character\s*varying))?\z/xi);
+}
+
+sub _is_binary_lob_type {
+ my ($self, $data_type) = @_;
+ $data_type && ($data_type =~ /blob|bfile|image|bytea/i
+ || $data_type =~ /^long(?:\s+(?:raw|bit\s*varying|varbit|binary))?\z/xi);
+}
+
+sub _is_text_lob_type {
+ my ($self, $data_type) = @_;
+ $data_type && ($data_type =~ /^(?:clob|memo)\z/i
+ || $data_type =~ /^long(?:\s+(?:varchar|character\s*varying|nvarchar
+ |national\s*character\s*varying))\z/xi);
}
1;