Skip to content

Commit

Permalink
Handle addition of enum values in ::Producer::PostgreSQL->alter_field
Browse files Browse the repository at this point in the history
Now, when producing a diff between two enum columns where the list
of values are known, and when the postgres version is greater than
9.001, the producer will generate an ALTER TYPE ... ADD VALUE
statments if the new enum includes values that the old one did not.
This is an easy case to support because it does not involve
re-creating the type.

The harder case of removing an enum value is not supported, yet.
The output includes a SQL comment to that effect, giving the
user a note that they need to perform that step on their own.
  • Loading branch information
nrdvana committed Nov 2, 2023
1 parent f114d3c commit 5c70879
Show file tree
Hide file tree
Showing 3 changed files with 73 additions and 13 deletions.
56 changes: 44 additions & 12 deletions lib/SQL/Translator/Producer/PostgreSQL.pm
Original file line number Diff line number Diff line change
Expand Up @@ -493,6 +493,20 @@ sub create_view {
return $create;
}

# Returns a enum custom type name and list of values iff the field looks like an enum.
sub _enum_typename_and_values {
my $field = shift;
if (ref $field->extra->{list} eq 'ARRAY') { # can't do anything unless we know the list
if ($field->extra->{custom_type_name}) {
return ( $field->extra->{custom_type_name}, $field->extra->{list} );
} elsif ($field->data_type eq 'enum') {
my $name= $field->table->name . '_' . $field->name . '_type';
return ( $name, $field->extra->{list} );
}
}
return ();
}

{

my %field_name_scope;
Expand Down Expand Up @@ -524,18 +538,17 @@ sub create_view {
#
my $data_type = lc $field->data_type;
my %extra = $field->extra;
my $list = $extra{'list'} || [];
my $commalist = join( ', ', map { __PACKAGE__->_quote_string($_) } @$list );

if ($postgres_version >= 8.003 && $data_type eq 'enum') {
my $type_name = $extra{'custom_type_name'} || $field->table->name . '_' . $field->name . '_type';
$field_def .= ' '. $type_name;
my $new_type_def = "DROP TYPE IF EXISTS $type_name CASCADE;\n" .
"CREATE TYPE $type_name AS ENUM ($commalist)";
if (! exists $type_defs->{$type_name} ) {
$type_defs->{$type_name} = $new_type_def;
} elsif ( $type_defs->{$type_name} ne $new_type_def ) {
die "Attempted to redefine type name '$type_name' as a different type.\n";
my ($enum_typename, $list) = _enum_typename_and_values($field);

if ($postgres_version >= 8.003 && $enum_typename) {
my $commalist = join( ', ', map { __PACKAGE__->_quote_string($_) } @$list );
$field_def .= ' '. $enum_typename;
my $new_type_def = "DROP TYPE IF EXISTS $enum_typename CASCADE;\n" .
"CREATE TYPE $enum_typename AS ENUM ($commalist)";
if (! exists $type_defs->{$enum_typename} ) {
$type_defs->{$enum_typename} = $new_type_def;
} elsif ( $type_defs->{$enum_typename} ne $new_type_def ) {
die "Attempted to redefine type name '$enum_typename' as a different type.\n";
}
} else {
$field_def .= ' '. convert_datatype($field);
Expand Down Expand Up @@ -896,6 +909,25 @@ sub alter_field
)
if($to_dt ne $from_dt);

my ($from_enum_typename, $from_list) = _enum_typename_and_values($from_field);
my ($to_enum_typename, $to_list ) = _enum_typename_and_values($to_field);
if ($from_enum_typename && $to_enum_typename && $from_enum_typename eq $to_enum_typename) {
# See if new enum values were added, and update the enum
my %existing_vals = map +($_ => 1), @$from_list;
my %desired_vals = map +($_ => 1), @$to_list;
my @add_vals = grep !$existing_vals{$_}, keys %desired_vals;
my @del_vals = grep !$desired_vals{$_}, keys %existing_vals;
my $pg_ver_ok= ($options->{postgres_version} || 0) >= 9.001;
push @out, '-- Set $sqlt->producer_args->{postgres_version} >= 9.001 to alter enums'
if !$pg_ver_ok && @add_vals;
for (@add_vals) {
push @out, sprintf '%sALTER TYPE %s ADD VALUE IF NOT EXISTS %s',
($pg_ver_ok? '':'-- '), $to_enum_typename, $generator->quote_string($_);
}
push @out, "-- Unimplemented: delete values from enum type '$to_enum_typename': ".join(", ", @del_vals)
if @del_vals;
}

my $old_default = $from_field->default_value;
my $new_default = $to_field->default_value;
my $default_value = $to_field->default_value;
Expand Down
28 changes: 28 additions & 0 deletions t/47postgres-producer.t
Original file line number Diff line number Diff line change
Expand Up @@ -391,6 +391,34 @@ is_deeply(
'Create real enum type works'
);

my $field5a = SQL::Translator::Schema::Field->new( name => 'enum_field',
table => $table,
data_type => 'enum',
extra => {
custom_type_name => 'mytable_enum_field_type',
list => [ 'Foo', 'Bar', 'Ba\'z' ]
},
is_auto_increment => 0,
is_nullable => 0,
is_foreign_key => 0,
is_unique => 0 );
my $field5b = SQL::Translator::Schema::Field->new( name => 'enum_field',
table => $table,
data_type => 'enum',
extra => {
custom_type_name => 'mytable_enum_field_type',
list => [ 'Foo', 'Bar', 'Ba\'z', 'Other' ]
},
is_auto_increment => 0,
is_nullable => 0,
is_foreign_key => 0,
is_unique => 0 );

$alter_field= SQL::Translator::Producer::PostgreSQL::alter_field($field5a,
$field5b,
{ postgres_version => 9.001 });
is( $alter_field, q(ALTER TYPE mytable_enum_field_type ADD VALUE IF NOT EXISTS 'Other'), 'Add value to enum' );

my $field6 = SQL::Translator::Schema::Field->new(
name => 'character',
table => $table,
Expand Down
2 changes: 1 addition & 1 deletion t/66-postgres-dbi-parser.t
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ my $sql = q[
drop table if exists sqlt_test1;
drop table if exists sqlt_products_1;
drop type if exists example_enum;
create type example_enum as enum('alpha','beta');
create table sqlt_test1 (
Expand Down

0 comments on commit 5c70879

Please sign in to comment.