1439 lines
		
	
	
		
			41 KiB
		
	
	
	
		
			Perl
		
	
	
		
			Executable File
		
	
	
	
	
			
		
		
	
	
			1439 lines
		
	
	
		
			41 KiB
		
	
	
	
		
			Perl
		
	
	
		
			Executable File
		
	
	
	
	
| #!/usr/bin/env perl
 | |
| #===============================================================================
 | |
| #
 | |
| #         FILE: copy_mysql_pg
 | |
| #
 | |
| #        USAGE: ./copy_mysql_pg
 | |
| #
 | |
| #  DESCRIPTION: Copies HPR show data from the MariaDB database to an
 | |
| #               experimental PostgreSQL database
 | |
| #
 | |
| #      OPTIONS: ---
 | |
| # REQUIREMENTS: ---
 | |
| #         BUGS: ---
 | |
| #        NOTES: ---
 | |
| #       AUTHOR: Dave Morriss (djm), Dave.Morriss@gmail.com
 | |
| #      VERSION: 0.0.4
 | |
| #      CREATED: 2017-03-15 18:50:08
 | |
| #     REVISION: 2017-10-17 22:37:08
 | |
| #
 | |
| #===============================================================================
 | |
| 
 | |
| 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 Text::CSV;
 | |
| use DBI;
 | |
| 
 | |
| use Data::Dumper;
 | |
| 
 | |
| #
 | |
| # Version number (manually incremented)
 | |
| #
 | |
| our $VERSION = '0.0.4';
 | |
| 
 | |
| #
 | |
| # 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/Database";
 | |
| my $configfile1 = "$basedir/.hpr_db.cfg";
 | |
| my $configfile2 = "$basedir/.hpr_pg.cfg";
 | |
| 
 | |
| my $email_template  = 'host_%s@hackerpublicradio.org';
 | |
| 
 | |
| my ( $dbh1, $sth1, $h1, $rv1 );
 | |
| my ( $dbh2, $sth2, $h2, $rv2 );
 | |
| 
 | |
| my (@phase_choices);
 | |
| my ( %eps_tags, %data );
 | |
| 
 | |
| my @phases = (
 | |
|     'episodes', 'hosts', 'eh_xref', 'series', 'es_xref', 'tags',
 | |
|     'comments', 'twat',  'epilogue'
 | |
| );
 | |
| 
 | |
| #
 | |
| # Enable Unicode mode
 | |
| #
 | |
| binmode STDOUT, ":encoding(UTF-8)";
 | |
| binmode STDERR, ":encoding(UTF-8)";
 | |
| 
 | |
| #
 | |
| # Load database configuration data
 | |
| #
 | |
| my $conf1 = Config::General->new(
 | |
|     -ConfigFile      => $configfile1,
 | |
|     -InterPolateVars => 1,
 | |
|     -ExtendedAccess  => 1
 | |
| );
 | |
| my %config1 = $conf1->getall();
 | |
| 
 | |
| my $conf2 = Config::General->new(
 | |
|     -ConfigFile      => $configfile2,
 | |
|     -InterPolateVars => 1,
 | |
|     -ExtendedAccess  => 1
 | |
| );
 | |
| my %config2 = $conf2->getall();
 | |
| 
 | |
| #-------------------------------------------------------------------------------
 | |
| # 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 $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;
 | |
| }
 | |
| 
 | |
| #-------------------------------------------------------------------------------
 | |
| # 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;
 | |
| 
 | |
| my %choices = map  { $_ => 1 } @phase_choices;
 | |
| 
 | |
| #
 | |
| # Perform phases in order, omitting those that are not in the list
 | |
| #
 | |
