The Perl Toolchain Summit 2025 Needs You: You can help 🙏 Learn more

#############################################################################
sub wish_to_adjust_options_for_table_columns {
my ($options) = @_;
$options -> {key} = ['name'];
}
#############################################################################
sub wish_to_clarify_demands_for_table_columns {
my ($i, $options) = @_;
$i -> {REMARKS} ||= delete $i -> {label};
exists $i -> {NULLABLE} or $i -> {NULLABLE} = $i -> {name} eq 'id' ? 0 : 1;
exists $i -> {COLUMN_DEF} or $i -> {COLUMN_DEF} = undef;
$i -> {TYPE_NAME} = uc $i -> {TYPE_NAME};
if ($i -> {TYPE_NAME} eq 'INT') {
$i -> {TYPE_NAME} = 'INT4';
}
if ($i -> {TYPE_NAME} =~ /(CHAR|TEXT)/) {
$i -> {TYPE_NAME} = 'TEXT';
}
if ($i -> {TYPE_NAME} eq 'VARBINARY' or $i -> {TYPE_NAME} eq 'BLOB') {
$i -> {TYPE_NAME} = 'BYTEA';
}
if ($i -> {TYPE_NAME} eq 'LONGBLOB') {
$i -> {TYPE_NAME} = 'OID';
}
if ($i -> {TYPE_NAME} eq 'TIMESTAMP') {
$i -> {COLUMN_DEF} = 'now()';
}
if ($i -> {TYPE_NAME} eq 'DATETIME') {
$i -> {TYPE_NAME} = 'TIMESTAMP';
}
if ($i -> {TYPE_NAME} eq 'DECIMAL') {
$i -> {TYPE_NAME} = 'NUMERIC';
}
if ($i -> {TYPE_NAME} eq 'NUMERIC') {
$i -> {COLUMN_SIZE} ||= 10;
$i -> {DECIMAL_DIGITS} ||= 0;
}
if ($i -> {TYPE_NAME} =~ /(MEDIUM|BIG)INT$/) {
$i -> {TYPE_NAME} = 'INT8';
}
if ($i -> {TYPE_NAME} =~ /(TINY|SMALL)INT$/) {
$i -> {TYPE_NAME} = 'INT2';
}
if (!$i -> {NULLABLE} && $i -> {TYPE_NAME} =~ /^(NUM|INT)/ && $i -> {name} ne 'id') {
$i -> {COLUMN_DEF} ||= 0;
}
if (defined $i -> {COLUMN_DEF}) {
$i -> {COLUMN_DEF} .= '';
}
}
################################################################################
sub wish_to_explore_existing_table_columns {
my ($options) = @_;
my $existing = {};
sql_select_loop (q {
SELECT
pg_attribute.*
, pg_type.typname
, pg_attrdef.adsrc
, pg_description.description
FROM
pg_namespace
LEFT JOIN pg_class ON (
pg_class.relnamespace = pg_namespace.oid
AND pg_class.relkind = 'r'
AND pg_class.relname = ?
)
LEFT JOIN pg_attribute ON (
pg_attribute.attrelid = pg_class.oid
AND pg_attribute.attnum > 0
AND NOT pg_attribute.attisdropped
)
LEFT JOIN pg_type ON pg_attribute.atttypid = pg_type.oid
LEFT JOIN pg_attrdef ON (
pg_attrdef.adrelid = pg_attribute.attrelid
AND pg_attrdef.adnum = pg_attribute.attnum
)
LEFT JOIN pg_description ON (
pg_description.objoid = pg_attribute.attrelid
AND pg_description.objsubid = pg_attribute.attnum
)
WHERE
pg_namespace.nspname = current_schema()
},
sub {
my $name = $i -> {attname};
$existing -> {$name} = (my $r = {
name => $name,
TYPE_NAME => uc $i -> {typname},
REMARKS => $i -> {description},
NULLABLE => 1 - $i -> {attnotnull},
COLUMN_DEF => undef,
});
if (length $i -> {adsrc} && $name ne 'id') {
$r -> {COLUMN_DEF} = $i -> {adsrc} . '';
$r -> {COLUMN_DEF} =~ s{\:\:\w+$}{};
}
if ($r -> {TYPE_NAME} eq 'NUMERIC') {
$r -> {COLUMN_SIZE} = $i -> {atttypmod} >> 16;
$r -> {DECIMAL_DIGITS} = $i -> {atttypmod} - ($r -> {COLUMN_SIZE} << 16) - 4;
}
},
$options -> {table},
);
return $existing;
}
#############################################################################
sub __genereate_sql_fragment_for_column {
my ($i) = @_;
return if $i -> {SQL};
$i -> {TYPE} = $i -> {TYPE_NAME} . (
$i -> {TYPE_NAME} eq 'NUMERIC' ? " ($i->{COLUMN_SIZE}, $i->{DECIMAL_DIGITS})" :
'');
$i -> {SQL} = $i -> {TYPE};
if (defined $i -> {COLUMN_DEF}) {
if ($i -> {COLUMN_DEF} !~ /\)/) {
$i -> {COLUMN_DEF} =~ s{'}{''}g; #';
$i -> {COLUMN_DEF} = "'$i->{COLUMN_DEF}'";
}
$i -> {SQL} .= " DEFAULT $i->{COLUMN_DEF}";
}
if (!$i -> {NULLABLE}) {
$i -> {SQL} .= " NOT NULL";
}
%$i = map {$_ => $i -> {$_}} qw (name SQL REMARKS NULLABLE TYPE_NAME TYPE COLUMN_DEF);
}
#############################################################################
sub wish_to_update_demands_for_table_columns {
my ($old, $new, $options) = @_;
__adjust_column_dimensions ($old, $new, {
char => qr {^-},
decimal => 'NUMERIC',
});
__genereate_sql_fragment_for_column ($_) foreach ($old, $new);
}
#############################################################################
sub wish_to_schedule_modifications_for_table_columns {
my ($old, $new, $todo, $options) = @_;
if ($old -> {REMARKS} ne $new -> {REMARKS}) {
push @{$todo -> {comment}}, {name => $new -> {name}, REMARKS => delete $new -> {REMARKS}};
delete $old -> {REMARKS};
return if Dumper ($old) eq Dumper ($new);
}
if ($old -> {TYPE} ne $new -> {TYPE}) {
push @{$new -> {actions}}, "TYPE $new->{TYPE}";
}
if ($old -> {COLUMN_DEF} ne $new -> {COLUMN_DEF}) {
push @{$new -> {actions}}, $new -> {COLUMN_DEF} eq '' ? "DROP DEFAULT" : "SET DEFAULT $new->{COLUMN_DEF}";
}
if (!$old -> {NULLABLE} and $new -> {NULLABLE}) {
push @{$new -> {actions}}, "DROP NOT NULL";
}
if ($old -> {NULLABLE} and !$new -> {NULLABLE}) {
push @{$new -> {actions}}, "SET NOT NULL";
}
push @{$todo -> {create}}, $new;
}
#############################################################################
sub wish_to_actually_comment_table_columns {
my ($items, $options) = @_;
foreach my $i (@$items) {
$i -> {REMARKS} =~ s{'}{''}g; #'
sql_do ("COMMENT ON COLUMN $options->{table}.$i->{name} IS '$i->{REMARKS}'");
}
}
#############################################################################
sub wish_to_actually_create_table_columns {
my ($items, $options) = @_;
my @to_comment = ();
my @actions = ();
foreach my $i (@$items) {
if ($i -> {actions}) {
push @actions, map {"ALTER $i->{name} $_"} @{$i -> {actions}};
}
else {
next if $i -> {name} eq 'id';
__genereate_sql_fragment_for_column ($i);
push @actions, "ADD $i->{name} $i->{SQL}";
push @to_comment, $i if $i -> {REMARKS};
}
}
sql_do ("ALTER TABLE $options->{table} " . (join ', ', @actions)) if @actions;
wish_to_actually_comment_table_columns (\@to_comment, $options);
}
1;