Revision history for DBIx::Class
+ - Complete Sybase RDBMS support including:
+ - Support for TEXT/IMAGE columns
+ - Support for the 'money' datatype
+ - Transaction savepoints support
+ - DateTime inflation support
+ - Support for bind variables when connecting to a newer Sybase with
+ OpenClient libraries
+ - Support for connections via FreeTDS with CASTs for bind variables
+ when needed
+ - Support for interpolated variables with proper quoting when
+ connecting to an older Sybase and/or via FreeTDS
- The hashref to connection_info now accepts a 'dbh_maker'
coderef, allowing better intergration with Catalyst
- Fixed a complex prefetch + regular join regression introduced
in 0.08108
+ - Fixed insert_bulk rebless handling
+ - Fixed Storable roundtrip regression, and general serialization
+ cleanup
- SQLT related fixes:
- sqlt_type is now called on the correct storage object
- hooks can now see the correct producer_type
+ - optional SQLT requirements for e.g. deploy() bumped to 0.11002
+ - Automatically detect MySQL v3 and use INNER JOIN instead of JOIN
- POD improvements
0.08109 2009-08-18 08:35:00 (UTC)
- Support for MSSQL 'money' type
- Support for 'smalldatetime' type used in MSSQL and Sybase for
InflateColumn::DateTime
- - support for Postgres 'timestamp without timezone' type in
+ - Support for Postgres 'timestamp without timezone' type in
InflateColumn::DateTime (RT#48389)
- Added new MySQL specific on_connect_call macro 'set_strict_mode'
(also known as make_mysql_not_suck_as_much)
all_from 'lib/DBIx/Class.pm';
- test_requires 'Test::Builder' => 0.33;
- test_requires 'Test::Deep' => 0;
- test_requires 'Test::Exception' => 0;
- test_requires 'Test::More' => 0.92;
- test_requires 'Test::Warn' => 0.21;
+ test_requires 'Test::Builder' => '0.33';
+ test_requires 'Test::Deep' => '0';
+ test_requires 'Test::Exception' => '0';
+ test_requires 'Test::More' => '0.92';
+ test_requires 'Test::Warn' => '0.21';
- test_requires 'File::Temp' => 0.22;
+ test_requires 'File::Temp' => '0.22';
# Core
- requires 'List::Util' => 0;
- requires 'Scalar::Util' => 0;
- requires 'Storable' => 0;
+ requires 'List::Util' => '0';
+ requires 'Scalar::Util' => '0';
+ requires 'Storable' => '0';
# Perl 5.8.0 doesn't have utf8::is_utf8()
- requires 'Encode' => 0 if ($] <= 5.008000);
+ requires 'Encode' => '0' if ($] <= 5.008000);
# Dependencies (keep in alphabetical order)
- requires 'Carp::Clan' => 6.0;
- requires 'Class::Accessor::Grouped' => 0.09000;
- requires 'Class::C3::Componentised' => 1.0005;
- requires 'Class::Inspector' => 1.24;
- requires 'Data::Page' => 2.00;
- requires 'DBD::SQLite' => 1.25;
- requires 'DBI' => 1.605;
- requires 'JSON::Any' => 1.18;
- requires 'MRO::Compat' => 0.09;
- requires 'Module::Find' => 0.06;
- requires 'Path::Class' => 0.16;
- requires 'Scope::Guard' => 0.03;
- requires 'SQL::Abstract' => 1.56;
- requires 'SQL::Abstract::Limit' => 0.13;
- requires 'Sub::Name' => 0.04;
-
- recommends 'SQL::Translator' => 0.09004;
+ requires 'Carp::Clan' => '6.0';
+ requires 'Class::Accessor::Grouped' => '0.09000';
+ requires 'Class::C3::Componentised' => '1.0005';
+ requires 'Class::Inspector' => '1.24';
+ requires 'Data::Page' => '2.00';
+ requires 'DBD::SQLite' => '1.25';
+ requires 'DBI' => '1.605';
+ requires 'JSON::Any' => '1.18';
+ requires 'MRO::Compat' => '0.09';
+ requires 'Module::Find' => '0.06';
+ requires 'Path::Class' => '0.16';
+ requires 'Scope::Guard' => '0.03';
+ requires 'SQL::Abstract' => '1.56';
+ requires 'SQL::Abstract::Limit' => '0.13';
+ requires 'Sub::Name' => '0.04';
my %replication_requires = (
- 'Moose', => 0.87,
- 'MooseX::AttributeHelpers' => 0.21,
- 'MooseX::Types', => 0.16,
- 'namespace::clean' => 0.11,
- 'Hash::Merge', => 0.11,
+ 'Moose', => '0.87',
+ 'MooseX::AttributeHelpers' => '0.21',
+ 'MooseX::Types', => '0.16',
+ 'namespace::clean' => '0.11',
+ 'Hash::Merge', => '0.11',
);
+ # when changing also adjust $DBIx::Class::minimum_sqlt_version
+ my $sqlt_recommends = '0.11002';
+
+ recommends 'SQL::Translator' => $sqlt_recommends;
+
my %force_requires_if_author = (
%replication_requires,
- # 'Module::Install::Pod::Inherit' => 0.01,
- 'Test::Pod::Coverage' => 1.04,
- 'SQL::Translator' => 0.09007,
+ # 'Module::Install::Pod::Inherit' => '0.01',
+ 'Test::Pod::Coverage' => '1.04',
+ 'SQL::Translator' => $sqlt_recommends,
# CDBI-compat related
- 'DBIx::ContextualFetch' => 0,
- 'Class::DBI::Plugin::DeepAbstractSearch' => 0,
- 'Class::Trigger' => 0,
- 'Time::Piece::MySQL' => 0,
- 'Clone' => 0,
- 'Date::Simple' => 3.03,
+ 'DBIx::ContextualFetch' => '0',
+ 'Class::DBI::Plugin::DeepAbstractSearch' => '0',
+ 'Class::Trigger' => '0',
+ 'Time::Piece::MySQL' => '0',
+ 'Clone' => '0',
+ 'Date::Simple' => '3.03',
# t/52cycle.t
- 'Test::Memory::Cycle' => 0,
- 'Devel::Cycle' => 1.10,
+ 'Test::Memory::Cycle' => '0',
+ 'Devel::Cycle' => '1.10',
# t/36datetime.t
# t/60core.t
- 'DateTime::Format::SQLite' => 0,
+ 'DateTime::Format::SQLite' => '0',
# t/96_is_deteministic_value.t
- 'DateTime::Format::Strptime'=> 0,
+ 'DateTime::Format::Strptime'=> '0',
# database-dependent reqs
#
$ENV{DBICTEST_PG_DSN}
? (
- 'Sys::SigAction' => 0,
- 'DBD::Pg' => 2.009002,
- 'DateTime::Format::Pg' => 0,
+ 'Sys::SigAction' => '0',
+ 'DBD::Pg' => '2.009002',
+ 'DateTime::Format::Pg' => '0',
) : ()
,
$ENV{DBICTEST_MYSQL_DSN}
? (
- 'DateTime::Format::MySQL' => 0,
+ 'DateTime::Format::MySQL' => '0',
) : ()
,
$ENV{DBICTEST_ORACLE_DSN}
? (
- 'DateTime::Format::Oracle' => 0,
+ 'DateTime::Format::Oracle' => '0',
) : ()
,
+
+ $ENV{DBICTEST_SYBASE_DSN}
+ ? (
+ 'DateTime::Format::Sybase' => 0,
+ ) : ()
+ ,
);
resources 'repository' => 'http://dev.catalyst.perl.org/svnweb/bast/browse/DBIx-Class/';
resources 'MailingList' => 'http://lists.scsys.co.uk/cgi-bin/mailman/listinfo/dbix-class';
-no_index 'DBIx::Class::Storage::DBI::Sybase::Base';
+no_index 'DBIx::Class::Storage::DBI::Sybase::Common';
no_index 'DBIx::Class::SQLAHacks';
no_index 'DBIx::Class::SQLAHacks::MSSQL';
no_index 'DBIx::Class::Storage::DBI::AmbiguousGlob';
# build columns (as long as select isn't set) into a set of as/select hashes
unless ( $attrs->{select} ) {
- @colbits = map {
- ( ref($_) eq 'HASH' )
- ? $_
- : {
- (
- /^\Q${alias}.\E(.+)$/
- ? "$1"
- : "$_"
- )
- =>
- (
- /\./
- ? "$_"
- : "${alias}.$_"
- )
- }
- } ( ref($attrs->{columns}) eq 'ARRAY' ) ? @{ delete $attrs->{columns}} : (delete $attrs->{columns} || $source->columns );
+
+ my @cols = ( ref($attrs->{columns}) eq 'ARRAY' )
+ ? @{ delete $attrs->{columns}}
+ : (
+ ( delete $attrs->{columns} )
+ ||
+ $source->storage->_order_select_columns(
+ $source,
+ [ $source->columns ],
+ )
+ )
+ ;
+
+ @colbits = map {
+ ( ref($_) eq 'HASH' )
+ ? $_
+ : {
+ (
+ /^\Q${alias}.\E(.+)$/
+ ? "$1"
+ : "$_"
+ )
+ =>
+ (
+ /\./
+ ? "$_"
+ : "${alias}.$_"
+ )
+ }
+ } @cols;
}
+
# add the additional columns on
foreach ( 'include_columns', '+columns' ) {
push @colbits, map {
if ( $attrs->{join} || $attrs->{prefetch} ) {
- $self->throw_exception ('join/prefetch can not be used with a literal scalarref {from}')
+ $self->throw_exception ('join/prefetch can not be used with a custom {from}')
if ref $attrs->{from} ne 'ARRAY';
my $join = delete $attrs->{join} || {};
sub _calculate_score {
my ($self, $a, $b) = @_;
+ if (defined $a xor defined $b) {
+ return 0;
+ }
+ elsif (not defined $a) {
+ return 1;
+ }
+
if (ref $b eq 'HASH') {
my ($b_key) = keys %{$b};
if (ref $a eq 'HASH') {
my $schema = MySchema->connect('dbi:SQLite:my.db');
$schema->storage->debug(1);
- $schema->dbh_do("DROP TABLE authors");
+
+ my @stuff = $schema->storage->dbh_do(
+ sub {
+ my ($storage, $dbh, @args) = @_;
+ $dbh->do("DROP TABLE authors");
+ },
+ @column_list
+ );
$schema->resultset('Book')->search({
written_on => $schema->storage->datetime_parser(DateTime->now)
my $self = shift;
my $code = shift;
- my $dbh = $self->_dbh;
+ my $dbh = $self->_get_dbh;
return $self->$code($dbh, @_) if $self->{_in_dbh_do}
|| $self->{transaction_depth};
my $want_array = wantarray;
eval {
- $self->_verify_pid if $dbh;
- if(!$self->_dbh) {
- $self->_populate_dbh;
- $dbh = $self->_dbh;
- }
if($want_array) {
@result = $self->$code($dbh, @_);
my $tried = 0;
while(1) {
eval {
- $self->_verify_pid if $self->_dbh;
- $self->_populate_dbh if !$self->_dbh;
+ $self->_get_dbh;
$self->txn_begin;
if($want_array) {
$self->_do_connection_actions(disconnect_call_ => $_) for @actions;
- $self->_dbh->rollback unless $self->_dbh_autocommit;
+ $self->_dbh_rollback unless $self->_dbh_autocommit;
+
$self->_dbh->disconnect;
$self->_dbh(undef);
$self->{_dbh_gen}++;
# this is the internal "get dbh or connect (don't check)" method
sub _get_dbh {
my $self = shift;
+ $self->_verify_pid if $self->_dbh;
$self->_populate_dbh unless $self->_dbh;
return $self->_dbh;
}
if ($self->_dbh) { # we are connected
$driver = $self->_dbh->{Driver}{Name};
} else {
- # try to use dsn to not require being connected, the driver may still
- # force a connection in _rebless to determine version
- ($driver) = $self->_dbi_connect_info->[0] =~ /dbi:([^:]+):/i;
- $started_unconnected = 1;
+ # if connect_info is a CODEREF, we have no choice but to connect
+ if (ref $self->_dbi_connect_info->[0] &&
+ Scalar::Util::reftype($self->_dbi_connect_info->[0]) eq 'CODE') {
+ $self->_populate_dbh;
+ $driver = $self->_dbh->{Driver}{Name};
+ }
+ else {
+ # try to use dsn to not require being connected, the driver may still
+ # force a connection in _rebless to determine version
+ ($driver) = $self->_dbi_connect_info->[0] =~ /dbi:([^:]+):/i;
+ $started_unconnected = 1;
+ }
}
my $storage_class = "DBIx::Class::Storage::DBI::${driver}";
my @bind = map { [ undef, $_ ] } @do_args;
$self->_query_start($sql, @bind);
- $self->_dbh->do($sql, $attrs, @do_args);
+ $self->_get_dbh->do($sql, $attrs, @do_args);
$self->_query_end($sql, @bind);
}
if($self->{transaction_depth} == 0) {
$self->debugobj->txn_begin()
if $self->debug;
-
- # being here implies we have AutoCommit => 1
- # if the user is utilizing txn_do - good for
- # him, otherwise we need to ensure that the
- # $dbh is healthy on BEGIN
- my $dbh_method = $self->{_in_dbh_do} ? '_dbh' : 'dbh';
- $self->$dbh_method->begin_work;
-
- } elsif ($self->auto_savepoint) {
+ $self->_dbh_begin_work;
+ }
+ elsif ($self->auto_savepoint) {
$self->svp_begin;
}
$self->{transaction_depth}++;
}
+sub _dbh_begin_work {
+ my $self = shift;
+
+ # if the user is utilizing txn_do - good for him, otherwise we need to
+ # ensure that the $dbh is healthy on BEGIN.
+ # We do this via ->dbh_do instead of ->dbh, so that the ->dbh "ping"
+ # will be replaced by a failure of begin_work itself (which will be
+ # then retried on reconnect)
+ if ($self->{_in_dbh_do}) {
+ $self->_dbh->begin_work;
+ } else {
+ $self->dbh_do(sub { $_[1]->begin_work });
+ }
+}
+
sub txn_commit {
my $self = shift;
if ($self->{transaction_depth} == 1) {
my $dbh = $self->_dbh;
$self->debugobj->txn_commit()
if ($self->debug);
- $dbh->commit;
+ $self->_dbh_commit;
$self->{transaction_depth} = 0
if $self->_dbh_autocommit;
}
}
}
+sub _dbh_commit {
+ my $self = shift;
+ $self->_dbh->commit;
+}
+
sub txn_rollback {
my $self = shift;
my $dbh = $self->_dbh;
if ($self->debug);
$self->{transaction_depth} = 0
if $self->_dbh_autocommit;
- $dbh->rollback;
+ $self->_dbh_rollback;
}
elsif($self->{transaction_depth} > 1) {
$self->{transaction_depth}--;
}
}
+sub _dbh_rollback {
+ my $self = shift;
+ $self->_dbh->rollback;
+}
+
# This used to be the top-half of _execute. It was split out to make it
# easier to override in NoBindVars without duping the rest. It takes up
# all of _execute's args, and emits $sql, @bind.
## only prepped once.
sub insert_bulk {
my ($self, $source, $cols, $data) = @_;
+
+ # redispatch to insert_bulk method of storage we reblessed into, if necessary
+ if (not $self->_driver_determined) {
+ $self->_determine_driver;
+ goto $self->can('insert_bulk');
+ }
+
my %colvalues;
my $table = $source->from;
@colvalues{@$cols} = (0..$#$cols);
my ($sql, @bind) = $self->sql_maker->insert($table, \%colvalues);
- $self->_determine_driver;
-
$self->_query_start( $sql, @bind );
my $sth = $self->sth($sql);
}
sub update {
- my $self = shift @_;
- my $source = shift @_;
- $self->_determine_driver;
+ my ($self, $source, @args) = @_;
+
+# redispatch to update method of storage we reblessed into, if necessary
+ if (not $self->_driver_determined) {
+ $self->_determine_driver;
+ goto $self->can('update');
+ }
+
my $bind_attributes = $self->source_bind_attributes($source);
- return $self->_execute('update' => [], $source, $bind_attributes, @_);
+ return $self->_execute('update' => [], $source, $bind_attributes, @args);
}
return @pcols ? \@pcols : [ 1 ];
}
+#
+# Returns an ordered list of column names before they are used
+# in a SELECT statement. By default simply returns the list
+# passed in.
+#
+# This may be overridden in a specific storage when there are
+# requirements such as moving BLOB columns to the end of the
+# SELECT list.
+sub _order_select_columns {
+ #my ($self, $source, $columns) = @_;
+ return @{$_[2]};
+}
sub source_bind_attributes {
my ($self, $source) = @_;
return undef
}
+# Check if placeholders are supported at all
+sub _placeholders_supported {
+ my $self = shift;
+ my $dbh = $self->_get_dbh;
+
+ # some drivers provide a $dbh attribute (e.g. Sybase and $dbh->{syb_dynamic_supported})
+ # but it is inaccurate more often than not
+ eval {
+ local $dbh->{PrintError} = 0;
+ local $dbh->{RaiseError} = 1;
+ $dbh->do('select ?', {}, 1);
+ };
+ return $@ ? 0 : 1;
+}
+
+# Check if placeholders bound to non-string types throw exceptions
+#
+sub _typeless_placeholders_supported {
+ my $self = shift;
+ my $dbh = $self->_get_dbh;
+
+ eval {
+ local $dbh->{PrintError} = 0;
+ local $dbh->{RaiseError} = 1;
+ # this specifically tests a bind that is NOT a string
+ $dbh->do('select 1 where 1 = ?', {}, 1);
+ };
+ return $@ ? 0 : 1;
+}
+
=head2 sqlt_type
Returns the database driver name.
%{$sqltargs || {}}
};
- $self->throw_exception(q{Can't create a ddl file without SQL::Translator 0.09003: '}
- . $self->_check_sqlt_message . q{'})
- if !$self->_check_sqlt_version;
+ $self->throw_exception("Can't create a ddl file without SQL::Translator: " . $self->_sqlt_version_error)
+ if !$self->_sqlt_version_ok;
my $sqlt = SQL::Translator->new( $sqltargs );
return join('', @rows);
}
- $self->throw_exception(q{Can't deploy without SQL::Translator 0.09003: '}
- . $self->_check_sqlt_message . q{'})
- if !$self->_check_sqlt_version;
+ $self->throw_exception("Can't deploy without either SQL::Translator or a ddl_dir: " . $self->_sqlt_version_error )
+ if !$self->_sqlt_version_ok;
# sources needs to be a parser arg, but for simplicty allow at top level
# coming in
sub build_datetime_parser {
my $self = shift;
my $type = $self->datetime_parser_type(@_);
- eval "use ${type}";
- $self->throw_exception("Couldn't load ${type}: $@") if $@;
+ $self->ensure_class_loaded ($type);
return $type;
}
- {
- my $_check_sqlt_version; # private
- my $_check_sqlt_message; # private
- sub _check_sqlt_version {
- return $_check_sqlt_version if defined $_check_sqlt_version;
- eval 'use SQL::Translator "0.09003"';
- $_check_sqlt_message = $@ || '';
- $_check_sqlt_version = !$@;
- }
-
- sub _check_sqlt_message {
- _check_sqlt_version if !defined $_check_sqlt_message;
- $_check_sqlt_message;
- }
- }
=head2 is_replicating
isa=>'DBIx::Class::Storage::DBI::Replicated::Pool',
lazy_build=>1,
handles=>[qw/
- connect_replicants
+ connect_replicants
replicants
has_replicants
/],
select
select_single
columns_info_for
- /],
+ /],
);
=head2 write_handler
is=>'ro',
isa=>Object,
lazy_build=>1,
- handles=>[qw/
+ handles=>[qw/
on_connect_do
- on_disconnect_do
+ on_disconnect_do
connect_info
throw_exception
sql_maker
create_ddl_dir
deployment_statements
datetime_parser
- datetime_parser_type
- build_datetime_parser
+ datetime_parser_type
+ build_datetime_parser
last_insert_id
insert
insert_bulk
sth
deploy
with_deferred_fk_checks
- dbh_do
+ dbh_do
reload_row
- with_deferred_fk_checks
+ with_deferred_fk_checks
_prep_for_execute
- backup
- is_datatype_numeric
- _count_select
- _subq_count_select
- _subq_update_delete
- svp_rollback
- svp_begin
- svp_release
+ backup
+ is_datatype_numeric
+ _count_select
+ _subq_count_select
+ _subq_update_delete
+ _order_select_columns
+ svp_rollback
+ svp_begin
+ svp_release
/],
);
);
$self->pool($self->_build_pool)
- if $self->pool;
+ if $self->pool;
}
if (@opts{qw/balancer_type balancer_args/}) {
);
$self->balancer($self->_build_balancer)
- if $self->balancer;
+ if $self->balancer;
}
$self->_master_connect_info_opts(\%opts);
my ($class, $schema, $storage_type_args, @args) = @_;
return {
- schema=>$schema,
- %$storage_type_args,
- @args
+ schema=>$schema,
+ %$storage_type_args,
+ @args
}
}
sub _build_balancer {
my $self = shift @_;
$self->create_balancer(
- pool=>$self->pool,
+ pool=>$self->pool,
master=>$self->master,
%{$self->balancer_args},
);
my $i = 0;
$i++ while $i < @$r && (reftype($r->[$i])||'') ne 'HASH';
-# make one if none
+# make one if none
$r->[$i] = {} unless $r->[$i];
# merge if two hashes
# delete them
splice @$r, $i+1, ($#{$r} - $i), ();
+ # make sure master/replicants opts don't clash
+ my %master_opts = %{ $self->_master_connect_info_opts };
+ if (exists $opts{dbh_maker}) {
+ delete @master_opts{qw/dsn user password/};
+ }
+ delete $master_opts{dbh_maker};
+
# merge with master
- %opts = %{ merge(\%opts, $self->_master_connect_info_opts) };
+ %opts = %{ merge(\%opts, \%master_opts) };
# update
$r->[$i] = \%opts;
($result[0]) = ($coderef->(@args));
} else {
$coderef->(@args);
- }
+ }
};
##Reset to the original state
- $self->read_handler($current);
+ $self->read_handler($current);
##Exception testing has to come last, otherwise you might leave the
##read_handler set to master.
if(@_) {
foreach my $source ($self->all_storages) {
$source->debug(@_);
- }
+ }
}
return $self->master->debug;
}
if(@_) {
foreach my $source ($self->all_storages) {
$source->debugobj(@_);
- }
+ }
}
return $self->master->debugobj;
}
if(@_) {
foreach my $source ($self->all_storages) {
$source->debugfh(@_);
- }
+ }
}
return $self->master->debugfh;
}
if(@_) {
foreach my $source ($self->all_storages) {
$source->debugcb(@_);
- }
+ }
}
return $self->master->debugcb;
}
my ($storage, $dbh) = @_;
eval { $dbh->do("DROP TABLE money_test") };
$dbh->do(<<'SQL');
-
CREATE TABLE money_test (
id INT IDENTITY PRIMARY KEY,
amount MONEY NULL
)
-
SQL
-
});
my $rs = $schema->resultset('Money');
});
lives_ok ( sub {
+ # start a new connection, make sure rebless works
+ my $schema = DBICTest::Schema->connect($dsn, $user, $pass);
$schema->populate ('Owners', [
[qw/id name /],
[qw/1 wiggle/],
}, 'populate with PKs supplied ok' );
lives_ok ( sub {
+ # start a new connection, make sure rebless works
+ my $schema = DBICTest::Schema->connect($dsn, $user, $pass);
$schema->populate ('BooksInLibrary', [
[qw/source owner title /],
[qw/Library 1 secrets0/],