The 2002 Perl Advent Calendar
[about] | [archives] | [contact] | [home]

On the 3rd day of Advent my True Language brought to me..
DBD::SQLite

The trouble with relational databases is that that they introduce complications to your project. Suddenly the requirements for your script to function properly change from just requiring a working copy of Perl to requiring a database be set up for your application.

This is quite a sudden increase in requirements; It might require a user to go off and fiddle with some permissions, create tables, or otherwise configure their database, ensuring that they don't break anything else that runs on that database server. It requires the user to somehow configure your script with the usernames and passwords that are needed to access the database. In the worst case scenario, the user has to install a database from scratch just to run your application.

All this seems a bit much to ask if all your programs wants is a place to store some data during your quick log progressing run.

This is where DBD::SQLite shines. It's a relational database driver like many of the others, but it ships with the database itself. Installing the module (which isn't too big, the whole distribution is 277KB at the time of writing) installs the database program.

SQLite databases are simply files created in the filesystem. This may come to a shock to traditional database users who are used to their database server hiding their data away somewhere out of sight. This just having the databases in the filesystem can encourage you to quickly create databases as and when you need them and simple delete them (using the unlink command) when you're done with them.

   # use the standard DBI (database interface)
   use DBI;
   # get a handle on our database, which is simply the file
   # "foo.db" in our current directory
   my $dbh = DBI->connect('dbi:SQLite:foo.db');

Since you're often need a database for each run of your script, and you don't want concurrent runs of the program interfering with each other's database it often makes sense to create the database in a separate temp file for each run.

   use File::Temp qw( :POSIX );
   use DBI;
   my $dbh = DBI->connect('dbi:SQLite:' . tmpnam());

Anyway, once you have a database handle you can treat it exactly like you would a normal database. First up you create a table:

  # just do the SQL needed to create a table
  $dbh->do(q{
    CREATE TABLE modules (day        INTEGER PRIMARY KEY,
                          name       TEXT,
                          maintainer TEXT)
  });

Note that the type system with SQLite is a little odd compared to other databases. SQLite doesn't really have types so specifying something as TEXT or INT(10) really won't make much difference, SQLite will accept anything. This is considered a feature rather than a bug - the view being that your database shouldn't really care about the type of data being stored into it.

The type is important when doing comparisons though with SELECT conditions and their like. If you've defined something as TEXT then it is compared with a string comparison (like Perl's eq operator) and if it's created with a number data type it's compared like a number (like Perl's == operator.)

Finally you might notice the INTEGER PRIMARY KEY field. This is an autoinc field. Every time data is added to the database this column will automatically get a new number, starting at 1.

Enough, let's enter some data:

  my $sth = $dbh->prepare(q{
    INSERT INTO modules (name, maintainer) VALUES (?,?)
  });
  # run that insert, with the text to replace the ? with
  $sth->execute("URI::Find",     "ROSCH");
  $sth->execute("IO::AtomicFile","ERYQ");
  $sth->execute("DBD::SQLite",   "MSERGEANT");

Note we don't assign any value to the day column, so it gets assigned NULL. The database will automatically fill that in for us. If we want the value that the database filled in for us we can get at it like so:

  print $dbh->func('last_insert_rowid');

Right, so we've got some data in, now we're ready to search it, right? Well, yes. However, note that if we'd turned autocommit off (enabling the rollback - the undo - feature of the database and making it transactional) we'd want to commit the data at this point.

   # connect to the database with no username or password but
   # set autocommit off so we can use transactions
   my $dbh = DBI->connect('dbi:SQLite:' . tmpnam(), "", "",
                          { AutoCommit => 0 });
	
   # do all the inserts and stuff
   ...
   # finally commit it
   $dbh->commit();

If we'd wanted to undo it instead of commiting it we could have said

   $dbh->rollback();

So, onto the searching:

  # load the module for the ordinate function
  # to make '1' '1st' and '2' '2nd' etc.
  use Lingua::EN::Numbers::Ordinate;
  # find which day someone's modules are shown on
  my $sth2 = $dbh->prepare(q{
    SELECT day, name FROM modules WHERE maintainer = ?
  });
  # look up baud's modules
  $sth2->execute("MSERGEANT");
  # print out each result
  while(my ($day, $name) = $sth2->fetchrow)
  {
    print "$name will be shown on the ".ordinate($day)."\n";
  }

  • SQLite homepage
  • DBI