why autoincrement primary key column not autoincrementing in sqlite3 - perl

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 (
?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
)

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!

Fetching one single row from database using Perl

I am trying to run a simple database connection with Perl using postgreSQL
use DBI;
$database = "postgres";
$user = "postgres";
$password = "admin";
my $dbh = DBI->connect( "dbi:Pg:dbname=$database"
, $user
, $password
)
or die "Can't Connect to database: $DBI::errstr\n";
# get data from the URL string
my $firstname = "haroon";
my $lastname ="ash";
my $age = 24;
# insert the data into the database
my $query = "INSERT INTO people (firstname, lastname, age)
VALUES ('$firstname','$lastname', '$age')";
$dbh->do($query);
# get the ID of the inserted person
$query = "SELECT MAX(id) FROM people";
my $sth = $dbh->prepare($query);
my $rv =$sth->execute;
if($rv < 0){
print $DBI::errstr;
}
else {
my $row = $sth->fetchrow_hashref;
my $person_id = $row->{'max'};
print $firstname, $lastname
. "was successfully inserted at position "
. $person_id;
}
I am trying to print the person id which i had entered latest. But my $person_id = $row->{'max'} seems to give me the correct answer instead of my $person_id = $row->{'id'};. I am not understanding why is that.
You might want to set column alias for query,
$query = "SELECT MAX(id) AS id FROM people";
as postgres is putting his own alias for you, and that is max.
If all you want is last inserted id, you can
my $query = "INSERT INTO people (firstname, lastname, age)
VALUES (?,?,?)
returning id
";
and fetch query as you would do with select. (check pg docs)
You can use the RETURNING keyword to return the id associated with the row you just inserted:
my $query = '
INSERT INTO people (firstname, lastname, age)
VALUES ($1, $2, $3)
RETURNING id';
my $sth = $dbh->prepare($query);
$sth->execute($firstname, $lastname, $age);
my $rv = $sth->fetchrow_hashref();
printf "%s, %s was successfully inserted at position %d\n",
$firstname, $lastname, $rv->{id};
You did not enter a PERSON_ID in your insert statement.
Instead it seems the ID field was populated by the DB, an auto increment value during insert, it seems.
Without knowing the table definition (and potential insert trigger on the table) it is hard/impossible to give you a better answer.

String getting converted to number when inserting it in database through Perl's DBI $sth->execute() function

I'm using Perl's DBI and SQLite database (I have DBD::SQLite installed). I have the following code:
my $dbh = DBI->connect("dbi:SQLite:dbname=$db", "", "", { RaiseError => 1, AutoCommit => 1 });
...
my $q = "INSERT OR IGNORE INTO books (identica, book_title) VALUES (?, ?)";
my $sth = $dbh->prepare($q);
$sth->execute($book_info->{identica}, $book_info->{book_title});
The problem I have is when $book_info->{identica} begins with 0's they get dropped and I get a number inserted in the database.
For example, identica of 00123 will get converted to 123.
I know SQLite doesn't have types, so how do I make DBI to insert the identica as string rather than number?
I tried quoting it as "$book_info->{identica}" when passing to $sth->execute but that didn't help.
EDIT
Even if I insert value directly in query it doesn't work:
my $i = $book_info->{identica};
my $q = "INSERT OR IGNORE INTO books (identica, book_title) VALUES ('$i', ?)";
my $sth = $dbh->prepare($q);
$sth->execute($book_info->{book_title});
This still coverts 00123 to 123, and 0000000009 to 9...
EDIT
Holy sh*t, I did this on the command line, and I got this:
sqlite> INSERT INTO books (identica, book_title) VALUES ('0439023521', 'a');
sqlite> select * from books where id=28;
28|439023521|a|
It was dropped by SQLite!
Here is how the schema looks:
CREATE TABLE books (
id INTEGER PRIMARY KEY AUTOINCREMENT,
identica STRING NOT NULL,
);
CREATE UNIQUE INDEX IDX_identica on books(identica);
CREATE INDEX IDX_book_title on books(book_title);
Any ideas what is going on?
SOLUTION
It's sqlite problem, see answer by in the comments by Jim. The STRING has to be TEXT in sqlite. Otherwise it treats it as number!
Changing schema to the following solved it:
CREATE TABLE books (
id INTEGER PRIMARY KEY AUTOINCREMENT,
identica TEXT NOT NULL,
);
Use bind params
my $sth = $dbh->prepare($q);
$sth->bind_param(1, 00123, { TYPE => SQL_VARCHAR });
$sth->bind_param(2, $book_info->{book_title});
$sth->execute();
UPDATE:
Read about type affinity in SQLite. Because your column type is STRING (technically unsupported), it defaults to INTEGER affinity. You need to create your column as TEXT instead.
According to the docs, if the column type (affinity) is TEXT it should store it as a string; otherwise it will be a number.

