* New Features / Changes
- Add quote_names connection option. When set to true automatically
sets quote_char and name_sep appropriate for your RDBMS
+ - IC::DateTime support for MSSQL over DBD::ADO
* Fixes
- Disable mysql_auto_reconnect for MySQL - depending on the ENV
my $rdbms_mssql_sybase = {
'DBD::Sybase' => '0',
};
+my $rdbms_mssql_ado = {
+ 'DBD::ADO' => '0',
+};
my $rdbms_mysql = {
'DBD::mysql' => '0',
};
},
pod => {
title => 'MSSQL support via DBD::Sybase',
- desc => 'Modules required to connect to MSSQL support via DBD::Sybase',
+ desc => 'Modules required to connect to MSSQL via DBD::Sybase',
+ },
+ },
+
+ rdbms_mssql_ado => {
+ req => {
+ %$rdbms_mssql_ado,
+ },
+ pod => {
+ title => 'MSSQL support via DBD::ADO (Windows only)',
+ desc => 'Modules required to connect to MSSQL via DBD::ADO. This particular DBD is available on Windows only',
},
},
},
},
+ test_rdbms_mssql_ado => {
+ req => {
+ $ENV{DBICTEST_MSSQL_ADO_DSN}
+ ? (
+ %$rdbms_mssql_ado,
+ ) : ()
+ },
+ },
+
test_rdbms_mssql_sybase => {
req => {
$ENV{DBICTEST_MSSQL_DSN}
-package # hide from PAUSE
- DBIx::Class::Storage::DBI::ADO;
+package DBIx::Class::Storage::DBI::ADO;
use base 'DBIx::Class::Storage::DBI';
+use mro 'c3';
use Try::Tiny;
use namespace::clean;
+=head1 NAME
+
+DBIx::Class::Storage::DBI::ADO - Support for L<DBD::ADO>
+
+=head1 DESCRIPTION
+
+This class provides a mechanism for discovering and loading a sub-class
+for a specific ADO backend, as well as some workarounds for L<DBD::ADO>. It
+should be transparent to the user.
+
+=cut
+
sub _rebless {
my $self = shift;
-# check for MSSQL
-# XXX This should be using an OpenSchema method of some sort, but I don't know
-# how.
-# Current version is stolen from Sybase.pm
- try {
- my $dbtype = @{$self->_get_dbh
- ->selectrow_arrayref(qq{sp_server_info \@attribute_id=1})
- }[2];
-
- $dbtype =~ s/\W/_/gi;
- my $subclass = "DBIx::Class::Storage::DBI::ADO::${dbtype}";
- if ($self->load_optional_class($subclass) && !$self->isa($subclass)) {
- bless $self, $subclass;
- $self->_rebless;
- }
+ my $dbtype = $self->_dbh_get_info(17);
+
+ if (not $dbtype) {
+ warn 'Unable to determine ADO driver, failling back to generic support';
+ return;
+ }
+
+ $dbtype =~ s/\W/_/gi;
+ my $subclass = "DBIx::Class::Storage::DBI::ADO::${dbtype}";
+ if ($self->load_optional_class($subclass) && !$self->isa($subclass)) {
+ bless $self, $subclass;
+ $self->_rebless;
+ }
+}
+
+# cleanup some warnings from DBD::ADO
+# RT#65563, not fixed as of DBD::ADO v2.98
+sub _dbh_get_info {
+ my $self = shift;
+
+ my $warn_handler = $SIG{__WARN__} || sub { warn @_ };
+
+ local $SIG{__WARN__} = sub {
+ $warn_handler->(@_)
+ unless $_[0] =~ m{^Missing argument in sprintf at \S+/ADO/GetInfo\.pm};
};
+
+ $self->next::method(@_);
}
# Here I was just experimenting with ADO cursor types, left in as a comment in
#}
1;
+
+=head1 AUTHOR
+
+See L<DBIx::Class/AUTHOR> and L<DBIx::Class/CONTRIBUTORS>.
+
+=head1 LICENSE
+
+You may distribute this code under the same terms as Perl itself.
+
+=cut
+# vim:sts=2 sw=2:
/;
use mro 'c3';
+=head1 NAME
+
+DBIx::Class::Storage::DBI::ADO::Microsoft_SQL_Server - Support for Microsoft
+SQL Server via DBD::ADO
+
+=head1 SYNOPSIS
+
+This subclass supports MSSQL server connections via L<DBD::ADO>.
+
+=head1 DESCRIPTION
+
+The MSSQL specific functionality is provided by
+L<DBIx::Class::Storage::DBI::MSSQL>.
+
+=head1 EXAMPLE DSN
+
+ dbi:ADO:provider=sqlncli10;server=EEEBOX\SQLEXPRESS
+
+=head1 CAVEATS
+
+=head2 identities
+
+C<_identity_method> is set to C<@@identity>, as C<SCOPE_IDENTITY()> doesn't work
+with L<DBD::ADO>. See L<DBIx::Class::Storage::DBI::MSSQL/IMPLEMENTATION NOTES>
+for caveats regarding this.
+
+=head2 truncation bug
+
+There is a bug with MSSQL ADO providers where data gets truncated based on the
+size of the bind sizes in the first prepare call:
+
+L<https://rt.cpan.org/Ticket/Display.html?id=52048>
+
+The C<ado_size> workaround is used (see L<DBD::ADO/"ADO Providers">) with the
+approximate maximum size of the data_type of the bound column, or 8000 (maximum
+VARCHAR size) if the data_type is not available.
+
+This code is incomplete and may be buggy. Particularly, C<VARCHAR(MAX)> is not
+supported yet. The data_type list for other DBs is also incomplete. Please
+report problems (and send patches.)
+
+=head2 fractional seconds
+
+Fractional seconds with L<DBIx::Class::InflateColumn::DateTime> are not
+currently supported, datetimes are truncated at the second.
+
+=cut
+
sub _rebless {
my $self = shift;
$self->_identity_method('@@identity');
}
}
-1;
-
-=head1 NAME
-
-DBIx::Class::Storage::DBI::ADO::Microsoft_SQL_Server - Support for Microsoft
-SQL Server via DBD::ADO
-
-=head1 SYNOPSIS
-
-This subclass supports MSSQL server connections via L<DBD::ADO>.
-
-=head1 DESCRIPTION
-
-The MSSQL specific functionality is provided by
-L<DBIx::Class::Storage::DBI::MSSQL>.
-
-=head2 CAVEATS
-
-=head3 identities
-
-C<_identity_method> is set to C<@@identity>, as C<SCOPE_IDENTITY()> doesn't work
-with L<DBD::ADO>. See L<DBIx::Class::Storage::DBI::MSSQL/IMPLEMENTATION NOTES>
-for caveats regarding this.
+sub datetime_parser_type {
+ 'DBIx::Class::Storage::DBI::ADO::Microsoft_SQL_Server::DateTime::Format'
+}
-=head3 truncation bug
+package # hide from PAUSE
+ DBIx::Class::Storage::DBI::ADO::Microsoft_SQL_Server::DateTime::Format;
-There is a bug with MSSQL ADO providers where data gets truncated based on the
-size of the bind sizes in the first prepare call:
+my $datetime_format = '%m/%d/%Y %I:%M:%S %p';
+my $datetime_parser;
-L<https://rt.cpan.org/Ticket/Display.html?id=52048>
+sub parse_datetime {
+ shift;
+ require DateTime::Format::Strptime;
+ $datetime_parser ||= DateTime::Format::Strptime->new(
+ pattern => $datetime_format,
+ on_error => 'croak',
+ );
+ return $datetime_parser->parse_datetime(shift);
+}
-The C<ado_size> workaround is used (see L<DBD::ADO/"ADO Providers">) with the
-approximate maximum size of the data_type of the bound column, or 8000 (maximum
-VARCHAR size) if the data_type is not available.
+sub format_datetime {
+ shift;
+ require DateTime::Format::Strptime;
+ $datetime_parser ||= DateTime::Format::Strptime->new(
+ pattern => $datetime_format,
+ on_error => 'croak',
+ );
+ return $datetime_parser->format_datetime(shift);
+}
-This code is incomplete and may be buggy. Particularly, C<VARCHAR(MAX)> is not
-supported yet. The data_type list for other DBs is also incomplete. Please
-report problems (and send patches.)
+1;
=head1 AUTHOR
-See L<DBIx::Class/CONTRIBUTORS>.
+See L<DBIx::Class/AUTHOR> and L<DBIx::Class/CONTRIBUTORS>.
=head1 LICENSE
You may distribute this code under the same terms as Perl itself.
=cut
+# vim:sts=2 sw=2:
use lib qw(t/lib);
use DBICTest;
+# Example DSN (from frew):
+# dbi:ADO:PROVIDER=sqlncli10;SERVER=tcp:172.24.2.10;MARS Connection=True;Initial Catalog=CIS;UID=cis_web;PWD=...;DataTypeCompatibility=80;
+
my ($dsn, $user, $pass) = @ENV{map { "DBICTEST_MSSQL_ADO_${_}" } qw/DSN USER PASS/};
plan skip_all => 'Set $ENV{DBICTEST_MSSQL_ADO_DSN}, _USER and _PASS to run this test'
isa_ok( $schema->storage, 'DBIx::Class::Storage::DBI::ADO::Microsoft_SQL_Server' );
+my $ver = $schema->storage->_server_info->{normalized_dbms_version};
+
+ok $ver, 'can introspect DBMS version';
+
+is $schema->storage->sql_limit_dialect, ($ver >= 9 ? 'RowNumberOver' : 'Top'),
+ 'correct limit dialect detected';
+
$schema->storage->dbh_do (sub {
my ($storage, $dbh) = @_;
- eval { $dbh->do("DROP TABLE artist") };
+ eval { local $^W = 0; $dbh->do("DROP TABLE artist") };
$dbh->do(<<'SQL');
CREATE TABLE artist (
artistid INT IDENTITY NOT NULL,
# test large column list in select
$found = $schema->resultset('Artist')->search({ name => 'foo' }, {
- select => ['artistid', 'name', map "'foo' foo_$_", 0..50],
- as => ['artistid', 'name', map "foo_$_", 0..50],
+ select => ['artistid', 'name', map \"'foo' foo_$_", 0..50],
+ as => ['artistid', 'name', map "foo_$_", 0..50],
})->first;
is $found->artistid, $new->artistid, 'select with big column list';
is $found->get_column('foo_50'), 'foo', 'last item in big column list';
# clean up our mess
END {
+ my $warn_handler = $SIG{__WARN__} || sub { warn @_ };
+ local $SIG{__WARN__} = sub {
+ $warn_handler->(@_) unless $_[0] =~ /Not a Win32::OLE object/
+ };
if (my $dbh = eval { $schema->storage->_dbh }) {
eval { $dbh->do("DROP TABLE $_") }
for qw/artist/;
}
}
-my ($dsn, $user, $pass) = @ENV{map { "DBICTEST_MSSQL_ODBC_${_}" } qw/DSN USER PASS/};
+my ($dsn, $user, $pass) = @ENV{map { "DBICTEST_MSSQL_ODBC_${_}" } qw/DSN USER PASS/};
my ($dsn2, $user2, $pass2) = @ENV{map { "DBICTEST_MSSQL_${_}" } qw/DSN USER PASS/};
+my ($dsn3, $user3, $pass3) = @ENV{map { "DBICTEST_MSSQL_ADO_${_}" } qw/DSN USER PASS/};
-if (not ($dsn || $dsn2)) {
+if (not ($dsn || $dsn2 || $dsn3)) {
plan skip_all =>
- 'Set $ENV{DBICTEST_MSSQL_ODBC_DSN} and/or $ENV{DBICTEST_MSSQL_DSN} _USER '
- .'and _PASS to run this test' .
- "\nWarning: This test drops and creates a table called 'small_dt'";
+ 'Set $ENV{DBICTEST_MSSQL_ODBC_DSN} and/or $ENV{DBICTEST_MSSQL_DSN} and/or '
+ .'$ENV{DBICTEST_MSSQL_ADO_DSN} _USER and _PASS to run this test' .
+ "\nWarning: This test drops and creates tables called 'event_small_dt' and"
+ ." 'track'.";
}
plan skip_all => 'Test needs ' . DBIx::Class::Optional::Dependencies->req_missing_for ('test_dt')
my @connect_info = (
[ $dsn, $user, $pass ],
[ $dsn2, $user2, $pass2 ],
+ [ $dsn3, $user3, $pass3 ],
);
my $schema;
my $guard = Scope::Guard->new(\&cleanup);
- try { $schema->storage->dbh->do("DROP TABLE track") };
+ # $^W because DBD::ADO is a piece of crap
+ try { local $^W = 0; $schema->storage->dbh->do("DROP TABLE track") };
$schema->storage->dbh->do(<<"SQL");
CREATE TABLE track (
trackid INT IDENTITY PRIMARY KEY,
last_updated_at DATETIME,
)
SQL
- try { $schema->storage->dbh->do("DROP TABLE event_small_dt") };
+ try { local $^W = 0; $schema->storage->dbh->do("DROP TABLE event_small_dt") };
$schema->storage->dbh->do(<<"SQL");
CREATE TABLE event_small_dt (
id INT IDENTITY PRIMARY KEY,
for my $dt_type (@dt_types) {
my ($type, $col, $source, $pk, $create_extra, $sample_dt) = @$dt_type;
+ delete $sample_dt->{nanosecond} if $dsn =~ /:ADO:/;
+
ok(my $dt = DateTime->new($sample_dt));
my $row;