DBIx-MyDatabaseMunger
view release on metacpan or search on metacpan
lib/DBIx/MyDatabaseMunger.pm view on Meta::CPAN
# Read the table name from the "CREATE TABLE `<NAME>` (
shift( @create_sql ) =~ m/CREATE TABLE `(.*)`/
or die "Create table SQL does not begin with CREATE TABLE!\n";
my $name = $1;
# The last line should have the table options
# ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8 COMMENT='App User'
# We don't need to understand every last option, but let's extract at least
# the ENGINE and COMMENT.
my $line = pop @create_sql;
my($table_options) = $line =~ m/\)\s*(.*)/;
# Extract the ENGINE= from the table options.
$table_options =~ s/ENGINE=(\S+)\s*//
or die "Table options lack ENGINE specification?!";
my $engine = $1;
# Drop data about AUTO_INCREMENT
$table_options =~ s/AUTO_INCREMENT=(\d+)\s*//;
# Extract the COMMENT and undo mysql ' quoting. We shouldn't have to deal
# with weird characters or backslashes in comments, so let's keep it
# simple.
my $comment;
if( $table_options =~ s/\s*COMMENT='(([^']|'')*)'// ) {
$comment = $1;
$comment =~ s/''/'/g;
}
# The remaining lines should be column definitions followed by keys.
my @columns;
my %column_definition;
my @constraints;
my %constraint_definition;
my @keys;
my %key_definition;
my @primary_key;
for my $line ( @create_sql ) {
$line =~ s/,$//; # Strip trailing commas
# Strip out DEFAULT NULL so that it is easier to compare column
# definitions.
$line =~ s/ DEFAULT NULL//;
if( $line =~ m/^\s*`([^`]+)`\s*(.*)/ ) {
my($col, $def) = ($1, $2);
push @columns, $col;
$column_definition{ $col } = $def;
} elsif( $line =~ m/^\s*PRIMARY KEY \(`(.*)`\)/ ) {
@primary_key = split( '`,`', $1 );
} elsif( $line =~ m/^\s*((UNIQUE )?KEY `([^`]+)`.*)/ ) {
my($key, $def) = ($3, $1);
push @keys, $key;
$key_definition{ $key } = $def;
} elsif( $line =~ m/^\s*
CONSTRAINT\s+`(.*)`\s+
FOREIGN\s+KEY\s+\(`(.*)`\)\s+
REFERENCES\s+`(.*)`\s+\(`(.*)`\)\s+(.*)
/x ) {
my($name, $cols, $reftable, $refcols, $cascade_opt) =
($1, $2, $3, $4, $5);
my @cols = split '`,`', $cols;
my @refcols = split '`,`', $refcols;
push @constraints, $name;
$constraint_definition{ $name } = {
name => $name,
columns => \@cols,
reference_table => $reftable,
reference_columns => \@refcols,
cascade_opt => $cascade_opt,
};
} else {
warn "Don't understand line in CREATE TABLE:\n$line";
}
}
return {
name => $name,
comment => $comment,
engine => $engine,
table_options => $table_options,
columns => \@columns,
column_definition => \%column_definition,
keys => \@keys,
key_definition => \%key_definition,
constraints => \@constraints,
constraint_definition => \%constraint_definition,
primary_key => \@primary_key,
};
}
=item $o->read_table_sql ( $table_name )
Given a table name, retrieve the table definition SQL.
=cut
sub read_table_sql : method
{
my $self = shift;
my($name) = @_;
# File slurp mode.
local $/;
open my $fh, "$self->{dir}/table/$name.sql";
my $sql = <$fh>;
close $fh;
return $sql;
}
=item $o->get_table_desc ( $table_name )
Given a table name, retrieve the parsed table definition.
=cut
sub get_table_desc : method
{
my $self = shift;
my($name) = @_;
my $sql = $self->read_table_sql( $name );
my $desc;
eval {
$desc = $self->parse_create_table_sql( $sql );
};
die "Error parsing SQL for table `$name`:\n$@" if $@;
die "Table name mismatch while reading SQL for `$name`, " .
lib/DBIx/MyDatabaseMunger.pm view on Meta::CPAN
$self->create_table_sql( $table, { no_constraints => 1 } ),
);
for my $constraint ( @{$table->{constraints}} ) {
$self->queue_add_table_constraint($table, $constraint);
}
}
=item $o->create_table_sql ( $table )
=cut
sub create_table_sql : method
{
my $self = shift;
my( $table, $opt ) = @_;
my $sql = "CREATE TABLE `$table->{name}` (\n";
for my $col ( @{ $table->{columns} } ) {
$sql .= " `$col` $table->{column_definition}{$col},\n";
}
for my $key ( sort @{ $table->{keys} } ) {
$sql .= " $table->{key_definition}{$key},\n";
}
unless( $opt->{no_constraints} ) {
for my $constraint ( sort @{$table->{constraints}} ) {
$sql .= " " . $self->constraint_sql(
$table->{constraint_definition}{$constraint}
) . ",\n";
}
}
$sql .= " PRIMARY KEY (`" . join('`,`', @{$table->{primary_key}}) . "`)\n";
$sql .= ") ENGINE=$table->{engine} $table->{table_options}";
if( $table->{comment} ) {
my $comment = $table->{comment};
$comment =~ s/'/''/g;
$sql .= " COMMENT='$comment'";
}
$sql .= "\n";
return $sql;
}
=item $o->constraint_sql ( $constraint )
=cut
sub constraint_sql : method
{
my $self = shift;
my($constraint) = @_;
return "CONSTRAINT `$constraint->{name}` FOREIGN KEY (`"
. join('`,`', @{$constraint->{columns}})
. "`) REFERENCES `$constraint->{reference_table}` (`"
. join('`,`', @{$constraint->{reference_columns}})
. "`)" . (
$constraint->{cascade_opt} ? " $constraint->{cascade_opt}" : ''
);
}
=item $o->queue_add_table_constraint ( $table, $constraint )
=cut
sub queue_add_table_constraint : method
{
my $self = shift;
my($table, $constraint) = @_;
my $def = $table->{constraint_definition}{$constraint};
$self->__queue_sql( 'add_constraint',
"Add constraint $constraint on $table->{name}.",
"ALTER TABLE `$table->{name}` ADD ".$self->constraint_sql( $def ),
);
return $self;
}
=item $o->queue_drop_table_constraint ( $table, $constraint )
=cut
sub queue_drop_table_constraint : method
{
my $self = shift;
my($table, $constraint) = @_;
$self->__queue_sql( 'drop_constraint',
"Drop constraint $constraint on $table->{name}.",
"ALTER TABLE `$table->{name}` DROP FOREIGN KEY `$constraint`",
);
return $self;
}
=item $o->queue_table_updates( $current, $desired )
=cut
sub queue_table_updates : method
{
my $self = shift;
my($current, $new) = @_;
for( my $i=0; $i < @{ $new->{columns} }; ++$i ) {
my $col = $new->{columns}[$i];
if( $current->{column_definition}{$col} ) {
unless( $current->{column_definition}{$col}
eq $new->{column_definition}{$col}
) {
$self->__queue_sql( 'modify_column',
"Modify column $col in $current->{name}\n",
"ALTER TABLE `$current->{name}` " .
"MODIFY COLUMN `$col` $new->{column_definition}{$col}",
);
}
( run in 1.666 second using v1.01-cache-2.11-cpan-524268b4103 )