Beefy Boxes and Bandwidth Generously Provided by pair Networks
No such thing as a small change
 
PerlMonks  

Class::DBI && Sybase

by kal (Hermit)
on Apr 03, 2003 at 13:39 UTC ( [id://247751]=perlquestion: print w/replies, xml ) Need Help??

kal has asked for the wisdom of the Perl Monks concerning the following question:

Hi guys.

I'm having wonderful fun implementing 'latest app' in Class::DBI and Class::DBI::Pager (I've put off learning these modules for a while, and now regret it ;). However, I do have a problem - I'm interfacing with a Sybase database setup with auto incrementing (IDENTITY) primary keys, and Class::DBI doesn't like them.

Looking at the code, there is already a bit of a hack in there for this:

my $id = $dbh->{mysql_insertid} # mysql || eval { $dbh->func('last_insert_rowid') }; # SQLite $self->_croak("Can't get last insert id") unless defined $id;

Now, sadly, I can't just hook in there, because on Sybase I need to execute 'SELECT @@identity' immediately after the INSERT, there's no convenient variable hanging around for me to poach. And herein lies the issue.

I've overridden _auto_increment_value() to do this extra query, but all I get back is '0'. It seems that by the time I get to execute my query, Sybase has forgotton what @@identity is. I'm assuming this is to do with the database handle - I've tried turning off autoupdate/autocommit, but that doesn't make any different. I guess I'm just not getting their in time. I've even tried overriding _insert_row(), still no go. Perhaps the answer lies in Ima::DBI somewhere.

Has anyone got this combination to work in this style? I don't personally want to run without auto-incrementing keys, because that would be work. However, I do need to find some way of getting back the id of the recently created object, otherwise Class::DBI isn't going to speak to me :/ Can anyone offer any hints?

Replies are listed 'Best First'.
Re: Class::DBI && Sybase
by IlyaM (Parson) on Apr 03, 2003 at 14:42 UTC
    From DBD::Sybase POD:

    It is not possible to retrieve the last IDENTITY value after an insert done with ?-style placeholders. This is a Sybase limitation/bug, not a DBD::Sybase problem.
    I guess Class::DBI uses placeholders internally.

    --
    Ilya Martynov, ilya@iponweb.net
    CTO IPonWEB (UK) Ltd
    Quality Perl Programming and Unix Support UK managed @ offshore prices - http://www.iponweb.net
    Personal website - http://martynov.org

      Blast, I think you've cracked it :( (Thanks to zengargoyle, incidentally - I had already thought of that solution, and it didn't work, but it was a good idea ;)

      Looking back at my previous code that pulls back @@identity (there's not all that much), I've realised that one consistent feature is that I don't use placeholders, so I think I've probably discovered this problem in the past and either got around it subconciously, or just forgotton the doc. I'm fairly sure I hadn't read that before, though.

      I guess the thing to do will be to re-write _insert_row() completely so that it generates the SQL without a placeholder syntax, which won't be too bad, but has implications in terms of inheritance that I haven't quite got my brane around yet. I think it should be okay, but it probably needs warning signs on it :/ Thanks very much though!

      (This also explains neatly why Class::DBI wouldn't support this for Sybase, even though the docs suggest it works - I might send an email to the author to make it clearer, because I wouldn't have thought to look to DBD::Sybase for the 'bug')

        Note that this was discussed recently on the dbi-dev mailing list (Tim wants to add a method to DBI to get the "last id" for auto-incremented values).

        That's when I found some interesting properties of DBD::Sybase that I didn't think would work...

        First, if you use placeholders then you can't get at the @@identity value, as is documented.

        However, if AutoCommit is off you can do the following (all error handling ommitted!):

        $sth = $dbh->prepare("insert foo(...) values(?, ?, ...)"); # insert first row: $sth->execute('foo', 'bar', ...); # fetch the id value (assuming "foo_id" is an identity column) $sth2 = $dbh->prepare("select max(foo_id) from foo"); $sth2->execute; while($data = $sth2->fetch) { $last_id = $data->[0]; } # insert second row $sth->execute('baz', ...); # fetch the id value $sth2 = $dbh->prepare("select max(foo_id) from foo"); $sth2->execute; while($data = $sth2->fetch) { $last_id = $data->[0]; } # etc.
        This surprised me greatly, to say the least, because I was sure that Sybase wouldn't let me execute the second prepare() while the original $sth was still active.

        You need to run this in AutoCommit off mode to make sure that your select max(id) statement will get your last id, not some other processes...

        Note that there are locking issues with this technique, as your process holds locks on the table until you execute the commit, which may be a problem for a high volume site.

        Michael

        i'm sorry my right before bedtime half-idea didn't work. FWIW, Class::DBI doesn't work with DBD::CSV either. DBD::CSV doesn't like to have the table id prepended to the column spec in queries (SELECT table.col FROM table). i'm now the proud owner of a Class::DBI subclass that loads a DB via DBD::CSV into a DBD::SQLite DB so the regular Class::DBI methods work, then on DESTROY the SQLite DB is copied back over the DBD::CSV DB so i can have my simple flat file format cake and eat it with Class::DBI.

Re: Class::DBI && Sybase
by DapperDan (Pilgrim) on Apr 03, 2003 at 14:00 UTC
    Maybe you should check out that your $dbh is still kosher by the time you get to running this identity query. Has it been reset or anything?

    With DBD::Pg and PostgreSQL, I do the following:

    # some standard DBI code doing an INSERT here.. my $dbh = ...; my $sth = $dbh->prepare('...'); $sth->execute(@values); my $seq = 'SOME_SEQUENCE_NAME'; my $sth = $dbh->prepare("SELECT currval('$seq')"); $sth->execute() or die $dbh->errstr; if (my @row = $sth->fetchrow_array) { return $row[0]; }

    The reason this works (I think) is that I am using the same $dbh as for an INSERT statement earlier in the code. That and PostgreSQL's way of doing this is better than TSQL's. I hated @@identity when I used to work with SQL Server.

    I realise the difference here is that in Sybase (and MS SQL Server) TSQL you have to use @@identity, rather than just SELECT on a sequence. I hope this gives you some insight all the same. Maybe you should try and do this in 'raw DBI' first, and then use that to patch Class::DBI. If you get something that works please post it on this thread; I don't have access to a Sybase instance but I would still be interested.

    That line in Class::DBI looks like it could use a polymorphic get_id method. I haven't used Class::DBI (yet) though so I won't comment further.

Re: Class::DBI && Sybase
by zengargoyle (Deacon) on Apr 03, 2003 at 14:30 UTC

    try this

    package MyDB; use base 'Class::DBI'; __PACKAGE__->set_db(...); # other table junk setup __PACKAGE__->set_sql('getlastid', 'SELECT @@identity',...); # see pe +rldoc Ima::DBI ... 1; # then where you need the last id you can try my ($id) = MyDBI->sql_getlastid()->execute(); # or something close (again see Ima::DBI)

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://247751]
Approved by robartes
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others admiring the Monastery: (3)
As of 2024-04-19 22:10 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found