Skip Menu |

This queue is for tickets about the DBD-PgPP CPAN distribution.

Report information
The Basics
Id: 15894
Status: resolved
Priority: 0/
Queue: DBD-PgPP

People
Owner: Nobody in particular
Requestors: carbon [...] pobox.com
Cc:
AdminCc:

Bug Information
Severity: Wishlist
Broken in: (no value)
Fixed in: 0.06



Subject: last_insert_id not implemented
DBI provides the last_insert_id method since v1.38 (current DBI version is 1.48) and most DB drivers already handle it properly, including DBD::Pg. I have found last_insert_id to be required in many codebases that have an adequate level of database independence.
From: crew [...] cs.stanford.edu
On Thu Nov 17 09:11:24 2005, guest wrote: Show quoted text
> DBI provides the last_insert_id method since v1.38 (current DBI > version is 1.48) and most DB drivers already handle it properly, > including DBD::Pg. I have found last_insert_id to be required in > many codebases that have an adequate level of database > independence.
Here is a patch to PgPP 0.05 that implements last_insert_id. It is not yet well tested but (1) it appears to work in my world (perl 5.8.8, DBI 1.50, Postgres 7.4.7) and (2) the code is pretty much verbatim from DBD::Pg except for one or two DBD::Pg-specific lines that had to be changed (I'm somewhat dismayed that DBD::Pg appears to assume that all connections via that driver will be to the same server -- my version, at least, won't have that problem) so I have at least some confidence it will work everywhere that DBD::Pg does. I'd feel a bit better about it if someone could test it on a pre 7.3 server (one that doesn't grok schema); unfortunately I don't have one of those anymore.
--- DBD/PgPP.pm.~1~ Sun Apr 16 01:07:34 2006 +++ DBD/PgPP.pm Sun Jul 23 02:47:20 2006 @@ -325,6 +325,138 @@ } +#--- lifted from from DBD::Pg.pm + +sub _server_version +{ + my ($dbh) = @_; + unless (defined $dbh->{pgpp_server_version}) { + my $sth = $dbh->prepare('SELECT version()'); + my $count = $sth->execute(); + if (!defined $count or $count eq '0E0') { + $sth->finish(); + $dbh->STORE('pgpp_server_version', 0); + } + elsif ($sth->fetchall_arrayref()->[0][0] =~ /^PostgreSQL (\d+)\.(\d+)\.?(\d*)/) { + $dbh->STORE('pgpp_server_version', ($1 * 100 + $2) * 100 + ($3 || 0)); + } + } + return $dbh->{pgpp_server_version}; +} + +sub last_insert_id +{ + my ($dbh, $catalog, $schema, $table, $col, $attr) = @_; + + ## Our ultimate goal is to get a sequence + my ($sth, $count, $SQL, $sequence); + + ## Cache all of our table lookups? Default is yes + my $use_cache = 1; + + ## Catalog and col are not used + $schema = '' if ! defined $schema; + $table = '' if ! defined $table; + my $cachename = "$schema.$table"; + + if (defined $attr and length $attr) { + ## If not a hash, assume it is a sequence name + if (! ref $attr) { + $attr = {sequence => $attr}; + } + elsif (ref $attr ne 'HASH') { + return $dbh->set_err(1, "last_insert_id must be passed a hashref as the final argument"); + } + ## Named sequence overrides any table or schema settings + if (exists $attr->{sequence} and length $attr->{sequence}) { + $sequence = $attr->{sequence}; + } + if (exists $attr->{pg_cache}) { + $use_cache = $attr->{pg_cache}; + } + } + + if (! defined $sequence and exists $dbh->{pgpp_liicache}{$cachename} and $use_cache) { + $sequence = $dbh->{pgpp_liicache}{$cachename}; + } + elsif (! defined $sequence) { + ## At this point, we must have a valid table name + if (! length $table) { + return $dbh->set_err(1, "last_insert_id needs at least a sequence or table name"); + } + my @args = ($table); + + ## Only 7.3 and up can use schemas + my $pg_catalog; + if (_server_version($dbh) < 70300) { + $schema = ''; + $pg_catalog = ''; + } + else { + $pg_catalog = 'pg_catalog.'; + } + + ## Make sure the table in question exists and grab its oid + my ($schemajoin,$schemawhere) = ('',''); + if (length $schema) { + $schemajoin = "\n JOIN pg_catalog.pg_namespace n ON (n.oid = c.relnamespace)"; + $schemawhere = "\n AND n.nspname = ?"; + push @args, $schema; + } + $SQL = "SELECT c.oid FROM ${pg_catalog}pg_class c $schemajoin\n WHERE relname = ?$schemawhere"; + $sth = $dbh->prepare($SQL); + $count = $sth->execute(@args); + if (!defined $count or $count eq '0E0') { + $sth->finish(); + my $message = qq{Could not find the table "$table"}; + length $schema and $message .= qq{ in the schema "$schema"}; + return $dbh->set_err(1, $message); + } + my $oid = $sth->fetchall_arrayref()->[0][0]; + ## This table has a primary key. Is there a sequence associated with it via a unique, indexed column? + $SQL = "SELECT a.attname, i.indisprimary, substring(d.adsrc for 128) AS def\n". + "FROM ${pg_catalog}pg_index i, ${pg_catalog}pg_attribute a, ${pg_catalog}pg_attrdef d\n ". + "WHERE i.indrelid = $oid AND d.adrelid=a.attrelid AND d.adnum=a.attnum\n". + " AND a.attrelid=$oid AND i.indisunique IS TRUE\n". + " AND a.atthasdef IS TRUE AND i.indkey[0]=a.attnum\n". + " AND d.adsrc ~ '^nextval'"; + $sth = $dbh->prepare($SQL); + $count = $sth->execute(); + if (!defined $count or $count eq '0E0') { + $sth->finish(); + $dbh->set_err(1, qq{No suitable column found for last_insert_id of table "$table"}); + } + my $info = $sth->fetchall_arrayref(); + + ## We have at least one with a default value. See if we can determine sequences + my @def; + for (@$info) { + next unless $_->[2] =~ /^nextval\('([^']+)'::/o; + push @$_, $1; + push @def, $_; + } + if (!@def) { + $dbh->set_err(1, qq{No suitable column found for last_insert_id of table "$table"\n}); + } + ## Tiebreaker goes to the primary keys + if (@def > 1) { + my @pri = grep { $_->[1] } @def; + if (1 != @pri) { + $dbh->set_err(1, qq{No suitable column found for last_insert_id of table "$table"\n}); + } + @def = @pri; + } + $sequence = $def[0]->[3]; + ## Cache this information for subsequent calls + $dbh->{pgpp_liicache}{$cachename} = $sequence; + } + + $sth = $dbh->prepare("SELECT currval(?)"); + $sth->execute($sequence); + return $sth->fetchall_arrayref()->[0][0]; +} +#--- end of last_insert_id + package DBD::PgPP::st; $DBD::PgPP::st::imp_data_size = 0;
This feature is present in DBD::PgPP 0.06. Thanks very much to Roger Crew for the initial implementation. Apologies for the absurdly long delay. -- Aaron Crane