From: Rafael Kitover Date: Fri, 24 Jul 2009 07:22:33 +0000 (+0000) Subject: add placeholder support detection for mssql through dbd::sybase X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?a=commitdiff_plain;h=7379eb67caa3d3d551fddca0c0d183820b57f3dc;p=dbsrgits%2FDBIx-Class-Historic.git add placeholder support detection for mssql through dbd::sybase --- diff --git a/lib/DBIx/Class/Storage/DBI/Sybase/Microsoft_SQL_Server.pm b/lib/DBIx/Class/Storage/DBI/Sybase/Microsoft_SQL_Server.pm index 27cda34..dfbe20b 100644 --- a/lib/DBIx/Class/Storage/DBI/Sybase/Microsoft_SQL_Server.pm +++ b/lib/DBIx/Class/Storage/DBI/Sybase/Microsoft_SQL_Server.pm @@ -9,6 +9,32 @@ use base qw/ /; use mro 'c3'; +sub _rebless { + my $self = shift; + my $dbh = $self->_dbh; + + my ($placeholders_supported) = eval { +# There's also $dbh->{syb_dynamic_supported} but it can be inaccurate for this +# purpose. + local $dbh->{PrintError} = 0; + $dbh->selectrow_array('select ?', {}, 1); + }; + + if (not $placeholders_supported) { + bless $self, + 'DBIx::Class::Storage::DBI::Sybase::Microsoft_SQL_Server::NoBindVars'; + $self->_rebless; + } + +# LongReadLen doesn't work with MSSQL through DBD::Sybase, and the default is +# huge on some versions of SQL server and can cause memory problems, so we +# fix it up here. + my $text_size = eval { $self->_dbi_connect_info->[-1]->{LongReadLen} } || + 32768; # the DBD::Sybase default + + $dbh->do("set textsize $text_size"); +} + 1; =head1 NAME @@ -20,14 +46,15 @@ SQL Server via DBD::Sybase This subclass supports MSSQL server connections via L. -=head1 CAVEATS +=head1 DESCRIPTION -This storage driver uses L as a base. -This means that bind variables will be interpolated (properly quoted of course) -into the SQL query itself, without using bind placeholders. +This driver tries to determine whether your version of L and +supporting libraries (usually FreeTDS) support using placeholders, if not the +storage will be reblessed to +L. -More importantly this means that caching of prepared statements is explicitly -disabled, as the interpolation renders it useless. +The MSSQL specific functionality is provided by +L. =head1 AUTHOR diff --git a/lib/DBIx/Class/Storage/DBI/Sybase/Microsoft_SQL_Server/NoBindVars.pm b/lib/DBIx/Class/Storage/DBI/Sybase/Microsoft_SQL_Server/NoBindVars.pm new file mode 100644 index 0000000..16db6d1 --- /dev/null +++ b/lib/DBIx/Class/Storage/DBI/Sybase/Microsoft_SQL_Server/NoBindVars.pm @@ -0,0 +1,53 @@ +package DBIx::Class::Storage::DBI::Sybase::Microsoft_SQL_Server::NoBindVars; + +use strict; +use warnings; + +use base qw/ + DBIx::Class::Storage::DBI::NoBindVars + DBIx::Class::Storage::DBI::Sybase::Microsoft_SQL_Server +/; +use mro 'c3'; + +sub _rebless { + my $self = shift; + + $self->disable_sth_caching(1); +} + +1; + +=head1 NAME + +DBIx::Class::Storage::DBI::Sybase::Microsoft_SQL_Server::NoBindVars - Support for Microsoft +SQL Server via DBD::Sybase without placeholders + +=head1 SYNOPSIS + +This subclass supports MSSQL server connections via DBD::Sybase when ? style +placeholders are not available. + +=head1 DESCRIPTION + +If you are using this driver then your combination of L and +libraries (most likely FreeTDS) does not support ? style placeholders. + +This storage driver uses L as a base. +This means that bind variables will be interpolated (properly quoted of course) +into the SQL query itself, without using bind placeholders. + +More importantly this means that caching of prepared statements is explicitly +disabled, as the interpolation renders it useless. + +In all other respects, it is a subclass of +L. + +=head1 AUTHOR + +See L. + +=head1 LICENSE + +You may distribute this code under the same terms as Perl itself. + +=cut diff --git a/t/74mssql.t b/t/74mssql.t index cbaffc0..55d599f 100644 --- a/t/74mssql.t +++ b/t/74mssql.t @@ -18,104 +18,128 @@ my ($dsn, $user, $pass) = @ENV{map { "DBICTEST_MSSQL_${_}" } qw/DSN USER PASS/}; plan skip_all => 'Set $ENV{DBICTEST_MSSQL_DSN}, _USER and _PASS to run this test' unless ($dsn); -plan tests => 13; +my $TESTS = 13; -my $schema = DBICTest::Schema->clone; -$schema->connection($dsn, $user, $pass); +plan tests => $TESTS * 2; -# start disconnected to test reconnection -$schema->storage->ensure_connected; -$schema->storage->_dbh->disconnect; +my @storage_types = ( + 'DBIx::Class::Storage::DBI::Sybase::Microsoft_SQL_Server', + 'DBIx::Class::Storage::DBI::Sybase::Microsoft_SQL_Server::NoBindVars', +); +my $storage_idx = -1; +my $schema; + +for my $storage_type (@storage_types) { + $storage_idx++; -isa_ok($schema->storage, 'DBIx::Class::Storage::DBI::Sybase::Microsoft_SQL_Server'); + $schema = DBICTest::Schema->clone; + + if ($storage_idx != 0) { # autodetect + $schema->storage_type($storage_type); + } -my $dbh; -lives_ok (sub { - $dbh = $schema->storage->dbh; -}, 'reconnect works'); + $schema->connection($dsn, $user, $pass); -$dbh->do("IF OBJECT_ID('artist', 'U') IS NOT NULL - DROP TABLE artist"); -$dbh->do("IF OBJECT_ID('cd', 'U') IS NOT NULL - DROP TABLE cd"); + $schema->storage->ensure_connected; -$dbh->do("CREATE TABLE artist (artistid INT IDENTITY PRIMARY KEY, name VARCHAR(100), rank INT DEFAULT '13', charfield CHAR(10) NULL);"); -$dbh->do("CREATE TABLE cd (cdid INT IDENTITY PRIMARY KEY, artist INT, title VARCHAR(100), year VARCHAR(100), genreid INT NULL, single_track INT NULL);"); + if ($storage_idx == 0 && ref($schema->storage) =~ /NoBindVars\z/) { + my $tb = Test::More->builder; + $tb->skip('no placeholders') for 1..$TESTS; + next; + } + + isa_ok($schema->storage, $storage_type); + +# start disconnected to test reconnection + $schema->storage->_dbh->disconnect; + + my $dbh; + lives_ok (sub { + $dbh = $schema->storage->dbh; + }, 'reconnect works'); + + $dbh->do("IF OBJECT_ID('artist', 'U') IS NOT NULL + DROP TABLE artist"); + $dbh->do("IF OBJECT_ID('cd', 'U') IS NOT NULL + DROP TABLE cd"); + + $dbh->do("CREATE TABLE artist (artistid INT IDENTITY PRIMARY KEY, name VARCHAR(100), rank INT DEFAULT '13', charfield CHAR(10) NULL);"); + $dbh->do("CREATE TABLE cd (cdid INT IDENTITY PRIMARY KEY, artist INT, title VARCHAR(100), year VARCHAR(100), genreid INT NULL, single_track INT NULL);"); # Just to test compat shim, Auto is in Core -$schema->class('Artist')->load_components('PK::Auto::MSSQL'); + $schema->class('Artist')->load_components('PK::Auto::MSSQL'); # Test PK -my $new = $schema->resultset('Artist')->create( { name => 'foo' } ); -ok($new->artistid, "Auto-PK worked"); + my $new = $schema->resultset('Artist')->create( { name => 'foo' } ); + ok($new->artistid, "Auto-PK worked"); # Test LIMIT -for (1..6) { - $schema->resultset('Artist')->create( { name => 'Artist ' . $_, rank => $_ } ); -} + for (1..6) { + $schema->resultset('Artist')->create( { name => 'Artist ' . $_, rank => $_ } ); + } -my $it = $schema->resultset('Artist')->search( { }, - { rows => 3, - offset => 2, - order_by => 'artistid' - } -); + my $it = $schema->resultset('Artist')->search( { }, + { rows => 3, + offset => 2, + order_by => 'artistid' + } + ); # Test ? in data don't get treated as placeholders -my $cd = $schema->resultset('CD')->create( { - artist => 1, - title => 'Does this break things?', - year => 2007, -} ); -ok($cd->id, 'Not treating ? in data as placeholders'); - -is( $it->count, 3, "LIMIT count ok" ); -ok( $it->next->name, "iterator->next ok" ); -$it->next; -$it->next; -is( $it->next, undef, "next past end of resultset ok" ); + my $cd = $schema->resultset('CD')->create( { + artist => 1, + title => 'Does this break things?', + year => 2007, + } ); + ok($cd->id, 'Not treating ? in data as placeholders'); + + is( $it->count, 3, "LIMIT count ok" ); + ok( $it->next->name, "iterator->next ok" ); + $it->next; + $it->next; + is( $it->next, undef, "next past end of resultset ok" ); # test MONEY column support -$schema->storage->dbh_do (sub { - my ($storage, $dbh) = @_; - eval { $dbh->do("DROP TABLE money_test") }; - $dbh->do(<<'SQL'); + $schema->storage->dbh_do (sub { + 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 -) + CREATE TABLE money_test ( + id INT IDENTITY PRIMARY KEY, + amount MONEY NULL + ) SQL -}); + }); -my $rs = $schema->resultset('Money'); + my $rs = $schema->resultset('Money'); -my $row; -lives_ok { - $row = $rs->create({ amount => 100 }); -} 'inserted a money value'; + my $row; + lives_ok { + $row = $rs->create({ amount => 100 }); + } 'inserted a money value'; -is $rs->find($row->id)->amount, 100, 'money value round-trip'; + is $rs->find($row->id)->amount, 100, 'money value round-trip'; -lives_ok { - $row->update({ amount => 200 }); -} 'updated a money value'; + lives_ok { + $row->update({ amount => 200 }); + } 'updated a money value'; -is $rs->find($row->id)->amount, 200, 'updated money value round-trip'; + is $rs->find($row->id)->amount, 200, 'updated money value round-trip'; -lives_ok { - $row->update({ amount => undef }); -} 'updated a money value to NULL'; + lives_ok { + $row->update({ amount => undef }); + } 'updated a money value to NULL'; -is $rs->find($row->id)->amount, undef,'updated money value to NULL round-trip'; + is $rs->find($row->id)->amount, undef,'updated money value to NULL round-trip'; +} # clean up our mess END { - $dbh->do("IF OBJECT_ID('artist', 'U') IS NOT NULL DROP TABLE artist") - if $dbh; - $dbh->do("IF OBJECT_ID('cd', 'U') IS NOT NULL DROP TABLE cd") - if $dbh; - $dbh->do("IF OBJECT_ID('money_test', 'U') IS NOT NULL DROP TABLE money_test") - if $dbh; + if (my $dbh = eval { $schema->storage->dbh }) { + $dbh->do("IF OBJECT_ID('artist', 'U') IS NOT NULL DROP TABLE artist"); + $dbh->do("IF OBJECT_ID('cd', 'U') IS NOT NULL DROP TABLE cd"); + $dbh->do("IF OBJECT_ID('money_test', 'U') IS NOT NULL DROP TABLE money_test"); + } }