Perl DBI and sql now() - perl

I have been trying to use sql NOW() function while I update a table. But nothing happens to that date field, I think DBI just ignores that value. Code is :
dbUpdate($id, (notes => 'This was an update', filesize => '100.505', dateEnd => 'NOW()'));
and the function is :
sub dbUpdate {
my $id = shift;
my %val_hash = #_;
my $table = 'backupjobs';
my #fields = keys %val_hash;
my #values = values %val_hash;
my $update_stmt = "UPDATE $table SET ";
my $count = 1;
foreach ( #fields ) {
$update_stmt .= "$_ = ? ";
$update_stmt .= ', ' if ($count != scalar #fields);
$count++;
}
$update_stmt .= " WHERE ID = ?";
print "update_stmt is : $update_stmt\n";
my $dbo = dbConnect();
my $sth = $dbo->prepare( $update_stmt );
$sth->execute( #values, $id ) or die "DB Update failed : $!\n";
$dbo->disconnect || die "Failed to disconnect\n";
return 1;
}#dbUpdate
As you can see this is a "dynamic" generation of the sql query and hence I dont know where an sql date function(like now()) come.
In the given example the sql query will be
UPDATE backupjobs SET filesize = ? , notes = ? , dateEnd = ? WHERE ID = ?
with param values
100.55, This was an update, NOW(), 7
But the date column still shows 0000-00-........
Any ideas how to fix this ?

I have been trying to use sql NOW() function while I update a table. But
nothing happens to that date field, I think DBI just ignores that
value.
No, it doesn't. But the DB thinks you are supplying a datetime or timestamp data type when you are in fact trying to add the string NOW(). That of course doesn't work. Unfortunately DBI cannot find that out for you. All it does is change ? into an escaped (via $dbh->quote()) version of the string it got.
There are several things you could do:
Supply a current timestamp string in the appropriate format instead of the string NOW().
If you have it available, you can use DateTime::Format::MySQL like this:
use DateTime::Format::MySQL;
dbUpdate(
$id,
(
notes => 'This was an update',
filesize => '100.505',
dateEnd => DateTime::Format::MySQL->format_datetime(DateTime->now),
)
);
If not, just use DateTime or Time::Piece.
use DateTime;
my $now = DateTime->now->datetime;
$now =~ y/T/ /;
dbUpdate(
$id,
(notes => 'This was an update', filesize => '100.505', dateEnd => $now));
Tell your dbUpdate function to look for things like NOW() and react accordingly.
You can do something like this. But there are better ways to code this. You should also consider that there is e.g. CURRENT_TIMESTAMP() as well.
sub dbUpdate {
my $id = shift;
my %val_hash = #_;
my $table = 'backupjobs';
my $update_stmt = "UPDATE $table SET ";
# Check for the NOW() value
# This could be done with others too
foreach ( keys %val_hash ) {
if ($val_hash{$_} =~ m/^NOW\(\)$/i) {
$update_stmt .= "$_ = NOW()";
$update_stmt .= ', ' if scalar keys %val_hash > 1;
delete $val_hash{$_};
}
}
# Put everything together, join keeps track of the trailing comma
$update_stmt .= join(', ', map { "$_=?" } keys %val_hash );
$update_stmt .= " WHERE ID = ?";
say "update_stmt is : $update_stmt";
say "values are: ", join(', ', values %val_hash);
my $dbo = dbConnect();
my $sth = $dbo->prepare( $update_stmt );
$sth->execute( values %val_hash, $id ) or die "DB Update failed : $!\n";
$dbo->disconnect || die "Failed to disconnect\n";
return 1;
}
Write your queries yourself.
You're probably not going to do it and I'll not add an example since you know how to do it anyway.
Here's something else: Is this the only thing you do with your database while your program runs? It is not wise to connect and disconnect the database every time you make a query. It would be better for performance to connect the database once you need it (or at the beginning of the program, if you always use it) and just use this dbh/dbo everywhere. It saves a lot of time (and code).

Sorry but you can't use NOW() as an interpolated value like that.
when a ? is used in an SQL statement, the corresponding value is (via some mechanism or other) passed to the database escaped, so whatever value is interpreted as a string, not as SQL. So you are in effect attempting to use the string 'NOW()' as a date value, not the function NOW().
To use NOW() as a function, you will have to insert it into the SQL itself rather than pass it as a bound value. This means you will either have to use a hack of your dbupdate function or write a new one, or obtain the time as a string in perl and pass the resulting string the dbupdate.

Related

SQLite & Perl Query Issue with Email Address

I'm trying to query an SQLite3 database (in preparation for building a perl based webpage with authentication for a university assignment). The problem I have is that the database is returning a null result when trying to query by the email address which is also the username for the purpose of this database.
The query looks as follows:
my $sql = "SELECT * FROM accounts WHERE email_address = 'xxxxx#xxxxx.xxxxx.edu.au'";
my $sth = $dbh->prepare( $sql ) or die( "Can't prepare: " . $dbh->errstr() );
$sth->execute() or die( "Can't execute: " . $sth->errstr() );
my #row;
while( #row = $sth->fetchrow_array() )
{
print join( q/, /, #row ), "\n";
}
This prints out a null result. Yet if I were to query via the first_name or last_name fields on the database I'm able to get the data to print out.
The email address is in the following format: xxx#xxx.xxx.edu.au
The database structure is as follows:
accounts(
email_address VARCHAR(50) PRIMARY KEY,
last_name VARCHAR(30) NOT NULL,
first_name VARCHAR(30) NOT NULL,
password VARCHAR(100) NOT NULL,
pass_salt VARCHAR(10) NOT NULL,
account_balance DOUBLE
);
Any advice anyone can offer would be much apreciated!
You correctly identified what the problem is, but your solution is less than optimal. Instead of escaping the input manually in your code, you should be using placeholders. DBI makes this very comfortable.
That's done putting ? without quotes into your SQL, and then passing the arguments to execute. DBI will handle all the escaping of characters that carry a meaning in SQL, thus rendering SQL injection impossible. This was not a problem in this case, but it's a nice bonus.
my $email = 'xxxxx#xxxxx.xxxxx.edu.au';
my $sql = "SELECT * FROM accounts WHERE email_address = ?";
my $sth = $dbh->prepare($sql) or die "Can't prepare: " . $dbh->errstr;
$sth->execute($email) or die "Can't execute: " . $sth->errstr;
my #row;
while ( #row = $sth->fetchrow_array ) {
print join( q/, /, #row ), "\n";
}
Of course, you could have simply used single quotes ' for the whole SQL as well since you're not doing any variable interpolation anyway. However, you should always use placeholders.
It looks like you are trying to produce a CSV file. Take a look at Text::CSV, it will make your life easier.
You are right about perl interpolating arrays into your string, but you should also have seen a message that warned you of that
Possible unintended interpolation of #xxxxx in string
It's also much better to use placeholders in your SQL string and pass the real data in your call to execute. It has the advantage that you need only prepare your SQL statement once, but execute it multiple times with different values for the email address. It also handles any necessary quoting of the values for you
I suggest your code should look like this
my $account_by_email = $dbh->prepare('SELECT * FROM accounts WHERE email_address = ?');
$account_by_email->execute('xxxxx#xxxxx.xxxxx.edu.au');
while ( my #row = $account_by_email->fetchrow_array ) {
print join( ', ', #row ), "\n";
}
Ok I'm blind, everyone ignore me. Forgetting that # dictates an array in perl I didn't think about escaping the # symbol in the email address. So looks like a minor bit of regex will be needed for email addresses to make sure queries can be put through.
Simple solution:
my $sql = "SELECT * FROM accounts WHERE email_address = 'xxxxx\#xxxxx.xxxxx.edu.au'";
^ inserted escape char!

Perl dbi sqlite 'select * ..' only returns first elem

got a problem with perl dbi sqlite.
I have set up a database (and checked it with sqlite command line).
Now i want to search in this database, which did not work.
So i tried to just do a 'SELECT *'
this prints only the first element in the database, but not as it should everything in this table.
I think the error that causes the select * to fail is the same that prevents me from using "like %..%" stuff.
This is the relevant code, if the code is correct and the database table seems good what else could have caused the problems ?
my $dbh = DBI->connect("dbi:SQLite:dbname=$dbfile","","") || die "Cannot connect: $DBI::errstr";
my $sth = $dbh->prepare('SELECT * FROM words');
$sth->execute;
my #result = $sth->fetchrow_array();
foreach( #result) {
print $_;
}
fetchrow_array() only fetches one row.
Try
while ( my #row = $sth->fetchrow_array ) {
print "#row\n";
}
According to the documentation, fetchrow_array
Fetches the next row of data and returns it as a list containing the field values.
If you want all of the data you can call fetchrow_array (or fetchrow_arrayref) repeatedly until you reach the end of the table, or you can use fetchall_arrayref:
The fetchall_arrayref method can be used to fetch all the data to be returned from a prepared and executed statement handle. It returns a reference to an array that contains one reference per row
The code would look like this
use strict;
use warnings;
use DBI;
my $dbfile = 'words.db';
my $dbh = DBI->connect("dbi:SQLite:dbname=$dbfile", '', '') or die "Cannot connect: $DBI::errstr";
my $sth = $dbh->prepare('SELECT * FROM words');
$sth->execute;
my $result = $sth->fetchall_arrayref;
foreach my $row ( #$result ) {
print "#$row\n";
}

DBI: selectall_arrayref and columnnames

When I fetch the data this way is it possible then to access the column names and the column types or do I need an explicit prepare to reach this?
use DBI;
my $dbh = DBI->connect( ... );
my $select = "...";
my #arguments = ( ... );
my $ref = $dbh->selectall_arrayref( $select, {}, #arguments, );
Update:
With prepare I would do it this way:
my $sth = $dbh->prepare( $select );
$sth->execute( #arguments );
my $col_names = $sth->{NAME};
my $col_types = $sth->{TYPE};
my $ref = $sth->fetchall_arrayref;
unshift #$ref, $col_names;
The best solution is to use prepare to get a statement handle, as you describe in the second part of your question. If you use selectall_hashref or selectall_arrayref, you don't get a statement handle, and have to query the column type information yourself via $dbh->column_info (docs):
my $sth = $dbh->column_info('','',$table,$column); # or $column='' for all
my $info = $sth->fetchall_arrayref({});
use Data::Dumper; print Dumper($info);
(specifically, the COLUMN_NAME and TYPE_NAME attributes).
However, this introduces a race condition if the table changes schema between the two queries.
Also, you may use selectall_arrayref with the Slice parameter to fetch all the columns into a hash ref, it needs no prepared statement and will return an array ref of the result set rows, with each rows columns the key's to a hash and the values are the column values. ie:
my $result = $dbh->selectall_arrayref( qq{
SELECT * FROM table WHERE condition = value
}, { Slice => {} }) or die "Error: ".$dbh->errstr;
$result = [
[0] = { column1 => 'column1Value', column2 => 'column2Value', etc...},
[1] = { column1 => 'column1Value', column2 => 'column2Value', etc...},
];
Making it easy to iterate over results.. ie:
for my $row ( #$results ){
print "$row->{column1Value}, $row->{column2Value}\n";
}
You can also specify which columns to extract but it's pretty useless due to the fact it's more efficient to do that in your SQL query syntax.
{ Slice => { column1Name => 1, column2Name => 1 } }
That would only return the values for column1Name and column2Name just like saying in your SQL:
SELECT column1Name, column2Name FROM table...

Why does MySQL DATE_FORMAT print a blank result?

For the past couple of hours I've been trying to format a MySQL timestamp using DATE_FORMAT and it doesn't do anything!
Perl Code:
use CGI;
use DBI;
my $q = new CGI;
# Database connection goes here
my $sth_select = $dbh->prepare(
"SELECT DATE_FORMAT(timestamp, '%m/%d/%y') FROM foo"
);
$sth_select->execute() || die "Unable to execute query: $dbh->errstr";
if (my $ref = $sth_select->fetchrow_hashref()) {
print $q->header;
print " TIME: $ref->{timestamp}";
exit;
}
Results
TIME:
It doesn't print the formatted time at all, it is blank!
When I attempt to print the timestamp it doesn't print anything, but if I were to remove DATA_FORMAT and just simply do a SELECT timestamp FROM foo, then it prints the timestamp just fine, albeit not formatted though. Can somebody provide their insight on this matter, please?
The hash returned has as keys column headers as provided by the database. When using a function like that, the column header is actually "DATE_FORMAT(timestamp, '%m/%d/%y')".
Try modifying your SQL to be:
my $sth_select = $dbh->prepare("SELECT DATE_FORMAT(timestamp, '%m/%d/%y') AS timestamp FROM foo");

How can I write a subroutine for DBI updates with varying number of arguments?

I'm writing a subroutine for DBI updates, and are having some trouble figuring out how to add placeholder and stuff...
I have this:
sub row_update {
my $table = shift;
my %updates = #_;
my $placeholders = ...
$dbh->do("UPDATE $table SET (foo) WHERE (bar)") etc...
}
Any ideas?
I just want a simple update function where I can send in x number of arguments (as a hash).
Thanks in advance!
Something like this might be Good Enough:
sub update {
my ($dbh, $args) = #_;
my $table = $args->{table} || die 'need table';
my $updates = $args->{updates} || die 'need updates';
my #cols = keys %$updates;
my $query = 'UPDATE $table SET '.
(join ', ', map { '$_ = ?' } #cols)
($args->{where} ? ' WHERE '. $args->{where} : '');
my $sth = $dbh->prepare($query);
$sth->execute(map { $updates->{$_} } #cols);
return $sth;
}
Use it like:
my $sth = update $dbh, {
table => 'foo',
updates => {
col1 => 'new_value',
col2 => 'another_value',
},
where => 'id=42',
};
Really, though, you want to look into using an ORM like
DBIx::Class. It will do a much better job of building queries than
string manipulation like this will.
(Rewriting the where clause to be parameterized is left as an exercise to the reader. You also need to quote the update keys and table name. See why people use ORMs?)
Edit: Thinking about this a bit more, you might like DBIx::Simple combined with SQL::Abstract. This will take less configuration effort than an ORM, but still give you many of the benefits.
If I understand the question correctly, it sounds like you're after SQL::Abstract. First, we create an SQL::Abstract object:
use SQL::Abstract;
my $sql = SQL::Abstract->new;
Now, as an example, we'll use it to insert some data into a table:
my %record = (
FirstName => 'Buffy',
LastName => 'Summers',
Address => '1630 Revello Drive',
City => 'Sunnydale',
State => 'California',
Occupation => 'Student',
Health => 'Alive',
);
my ($stmt, #bind) = $sql->insert(’staff’,\%record);
This results in:
$stmt = "INSERT INTO staff
(FirstName, LastName, Address, City,
State, Occupation, Health)
VALUES (?, ?, ?, ?, ?, ?, ?)";
#bind = ('Buffy','Summers','1630 Revello Drive',
'Sunnydale',’California','Student','Alive');
The nice thing about this is we can pass it directly to DBI:
$dbh->do($stmt, undef, #bind);
Of course, you want to be updating records, not just inserting them. Luckily, this is also quite easy:
my $table = 'People';
my %new_fields = (
Occupation => 'Slayer',
Health => 'Dead',
);
my %where = (
FirstName => 'Buffy',
LastName => 'Summers',
);
my ($stmt, #bind) = $sql->update($table, \%new_fields, \%where);
$dbh->do($stmt, undef, #bind);
This produces:
$stmt = 'UPDATE People SET Health = ?, Occupation = ?
WHERE ( FirstName = ? AND LastName = ? )';
#bind = ('Dead', 'Slayer', 'Buffy', 'Summers');
If you're after more information about SQL::Abstract, I recommend you look at its CPAN page. There's also a chapter in Perl Training Australia's Database Programming with Perl manual, which are freely available from our course notes page.
All the best,
Paul
Disclaimer: I'm managing director of Perl Training Australia, and therefore think that our course notes are pretty good.
Others suggested the usual "build a query with the right number of '?'s" approach.
For most queries like this the DBI->quote method is forgotten, but it can keep the code simpler and in most cases it's not any slower than the "proper" placeholder approach.
1) Use DBI->quote instead of place holders to build the query. For example for a simple select:
my $sql = "select foo from bar where baz in ("
. join(",", map { DBI->quote($_) } #bazs)
. ")";
my $data = $dbh->selectall_arrayref($sql);
2) As jrockway suggested - use an ORM to do this sort of low level stuff for you. DBIx::Class or Rose::DB::Object, for example.
The NUM_OF_PARAMS attribute may be of assistance:
"NUM_OF_PARAMS" (integer, read-only)
The number of parameters (placeholders) in the prepared
statement.
So for instance, I've got a script to run arbitrary SQL from the command line that uses this code:
my $s = $h->prepare($_);
for my $i (1..$s->{NUM_OF_PARAMS}){
my $param = shift #ARGV;
$s->bind_param($i, $param);
print LOG "Bind param $i using $param.\n"
or die "can't append to $opt{log}: $!";
}
$s->execute();
I can't say I've used the other modules that have been suggested, so they may do a better job.