#!/usr/bin/perl
use strict;


use Data::Dumper;
use Getopt::Std;
use POSIX qw(ceil);
use POSIX ":sys_wait_h";
use Sys::Hostname;
use Carp;


use lib '/app/tools/common/lib';
use Common::DB::Item;
use Common::Assert;
use Common::TextProgressBar;
use Common::Timer;
use Common::RSDB;
use Common::Util;
use Common::DB::Item::Service;


use lib '/app/tools/rps/lib';
use RPS::DB::Item::MechanicalCarryover;
use RPS::DB::Item::TrackLicense;
use RPS::DB::Item::Track;
use RPS::DB::Item::TrackContract;
use RPS::DB::Item::Master;
use RPS::DB::Item::Album;
#use RPS::DB::Item::License;
use RPS::DB::Item::LicenseReserve;
use RPS::DB::Item::LicenseReserveLiquidation;
use RPS::DB::Item::StatRate;
use RPS::DB::Item::ProductTrack;
use RPS::DB::Item::Song;

use RPS::DB::Item::Publisher;
use RPS::DB::Item::Product;
use RPS::DB::Item::ControlledComposition;

use RPS::DB::Item::AlbumContract;
use RPS::DB::Item::MechanicalRun;
use RPS::DB::Item::MechanicalStatement;
use RPS::DB::Item::MechanicalStatementItem;
use RPS::DB::Item::MechanicalStatementItem;
use RPS::DB::Item::MechanicalStatementTrack;
use RPS::DB::Item::MechanicalStatementLicense;
use RPS::DB::Item::MechanicalStatementAdjustmentItem;
use RPS::DB::Item::SaleMechanicalStatementItemMap;

use RPS::License::TrackLicense;
use RPS::License::TrackLicenseList;
use RPS::DB::Item::NewArtistContract;
use RPS::DB::Item::ReserveLiquidation;

use constant kRunFile => '/tmp/integration_alter_run_id';

# I want this script to do it all, which means:
# - Copies over tables from raptor01
# - Sets up the RAPTOR_* version of the database
# - Does the required alters.
#
# - Not going to be able to use the DB::Item stuff, since that latches on to a particular
# - database, and we need to be able to move stuff between dbs...
#

my $gVerbosityLevel = 1;
my $gDropOldColumns = 0;

# Parse the command-line options.
#
my %options;
_parseCommandLine(\%options);


my @clientIDs;
if ($options{clientID})
{
    push @clientIDs, $options{clientID};
}
else
{
    @clientIDs = _getAllAvailableClientIDs();
}



my $runID;
my $recoverFlag;
# If there is a run file hanging around, get the run from that.
#
if (-e kRunFile)
{
    open RUNFILE, kRunFile;
    my $line = <RUNFILE>;
    close RUNFILE;
    chomp $line;
    $runID = $line;
    $recoverFlag = 1;
}

if (! $runID)
{
    $runID = time;
} 

open RUNFILE, "> " . kRunFile;
print RUNFILE "$runID\n";
_report("token: $runID", 4);
close RUNFILE;

# !!! I want to make this a little more efficient (and have to enter my password less).
# So I'm going to have it batch up the extraction
#
_transferDBs($runID, $recoverFlag, @clientIDs);


# Convert RSCOMMON first
#
#my $userIDMap = _convertRSCommon();

foreach my $clientID (@clientIDs)
{
    _alterAlbum($clientID);
    _alterArtist($clientID);
    _alterProduct($clientID);
    _alterTrack($clientID);
    _alterTrackLicense($clientID);
    _alterArtistContract($clientID);
    _alterPayor($clientID);
    _alterMechanicalStatement($clientID);
    _alterArtistRoyaltyStatement($clientID);
    _createNewTables($clientID);
    _copyMissingTables($clientID);
    _mergeFile($clientID);
    _mergeSale($clientID);
    
    # !!! disabling the user id update for the moment.
    #
#    _updateUserIDs($clientID, $userIDMap);

    _createAggTables($clientID);

    _cleanup($clientID);
}

unlink kRunFile;

# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=

sub _mergeFile
{
    my ($clientID) = @_;

    my $dbName = Common::RSDB::ClientIDToDBName($clientID);

    _report("copying over file table");
    if (0 == system("mysqldump -uroot $dbName"."_RAPTOR file > /tmp/db_transfer"))
    {
        system("mysql -uroot $dbName < /tmp/db_transfer");
    }
    else
    {
        die "Error! unable to mysqldump file table";
    }
}

sub _mergeSale
{
    my ($clientID) = @_;

    my $dbName = Common::RSDB::ClientIDToDBName($clientID);

    _report("preserving sale status codes");
    if (0 != system("/usr/bin/perl /app/tools/rps/bin/royalty/preserve_sale_status -c $clientID > /tmp/sale_status"))
    {
        die "Error preserving sale status";
    }

    _report("copying over sale table");
    if (0 != system("mysqldump -uroot $dbName"."_RAPTOR sale > /tmp/db_transfer"))
    {
        die "Error! unable to mysqldump sale table";
    }

    if (0 != system("mysql -uroot $dbName < /tmp/db_transfer"))
    {
        die "Error! unable to copy sale table";
    }
    
    # alter sale table
    #
    _report("altering sale table");
    my $appSingleton = Common::RSApp->new(clientID => $clientID);

    # Add the status columns
    #
    my $dbo = Common::RSApp::GetClientDB();
    my $sql = "ALTER TABLE sale ADD COLUMN mechanical_royalty_status tinyint(3) unsigned not null default 0";
    if (!$dbo->DoCmd($sql))
    {
        _report("error " . $dbo->LastErrorStr());
        return;
    }
    $sql = "ALTER TABLE sale ADD COLUMN artist_royalty_status tinyint(3) unsigned not null default 0";
    if (!$dbo->DoCmd($sql))
    {
        _report("error " . $dbo->LastErrorStr());
        return;
    }

    # restore status codes
    #
    if (0 != system("/usr/bin/perl /app/tools/rps/bin/royalty/restore_sale_status -c $clientID < /tmp/sale_status"))
    {
        die "Error restoring sale status";
    }
}


sub _readClientTrackIDFromRaptor
{
    my ($clientID) = @_;
    assert($clientID);

    my %map;

    # Can't use a fancy db abstraction layer safely, so we'll query this the old fashioned way
    #
    my $dbName = Common::RSDB::ClientIDToDBName($clientID);
    $dbName .= '_RAPTOR';
 	my $dbo = new Common::RSDB(client_id => 999, db_name => $dbName);
    my $sql = "select track_id, client_track_id, genre_name, track_name_clean from track";
    my $sth = $dbo->DoCmd($sql);
    while (my $hr = $sth->fetchrow_hashref())
    {
        $map{$hr->{track_id}} = 
        {
            clientTrackID => $hr->{client_track_id},
            genreName => $hr->{genre_name},
            trackNameClean => $hr->{track_name_clean},
        };
    }

    return \%map;
}

