views:

46

answers:

3

In my Perl script, I use DBD::Sybase (via DBI module) to connect to a SQL Server 2008. The base program as below runs without problem:

use DBI;

# assign values to $host, $usr, $pwd
my $dbh = DBI->connect("dbi:Sybase:$host", $usr, $pwd);
$dbh->do("BEGIN TRAN tr1");
my $update = $dbh->prepare("UPDATE mytable SET qty = ? where name = ?");
$update->execute(100, 'apple');
$dbh->do("END TRAN tr1");

however, if I insert one more prepare statement right before the existing prepare statement, to have the program look like:

...
my $insert = $dbh->prepare("INSERT INTO mytable (name, qty) VALUES (?, ?)");
my $update = $dbh->prepare("UPDATE mytable SET qty = ? where name = ?");
...

and the rest is all the same, then when I run it, I got:

DBD::Sybase::db do failed: Server message number=3902 severity=16 state=1 line=1 server=xxx text=The COMMIT TRANSACTION request has no corresponding BEGIN TRANSACTION.

So looks like the additional prepare statement somehow disrupted the entire transaction flow. I had been running the same code via the DBD::ODBC driver with no problem against a SQL SERVER 2005. (But my firm upgraded to 2008 and I had to use the DBD::Sybase to get around some other problems.)

Any help / suggestion on how to resolve this issue would be much appreciated. In particular, using a different db handle for the other prepare is not a desired solution since that will beat the purpose of having them in a single transaction.

UPDATE: Turns out if I execute at least once on the additional insert, then the program is again run fine. So looks like every prepared statement needs to be run under Sybase. But that isn't a requirement with ODBC and isn't a reasonable requirement in general. Anyway to get around it?

A: 

Turns out that DBI's prepare method is not quite portable across various database drivers as noted here. For the Sybase driver, it is most likely that prepare is not working as intended. One way to tell is that after running prepare, the variable $insert->{NUM_OF_FIELDS} is undefined.

To get around the problem, do one of the following:

1) do not prepare anything. Just dynamically construct the statement in text string and run $dbh->do($stmt), or

2) run finish on all outstanding statement handles (under that database handle) before running COMMIT TRAN. I personally prefer this way much better.

Zhang18
A: 

Don't dynamically create SQL, it is dangerous (sql injection).

You should be able to prepare multiple inserts/updates and your link to the DBI documentation does not say you cannot, it says some drivers may not be able to tell you much about a statement which is ONLY prepared.

I'd post a failing example with error to the dbi-users list for comment as the DBD::Sybase maintainer hangs out there (see dbi.perl.org).

bohica
I appreciate the comment. But so far without constructing statements on the fly, there is no known work around of my original issue, right?
Zhang18
What I'm saying is that I think what you were doing should work and so you should follow it up on the dbi-users list or contact the DBD::Sybase author.
bohica
A: 

You are learning perl AND Sybase basics and making several incorrect conclusions.

Forget about what it does under ODBC for a moment. ODBC most probably has AUTOCOMMIT turned on, and thus you have no transaction control whatsoever. (Why anyone would use ODBC when the DBD:: supports DB-Lib and CT-Lib is beyond me, but that's a separate story.)

Re: "So looks like every prepared statement needs to be run under Sybase."

Rawheiser is correct. What exactly do you expect to achieve by preparing a batch but performing a Do instead ? Where else do you expect to execute the batch prepared under Sybase, other than under Sybase?

Do vs prepare/execute are quite different. prepare/execute for Sybase works just fine in millions of programs. you just have to learn what it does, not what you think it should do. prepare let's you load a batch, a block of commands terminated by GO in the normal Sybase sense. Execute executes the prepared batch (supplies the GO and sends the batch to the server), and captures whatever is returned (according to whatever array/variables you have set).

Do is immediate, single command, with no prepare. A prepare+execute combined.

Performing only single-statement do's, and only dynamic SQL, simply because that's all that you could get to work, is very limiting and quite unnecessary.

You currently have:

Prepare:

UPDATE
Execute (100)
ExecuteImmediate(Do):
COMMIT TRAN

So of course, there is no BEGIN TRAN. (The first "do" executed, the BEGIN TRAN is gone)

I think what you want (intended originally) is this. Forget the 'do':

Prepare:

BEGIN TRAN
UPDATE
COMMIT TRAN

Execute (100)

Then change it to:

BEGIN TRAN
INSERT
UPDATE
COMMIT TRAN

Execute (100)

Your $update and $insert will confuse you (you're executing a multi-statement batch, right ?not a isolated single command in the middle of a prepare batch). If you get rid of them, and think in terms of $execute [whatever you have prepared in the batch], it might help you to understand the problem better.

Do not form conclusions until you have all the above working as intended.

And read up on BEGIN/COMMIT TRAN.

Last, What exactly is a "END TRAN" ? I do not think the code block you have posted is real.

PerformanceDBA