1266 lines
42 KiB
Plaintext
1266 lines
42 KiB
Plaintext
|
#!/usr/bin/env perl
|
||
|
#===============================================================================
|
||
|
#
|
||
|
# FILE: update_mysql_pg_2
|
||
|
#
|
||
|
# USAGE: ./update_mysql_pg_2
|
||
|
#
|
||
|
# DESCRIPTION: Performs updates on the PostgreSQL Database 'HPR2'.
|
||
|
# ** Incomplete. Do not use! **
|
||
|
#
|
||
|
# OPTIONS: ---
|
||
|
# REQUIREMENTS: ---
|
||
|
# BUGS: ---
|
||
|
# NOTES: ---
|
||
|
# AUTHOR: Dave Morriss (djm), Dave.Morriss@gmail.com
|
||
|
# VERSION: 0.0.2
|
||
|
# CREATED: 2019-05-14 12:49:26
|
||
|
# REVISION: 2019-10-07 15:03:06
|
||
|
#
|
||
|
#===============================================================================
|
||
|
|
||
|
use 5.010;
|
||
|
use strict;
|
||
|
use warnings;
|
||
|
use utf8;
|
||
|
|
||
|
use Getopt::Long;
|
||
|
use Pod::Usage;
|
||
|
|
||
|
use Config::General;
|
||
|
use List::MoreUtils qw{uniq apply};
|
||
|
|
||
|
use Date::Manip::Delta;
|
||
|
use DateTime;
|
||
|
use DateTime::Format::Pg;
|
||
|
use DateTime::Format::Duration;
|
||
|
|
||
|
use Text::CSV;
|
||
|
use DBI;
|
||
|
use SQL::Abstract;
|
||
|
|
||
|
use Data::Dumper;
|
||
|
|
||
|
#
|
||
|
# Version number (manually incremented)
|
||
|
#
|
||
|
our $VERSION = '0.0.2';
|
||
|
|
||
|
#
|
||
|
# Script and directory names
|
||
|
#
|
||
|
( my $PROG = $0 ) =~ s|.*/||mx;
|
||
|
( my $DIR = $0 ) =~ s|/?[^/]*$||mx;
|
||
|
$DIR = '.' unless $DIR;
|
||
|
|
||
|
#-------------------------------------------------------------------------------
|
||
|
# Declarations
|
||
|
#-------------------------------------------------------------------------------
|
||
|
#
|
||
|
# Constants and other declarations
|
||
|
#
|
||
|
my $basedir = "$ENV{HOME}/HPR/PostgreSQL_Database";
|
||
|
my $configfile1 = "$basedir/.hpr_db.cfg";
|
||
|
my $configfile2 = "$basedir/.hpr_pg2.cfg";
|
||
|
my $database3 = "$basedir/ia.db"; # soft link
|
||
|
|
||
|
my $email_template = 'host_%s@hackerpublicradio.org';
|
||
|
my $default_licence = 'CC-BY-SA';
|
||
|
|
||
|
my ( $dbh1, $sth1, $h1, $rv1 );
|
||
|
my ( $dbh2, $sth2, $h2, $rv2 );
|
||
|
my ( $dbh3, $sth3, $h3, $rv3 );
|
||
|
|
||
|
my (@phase_choices);
|
||
|
|
||
|
my @phases = (
|
||
|
'episodes', 'hosts', 'eh_xref', 'series', 'es_xref', 'tags',
|
||
|
'comments', 'archived', 'assets', 'epilogue'
|
||
|
);
|
||
|
|
||
|
my $licenses;
|
||
|
|
||
|
#
|
||
|
# Shows parity between the MySQL tables and fields and the PostgreSQL ones.
|
||
|
#
|
||
|
# Organisation:
|
||
|
# hash: named from the two table names
|
||
|
# ----
|
||
|
# key: '_fields'
|
||
|
# value: array of the MySQL field names in desired order
|
||
|
# ----
|
||
|
# key: '_MSQL'
|
||
|
# value: SQL to be used to query the MySQL database when scanning for
|
||
|
# updates
|
||
|
# ----
|
||
|
# key: '_PGSQL'
|
||
|
# value: SQL to be used to query the PostgreSQL database when looking
|
||
|
# for the record in a table corresponding to the MySQL one
|
||
|
# ----
|
||
|
# key: '_PK'
|
||
|
# value: arrayref containing the names of the primary key fields of the
|
||
|
# MySQL and PostgreSQL tables
|
||
|
# ----
|
||
|
# key: '_PGTABLE'
|
||
|
# value: the name of the PostgreSQL table
|
||
|
# ----
|
||
|
# key: name of MySQL field (as listed in '_fields')
|
||
|
# value: array of 2-4 elements
|
||
|
# 0: name of Pg field for comparison purposes
|
||
|
# 1: function to manipulate one field into the other, or undef if
|
||
|
# a straight copy
|
||
|
# 2: name of Pg field in the table
|
||
|
# 3: function to transform the MySQL field to the Pg one
|
||
|
#
|
||
|
my %table_maps = (
|
||
|
# --------------------------------------------------------------------------
|
||
|
'eps_episodes' => {
|
||
|
'_fields' => [
|
||
|
'id', 'date', 'title', 'duration',
|
||
|
'summary', 'notes', 'explicit', 'license',
|
||
|
'downloads'
|
||
|
],
|
||
|
'_MSQL' =>
|
||
|
# q{SELECT * FROM eps WHERE id BETWEEN 850 AND 900},
|
||
|
# q{SELECT * FROM eps WHERE id BETWEEN 2501 AND 2850 ORDER BY id},
|
||
|
# q{SELECT * FROM eps WHERE id = 700},
|
||
|
q{SELECT * FROM eps WHERE status != 'reserved' ORDER BY id},
|
||
|
'_PGSQL' => q{
|
||
|
SELECT e.*,l.short_name AS license_short_name
|
||
|
FROM episodes e
|
||
|
JOIN licenses l ON e.license = l.license_id
|
||
|
WHERE episode_key = ?
|
||
|
},
|
||
|
'_INSERT' => q{
|
||
|
INSERT INTO episodes
|
||
|
(episode_key, release_date, title, duration, summary, notes,
|
||
|
explicit, license, downloads, status)
|
||
|
VALUES (?,?,?,?,?,?,?,?,?,'posted')
|
||
|
},
|
||
|
'_PK' => ['id','episode_key'],
|
||
|
'_PGTABLE' => 'episodes',
|
||
|
id => [
|
||
|
'episode_key',
|
||
|
sub {
|
||
|
return sprintf( "hpr%04d", $_[0] );
|
||
|
}
|
||
|
],
|
||
|
'date' => [ 'release_date', undef ],
|
||
|
'title' => [ 'title', undef ],
|
||
|
'duration' => [
|
||
|
'duration',
|
||
|
sub {
|
||
|
return interval( $_[0] );
|
||
|
}
|
||
|
],
|
||
|
'summary' => [ 'summary', undef ],
|
||
|
'notes' => [ 'notes', undef ],
|
||
|
'explicit' => [ 'explicit', undef ],
|
||
|
'license' => [
|
||
|
'license_short_name',
|
||
|
undef,
|
||
|
'license',
|
||
|
sub {
|
||
|
return $licenses->{$_[0]};
|
||
|
}
|
||
|
],
|
||
|
'downloads' => [ 'downloads', undef ],
|
||
|
},
|
||
|
# --------------------------------------------------------------------------
|
||
|
'hosts_hosts' => {
|
||
|
'_fields' => [
|
||
|
'hostid', 'host', 'email', 'profile',
|
||
|
'license', 'local_image', 'gpg', 'espeak_name'
|
||
|
],
|
||
|
'_MSQL' =>
|
||
|
# q{SELECT * FROM hosts ORDER BY hostid LIMIT 30 OFFSET 269},
|
||
|
q{SELECT * FROM hosts WHERE hostid = 379 ORDER BY hostid},
|
||
|
# q{SELECT * FROM hosts ORDER BY hostid},
|
||
|
'_PGSQL' => q{
|
||
|
SELECT * FROM hosts WHERE host_id = ?
|
||
|
},
|
||
|
'_INSERT' => q{INSERT INTO hosts
|
||
|
(host_id, host, email, profile, license, local_image, gpg, espeak_name)
|
||
|
VALUES (?,?,?,?,?,?,?,?)
|
||
|
},
|
||
|
'_PK' => ['hostid','host_id'],
|
||
|
'_PGTABLE' => 'hosts',
|
||
|
'hostid' => [ 'host_id', undef ],
|
||
|
'host' => [ 'host', undef ],
|
||
|
'email' => [ 'email', undef ],
|
||
|
'profile' => [ 'profile', undef ],
|
||
|
'license' => [ 'license',
|
||
|
sub {
|
||
|
return $licenses->{$_[0]};
|
||
|
}
|
||
|
],
|
||
|
'local_image' => [ 'local_image', undef ],
|
||
|
'gpg' => [ 'gpg', undef ],
|
||
|
'espeak_name' => [ 'espeak_name', undef ],
|
||
|
},
|
||
|
# --------------------------------------------------------------------------
|
||
|
'episodes_hosts_xref' => {
|
||
|
'_fields' => [ 'id', 'hostid' ],
|
||
|
'_MSQL' =>
|
||
|
q{SELECT e.id,h.hostid
|
||
|
FROM eps e, hosts h
|
||
|
WHERE e.hostid = h.hostid},
|
||
|
'_PGSQL' =>
|
||
|
q{INSERT INTO episodes_hosts_xref
|
||
|
SELECT e.episode_id, h.host_id
|
||
|
FROM episodes e, hosts h
|
||
|
WHERE e.episode_key = ? AND h.host_id = ?},
|
||
|
'_INSERT' => q{INSERT INTO episodes_hosts_xref VALUES (?,?)},
|
||
|
'_PK' => [ undef ],
|
||
|
'_PGTABLE' => 'episodes_hosts_xref',
|
||
|
},
|
||
|
# --------------------------------------------------------------------------
|
||
|
'miniseries_series' => {
|
||
|
'_fields' =>
|
||
|
[ 'id', 'name', 'description', 'private', 'image' ],
|
||
|
'_MSQL' =>
|
||
|
q{SELECT * FROM miniseries ORDER BY id},
|
||
|
'_PGSQL' => q{
|
||
|
SELECT * FROM series WHERE series_id = ?
|
||
|
},
|
||
|
'_INSERT' => q{INSERT INTO series VALUES (?,?,?,?,?)},
|
||
|
'_PK' => [ 'id', 'series_id' ],
|
||
|
'_PGTABLE' => 'series',
|
||
|
'id' => [ 'series_id', undef ],
|
||
|
'name' => [ 'name', undef ],
|
||
|
'description' => [ 'description', undef ],
|
||
|
'private' => [ 'private', undef ],
|
||
|
'image' => [ 'image', undef ],
|
||
|
},
|
||
|
# --------------------------------------------------------------------------
|
||
|
'comments_comments' => {
|
||
|
'_fields' => [
|
||
|
'id', 'eps_id',
|
||
|
'comment_timestamp', 'comment_author_name',
|
||
|
'comment_title', 'comment_text', 'last_changed'
|
||
|
],
|
||
|
'_MSQL' =>
|
||
|
q{SELECT * FROM comments ORDER BY id},
|
||
|
'_PGSQL' => q{
|
||
|
SELECT
|
||
|
e.episode_key,
|
||
|
c.*
|
||
|
FROM comments c
|
||
|
JOIN episodes e USING (episode_id)
|
||
|
WHERE comment_id = ?
|
||
|
},
|
||
|
# (comment_id, episode_id, comment_timestamp, comment_author_name,
|
||
|
# comment_title, comment_text)
|
||
|
'_INSERT' => q{INSERT INTO comments
|
||
|
VALUES (?,id_in_episodes(?),?,?,?,?,?)
|
||
|
},
|
||
|
'_PRE_UPDATE' => q{
|
||
|
ALTER TABLE comments DISABLE TRIGGER USER
|
||
|
},
|
||
|
'_POST_UPDATE' => q{
|
||
|
ALTER TABLE comments ENABLE TRIGGER USER
|
||
|
},
|
||
|
'_PK' => [ 'id', 'comment_id' ],
|
||
|
'_PGTABLE' => 'comments',
|
||
|
'id' => [ 'comment_id', undef ],
|
||
|
'eps_id' => [ 'episode_key',
|
||
|
sub {
|
||
|
return sprintf( "hpr%04d", $_[0] );
|
||
|
}
|
||
|
],
|
||
|
'comment_timestamp' => [ 'comment_timestamp', undef ],
|
||
|
'comment_author_name' => [ 'comment_author_name', undef ],
|
||
|
'comment_title' => [ 'comment_title', undef ],
|
||
|
'comment_text' => [ 'comment_text', undef ],
|
||
|
'last_changed' => [ 'last_changed', undef ],
|
||
|
},
|
||
|
# --------------------------------------------------------------------------
|
||
|
);
|
||
|
|
||
|
#
|
||
|
# Enable Unicode mode
|
||
|
#
|
||
|
binmode STDOUT, ":encoding(UTF-8)";
|
||
|
binmode STDERR, ":encoding(UTF-8)";
|
||
|
|
||
|
#-------------------------------------------------------------------------------
|
||
|
# Options and arguments
|
||
|
#-------------------------------------------------------------------------------
|
||
|
my $DEF_DEBUG = 0;
|
||
|
|
||
|
#
|
||
|
# Process options
|
||
|
#
|
||
|
my %options;
|
||
|
Options( \%options );
|
||
|
|
||
|
#
|
||
|
# Default help
|
||
|
#
|
||
|
pod2usage( -msg => "$PROG version $VERSION\n", -exitval => 1 )
|
||
|
if ( $options{'help'} );
|
||
|
|
||
|
#
|
||
|
# Collect options
|
||
|
#
|
||
|
my $DEBUG = ( defined( $options{debug} ) ? $options{debug} : $DEF_DEBUG );
|
||
|
my $cfgfile1
|
||
|
= ( defined( $options{config} ) ? $options{config} : $configfile1 );
|
||
|
my $verbose = ( defined( $options{verbose} ) ? $options{verbose} : 0 );
|
||
|
my $dry_run = ( defined( $options{'dry-run'} ) ? $options{'dry-run'} : 0 );
|
||
|
|
||
|
#
|
||
|
# This option is a list, provided as a CSV
|
||
|
#
|
||
|
my $phase_choices = $options{phases};
|
||
|
if ( defined($phase_choices) ) {
|
||
|
#
|
||
|
# We have a list which we'll parse, validate, sort, make unique and filter
|
||
|
#
|
||
|
my $lcsv = Text::CSV_XS->new( { binary => 1, } );
|
||
|
if ( $lcsv->parse($phase_choices) ) {
|
||
|
# Sort fields
|
||
|
@phase_choices = uniq( sort { $a cmp $b } $lcsv->fields() );
|
||
|
# Trim leading and trailing spaces
|
||
|
@phase_choices = apply { $_ =~ s/(^\s*|\s*$)// } @phase_choices;
|
||
|
|
||
|
# Make a list of invalid keywords
|
||
|
my %tmp = map { $_ => 1 } @phases;
|
||
|
my @bad = grep { not exists $tmp{$_} } @phase_choices;
|
||
|
|
||
|
# Deal with all errors
|
||
|
die "Invalid list; no elements\n" if scalar(@phase_choices) == 0;
|
||
|
die "Invalid list; too many elements\n"
|
||
|
if scalar(@phase_choices) > scalar(@phases);
|
||
|
die "Invalid list elements: ", join( ",", @bad ) . "\n"
|
||
|
if scalar(@bad) > 0;
|
||
|
}
|
||
|
else {
|
||
|
die "Failed to parse -list='$phase_choices'\n"
|
||
|
. $lcsv->error_diag() . "\n";
|
||
|
}
|
||
|
}
|
||
|
else {
|
||
|
#
|
||
|
# By default we do all phases
|
||
|
#
|
||
|
@phase_choices = @phases;
|
||
|
}
|
||
|
|
||
|
_debug( $DEBUG > 2, Dumper(\@phase_choices) );
|
||
|
|
||
|
#-------------------------------------------------------------------------------
|
||
|
# Validate the %table_maps hash
|
||
|
#-------------------------------------------------------------------------------
|
||
|
unless ( validate_maps(\%table_maps) ) {
|
||
|
warn "Hash \%table_maps is wrongly structured\n";
|
||
|
exit 1;
|
||
|
}
|
||
|
|
||
|
#-------------------------------------------------------------------------------
|
||
|
# Configuration file for MySQL/MariaDB - load data
|
||
|
#-------------------------------------------------------------------------------
|
||
|
my $conf1 = Config::General->new(
|
||
|
-ConfigFile => $cfgfile1,
|
||
|
-InterPolateVars => 1,
|
||
|
-ExtendedAccess => 1
|
||
|
);
|
||
|
my %config1 = $conf1->getall();
|
||
|
|
||
|
my $conf2 = Config::General->new(
|
||
|
-ConfigFile => $configfile2,
|
||
|
-InterPolateVars => 1,
|
||
|
-ExtendedAccess => 1
|
||
|
);
|
||
|
my %config2 = $conf2->getall();
|
||
|
|
||
|
#-------------------------------------------------------------------------------
|
||
|
# Connect to the MariaDB database
|
||
|
#-------------------------------------------------------------------------------
|
||
|
my $dbtype1 = $config1{database}->{type} // 'mysql';
|
||
|
my $dbhost1 = $config1{database}->{host} // '127.0.0.1';
|
||
|
my $dbport1 = $config1{database}->{port} // 3306;
|
||
|
my $dbname1 = $config1{database}->{name};
|
||
|
my $dbuser1 = $config1{database}->{user};
|
||
|
my $dbpwd1 = $config1{database}->{password};
|
||
|
$dbh1
|
||
|
= DBI->connect( "dbi:$dbtype1:host=$dbhost1;port=$dbport1;database=$dbname1",
|
||
|
$dbuser1, $dbpwd1, { AutoCommit => 1 } )
|
||
|
or die $DBI::errstr;
|
||
|
|
||
|
#
|
||
|
# Enable client-side UTF8
|
||
|
#
|
||
|
$dbh1->{mysql_enable_utf8} = 1;
|
||
|
|
||
|
#-------------------------------------------------------------------------------
|
||
|
# Connect to the PostgreSQL database
|
||
|
#-------------------------------------------------------------------------------
|
||
|
my $dbtype2 = $config2{database}->{type} // 'Pg';
|
||
|
my $dbhost2 = $config2{database}->{host} // '127.0.0.1';
|
||
|
my $dbport2 = $config2{database}->{port} // 5432;
|
||
|
my $dbname2 = $config2{database}->{name};
|
||
|
my $dbuser2 = $config2{database}->{user};
|
||
|
my $dbpwd2 = $config2{database}->{password};
|
||
|
$dbh2 = DBI->connect( "dbi:$dbtype2:host=$dbhost2;database=$dbname2;port=$dbport2",
|
||
|
$dbuser2, $dbpwd2, { PrintError => 0, AutoCommit => 1 } )
|
||
|
or die $DBI::errstr;
|
||
|
|
||
|
#
|
||
|
# Enable client-side UTF8
|
||
|
#
|
||
|
$dbh2->{pg_enable_utf8} = 1;
|
||
|
|
||
|
#-------------------------------------------------------------------------------
|
||
|
# Connect to the SQLite database
|
||
|
#-------------------------------------------------------------------------------
|
||
|
$dbh3 = DBI->connect( "dbi:SQLite:dbname=$database3", "", "" );
|
||
|
|
||
|
#-------------------------------------------------------------------------------
|
||
|
# Gather the licence details from the Pg database
|
||
|
#-------------------------------------------------------------------------------
|
||
|
$licenses = load_licenses($dbh2);
|
||
|
|
||
|
#
|
||
|
# Populate the %choices hash
|
||
|
#
|
||
|
my %choices = map { $_ => 1 } @phase_choices;
|
||
|
|
||
|
#
|
||
|
# Perform phases in order, omitting those that are not in the list
|
||
|
#
|
||
|
for my $phase (@phases) {
|
||
|
#---------------------------------------------------------------------------
|
||
|
# Update from the 'eps' table to 'episodes'
|
||
|
#---------------------------------------------------------------------------
|
||
|
if ( $phase eq 'episodes' && exists( $choices{$phase} ) ) {
|
||
|
print "Find episode updates\n" if ($verbose);
|
||
|
update_table( $dbh1, $dbh2, $table_maps{'eps_episodes'}, $dry_run, $verbose );
|
||
|
}
|
||
|
#---------------------------------------------------------------------------
|
||
|
# Update from the 'hosts' table to 'hosts'
|
||
|
#---------------------------------------------------------------------------
|
||
|
elsif ( $phase eq 'hosts' && exists( $choices{$phase} ) ) {
|
||
|
print "Find host updates\n" if ( $verbose > 0 );
|
||
|
update_table( $dbh1, $dbh2, $table_maps{'hosts_hosts'}, $dry_run, $verbose );
|
||
|
}
|
||
|
#---------------------------------------------------------------------------
|
||
|
# Update the 'episodes_hosts_xref' table
|
||
|
#---------------------------------------------------------------------------
|
||
|
elsif ( $phase eq 'eh_xref' && exists( $choices{$phase} ) ) {
|
||
|
print "Find episode/host updates\n" if ( $verbose > 0 );
|
||
|
}
|
||
|
#---------------------------------------------------------------------------
|
||
|
# Update from the 'miniseries' table to 'series'
|
||
|
#---------------------------------------------------------------------------
|
||
|
elsif ( $phase eq 'series' && exists( $choices{$phase} ) ) {
|
||
|
print "Find series updates\n" if ( $verbose > 0 );
|
||
|
update_table( $dbh1, $dbh2, $table_maps{'miniseries_series'},
|
||
|
$dry_run, $verbose );
|
||
|
}
|
||
|
#---------------------------------------------------------------------------
|
||
|
# Update the 'episodes_series_xref' table
|
||
|
#---------------------------------------------------------------------------
|
||
|
elsif ( $phase eq 'es_xref' && exists( $choices{$phase} ) ) {
|
||
|
print "Find episode/series updates\n" if ( $verbose > 0 );
|
||
|
}
|
||
|
#---------------------------------------------------------------------------
|
||
|
# Collect and store the id numbers and tags from the MySQL 'eps' table,
|
||
|
# then update the PostgreSQL tables.
|
||
|
#---------------------------------------------------------------------------
|
||
|
elsif ( $phase eq 'tags' && exists( $choices{$phase} ) ) {
|
||
|
print "Find tag updates\n" if ( $verbose > 0 );
|
||
|
}
|
||
|
#---------------------------------------------------------------------------
|
||
|
# Update from the 'comments' table to 'comments'
|
||
|
#---------------------------------------------------------------------------
|
||
|
elsif ( $phase eq 'comments' && exists( $choices{$phase} ) ) {
|
||
|
print "Find comment updates\n" if ( $verbose > 0 );
|
||
|
update_table( $dbh1, $dbh2, $table_maps{'comments_comments'},
|
||
|
$dry_run, $verbose );
|
||
|
}
|
||
|
#---------------------------------------------------------------------------
|
||
|
# Update archive-related fields the 'episodes' table from 'ia.db'
|
||
|
#---------------------------------------------------------------------------
|
||
|
elsif ( $phase eq 'archived' && exists( $choices{$phase} ) ) {
|
||
|
print "Find archive data updates\n" if ( $verbose > 0 );
|
||
|
}
|
||
|
#---------------------------------------------------------------------------
|
||
|
# Update from the 'assets' table in 'ia.db' to 'assets'
|
||
|
#---------------------------------------------------------------------------
|
||
|
elsif ( $phase eq 'assets' && exists( $choices{$phase} ) ) {
|
||
|
print "Find asset updates\n" if ( $verbose > 0 );
|
||
|
}
|
||
|
#---------------------------------------------------------------------------
|
||
|
# Perform the 'epilogue' actions
|
||
|
#---------------------------------------------------------------------------
|
||
|
elsif ( $phase eq 'epilogue' && exists( $choices{$phase} ) ) {
|
||
|
}
|
||
|
}
|
||
|
|
||
|
exit;
|
||
|
|
||
|
#=== FUNCTION ================================================================
|
||
|
# NAME: validate_maps
|
||
|
# PURPOSE: Validates the hash containing the maps that drive this script
|
||
|
# PARAMETERS: $maps hashref to the maps hash
|
||
|
# $verbose how much to report
|
||
|
# RETURNS: True if the maps are valid, otherwise false
|
||
|
# DESCRIPTION:
|
||
|
# THROWS: No exceptions
|
||
|
# COMMENTS: None
|
||
|
# SEE ALSO: N/A
|
||
|
#===============================================================================
|
||
|
sub validate_maps {
|
||
|
my ( $maps, $verbose ) = @_;
|
||
|
|
||
|
return 0 unless defined($maps);
|
||
|
|
||
|
foreach my $key (sort(keys(%$maps))) {
|
||
|
foreach my $k (qw{_fields _MSQL _PGSQL _INSERT _PK _PGTABLE}) {
|
||
|
unless (exists($maps->{$key}->{$k})) {
|
||
|
print "Missing key: %table_maps{$key}->{$k}\n";
|
||
|
return 0;
|
||
|
}
|
||
|
}
|
||
|
foreach my $k (@{$maps->{_fields}}) {
|
||
|
unless (defined($maps->{$key}->{$k})) {
|
||
|
print "Missing key: %table_maps{$key}->{$k}\n";
|
||
|
return 0;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return 1;
|
||
|
|
||
|
}
|
||
|
|
||
|
#=== FUNCTION ================================================================
|
||
|
# NAME: load_licenses
|
||
|
# PURPOSE: Loads the 'licenses' table from the Pg database
|
||
|
# PARAMETERS: $dbh Handle for the Pg database
|
||
|
# RETURNS: A hashref containing the licence information
|
||
|
# DESCRIPTION:
|
||
|
# THROWS: No exceptions
|
||
|
# COMMENTS: None
|
||
|
# SEE ALSO: N/A
|
||
|
#===============================================================================
|
||
|
sub load_licenses {
|
||
|
my ( $dbh ) = @_;
|
||
|
|
||
|
my ( $licenses, %lic_n2id );
|
||
|
|
||
|
my $sth = $dbh->prepare( q{SELECT * FROM licenses ORDER BY license_id} )
|
||
|
or die $DBI::errstr;
|
||
|
if ( $dbh->err ) {
|
||
|
warn $dbh->errstr;
|
||
|
}
|
||
|
|
||
|
$sth->execute;
|
||
|
if ( $dbh->err ) {
|
||
|
warn $dbh->errstr;
|
||
|
}
|
||
|
|
||
|
#
|
||
|
# Load the entire table as an arrayref of hashrefs
|
||
|
#
|
||
|
$licenses = $sth->fetchall_arrayref( {} );
|
||
|
|
||
|
for my $row (@$licenses) {
|
||
|
$lic_n2id{$row->{short_name}} = $row->{license_id};
|
||
|
}
|
||
|
|
||
|
return \%lic_n2id;
|
||
|
|
||
|
}
|
||
|
|
||
|
#=== FUNCTION ================================================================
|
||
|
# NAME: update_table
|
||
|
# PURPOSE: Updates a Pg table from the corresponding MySQL table driven
|
||
|
# by the global hash %table_maps.
|
||
|
# PARAMETERS: $dbh1 Handle for the MariaDB database
|
||
|
# $dbh2 Handle for the Pg database
|
||
|
# $map Hashref to the sub-hash in the %table_maps hash
|
||
|
# $dry_run Dry run setting
|
||
|
# $verbose Verbosity level
|
||
|
# RETURNS:
|
||
|
# DESCRIPTION: There is not always a direct table to table correspondence, but
|
||
|
# the %table_maps entries present what is necessary to perform
|
||
|
# the actions.
|
||
|
# THROWS: No exceptions
|
||
|
# COMMENTS: None
|
||
|
# SEE ALSO: N/A
|
||
|
#===============================================================================
|
||
|
sub update_table {
|
||
|
my ( $dbh1, $dbh2, $map, $dry_run, $verbose ) = @_;
|
||
|
|
||
|
my ( $sth1, $h1, $sth2, $h2, $count, $pgkey );
|
||
|
my ( %diffs, $diffhash );
|
||
|
|
||
|
#
|
||
|
# The primary keys for the two tables
|
||
|
#
|
||
|
my ( $pk1, $pk2 ) = @{ $map->{'_PK'} };
|
||
|
|
||
|
#
|
||
|
# Prepare a query to collect rows from the MySQL database
|
||
|
#
|
||
|
$sth1 = $dbh1->prepare( $map->{'_MSQL'} )
|
||
|
or die $DBI::errstr;
|
||
|
if ( $dbh1->err ) {
|
||
|
warn $dbh1->errstr;
|
||
|
}
|
||
|
|
||
|
#
|
||
|
# Query to look up the equivalent episode in the Pg database.
|
||
|
# TODO: ensure there's SQL in the table before using it!
|
||
|
#
|
||
|
$sth2 = $dbh2->prepare( $map->{'_PGSQL'} )
|
||
|
or die $DBI::errstr;
|
||
|
|
||
|
#
|
||
|
# Query MySQL for the nominated table
|
||
|
#
|
||
|
$sth1->execute;
|
||
|
if ( $dbh1->err ) {
|
||
|
die $dbh1->errstr;
|
||
|
}
|
||
|
|
||
|
#
|
||
|
# Loop though MySQL table rows looking for changes
|
||
|
#
|
||
|
$count = 0;
|
||
|
while ( $h1 = $sth1->fetchrow_hashref ) {
|
||
|
#
|
||
|
# Build the search string for Pg, transforming it if there is code in
|
||
|
# the table to do so.
|
||
|
#
|
||
|
$pgkey = $h1->{$pk1};
|
||
|
if (defined($map->{$pk1}->[1])) {
|
||
|
if (ref($map->{$pk1}->[1]) eq 'CODE') {
|
||
|
$pgkey = $map->{$pk1}->[1]($h1->{$pk1});
|
||
|
}
|
||
|
}
|
||
|
|
||
|
#
|
||
|
# Find the corresponding row in the Pg table
|
||
|
#
|
||
|
$sth2->execute($pgkey);
|
||
|
if ( $dbh2->err ) {
|
||
|
die $dbh2->errstr;
|
||
|
}
|
||
|
|
||
|
if ( $h2 = $sth2->fetchrow_hashref ) {
|
||
|
#
|
||
|
# Found the record in Pg, so now check whether there's
|
||
|
# a difference, and accumulate them if found
|
||
|
#
|
||
|
print "Record found: ", $h1->{$pk1}, "\n" if ( $verbose > 1 );
|
||
|
$diffhash
|
||
|
= compare_fields( $h1, $h2, $map, $verbose );
|
||
|
$diffs{ $h2->{$pk2} } = $diffhash if ( defined($diffhash) );
|
||
|
}
|
||
|
else {
|
||
|
#
|
||
|
# Record was not found so now we have to add it. TODO
|
||
|
#
|
||
|
print "Record not found: ",$h1->{$pk1}, "\n" if ($verbose > 1);
|
||
|
add_row($dbh1, $dbh2, $map, $h1, $dry_run, $verbose );
|
||
|
}
|
||
|
|
||
|
$count++;
|
||
|
}
|
||
|
|
||
|
if (%diffs) {
|
||
|
print "Differences found: ",scalar(keys(%diffs)),"\n" if ($verbose);
|
||
|
_debug( $DEBUG >= 3, Dumper(\%diffs) );
|
||
|
|
||
|
#
|
||
|
# Call a function which iterates through the keys of %diffs (in
|
||
|
# numerical order). For each key construct something like:
|
||
|
# update episodes set title = 'value1', summary = 'value2' where
|
||
|
# episode_key = 'value3';
|
||
|
#
|
||
|
update_differences( $dbh1, $dbh2, $map, $map->{'_PGTABLE'},
|
||
|
$map->{'_PK'}->[1], \%diffs, $dry_run, $verbose );
|
||
|
}
|
||
|
|
||
|
return $count;
|
||
|
}
|
||
|
|
||
|
#=== FUNCTION ================================================================
|
||
|
# NAME: compare_fields
|
||
|
# PURPOSE: Compares fields in a database row looking for changes
|
||
|
# PARAMETERS: $h1 hashref to the current row from the MySQL query
|
||
|
# $h2 hashref to the current row from the Pg query
|
||
|
# $map hashref to the section of the table_maps hash
|
||
|
# for this table update
|
||
|
# RETURNS: A hashref to hash of differences (or undef if none)
|
||
|
# DESCRIPTION:
|
||
|
# THROWS: No exceptions
|
||
|
# COMMENTS: None
|
||
|
# SEE ALSO: N/A
|
||
|
#===============================================================================
|
||
|
sub compare_fields {
|
||
|
my ($h1, $h2, $map, $verbose ) = @_;
|
||
|
|
||
|
my (%diffs, $f1, $f2);
|
||
|
my $len = ($verbose * 40);
|
||
|
my $pk1 = $map->{_PK}->[0];
|
||
|
|
||
|
my @flds = @{$map->{_fields}};
|
||
|
for my $fld (@flds) {
|
||
|
#
|
||
|
# Contents of field in MySQL. We force blank strings to NULL here in
|
||
|
# line with copy_mysql_pg_2.
|
||
|
#
|
||
|
$f1 = nullif($h1->{$fld},'^\s*$');
|
||
|
|
||
|
#
|
||
|
# Contents of field in Pg
|
||
|
#
|
||
|
$f2 = $h2->{$map->{$fld}->[0]};
|
||
|
|
||
|
#
|
||
|
# If there's code to transform fields then run it. Always convert the
|
||
|
# MySQL to the Pg form. This for building the differences, which may
|
||
|
# not be what we want to write to the database.
|
||
|
#
|
||
|
if (defined($map->{$fld}->[1])) {
|
||
|
if (ref($map->{$fld}->[1]) eq 'CODE') {
|
||
|
$f1 = $map->{$fld}->[1]($f1);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if ($DEBUG > 2) {
|
||
|
printf "D> %-5s %s = %s\n",'MySQL',$fld,coalesce($f1,'[undefined]');
|
||
|
printf "D> %-5s %s = %s\n",'Pg',$fld,coalesce($f2,'[undefined]');
|
||
|
}
|
||
|
|
||
|
#
|
||
|
# Do the fields differ?
|
||
|
#
|
||
|
if ( !equal( $f1, $f2 ) ) {
|
||
|
_debug( $DEBUG > 1, $h1->{$pk1} );
|
||
|
printf "D> Difference found: %s\n%s\n%s\n", $fld,
|
||
|
coalesce( trunc( $f1, $len ), '[undefined]' ),
|
||
|
coalesce( trunc( $f2, $len ), '[undefined]' )
|
||
|
if ( $DEBUG > 1 );
|
||
|
#
|
||
|
# If there are further elements in the array deal with them
|
||
|
#
|
||
|
if ( defined( $map->{$fld}->[3] ) ) {
|
||
|
if ( ref( $map->{$fld}->[3] ) eq 'CODE' ) {
|
||
|
$f1 = $map->{$fld}->[3]($f1);
|
||
|
}
|
||
|
}
|
||
|
$diffs{$fld} = [ $f1, $f2 ];
|
||
|
}
|
||
|
}
|
||
|
|
||
|
#
|
||
|
# Return any differences we found or 'undef' if nothing was found. This
|
||
|
# way of doing it is ugly but it works.
|
||
|
#
|
||
|
return ( scalar( keys(%diffs) ) > 0 ? \%diffs : undef );
|
||
|
}
|
||
|
|
||
|
#=== FUNCTION ================================================================
|
||
|
# NAME: update_differences
|
||
|
# PURPOSE: Updates differences in a Pg table
|
||
|
# PARAMETERS: $dbh1 Handle for the MariaDB database
|
||
|
# $dbh2 Handle for the Pg database
|
||
|
# $map hashref to the section of the table_maps hash
|
||
|
# for this table update
|
||
|
# $table Name of table to update
|
||
|
# $keyfld Name of field within $table for 'WHERE' clause
|
||
|
# $diffs Hashref of a hash of differences
|
||
|
# $dry_run Dry run setting
|
||
|
# $verbose Verbosity level
|
||
|
# RETURNS: Nothing
|
||
|
# DESCRIPTION:
|
||
|
# THROWS: No exceptions
|
||
|
# COMMENTS: None
|
||
|
# SEE ALSO: N/A
|
||
|
#===============================================================================
|
||
|
sub update_differences {
|
||
|
my ( $dbh1, $dbh2, $map, $table, $keyfld, $diffs, $dry_run, $verbose )
|
||
|
= @_;
|
||
|
|
||
|
my ( $sql, $stmt, @bind, %data, %where, $sth2 );
|
||
|
|
||
|
#
|
||
|
# Loop through the updates by key
|
||
|
#
|
||
|
foreach my $key ( sort( keys(%$diffs) ) ) {
|
||
|
#
|
||
|
# Build new SQL each time
|
||
|
#
|
||
|
$sql = SQL::Abstract->new;
|
||
|
|
||
|
#
|
||
|
# Populate the %data hash from the differences, accepting the MySQL
|
||
|
# value (the new one). If we find an empty string we make it 'undef'
|
||
|
# (NULL in the database).
|
||
|
#
|
||
|
foreach my $k ( keys( %{ $diffs->{$key} } ) ) {
|
||
|
$data{$k} = $diffs->{$key}->{$k}->[0];
|
||
|
}
|
||
|
|
||
|
#
|
||
|
# Populate the %where hash
|
||
|
#
|
||
|
%where = ( $keyfld => $key );
|
||
|
|
||
|
if ( $DEBUG > 2 ) {
|
||
|
_debug( 1, '%data: ' . Dumper( \%data ) );
|
||
|
_debug( 1, '%where: ' . Dumper( \%where ) );
|
||
|
}
|
||
|
|
||
|
#
|
||
|
# Build the SQL and the arguments to fill the gaps
|
||
|
#
|
||
|
( $stmt, @bind ) = $sql->update( $table, \%data, \%where );
|
||
|
|
||
|
#
|
||
|
# Report it or do it depending on $dry_run
|
||
|
#
|
||
|
if ($dry_run) {
|
||
|
printf "Not changed %s: dry run mode on\n", $key;
|
||
|
_debug( $DEBUG > 2, "\$stmt: $stmt" );
|
||
|
_debug( $DEBUG > 3, "\@bind: " . join( ', ', @bind ) );
|
||
|
}
|
||
|
else {
|
||
|
#
|
||
|
# If there's a pre-update apply it
|
||
|
#
|
||
|
if ( defined( $map->{_PRE_UPDATE} ) ) {
|
||
|
$dbh2->do( $map->{_PRE_UPDATE} );
|
||
|
if ( $dbh2->err ) {
|
||
|
warn $dbh2->errstr;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
$sth2 = $dbh2->prepare($stmt)
|
||
|
or die $DBI::errstr;
|
||
|
if ( $dbh2->err ) {
|
||
|
warn $dbh2->errstr;
|
||
|
}
|
||
|
$sth2->execute(@bind);
|
||
|
if ( $dbh2->err ) {
|
||
|
warn $dbh2->errstr;
|
||
|
}
|
||
|
else {
|
||
|
printf "Updated recordi %s\n", $key if $verbose;
|
||
|
}
|
||
|
|
||
|
#
|
||
|
# If there's a post-update apply it
|
||
|
#
|
||
|
if ( defined( $map->{_POST_UPDATE} ) ) {
|
||
|
$dbh2->do( $map->{_POST_UPDATE} );
|
||
|
if ( $dbh2->err ) {
|
||
|
warn $dbh2->errstr;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
#
|
||
|
# Prevent data 'bleed-through'
|
||
|
#
|
||
|
undef %data;
|
||
|
undef %where;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
#=== FUNCTION ================================================================
|
||
|
# NAME: add_row
|
||
|
# PURPOSE: Add a row to a table
|
||
|
# PARAMETERS: $dbh1 Handle for the MariaDB database
|
||
|
# $dbh2 Handle for the Pg database
|
||
|
# $map hashref to the section of the table_maps hash
|
||
|
# for this table update
|
||
|
# $h1 hashref to the current row from the MySQL query
|
||
|
# $dry_run Dry run setting
|
||
|
# $verbose Verbosity level
|
||
|
# RETURNS:
|
||
|
# DESCRIPTION:
|
||
|
# THROWS: No exceptions
|
||
|
# COMMENTS: None
|
||
|
# SEE ALSO: N/A
|
||
|
#===============================================================================
|
||
|
sub add_row {
|
||
|
my ( $dbh1, $dbh2, $map, $h1, $dry_run, $verbose ) = @_;
|
||
|
|
||
|
my ( @data, $fvalue, $sth2 );
|
||
|
|
||
|
my @flds = @{ $map->{_fields} };
|
||
|
for my $fld (@flds) {
|
||
|
|
||
|
#
|
||
|
# Ensure empty strings become NULL values
|
||
|
#
|
||
|
$fvalue = nullif( $h1->{$fld}, '^\s*$' );
|
||
|
|
||
|
#
|
||
|
# Run the code if any, giving precedence to the second pair of
|
||
|
# elements in the controlling array
|
||
|
#
|
||
|
if ( defined( $map->{$fld}->[3] ) ) {
|
||
|
if ( ref( $map->{$fld}->[3] ) eq 'CODE' ) {
|
||
|
$fvalue = $map->{$fld}->[3]($fvalue);
|
||
|
}
|
||
|
}
|
||
|
elsif ( defined( $map->{$fld}->[1] ) ) {
|
||
|
if ( ref( $map->{$fld}->[1] ) eq 'CODE' ) {
|
||
|
$fvalue = $map->{$fld}->[1]($fvalue);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
#
|
||
|
# Save the value for writing to the database
|
||
|
#
|
||
|
push( @data, $fvalue );
|
||
|
}
|
||
|
|
||
|
if ($dry_run) {
|
||
|
printf "Not added %s, dry run mode on\n", $h1->{ $map->{'_PK'}->[0] };
|
||
|
_debug( $DEBUG >= 3,
|
||
|
join( "\n", map { coalesce( $_, '[undef]' ) } @data ) );
|
||
|
}
|
||
|
else {
|
||
|
$sth2 = $dbh2->prepare( $map->{'_INSERT'} )
|
||
|
or die $DBI::errstr;
|
||
|
if ( $dbh2->err ) {
|
||
|
warn $dbh2->errstr;
|
||
|
}
|
||
|
$sth2->execute(@data);
|
||
|
if ( $dbh2->err ) {
|
||
|
warn $dbh2->errstr;
|
||
|
}
|
||
|
else {
|
||
|
printf "Added record %s\n", $h1->{ $map->{'_PK'}->[0] }
|
||
|
if $verbose;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
#=== FUNCTION ================================================================
|
||
|
# NAME: trunc
|
||
|
# PURPOSE: Truncate a string to a specified length and add '...' to show
|
||
|
# it was truncated
|
||
|
# PARAMETERS: $string the string to truncate
|
||
|
# $len the length to truncate to
|
||
|
# RETURNS: The truncated string (if it's longer than $length)
|
||
|
# DESCRIPTION:
|
||
|
# THROWS: No exceptions
|
||
|
# COMMENTS: None
|
||
|
# SEE ALSO: N/A
|
||
|
#===============================================================================
|
||
|
sub trunc {
|
||
|
my ( $str, $len ) = @_;
|
||
|
|
||
|
return unless defined($str);
|
||
|
return $str if ( $len >= length($str) );
|
||
|
return substr( $str, 0, $len ) . '...';
|
||
|
}
|
||
|
|
||
|
#=== FUNCTION ================================================================
|
||
|
# NAME: interval
|
||
|
# PURPOSE: Convert a time in seconds to a valid 'HH:MM:SS' interval
|
||
|
# PARAMETERS: $time the time to convert in seconds
|
||
|
# RETURNS: The interval string in the format 'HH:MM:SS' or undef
|
||
|
# DESCRIPTION: TODO
|
||
|
# THROWS: No exceptions
|
||
|
# COMMENTS: Adapted from a routine for generating valid PostgreSQL
|
||
|
# interval times. Probably could be simplified
|
||
|
# SEE ALSO:
|
||
|
#===============================================================================
|
||
|
sub interval {
|
||
|
my ($time) = @_;
|
||
|
|
||
|
return '00:00:00' unless $time; ## no critic
|
||
|
|
||
|
my $date = Date::Manip::Delta->new;
|
||
|
unless ( $date->parse($time) ) {
|
||
|
return $date->printf("%02hv:%02mv:%02sv");
|
||
|
}
|
||
|
else {
|
||
|
warn "Invalid time $time\n";
|
||
|
return undef; ## no critic
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
#=== FUNCTION ================================================================
|
||
|
# NAME: default_email
|
||
|
# PURPOSE: Make a default email address for hosts with none
|
||
|
# PARAMETERS: $email Original email address
|
||
|
# $regex Regular expression to check the email against
|
||
|
# $template Template for building the default
|
||
|
# $hostid Host id number to use in the default
|
||
|
# RETURNS: The email address to be used
|
||
|
# DESCRIPTION: If the email address matches a regular expression then
|
||
|
# generate a default from the template and the host id,
|
||
|
# otherwise just return the address untouched.
|
||
|
# THROWS: No exceptions
|
||
|
# COMMENTS: None
|
||
|
# SEE ALSO: N/A
|
||
|
#===============================================================================
|
||
|
sub default_email {
|
||
|
my ( $email, $regex, $template, $hostid ) = @_;
|
||
|
|
||
|
return (
|
||
|
$email =~ $regex
|
||
|
? sprintf( $template, $hostid )
|
||
|
: $email
|
||
|
);
|
||
|
}
|
||
|
|
||
|
#=== FUNCTION ================================================================
|
||
|
# NAME: equal
|
||
|
# PURPOSE: Compare two strings even if undefined
|
||
|
# PARAMETERS: $s1 The first string
|
||
|
# $s2 The second string
|
||
|
# RETURNS: True if both strings are undefined, false if one isn't
|
||
|
# defined, otherwise the result of comparing them.
|
||
|
# DESCRIPTION: Works on the principle that two undefined strings are equal,
|
||
|
# a defined and an undefined string are not, and otherwise they
|
||
|
# are equal if they are equal!
|
||
|
# THROWS: No exceptions
|
||
|
# COMMENTS: None
|
||
|
# SEE ALSO:
|
||
|
#===============================================================================
|
||
|
sub equal {
|
||
|
my ( $s1, $s2 ) = @_;
|
||
|
|
||
|
return 1 if ( !defined($s1) && !defined($s2) );
|
||
|
return 0 if ( !defined($s1) || !defined($s2) );
|
||
|
|
||
|
return ( $s1 eq $s2 );
|
||
|
}
|
||
|
|
||
|
#=== FUNCTION ================================================================
|
||
|
# NAME: nullif
|
||
|
# PURPOSE: Tests a value and makes it 'undef' (equivalent to NULL in the
|
||
|
# database) if it matches a regular expression.
|
||
|
# PARAMETERS: $value Value to test
|
||
|
# $regex Regular expression to match against
|
||
|
# RETURNS: 'undef' if the values match, otherwise the original value
|
||
|
# DESCRIPTION: This is very simple, just a wrapper around the test.
|
||
|
# THROWS: No exceptions
|
||
|
# COMMENTS: None
|
||
|
# SEE ALSO: N/A
|
||
|
#===============================================================================
|
||
|
sub nullif {
|
||
|
my ( $value, $regex ) = @_;
|
||
|
|
||
|
return $value unless defined($value);
|
||
|
return ( $value =~ $regex ? undef : $value );
|
||
|
}
|
||
|
|
||
|
#=== FUNCTION ================================================================
|
||
|
# NAME: coalesce
|
||
|
# PURPOSE: To find the first defined argument and return it
|
||
|
# PARAMETERS: Arbitrary number of arguments
|
||
|
# RETURNS: The first defined argument or undef if there are none
|
||
|
# DESCRIPTION: Modelled on the SQL function of the same name. It takes a list
|
||
|
# of arguments, scans it for the first one that is not undefined
|
||
|
# and returns it. If an argument is defined and it's an arrayref
|
||
|
# then the referenced array is returned comma-delimited. This
|
||
|
# allows calls such as "coalesce($var,'undef')" which returns
|
||
|
# the value of $var if it's defined, and 'undef' if not and
|
||
|
# doesn't break anything along the way.
|
||
|
# THROWS: No exceptions
|
||
|
# COMMENTS: None
|
||
|
# SEE ALSO: N/A
|
||
|
#===============================================================================
|
||
|
sub coalesce {
|
||
|
foreach (@_) {
|
||
|
if ( defined($_) ) {
|
||
|
if ( ref($_) eq 'ARRAY' ) {
|
||
|
return join( ',', @{$_} );
|
||
|
}
|
||
|
else {
|
||
|
return $_;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
return; # implicit undef
|
||
|
}
|
||
|
|
||
|
#=== FUNCTION ================================================================
|
||
|
# NAME: _debug
|
||
|
# PURPOSE: Prints debug reports
|
||
|
# PARAMETERS: $active Boolean: 1 for print, 0 for no print
|
||
|
# $message Message to print
|
||
|
# RETURNS: Nothing
|
||
|
# DESCRIPTION: Outputs a message if $active is true. It removes any trailing
|
||
|
# newline and then adds one in the 'print' to the caller doesn't
|
||
|
# have to bother. Prepends the message with 'D> ' to show it's
|
||
|
# a debug message.
|
||
|
# THROWS: No exceptions
|
||
|
# COMMENTS: None
|
||
|
# SEE ALSO: N/A
|
||
|
#===============================================================================
|
||
|
sub _debug {
|
||
|
my ( $active, $message ) = @_;
|
||
|
|
||
|
chomp($message);
|
||
|
print "D> $message\n" if $active;
|
||
|
}
|
||
|
|
||
|
#=== FUNCTION ================================================================
|
||
|
# NAME: Options
|
||
|
# PURPOSE: Processes command-line options
|
||
|
# PARAMETERS: $optref Hash reference to hold the options
|
||
|
# RETURNS: Undef
|
||
|
# DESCRIPTION:
|
||
|
# THROWS: no exceptions
|
||
|
# COMMENTS: none
|
||
|
# SEE ALSO: n/a
|
||
|
#===============================================================================
|
||
|
sub Options {
|
||
|
my ($optref) = @_;
|
||
|
|
||
|
my @options = ( "help", "config=s", "debug=i", "dry-run!", "verbose+",
|
||
|
"phases=s" );
|
||
|
|
||
|
if ( !GetOptions( $optref, @options ) ) {
|
||
|
pod2usage( -msg => "$PROG version $VERSION\n", -exitval => 1 );
|
||
|
}
|
||
|
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
__END__
|
||
|
|
||
|
|
||
|
#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||
|
# Application Documentation
|
||
|
#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||
|
#{{{
|
||
|
|
||
|
=head1 NAME
|
||
|
|
||
|
update_mysql_pg_2 - Update the PostgreSQL database HPR2 from the live MySQL
|
||
|
database or a local copy
|
||
|
|
||
|
=head1 VERSION
|
||
|
|
||
|
This documentation refers to update_mysql_pg_2 version 0.0.2
|
||
|
|
||
|
|
||
|
=head1 USAGE
|
||
|
|
||
|
update_mysql_pg_2 -verbose
|
||
|
update_mysql_pg_2 -config=.hpr_livedb.cfg -verbose
|
||
|
update_mysql_pg_2 -verbose \
|
||
|
-phase='episodes,hosts,eh_xref,series,es_xref,tags,comments,archived,assets,epilogue'
|
||
|
|
||
|
=head1 REQUIRED ARGUMENTS
|
||
|
|
||
|
A complete list of every argument that must appear on the command line.
|
||
|
when the application is invoked, explaining what each of them does, any
|
||
|
restrictions on where each one may appear (i.e. flags that must appear
|
||
|
before or after filenames), and how the various arguments and options
|
||
|
may interact (e.g. mutual exclusions, required combinations, etc.)
|
||
|
|
||
|
If all of the application's arguments are optional this section
|
||
|
may be omitted entirely.
|
||
|
|
||
|
|
||
|
=head1 OPTIONS
|
||
|
|
||
|
A complete list of every available option with which the application
|
||
|
can be invoked, explaining what each does, and listing any restrictions,
|
||
|
or interactions.
|
||
|
|
||
|
If the application has no options this section may be omitted entirely.
|
||
|
|
||
|
|
||
|
=head1 DESCRIPTION
|
||
|
|
||
|
A full description of the application and its features.
|
||
|
May include numerous subsections (i.e. =head2, =head3, etc.)
|
||
|
|
||
|
|
||
|
=head1 DIAGNOSTICS
|
||
|
|
||
|
A list of every error and warning message that the application can generate
|
||
|
(even the ones that will "never happen"), with a full explanation of each
|
||
|
problem, one or more likely causes, and any suggested remedies. If the
|
||
|
application generates exit status codes (e.g. under Unix) then list the exit
|
||
|
status associated with each error.
|
||
|
|
||
|
|
||
|
=head1 CONFIGURATION AND ENVIRONMENT
|
||
|
|
||
|
A full explanation of any configuration system(s) used by the application,
|
||
|
including the names and locations of any configuration files, and the
|
||
|
meaning of any environment variables or properties that can be set. These
|
||
|
descriptions must also include details of any configuration language used
|
||
|
|
||
|
|
||
|
=head1 DEPENDENCIES
|
||
|
|
||
|
A list of all the other modules that this module relies upon, including any
|
||
|
restrictions on versions, and an indication whether these required modules are
|
||
|
part of the standard Perl distribution, part of the module's distribution,
|
||
|
or must be installed separately.
|
||
|
|
||
|
|
||
|
=head1 INCOMPATIBILITIES
|
||
|
|
||
|
A list of any modules that this module cannot be used in conjunction with.
|
||
|
This may be due to name conflicts in the interface, or competition for
|
||
|
system or program resources, or due to internal limitations of Perl
|
||
|
(for example, many modules that use source code filters are mutually
|
||
|
incompatible).
|
||
|
|
||
|
|
||
|
=head1 BUGS AND LIMITATIONS
|
||
|
|
||
|
A list of known problems with the module, together with some indication
|
||
|
whether they are likely to be fixed in an upcoming release.
|
||
|
|
||
|
Also a list of restrictions on the features the module does provide:
|
||
|
data types that cannot be handled, performance issues and the circumstances
|
||
|
in which they may arise, practical limitations on the size of data sets,
|
||
|
special cases that are not (yet) handled, etc.
|
||
|
|
||
|
The initial template usually just has:
|
||
|
|
||
|
There are no known bugs in this module.
|
||
|
Please report problems to <Maintainer name(s)> (<contact address>)
|
||
|
Patches are welcome.
|
||
|
|
||
|
=head1 AUTHOR
|
||
|
|
||
|
<Author name(s)> (<contact address>)
|
||
|
|
||
|
|
||
|
=head1 LICENCE AND COPYRIGHT
|
||
|
|
||
|
Copyright (c) <year> <copyright holder> (<contact address>). All rights reserved.
|
||
|
|
||
|
Followed by whatever licence you wish to release it under.
|
||
|
For Perl code that is often just:
|
||
|
|
||
|
This module is free software; you can redistribute it and/or
|
||
|
modify it under the same terms as Perl itself. See perldoc perlartistic.
|
||
|
|
||
|
This program is distributed in the hope that it will be useful,
|
||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||
|
|
||
|
=cut
|
||
|
|
||
|
#}}}
|
||
|
|
||
|
# [zo to open fold, zc to close]
|
||
|
|
||
|
# vim: syntax=perl:ts=8:sw=4:et:ai:tw=78:fo=tcrqn21:fdm=marker
|
||
|
|