better type info for Sybase ASE and better data type tests
Rafael Kitover [Sun, 21 Mar 2010 22:03:01 +0000 (18:03 -0400)]
lib/DBIx/Class/Schema/Loader/DBI/Sybase.pm
lib/DBIx/Class/Schema/Loader/DBI/Sybase/Common.pm
t/12pg_common.t
t/15sybase_common.t
t/lib/dbixcsl_common_tests.pm

index 186581f..865cb92 100644 (file)
@@ -10,20 +10,12 @@ our $VERSION = '0.05003';
 
 =head1 NAME
 
-DBIx::Class::Schema::Loader::DBI::Sybase - DBIx::Class::Schema::Loader::DBI Sybase Implementation.
-
-=head1 SYNOPSIS
-
-  package My::Schema;
-  use base qw/DBIx::Class::Schema::Loader/;
-
-  __PACKAGE__->loader_options( debug => 1 );
-
-  1;
+DBIx::Class::Schema::Loader::DBI::Sybase - DBIx::Class::Schema::Loader::DBI
+Sybase ASE Implementation.
 
 =head1 DESCRIPTION
 
-See L<DBIx::Class::Schema::Loader::Base>.
+See L<DBIx::Class::Schema::Loader> and L<DBIx::Class::Schema::Loader::Base>.
 
 =cut
 