How do I insert values from parallel arrays into a database using Perl's DBI module?

I need to insert values in database using Perl's DBI module. I have parsed a file to obtain these values and hence these values are present in an arrays, say #array1, #array2, #array3. I know how to insert one value at a time but not from an arrays.
I know insert one value at a time:
$dbh = DBI->connect("dbi:Sybase:server=$Srv;database=$Db", "$user", "$passwd") or die "could not connect to database";
$query= "INSERT INTO table1 (id, name, address) VALUES (DEFAULT, tom, Park_Road)";
$sth = $dbh->prepare($query) or die "could not prepare statement\n";
$sth-> execute or die "could not execute statement\n $command\n";
I am not sure if I have array1 containing ids, array2 containing names, and array3 containing address, how would I insert values.
Since you have parallel arrays, you could take advantange of execute_array:
my $sth = $dbh->prepare('INSERT INTO table1 (id, name, address) VALUES (?, ?, ?)');
my $num_tuples_executed = $sth->execute_array(
{ ArrayTupleStatus => \my #tuple_status },
\#ids,
\#names,
\#addresses,
);
Please note that this is a truncated (and slightly modified) example from the documentation. You'll definitely want to check out the rest of it if you decide to use this function.
Use placeholders.
Update: I just realized you have parallel arrays. That is really not a good way of working with data items that go together. With that caveat, you can use List::MoreUtils::each_array:
#!/usr/bin/perl
use strict; use warnings;
use DBI;
use List::MoreUtils qw( each_array );
my $dbh = DBI->connect(
"dbi:Sybase:server=$Srv;database=$Db",
$user, $passwd,
) or die sprintf 'Could not connect to database: %s', DBI->errstr;
my $sth = $dbh->prepare(
'INSERT INTO table1 (id, name, address) VALUES (?, ?, ?)'
) or die sprintf 'Could not prepare statement: %s', $dbh->errstr;
my #ids = qw( a b c);
my #names = qw( d e f );
my #addresses = qw( g h i);
my $it = each_array(#ids, #names, #address);
while ( my #data = $it->() ) {
$sth->execute( #data )
or die sprintf 'Could not execute statement: %s', $sth->errstr;
}
$dbh->commit
or die sprintf 'Could not commit updates: %s', $dbh->errstr;
$dbh->disconnect;
Note that the code is not tested.
You might also want to read the FAQ: What's wrong with always quoting "$vars"?.
Further, given that the only way you are handling error is by dying, you might want to consider specifying { RaiseError => 1 } in the connect call.
How could you not be sure what your arrays contain? Anyway the approach would be the iterate through one array and assuming the other arrays have corresponding values put those into the insert statement
Another way would be to use a hash as an intermediate storage area. IE:
my $hash = {};
foreach(#array1) {
$hash->{id} = $array1[$_];
$hash->{name} = $array2[$_];
$hash->{address} = $array3[$_];
}
foreach( keys %$hash ) {
$sql = "insert into table values(?,?,?)";
$sth = $dbh->prepare($sql) or die;
$sth->execute($hash->{id}, $hash->{name}, $hash->{address}) or die;
}
Though again this depends on the three arrays being synced up. However you could modify this to do value modifications or checks or greps in the other arrays within the first loop through array1 (ie: if your values in array2 and array3 are maybe stored as "NN-name" and "NN-address" where NN is the id from the first array and you need to find the corresponding values and remove the NN- with a s// regex). Depends on how your data is structured though.
Another note is to check out Class::DBI and see if it might provide a nicer and more object oriented way of getting your data in.

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.