# ================================================================== # Gossamer Threads Module Library - http://gossamer-threads.com/ # # GT::SQL::Driver::PG # Author : Alex Krohn # $Id: PG.pm,v 1.25 2003/02/25 23:00:28 bao Exp $ # # Copyright (c) 2000 Gossamer Threads Inc. All Rights Reserved. # ================================================================== # # Description: Postgres driver for GT::SQL # package GT::SQL::Driver::PG; # ==================================================================== use strict; use vars qw/@ISA $ERROR_PACKAGE $ERROR_MESSAGE/; use GT::SQL::Driver; use GT::AutoLoader; $ERROR_PACKAGE = 'GT::SQL'; $ERROR_MESSAGE = 'GT::SQL'; @ISA = qw/GT::SQL::Driver/; sub create_dsn { # ------------------------------------------------------------------- # Override the default create dsn, with our own. Creates DSN like: # DBI:Pg:dbname=database;host=localhost # my ($self, $connect) = @_; $connect->{driver} ||= 'mysql'; $connect->{host} ||= 'localhost'; $self->{driver} = $connect->{driver}; my $dsn = "DBI:$connect->{driver}:"; $dsn .= "dbname=$connect->{database}"; $dsn .= ";host=$connect->{host}"; $dsn .= ":$connect->{port}" if $connect->{port}; return $dsn; } sub prepare { # ------------------------------------------------------------------- # Prepares a query, doesn't go through all the wrappers that the base # driver does. # my ($self, $query) = @_; if (! defined $query) { return $self->error ('CANTPREPARE', 'WARN', "", "Empty Query"); } # We rewrite MySQL LIMIT OFFSET,MAX to PG LIMIT MAX,OFFSET if ($query =~ /^\s*SELECT/i) { $query =~ s/(\s*LIMIT\s*)(\d+)\s*,\s*(\d+)/$1$3,$2/; } $self->{query} = $query; $self->debug ("Preparing query: $query") if ($self->{_debug} > 1); $self->{sth} = $self->{dbh}->prepare ($query) or return $self->error ("CANTPREPARE", "WARN", $query); my $pkg = ref($self) . '::sth'; $self->debug ("Creating $pkg object") if ($self->{_debug} > 2); return $self->SUPER::prepare($query); } $COMPILE{_prepare_describe} = __LINE__ . <<'END_OF_SUB'; sub _prepare_describe { # ------------------------------------------------------------------ # Oracle supports USER_TAB_COLUMNS to get information # about a table. # my ($self, $query) = @_; if ($query =~ /DESCRIBE\s*(\w+)/i) { my $table = lc $1; $query =qq! SELECT a.attnum, a.attname, t.typname, a.attlen, a.atttypmod, a.attnotnull FROM pg_class c, pg_attribute a,pg_type t WHERE relkind = 'r' AND a.attnum > 0 AND a.atttypid = t.oid AND a.attrelid = c.oid AND c.relname='$table' ORDER by a.attnum !; return $query; } else { return $self->error ('CANTPREPARE', 'WARN', $query, "Cannot get properties from db_tab_columns"); } } END_OF_SUB $COMPILE{_create_sql} = __LINE__ . <<'END_OF_SUB'; sub _create_sql { # ------------------------------------------------------------------- # Takes a table schema, and generates a create table query. # my ($self, $force) = @_; my (%pos, @field_defs, @index_defs); # Sort fields for create statement based on pos attribute. foreach my $field (keys %{$self->{schema}->{cols}}) { $pos{$field} = $self->{schema}->{cols}->{$field}->{pos}; } # Now generate the SQL needed to create the table. foreach my $field (sort { $pos{$a} <=> $pos{$b} } keys %{$self->{schema}->{cols}}) { # Weed out the col defs that have a not true value my %field_def = map { defined $self->{schema}->{cols}->{$field}->{$_} ? ($_ => $self->{schema}->{cols}->{$field}->{$_}) : () } keys %{$self->{schema}->{cols}->{$field}}; # Get the sql for the type my $def = $field . ' ' . $self->_column_sql (\%field_def) || return; push @field_defs, $def; } # Need the table name for the create my $table = $self->{name}; my $prefix = $self->{connect}->{PREFIX}; # Create the sql for the indexes foreach my $index_name (keys %{$self->{schema}->{index}}) { next if not @{$self->{schema}->{index}->{$index_name}}; if (defined $force and $force eq 'force') { my $sth = $self->{dbh}->prepare ("DROP INDEX $prefix$table$index_name"); eval { $sth->execute if ($sth); }; $@ and $self->error( 'CANTEXECUTE', 'WARN', "DROP INDEX $prefix$table$index_name", $GT::SQL::error ); } push @index_defs, "CREATE INDEX $prefix$table$index_name ON $table (" . join (", " => @{$self->{schema}->{index}->{$index_name}}) . ")"; } foreach my $index_name (keys %{$self->{schema}->{unique}}) { next if not @{$self->{schema}->{unique}->{$index_name}}; if (defined $force and $force eq 'force') { my $sth = $self->{dbh}->prepare ("DROP INDEX $prefix$table$index_name"); eval { $sth->execute if ($sth); }; $@ and $self->error( 'CANTEXECUTE', 'WARN', "DROP INDEX $prefix$table$index_name", $GT::SQL::error ); } push @index_defs, "CREATE UNIQUE INDEX $prefix$table$index_name ON $table (" . join (", " => @{$self->{schema}->{unique}->{$index_name}}) . ")"; } # Add the primary key in my %pk = map { $_ => 1 } @{$self->{schema}->{pk}}; if (keys %pk) { push (@field_defs, "PRIMARY KEY (" . join ( ",", keys %pk) . ")"); } # Turn the array into SQL my $field_defs = join ",\n\t\t\t\t\t", @field_defs; # Create a sequence if needed. my $ai = $self->{schema}->{ai}; if ($ai) { my $seq = $table . "_seq"; if (defined $force and $force eq 'force') { my $sth = $self->{dbh}->prepare("DROP SEQUENCE $seq"); eval { $sth->execute if ($sth); }; $@ and $self->error( 'CANTEXECUTE', 'WARN', "DROP SEQUENCE $seq", $GT::SQL::error ); } push @index_defs, "CREATE SEQUENCE $seq INCREMENT 1 START 1"; } return qq!CREATE TABLE $table ( $field_defs )!, @index_defs; } END_OF_SUB $COMPILE{_drop_sql} = __LINE__ . <<'END_OF_SUB'; sub _drop_sql { # ------------------------------------------------------------------- # Drop a sequence if needed # my ($self, $table) = @_; # Need the prefix my $prefix = $self->{connect}->{PREFIX}; my $seq = $prefix . $table . "_seq"; my $lc_seq = lc $seq; my $sth = $self->{dbh}->prepare("SELECT relname FROM pg_class WHERE relname = '$lc_seq'"); $sth->execute(); my $seq_name = $sth->fetchrow_array; if ( $seq_name ) { my $sth2 = $self->{dbh}->prepare("DROP SEQUENCE $seq"); $sth2->execute or $self->error( 'CANTEXECUTE', 'WARN', "DROP SEQUENCE $seq", $GT::SQL::error );; } return "DROP TABLE $table"; } END_OF_SUB $COMPILE{_alter_drop_sql} = __LINE__ . <<'END_OF_SUB'; sub _alter_drop_sql { # ------------------------------------------------------------------- # Generates the SQL to drop a column. # my ($self, $table, $column) = @_; my (%pos, $cols); $cols = $self->{schema}->{cols}; # Sort fields for create statement based on pos attribute. foreach my $field (keys %{$cols}) { $pos{$field} = $self->{schema}->{cols}->{$field}->{pos}; } # Now generate the SQL needed to create the table based on def file. my (@results, @selected_fields); foreach my $field (sort { $pos{$a} <=> $pos{$b} } keys %{$self->{schema}->{cols}}) { next if ( $field eq $column ); my $type = lc $cols->{$field}->{type}; my $size = $cols->{$field}->{size}; my $default = ( $cols->{$field}->{default} ) ? "DEFAULT '$cols->{$field}->{default}'" : ''; my $null = ( $cols->{$field}->{not_null} ) ? 'NOT NULL' : ''; if ( $type =~ /char|bpchar|enum|bool/ ) { $type = 'varchar'; } if ( $type eq 'bool' ) { $size = 1; } if ($type =~ /int|varchar|float/ and $size) { $type = "$type($size)"; } push @results, [$field, $type, $null, '', $default]; push @selected_fields, $field; } # Loads primary keys foreach my $pk ( @{$self->{schema}->{pk}} ) { foreach my $f ( @results ) { if ( $f->[0] eq $pk ) { $f->[3] = 'PRIMARY KEY'; last; } } } foreach ( @results ) { $_ = join(' ', @$_); } my $str_fields = join(',', @results); my $str_selected = join(',', @selected_fields); # Create SQL query my $count = substr(time, -4) . int(rand(10000)); my $query = qq!; BEGIN; LOCK TABLE $table; CREATE TABLE GTTemp$count ( $str_fields ); INSERT INTO GTTemp$count ( $str_selected ) SELECT $str_selected FROM $table; DROP TABLE $table; ALTER TABLE GTTemp$count RENAME TO $table; COMMIT; !; return $query; } END_OF_SUB $COMPILE{_alter_change_sql} = __LINE__ . <<'END_OF_SUB'; sub _alter_change_sql { # ------------------------------------------------------------------- # Generates the SQL to change a column. # my ($self, $table, $column, $new_def, $old_type) = @_; $old_type = lc $old_type; my (%pos, $cols); $cols = $self->{schema}->{cols}; # Sort fields for create statement based on pos attribute. foreach my $field (keys %{$cols}) { $pos{$field} = $self->{schema}->{cols}->{$field}->{pos}; } # Now generate the SQL needed to create the table based on def file. my (@results, @selected_fields); foreach my $field (sort { $pos{$a} <=> $pos{$b} } keys %{$self->{schema}->{cols}}) { my $type = lc $cols->{$field}->{type}; my $size = $cols->{$field}->{size}; my $default = ( $cols->{$field}->{default} ) ? "DEFAULT '$cols->{$field}->{default}'" : ''; my $null = ( $cols->{$field}->{not_null} ) ? 'NOT NULL' : ''; if ( $type =~ /char|bpchar|enum|bool/ ) { $type = 'varchar'; } if ( $type eq 'bool' ) { $size = 1; } if ($type =~ /int|varchar|float/ and $size) { $type = "$type($size)"; } push @results, [$field, $type, $null, '', $default]; next if ( ($field eq $column) and ($type ne $old_type) and ($type =~ /int|float/) and ( $old_type =~ /char|varchar|text/) ); push @selected_fields, $field; } # Loads primary keys foreach my $pk ( @{$self->{schema}->{pk}} ) { foreach my $f ( @results ) { if ( $f->[0] eq $pk ) { $f->[3] = 'PRIMARY KEY'; last; } } } foreach ( @results ) { $_ = join(' ', @$_); } my $str_fields = join(',', @results); my $str_selected = join(',', @selected_fields); # Create SQL query my $count = substr(time, -4) . int(rand(10000)); # Loads unique my $unique = ''; foreach my $un (keys % { $self->{schema}->{unique} } ) { $unique .= $self->_alter_add_unique_sql($table, $un, @{$self->{schema}->{unique}->{$un}} ); } my $query = qq!; BEGIN; LOCK TABLE $table; CREATE TABLE GTTemp$count ( $str_fields ); INSERT INTO GTTemp$count ( $str_selected ) SELECT $str_selected FROM $table; DROP TABLE $table; ALTER TABLE GTTemp$count RENAME TO $table; $unique COMMIT; !; return $query; } END_OF_SUB $COMPILE{_alter_add_unique_sql} = __LINE__ . <<'END_OF_SUB'; sub _alter_add_unique_sql { # ------------------------------------------------------------------- # Generates the SQL to add a unique index. # my ($self, $table, $index_name, @index_cols) = @_; return "ALTER TABLE $table ADD CONSTRAINT $table$index_name UNIQUE (" . join(",", @index_cols) . ");"; } END_OF_SUB $COMPILE{_alter_drop_unique_sql} = __LINE__ . <<'END_OF_SUB'; sub _alter_drop_unique_sql { # ------------------------------------------------------------------- # Generates the SQL to add a unique index. # my ($self, $table, $index_name) = @_; return "DROP INDEX $table$index_name"; } END_OF_SUB $COMPILE{_alter_add_index_sql} = __LINE__ . <<'END_OF_SUB'; sub _alter_add_index_sql { # ------------------------------------------------------------------- # Generates the SQL to add an index. # my ($self, $table, $index_name, @index_cols) = @_; return "CREATE INDEX $table$index_name ON $table (" . join(",", @index_cols) . ")"; } END_OF_SUB $COMPILE{_alter_drop_index_sql} = __LINE__ . <<'END_OF_SUB'; sub _alter_drop_index_sql { # ------------------------------------------------------------------- # Generates the SQL to add a unique index. # my ($self, $table, $index_name) = @_; return "DROP INDEX $table$index_name"; } END_OF_SUB sub _insert_sql { # ------------------------------------------------------------------- # We need to fix up datetime, inserting null instead of empty strings. # my ($self, $input) = @_; my ($names, $values) = ('', ''); my @values; my $ai = $self->{schema}->{ai}; my %got; while (my ($col,$val) = each %$input) { next if ($ai and ($col eq $ai)); $got{$col} = 1; $names .= $col . ","; if ($self->{schema}->{cols}->{$col}->{time_check} and ($input->{$col} eq '')) { $values .= 'NOW(),'; } elsif (($self->{schema}->{cols}->{$col}->{type} =~ /DATE/) and ($input->{$col} eq '')) { $values .= 'NULL,'; } else { if (ref $val eq 'SCALAR') { $values .= "$$val,"; } else { $values .= "?,"; push @values, $val; } } } for my $col (keys %{$self->{schema}->{cols}}) { next if $ai and $col eq $ai or $got{$col} or not $self->{schema}->{cols}->{$col}->{not_null} or not exists $self->{schema}->{cols}->{$col}->{default}; my $default = $self->{schema}->{cols}->{$col}->{default}; $names .= $col . ","; if ($self->{schema}->{cols}->{$col}->{time_check} and $default eq '') { $values .= 'NOW(),'; } elsif ($self->{schema}->{cols}->{$col}->{type} =~ /DATE/ and $default eq '') { $values .= 'NULL,'; } else { $values .= $self->quote($default) . ","; } $got{$col} = 1; } if ($ai) { my $seq = $self->{name} . '_seq'; $names .= $ai; $values .= "NEXTVAL('$seq')"; } else { chop $names; chop $values; } # Create the SQL and statement handle. my $sql = "INSERT INTO $self->{name} ($names) VALUES ($values)"; return ($sql, \@values); } sub _update_sql { # ------------------------------------------------------------------- # We need to fix up datetime, inserting null instead of empty strings. # my ($self, $set, $where) = @_; my $c = $self->{schema}->{cols}; my %set; foreach my $cond (@{$set->{cond}}) { if (ref $cond eq 'ARRAY') { if (exists $c->{$cond->[0]}) { if (($c->{$cond->[0]}->{type} =~ /DATE|TIMESTAMP/) and ($cond->[2] eq '')) { $cond->[2] = undef; } $c->{$cond->[0]}->{time_check} and $set{$cond->[0]}++; } } } foreach my $col (keys %$c) { next unless ($c->{$col}->{time_check}); next if ($set{$col}); $set->add ($col, '=', \'NOW()'); } return $self->SUPER::_update_sql($set, $where); } sub quote { # ----------------------------------------------------------- # This subroutines quotes (or not) a value given its column. # Postgres can't handle any text fields containing null # characters, so this has to go beyond the ordinary quote() in # GT::SQL::Driver by stripping out null characters. # NOTE THAT THIS WILL CAUSE PROBLEMS IF ATTEMPTING TO USE BLOBS. # my $val = pop; defined ($val) or return 'NULL'; return $$val if ref $val eq 'SCALAR'; $val =~ y/\x00//d; return (values %GT::SQL::Driver::CONN)[0]->quote($val); } package GT::SQL::Driver::PG::sth; # ==================================================================== use strict; use vars qw/@ISA $ERROR_PACKAGE $ERROR_MESSAGE/; use GT::SQL::Driver; use GT::AutoLoader; $ERROR_PACKAGE = 'GT::SQL'; $ERROR_MESSAGE = 'GT::SQL'; @ISA = qw/GT::SQL::Driver::sth/; sub execute { # ------------------------------------------------------------------- # Overrides the base method to provide a simpler execute. # my $self = shift; # Debugging, stack trace is printed if debug >= 2. my $time; if ($self->{_debug}) { $self->last_query ($self->{query}, @_); my $stack = ''; if ($self->{_debug} > 1) { $stack = GT::Base->stack_trace(1,1); $stack =~ s/
/\n /g; $stack =~ s/ / /g; $stack = "\n $stack\n" } my $query = GT::SQL::Driver::debug->replace_placeholders($self->{query}, @_); $self->debug ("Executing query: $query$stack"); $time = Time::HiRes::time() if exists $INC{"Time/HiRes.pm"}; } my $rc = $self->{sth}->execute(@_) or return $self->error ("CANTEXECUTE", "WARN", $self->{query}, $DBI::errstr); $self->{_results} = []; $self->{_insert_id} = ''; $self->{_names} = $self->{sth}->{NAME}; if ($self->{do} eq 'SELECT') { $self->{_lim_cnt} = 0; if ($self->{_limit}) { my $begin = $self->{_lim_offset} || 0; my $end = $begin + $self->{_lim_rows}; my $i = -1; while (my $rec = $self->{sth}->fetchrow_arrayref) { $i++; next if ($i < $begin); last if ($i >= $end); push @{$self->{_results}}, [ @$rec ]; # Must copy as ref is reused in DBI. } } else { $self->{_results} = $self->{sth}->fetchall_arrayref; } $self->{rows} = @{$self->{_results}}; } elsif ( $self->{do} eq 'DESCRIBE' ) { $rc = $self->_fixup_describe(); } else { $self->{rows} = $self->{sth}->rows; } if ($self->{_debug} and exists $INC{"Time/HiRes.pm"}) { my $elapsed = Time::HiRes::time() - $time; $self->debug(sprintf("Query execution took: %.6fs", $elapsed)); } return $rc; } $COMPILE{_fixup_describe} = __LINE__ . <<'END_OF_SUB'; sub _fixup_describe { # --------------------------------------------------------------- # Converts output of 'sp_columns tablename' into similiar results # of mysql's describe tablename. # my $self = shift; my @results; my $table = lc $self->{name}; my $old_cols = $self->schema->{cols}; my %lc_cols; foreach (keys % $old_cols) { $lc_cols{lc $_} = $_; } while (my $col = $self->{sth}->fetchrow_hashref) { my $field = (exists %lc_cols->{$col->{attname}} ) ? %lc_cols->{$col->{attname}} : $col->{attname}; my $type = $col->{typname}; my $size = ( $col->{atttypmod} > 4 ) ? $col->{atttypmod} - 4 : ''; my $null = ( $col->{attnotnull} ) ? 0 : 1; my $default = ''; # Load defaults values my $sth_df = $self->{dbh}->prepare("SELECT d.adsrc FROM pg_attrdef d, pg_class c WHERE d.adrelid = c.relfilenode and c.relname='$table' and adnum = $col->{attnum}"); $sth_df->execute; if ( $sth_df->rows > 0 ) { $default = $sth_df->fetchrow_array; $default =~ s/\'//g; } if ( $type eq 'bpchar' ) { $type = 'varchar' } if ( $type eq 'bool' ) { $type = 'varchar'; $size = 1; } if ($type =~ /varchar/ and !$size ) { $size = 255; } if ($type =~ /int|varchar|float/) { $type = "$type($size)"; } push @results, [$field, $type, $null, '', $default, '']; } ( $#results < 0 ) and return; # Fetch the Primary key my $que_pk = qq! SELECT ic.relname AS index_name, a.attname AS column_name,i.indisunique AS unique_key, i.indisprimary AS primary_key FROM pg_class bc, pg_class ic, pg_index i, pg_attribute a WHERE bc.oid = i.indrelid AND ic.oid = i.indexrelid AND (i.indkey[0] = a.attnum OR i.indkey[1] = a.attnum OR i.indkey[2] = a.attnum OR i.indkey[3] = a.attnum OR i.indkey[4] = a.attnum OR i.indkey[5] = a.attnum OR i.indkey[6] = a.attnum OR i.indkey[7] = a.attnum) AND a.attrelid = bc.oid AND bc.relname = '$table' !; my $sth_pk = $self->{dbh}->prepare($que_pk); $sth_pk->execute; my $indexes = {}; while ( my $col = $sth_pk->fetchrow_hashref ) { if ( $col->{primary_key} ) { $indexes->{$col->{column_name}} = "PRI"; } elsif ( $col->{unique_key} ) { $indexes->{$col->{column_name}} = "UNIQUE"; } } $sth_pk->finish(); foreach my $result ( @results ) { if ( defined $indexes->{$result->[0]} ) { $result->[3] = $indexes->{$result->[0]}; if ( $result->[1] =~ /^int/ ) { # Set extra my $sth_extra = $self->{dbh}->prepare("SELECT relname FROM pg_class WHERE relkind='S' and relname = '".$table."_seq'"); $sth_extra->execute; my $seq = $sth_extra->fetchrow_array; if ( $seq ) { $result->[5] = 'auto_increment'; } $sth_extra->finish; } } } my $old_cols = $self->{schema}->{cols}; $self->{_results} = \@results; $self->{_names} = ['Field', 'Type', 'Null', 'Key', 'Default', 'Extra']; $self->{rows} = @{$self->{_results}}; return 1; } END_OF_SUB sub fetchrow_arrayref { # --------------------------------------------------------------- my $self = shift; return shift @{$self->{_results}}; } sub fetchrow_array { # --------------------------------------------------------------- # DBI seems to return the first element when called in scalar context # so when in rome.. # my $self = shift; my $arr = shift @{$self->{_results}}; return ref $arr ? (wantarray ? @$arr : $arr->[0]) : (); } sub fetchrow { # --------------------------------------------------------------- my $self = shift; my $arr = shift @{$self->{_results}}; return ref $arr ? (wantarray ? @$arr : $arr->[0]) : (); } sub fetchrow_hashref { # --------------------------------------------------------------- my $self = shift; my $arr = shift @{$self->{_results}}; ref $arr or return; my $table = $self->{name}; my $i = 0; my %selected = map { $_ => $i++ } @{$self->{_names}}; my %lc_cols; if (exists $self->{schema}->{cols}) { my $cols = $self->{schema}->{cols}; %lc_cols = map { lc $_ => $_ } keys %$cols; } else { foreach my $table (keys %{$self->{schema}}) { foreach my $col (keys %{$self->{schema}->{$table}->{schema}->{cols}}) { $lc_cols{lc $col} = $col; } } } my %hash; foreach my $lc_col (keys %selected) { if (exists $lc_cols{$lc_col}) { $hash{$lc_cols{$lc_col}} = $arr->[$selected{$lc_col}]; } else { $hash{$lc_col} = $arr->[$selected{$lc_col}]; } } return \%hash; } sub fetchall_hashref { # --------------------------------------------------------------- my $self = shift; my @ret; while (my $hash = $self->fetchrow_hashref) { push @ret, $hash; } return \@ret; } sub fetchall_arrayref { # --------------------------------------------------------------- if ($_[1] and (ref($_[1]) eq 'HASH')) { my @ret; while (my $row = $_[0]->fetchrow_hashref) { my $hsh; for (keys %{$row}) { if (exists $_[1]->{$_}) { $hsh->{$_} = $row->{$_}; } } push @ret, $hsh; } return \@ret; } return $_[0]->{_results}; } sub rows { # -------------------------------------------------------------- return shift->{rows}; } sub finish { # -------------------------------------------------------------- my $self = shift; delete $GT::SQL::Driver::ORACLE::BINDS{$self->{query}}; $self->SUPER::finish; } sub insert_id { # ------------------------------------------------------------------- # Retrieves the current sequence. # my $self = shift; my ($table) = $self->{query} =~ /\s*insert\s*into\s*(\w+)/i; $table ||= $self->{name}; my $seq = $table . "_seq"; my $query = "SELECT CURRVAL('$seq')"; my $sth = $self->{dbh}->prepare($query) or return $self->error ('CANTPREPARE', 'FATAL', $query, $DBI::errstr); $sth->execute or return $self->error ('CANTEXECUTE', 'FATAL', $query, $DBI::errstr); my ($id) = $sth->fetchrow_array; return $id; } # ------------------------------------------------------------------------------------------------ # # DATA TYPE MAPPINGS # ------------------------------------------------------------------------------------------------ # package GT::SQL::Driver::PG::Type; # =============================================================== use strict; sub _base { my $args = shift; my $name = shift; my $out = $name; defined $args->{default} and $out .= ' DEFAULT ' . GT::SQL::Driver->quote ($args->{default}); $args->{not_null} and $out .= ' NOT NULL'; return $out; } sub TINYINT { _base(shift, 'SMALLINT'); } sub SMALLINT { _base(shift, 'SMALLINT'); } sub INT { _base(shift, 'INT'); } sub INTEGER { _base(shift, 'INTEGER'); } sub MEDIUMINT { _base(shift, 'MEDIUMINT'); } sub BIGINT { _base(shift, 'BIGINT'); } sub FLOAT { _base(shift, 'FLOAT'); } sub DOUBLE { _base(shift, 'DOUBLE'); } # Can't use defaults on timestamps in Pg. # Not quite true, use: CREATE TABLE test (x int, modtime timestamp DEFAULT CURRENT_TIMESTAMP ); # need to investigate quoting. sub DATE { delete $_[0]->{default}; _base(shift, 'DATE'); } sub DATETIME { delete $_[0]->{default}; _base(shift, 'TIMESTAMP'); } sub TIMESTAMP { delete $_[0]->{default}; _base(shift, 'TIMESTAMP'); } sub TIME { delete $_[0]->{default}; _base(shift, 'TIME'); } sub YEAR { delete $_[0]->{default}; _base(shift, 'TIMESTAMP'); } sub CHAR { my $args = shift; my $out = shift || 'VARCHAR'; ($args->{size} and $args->{size} > 255) and $args->{size} = 255; $out .= $args->{size} ? "(" . $args->{size} . ")" : "(255)"; defined $args->{default} and $out .= ' DEFAULT ' . GT::SQL::Driver->quote ($args->{default}); $args->{not_null} and $out .= ' NOT NULL'; return $out; } sub VARCHAR { CHAR(shift, 'VARCHAR'); } sub TINYBLOB { _base(shift, 'BLOB'); } sub TINYTEXT { _base(shift, 'TEXT'); } sub BLOB { _base(shift, 'BLOB'); } sub TEXT { _base(shift, 'TEXT'); } sub MEDIUMBLOB { _base(shift, 'BLOB'); } sub MEDIUMTEXT { _base(shift, 'TEXT'); } sub LONGBLOB { _base(shift, 'BLOB'); } sub ENUM { my $args = shift; my $max = 0; @{$args->{'values'}} or return; foreach my $val (@{$args->{'values'}}) { my $len = length ($val); $max = $len if ($len > $max); } my $out; if ($max > 255) { $out = 'TEXT'; } else { $out = "VARCHAR($max)"; } defined $args->{default} and $out .= ' DEFAULT ' . GT::SQL::Driver->quote ($args->{default}); $args->{not_null} and $out .= ' NOT NULL'; return $out; } 1;