5

I'm going nuts about how the Sybase JDBC driver handles stored procedures with mixed IN and OUT parameters. Check out this simple stored procedure:

CREATE OR REPLACE PROCEDURE p (IN i1 INT, OUT o1 INT, IN i2 INT, OUT o2 INT)
BEGIN
    set o1 = i1;
    set o2 = i2;
END

And here's how I'd call it with JDBC:

CallableStatement c = connection.prepareCall("{ call dba.p(?, ?, ?, ?) }");
c.setInt(1, 1);
c.setInt(3, 2);
c.registerOutParameter(2, Types.INTEGER);
c.registerOutParameter(4, Types.INTEGER);
c.execute();
System.out.println(c.getObject(2));
System.out.println(c.getObject(4));

But this results in

1
null

What's going on?? Is that a really wicked bug in the JDBC driver or am I completely missing something? By trial and error, I found this to be a way how it works:

c.setInt(1, 1);
c.setInt(2, 2);
c.registerOutParameter(3, Types.INTEGER);
c.registerOutParameter(4, Types.INTEGER);
c.execute();
System.out.println(c.getObject(3));
System.out.println(c.getObject(4));

Now the result is

1
2

Does the JDBC driver secretly re-order IN and OUT parameters??

I'm using SQL Anywhere 12 and jconn3.jar

Danny Beckett
  • 20,529
  • 24
  • 107
  • 134
Lukas Eder
  • 211,314
  • 129
  • 689
  • 1,509

3 Answers3

7

Looks like a bug in the driver.

I suspect the buggy driver expects parameters to be passed/registered in the order (i.e. 1,2,3,4). When you do registerOut(2), the statement apparently forgets you did set(3) :-)

Or, may be, all OUT should be done after all IN. Then again, this is a bug in the driver.

UPDATE

Wait, you didn't change the procedure for the second variant? That result doesn't make any sense. Unless, as you said, driver does reordering. Which is unusual, to say the least.

UPDATE 2

I have decompiled the driver. It does some pretty funny games around out parameters, and with all this joggling I feel they have a fair potential for a bug there, but so far I do not see it plainly.

The only funny thing I noticed is that apparently if the parameter at position n is not out, the driver will scan parameters forward until it will find the value; if value is not found, it goes to the next row:

  s.registerOutParameter(5,Types.INT);
  ...
  // no out value at position 4, it will go to 5 and get the value
  rs.getInteger(4);

UPDATE 3

It may be interesting to see the output of all 4 parameters in example 1, i.e.:

CallableStatement c = connection.prepareCall("{ call dba.p(?, ?, ?, ?) }");
c.setInt(1, 1);
c.setInt(3, 2);
c.registerOutParameter(2, Types.INTEGER);
c.registerOutParameter(4, Types.INTEGER);
c.execute();
System.out.println(c.getObject(1));
System.out.println(c.getObject(2));
System.out.println(c.getObject(3));
System.out.println(c.getObject(4));
Vladimir Dyuzhev
  • 18,130
  • 10
  • 48
  • 62
  • Yes. It's the same procedure, just the call is different. I also suspect that this driver does parameter reordering. But since I couldn't imagine such a horrible flaw in a major RDBMS's driver, I thought maybe I got something wrong... – Lukas Eder May 10 '11 at 07:30
  • I wanted to decompile the driver, but could not find it with google, only new versions. Do you have a link pointing to this buggy driver? – jabal May 10 '11 at 08:56
  • You can find it here: http://www.sybase.com/detail?id=1041013. jconn3.jar is contained in the jconnect 6 bundle – Lukas Eder May 10 '11 at 09:39
  • I guess you were not fast enough for the bounty... sorry. I don't know about this bounty business. I raised the issue on meta, too, now: http://meta.stackexchange.com/questions/84785/preventing-automatic-bounty/90625#90625 – Lukas Eder May 10 '11 at 11:47
  • 1
    care not about the bounty :-D the issue at hand is what is exciting! – Vladimir Dyuzhev May 10 '11 at 14:07
  • OMG, you're nuts! OK, I'll have another look into it. Or I'll just file a bug report to Sybase... ;-) – Lukas Eder May 12 '11 at 17:02
4

I tried this with Oracle 9.2 and it works as expected.. I think this problem is related to your JDBC driver, not to JDBC itself.

    public static void main(String[] args) throws Exception {

    Connection connection = getConnection();

    CallableStatement c = connection.prepareCall("{ call p(?, ?, ?, ?) }");
    c.setInt(1, 1);
    c.setInt(3, 2);
    c.registerOutParameter(2, Types.INTEGER);
    c.registerOutParameter(4, Types.INTEGER);
    c.execute();
    System.out.println(c.getObject(2));
    System.out.println(c.getObject(4));

}

Output:

Connected to database
1
2
jabal
  • 11,987
  • 12
  • 51
  • 99
  • The second variation you mentioned in your question results in null, null with my Oracle 9.2 JDBC driver. – jabal May 09 '11 at 11:26
  • 3
    You wrote in the question: "Is that a really wicked bug in the JDBC driver or am I completely missing something?" This is why I tried it with Oracle to ensure this is only in your JDBC driver. Based on this I could write: yes Lukas, this is possibly a bug in your sybase jdbc driver, as works well with Oracle. Anyways: thank you for voting down the only one who spent any time on investigating this issue.. :-( – jabal May 09 '11 at 11:47
  • Ah, look. You got half the bounty, automatically... ;-) – Lukas Eder May 10 '11 at 11:24
  • thanks :-) it is funny how these valueless reputation points can motivate bored people – jabal May 10 '11 at 11:30
1
Below sybase stored procedure works for me
public String IDGEN(String tableName,  Connection con , LOG _log)
    { 
        String strReturnValue = "-1"; 
        try
        {
             CallableStatement cs = con.prepareCall("{call usp_NEWPK_string_v6(?,?)}");
             cs.registerOutParameter(2,java.sql.Types.NUMERIC);
             cs.setString(1,tableName);
             cs.setInt(2,0);
             cs.execute();
             strReturnValue = cs.getLong(2) + "";

        } catch (Exception ex) {
            _log.logInstance(" ERROR: [IDGEN] " + ex.getMessage()); 
        }
        return strReturnValue;
    } 
// abobjects.com

create proc usp_NEWPK_string_v6 (@tablename  varchar(32) , @ID  numeric output
          )
as
declare @newValue     numeric
declare @oldValue  numeric
select @oldValue=IDGEN_ID from DB_IDGEN where IDGEN_TableName = @tablename
select @newValue=@oldValue + 1 from DB
 _IDGEN where IDGEN_TableName = @tablename
update DB_IDGEN set IDGEN_ID = @newValue where IDGEN_TableName = @tablename
select @ID  = @oldValue
return