SQLite & Perl Query Issue with Email Address - perl

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!

Related

why autoincrement primary key column not autoincrementing in sqlite3

I have an sqlite3 db for an address book, and I created the address table with
my $dbcreate = qq(CREATE TABLE IF NOT EXISTS address
(ID INTEGER PRIMARY KEY AUTOINCREMENT,
LASTNAME TEXT NOT NULL,
FIRSTNAME TEXT NOT NULL,
COMPANY TEXT,
STREET TEXT,
CITY TEXT,
STATE TEXT,
COUNTRY TEXT,
ZIPCODE TEXT,
MOBILE TEXT,
OTHERPHONE TEXT,
FAX TEXT,
EMAIL TEXT,
EMAIL2 TEXT,
WEBSITE TEXT,
WEBSITE2 TEXT,
DOB DATE,
NOTES TEXT,
TAGS TEXT););
So the first column SHOULD, as I understand it, be an autoincrementing primary ID, only, it's not autoincrementing. My understanding from reading sqlite tutorials is that I should only have to enter the other 18 values, and that column should autoincrement, but if I insert 18 values, I'm told I'm missing one.
I've only been able to insert rows if I manually set the ROWID.
What gives?
Here's how I'm adding rows (unsuccessfully)
#!/usr/bin/perl -w
# script to enter a new addressbook entry
use DBI;
use strict;
use warnings;
print "Please enter a DB username? \n";
my $userid=<STDIN>;
chomp($userid);
print "Please enter a DB password? \n";
my $password=<STDIN>;
chomp($password);
my $driver = "SQLite";
my $database = "myaddress.db";
my $dsn = "DBI:$driver:dbname=$database";
my $dbh = DBI->connect($dsn, $userid, $password, { RaiseError => 1 })
or die $DBI::errstr;
print "Successfull DB connection\n";
print "Creating new address book entry\n";
my $arval = 0;
my #dbits = ('LASTNAME','FIRSTNAME','COMPANY','STREET','CITY','STATE','COUNTRY','ZIPCODE','MOBILE','OTHERPHONE','FAX','EMAIL','EMAIL2','WEBSITE','WEBSITE2','DOB','NOTES','TAGS');
# print "#dbits";
# for $b (0 .. 17) {
# print "\'$dbits[$b]\', ";
# }
print "-----\n";
my #nubits = ();
foreach my $databit(#dbits) {
print "Enter $databit: \n";
my $nubit = <STDIN>;
chomp($nubit);
print "$databit = $nubit\n\n";
push(#nubits,$nubit);
print "$databit = $nubits[$arval]\n";
my $arval = (++$arval);
print "$arval\n";
}
# print "new entry will be:\n";
# for $b (0 .. 17) {
# print "$nubits[$b] | ";
# }
my $entry = qq(INSERT INTO address VALUES('$nubits[0]','$nubits[1]','$nubits[2]','$nubits[3]','$nubits[4]','$nubits[5]','$nubits[6]','$nubits[7]','$nubits[8]','$nubits[9]','$nubits[10]','$nubits[11]','$nubits[12]','$nubits[13]','$nubits[14]','$nubits[15]','$nubits[16]','$nubits[17]'););
my $retval = $dbh->do($entry);
if($retval < 0){
print $DBI::errstr;
} else {
print "entry created successfully\n";
}
my $query = qq(select * from address);
print $query;
$dbh->disconnect();
I can manually use sqlite3 from the cli to enter rows, but, as mentioned, I have to manually set the ROWID/Primary ID.
In the past, I've only used sqlite with tcl/tk, never perl, but even though I'm a complete perl n00b, I don't think perl is my problem. sqlite3 is not behaving as expected (unless I've completely misread about a dozen tutorials that state that a primary key id set to autoincrement should, well, autoincrement).
It's not auto incrementing because you're already giving it an id, $nubits[0].
With no column list, INSERT INTO address VALUES (...) inserts all columns in the table. You could use NULL as #ReenactorRob suggests, but that just hides another problem with that query.
INSERT INTO address VALUES (...) requires knowledge of the order in which address was created to know that element 5 is street. If anything changes the ordering in the table, your INSERT breaks. If a column is added, your query breaks. If you put a value in the wrong slot (as you did) its difficult to tell that. You're much better off using an explicit column list.
INSERT INTO address
(lastname, firstname, company, street, ...)
VALUES
(...)
Now your ID will increment and you're protected from future table changes. If that seems like a lot of work, make it a function that takes a hash of values to build a query. It'll be much more readable than remembering what $nubits[12] is. But before you do that, look at DBIx::Class which has already done this for you.
I would be remiss if I didn't mention bind parameters. These are faster, allowing you to use prepared statements, and they protect you against SQL injection attacks.
Try using null in the insert like this:
my $entry = qq(INSERT INTO address VALUES(null,'$nubits[0]','$nubits[1]',...
As has been explained, the problem is that the items in the VALUES section of the INSERT are assigned one-for-one to the table's columns in the order they were first created. If you need to provide values for only a subset of the columns then you also need to provide a list of the column names that should receive the values.
Without the column names the values will be assigned to columns in the order that they were created, so it is the columns at the end that will not receive a value. In this case the database will allocate the value for the LASTNAME column to ID, and it is the TAGS element at the end that doesn't have a value.
It is also much better to use placeholders for the values in your SQL statement, and use prepare and execute instead of just do. That way the DBI module will quote and escape the actual values appropriately according to their data type.
This program shows how to do that
#!/usr/bin/perl
use strict;
use warnings;
use DBI;
use DBD::SQLite;
STDOUT->autoflush;
my ($driver, $dbname) = qw/ SQLite myaddress.db /;
my $dsn = "DBI:$driver:dbname=$dbname";
print 'Please enter a DB username: ';
my $username = <STDIN>;
chomp($username);
print 'Please enter a DB password: ';
my $password = <STDIN>;
chomp($password);
my $dbh = DBI->connect($dsn, $username, $password, { RaiseError => 1 });
print "Successful DB connection\n";
print "Creating new address book entry...\n";
print "-----\n";
my #dbits = qw/
LASTNAME FIRSTNAME COMPANY STREET
CITY STATE COUNTRY ZIPCODE
MOBILE OTHERPHONE FAX EMAIL
EMAIL2 WEBSITE WEBSITE2 DOB
NOTES TAGS
/;
my #nubits;
for my $databit (#dbits) {
print "Enter $databit: ";
my $nubit = <STDIN>;
chomp($nubit);
push #nubits, $nubit;
print "$databit = $nubits[-1]\n";
print scalar #nubits, "\n";
}
my $columns = join ', ', #dbits;
my $placeholders = join ', ', ('?') x #dbits;
my $insert = <<"__END__SQL__";
INSERT INTO address (
$columns
)
VALUES (
$placeholders
)
__END__SQL__
$insert = $dbh->prepare($insert);
$insert->execute(#nubits);
print "Entry created successfully\n";
$dbh->disconnect;
This creates an SQL statement in $insert that looks like this
INSERT INTO address (
LASTNAME, FIRSTNAME, COMPANY, STREET, CITY, STATE, COUNTRY, ZIPCODE, MOBILE, OTHERPHONE, FAX, EMAIL, EMAIL2, WEBSITE, WEBSITE2, DOB, NOTES, TAGS
)
VALUES (
?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
)

Perl DBI and sql now()

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.

DBI::Sybase data-conversion resulted in overflow

I am writing a Perl script that is using the DBI module and is connecting to a Sybase DB. I am calling a stored procedure (one that I don't have access to so I cannot post sample code) and when I get data back I get an error that reads "error_handler: Data-conversion resulted in overflow". I still get data back and after doing some intensive research it seems that some data types in the columns (such as BigInt, nvarchar, etc) are the culprits. Now the question is, how can I fix this? Can this be fixed on the client side or can it only be fixed on the server side?
my $dbh = DBI->connect("DBI:Sybase:server=$server", $username, $password, {PrintError => 0}) or die;
$dbh->do("use $database") or die;
my $sql = &getQuery;
my $sth = $dbh->prepare($sql) or die;
$sth->execute() or die;
while ($rowRef = $sth->fetchrow_arrayref) #Error seems to occur here
{
#Parse through each row
}
Part of the FreeTDS 0.82 log that explains the problem:
_ct_bind_data(): column 7 is type 38 and has length 8
_ct_get_server_type(0)
_ct_get_client_type(type 38, user 0, size 8)
cs_convert(0x18dfed40, 0x7fff73216050, 0x18e44250, 0x7fff73215fa0, 0x18e387c0, 0x18e45a64)
_ct_get_server_type(30)
_ct_get_server_type(0)
converting type 127 (8 bytes) to type = 47 (9 bytes)
cs_convert() calling tds_convert
cs_convert() tds_convert returned 10
cs_prretcode(0)
cs_convert() returning CS_FAIL
cs_convert-result = 1
The problem is on the FreeTDS side. I've had the same problem before and successfully fixed it by converting the returned fields to varchar in the select statement.
Given you don't have access to modify the original query, you can do some regex search and replace on the returned $sql variable in your code. In particular, if the original query has a part that looks like
SELECT field1, field2, field3 FROM ...
After you retrieve the query statement, you may run
my $new_sql;
if ($sql =~ /SELECT\s+(.*)\s+FROM/i) { # match selected field string
my $field_str = $1;
my #fields = split ",", $field_str; # parse individual fields
map s/\s//g, #fields; # get rid of spaces
my $new_str = join ", ", (map {sprintf "convert(varchar, $_)"} #fields); # construct new query string
my $quoted_field_str = quotemeta($field_str); # prepare regex replacement string
$new_sql = $sql;
$new_sql =~ s/$quoted_field_str/$new_str/i # actual replacement
}
print $new_sql;
Of course, if your original statement is more complex, you should print it out and check how to modify it with a generic replacement bearing the same spirit. Alternatively, you can ask your DBA (or whoever has access to the stored procedure) to modify the actual query directly.
Hope this helps.

Can I get the table names from an SQL query with Perl's DBI?

I am writing small snippets in Perl and DBI (SQLite yay!)
I would like to log some specific queries to text files having the same filename as that of the table name(s) on which the query is run.
Here is the code I use to dump results to a text file :
sub dumpResultsToFile {
my ( $query ) = #_;
# Prepare and execute the query
my $sth = $dbh->prepare( $query );
$sth->execute();
# Open the output file
open FILE, ">results.txt" or die "Can't open results output file: $!";
# Dump the formatted results to the file
$sth->dump_results( 80, "\n", ", ", \*FILE );
# Close the output file
close FILE or die "Error closing result file: $!\n";
}
Here is how I can call this :
dumpResultsToFile ( <<" END_SQL" );
SELECT TADA.fileName, TADA.labelName
FROM TADA
END_SQL
What I effectively want is, instead of stuff going to "results.txt" ( that is hardcoded above ), it should now go to "TADA.txt".
Had this been a join between tables "HAI" and "LOL", then the resultset should be written to "HAI.LOL.txt"
Is what I am saying even possible using some magic in DBI?
I would rather do without parsing the SQL query for tables, but if there is a widely used and debugged function to grab source table names in a SQL query, that would work for me too.
What I want is just to have a filename
that gives some hint as to what query
output it holds. Seggregating based on
table name seems a nice way for now.
Probably not. Your SQL generation code takes the wrong approach. You are hiding too much information from your program. At some point, your program knows which table to select from. Instead of throwing that information away and embedding it inside an opaque SQL command, you should keep it around. Then your logger function doesn't have to guess where the log data should go; it knows.
Maybe this is clearer with some code. Your code looks like:
sub make_query {
my ($table, $columns, $conditions) = #_;
return "SELECT $columns FROM $table WHERE $conditions";
}
sub run_query {
my ($query) = #_;
$dbh->prepare($query);
...
}
run_query( make_query( 'foo', '*', '1=1' ) );
This doesn't let you do what you want to do. So you should structure
your program to do something like:
sub make_query {
my ($table, $columns, $conditions) = #_;
return +{
query => "SELECT $columns FROM $table WHERE $conditions",
table => $table,
} # an object might not be a bad idea
}
sub run_query {
my ($query) = #_;
$dbh->prepare($query->{query});
log_to_file( $query->{table}.'.log', ... );
...
}
run_query( make_query( 'foo', '*', '1=1' ) );
The API is the same, but now you have the information you need to log
the way you want.
Also, consider SQL::Abstract for dynamic SQL generation. My code
above is just an example.
Edit: OK, so you say you're using SQLite. It has an EXPLAIN command
which you could parse the output of:
sqlite> explain select * from test;
0|Trace|0|0|0|explain select * from test;|00|
1|Goto|0|11|0||00|
2|SetNumColumns|0|2|0||00|
3|OpenRead|0|2|0||00|
4|Rewind|0|9|0||00|
5|Column|0|0|1||00|
6|Column|0|1|2||00|
7|ResultRow|1|2|0||00|
8|Next|0|5|0||00|
9|Close|0|0|0||00|
10|Halt|0|0|0||00|
11|Transaction|0|0|0||00|
12|VerifyCookie|0|1|0||00|
13|TableLock|0|2|0|test|00|
14|Goto|0|2|0||00|
Looks like TableLock is what you would want to look for. YMMV, this
is a bad idea.
In general, in SQL, you cannot reliably deduce table names from result set, both for theoretical reasons (the result set may only consist of computed columns) and practical (the result set never includes table names - only column names - in its data).
So the only way to figure out the tables used is to stored them with (or deduce them from) the original query.
I've heard good things about the parsing ability of SQL::Statement but never used it before now myself.
use SQL::Statement;
use strict;
use warnings;
my $sql = <<" END_SQL";
SELECT TADA.fileName, TADA.labelName
FROM TADA
END_SQL
my $parser = SQL::Parser->new();
$parser->{RaiseError} = 1;
$parser->{PrintError} = 0;
my $stmt = eval { SQL::Statement->new($sql, $parser) }
or die "parse error: $#";
print join',',map{$_->name}$stmt->tables;

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.