@@ -229,11 +221,11 @@ sub _columns_info_for {
 
     my $dbh = $self->schema->storage->dbh;
     my $sth = $dbh->prepare(qq{
-SELECT c.name name, t.name type, cm.text deflt, c.prec prec, c.scale scale,
-        c.length len
+SELECT c.name name, bt.name base_type, ut.name user_type, cm.text deflt, c.prec prec, c.scale scale, c.length len
 FROM syscolumns c
 JOIN sysobjects o ON c.id = o.id
-LEFT JOIN systypes t ON c.type = t.type AND c.usertype = t.usertype
+LEFT JOIN systypes bt ON c.type     = bt.type 
+LEFT JOIN systypes ut ON c.usertype = ut.usertype
 LEFT JOIN syscomments cm
     ON cm.id = CASE WHEN c.cdefault = 0 THEN c.computedcol ELSE c.cdefault END
 WHERE o.name = @{[ $dbh->quote($table) ]} AND o.type = 'U'
@@ -243,7 +235,7 @@ WHERE o.name = @{[ $dbh->quote($table) ]} AND o.type = 'U'
     my $info = $sth->fetchall_hashref('name');
 
     while (my ($col, $res) = each %$result) {
-        my $data_type = $res->{data_type} = $info->{$col}{type};
+        my $data_type = $res->{data_type} = $info->{$col}{user_type} || $info->{$col}{base_type};
 
         if ($data_type && $data_type =~ /^timestamp\z/i) {
             $res->{inflate_datetime} = 0;
@@ -257,6 +249,9 @@ WHERE o.name = @{[ $dbh->quote($table) ]} AND o.type = 'U'
                 if ($function =~ /^getdate\b/) {
                     $res->{inflate_datetime} = 1;
                 }
+
+                delete $res->{size};
+                $res->{data_type} = undef;
             }
             elsif ($default =~ /^DEFAULT \s+ (\S+)/ix) {
                 my ($constant_default) = $1 =~ /^['"\[\]]?(.*?)['"\[\]]?\z/;
@@ -264,19 +259,35 @@ WHERE o.name = @{[ $dbh->quote($table) ]} AND o.type = 'U'
             }
         }
 
-# XXX we need to handle "binary precision" for FLOAT(X)
-# (see: http://msdn.microsoft.com/en-us/library/aa258876(SQL.80).aspx )
         if (my $data_type = $res->{data_type}) {
-            if ($data_type =~ /^(?:text|unitext|image|bigint|int|integer|smallint|tinyint|real|double|double precision|float|date|time|datetime|smalldatetime|money|smallmoney|timestamp|bit)\z/i) {
+            if ($data_type eq 'int') {
+                $data_type = $res->{data_type} = 'integer';
+            }
+            elsif ($data_type eq 'decimal') {
+                $data_type = $res->{data_type} = 'numeric';
+            }
+
+            if ($data_type =~ /^(?:text|unitext|image|bigint|integer|smallint|tinyint|real|double|double precision|float|date|time|datetime|smalldatetime|money|smallmoney|timestamp|bit)\z/i) {
                 delete $res->{size};
             }
-            elsif ($data_type =~ /^(?:numeric|decimal)\z/i) {
-                $res->{size} = [ $info->{$col}{prec}, $info->{$col}{scale} ];
+            elsif ($data_type eq 'numeric') {
+                my ($prec, $scale) = @{$info->{$col}}{qw/prec scale/};
+
+                if ($prec == 18 && $scale == 0) {
+                    delete $res->{size};
+                }
+                else {
+                    $res->{size} = [ $prec, $scale ];
+                }
             }
             elsif ($data_type =~ /^(?:unichar|univarchar)\z/i) {
                 $res->{size} /= 2;
             }
         }
+
+        if ($data_type eq 'float') {
+            $res->{data_type} = $info->{$col}{len} <= 4 ? 'real' : 'double precision';
+        }
     }
 
     return $result;
@@ -299,6 +310,7 @@ sub _extra_column_info {
 
 =head1 SEE ALSO
 
+L<DBIx::Class::Schema::Loader::DBI::Sybase::Common>,
 L<DBIx::Class::Schema::Loader>, L<DBIx::Class::Schema::Loader::Base>,
 L<DBIx::Class::Schema::Loader::DBI>
 
index 676efd8..0278cf4 100644 (file)
@@ -10,12 +10,12 @@ our $VERSION = '0.05003';
 
 =head1 NAME
 
-DBIx::Class::Schema::Loader::DBI::Sybase::Common - Common functions for Sybase
+DBIx::Class::Schema::Loader::DBI::Sybase::Common - Common methods for Sybase
 and MSSQL
 
 =head1 DESCRIPTION
 
-See L<DBIx::Class::Schema::Loader::Base>.
+See L<DBIx::Class::Schema::Loader> and L<DBIx::Class::Schema::Loader::Base>.
 
 =cut
 
@@ -80,6 +80,8 @@ sub _columns_info_for {
 
 L<DBIx::Class::Schema::Loader::DBI::Sybase>,
 L<DBIx::Class::Schema::Loader::DBI::MSSQL>,
+L<DBIx::Class::Schema::Loader::DBI::ODBC::Microsoft_SQL_Server>,
+L<DBIx::Class::Schema::Loader::DBI::Sybase::Microsoft_SQL_Server>,
 L<DBIx::Class::Schema::Loader::DBI>
 L<DBIx::Class::Schema::Loader>, L<DBIx::Class::Schema::Loader::Base>,
 
index 157decf..723fc5a 100644 (file)
@@ -16,47 +16,47 @@ my $tester = dbixcsl_common_tests->new(
     user        => $user,
     password    => $password,
     data_types  => {
-       'bigint'    => { data_type => 'bigint' },
-       'int8'      => { data_type => 'bigint' },
-       'bigserial' => { data_type => 'bigint', is_auto_increment => 1 },
-       'serial8'   => { data_type => 'bigint', is_auto_increment => 1 },
-       'bit'       => { data_type => 'bit' },
-       'boolean'   => { data_type => 'boolean' },
-       'bool'      => { data_type => 'boolean' },
-       'box'       => { data_type => 'box' },
-       'bytea'     => { data_type => 'bytea' },
-       'cidr'      => { data_type => 'cidr' },
-       'circle'    => { data_type => 'circle' },
-       'date'      => { data_type => 'date' },
+       bigint    => { data_type => 'bigint' },
+       int8      => { data_type => 'bigint' },
+       bigserial => { data_type => 'bigint', is_auto_increment => 1 },
+       serial8   => { data_type => 'bigint', is_auto_increment => 1 },
+       bit       => { data_type => 'bit' },
+       boolean   => { data_type => 'boolean' },
+       bool      => { data_type => 'boolean' },
+       box       => { data_type => 'box' },
+       bytea     => { data_type => 'bytea' },
+       cidr      => { data_type => 'cidr' },
+       circle    => { data_type => 'circle' },
+       date      => { data_type => 'date' },
        'double precision' => { data_type => 'double precision' },
-       'float8'      => { data_type => 'double precision' },
-       'inet'        => { data_type => 'inet' },
-       'integer'     => { data_type => 'integer' },
-       'int'         => { data_type => 'integer' },
-       'int4'        => { data_type => 'integer' },
-       'interval'    => { data_type => 'interval' },
+       float8      => { data_type => 'double precision' },
+       inet        => { data_type => 'inet' },
+       integer     => { data_type => 'integer' },
+       int         => { data_type => 'integer' },
+       int4        => { data_type => 'integer' },
+       interval    => { data_type => 'interval' },
        'interval(2)' => { size => 2, data_type => 'interval' },
-       'line'        => { data_type => 'line' },
-       'lseg'        => { data_type => 'lseg' },
-       'macaddr'     => { data_type => 'macaddr' },
-       'money'       => { data_type => 'money' },
-       'path'        => { data_type => 'path' },
-       'point'       => { data_type => 'point' },
-       'polygon'     => { data_type => 'polygon' },
-       'real'        => { data_type => 'real' },
-       'float4'      => { data_type => 'real' },
-       'smallint'    => { data_type => 'smallint' },
-       'int2'        => { data_type => 'smallint' },
-       'serial'      => { data_type => 'integer', is_auto_increment => 1 },
-       'serial4'     => { data_type => 'integer', is_auto_increment => 1 },
-       'text'        => { data_type => 'text' },
-       'time'        => { data_type => 'time without time zone' },
+       line        => { data_type => 'line' },
+       lseg        => { data_type => 'lseg' },
+       macaddr     => { data_type => 'macaddr' },
+       money       => { data_type => 'money' },
+       path        => { data_type => 'path' },
+       point       => { data_type => 'point' },
+       polygon     => { data_type => 'polygon' },
+       real        => { data_type => 'real' },
+       float4      => { data_type => 'real' },
+       smallint    => { data_type => 'smallint' },
+       int2        => { data_type => 'smallint' },
+       serial      => { data_type => 'integer', is_auto_increment => 1 },
+       serial4     => { data_type => 'integer', is_auto_increment => 1 },
+       text        => { data_type => 'text' },
+       time        => { data_type => 'time without time zone' },
        'time(2)'     => { size => 2, data_type => 'time without time zone' },
        'time without time zone'         => { data_type => 'time without time zone' },
        'time(2) without time zone'      => { size => 2, data_type => 'time without time zone' },
        'time with time zone'            => { data_type => 'time with time zone' },
        'time(2) with time zone'         => { size => 2, data_type => 'time with time zone' },
-       'timestamp'                      => { data_type => 'timestamp without time zone' },
+       timestamp                        => { data_type => 'timestamp without time zone' },
        'timestamp(2)'                   => { size => 2, data_type => 'timestamp without time zone' },
        'timestamp without time zone'    => { data_type => 'timestamp without time zone' },
        'timestamp(2) without time zone' => { size => 2, data_type => 'timestamp without time zone' },
@@ -70,11 +70,11 @@ my $tester = dbixcsl_common_tests->new(
        'char(2)'                        => { size => 2, data_type => 'character' },
        'numeric(6, 3)'                  => { size => [6,3], data_type => 'numeric' },
        'decimal(6, 3)'                  => { size => [6,3], data_type => 'numeric' },
-        'numeric'                        => { data_type => 'numeric' },
-        'decimal'                        => { data_type => 'numeric' },
+        numeric                          => { data_type => 'numeric' },
+        decimal                          => { data_type => 'numeric' },
         'float(24)'                      => { data_type => 'real' },
         'float(53)'                      => { data_type => 'double precision' },
-        'float'                          => { data_type => 'double precision' },
+        float                            => { data_type => 'double precision' },
     },
     extra       => {
         create => [
index 83b24fe..cace91e 100644 (file)
@@ -17,135 +17,45 @@ my $tester = dbixcsl_common_tests->new(
     dsn         => $dsn,
     user        => $user,
     password    => $password,
-    extra       => {
-        create  => [
-            q{
-                CREATE TABLE sybase_loader_test1 (
-                    id INTEGER IDENTITY NOT NULL PRIMARY KEY,
-                    ts timestamp,
-                    computed_dt AS getdate()
-                )
-            },
 # Test data types, see http://ispirer.com/wiki/sqlways/sybase/data-types
-# XXX handle FLOAT(P) at some point
-# ( http://msdn.microsoft.com/en-us/library/aa258876(SQL.80).aspx )
-            q{
-                CREATE TABLE sybase_loader_test2 (
-                    id INTEGER IDENTITY NOT NULL PRIMARY KEY,
-                    a_text TEXT,
-                    a_unitext UNITEXT,
-                    an_image IMAGE,
-                    a_bigint BIGINT,
-                    an_int INT,
-                    an_integer INTEGER,
-                    a_smallint SMALLINT,
-                    a_tinyint TINYINT,
-                    a_real REAL,
-                    a_double_precision DOUBLE PRECISION,
-                    a_date DATE,
-                    a_time TIME,
-                    a_datetime DATETIME,
-                    a_smalldatetime SMALLDATETIME,
-                    a_money MONEY,
-                    a_smallmoney SMALLMONEY,
-                    a_timestamp timestamp,
-                    a_bit BIT,
-                    a_char_with_precision CHAR(2),
-                    an_nchar_with_precision NCHAR(2),
-                    a_unichar_with_precision UNICHAR(2),
-                    a_varchar_with_precision VARCHAR(2),
-                    an_nvarchar_with_precision NVARCHAR(2),
-                    a_univarchar_with_precision UNIVARCHAR(2),
-                    a_float FLOAT,
-                    a_binary_with_precision BINARY(2),
-                    a_varbinary_with_precision VARBINARY(2),
-                    the_numeric NUMERIC(6,3),
-                    the_decimal DECIMAL(6,3)
-                )
-            },
-        ],
-        drop  => [ qw/ sybase_loader_test1 sybase_loader_test2 / ],
-        count => 37,
-        run   => sub {
-            my ($schema, $monikers, $classes) = @_;
-
-            my $rs = $schema->resultset($monikers->{sybase_loader_test1});
-            my $rsrc = $rs->result_source;
-
-            is $rsrc->column_info('id')->{data_type},
-                'int',
-                'INTEGER IDENTITY data_type is correct';
-
-            is $rsrc->column_info('id')->{is_auto_increment},
-                1,
-                'INTEGER IDENTITY is_auto_increment => 1';
-
-            is $rsrc->column_info('ts')->{data_type},
-               'timestamp',
-               'timestamps have the correct data_type';
-
-            is $rsrc->column_info('ts')->{inflate_datetime},
-                0,
-                'timestamps have inflate_datetime => 0';
-
-            ok ((exists $rsrc->column_info('computed_dt')->{data_type}
-              && (not defined $rsrc->column_info('computed_dt')->{data_type})),
-                'data_type for computed column exists and is undef')
-            or diag "Data type is: ",
-                $rsrc->column_info('computed_dt')->{data_type}
-            ;
-
-            my $computed_dt_default =
-                $rsrc->column_info('computed_dt')->{default_value};
-
-            ok ((ref $computed_dt_default eq 'SCALAR'),
-                'default_value for computed column is a scalar ref')
-            or diag "default_value is: ", $computed_dt_default
-            ;
-
-            eval { is $$computed_dt_default,
-                'getdate()',
-                'default_value for computed column is correct' };
-
-            $rsrc = $schema->resultset($monikers->{sybase_loader_test2})
-                ->result_source;
-            my @type_columns = grep /^a/, $rsrc->columns;
-            my @without_precision = grep !/_with_precision\z/, @type_columns;
-            my @with_precision    = grep  /_with_precision\z/, @type_columns;
-            my %with_precision;
-            @with_precision{
-                apply { s/_with_precision\z// } @with_precision
-            } = ();
-
-            for my $col (@without_precision) {
-                my ($data_type) = $col =~ /^an?_(.*)/;
-                $data_type =~ s/_/ /g;
-
-                ok((not exists $rsrc->column_info($col)->{size}),
-                    "$data_type " .
-                    (exists $with_precision{$col} ? 'without precision ' : '') .
-                    "has no 'size' column_info")
-                or diag "size is ".$rsrc->column_info($col)->{size}."\n";
-            }
-
-            for my $col (@with_precision) {
-                my ($data_type) = $col =~ /^an?_(.*)_with_precision\z/;
-                ($data_type = uc $data_type) =~ s/_/ /g;
-                my $size = $rsrc->column_info($col)->{size};
-
-                is $size, 2,
-                  "$data_type with precision has a correct 'size' column_info";
-            }
-
-            is_deeply $rsrc->column_info('the_numeric')->{size}, [6,3],
-                'size for NUMERIC(precision, scale) is correct';
-
-            is_deeply $rsrc->column_info('the_decimal')->{size}, [6,3],
-                'size for DECIMAL(precision, scale) is correct';
-
-            is $schema->resultset($monikers->{loader_test35})->result_source->column_info('a_function')->{inflate_datetime}, 1,
-                'AS getdate() columns get inflate_datetime => 1';
-        },
+    data_types  => {
+        'integer identity' => { data_type => 'integer', is_auto_increment => 1 },
+        'AS getdate()'     => { data_type => undef, inflate_datetime => 1, default_value => \'getdate()' },
+        text     => { data_type => 'text' },
+        unitext  => { data_type => 'unitext' },
+        image    => { data_type => 'image' },
+        bigint   => { data_type => 'bigint' },
+        int      => { data_type => 'integer' },
+        integer  => { data_type => 'integer' },
+        smallint => { data_type => 'smallint' },
+        tinyint  => { data_type => 'tinyint' },
+        date     => { data_type => 'date' },
+        time     => { data_type => 'time' },
+        datetime => { data_type => 'datetime' },
+        smalldatetime  => { data_type => 'smalldatetime' },
+        money          => { data_type => 'money' },
+        smallmoney     => { data_type => 'smallmoney' },
+        timestamp      => { data_type => 'timestamp', inflate_datetime => 0 },
+        bit            => { data_type => 'bit' },
+        'char(2)'      => { data_type => 'char', size => 2 },
+        'nchar(2)'     => { data_type => 'nchar', size => 2 },
+        'unichar(2)'   => { data_type => 'unichar', size => 2 },
+        'varchar(2)'   => { data_type => 'varchar', size => 2 },
+        'nvarchar(2)'  => { data_type => 'nvarchar', size => 2 },
+        'univarchar(2)' => { data_type => 'univarchar', size => 2 },
+        'binary(2)'    => { data_type => 'binary', size => 2 },
+        'varbinary(2)' => { data_type => 'varbinary', size => 2 },
+        'double precision' => { data_type => 'double precision' },
+        real           => { data_type => 'real' },
+        float          => { data_type => 'double precision' },
+        'float(14)'    => { data_type => 'real' },
+        'float(15)'    => { data_type => 'real' },
+        'float(16)'    => { data_type => 'double precision' },
+        'float(48)'    => { data_type => 'double precision' },
+        'numeric(6,3)' => { data_type => 'numeric', size => [6,3] },
+        'decimal(6,3)' => { data_type => 'numeric', size => [6,3] },
+        numeric        => { data_type => 'numeric' },
+        decimal        => { data_type => 'numeric' },
     },
 );
 
index dd97705..6b0a0d7 100644 (file)
@@ -1556,12 +1556,15 @@ sub setup_data_type_tests {
     while (my ($col_def, $expected_info) = each %$types) {
         my $have_size = $col_def =~ /\(/ ? 1 : 0;
 
-        (my $type_alias = $col_def) =~ s/\([^()]+\)//g;
+        (my $type_alias = lc($col_def)) =~ s/\([^()]+\)//g;
         $type_alias =~ s/\s/_/g;
+        $type_alias =~ s/\W//g;
 
-        my $col_name = $type_alias . ($have_size ? '_with_size' : '');
+        my @size = grep defined($_), $col_def =~ /\( \s* (\d+) \s* ,? \s* (\d+)?/x;
 
-        $col_name .= $seen_col_names{$col_name} if $seen_col_names{$col_name}++;
+        my $col_name = $type_alias . ($have_size ? "_with_size_".(join '_', @size) : '');
+
+        $col_name .= "_$seen_col_names{$col_name}" if $seen_col_names{$col_name}++;
 
         $ddl .= "    $col_name $col_def,\n";