Question

My data file: votes.txt is formed with rows identical to:

VOTE 1168241980 Campaign:ssss_uk_01B Validity:during 
Choice:Tupele CONN:MIG00VU MSISDN:00088866655598 
GUID:A34-FDS87-FHDHDH-DHDHDHD0 Shortcode:63334

with each entry separated by a space. I currently have a sample of 19 rows.

Fields:

CONN:MIG00VU MSISDN:00088866655598 
GUID:A34-FDS87-FHDHDH-DHDHDHD0 Shortcode:63334

are not being used for this exercise.

#!/usr/bin/perl 

use warnings;
use strict;
use Switch;
use DBI();

#
# _voting.pl
#

# Connect to the database.
my $dbh = DBI->connect("DBI:mysql:database=sms_voting;host=localhost",
                    "sisisi", "*********",
                    {'RaiseError' => 1});

my $sth = $dbh->prepare("INSERT INTO voting 
(epoch, validity, choice, campaigns_id, candidates_id ) VALUES (?,?,?,?,?)");

open (LOGFILE, '/var/www/voting/votes.txt') or die("Could not open log file.");

my $errors = 0;
my $campaign_id = 0;
my $candidate_id = 0;

foreach my $line (<LOGFILE>) {  

    my ($vote, $epoch, $campaign, $validity, 
 $choice, $CONN, $MSISDN, $GUID, $Shortcode) = split(' ', $line);

# parse the field:value entries...
$campaign = substr $campaign, 8, 11, '';
$validity = substr $validity, 9, 6, ''; # during
$choice = substr $choice, 7, 10, ''; # Brown

# case statements to define correct foreign keys...
 switch ($campaign) {
        case ("ssss_uk_01B")    { $campaign_id = 1 } 
        case ("ssss_uk_01C")    { $campaign_id = 2 } 
        case ("ssss_uk_01D")    { $campaign_id = 3 } 
        case ("ssss_uk_01E")    { $campaign_id = 4 } 
        case ("ssss_uk_01F")    { $campaign_id = 5 } 
        case ("ssss_uk_01G")    { $campaign_id = 6 } 
}

switch ($choice) {
        case ("Brown")      { $candidate_id = 1 } 
        case ("Cameron")    { $candidate_id = 2 } 
        case ("Balls")      { $candidate_id = 3 } 
        case ("Green")      { $candidate_id = 4 } 
        case ("Boring")     { $candidate_id = 5 } 
        case ("Tupele")     { $candidate_id = 6 } 
}

if ($epoch && $validity && $choice && $campaign_id && $candidate_id ) {

    $sth->execute($epoch, $validity, $choice, $campaign_id, $candidate_id);
    # debug
    print "$epoch $validity $choice \n"; # 1161048980 during Green
    next;
} 

$errors++;
 }

close (LOGFILE);

# debug
print qq(errors=$errors\n);

For each loop of the foreach, the $campaign, and $choice variables are run through switch statements in order to define candidate_id & campaign_id numbers. These foreign keys will map to candidates & campaigns tables resp.

see: http://acookson.org/wp-content/themes/cookie_23112012/img/sms_voting.png for the database model.

i.e.

INSERT INTO voting (epoch, validity, choice, campaigns_id, candidates_id ) 
VALUES (1161048980,'during','Brown', 1, 1),
(1161048980,'during','Tupele', 3, 5), ... etc

Any null values detected in votes.txt will result in the $errors variable being incremented.

The script appears to loop through votes.txt successfully but fails to initialize: $campaign_id & $candidate_id variables resp. Upon completion, errors=19 is printed to the terminal; the total number of rows in my sample data votes.txt; meaning every row in this file has failed to be inserted into the database.

mysql> select * from voting;
Empty set (0.00 sec)

confirms this.

The script reports no syntax errors; hence it's more lower level. It works fine without the switch; hence that's narrowed it down somewhat. I can't see the problem with the switch however so am here looking for advice.

Was it helpful?

Solution

Not sure what your issue is, but here's some pointers on your Switch statement and other code.

As far as I know, the use Switch feature/module has been replaced in perl v5.10.1 with the given/when feature, and is now deprecated. Personally, I have always found these statements a little odd and unreliable. And not irreplaceable by any means, as perl is such a flexible language.

In your case, I would recommend a hash lookup instead:

my %camp = ("ssss_uk_01B" => 1,
            "ssss_uk_01C" => 2,
            ...);
my %cand = ("Brown"       => 1,
            "Cameron"     => 2,
            ...);

$campaign_id  = $camp{$campaign};
$candidate_id = $cand{$choice}

If you need to provide default values, you can use the defined-or assignment operator:

$campaign_id //= "default value here";

Your substr assignments are a bit wrong. First off, you use all four arguments, which in perldoc is described like this:

substr EXPR,OFFSET,LENGTH,REPLACEMENT

Which means that you replace your match with '' the empty string. This affects the variable itself, and would delete your wanted data, if left on its own. Length in your case is not needed either, since it is the end of the string you are after. Lastly, you are saved by the fact that you use the return value of the substr statement.

Finally, when I try out your assignments, I get a one-off error on your first assignment ($campaign). The output is :ssss_uk_01, and you expect ssss_uk_01.

To use the substr function correctly, you should use:

$campaign = substr $campaign, 9;
$validity = substr $validity, 9;
$choice   = substr $choice, 7;

OTHER TIPS

I agree with TLP's suggestion on using hashes for lookup tables. But in a real application those lookup tables should be populated with a database lookup to avoid needing to alter the application when the data in the other tables change.

I'd also tend to be a little less trusting of the format of the data in the log. Using split alleviates the need to know the exact length of the label+colon size. Along that line I also suggest normalizing the data before using it as a key in the lookup hash. In this example I just made it all lower case.

While you're using warnings, you might as well use the warn function to bark out about error conditions. In a real application you might tap warnings into an email alert system so that you can always know when your unattended software is having issues.

The or+die idiom is an excellent practice when doing your file operations. But it will be more informative when these dies occur if you include $OS_ERROR (a.k.a. $!), even for print. In that way you will know the difference between permission denied, doesn't exist, or disk full, etc.

Those are suggestions that speak to issues of writing maintainable and resilient programs. But I personally believe that aesthetics of the code also contribute to the maintainability. Meaningful naming conventions and code formatting can make a big difference when you open up this program a year or two from now to make an improvement or a bug fix. I prefer to create code that lacks unnecessary clutter such as comments that state the obvious or the use of commenting to remove blocks of code.

Keep up the good work. The world needs more software automation.

#!/usr/bin/perl

use strict;
use warnings;
use DBI;

#
# _voting.pl
#
my $logfile = '/var/www/voting/votes.txt';

my $errors = 0;

my %campaign_id_for = (
    'ssss_uk_01b' => 1,
    'ssss_uk_01c' => 2,
    'ssss_uk_01d' => 3,
    'ssss_uk_01e' => 4,
    'ssss_uk_01f' => 5,
    'ssss_uk_01g' => 6,
);

my %candidate_id_for = (
    'brown'   => 1,
    'cameron' => 2,
    'balls'   => 3,
    'green'   => 4,
    'boring'  => 5,
    'tupele'  => 6,
);

my $dbh = DBI->connect( 
    'DBI:mysql:database=sms_voting;host=localhost',
    'sisisi', 
    '*********', 
    { 'RaiseError' => 1 } 
);

my $sth = $dbh->prepare(q{
    INSERT INTO voting (
        epoch,
        validity,
        choice,
        campaigns_id,
        candidates_id
    ) VALUES (
        ?,
        ?,
        ?,
        ?,
        ?
    )
});

my $fh;

open $fh, '<', $logfile
    or die "open $logfile: $!";

LINE:
for my $line (<$fh>) {

    my ($vote, $epoch,  $campaign, $validity, $choice,
        $CONN, $MSISDN, $GUID,     $Shortcode
    ) = split /\s+/, $line;

    ($campaign) = reverse split /:/, $campaign;
    ($validity) = reverse split /:/, $validity;
    ($choice)   = reverse split /:/, $choice;

    my $campaign_id  = $campaign_id_for{ lc $campaign };
    my $candidate_id = $candidate_id_for{ lc $choice };

    if ( $epoch && $validity && $choice && $campaign_id && $candidate_id ) {

        $sth->execute(
            $epoch,
            $validity,
            $choice,
            $campaign_id,
            $candidate_id
        );

        print "$epoch $validity $choice\n"
            or die "print: $!";

        next LINE;
    }
    else {

        warn "failed to parse: $line\n";
        $errors++;
    }
}

close $fh
    or die "close $logfile: $!";

# debug
print "error count: $errors\n"
    or die "print: $!";
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top