| for my $phase (@phases) {
 | |
|     #---------------------------------------------------------------------------
 | |
|     # Copy the 'eps' table to 'episodes'
 | |
|     #---------------------------------------------------------------------------
 | |
|     if ( $phase eq 'episodes' && exists( $choices{$phase} ) ) {
 | |
|         print "Build episodes table\n" if ( $verbose > 0 );
 | |
|         if ( check_table( $dbh2, 'episodes' ) ) {
 | |
|             build_episodes_table( $dbh1, $dbh2, $verbose ) unless $dry_run;
 | |
|         }
 | |
|         else {
 | |
|             print "** Table 'episodes' is not empty\n";
 | |
|         }
 | |
|     }
 | |
|     #---------------------------------------------------------------------------
 | |
|     # Copy the 'hosts' table to 'hosts'
 | |
|     #---------------------------------------------------------------------------
 | |
|     elsif ( $phase eq 'hosts' && exists( $choices{$phase} ) ) {
 | |
|         print "Build hosts table\n" if ( $verbose > 0 );
 | |
|         if ( check_table( $dbh2, 'hosts' ) ) {
 | |
|             build_hosts_table( $dbh1, $dbh2, $verbose ) unless $dry_run;
 | |
|         }
 | |
|         else {
 | |
|             print "** Table 'hosts' is not empty\n";
 | |
|         }
 | |
|     }
 | |
|     #---------------------------------------------------------------------------
 | |
|     # Generate the 'episodes_hosts_xref' table
 | |
|     #---------------------------------------------------------------------------
 | |
|     elsif ( $phase eq 'eh_xref' && exists( $choices{$phase} ) ) {
 | |
|         print "Build episodes_hosts_xref table\n" if ( $verbose > 0 );
 | |
|         if ( check_table( $dbh2, 'episodes_hosts_xref' ) ) {
 | |
|             build_episodes_hosts_xref_table( $dbh1, $dbh2, $verbose )
 | |
|                 unless $dry_run;
 | |
|         }
 | |
|         else {
 | |
|             print "** Table 'episodes_hosts_xref' is not empty\n";
 | |
|         }
 | |
|     }
 | |
|     #---------------------------------------------------------------------------
 | |
|     # Copy the 'miniseries' table to 'series'
 | |
|     #---------------------------------------------------------------------------
 | |
|     elsif ( $phase eq 'series' && exists( $choices{$phase} ) ) {
 | |
|         print "Build series table\n" if ( $verbose > 0 );
 | |
|         if ( check_table( $dbh2, 'series' ) ) {
 | |
|             build_series_table( $dbh1, $dbh2, $verbose ) unless $dry_run;
 | |
|         }
 | |
|         else {
 | |
|             print "** Table 'series' is not empty\n";
 | |
|         }
 | |
|     }
 | |
|     #---------------------------------------------------------------------------
 | |
|     # Generate the 'episodes_series_xref' table
 | |
|     #---------------------------------------------------------------------------
 | |
|     elsif ( $phase eq 'es_xref' && exists( $choices{$phase} ) ) {
 | |
|         print "Build episodes_series_xref table\n" if ( $verbose > 0 );
 | |
|         if ( check_table( $dbh2, 'episodes_series_xref' ) ) {
 | |
|             build_episodes_series_xref_table( $dbh1, $dbh2, $verbose )
 | |
|                 unless $dry_run;
 | |
|         }
 | |
|         else {
 | |
|             print "** Table 'episodes_series_xref' is not empty\n";
 | |
|         }
 | |
|     }
 | |
|     #---------------------------------------------------------------------------
 | |
|     #  Collect and store the id numbers and tags from the 'eps' table, then add
 | |
|     #  them to the PostgreSQL tables.
 | |
|     #---------------------------------------------------------------------------
 | |
|     elsif ( $phase eq 'tags' && exists( $choices{$phase} ) ) {
 | |
|         print "Build tags and episodes_tags_xref tables\n" if ( $verbose > 0 );
 | |
|         if ( check_table( $dbh2, 'tags' )
 | |
|             && check_table( $dbh2, 'episodes_tags_xref' ) )
 | |
|         {
 | |
|             unless ($dry_run) {
 | |
|                 %eps_tags = %{ collect_eps_tags( $dbh1, $verbose ) };
 | |
| 
 | |
|                 if (%eps_tags) {
 | |
|                     build_tags_table( $dbh2, $verbose, \%eps_tags );
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
|         else {
 | |
|             print "** Tables 'tags' and/or 'episodes_tags_xref' are not empty\n";
 | |
|         }
 | |
|     }
 | |
|     #---------------------------------------------------------------------------
 | |
|     # Copy the 'comments' table to 'comments'
 | |
|     #---------------------------------------------------------------------------
 | |
|     elsif ( $phase eq 'comments' && exists( $choices{$phase} ) ) {
 | |
|         print "Build comments table\n" if ( $verbose > 0 );
 | |
|         if (check_table($dbh2,'comments')) {
 | |
|             build_comments_table( $dbh1, $dbh2, $verbose ) unless $dry_run;
 | |
|         }
 | |
|         else {
 | |
|             print "** Table 'comments' is not empty\n";
 | |
|         }
 | |
|     }
 | |
|     #---------------------------------------------------------------------------
 | |
|     # Perform the 'twat' actions
 | |
|     #---------------------------------------------------------------------------
 | |
|     elsif ( $phase eq 'twat' && exists( $choices{$phase} ) ) {
 | |
|         print "Perform twat actions\n" if ( $verbose > 0 );
 | |
| 
 | |
|         #
 | |
|         # Incorporate the TwaT tables (assuming they exist)
 | |
|         #
 | |
|         if (check_table($dbh2,'twat_hosts')) {
 | |
|             load_twat_hosts($dbh1,$dbh2,$verbose) unless $dry_run;
 | |
|         }
 | |
|         else {
 | |
|             print "** Table 'twat_hosts' is not empty\n";
 | |
|         }
 | |
| 
 | |
|         if (check_table($dbh2,'twat_episodes')) {
 | |
|             load_twat_episodes($dbh1,$dbh2,$verbose) unless $dry_run;
 | |
|         }
 | |
|         else {
 | |
|             print "** Table 'twat_episodes' is not empty\n";
 | |
|         }
 | |
|     }
 | |
|     #---------------------------------------------------------------------------
 | |
|     # Perform the 'epilogue' actions
 | |
|     #---------------------------------------------------------------------------
 | |
|     elsif ( $phase eq 'epilogue' && exists( $choices{$phase} ) ) {
 | |
|         print "Perform epilogue actions\n" if ( $verbose > 0 );
 | |
| 
 | |
|         #
 | |
|         # Determine the first show date per host, assuming that's when they
 | |
|         # were added to the database
 | |
|         #
 | |
|         compute_host_date_added($dbh2) unless $dry_run;
 | |
|     }
 | |
| }
 | |
| 
 | |
| exit;
 | |
| 
 | |
| #===  FUNCTION  ================================================================
 | |
| #         NAME: build_episodes_table
 | |
| #      PURPOSE: Copy the data from the MariaDB 'eps' table to the Pg
 | |
| #               'episodes' table
 | |
| #   PARAMETERS: $dbh1           Handle for the MariaDB database
 | |
| #               $dbh2           Handle for the Pg database
 | |
| #               $verbose        Verbosity level
 | |
| #      RETURNS: Nothing
 | |
| #  DESCRIPTION:
 | |
| #       THROWS: No exceptions
 | |
| #     COMMENTS: None
 | |
| #     SEE ALSO: N/A
 | |
| #===============================================================================
 | |
| sub build_episodes_table {
 | |
|     my ( $dbh1, $dbh2, $verbose ) = @_;
 | |
| 
 | |
|     my ( $sth1, $h1, $sth2, $count );
 | |
| 
 | |
|     $sth1 = $dbh1->prepare('SELECT * FROM eps') or die $DBI::errstr;
 | |
|     if ( $dbh1->err ) {
 | |
|         warn $dbh1->errstr;
 | |
|     }
 | |
| 
 | |
|     $sth2 = $dbh2->prepare('INSERT INTO episodes VALUES (?,?,?,?,?,?,?,?,?)')
 | |
|         or die $DBI::errstr;
 | |
|     if ( $dbh2->err ) {
 | |
|         warn $dbh2->errstr;
 | |
|     }
 | |
| 
 | |
|     #
 | |
|     # Query MariaDB for the entire 'eps' table
 | |
|     #
 | |
|     $sth1->execute;
 | |
|     if ( $dbh1->err ) {
 | |
|         die $dbh1->errstr;
 | |
|     }
 | |
| 
 | |
|     #
 | |
|     # Loop though 'eps' table rows writing them to the PostgreSQL 'episodes'
 | |
|     # table
 | |
|     #
 | |
|     $count = 0;
 | |
|     while ( $h1 = $sth1->fetchrow_hashref ) {
 | |
|         $count++;
 | |
|         $sth2->execute(
 | |
|             $h1->{id}, $h1->{date},
 | |
|             $h1->{title},   nullif( $h1->{summary}, '^\s*$' ),
 | |
|             $h1->{notes},   $h1->{explicit},
 | |
|             $h1->{license}, $h1->{duration},
 | |
|             $h1->{downloads},
 | |
|         );
 | |
|         if ( $dbh2->err ) {
 | |
|             die $dbh2->errstr;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     print "Copied $count records\n" if ($verbose > 0);
 | |
| 
 | |
|     #
 | |
|     # Set the sequence to the correct value
 | |
|     #
 | |
|     alter_seq($dbh2,'episodes','episode_seq');
 | |
| 
 | |
| }
 | |
| 
 | |
| #===  FUNCTION  ================================================================
 | |
| #         NAME: build_hosts_table
 | |
| #      PURPOSE: Copy the data from the Mariadb 'hosts' table to the Pg 'hosts'
 | |
| #               table
 | |
| #   PARAMETERS: $dbh1           Handle for the MariaDB table
 | |
| #               $dbh2           Handle for the Pg database
 | |
| #               $verbose        Verbosity level
 | |
| #      RETURNS: Nothing
 | |
| #  DESCRIPTION:
 | |
| #       THROWS: No exceptions
 | |
| #     COMMENTS: None
 | |
| #     SEE ALSO: N/A
 | |
| #===============================================================================
 | |
| sub build_hosts_table {
 | |
|     my ( $dbh1, $dbh2, $verbose ) = @_;
 | |
| 
 | |
|     my ( $sth1, $h1, $sth2, $rv, $count );
 | |
| 
 | |
|     $sth1 = $dbh1->prepare('SELECT * FROM hosts') or die $DBI::errstr;
 | |
|     if ( $dbh1->err ) {
 | |
|         warn $dbh1->errstr;
 | |
|     }
 | |
| 
 | |
|     $sth2 = $dbh2->prepare(
 | |
|         q{INSERT INTO hosts
 | |
|             (id, host, email, profile, license, local_image, gpg, valid)
 | |
|             VALUES (?,?,?,?,?,?,?,?)}
 | |
|     ) or die $DBI::errstr;
 | |
|     if ( $dbh2->err ) {
 | |
|         warn $dbh2->errstr;
 | |
|     }
 | |
| 
 | |
|     $sth1->execute;
 | |
|     if ( $dbh1->err ) {
 | |
|         die $dbh1->errstr;
 | |
|     }
 | |
| 
 | |
|     $count = 0;
 | |
|     while ( $h1 = $sth1->fetchrow_hashref ) {
 | |
|         $count++;
 | |
|         $sth2->execute(
 | |
|             $h1->{hostid},
 | |
|             $h1->{host},
 | |
|             default_email(
 | |
|                 $h1->{email},    '^(\s*|admin@hackerpublicradio.org)$',
 | |
|                 $email_template, $h1->{hostid}
 | |
|             ),
 | |
|             nullif( $h1->{profile}, '^\s*$' ),
 | |
|             $h1->{license},
 | |
|             $h1->{local_image},
 | |
|             nullif( $h1->{gpg}, '^\s*$' ),
 | |
|             $h1->{valid},
 | |
|         );
 | |
|         if ( $dbh2->err ) {
 | |
|             die $dbh2->errstr;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     print "Copied $count records\n" if ( $verbose > 0 );
 | |
| 
 | |
|     #
 | |
|     # Set the sequence to the correct value
 | |
|     #
 | |
|     alter_seq( $dbh2, 'hosts', 'host_seq' );
 | |
| 
 | |
| }
 | |
| 
 | |
| #===  FUNCTION  ================================================================
 | |
| #         NAME: build_episodes_hosts_xref_table
 | |
| #      PURPOSE: Generates the cross reference table by examining the 'eps' and
 | |
| #               'hosts' tables in the MariaDB database.
 | |
| #   PARAMETERS: $dbh1           Handle for the MariaDB table
 | |
| #               $dbh2           Handle for the Pg database
 | |
| #               $verbose        Verbosity level
 | |
| #      RETURNS: Nothing
 | |
| #  DESCRIPTION:
 | |
| #       THROWS: No exceptions
 | |
| #     COMMENTS: None
 | |
| #     SEE ALSO: N/A
 | |
| #===============================================================================
 | |
| sub build_episodes_hosts_xref_table {
 | |
|     my ( $dbh1, $dbh2, $verbose ) = @_;
 | |
| 
 | |
|     my ( $sth1, $h1, $sth2, $count );
 | |
| 
 | |
|     $sth1
 | |
|         = $dbh1->prepare(
 | |
|         'SELECT e.id,h.hostid FROM eps e, hosts h WHERE e.hostid = h.hostid')
 | |
|         or die $DBI::errstr;
 | |
|     if ( $dbh1->err ) {
 | |
|         warn $dbh1->errstr;
 | |
|     }
 | |
| 
 | |
|     $sth2 = $dbh2->prepare('INSERT INTO episodes_hosts_xref VALUES (?,?)')
 | |
|         or die $DBI::errstr;
 | |
|     if ( $dbh2->err ) {
 | |
|         warn $dbh2->errstr;
 | |
|     }
 | |
| 
 | |
|     $sth1->execute;
 | |
|     if ( $dbh1->err ) {
 | |
|         die $dbh1->errstr;
 | |
|     }
 | |
| 
 | |
|     $count = 0;
 | |
|     while ( $h1 = $sth1->fetchrow_hashref ) {
 | |
|         $count++;
 | |
|         $sth2->execute( $h1->{id}, $h1->{hostid}, );
 | |
|         if ( $dbh2->err ) {
 | |
|             die $dbh2->errstr;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     print "Copied $count records\n" if ($verbose > 0);
 | |
| 
 | |
| }
 | |
| 
 | |
| #===  FUNCTION  ================================================================
 | |
| #         NAME: build_series_table
 | |
| #      PURPOSE: Copy the data from the Mariadb 'miniseries' table to the Pg
 | |
| #               'series' table
 | |
| #   PARAMETERS: $dbh1           Handle for the MariaDB database
 | |
| #               $dbh2           Handle for the Pg database
 | |
| #               $verbose        Verbosity level
 | |
| #      RETURNS: Nothing
 | |
| #  DESCRIPTION:
 | |
| #       THROWS: No exceptions
 | |
| #     COMMENTS: None
 | |
| #     SEE ALSO: N/A
 | |
| #===============================================================================
 | |
| sub build_series_table {
 | |
|     my ( $dbh1, $dbh2, $verbose ) = @_;
 | |
| 
 | |
|     my ( $sth1, $h1, $sth2, $count );
 | |
| 
 | |
|     $sth1 = $dbh1->prepare('SELECT * FROM miniseries') or die $DBI::errstr;
 | |
|     if ( $dbh1->err ) {
 | |
|         warn $dbh1->errstr;
 | |
|     }
 | |
| 
 | |
|     $sth2 = $dbh2->prepare('INSERT INTO series VALUES (?,?,?,?,?,?)')
 | |
|         or die $DBI::errstr;
 | |
|     if ( $dbh2->err ) {
 | |
|         warn $dbh2->errstr;
 | |
|     }
 | |
| 
 | |
|     #
 | |
|     # Query MariaDB for the entire 'miniseries' table
 | |
|     #
 | |
|     $sth1->execute;
 | |
|     if ( $dbh1->err ) {
 | |
|         die $dbh1->errstr;
 | |
|     }
 | |
| 
 | |
|     #
 | |
|     # Loop though 'miniseries' table rows writing them to the PostgreSQL
 | |
|     # 'series' table
 | |
|     #
 | |
|     $count = 0;
 | |
|     while ( $h1 = $sth1->fetchrow_hashref ) {
 | |
|         $count++;
 | |
|         $sth2->execute(
 | |
|             $h1->{id}, $h1->{name},
 | |
|             $h1->{description}, $h1->{private},
 | |
|             nullif( $h1->{image}, '^\s*$' ),
 | |
|             $h1->{valid},
 | |
|         );
 | |
|         if ( $dbh2->err ) {
 | |
|             die $dbh2->errstr;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     print "Copied $count records\n" if ($verbose > 0);
 | |
| 
 | |
|     #
 | |
|     # Set the sequence to the correct value
 | |
|     #
 | |
|     alter_seq($dbh2,'series','series_seq');
 | |
| 
 | |
| }
 | |
| 
 | |
| #===  FUNCTION  ================================================================
 | |
| #         NAME: build_episodes_series_xref_table
 | |
| #      PURPOSE: Generates the cross reference table by examining the 'eps' and
 | |
| #               'series' tables in the MariaDB database.
 | |
| #   PARAMETERS: $dbh1           Handle for the MariaDB table
 | |
| #               $dbh2           Handle for the Pg database
 | |
| #               $verbose        Verbosity level
 | |
| #      RETURNS: Nothing
 | |
| #  DESCRIPTION:
 | |
| #       THROWS: No exceptions
 | |
| #     COMMENTS: None
 | |
| #     SEE ALSO: N/A
 | |
| #===============================================================================
 | |
| sub build_episodes_series_xref_table {
 | |
|     my ( $dbh1, $dbh2, $verbose ) = @_;
 | |
| 
 | |
|     my ( $sth1, $h1, $sth2, $count );
 | |
| 
 | |
|     $sth1
 | |
|         = $dbh1->prepare(
 | |
|         'SELECT e.id AS epid, m.id AS msid FROM eps e, miniseries m WHERE e.series = m.id')
 | |
|         or die $DBI::errstr;
 | |
|     if ( $dbh1->err ) {
 | |
|         warn $dbh1->errstr;
 | |
|     }
 | |
| 
 | |
|     $sth2 = $dbh2->prepare('INSERT INTO episodes_series_xref VALUES (?,?)')
 | |
|         or die $DBI::errstr;
 | |
|     if ( $dbh2->err ) {
 | |
|         warn $dbh2->errstr;
 | |
|     }
 | |
| 
 | |
|     $sth1->execute;
 | |
|     if ( $dbh1->err ) {
 | |
|         die $dbh1->errstr;
 | |
|     }
 | |
| 
 | |
|     $count = 0;
 | |
|     while ( $h1 = $sth1->fetchrow_hashref ) {
 | |
|         $count++;
 | |
|         $sth2->execute( $h1->{epid}, $h1->{msid}, );
 | |
|         if ( $dbh2->err ) {
 | |
|             die $dbh2->errstr;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     print "Copied $count records\n" if ($verbose > 0);
 | |
| 
 | |
| }
 | |
| 
 | |
| #===  FUNCTION  ================================================================
 | |
| #         NAME: build_tags_table
 | |
| #      PURPOSE: Using the data structure built from the MariaDB database
 | |
| #               populate the many-to-many table in the Pg database
 | |
| #   PARAMETERS: $dbh            Handle for the Pg database
 | |
| #               $verbose        Verbosity level
 | |
| #               $tag_hash       Reference to a hash of episode ids and tags
 | |
| #                               for each episode
 | |
| #      RETURNS: Nothing
 | |
| #  DESCRIPTION:
 | |
| #       THROWS: No exceptions
 | |
| #     COMMENTS: None
 | |
| #     SEE ALSO: N/A
 | |
| #===============================================================================
 | |
| sub build_tags_table {
 | |
|     my ( $dbh, $verbose, $tag_hash ) = @_;
 | |
| 
 | |
|     my ( $sth1, $h1, $sth2, $sth3, $rv, $tags, $tid, $count1, $count2 );
 | |
| 
 | |
|     #
 | |
|     # Query to find if a tag already exists
 | |
|     #
 | |
|     $sth1 = $dbh->prepare(q{SELECT * FROM tags WHERE tag = ?});
 | |
| 
 | |
|     #
 | |
|     # Query to add a new tag
 | |
|     #
 | |
|     $sth2 = $dbh->prepare(q{INSERT INTO tags (tag) VALUES(?)});
 | |
| 
 | |
|     #
 | |
|     # Query to add a new joining row
 | |
|     #
 | |
|     $sth3 = $dbh->prepare(q{INSERT INTO episodes_tags_xref VALUES(?,?)});
 | |
| 
 | |
|     $count1 = $count2 = 0;
 | |
|     foreach my $id ( sort { $a <=> $b } keys( %{$tag_hash} ) ) {
 | |
|         #
 | |
|         # Get the array of tags for this episode id
 | |
|         #
 | |
|         $tags = $tag_hash->{$id};
 | |
| 
 | |
|         #
 | |
|         # Loop through the array of tags (using an integer so we can index the
 | |
|         # current tag)
 | |
|         #
 | |
|         for my $i ( 0 .. $#$tags ) {
 | |
|             #
 | |
|             # Look to see if this tag exists
 | |
|             #
 | |
|             $sth1->execute( $tags->[$i] );
 | |
|             if ( $dbh->err ) {
 | |
|                 warn $dbh->errstr;
 | |
|             }
 | |
| 
 | |
|             #
 | |
|             # If it's already in the table just store the id for later
 | |
|             # otherwise add a new entry
 | |
|             #
 | |
|             if ( $h1 = $sth1->fetchrow_hashref ) {
 | |
|                 $tid = $h1->{id};
 | |
|             }
 | |
|             else {
 | |
|                 #
 | |
|                 # Add the tag to 'tags'
 | |
|                 #
 | |
|                 $count1++;
 | |
|                 $rv = $sth2->execute( $tags->[$i] );
 | |
|                 if ( $dbh->err ) {
 | |
|                     warn $dbh->errstr;
 | |
|                 }
 | |
|                 $rv = 0 if ( $rv eq '0E0' );
 | |
| 
 | |
|                 #
 | |
|                 # Get the id number of the inserted tag
 | |
|                 #
 | |
|                 $tid = $dbh->last_insert_id( undef, undef, undef, undef,
 | |
|                     { sequence => 'tag_seq' } );
 | |
|             }
 | |
| 
 | |
|             $count2++;
 | |
|             $rv = $sth3->execute( $id, $tid );
 | |
|             if ( $dbh->err ) {
 | |
|                 warn $dbh->errstr;
 | |
|             }
 | |
|             $rv = 0 if ( $rv eq '0E0' );
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     if ($verbose > 0) {
 | |
|         print "Added $count1 tags\n";
 | |
|         print "Added $count2 cross references\n";
 | |
|     }
 | |
| 
 | |
| }
 | |
| 
 | |
| #===  FUNCTION  ================================================================
 | |
| #         NAME: collect_eps_tags
 | |
| #      PURPOSE: Collects the tags from the eps.tags field
 | |
| #   PARAMETERS: $dbh            Database handle
 | |
| #               $verbose        Verbosity level
 | |
| #      RETURNS: A reference to the hash created by collecting all the tags
 | |
| #  DESCRIPTION: Queries the MariaDB 'eps' table for all of the rows containing
 | |
| #               tags, returning the comma-separated list with the id number.
 | |
| #               Each CSV list is then parsed and the result turned into a hash
 | |
| #               keyed on the id number and containing a sorted array of tags.
 | |
| #               If the level of verbosity is greater than 2 the tags has is
 | |
| #               dumped (ironically, as a CSV list!).
 | |
| #       THROWS: No exceptions
 | |
| #     COMMENTS: None
 | |
| #     SEE ALSO: N/A
 | |
| #===============================================================================
 | |
| sub collect_eps_tags {
 | |
|     my ( $dbh, $verbose ) = @_;
 | |
| 
 | |
|     my ( $status, @fields, %hash );
 | |
|     my ( $sth, $h );
 | |
| 
 | |
|     #
 | |
|     # For parsing the field as CSV
 | |
|     #
 | |
|     my $csv = Text::CSV_XS->new;
 | |
| 
 | |
|     #
 | |
|     # Query the MariaDB 'eps' table for all the id and tags
 | |
|     #
 | |
|     $sth = $dbh->prepare(
 | |
|         q{SELECT id,tags FROM eps
 | |
|             WHERE length(tags) > 0
 | |
|             ORDER BY id}
 | |
|     ) or die $DBI::errstr;
 | |
|     if ( $dbh->err ) {
 | |
|         warn $dbh->errstr;
 | |
|     }
 | |
| 
 | |
|     $sth->execute;
 | |
|     if ( $dbh->err ) {
 | |
|         warn $dbh->errstr;
 | |
|     }
 | |
| 
 | |
|     #
 | |
|     # Loop through what we got
 | |
|     #
 | |
|     while ( $h = $sth->fetchrow_hashref ) {
 | |
|         #
 | |
|         # Parse the tag list
 | |
|         #
 | |
|         $status = $csv->parse( $h->{tags} );
 | |
|         unless ($status) {
 | |
|             #
 | |
|             # Report any errors
 | |
|             #
 | |
|             print "Parse error on episode ", $h->{id}, "\n";
 | |
|             print $csv->error_input(), "\n";
 | |
|             next;
 | |
|         }
 | |
|         @fields = $csv->fields();
 | |
| 
 | |
|         next unless (@fields);
 | |
| 
 | |
|         #
 | |
|         # Trim all tags (don't alter $_ when doing it)
 | |
|         #
 | |
|         @fields = map {
 | |
|             my $t = $_;
 | |
|             $t =~ s/(^\s+|\s+$)//g;
 | |
|             $t;
 | |
|         } @fields;
 | |
| 
 | |
|         #print "$h->{id}: ",join(",",@fields),"\n";
 | |
| 
 | |
|         #
 | |
|         # Save the id and its tags, sorted for comparison
 | |
|         #
 | |
|         $hash{ $h->{id} } = [ sort @fields ];
 | |
| 
 | |
|     }
 | |
| 
 | |
|     #
 | |
|     # Dump all id numbers and tags if the verbose level is high enough
 | |
|     #
 | |
|     if ( $verbose >= 2 ) {
 | |
|         print "\nTags collected from the 'eps' table\n\n";
 | |
|         foreach my $id ( sort { $a <=> $b } keys(%hash) ) {
 | |
|             printf "%04d: %s\n", $id, join( ",", @{ $hash{$id} } );
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     return \%hash;
 | |
| 
 | |
| }
 | |
| 
 | |
| #===  FUNCTION  ================================================================
 | |
| #         NAME: build_comments_table
 | |
| #      PURPOSE: Copy the data from the Mariadb 'comments' table to the Pg
 | |
| #               'comments' table
 | |
| #   PARAMETERS: $dbh1           Handle for the MariaDB database
 | |
| #               $dbh2           Handle for the Pg database
 | |
| #               $verbose        Verbosity level
 | |
| #      RETURNS: Nothing
 | |
| #  DESCRIPTION:
 | |
| #       THROWS: No exceptions
 | |
| #     COMMENTS: None
 | |
| #     SEE ALSO: N/A
 | |
| #===============================================================================
 | |
| sub build_comments_table {
 | |
|     my ( $dbh1, $dbh2, $verbose ) = @_;
 | |
| 
 | |
|     my ( $sth1, $h1, $sth2, $count );
 | |
| 
 | |
|     $sth1 = $dbh1->prepare('SELECT * FROM comments') or die $DBI::errstr;
 | |
|     if ( $dbh1->err ) {
 | |
|         warn $dbh1->errstr;
 | |
|     }
 | |
| 
 | |
|     $sth2 = $dbh2->prepare('INSERT INTO comments VALUES (?,?,?,?,?,?,?)')
 | |
|         or die $DBI::errstr;
 | |
|     if ( $dbh2->err ) {
 | |
|         warn $dbh2->errstr;
 | |
|     }
 | |
| 
 | |
|     #
 | |
|     # Query MariaDB for the entire 'comments' table
 | |
|     #
 | |
|     $sth1->execute;
 | |
|     if ( $dbh1->err ) {
 | |
|         die $dbh1->errstr;
 | |
|     }
 | |
| 
 | |
|     #
 | |
|     # Loop though 'comments' table rows writing them to the PostgreSQL
 | |
|     # 'comments' table
 | |
|     #
 | |
|     $count = 0;
 | |
|     while ( $h1 = $sth1->fetchrow_hashref ) {
 | |
|         $count++;
 | |
|         $sth2->execute(
 | |
|             $h1->{id},
 | |
|             $h1->{eps_id},
 | |
|             $h1->{comment_timestamp},
 | |
|             nullif( $h1->{comment_author_name}, '^\s*$' ),
 | |
|             nullif( $h1->{comment_title},       '^\s*$' ),
 | |
|             $h1->{comment_text},
 | |
|             $h1->{last_changed},
 | |
|         );
 | |
|         if ( $dbh2->err ) {
 | |
|             die $dbh2->errstr;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     print "Copied $count records\n" if ($verbose > 0);
 | |
| 
 | |
|     #
 | |
|     # Set the sequence to the correct value
 | |
|     #
 | |
|     alter_seq($dbh2,'comments','comment_seq');
 | |
| 
 | |
| }
 | |
| 
 | |
| #===  FUNCTION  ================================================================
 | |
| #         NAME: load_twat_hosts
 | |
| #      PURPOSE: Copy the temporary twat_hosts table from the MySQL database
 | |
| #               into the PostgreSQL equivalent ready for merging with the HPR
 | |
| #               data
 | |
| #   PARAMETERS: $dbh1           Handle for the MariaDB database
 | |
| #               $dbh2           Handle for the Pg database
 | |
| #               $verbose        Verbosity level
 | |
| #      RETURNS: Nothing
 | |
| #  DESCRIPTION: 
 | |
| #       THROWS: No exceptions
 | |
| #     COMMENTS: None
 | |
| #     SEE ALSO: N/A
 | |
| #===============================================================================
 | |
| sub load_twat_hosts {
 | |
|     my ( $dbh1, $dbh2, $verbose ) = @_;
 | |
| 
 | |
|     my ( $sth1, $h1, $sth2, $count );
 | |
| 
 | |
|     #
 | |
|     # Copy the 'twat_hosts' table
 | |
|     #
 | |
|     $sth1 = $dbh1->prepare('SELECT * FROM twat_hosts') or die $DBI::errstr;
 | |
|     if ( $dbh1->err ) {
 | |
|         warn $dbh1->errstr;
 | |
|     }
 | |
| 
 | |
|     #
 | |
|     # Omit the 'id' here because it's an addition for PostgreSQL
 | |
|     #
 | |
|     $sth2
 | |
|         = $dbh2->prepare(
 | |
|         'INSERT INTO twat_hosts (host,email,website,repeat) VALUES (?,?,?,?)')
 | |
|         or die $DBI::errstr;
 | |
|     if ( $dbh2->err ) {
 | |
|         warn $dbh2->errstr;
 | |
|     }
 | |
| 
 | |
|     #
 | |
|     # Query MariaDB for the entire 'comments' table
 | |
|     #
 | |
|     $sth1->execute;
 | |
|     if ( $dbh1->err ) {
 | |
|         die $dbh1->errstr;
 | |
|     }
 | |
| 
 | |
|     #
 | |
|     # Loop though 'twat_hosts' table rows writing them to the PostgreSQL
 | |
|     # 'twat_hosts' table
 | |
|     #
 | |
|     $count = 0;
 | |
|     while ( $h1 = $sth1->fetchrow_hashref ) {
 | |
|         $count++;
 | |
|         $sth2->execute(
 | |
|             $h1->{host},
 | |
|             nullif( $h1->{email},   '^\s*$' ),
 | |
|             nullif( $h1->{website}, '^\s*$' ),
 | |
|             $h1->{repeat},
 | |
|         );
 | |
|         if ( $dbh2->err ) {
 | |
|             die $dbh2->errstr;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     print "Copied $count records\n" if ( $verbose > 0 );
 | |
| 
 | |
| }
 | |
| 
 | |
| #===  FUNCTION  ================================================================
 | |
| #         NAME: load_twat_episodes
 | |
| #      PURPOSE: Copy the temporary twat_episodes table from the MySQL database
 | |
| #               into the PostgreSQL equivalent ready for merging with the HPR
 | |
| #               data
 | |
| #   PARAMETERS: $dbh1           Handle for the MariaDB database
 | |
| #               $dbh2           Handle for the Pg database
 | |
| #               $verbose        Verbosity level
 | |
| #      RETURNS: Nothing
 | |
| #  DESCRIPTION: 
 | |
| #       THROWS: No exceptions
 | |
| #     COMMENTS: None
 | |
| #     SEE ALSO: N/A
 | |
| #===============================================================================
 | |
| sub load_twat_episodes {
 | |
|     my ( $dbh1, $dbh2, $verbose ) = @_;
 | |
| 
 | |
|     my ( $sth1, $h1, $sth2, $count );
 | |
| 
 | |
|     #
 | |
|     # Copy the 'twat_episodes' table
 | |
|     #
 | |
|     $sth1 = $dbh1->prepare('SELECT * FROM twat_episodes') or die $DBI::errstr;
 | |
|     if ( $dbh1->err ) {
 | |
|         warn $dbh1->errstr;
 | |
|     }
 | |
| 
 | |
|     #
 | |
|     # Omit the 'id' here because it's an addition for PostgreSQL
 | |
|     #
 | |
|     $sth2
 | |
|         = $dbh2->prepare(
 | |
|         'INSERT INTO twat_episodes VALUES (?,?,?,?,?,?)')
 | |
|         or die $DBI::errstr;
 | |
|     if ( $dbh2->err ) {
 | |
|         warn $dbh2->errstr;
 | |
|     }
 | |
| 
 | |
|     #
 | |
|     # Query MariaDB for the entire 'comments' table
 | |
|     #
 | |
|     $sth1->execute;
 | |
|     if ( $dbh1->err ) {
 | |
|         die $dbh1->errstr;
 | |
|     }
 | |
| 
 | |
|     #
 | |
|     # Loop though 'twat_hosts' table rows writing them to the PostgreSQL
 | |
|     # 'twat_hosts' table
 | |
|     #
 | |
|     $count = 0;
 | |
|     while ( $h1 = $sth1->fetchrow_hashref ) {
 | |
|         $count++;
 | |
|         $sth2->execute(
 | |
|             $h1->{ep_num},
 | |
|             $h1->{date},
 | |
|             $h1->{host},
 | |
|             $h1->{topic},
 | |
|             nullif( $h1->{writeup}, '^\s*$' ),
 | |
|             $h1->{url},
 | |
|         );
 | |
|         if ( $dbh2->err ) {
 | |
|             die $dbh2->errstr;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     print "Copied $count records\n" if ( $verbose > 0 );
 | |
| 
 | |
| }
 | |
| 
 | |
| #===  FUNCTION  ================================================================
 | |
| #         NAME: compute_host_date_added
 | |
| #      PURPOSE: Determine the 'hosts.date_added' field once the database is
 | |
| #               fully populated.
 | |
| #   PARAMETERS: $dbh            Handle for the PostgreSQL database
 | |
| #      RETURNS: Nothing
 | |
| #  DESCRIPTION:
 | |
| #       THROWS: No exceptions
 | |
| #     COMMENTS: None
 | |
| #     SEE ALSO: N/A
 | |
| #===============================================================================
 | |
| sub compute_host_date_added {
 | |
|     my ($dbh) = @_;
 | |
| 
 | |
|     my $rv;
 | |
| 
 | |
|     #
 | |
|     # Allocate date_added values where possible
 | |
|     #
 | |
|     $rv = $dbh->do(
 | |
|         q{
 | |
|         UPDATE hosts
 | |
|             SET date_added = sq.date_added
 | |
|         FROM (
 | |
|             SELECT h.id,min(e.release_date) AS date_added
 | |
|             FROM episodes e
 | |
|             JOIN episodes_hosts_xref eh ON (e.id = eh.episodes_id)
 | |
|             JOIN hosts h ON (h.id = eh.hosts_id)
 | |
|             GROUP BY h.id
 | |
|             ORDER by min(e.release_date)) AS sq
 | |
|         WHERE hosts.id = sq.id
 | |
|     }
 | |
|     );
 | |
|     if ( $dbh->err ) {
 | |
|         warn $dbh->errstr;
 | |
|     }
 | |
|     $rv = 0 if ( $rv eq '0E0' );
 | |
| 
 | |
|     print "Added $rv dates to the 'date_added' column\n";
 | |
| 
 | |
| }
 | |
| 
 | |
| #===  FUNCTION  ================================================================
 | |
| #         NAME: alter_seq
 | |
| #      PURPOSE: Ensure the PostgreSQL sequence associated with a table has the
 | |
| #               correct value.
 | |
| #   PARAMETERS: $dbh            Handle for the PostgreSQL database
 | |
| #               $table          Table name for the query
 | |
| #               $sequence       Sequence name
 | |
| #      RETURNS: Nothing
 | |
| #  DESCRIPTION: 
 | |
| #       THROWS: No exceptions
 | |
| #     COMMENTS: None
 | |
| #     SEE ALSO: N/A
 | |
| #===============================================================================
 | |
| sub alter_seq {
 | |
|     my ( $dbh, $table, $sequence ) = @_;
 | |
| 
 | |
|     my ( $sth, $h, $rv, $maxid );
 | |
| 
 | |
|     #
 | |
|     # Find the maximum id number in the table
 | |
|     #
 | |
|     $sth = $dbh->prepare("SELECT max(id) as maxid FROM $table")
 | |
|         or die $DBI::errstr;
 | |
|     if ( $dbh->err ) {
 | |
|         warn $dbh->errstr;
 | |
|     }
 | |
| 
 | |
|     $sth->execute;
 | |
|     if ( $dbh->err ) {
 | |
|         die $dbh->errstr;
 | |
|     }
 | |
| 
 | |
|     #
 | |
|     # Save the maximum
 | |
|     #
 | |
|     if ( $h = $sth->fetchrow_hashref ) {
 | |
|         $maxid = $h->{maxid};
 | |
|         $sth->finish;
 | |
|     }
 | |
| 
 | |
|     #
 | |
|     # Reset the sequence one more than the maximum
 | |
|     #
 | |
|     $maxid++;
 | |
|     $rv = $dbh->do("ALTER SEQUENCE $sequence RESTART WITH $maxid");
 | |
|     if ( $dbh->err ) {
 | |
|         warn $dbh->errstr;
 | |
|     }
 | |
|     $rv = 0 if ( $rv eq '0E0' );
 | |
| 
 | |
|     warn "Failed to reset $sequence\n" unless (defined($rv));
 | |
| 
 | |
|     return;
 | |
| }
 | |
| 
 | |
| #===  FUNCTION  ================================================================
 | |
| #         NAME: check_table
 | |
| #      PURPOSE: Check that a given PostgreSQL table is empty
 | |
| #   PARAMETERS: $dbh            Handle for the PostgreSQL database
 | |
| #               $table          Name of table
 | |
| #      RETURNS: True if empty, otherwise false
 | |
| #  DESCRIPTION: Simply perform a query on the nominated table which counts
 | |
| #               rows. If the table does not exist a DBI method will fail (the
 | |
| #               execute?), so we treat this as a 'no empty' to make the caller
 | |
| #               take error action.
 | |
| #       THROWS: No exceptions
 | |
| #     COMMENTS: None
 | |
| #     SEE ALSO: N/A
 | |
| #===============================================================================
 | |
| sub check_table {
 | |
|     my ( $dbh, $table ) = @_;
 | |
| 
 | |
|     my ( $sth, $h, $count );
 | |
| 
 | |
|     $sth = $dbh->prepare("SELECT count(*) AS count FROM $table")
 | |
|         or die $DBI::errstr;
 | |
|     if ( $dbh->err ) {
 | |
|         warn $dbh->errstr;
 | |
|         return 0;
 | |
|     }
 | |
| 
 | |
|     $sth->execute;
 | |
|     if ( $dbh->err ) {
 | |
|         warn $dbh->errstr;
 | |
|         return 0;
 | |
|     }
 | |
| 
 | |
|     if ( $h = $sth->fetchrow_hashref ) {
 | |
|         $count = $h->{count};
 | |
|         $sth->finish;
 | |
|     }
 | |
| 
 | |
|     return $count == 0;
 | |
| }
 | |
| 
 | |
| #===  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: 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: 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", "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
 | |
| 
 | |
| copy_mysql_pg - copy the HPR database from MySQL to PostgreSQL
 | |
| 
 | |
| =head1 VERSION
 | |
| 
 | |
| This documentation refers to B<copy_mysql_pg> version 0.0.4
 | |
| 
 | |
| 
 | |
| =head1 USAGE
 | |
| 
 | |
|  copy_mysql_pg -verbose
 | |
|  copy_mysql_pg -verbose -verbose
 | |
|  copy_mysql_pg -verbose \
 | |
|     -phase='episodes,hosts,eh_xref,series,es_xref,tags,comments,epilogue'
 | |
| 
 | |
| =head1 OPTIONS
 | |
| 
 | |
| =over 8
 | |
| 
 | |
| =item B<-help>
 | |
| 
 | |
| Prints a brief help message describing the usage of the program, and then exits.
 | |
| 
 | |
| =item B<-debug=N>
 | |
| 
 | |
| Selects a level of debugging. Debug information consists of a line or series
 | |
| of lines prefixed with the characters 'D>':
 | |
| 
 | |
| =over 4
 | |
| 
 | |
| =item B<0>
 | |
| 
 | |
| No debug output is generated: this is the default
 | |
| 
 | |
| =back
 | |
| 
 | |
| =item B<-[no]dry-run>
 | |
| 
 | |
| When enabled (B<-dry-run>) the script will report what it would do, but will
 | |
| make no changes to the target database. In the default state (B<-nodry-run>)
 | |
| then changes are made.
 | |
| 
 | |
| =item B<-verbose>
 | |
| 
 | |
| Makes the script verbose resulting in the production of more information about
 | |
| what it is doing.
 | |
| 
 | |
| The option may be repeated to increase the level of verbosity. The levels are:
 | |
| 
 | |
| =over 4
 | |
| 
 | |
| =item B<0>
 | |
| 
 | |
| No output is generated (apart from errors and warnings if appropriate). This
 | |
| is the default level.
 | |
| 
 | |
| =item B<1>
 | |
| 
 | |
| A message is generated per phase to indicate which actions are taking place.
 | |
| This includes a report of the number of rows copied from the MySQL database to
 | |
| the PostgreSQL one.
 | |
| 
 | |
| =item B<2>
 | |
| 
 | |
| Following the process of collecting the CSV tags from the MySQL 'episodes' table
 | |
| these are reported as a list per episode. This output will be long!
 | |
| 
 | |
| =back
 | |
| 
 | |
| =item B<-phase=CSV_LIST>
 | |
| 
 | |
| This option allows the phases of the copying process to be selected
 | |
| individually. The argument B<CSV_LIST> is a list of phase names, which have to
 | |
| be typed exactly. The order is not important since the script will scan its
 | |
| version of the list of phases in order and will check to see if each has been
 | |
| selected.
 | |
| 
 | |
| The phase names are:
 | |
| 
 | |
| =over 4
 | |
| 
 | |
| =item B<episodes>
 | |
| 
 | |
| Causes the B<episodes> table to be filled.
 | |
| 
 | |
| =item B<hosts>
 | |
| 
 | |
| Causes the B<hosts> table to be filled.
 | |
| 
 | |
| =item B<eh_xref>
 | |
| 
 | |
| Causes the B<episodes_hosts_xref> table to be filled.
 | |
| 
 | |
| =item B<series>
 | |
| 
 | |
| Causes the B<series> table to be filled.
 | |
| 
 | |
| =item B<es_xref>
 | |
| 
 | |
| Causes the B<episodes_series_xref> table to be filled.
 | |
| 
 | |
| =item B<tags>
 | |
| 
 | |
| Causes the B<tags> and the B<episodes_tags_xref> tables to be filled.
 | |
| 
 | |
| =item B<comments>
 | |
| 
 | |
| Causes the B<comments> table to be filled.
 | |
| 
 | |
| =item B<epilogue>
 | |
| 
 | |
| Runs various tasks that can only be carried out after the database has been
 | |
| populated.
 | |
| 
 | |
| =back
 | |
| 
 | |
| =back
 | |
| 
 | |
| =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
 | |
| 
 |