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

On the 10th day of Advent my True Language brought to me..
Data::UUID

We often need to create a unique value to index something by - a unique filename, primary key in a database, user id, or url. The situations we need these in are varied; We might want to write to different temp files so our scripts don't interfere with each other. We might want to hand tickets out to users and make sure our tickets are can't be confused with one another no matter where the user got that ticket from. We might want to store rows in a table en mass without having to rely on an auto-incrementing key.

I've seen many schemes to create unique numbers. The simplest solution of starting with one and working your way up to two, then three, then four, and so on is effective but can be unreliable if you don't have one process doing the allocation (as you'll end up with a race condition where while program's counting then allocating a number, another program is doing the same and they both get the same number.) Sometimes it's impossible to have a central program to do the allocation (like with normally offline systems half way round the world) - and sometimes it's just too much hassle (who wants to write a service just to serve webpages?)

Data::UUID will give you a globally unique identifier without needing a central server. It'll give you a thirty six character string based on the time it's called - measured very, very accurately, and something that it can determine that is unique to your computer (like the mac address in your network card.) And it'll guarantee that at no other time will that string anywhere in the world be issued again.

Creating and using Data::UUID strings is simple.

   use Data::UUID;
   my $uf = Data::UUID->new();  # creates a factory;
   print $uf->create_str(), "\n" for 1..10;

This (on and only on my laptop and only at this time and no other time) prints:

  DA4BBCAA-47AF-11D9-B274-8E5F89426015
  DA4C28A2-47AF-11D9-B274-8E5F89426015
  DA4C5836-47AF-11D9-B274-8E5F89426015
  DA4C8B8A-47AF-11D9-B274-8E5F89426015
  DA4CB628-47AF-11D9-B274-8E5F89426015
  DA4CDE32-47AF-11D9-B274-8E5F89426015
  DA4D3972-47AF-11D9-B274-8E5F89426015
  DA4D810C-47AF-11D9-B274-8E5F89426015
  DA4DB096-47AF-11D9-B274-8E5F89426015
  DA4DD990-47AF-11D9-B274-8E5F89426015

You'll note that quite a lot of the string is the same for each instance. That's because for the most part a large part of the data hasn't changed. Most of the time is still the same (it's just the ticks and seconds that are changing) and the seed that's unique to the machine doesn't change. If I run it again about an hour later we see that the 47AF in the second column has changed into a 47BA as a little time has passed, but the rest stays the same.

  C604815E-47BA-11D9-B274-8E5F89426015
  C604F2C4-47BA-11D9-B274-8E5F89426015
  C6052460-47BA-11D9-B274-8E5F89426015
  C60550F2-47BA-11D9-B274-8E5F89426015
  C605776C-47BA-11D9-B274-8E5F89426015
  C605A8D6-47BA-11D9-B274-8E5F89426015
  C605D3B0-47BA-11D9-B274-8E5F89426015
  C606006A-47BA-11D9-B274-8E5F89426015
  C6062748-47BA-11D9-B274-8E5F89426015
  C6064DE0-47BA-11D9-B274-8E5F89426015

If we run the same code on a separate computer quickly afterwards we notice that the start of the string is the same (apart from it's progressed on a slight time in the time it took us to change windows) but the end of the string is radically different.

  CBEC90C0-47BA-11D9-9C9F-AE87831498F6
  CBECAEA2-47BA-11D9-9C9F-AE87831498F6
  CBECB8B6-47BA-11D9-9C9F-AE87831498F6
  CBECC0CC-47BA-11D9-9C9F-AE87831498F6
  CBECC810-47BA-11D9-9C9F-AE87831498F6
  CBECD044-47BA-11D9-9C9F-AE87831498F6
  CBECD756-47BA-11D9-9C9F-AE87831498F6
  CBECDE72-47BA-11D9-9C9F-AE87831498F6
  CBECE58E-47BA-11D9-9C9F-AE87831498F6
  CBECECA0-47BA-11D9-9C9F-AE87831498F6

An example in use:

Imagine we have a CGI script that creates a table of data from some constantly changing source. In addition to displaying this data on the web we want to offer this data as an Excel file for download. The thing we need to remember is that this data keeps changing - so the link at the bottom of the page can't be a link to a separate CGI that goes and fetches the data source again, it has to be a link to a excel spreadsheet we create when we create the webpage.

In order to allow many people to use the website at the same time we need a unique filename for each excel document to prevent new users overwriting the excel sheets that were previously created. And this is where Data::UUID comes in.

  #!/usr/bin/perl
  # turn on perl's safety features
  use strict;
  use warnings;
  # load the modules we need
  use HTML::Entities;
  use Data::UUID;
  use Spreadsheet::WriteExcel;
  # get the data for now.  This data is constantly changing
  # so the results will be different each time it's called.
  use Fake::Datasource;
  my @tables = Fake::Datasource->tables();
  # create a unique filename
  my $filename = Data::UUID->new->create_str . ".xls";

We now need to create the HTML. We loop though the array of tables, and the array of rows contained within and through each value. At the end we print a link to the Excel file we're going to create.

  print "<html><body>";
  # each table...
  foreach my $table (@tables)  
  {
    print '<table border="1">';
    # ...has many rows which contain columns
    foreach my $row (@{ $table })
    {
      print "<tr>";
      foreach my $col (@{ $row })
       { print "<td>".encode_entities($col)."</td>" }
      print "</tr>";
    }
    print "</table>";
  }
  print qq{<a href="/files/$filename">Download as Excel</a>};
  print "</body></html>";

Now all that's left to do is do the same thing to create the Excel file:

  # create a new excel file
  my $workbook = Spreadsheet::WriteExcel->new("files/$filename");
  # for each table add a sheet...
  foreach my $table (@tables)
  {
    my $sheet = $workbook->add_worksheet();
    # ...and write each row in that table to the sheet
    my $row_index = 0;
    foreach my $row (@{ $table })
     { $sheet->write_row($row_index++, 0, $row); }
  }

And we're done.

  • The Nework UUID and GUID draft
  • Tie::DataUUID