Alice Wonder has asked for the wisdom of the Perl Monks concerning the following question:

Hello, I usually do not program in perl, so I apologize that I do not even know where to begin looking for solution.

Web app I am building in php, I am trying to be very careful with user e-mails to avoid data leaks and they are in a different database then the web app uses, the web app cues a message to a user id when it wants to e-mail user.

That is where perl comes in. I want perl script running from cron every 10 minutes to fetch messages from the cue, associate e-mail with user id, and send it.

sendmail (er, postfix...) is working on the server and sending mail via /usr/sbin/sendmail works. Host is CentOS 7 x86_64

The error I get from the script:

Name "main::id" used only once: possible typo at sendmessages.pl line +39. Use of uninitialized value in lc at /usr/share/perl5/vendor_perl/Email +/Simple/Header.pm line 181. Use of uninitialized value in lc at /usr/share/perl5/vendor_perl/Email +/Simple/Header.pm line 181. Use of uninitialized value in lc at /usr/share/perl5/vendor_perl/Email +/Simple/Header.pm line 181. no recipients Trace begun at /usr/share/perl5/vendor_perl/Email/Sender/Simple.pm lin +e 107 Email::Sender::Simple::send_email('Email::Sender::Simple', 'Email::Abs +tract=ARRAY(0x34f66e8)', 'HASH(0x35be030)') called at /usr/share/perl +5/vendor_perl/Email/Sender/Role/CommonSending.pm line 27 Email::Sender::Role::CommonSending::__ANON__ at /usr/share/perl5/vendo +r_perl/Try/Tiny.pm line 71 eval {...} at /usr/share/perl5/vendor_perl/Try/Tiny.pm line 67 Try::Tiny::try('CODE(0x35bdfd0)', 'Try::Tiny::Catch=REF(0x34ccf28)') c +alled at /usr/share/perl5/vendor_perl/Email/Sender/Role/CommonSending +.pm line 40 Email::Sender::Role::CommonSending::send('Email::Sender::Simple', 'ali +cewonder@###') called at /usr/share/perl5/vendor_perl/Sub/Exporter/Ut +il.pm line 18 Sub::Exporter::Util::__ANON__('alicewonder@###') called at sendmessage +s.pl line 64 (### is hiding the valid domain, I h8 spam)

(the main::id I understand, it will be used in code once I get send working)

The code:

#!/usr/bin/perl -w use DBI; $dbh = DBI->connect('dbi:mysql:alicenet','####','####', { mysql_enable +_utf8 => 1, }) or die "Connection Error: $DBI::errstr\n"; $adbh = DBI->connect('dbi:mysql:anetauth','####','####', { mysql_enabl +e_utf8 => 1, }) or die "Connection Error: $DBI::errstr\n"; #count the messages $sql = "SELECT COUNT(id) AS count FROM mailcue WHERE failed < 3 AND la +sttry < (NOW() - INTERVAL 90 MINUTE)"; $sth = $dbh->prepare($sql); $sth->execute or die "SQL Error: $DBI:: errstr\n"; @row = $sth->fetchrow; $count = $row[0]; #max number of cued messages to fetch $n = ($count < 180)? 30 : int($count/6); #seconds to sleep between action - we want to finish in 8 minutes $sleep = int(480/$n); $sql = "SELECT id,userid,subject,message FROM mailcue WHERE failed < 3 + AND lasttry < (NOW() - INTERVAL 90 MINUTE) ORDER BY id LIMIT $n"; $sth = $dbh->prepare($sql); $sth->execute or die "SQL Error: $DBI::errstr\n"; while (@row = $sth->fetchrow_array) { $id = $row[0]; $userid = $row[1]; $subject = $row[2]; $body = $row[3]; $asql = "SELECT email FROM userauth WHERE id=$userid"; $asth = $adbh->prepare($asql); $asth->execute; @arow = $asth->fetchrow; $email = $arow[0]; # okay we can send this futher mocker use Email::MIME; my $message = Email::MIME->create( header_str => [ From => 'anetbot@domblogger.net', To => $email, Subject => $subject, ], attributes => { encoding => '8bit', charset => 'utf-8', }, body_str => $body, ); # send it use Email::Sender::Simple qw(sendmail); my $response = sendmail($email); # on success, delete from mailcue. On fail, increment failed field in + mailcue and insert error message into error field of mailcue. sleep $sleep; }

Thank you for any assistance in figuring out how to fix that error.

Replies are listed 'Best First'.
Re: Use of uninitialized value in lc
by toolic (Bishop) on Nov 08, 2014 at 13:34 UTC

      figured it out - I was using $email for the address and $message for the constructed e-mail but then calling sendmail($email) not sendmail($message)

      Doh!

      I need more sleep ;)

        You can join tables in different databases if they are on the same server. Also consider using placeholders (?) in the queries. For example
        #!/usr/bin/perl use strict; use warnings; use DBI; my $dbh = DBI->connect('dbi:mysql:alicenet','user','password', {mysql_enable_utf8 => 1,RaiseError => 1,PrintError => 1}) or die "Connection Error: $DBI::errstr\n"; my $where = 'WHERE M.failed < 3 AND M.lasttry < (NOW() - INTERVAL 90 MINUTE)'; my $sql = "SELECT COUNT(id) FROM mailcue AS M $where"; #count the messages my ($count) = $dbh->selectrow_array($sql); #max number of cued messages to fetch my $max = ($count < 180)? 30 : int($count/6); $sql = "SELECT U.email,M.subject,M.message,M.id,M.failed FROM mailcue as M LEFT JOIN anetauth.userauth as U ON U.id = M.userid $where ORDER BY M.id LIMIT ?"; my $sql_fail = "UPDATE mailcue SET lasttry = NOW(),failed = ? WHERE id = ?"; my $sth_fail = $dbh->prepare($sql_fail); my $sql_ok = 'DELETE FROM mailcue WHERE id = ?'; my $sth_ok = $dbh->prepare($sql_ok); my $ar = $dbh->selectall_arrayref($sql,undef,$max); for (@$ar){ my ($email_addr,$subject,$message,$id,$failed) = @$_; # replace print with email create and send print " -------------------- to = $email_addr subject = $subject body = $message id = $id failed = $failed =================="; my $response = 'fail'; # test if ($response eq ''){ # change to suit $sth_ok->execute($id); } else { $sth_fail->execute($failed+1,$id); } }
        poj