sub _createNewTables
{
    my ($clientID) = @_;
    assert($clientID);

    my $appSingleton = Common::RSApp->new(clientID => $clientID);

    # Create the 'payor' table
    #
#my $sql = "
#CREATE TABLE `payor` (
#  `payor_id` int(10) unsigned NOT NULL auto_increment,
#  `name` varchar(255) NOT NULL default '',
#  `street_address` text NOT NULL,
#  `city` varchar(80) NOT NULL default '',
#  `state_province` varchar(80) NOT NULL default '',
#  `country_code` char(2) NOT NULL default 'US',
#  `postal_code` varchar(15) NOT NULL default '',
#  `email` varchar(80) NOT NULL default '',
#  `phone_number` varchar(30) NOT NULL default '',
#  `fax_number` varchar(30) NOT NULL default '',
#  `cycle_code` int(3) unsigned NOT NULL default '0',
#  `begin_month_code` int(3) unsigned NOT NULL default '0',
#  `comments` text NOT NULL,
#  `tax_id` varchar(30) NOT NULL default '',
#  `client_payor_id` varchar(255) NOT NULL default '',
#  `inactive` tinyint(1) unsigned NOT NULL default '0',
#  `is_default` tinyint(1) unsigned NOT NULL default '0',
#  `date_created` timestamp NOT NULL default '0000-00-00 00:00:00',
#  `created_by` int(10) unsigned NOT NULL default '0',
#  `date_modified` timestamp NOT NULL default CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP,
#  `modified_by` int(10) unsigned NOT NULL default '0',
#  PRIMARY KEY  (`payor_id`),
#  KEY `name` (`name`)
#) ENGINE=MyISAM DEFAULT CHARSET=latin1;
#";
#
#    my $dbo = Common::RSApp::GetClientDB();
#    if (!$dbo->DoCmd($sql))
#    {
#        _report("error " . $dbo->LastErrorStr());
#        return;
#    }
    
my $sql = "
CREATE TABLE `command_log` (
  `command_log_id` int(10) unsigned NOT NULL auto_increment,
  `command_name` varchar(64) NOT NULL default '',
  `command_id` int(10) unsigned NOT NULL,
  `command` text,
  `date_created` timestamp NOT NULL default '0000-00-00 00:00:00',
  `created_by` int(10) unsigned NOT NULL default '0',
  PRIMARY KEY (`command_log_id`),
  KEY `command_name_id` (`command_name`, `command_id`),
  KEY `created_by` (`created_by`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1;
";
    my $dbo = Common::RSApp::GetClientDB();
    if (!$dbo->DoCmd($sql))
    {
        _report("error " . $dbo->LastErrorStr());
        return;
    }
}

sub _alterMechanicalStatement
{
    my ($clientID) = @_;
    assert($clientID);

    my $appSingleton = Common::RSApp->new(clientID => $clientID);

    # Add the payor_id column
    #
    my $dbo = Common::RSApp::GetClientDB();
    my $sql = "ALTER TABLE mechanical_statement ADD COLUMN payor_id int(10) unsigned not null default 0";
    if (!$dbo->DoCmd($sql))
    {
        _report("error " . $dbo->LastErrorStr());
        return;
    }
}

sub _alterArtistRoyaltyStatement
{
    my ($clientID) = @_;
    assert($clientID);

    my $appSingleton = Common::RSApp->new(clientID => $clientID);

    # Add the payor_id column
    #
    my $dbo = Common::RSApp::GetClientDB();
    my $sql = "ALTER TABLE artist_royalty_statement ADD COLUMN payor_id int(10) unsigned not null default 0";
    if (!$dbo->DoCmd($sql))
    {
        _report("error " . $dbo->LastErrorStr());
        return;
    }
}


sub _alterPayor
{
    my ($clientID) = @_;
    assert($clientID);

    my $appSingleton = Common::RSApp->new(clientID => $clientID);

    # Add the payor_id column
    #
    my $dbo = Common::RSApp::GetClientDB();
    my $sql = "ALTER TABLE payor ADD COLUMN inactive tinyint(3) unsigned not null default 0";
    if (!$dbo->DoCmd($sql))
    {
        _report("error " . $dbo->LastErrorStr());
        return;
    }
    $sql = "ALTER TABLE payor ADD COLUMN client_payor_id varchar(255)";
    if (!$dbo->DoCmd($sql))
    {
        _report("error " . $dbo->LastErrorStr());
        return;
    }
    $sql = "ALTER TABLE payor ADD COLUMN is_default tinyint(3) unsigned not null default 0";
    if (!$dbo->DoCmd($sql))
    {
        _report("error " . $dbo->LastErrorStr());
        return;
    }
}


sub _alterArtistContract
{
    my ($clientID) = @_;
    assert($clientID);

    my $appSingleton = Common::RSApp->new(clientID => $clientID);

    # Add the payor_id column
    #
    my $dbo = Common::RSApp::GetClientDB();
    my $sql = "ALTER TABLE new_artist_contract ADD COLUMN payor_id int(10) unsigned NOT NULL default 0";
    if (!$dbo->DoCmd($sql))
    {
        _report("error " . $dbo->LastErrorStr());
        return;
    }
    # Make it a key
    #
    $sql = 'ALTER TABLE new_artist_contract ADD KEY `payor_id` (`payor_id`)';
    if (!$dbo->DoCmd($sql))
    {
        _report("error " . $dbo->LastErrorStr());
        return;
    }

}

sub _alterTrackLicense
{
    my ($clientID) = @_;
    assert($clientID);

    my $appSingleton = Common::RSApp->new(clientID => $clientID);

    # Add the payor_id column
    #
    my $dbo = Common::RSApp::GetClientDB();
    my $sql = "ALTER TABLE track_license ADD COLUMN payor_id int(10) unsigned NOT NULL default 0";
    if (!$dbo->DoCmd($sql))
    {
        _report("error " . $dbo->LastErrorStr());
        return;
    }
    # Make it a key
    #
    $sql = 'ALTER TABLE track_license ADD KEY `payor_id` (`payor_id`)';
    if (!$dbo->DoCmd($sql))
    {
        _report("error " . $dbo->LastErrorStr());
        return;
    }

    # Add the percentage_of_sales column
    #
    $sql = "ALTER TABLE track_license ADD COLUMN percentage_of_sales decimal(8,4) unsigned default 100 after rate_percentage";
    if (!$dbo->DoCmd($sql))
    {
        _report("error " . $dbo->LastErrorStr());
        return;
    }

}


sub _alterTrack
{
    my ($clientID) = @_;
    assert($clientID);

    my $clientTrackIDMap = _readClientTrackIDFromRaptor($clientID);

    my $appSingleton = Common::RSApp->new(clientID => $clientID);

    # Add the client_track_id column
    #
    my $dbo = Common::RSApp::GetClientDB();
    my $sql = "ALTER TABLE track ADD COLUMN client_track_id varchar(50)";
    if (!$dbo->DoCmd($sql))
    {
        _report("error " . $dbo->LastErrorStr());
        return;
    }
    # Make client_track_id a key
    #
    $sql = 'ALTER TABLE track ADD KEY `client_track_id` (`client_track_id`)';
    if (!$dbo->DoCmd($sql))
    {
        _report("error " . $dbo->LastErrorStr());
        return;
    }


    # Add genre_name column
    #
    $sql = 'ALTER TABLE track ADD COLUMN genre_name varchar(32)';
    if (!$dbo->DoCmd($sql))
    {
        _report("error " . $dbo->LastErrorStr());
        return;
    }

    $sql = 'ALTER TABLE track ADD COLUMN title_clean varchar(255)';
    if (!$dbo->DoCmd($sql))
    {
        _report("error " . $dbo->LastErrorStr());
        return;
    }
    $sql = 'ALTER TABLE track ADD COLUMN title_alt varchar(255)';
    if (!$dbo->DoCmd($sql))
    {
        _report("error " . $dbo->LastErrorStr());
        return;
    }
    $sql = 'ALTER TABLE track ADD COLUMN title_alt_clean varchar(255)';
    if (!$dbo->DoCmd($sql))
    {
        _report("error " . $dbo->LastErrorStr());
        return;
    }


    # Add the custom columns
    #
    $sql = "ALTER TABLE track ADD COLUMN custom_1 varchar(255)";
    if (!$dbo->DoCmd($sql))
    {
        _report("error " . $dbo->LastErrorStr());
        return;
    }
    $sql = "ALTER TABLE track ADD COLUMN custom_2 varchar(255)";
    if (!$dbo->DoCmd($sql))
    {
        _report("error " . $dbo->LastErrorStr());
        return;
    }
    $sql = "ALTER TABLE track ADD COLUMN custom_3 varchar(255)";
    if (!$dbo->DoCmd($sql))
    {
        _report("error " . $dbo->LastErrorStr());
        return;
    }


    # Fill in client_track_id and genre_name
    #
    while (my ($trackID, $raptorInfoHash) = each(%$clientTrackIDMap))
    {
        my $clientTrackID = $raptorInfoHash->{clientTrackID};
        my $genreName = $raptorInfoHash->{genreName};
        my $cleanName = $raptorInfoHash->{trackNameClean};

        $sql = "UPDATE track SET client_track_id = " . $dbo->DBQuote($clientTrackID) 
             . ", genre_name = " . $dbo->DBQuote($genreName) 
             . ", title_clean = " . $dbo->DBQuote($cleanName) 
             . " WHERE track_id = " . $trackID;
        if (!$dbo->DoCmd($sql))
        {
            _report("error " . $dbo->LastErrorStr());
            return;
        }
    }


    # Change 'track_number' to 'track_order'
    #
    $sql = 'ALTER TABLE track CHANGE COLUMN track_number track_order tinyint(3) unsigned DEFAULT NULL';
    if (!$dbo->DoCmd($sql))
    {
        _report("error " . $dbo->LastErrorStr());
        return;
    }

    # Create some additional indicies
    #
    $sql = "create index artist_id on track (artist_id)";
    if (!$dbo->DoCmd($sql))
    {
        _report("error " . $dbo->LastErrorStr());
#        return;
    }
    $sql = "create index title_clean on track (title_clean)";
    if (!$dbo->DoCmd($sql))
    {
        _report("error " . $dbo->LastErrorStr());
#        return;
    }
    $sql = "create fulltext index track_title_text on track (title)";
    if (!$dbo->DoCmd($sql))
    {
        _report("error " . $dbo->LastErrorStr());
#        return;
    }

}



sub _createAggTables
{
    my ($clientID) = @_;
    _report("building agg tables");

    my $dbName = Common::RSDB::ClientIDToDBName($clientID);

    # I think I may want to truncate them first?
    #
    foreach my $tableName(
     'agg_file_summary','agg_m_artist_sales','agg_m_sales','agg_m_summary','agg_q_artist_sales','agg_q_product_sales',
     'agg_q_sales','agg_q_summary','agg_q_top_products','agg_sale_log','agg_y_artist_sales','agg_y_product_sales',
     'agg_y_sales','agg_y_summary', 'agg_sale_stage', 'agg_m_product_sales'
     )
    {
        system("mysql -uroot $dbName -e 'truncate table $tableName'");
    }
    system("/app/tools/stats/bin/build_sale_aggregates.pl -c $clientID");
}

sub _copyMissingTables
{
    my ($clientID) = @_;

    my $dbName = Common::RSDB::ClientIDToDBName($clientID);

    foreach my $tableName('format','meta_update','pattern_level','period','product_input_map','product_pattern_match',
     'agg_file_summary','agg_m_artist_sales','agg_m_sales','agg_m_summary','agg_q_artist_sales','agg_q_product_sales',
     'agg_q_sales','agg_q_summary','agg_q_top_products','agg_sale_log','agg_y_artist_sales','agg_y_product_sales',
     'agg_y_sales','agg_y_summary', 'agg_sale_stage', 'agg_m_product_sales')
    {
        _report("copying over $tableName");
        if (0 == system("mysqldump -uroot $dbName"."_RAPTOR $tableName > /tmp/db_transfer"))
        {
            system("mysql -uroot $dbName < /tmp/db_transfer");
        }
        else
        {
            # Assume error because table does not exist - create it.
            # - I just have the schema for meta_update for now...
            #
            if ('meta_update' eq $tableName)
            {
                open TEMPSQL, "/tmp/tmp.sql";
                print TEMPSQL "CREATE TABLE `meta_update` ("
                . "`action` char(1) NOT NULL default '',"
                . "`product_type` char(1) NOT NULL default '',"
                . "`owner_id` int(10) unsigned NOT NULL default '0',"
                . "`changed` varchar(20) default NULL,"
                . "`orig_value` varchar(255) default NULL,"
                . "`new_value` varchar(255) default NULL,"
                . "`line_num` int(10) unsigned default NULL"
                . ") ENGINE=MyISAM DEFAULT CHARSET=latin1;\n";
                close TEMPSQL;
                system("mysql -uroot $dbName < /tmp/tmp.sql");
            }
        }
    }
}

sub _transferDBs
{
    my ($runID, $recoverFlag, @clientIDs) = @_;

    my $transDir = "DBTRANSFER_$runID";
    if (! $recoverFlag)
    {
        my @commands;
        push @commands, "cd /tmp";
        push @commands, "mkdir $transDir";
#        push @commands, "mysqldump -uroot RSCOMMON > $transDir/RSCOMMON";
    
        foreach my $clientID (@clientIDs)
        {
            my $dbName = Common::RSDB::ClientIDToDBName($clientID);
            push @commands, "mysqldump -uroot $dbName > $transDir/$dbName";
        }

        push @commands, "tar czvf $transDir.tz $transDir";

        my $bigOleCommand = join(";", @commands);
        _report("\n+++ Compressing remote databases +++\n");
        system("ssh raptor01 '$bigOleCommand'");
    }

    _report("\n+++ Transfering databases +++\n");
    my $transferStatus = system("rsync --timeout=60 -P -e ssh raptor01:/tmp/$transDir.tz /tmp/$transDir.tz");
    _report("transfer status: $transferStatus", 2);
    die "ERROR - transfer failed" if (0 != $transferStatus);


    _report("\n+++ Uncompressing databases +++\n");
    system("cd /tmp;tar xzvf $transDir.tz");

    _report("\n+++ Creating local copies of the raptor databases+++\n");
    if (0)
    {
    my $dbName = "RSCOMMON";
    my $newDBName = "RSCOMMON_RAPTOR";
    system("mysql -uroot -e 'create database $newDBName'");
    system("mysql -uroot $newDBName < /tmp/$transDir/$dbName");
    }

    foreach my $clientID (@clientIDs)
    {
        my $dbName = Common::RSDB::ClientIDToDBName($clientID);
        my $newDBName = "$dbName" . "_RAPTOR";
        system("mysql -uroot -e 'create database $newDBName'");
        system("mysql -uroot $newDBName < /tmp/$transDir/$dbName");
    }
}

sub _cleanup
{
    my ($clientID) = @_;

    my $dbName = Common::RSDB::ClientIDToDBName($clientID);

    $dbName .= '_RAPTOR';
    `mysql -uroot -e 'drop database $dbName'`;
}


sub _updateUserIDs
{
    my ($clientID, $userIDMap) = @_;

    my $dbName = Common::RSDB::ClientIDToDBName($clientID);

 	my $dbo = new Common::RSDB(client_id => $clientID);

    my @tables = ('new_artist_contract','album','expense','artist_payee','finance_account','master',
     'mechanical_run','mechanical_statement','product','publisher','song','track','track_license',
     'artist_royalty_run','payor');
    
    foreach my $tableName (@tables)
    {
        my $keyName = $tableName . "_id";
        if ('new_artist_contract' eq $tableName)
        {
            $keyName = 'artist_contract_id';
        }

        # Not all tables have both created_by and modified_by.
        # So I'll select everything so I can be smart about what I try and change.
        #
        my $sql = "SELECT * from $tableName";
        my $sth = $dbo->DoCmd($sql);
        while (my $hr = $sth->fetchrow_hashref())
        {
            my $keyValue = $hr->{$keyName};

            # not going for speed here, obviously...
            #
            if ($hr->{created_by} && $userIDMap->{$hr->{created_by}})
            {
                my $newUserID = $userIDMap->{$hr->{created_by}};
                $sql = "UPDATE $tableName SET created_by=$newUserID WHERE $keyName=$keyValue";
                $dbo->DoCmd($sql);
            }
            if ($hr->{modified_by} && $userIDMap->{$hr->{modified_by}})
            {
                my $newUserID = $userIDMap->{$hr->{modified_by}};
                $sql = "UPDATE $tableName SET modified_by=$newUserID WHERE $keyName=$keyValue";
                $dbo->DoCmd($sql);
            }
        }
    }
}


sub _convertRSCommon
{
    _report("converting RSCOMMON");

#    _transferRaptorRSCOMMON();


    # need to merge the user table first, and build the raptor->rps id mapping table.
    # Then have to _merge_ the phone, address, user_access and user_logins tables

    my ($userIDMap, $newUsers) = _mergeUser();

    _mergeAddress($userIDMap, $newUsers);

    _alterAddress();

    _alterUser($userIDMap, $newUsers);

    _alterClient();

    _renameRSCOMMON_RAPTOR();

    return $userIDMap;
}


sub _renameRSCOMMON_RAPTOR
{
    `mysqldump -uroot RSCOMMON_RAPTOR > /tmp/RSCOMMON_RAPTOR`;
    `mysql -uroot -e 'drop database RSCOMMON_RAPTOR'`;
    `mysql -uroot RSCOMMON < /tmp/RSCOMMON_RAPTOR`;
}


sub _alterUser
{
    my ($userIDMap, $newUsers) = @_;

 	my $dbo_raptor = new Common::RSDB(client_id => 999, db_name => 'RSCOMMON_RAPTOR');
 	my $dbo_rps = new Common::RSDB(client_id => 999, db_name => 'RSCOMMON');


    # Make a new user id map, which we'll use later when iterating through the table.
    #
    my %newUserMap;
    foreach my $newUserData (@$newUsers)
    {
        my $rpsUserID = $newUserData->{user_id};
        my $newID = $userIDMap->{$rpsUserID};
        $newUserMap{$newID} = 1;
    }


    # Ditch the phone_id column, replace with phone1,phone2,phone3
    #
	my $sql = "ALTER TABLE user ADD COLUMN phone1 varchar(32) default NULL";
    if (!$dbo_raptor->DoCmd($sql))
    {
        _report("error " . $dbo_raptor->LastErrorStr());
        return;
    }

	$sql = "ALTER TABLE user ADD COLUMN phone2 varchar(32) default NULL";
    if (!$dbo_raptor->DoCmd($sql))
    {
        _report("error " . $dbo_raptor->LastErrorStr());
        return;
    }

	$sql = "ALTER TABLE user ADD COLUMN phone3 varchar(32) default NULL";
    if (!$dbo_raptor->DoCmd($sql))
    {
        _report("error " . $dbo_raptor->LastErrorStr());
        return;
    }


    # Now iterate through the user table and populate phone1.
    # (I happen to know there these is no more than 1 phone per user... so I can cheat a bit)
    #
    my $sth = $dbo_raptor->DoCmd("SELECT * from user");
    while (my $hr = $sth->fetchrow_hashref())
    {
        if ($hr->{phone_id})
        {
            if ($newUserMap{$hr->{user_id}})
            {
                # Get the phone record from RPS
                #
                my $pSTH = $dbo_rps->DoCmd("SELECT phone_number from phone where phone_id=".$hr->{phone_id});
                my $pHR = $pSTH->fetchrow_hashref();
                if ($pHR)
                {
                    my $sql = "UPDATE user SET phone1=".$dbo_raptor->DBQuote($pHR->{phone_number})
                     . " WHERE user_id=".$hr->{user_id};
                    $dbo_raptor->DoCmd($sql);
                }
            }
            else
            {
                my $pSTH = $dbo_raptor->DoCmd("SELECT phone_number from phone where phone_id=".$hr->{phone_id});
                my $pHR = $pSTH->fetchrow_hashref();
                if ($pHR)
                {
                    my $sql = "UPDATE user SET phone1=".$dbo_raptor->DBQuote($pHR->{phone_number})
                     . " WHERE user_id=".$hr->{user_id};
                    $dbo_raptor->DoCmd($sql);
                }
            }
        }
    }
}


sub _mergeAddress
{
    my ($userIDMap, $newUsers) = @_;

 	my $dbo_raptor = new Common::RSDB(client_id => 999, db_name => 'RSCOMMON_RAPTOR');
 	my $dbo_rps = new Common::RSDB(client_id => 999, db_name => 'RSCOMMON');

    
    # Here's the trick - unlike user, we don't have a unique field we can use to determine whether
    # this address is 'new' or not.
    #
    # I don't really _care_ about users that have _moved_. I only care about _new_ users... I will need
    # to bring their addresses over.  The rest, honestly, are irrelevant.
    #
    # Going to also need to update the address_id field for these new users to reflect the new address ids.
    #

    # Bring over addresses from RPS that belong to new users (re-mapping the user_id field along the way).
    #
    foreach my $rpsUser (@$newUsers)
    {
        my $rpsUserID = $rpsUser->{user_id};
        my $newID = $userIDMap->{$rpsUserID};

        my $sth = $dbo_rps->DoCmd("SELECT * from address where user_id=$rpsUserID");
        my $newPrimaryID;
        while (my $hr = $sth->fetchrow_hashref())
        {
            my $oldAddressID = $hr->{address_id};
            my $sql = "INSERT INTO address SET";
            $sql .= " user_id=$newID";
            $sql .= ",address_type_id=".$hr->{address_type_id};
            $sql .= ",address1=".$dbo_raptor->DBQuote($hr->{address1}) if ($hr->{address1});
            $sql .= ",address2=".$dbo_raptor->DBQuote($hr->{address2}) if ($hr->{address2});
            $sql .= ",city=".$dbo_raptor->DBQuote($hr->{city}) if ($hr->{city});
            $sql .= ",state=".$dbo_raptor->DBQuote($hr->{state}) if ($hr->{state});
            $sql .= ",province=".$dbo_raptor->DBQuote($hr->{province}) if ($hr->{province});
            $sql .= ",postal_code=".$dbo_raptor->DBQuote($hr->{postal_code}) if ($hr->{postal_code});
            $sql .= ",country=".$dbo_raptor->DBQuote($hr->{country}) if ($hr->{country});
            $sql .= ",date_created=".$dbo_raptor->DBQuote($hr->{date_created}) if ($hr->{date_created});
            $sql .= ",date_modified=".$dbo_raptor->DBQuote($hr->{date_modified}) if ($hr->{date_modified});

            $dbo_raptor->DoCmd($sql);
            my $newAddressID = $dbo_raptor->LastInsertID();

            # Was this address the user's primary?
            #
            if ($rpsUser->{address_id} == $oldAddressID)
            {
                $newPrimaryID = $newAddressID;
            }
        }

        if ($newPrimaryID)
        {
            $dbo_rps->DoCmd("UPDATE user set address_id=$newPrimaryID where user_id=$newID");
        }
    }
}


sub _transferRaptorRSCOMMON
{

    my $dbName = 'RSCOMMON';
    _report("dbName : $dbName", 4);
    

    # So, first we'll need to tell the remote host to dump the raptor database.
    #
    _report("-- extracting database $dbName on raptor01");
    my $cmd = "rm -f /tmp/$dbName*;mysqldump -uroot $dbName > /tmp/$dbName;gzip /tmp/$dbName";
    `ssh raptor01 '$cmd'`;


    # Now we copy it over to here.
    #
    _report("-- copying database dump to local host");
    `rm -f /tmp/$dbName*`;
    `scp -o 'TCPKeepAlive yes' raptor01:/tmp/$dbName.gz /tmp/$dbName.gz`;


    # Expand it.
    #
    _report("-- uncompressing data");
    `gunzip /tmp/$dbName.gz`;


    # Create the new raptor db on the local host.
    #
    my $newDBName = $dbName . "_RAPTOR";
    _report("-- importing data into new database $newDBName");
    `mysql -uroot -e 'create database $newDBName'`;
    `mysql -uroot $newDBName < /tmp/$dbName`;
}


sub _mergeUser
{
 	my $dbo_raptor = new Common::RSDB(client_id => 999, db_name => 'RSCOMMON_RAPTOR');
 	my $dbo_rps = new Common::RSDB(client_id => 999, db_name => 'RSCOMMON');

    my %map;
    my @newUsers;

    # Iterate through the RPS version of user.
    # We're looking to find both new users, and
    # users that have different ids.
    #
    my $searchSQL = "SELECT * from user";
    my $searchSTH = $dbo_rps->DoCmd($searchSQL);
    while (my $hr = $searchSTH->fetchrow_hashref())
    {
        my $email = $hr->{email};
        my $raptorSTH = $dbo_raptor->DoCmd("SELECT user_id from user where email=" . $dbo_raptor->DBQuote($email));
        my $raptorHR = $raptorSTH->fetchrow_hashref();
        if (! $raptorHR || ! $raptorHR->{user_id})
        {
            #new user = insert at the end (later)
            #
            push @newUsers, $hr;
        }
        elsif ($raptorHR->{user_id} != $hr->{user_id})
        {
            # same user, different id
            #
            $map{$hr->{user_id}} = $raptorHR->{user_id};
            _report("user " . $hr->{user_id} . " exists in Raptor as id " . $raptorHR->{user_id}, 4);
        }
    }

    # Create entries for all the new users
    # Going to have to re-map primary_contact_ids for these that are being moved.
    # I believe I can limit this to just these that have been moved.
    #
    my @needToAdjustContactIDs;
    _report("have to create " . scalar @newUsers . " users", 4);
    foreach my $data (@newUsers)
    {
        # Raptor is reasonably full, so don't worry about trying to re-use the id.
        # Just take this new user onto the back of the line.
        #
        my $sql = "INSERT INTO user SET";
        $sql .= " email=" . $dbo_rps->DBQuote($data->{email});
        $sql .= ",password=" . $dbo_rps->DBQuote($data->{password}) if (defined $data->{password});
        $sql .= ",first_name=" . $dbo_rps->DBQuote($data->{first_name});
        $sql .= ",last_name=" . $dbo_rps->DBQuote($data->{last_name});
        $sql .= ",address_id=" . $data->{address_id} if (defined $data->{address_id});
        $sql .= ",phone_id=" . $data->{phone_id} if (defined $data->{phone_id});
        $sql .= ",primary_contact_id=" . $data->{primary_contact_id} if (defined $data->{primary_contact_id});
        $sql .= ",secondary_contact_id=" . $data->{secondary_contact_id} if (defined $data->{secondary_contact_id});
        $sql .= ",disabled=" . $data->{disabled};
        $sql .= ",date_created=" . $dbo_rps->DBQuote($data->{date_created}) if (defined $data->{date_created});
        $sql .= ",date_modified=" . $dbo_rps->DBQuote($data->{date_modified}) if (defined $data->{date_modified});

        $dbo_raptor->DoCmd($sql);
        my $newID = $dbo_raptor->LastInsertID();
        _report(" moved " . $data->{user_id} . " to $newID", 4);
        $map{$data->{user_id}} = $newID;
        push @needToAdjustContactIDs, $newID;
    }

    _report("map: - " . Dumper(\%map), 4);

    # Now adjust the contact ids for these that have moved.
    #
    foreach my $id (@needToAdjustContactIDs)
    {
        my $sql = "select primary_contact_id, secondary_contact_id from user where user_id=$id";
        my $sth = $dbo_raptor->DoCmd($sql);
        my $hr = $sth->fetchrow_hashref();
        my $newPrimary;
        my $newSecondary;
        if ($hr->{primary_contact_id} > 0)
        {
            # assuming if they have 1, they have both
            #
            $newPrimary = $map{$hr->{primary_contact_id}};
            $newPrimary = $hr->{primary_contact_id} unless $newPrimary;
            $newSecondary = $map{$hr->{secondary_contact_id}};
            $newSecondary = $hr->{secondary_contact_id} unless $newSecondary;
            $dbo_raptor->DoCmd("UPDATE user set primary_contact_id=$newPrimary, secondary_contact_id=$newSecondary where user_id=$id");
        }
    }

    return (\%map, \@newUsers);
}



sub _updateHRToDB
{
    my ($hr, $dbo, $tableName, $keyColumn, $keyValue) = @_;

    my (@keys, @values);

    foreach my $key (keys %$hr)
    {
        next if ($key eq $keyColumn);
        push @values, $hr->{$key};

        push @keys, "$key=?";
    }
    my $setClause = join(',', @keys);

    my $sql = "INSERT INTO $tableName SET $setClause WHERE $keyColumn = $keyValue";
    $dbo->DoCmdWithPlaceholders($sql, \@values);
}

sub _alterClient
{
    _report("altering address table");


    # Replacing the two seperate state and province columns with a single state_province table.
    #
 	my $dbo = new Common::RSDB(client_id => 999, db_name => 'RSCOMMON_RAPTOR');

	my $sql = "ALTER TABLE client ADD COLUMN royalty_period_mechanical char(1) default 'Q'";
    if (!$dbo->DoCmd($sql))
    {
        _report("error " . $dbo->LastErrorStr());
        return;
    }
	$sql = "ALTER TABLE client ADD COLUMN royalty_period_artist char(1) default 'Q'";
    if (!$dbo->DoCmd($sql))
    {
        _report("error " . $dbo->LastErrorStr());
        return;
    }
	$sql = "ALTER TABLE client ADD COLUMN hfa_manufacturer_code varchar(20) default NULL";
    if (!$dbo->DoCmd($sql))
    {
        _report("error " . $dbo->LastErrorStr());
        return;
    }
	$sql = "ALTER TABLE client ADD COLUMN use_net_for_mechanical_deductions tinyint(1) default 0";
    if (!$dbo->DoCmd($sql))
    {
        _report("error " . $dbo->LastErrorStr());
        return;
    }
	$sql = "ALTER TABLE client ADD COLUMN subtract_stat_rate_from_net_revenue tinyint(1) default 0";
    if (!$dbo->DoCmd($sql))
    {
        _report("error " . $dbo->LastErrorStr());
        return;
    }


    # Now populate these new columns
    #
    my $dboRPS = new Common::RSDB(client_id => 999, db_name => 'RSCOMMON');
    $sql = "SELECT * from client";
    my $sth = $dboRPS->DoCmd($sql);
    while (my $hr = $sth->fetchrow_hashref())
    {

        my @updates;
        push @updates, "royalty_period_mechanical=".$dbo->DBQuote($hr->{royalty_period_mechanical}) if defined $hr->{royalty_period_mechanical};
        push @updates, "royalty_period_artist=".$dbo->DBQuote($hr->{royalty_period_artist}) if defined $hr->{royalty_period_artist};
        push @updates, "hfa_manufacturer_code=".$dbo->DBQuote($hr->{hfa_manufacturer_code}) if defined $hr->{hfa_manufacturer_code};
        push @updates, "use_net_for_mechanical_deductions=".$hr->{use_net_for_mechanical_deductions} if defined $hr->{use_net_for_mechanical_deductions};
        push @updates, "subtract_stat_rate_from_net_revenue=".$hr->{subtract_stat_rate_from_net_revenue} if defined $hr->{subtract_stat_rate_from_net_revenue};

        if (scalar @updates)
        {
            $sql = "UPDATE client SET " . join(',',@updates) . " WHERE client_id=".$hr->{client_id};

            $dbo->DoCmd($sql);
        }
    }

}

sub _alterAddress
{
    _report("altering address table");


    # Replacing the two seperate state and province columns with a single state_province table.
    #
 	my $dbo = new Common::RSDB(client_id => 999, db_name => 'RSCOMMON_RAPTOR');

	my $sql = "ALTER TABLE address ADD COLUMN state_province varchar(80) default NULL";
    if (!$dbo->DoCmd($sql))
    {
        _report("error " . $dbo->LastErrorStr());
        return;
    }

	$sql = "ALTER TABLE address ADD COLUMN street_address varchar(80) default NULL";
    if (!$dbo->DoCmd($sql))
    {
        _report("error " . $dbo->LastErrorStr());
        return;
    }

    # populate the state_province 
    #
    $sql = "SELECT address_id,state,province from address";
    my $sth = $dbo->DoCmd($sql);
    while (my $hr = $sth->fetchrow_hashref())
    {
        my $sp = $hr->{state};
        if (! $sp)
        {
            $sp = $hr->{province};
        }

        if ($sp)
        {
            $dbo->DoCmd("UPDATE address set state_province =" . $dbo->DBQuote($sp) . " WHERE address_id=" . $hr->{address_id});
        }
    }

    # populate the street_address column by concatenating address1 and address2
    #
    $sql = "SELECT address_id,address1,address2 from address";
    $sth = $dbo->DoCmd($sql);
    while (my $hr = $sth->fetchrow_hashref())
    {
        my $newAddress = $hr->{address1};
        if ($newAddress && $hr->{address2})
        {
            $newAddress .= ', ' . $hr->{address2};
        }

        $dbo->DoCmd("UPDATE address set street_address=" . $dbo->DBQuote($newAddress) . " WHERE address_id=" . $hr->{address_id});
    }

    
    # Drop the old columns
    # !!! SKIP THIS STEP FOR NOW !!!
    #
    if ($gDropOldColumns)
    {
        $sql = "ALTER TABLE address DROP COLUMN address1";
        if (!$dbo->DoCmd($sql))
        {
            _report("error " . $dbo->LastErrorStr());
            return;
        }

        $sql = "ALTER TABLE address DROP COLUMN address2";
        if (!$dbo->DoCmd($sql))
        {
            _report("error " . $dbo->LastErrorStr());
            return;
        }
        $sql = "ALTER TABLE address DROP COLUMN state";
        if (!$dbo->DoCmd($sql))
        {
            _report("error " . $dbo->LastErrorStr());
            return;
        }
        $sql = "ALTER TABLE address DROP COLUMN province";
        if (!$dbo->DoCmd($sql))
        {
            _report("error " . $dbo->LastErrorStr());
            return;
        }
    }
}


sub _getAllAvailableClientIDs
{
    # First, get the list of _possible_ ids.
    #
    my @allIDs = Common::RSDB::GetAllClientIDs();


    # Now, we want to see which of these clients is actually present on this machine.
    #
    my %dbNames;
    `mysql -uroot -e 'show databases' > /tmp/DBLIST`;
    open DBLIST, "/tmp/DBLIST" or die "ERROR - unable to open /tmp/DBLIST : $!";
    while (my $line = <DBLIST>)
    {
        chomp $line;
        $dbNames{$line} = 1;
    }
    close DBLIST;


    # Match these up with an id
    #
    my @foundIDs;
    foreach my $id (@allIDs)
    {
        my $name = Common::RSDB::ClientIDToDBName($id);
        foreach my $dbName (keys %dbNames)
        {
            if ($name eq $dbName)
            {
                push @foundIDs, $id;
                last;
            }
        }
    }

    return @foundIDs;
}

sub _deleteDBs
{
    my ($clientID) = @_;

    my $dbName = Common::RSDB::ClientIDToDBName($clientID);

    `rm -f /tmp/$dbName*`;
    `mysql -uroot -e 'drop database $dbName'`;
    $dbName .= '_RAPTOR';
    `mysql -uroot -e 'drop database $dbName'`;
}


sub _getUPCAlt
{
    my ($clientID) = @_;

    my $dbName = Common::RSDB::ClientIDToDBName($clientID);
    $dbName .= '_RAPTOR';

    my %map;
 	my $dbo = new Common::RSDB(client_id => $clientID, db_name => $dbName);

    my $sql = "SELECT album_id, upc_alt from album";
    my $sth = $dbo->DoCmd($sql);
    while (my $hr = $sth->fetchrow_hashref())
    {
        $map{$hr->{album_id}} = $hr->{upc_alt};
    }

    return \%map;
}



sub _alterProduct
{
    my ($clientID) = @_;

    _report("altering product table");


    my $upcMap = _getUPCAlt($clientID);

    # Instantiate the application singleton object.
    #
    my $appSingleton = Common::RSApp->new(clientID => $clientID);
    _report("altering product table");

    # First, we'll add the new column.
    #
    _report("adding asset_id column");

	my $dbo = Common::RSApp::GetClientDB();
	my $sql = "ALTER TABLE product ADD COLUMN asset_id int(10) unsigned default NULL";
    if (!$dbo->DoCmd($sql))
    {
        _report("error " . $dbo->LastErrorStr());
        return;
    }
	$sql = "ALTER TABLE product ADD COLUMN upc_alt varchar(25) default NULL";
    if (!$dbo->DoCmd($sql))
    {
        _report("error " . $dbo->LastErrorStr());
        return;
    }


    # Now we'll move through the products, setting asset_id and upc_alt to the proper value.
    #
	$dbo = Common::RSApp::GetClientDB();
    $sql = "SELECT product_id, product_type_id, track_id, album_id from product";
    my $sth = $dbo->DoCmd($sql);
    while (my $hr = $sth->fetchrow_hashref())
    {
        my $productID = $hr->{product_id};
        my $upcAlt;
        my $assetID;
        if (4 == $hr->{product_type_id})
        {
            $assetID = $hr->{track_id};
        }
        else
        {
            $assetID = $hr->{album_id};
            $upcAlt = $upcMap->{$assetID};
        }

        if (0 == $assetID)
        {
            _report("ERROR - no asset id: record: " . Dumper($hr));
        }
        
        $sql = "UPDATE product SET asset_id=$assetID, upc_alt=" . $dbo->DBQuote($upcAlt) . " WHERE product_id=$productID";
        $dbo->DoCmd($sql);
    }


    # Last, drop track_id and album_id
    #
    _report("dropping album_id");
    $sql = "ALTER TABLE product DROP COLUMN album_id";
    if (!$dbo->DoCmd($sql))
    {
        _report("error " . $dbo->LastErrorStr());
        return;
    }


    _report("dropping track_id");
    $sql = "ALTER TABLE product DROP COLUMN track_id";
    if (!$dbo->DoCmd($sql))
    {
        _report("error " . $dbo->LastErrorStr());
        return;
    }

    _report("product table has been altered");
}



sub _readClientArtistIDFromRaptor
{
    my ($clientID) = @_;
    assert($clientID);

    my $dbName = Common::RSDB::ClientIDToDBName($clientID);
    $dbName .= '_RAPTOR';

    my %map;
 	my $dbo = new Common::RSDB(client_id => $clientID, db_name => $dbName);

    # Can't use a fancy db abstraction layer safely, so we'll query this the old fashioned way
    #
    my $sql = "select artist_id, client_artist_id, artist_name_clean from artist";
    my $sth = $dbo->DoCmd($sql);
    while (my $hr = $sth->fetchrow_hashref())
    {
        $map{$hr->{artist_id}} = 
        {
            clientArtistID => $hr->{client_artist_id},
            nameClean => $hr->{artist_name_clean},
        };
    }

    return \%map;
}

sub _alterArtist
{
    my ($clientID) = @_;
    assert($clientID);

    _report("altering artist table");

    my $clientArtistIDMap = _readClientArtistIDFromRaptor($clientID);

    my $appSingleton = Common::RSApp->new(clientID => $clientID);

    my $dbo = Common::RSApp::GetClientDB();
    my $sql = "ALTER TABLE artist ADD COLUMN client_artist_id varchar(50)";
    if (!$dbo->DoCmd($sql))
    {
        _report("error " . $dbo->LastErrorStr());
        return;
    }

    $sql = 'ALTER TABLE artist ADD KEY `client_artist_id` (`client_artist_id`)';
    if (!$dbo->DoCmd($sql))
    {
        _report("error " . $dbo->LastErrorStr());
        return;
    }


    $sql = 'ALTER TABLE artist ADD COLUMN name_clean varchar(255)';
    if (!$dbo->DoCmd($sql))
    {
        _report("error " . $dbo->LastErrorStr());
        return;
    }


    while (my ($artistID, $raptorInfoHash) = each(%$clientArtistIDMap))
    {
        my $clientArtistID = $raptorInfoHash->{clientArtistID};
        my $nameClean = $raptorInfoHash->{nameClean};

        $sql = "UPDATE artist SET client_artist_id = " . $dbo->DBQuote($clientArtistID) 
             . ", name_clean = " . $dbo->DBQuote($nameClean) 
             . " WHERE artist_id = " . $artistID;
        if (!$dbo->DoCmd($sql))
        {
            _report("error " . $dbo->LastErrorStr());
            return;
        }
    }

    $sql = "create index name_clean on artist (name_clean)";
    if (!$dbo->DoCmd($sql))
    {
        _report("error " . $dbo->LastErrorStr());
        return;
    }
    $sql = "create fulltext index artist_name_text on artist (name)";
    if (!$dbo->DoCmd($sql))
    {
        _report("error " . $dbo->LastErrorStr());
        return;
    }

}




sub _alterAlbum
{
    my ($clientID) = @_;
    assert($clientID);

    _report("altering album table");
    my $clientAlbumIDMap = _readClientAlbumIDFromRaptor($clientID);


    my $appSingleton = Common::RSApp->new(clientID => $clientID);

    # Add the client_album_id column
    #
    my $dbo = Common::RSApp::GetClientDB();
    my $sql = "ALTER TABLE album ADD COLUMN client_album_id varchar(50)";
    if (!$dbo->DoCmd($sql))
    {
        _report("error " . $dbo->LastErrorStr());
        return;
    }
    $sql = "ALTER TABLE album ADD COLUMN company_id varchar(50)";
    if (!$dbo->DoCmd($sql))
    {
        _report("error " . $dbo->LastErrorStr());
        return;
    }
    $sql = "ALTER TABLE album ADD COLUMN vendor_id varchar(50)";
    if (!$dbo->DoCmd($sql))
    {
        _report("error " . $dbo->LastErrorStr());
        return;
    }
    $sql = "ALTER TABLE album ADD COLUMN location_id varchar(50)";
    if (!$dbo->DoCmd($sql))
    {
        _report("error " . $dbo->LastErrorStr());
        return;
    }
    $sql = "ALTER TABLE album ADD COLUMN title_clean varchar(255)";
    if (!$dbo->DoCmd($sql))
    {
        _report("error " . $dbo->LastErrorStr());
        return;
    }

    $sql = 'ALTER TABLE album ADD KEY `client_album_id` (`client_album_id`)';
    if (!$dbo->DoCmd($sql))
    {
        _report("error " . $dbo->LastErrorStr());
        return;
    }
    $sql = 'ALTER TABLE album ADD KEY `title_clean` (`title_clean`)';
    if (!$dbo->DoCmd($sql))
    {
        _report("error " . $dbo->LastErrorStr());
        return;
    }

    # Add the custom columns
    #
    $sql = "ALTER TABLE album ADD COLUMN custom_1 varchar(255)";
    if (!$dbo->DoCmd($sql))
    {
        _report("error " . $dbo->LastErrorStr());
        return;
    }
    $sql = "ALTER TABLE album ADD COLUMN custom_2 varchar(255)";
    if (!$dbo->DoCmd($sql))
    {
        _report("error " . $dbo->LastErrorStr());
        return;
    }
    $sql = "ALTER TABLE album ADD COLUMN custom_3 varchar(255)";
    if (!$dbo->DoCmd($sql))
    {
        _report("error " . $dbo->LastErrorStr());
        return;
    }


    # I need to set the title_clean field.   To insure consistency, I'll derive it
    # from the current title.
    #
    $sql = "SELECT album_id, title from album";
    my $sth = $dbo->DoCmd($sql);
    while (my $hr = $sth->fetchrow_hashref)
    {
        $clientAlbumIDMap->{$hr->{album_id}}{titleClean} = Common::Util::clean_name($hr->{title});
    }

    while (my ($albumID, $raptorData) = each(%$clientAlbumIDMap))
    {
        my $clientAlbumID = $raptorData->{clientAlbumID};
        my $companyID = $raptorData->{companyID};
        my $vendorID = $raptorData->{vendorID};
        my $locationID = $raptorData->{locationID};
        my $upcAlt = $raptorData->{upcAlt};
        my $titleClean = $raptorData->{titleClean};

        $sql = "UPDATE album SET client_album_id = " . $dbo->DBQuote($clientAlbumID) 
             . ", company_id = " . $dbo->DBQuote($companyID)
             . ", vendor_id = " . $dbo->DBQuote($vendorID)
             . ", location_id = " . $dbo->DBQuote($locationID)
             . ", title_clean = " . $dbo->DBQuote($titleClean)
             . " WHERE album_id = " . $albumID;
        if (!$dbo->DoCmd($sql))
        {
            _report("error " . $dbo->LastErrorStr());
            return;
        }
    }


    # Attempt to create indexes - if these fail, it's probably because
    # they already exist (so don't bail on error).
    #
    $sql = "create index artist_id on album (artist_id)";
    if (!$dbo->DoCmd($sql))
    {
        _report("error " . $dbo->LastErrorStr());
#        return;
    }
    $sql = "create index title_clean on album (title_clean)";
    if (!$dbo->DoCmd($sql))
    {
        _report("error " . $dbo->LastErrorStr());
#        return;
    }
    $sql = "create fulltext index album_title_text on album (title)";
    if (!$dbo->DoCmd($sql))
    {
        _report("error " . $dbo->LastErrorStr());
#        return;
    }
}



sub _transferRaptorDB
{
    my ($clientID) = @_;
    assert($clientID);

    # We'll need to know the name of the database.
    #
    my $dbName = Common::RSDB::ClientIDToDBName($clientID);
    _report("dbName : $dbName", 4);
    

    # So, first we'll need to tell the remote host to dump the raptor database.
    #
    _report("-- extracting database $dbName on raptor01");
    my $cmd = "rm -f /tmp/$dbName*;mysqldump -uroot $dbName > /tmp/$dbName;gzip /tmp/$dbName";
    `ssh raptor01 '$cmd'`;


    # Now we copy it over to here.
    #
    _report("-- copying database dump to local host");
    `rm -f /tmp/$dbName*`;
    `scp -o 'TCPKeepAlive yes' raptor01:/tmp/$dbName.gz /tmp/$dbName.gz`;

    # Expand it.
    #
    _report("-- uncompressing data");
    `gunzip /tmp/$dbName.gz`;


    # Create the new raptor db on the local host.
    #
    my $newDBName = $dbName . "_RAPTOR";
    _report("-- importing data into new database $newDBName");
    `mysql -uroot -e 'create database $newDBName'`;
    `mysql -uroot $newDBName < /tmp/$dbName`;
}


sub _readClientAlbumIDFromRaptor
{
    my ($clientID) = @_;
    assert($clientID);

    my $dbName = Common::RSDB::ClientIDToDBName($clientID);
    $dbName .= '_RAPTOR';

    my %map;
 	my $dbo = new Common::RSDB(client_id => $clientID, db_name => $dbName);

    # Can't use a fancy db abstraction layer safely, so we'll query this the old fashioned way
    #
    my $sql = "select album_id, client_album_id, company_id, vendor_id, location_id from album";
    my $sth = $dbo->DoCmd($sql);
    while (my $hr = $sth->fetchrow_hashref())
    {
        $map{$hr->{album_id}} = 
        {
            clientAlbumID => $hr->{client_album_id},
            companyID => $hr->{company_id},
            vendorID => $hr->{vendor_id},
            locationID => $hr->{location_id},
        }
    }

    return \%map;
}



sub _usage
{
    print "\nusage: $0 -c client_id [-V]\n";
    print "\n";
    print "Arguments:\n";
    print "\t-c <client_id>\t\tThe client_id of the client to process\n";
    print "\t-V <N>\t\t\tVerbosity level - 0 means no output\n";
}


sub _report
{
    my ($string, $verbosity) = @_;
    $verbosity = 1 unless defined $verbosity;

    if ($gVerbosityLevel >= $verbosity)
    {
        print STDERR $string . "\n";
    }
}


# Pass dates in 'yyyy-mm-dd' format.
#
sub _parseCommandLine
{
    my ($settings) = @_;

    my %opt;
    getopts('dc:V:', \%opt);

    if ($opt{d})
    {
        $settings->{deleteFirst} = 1;
    }


    if ($opt{c})
    {
        $settings->{clientID} = $opt{c};
    }

    if (defined $opt{V})
    {
        $gVerbosityLevel = $opt{V};
    }
}

