#!/usr/bin/perl -w ############################################################################### =pod =head1 NAME mig-mapper Interactive session for analyzing, flagging, and mapping legacy field data to Evergreen fields. Upon exit, generate either [file].clean.map.sql or _map.sql. The SQL generated will be UPDATE's for setting the Evergreen-specific columns for a given file's staging tables, and TRUNCATE's and INSERT's for auxilary tables. The files will have \include hooks for pulling in additional mapping files (for example, end-user mappings for circ modifiers, etc.) =head1 SYNOPSIS B =cut ############################################################################### use strict; use Term::ReadLine; use Switch; use Env qw( HOME PGHOST PGPORT PGUSER PGDATABASE MIGSCHEMA MIGBASEWORKDIR MIGBASEGITDIR MIGGITDIR MIGWORKDIR ); use Pod::Usage; use DBI; use Cwd 'abs_path'; use FindBin; my $mig_bin = "$FindBin::Bin/"; use lib "$FindBin::Bin/"; use Mig; pod2usage(-verbose => 2) if ! $ARGV[0] || $ARGV[0] eq '--help'; Mig::die_if_no_env_migschema(); Mig::die_if_mig_tracking_table_does_not_exist(); my $column_filter = 1; # show all fields my $file = abs_path($ARGV[0]); my $fdata; my $tracked_file_id = Mig::check_for_tracked_file($file); if ($tracked_file_id) { $fdata = Mig::status_this_file($file); } else { die "File not currently tracked: $file\n"; } my $table = $fdata->{staged_table}; if (!$table) { die "No staged staged table for file: $file\n"; } my $loop = 1; my $term = Term::ReadLine->new('mapper'); my $prompt; my $OUT = $term->OUT || \*STDOUT; my @dtd_identifiers; table_menu(); $prompt = "$fdata->{staged_table}: "; while ( $loop && defined (my $cmd = $term->readline($prompt)) ) { top: $cmd =~ s/^\s+//; $cmd =~ s/\s+$//; $term->addhistory($cmd) if $cmd =~ /\S/; if ($cmd =~ /^\d+$/) { my $ret = column_menu($cmd); if ($ret) { $cmd = $ret; goto top; } } else { switch($cmd) { case /^(ls|\?|\.|;)$/ { table_menu(); } case '' { table_menu(); } case 'l' { list_ten(); } case 'f1' { $column_filter = 1; table_menu(); } case 'f2' { $column_filter = 2; table_menu(); } case 'f3' { $column_filter = 3; table_menu(); } } } $loop = 0 if $cmd =~ /^q/io; } exit 0; ############################################################################### sub table_menu { print "\n=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=\n"; print "$table"; print "\n=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=\n"; print "\n"; print " l) SELECT * FROM $fdata->{staged_table} LIMIT 10;\n"; print "f1) show all fields (default)\n"; print "f2) show legacy fields\n"; print "f3) show EG fields\n"; print " q) quit\n\n"; printf "%-36s", "Columns (* for required)"; printf "%-30s", "Target"; printf "%-30s", "Transform"; printf "%-30s", "First Row"; printf "%-30s", "Migration Note"; print "\n"; printf "%-36s", "-------"; printf "%-30s", "------"; printf "%-30s", "---------"; printf "%-30s", "---------"; printf "%-30s", "--------------"; print "\n"; my $dbh = Mig::db_connect(); my $sth = $dbh->prepare(" SELECT * FROM information_schema.columns WHERE table_schema = " . $dbh->quote($MIGSCHEMA) . " AND table_name = " . $dbh->quote($table) . " ORDER BY dtd_identifier::INTEGER ASC; "); my $rv = $sth->execute() || die "Error retrieving data from information_schema: $!"; my $sth2 = $dbh->prepare(" SELECT * FROM $MIGSCHEMA.$table LIMIT 1; "); my $rv2 = $sth2->execute() || die "Error retrieving data from $MIGSCHEMA.$table: $!"; my $row = $sth2->fetchrow_hashref; open LESS, "|less -F"; @dtd_identifiers = (); while (my $data = $sth->fetchrow_hashref) { my $column = $data->{column_name}; if ($column_filter == 2 && !($column =~ /^[xl]_/)) { next; } if ($column_filter == 3 && ($column =~ /^[xl]_/)) { next; } my $cdata = status_this_column($column); printf LESS $cdata->{required} ? '*' : ' '; printf LESS "%3s) ", $data->{dtd_identifier}; push @dtd_identifiers, $data->{dtd_identifier}; printf LESS "%-30s", $column; printf LESS "%-30s", defined $cdata->{target_table} ? ( $cdata->{target_table} ne $table ? $cdata->{target_table} . '.' : '') . $cdata->{target_column} : ''; printf LESS "%-30s", defined $cdata->{transform} ? $cdata->{transform} : ''; printf LESS "%-30s", defined $$row{$column} ? $$row{$column} : ''; printf LESS "%-30s", defined $cdata->{comment} ? $cdata->{comment} : ''; print LESS "\n"; } close LESS; print "\n"; $sth->finish; $sth2->finish; Mig::db_disconnect($dbh); } sub column_menu { my $dtd_identifier = shift; my $dbh = Mig::db_connect(); my $sth = $dbh->prepare(" SELECT * FROM information_schema.columns WHERE table_schema = " . $dbh->quote($MIGSCHEMA) . " AND table_name = " . $dbh->quote($table) . " AND dtd_identifier = " . $dbh->quote($dtd_identifier) . "; "); my $rv = $sth->execute() || die "Error retrieving data from information_schema: $!"; my $data = $sth->fetchrow_hashref; $sth->finish; Mig::db_disconnect($dbh); my $column = $data->{column_name}; my $prompt = "$table.$column: "; sub print_menu { my $column = shift; my $cdata = status_this_column($column); print "\n=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=\n"; print "$column"; print "\n-------------------------------------------------------------------------------------------------\n"; print " target: " . ( defined $cdata->{target_table} ? ( $cdata->{target_table} ne $table ? $cdata->{target_table} . '.' : '') . $cdata->{target_column} : '' ) . "\n"; print "transform: " . (defined $cdata->{transform} ? $cdata->{transform} : '') . "\n"; print " comment: " . (defined $cdata->{comment} ? $cdata->{comment} : '') . "\n"; print "\n=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=\n"; print "\n"; print " l) SELECT $column FROM $fdata->{staged_table} LIMIT 10;\n"; print " s) summarize\n" if $column ne 'x_migrate'; print " g) group browse\n"; print "g2) group browse (order by count desc)\n"; print " c) comment\n"; print " f) flag for end-user mapping\n"; print " t) target\n"; print " e) eval/transform\n"; print " n) next column\n"; print " p) prev column\n"; print " q) quit back to table menu\n"; print "\n"; } print_menu($column); my $loop = 1; while ( $loop && defined (my $cmd = $term->readline($prompt)) ) { $cmd =~ s/^\s+//; $cmd =~ s/\s+$//; $term->addhistory($cmd) if $cmd =~ /\S/; $loop = 0 if $cmd =~ /^q/io; switch($cmd) { case /^(ls|\?|\.|;)$/ { print_menu($column); } case '' { print_menu($column); } case 'l' { list_ten($column); } case 's' { summarize($column); } case 'g' { group_browse($column); } case 'g2' { group_browse($column,'GROUP BY 1 ORDER BY 2 DESC'); } case /^c/io { if ($cmd =~ /^c\s+(.+)$/) { set_comment($column,$1); } } case /^t/io { if ($cmd =~ /^t\s+(.+)$/) { set_target($column,$1); } } case /^e/io { if ($cmd =~ /^e\s+(.+)$/) { set_transform($column,$1); } } case 'n' { my( $index )= grep { $dtd_identifiers[$_] eq $dtd_identifier } 0..$#dtd_identifiers; return $dtd_identifiers[$index + 1]; } case 'p' { my( $index )= grep { $dtd_identifiers[$_] eq $dtd_identifier } 0..$#dtd_identifiers; return $dtd_identifiers[$index - 1]; } } } } sub list_ten { my $column = shift; my $dbh = Mig::db_connect(); my $sth; my $rv; my @cols; $sth = $dbh->prepare(Mig::sql(" SELECT " . (defined $column ? $column : '*') . " FROM $MIGSCHEMA.$table LIMIT 10; ")); $rv = $sth->execute() || die "Error retrieving data from $MIGSCHEMA.$table: $!"; print "\n"; while (@cols = $sth->fetchrow_array) { print "\t" . join(',',map {defined $_ ? $_ : ''} @cols) . "\n"; } print "\n"; $sth->finish; } sub summarize { my $column = shift; my $count; my $non_empty_count; my $distinct_value_count; my $distinct_integer_value_count; my $distinct_money6_value_count; my $distinct_money8_value_count; my $distinct_date_value_count; my $distinct_timestamptz_value_count; my $min_value; my $min_length; my $min_length_min_value; my $max_value; my $max_length; my $max_length_max_value; my $min_value_as_integer; my $max_value_as_integer; my $min_value_as_money6; my $max_value_as_money6; my $min_value_as_money8; my $max_value_as_money8; my $min_value_as_date; my $max_value_as_date; my $min_value_as_timestamptz; my $max_value_as_timestamptz; my $dbh = Mig::db_connect(); my $sth; my $rv; my @cols; ### count $sth = $dbh->prepare(" SELECT COUNT(*) FROM $MIGSCHEMA.$table; "); $rv = $sth->execute() || die "Error retrieving data from $MIGSCHEMA.$table: $!"; @cols = $sth->fetchrow_array; $sth->finish; $count = $cols[0]; ### non_empty_count $sth = $dbh->prepare(" SELECT COUNT(*) FROM $MIGSCHEMA.$table WHERE $column IS NOT NULL AND BTRIM($column) <> ''; "); $rv = $sth->execute() || die "Error retrieving data from $MIGSCHEMA.$table: $!"; @cols = $sth->fetchrow_array; $sth->finish; $non_empty_count = $cols[0]; ### distinct_value_count $sth = $dbh->prepare(" SELECT COUNT(DISTINCT $column) FROM $MIGSCHEMA.$table; "); $rv = $sth->execute() || die "Error retrieving data from $MIGSCHEMA.$table: $!"; @cols = $sth->fetchrow_array; $sth->finish; $distinct_value_count = $cols[0]; ### distinct_integer_value_count $sth = $dbh->prepare(" SELECT COUNT(DISTINCT migration_tools.attempt_cast($column,'INTEGER')::INTEGER) FROM $MIGSCHEMA.$table; "); $rv = $sth->execute() || die "Error retrieving data from $MIGSCHEMA.$table: $!"; @cols = $sth->fetchrow_array; $sth->finish; $distinct_integer_value_count = $cols[0]; ### distinct_money6_value_count $sth = $dbh->prepare(" SELECT COUNT(DISTINCT NULLIF(migration_tools.attempt_money6($column,'-0.01'),-0.01)) FROM $MIGSCHEMA.$table; "); $rv = $sth->execute() || die "Error retrieving data from $MIGSCHEMA.$table: $!"; @cols = $sth->fetchrow_array; $sth->finish; $distinct_money6_value_count = $cols[0]; ### distinct_money8_value_count $sth = $dbh->prepare(" SELECT COUNT(DISTINCT NULLIF(migration_tools.attempt_money($column,'-0.01'),-0.01)) FROM $MIGSCHEMA.$table; "); $rv = $sth->execute() || die "Error retrieving data from $MIGSCHEMA.$table: $!"; @cols = $sth->fetchrow_array; $sth->finish; $distinct_money8_value_count = $cols[0]; ### distinct_date_value_count $sth = $dbh->prepare(" SELECT COUNT(DISTINCT NULLIF(migration_tools.attempt_date($column,'1969-06-09'),'1969-06-09'::DATE)) FROM $MIGSCHEMA.$table; "); $rv = $sth->execute() || die "Error retrieving data from $MIGSCHEMA.$table: $!"; @cols = $sth->fetchrow_array; $sth->finish; $distinct_date_value_count = $cols[0]; ### distinct_timestamptz_value_count $sth = $dbh->prepare(" SELECT COUNT(DISTINCT NULLIF(migration_tools.attempt_timestamptz($column,'1969-06-09'),'1969-06-09'::TIMESTAMPTZ)) FROM $MIGSCHEMA.$table; "); $rv = $sth->execute() || die "Error retrieving data from $MIGSCHEMA.$table: $!"; @cols = $sth->fetchrow_array; $sth->finish; $distinct_timestamptz_value_count = $cols[0]; ### min_value $sth = $dbh->prepare(" SELECT MIN($column) FROM $MIGSCHEMA.$table; "); $rv = $sth->execute() || die "Error retrieving data from $MIGSCHEMA.$table: $!"; @cols = $sth->fetchrow_array; $sth->finish; $min_value = $cols[0]; ### min_length $sth = $dbh->prepare(" SELECT MIN(LENGTH($column)) FROM $MIGSCHEMA.$table; "); $rv = $sth->execute() || die "Error retrieving data from $MIGSCHEMA.$table: $!"; @cols = $sth->fetchrow_array; $sth->finish; $min_length = $cols[0]; ### min_length_min_value $sth = $dbh->prepare(" SELECT MIN($column) FROM $MIGSCHEMA.$table WHERE LENGTH($column) = $min_length; "); $rv = $sth->execute() || die "Error retrieving data from $MIGSCHEMA.$table: $!"; @cols = $sth->fetchrow_array; $sth->finish; $min_length_min_value = $cols[0]; ### min_value_as_integer $sth = $dbh->prepare(" SELECT MIN(migration_tools.attempt_cast($column,'INTEGER')::INTEGER) FROM $MIGSCHEMA.$table; "); $rv = $sth->execute() || die "Error retrieving data from $MIGSCHEMA.$table: $!"; @cols = $sth->fetchrow_array; $sth->finish; $min_value_as_integer = $cols[0]; ### min_value_as_money6 $sth = $dbh->prepare(" SELECT MIN(NULLIF(migration_tools.attempt_money6($column,'-0.01'),-0.01)) FROM $MIGSCHEMA.$table; "); $rv = $sth->execute() || die "Error retrieving data from $MIGSCHEMA.$table: $!"; @cols = $sth->fetchrow_array; $sth->finish; $min_value_as_money6 = $cols[0]; ### min_value_as_money8 $sth = $dbh->prepare(" SELECT MIN(NULLIF(migration_tools.attempt_money($column,'-0.01'),-0.01)) FROM $MIGSCHEMA.$table; "); $rv = $sth->execute() || die "Error retrieving data from $MIGSCHEMA.$table: $!"; @cols = $sth->fetchrow_array; $sth->finish; $min_value_as_money8 = $cols[0]; ### min_value_as_date $sth = $dbh->prepare(" SELECT MIN(NULLIF(migration_tools.attempt_date($column,'1969-06-09'),'1969-06-09'::DATE)) FROM $MIGSCHEMA.$table; "); $rv = $sth->execute() || die "Error retrieving data from $MIGSCHEMA.$table: $!"; @cols = $sth->fetchrow_array; $sth->finish; $min_value_as_date = $cols[0]; ### min_value_as_timestamptz $sth = $dbh->prepare(" SELECT MIN(NULLIF(migration_tools.attempt_timestamptz($column,'1969-06-09'),'1969-06-09'::TIMESTAMPTZ)) FROM $MIGSCHEMA.$table; "); $rv = $sth->execute() || die "Error retrieving data from $MIGSCHEMA.$table: $!"; @cols = $sth->fetchrow_array; $sth->finish; $min_value_as_timestamptz = $cols[0]; ### max_value $sth = $dbh->prepare(" SELECT MAX($column) FROM $MIGSCHEMA.$table; "); $rv = $sth->execute() || die "Error retrieving data from $MIGSCHEMA.$table: $!"; @cols = $sth->fetchrow_array; $sth->finish; $max_value = $cols[0]; ### max_length $sth = $dbh->prepare(" SELECT MAX(LENGTH($column)) FROM $MIGSCHEMA.$table; "); $rv = $sth->execute() || die "Error retrieving data from $MIGSCHEMA.$table: $!"; @cols = $sth->fetchrow_array; $sth->finish; $max_length = $cols[0]; ### max_length_max_value $sth = $dbh->prepare(" SELECT MAX($column) FROM $MIGSCHEMA.$table WHERE LENGTH($column) = $max_length; "); $rv = $sth->execute() || die "Error retrieving data from $MIGSCHEMA.$table: $!"; @cols = $sth->fetchrow_array; $sth->finish; $max_length_max_value = $cols[0]; ### max_value_as_integer $sth = $dbh->prepare(" SELECT MAX(migration_tools.attempt_cast($column,'INTEGER')::INTEGER) FROM $MIGSCHEMA.$table; "); $rv = $sth->execute() || die "Error retrieving data from $MIGSCHEMA.$table: $!"; @cols = $sth->fetchrow_array; $sth->finish; $max_value_as_integer = $cols[0]; ### max_value_as_money6 $sth = $dbh->prepare(" SELECT MAX(NULLIF(migration_tools.attempt_money6($column,'-0.01'),-0.01)) FROM $MIGSCHEMA.$table; "); $rv = $sth->execute() || die "Error retrieving data from $MIGSCHEMA.$table: $!"; @cols = $sth->fetchrow_array; $sth->finish; $max_value_as_money6 = $cols[0]; ### max_value_as_money8 $sth = $dbh->prepare(" SELECT MAX(NULLIF(migration_tools.attempt_money($column,'-0.01'),-0.01)) FROM $MIGSCHEMA.$table; "); $rv = $sth->execute() || die "Error retrieving data from $MIGSCHEMA.$table: $!"; @cols = $sth->fetchrow_array; $sth->finish; $max_value_as_money8 = $cols[0]; ### max_value_as_date $sth = $dbh->prepare(" SELECT MAX(NULLIF(migration_tools.attempt_date($column,'1969-06-09'),'1969-06-09'::DATE)) FROM $MIGSCHEMA.$table; "); $rv = $sth->execute() || die "Error retrieving data from $MIGSCHEMA.$table: $!"; @cols = $sth->fetchrow_array; $sth->finish; $max_value_as_date = $cols[0]; ### max_value_as_timestamptz $sth = $dbh->prepare(" SELECT MAX(NULLIF(migration_tools.attempt_timestamptz($column,'1969-06-09'),'1969-06-09'::TIMESTAMPTZ)) FROM $MIGSCHEMA.$table; "); $rv = $sth->execute() || die "Error retrieving data from $MIGSCHEMA.$table: $!"; @cols = $sth->fetchrow_array; $sth->finish; $max_value_as_timestamptz = $cols[0]; Mig::db_disconnect($dbh); print "\n"; print "# of rows = $count\n"; print "# of non-empty rows = $non_empty_count\n"; print "# of distinct values (as text) = $distinct_value_count\n"; print "# of distinct values (as integer) = $distinct_integer_value_count\n"; print "# of distinct values (as money6) = $distinct_money6_value_count\n"; print "# of distinct values (as money8) = $distinct_money8_value_count\n"; print "# of distinct values (as date) = $distinct_date_value_count\n"; print "# of distinct values (as timestamptz) = $distinct_timestamptz_value_count\n"; print "\n"; print "minimum value (as text) = $min_value\n"; print "maximum value (as text) = $max_value\n"; print "\n"; print "minimum value length (as text) = $min_length (min value: $min_length_min_value)\n"; print "maximum value length (as text) = $max_length (max value: $max_length_max_value)\n"; print "\n"; print "minimum value (as integer) = " . ($min_value_as_integer ? $min_value_as_integer : '') . "\n"; print "maximum value (as integer) = " . ($max_value_as_integer ? $max_value_as_integer : '') . "\n"; print "\n"; print "minimum value (as money6) = " . ($min_value_as_money6 ? $min_value_as_money6 : '') . "\n"; print "maximum value (as money6) = " . ($max_value_as_money6 ? $max_value_as_money6 : '') . "\n"; print "\n"; print "minimum value (as money8) = " . ($min_value_as_money8 ? $min_value_as_money8 : '') . "\n"; print "maximum value (as money8) = " . ($max_value_as_money8 ? $max_value_as_money8 : '') . "\n"; print "\n"; print "minimum value (as date) = " . ($min_value_as_date ? $min_value_as_date : '') . "\n"; print "maximum value (as date) = " . ($max_value_as_date ? $max_value_as_date : '') . "\n"; print "\n"; print "minimum value (as timestamptz) = " . ($min_value_as_timestamptz ? $min_value_as_timestamptz : '') . "\n"; print "maximum value (as timestamptz) = " . ($max_value_as_timestamptz ? $max_value_as_timestamptz : '') . "\n"; print "\n"; } sub group_browse { my ($column,$option) = (shift,shift||"GROUP BY 1 ORDER BY 1"); my $dbh = Mig::db_connect(); my $sth; my $rv; $sth = $dbh->prepare(Mig::sql(" SELECT $column, COUNT(*) FROM $MIGSCHEMA.$table $option; ")); $rv = $sth->execute() || die "Error retrieving data from $MIGSCHEMA.$table: $!"; print "\n"; open LESS, "|less -F"; printf LESS "%-30s", "Value:"; print LESS "Count:\n\n"; while (my @cols = $sth->fetchrow_array) { my $value = $cols[0]; my $count = $cols[1]; printf LESS "%-30s", defined $value ? $value : ''; print LESS "$count\n"; } close LESS; print "\n"; $sth->finish; } ############################################################################### sub add_this_column { my $column = shift; if (!Mig::check_for_tracked_column($table,$column)) { my $dbh = Mig::db_connect(); my $rv = $dbh->do(" INSERT INTO $MIGSCHEMA.tracked_column ( base_filename ,parent_table ,staged_table ,staged_column ) VALUES ( " . $dbh->quote($file) . " ," . $dbh->quote($fdata->{parent_table}) . " ," . $dbh->quote($table) . " ," . $dbh->quote($column) . " ); ") || die "Error inserting into table $MIGSCHEMA.tracked_column: $!\n"; Mig::db_disconnect($dbh); } } sub status_this_column { my $column = shift; my $data = Mig::status_this_column($table,$column); if (!$data) { add_this_column($column); $data = Mig::status_this_column($table,$column); } if ($$data{parent_table}) { my $dbh = Mig::db_connect(); my $sth = $dbh->prepare(" SELECT * FROM $MIGSCHEMA.fields_requiring_mapping WHERE table_name = " . $dbh->quote( $$data{parent_table} ) . " AND column_name = " . $dbh->quote( $column ) . ";" ); my $rv = $sth->execute() || die "Error checking table (tracked_column) for $table.$column: $!"; my $data2 = $sth->fetchrow_hashref; if ($data2) { $$data{required} = 1; } else { $$data{required} = 0; } $sth->finish; Mig::db_disconnect($dbh); } return $data; } sub set_comment { my ($column,$comment) = (shift,shift); if ($comment) { my $data = status_this_column($column); my $dbh = Mig::db_connect(); my $rv = $dbh->do(" UPDATE $MIGSCHEMA.tracked_column SET comment = " . $dbh->quote($comment) . " WHERE id = " . $dbh->quote($data->{id}) . "; ") || die "Error updating table $MIGSCHEMA.tracked_column: $!\n"; Mig::db_disconnect($dbh); } } sub set_transform { my ($column,$transform) = (shift,shift); if ($transform) { my $data = status_this_column($column); my $dbh = Mig::db_connect(); my $rv = $dbh->do(" UPDATE $MIGSCHEMA.tracked_column SET transform = " . $dbh->quote($transform) . " WHERE id = " . $dbh->quote($data->{id}) . "; ") || die "Error updating table $MIGSCHEMA.tracked_column: $!\n"; Mig::db_disconnect($dbh); } } sub set_target { my ($column,$target) = (shift,shift); my $target_table = $table; my $target_column = $target; if ($target) { if ($target =~ /^(.+)\.(.+)$/) { $target_table = $1; $target_column = $2; } my $data = status_this_column($column); my $dbh = Mig::db_connect(); my $rv = $dbh->do(" UPDATE $MIGSCHEMA.tracked_column SET target_table = " . $dbh->quote($target_table) . " ,target_column = " . $dbh->quote($target_column) . " WHERE id = " . $dbh->quote($data->{id}) . "; ") || die "Error updating table $MIGSCHEMA.tracked_column: $!\n"; Mig::db_disconnect($dbh); } }