Re: Use of uninitialized value in lc
by ww (Archbishop) on Nov 08, 2014 at 14:22 UTC
    I see the SOPW has a better answer already... but I'm posting this anyway in order to illustrate a process other than the add print statements suggested above.

    Inspection of Email::Sender::Simple shows sub header { starting at Ln 176:

    sub header { # Ln 176 my ($self, $field) = @_; my $headers = $self->{headers}; my $lc_field = lc $field; # Ln 181

    Based on what you've told us, one or both the variables that are supposed to be passed to the sub isn'/aren't getting there. Could that be something that will be supplied by main::id?

    Alternately, I can only suggest that you track thru the module's source to find the call and backtrack from there to find why data isn't getting to $field



    check Ln42!

Re: Use of uninitialized value in lc
by chacham (Prior) on Nov 09, 2014 at 04:46 UTC

    FWIW: COUNT(*) is probably better than COUNT(id). COUNT(*) checks how many rows there are, and there are sometimes efficiencies in that such as checking the high water mark. COUNT(id) checks how many ids are not null, which means every value must be checked (unless it is a not null column and the optimizer is smart enough to rewrite the query.)

      Except if "id" is indexed as unique (such as a primary key), in which case COUNT(id) should be slightly faster than COUNT(*). At least in theory.

      -- FloydATC

      Time flies when you don't know what you're doing

        Except if "id" is indexed as unique (such as a primary key), in which case COUNT(id) should be slightly faster than COUNT(*). At least in theory.

        Sorry, but that is wrong on three counts (no pun intended.)

        1. Even if id is unique, there may be NULLs. And that depends on the RDBMS, where Oracle and mysql, do not store NULLs on the INDEX, you are correct that an index full scan is enough, however, in SQL Server, DB2, and other that do store NULL, there may be a NULL on the index which needs to be checked for. In any case, COUNT(*) may give a different number as those NULLs are counted.
        2. The primary key is not only unique, it is also not null, which means that it and COUNT(*) will return the same number. However, this requires reading the data dictionary to find that this is indeed the PK, and then reading in all the blocks if it isn't already in memory. Though, if the optimizer is smart enough, it won't bother doing this anyway and it will rewrite it to the equivalent of COUNT(*).
        3. COUNT(*) only needs a number, so if statistics were taken, it just needs to check the high water mark, an efficiency not available to counting columns.

        Logically, nothing can be faster than COUNT(*) (or COUNT(1), etc...).