From: Rafael Kitover Date: Fri, 23 Jul 2010 22:27:37 +0000 (-0400) Subject: add mysql bit_as_unsigned on_connect_call option, and tests for bit type X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?a=commitdiff_plain;h=5c16029880a68acefbfbefbb445a6d9b653bbd4b;p=dbsrgits%2FDBIx-Class.git add mysql bit_as_unsigned on_connect_call option, and tests for bit type --- diff --git a/Changes b/Changes index e5d5b23..c4251c7 100644 --- a/Changes +++ b/Changes @@ -16,6 +16,7 @@ Revision history for DBIx::Class failures - Fixed distinct with order_by to not double-specify the same column in the GROUP BY clause + - add bit_as_unsigned on_connect_call option for MySQL (RT#59659) * Misc - Refactored capability handling in Storage::DBI, allows for diff --git a/lib/DBIx/Class/Storage/DBI/mysql.pm b/lib/DBIx/Class/Storage/DBI/mysql.pm index 7d80187..7663735 100644 --- a/lib/DBIx/Class/Storage/DBI/mysql.pm +++ b/lib/DBIx/Class/Storage/DBI/mysql.pm @@ -8,9 +8,13 @@ use base qw/ DBIx::Class::Storage::DBI /; use mro 'c3'; +use DBI ':sql_types'; +use namespace::clean; __PACKAGE__->sql_maker_class('DBIx::Class::SQLAHacks::MySQL'); +__PACKAGE__->mk_group_accessors(simple => qw/_bit_as/); + sub with_deferred_fk_checks { my ($self, $sub) = @_; @@ -101,6 +105,73 @@ sub _subq_update_delete { return shift->_per_row_update_delete (@_); } +# handle bit fields properly + +=head2 connect_call_bit_as_unsigned + +Used as: + + on_connect_call => 'bit_as_unsigned' + +in your L. + +Beginning in MySQL 5.0.3 C columns are stored as binary data, where before +they were an alias for C. + +See L for details. + +This option allows you to use C columns as C integers in all +versions of MySQL. + +B do not insert negative values when using this option, they will not be +inserted correctly. + +=cut + +sub connect_call_bit_as_unsigned { + my $self = shift; + + $self->_bit_as('UNSIGNED'); +} + +sub bind_attribute_by_data_type { + my $self = shift; + my ($data_type) = @_; + + my $res = $self->next::method(@_) || {}; + + if ($data_type && $self->_bit_as && lc($data_type) eq 'bit') { + $res->{TYPE} = SQL_INTEGER; + } + + return $res; +} + +sub _select_args { + my $self = shift; + my ($ident, $select) = @_; + + return $self->next::method(@_) unless $self->_bit_as; + + my $col_info = $self->_resolve_column_info($ident); + + for my $select_idx (0..$#$select) { + my $selected = $select->[$select_idx]; + + next if ref $selected; + + my $data_type = $col_info->{$selected}{data_type}; + + if ($data_type && lc($data_type) eq 'bit') { + $selected = $self->sql_maker->_quote($selected); + + $select->[$select_idx] = \("CAST($selected AS " . $self->_bit_as . ")"); + } + } + + return $self->next::method(@_); +} + 1; =head1 NAME diff --git a/t/71mysql.t b/t/71mysql.t index 8d5a323..29aca08 100644 --- a/t/71mysql.t +++ b/t/71mysql.t @@ -7,6 +7,9 @@ use lib qw(t/lib); use DBICTest; use DBI::Const::GetInfoType; use DBIC::SqlMakerTest; +use Try::Tiny; + +DBICTest::Schema->load_classes('BitField'); my ($dsn, $user, $pass) = @ENV{map { "DBICTEST_MYSQL_${_}" } qw/DSN USER PASS/}; @@ -43,6 +46,18 @@ $dbh->do("DROP TABLE IF EXISTS books;"); $dbh->do("CREATE TABLE books (id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY, source VARCHAR(100) NOT NULL, owner integer NOT NULL, title varchar(100) NOT NULL, price integer);"); +$dbh->do("DROP TABLE IF EXISTS bitfield_test;"); + +my $have_binary_bit = + $schema->storage->_server_info->{normalized_dbms_version} > 5.000002; + +if ($have_binary_bit) { + $dbh->do("CREATE TABLE bitfield_test (id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY, bitfield_1 BIT(1), bitfield_32 bit(32), bitfield_64 bit(64));"); +} +else { + $dbh->do("CREATE TABLE bitfield_test (id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY, bitfield_1 BIT(1));"); +} + #'dbi:mysql:host=localhost;database=dbic_test', 'dbic_test', ''); # make sure sqlt_type overrides work (::Storage::DBI::mysql does this) @@ -325,6 +340,73 @@ ZEROINSEARCH: { ); } +# test bit fields as binary +SKIP: { + skip 'no binary BIT(X) support in your MySQL', 4 unless $have_binary_bit; + + my $rs = $schema->resultset('BitField'); + + my $b1_bit = "\01"; + my $b32_bits = "\0xFFFFFFFF"; + my $b64_bits = "\0xFFFFFFFFFFFFFFFF"; + + ok((my $row1 = $rs->create({ + bitfield_1 => $b1_bit, bitfield_32 => $b32_bits, bitfield_64 => $b64_bits + })), 'created a row with bit columns'); + + is $row1->bitfield_1, $b1_bit, 'bit(1) field set correctly'; + is $row1->bitfield_32, $b32_bits, 'bit(32) field set correctly'; + is $row1->bitfield_64, $b64_bits, 'bit(64) field set correctly'; + + $row1->delete; +} + +# test bit fields with bit_as_unsigned +{ + my $bit_schema = DBICTest::Schema->connect ($dsn, $user, $pass, { + on_connect_call => 'bit_as_unsigned' + }); + + my $rs = $bit_schema->resultset('BitField'); + + require Math::BigInt; + + my $i32_bits = Math::BigInt->new('0xFFFFFFFF'); + my $i64_bits = Math::BigInt->new('0xFFFFFFFFFFFFFFFF'); + + ok((my $row1 = $rs->create({ + bitfield_1 => 1, + ($have_binary_bit ? + (bitfield_32 => $i32_bits, bitfield_64 => $i64_bits) : + () + ), + })), 'created a row with bit columns'); + + $row1->discard_changes; + + is $row1->bitfield_1, 1, 'bit(1) field set to 1 correctly'; + + SKIP: { + skip 'no binary BIT(X) support in your MySQL', 2 unless $have_binary_bit; + + is $row1->bitfield_32, $i32_bits, 'bit(32) field set correctly'; + is $row1->bitfield_64, $i64_bits, 'bit(64) field set correctly'; + }; + + ok((my $row2 = $rs->create({ bitfield_1 => 0 })), + 'created a row with bit columns'); + + $row2->discard_changes; + + is $row2->bitfield_1, 0, 'bit(1) field set to 0 correctly'; + + is $rs->search({ bitfield_1 => 0 })->count, 1, + 'correct count of rows with bit field set to 0'; + + is $rs->search({ bitfield_1 => 1 })->count, 1, + 'correct count of rows with bit field set to 1'; +} + ## If find() is the first query after connect() ## DBI::Storage::sql_maker() will be called before ## _determine_driver() and so the ::SQLHacks class for MySQL @@ -335,3 +417,12 @@ $schema2->resultset("Artist")->find(4); isa_ok($schema2->storage->sql_maker, 'DBIx::Class::SQLAHacks::MySQL'); done_testing; + +END { + if (my $dbh = try { $schema->storage->dbh }) { + foreach my $table (qw/artist cd producer cd_to_producer owners books + bitfield_test/) { + $dbh->do("DROP TABLE $table"); + } + } +} diff --git a/t/lib/DBICTest/Schema/BitField.pm b/t/lib/DBICTest/Schema/BitField.pm new file mode 100644 index 0000000..38c7aba --- /dev/null +++ b/t/lib/DBICTest/Schema/BitField.pm @@ -0,0 +1,30 @@ +package # hide from PAUSE + DBICTest::Schema::BitField; + +use base qw/DBICTest::BaseResult/; + +__PACKAGE__->table('bitfield_test'); +__PACKAGE__->add_columns( + 'id' => { + data_type => 'integer', + is_auto_increment => 1, + }, + 'bitfield_1' => { + data_type => 'bit', + size => 1, + is_nullable => 1, + }, + 'bitfield_32' => { + data_type => 'bit', + size => 32, + is_nullable => 1, + }, + 'bitfield_64' => { + data_type => 'bit', + size => 64, + is_nullable => 1, + }, +); +__PACKAGE__->set_primary_key('id'); + +1;