From 3162d5b47ae046bdcdd67ac836097d52deaaa334 Mon Sep 17 00:00:00 2001 From: Greg Nancarrow Date: Thu, 5 Sep 2019 22:31:37 +1000 Subject: [PATCH] fix: allow OUT parameter registration when using CallableStatement native CALL Currently, using CallableStatement, the PGJDBC driver will allow invocation of procedures using the native "call myproc(...)" syntax, provided the procedure only has IN parameters or no parameters. However, if the procedure has any INOUT parameters, then an attempt to invoke the CallableStatement's registerOutParameter() method, which is required in order to register the OUT part of the INOUT parameter, will fail with the following error: This statement does not declare an OUT parameter. Use { ?= call ... } to declare one. This fixes issue: https://github.com/pgjdbc/pgjdbc/issues/1545 --- .../main/java/org/postgresql/core/Parser.java | 15 +++++++++ .../jdbc3/Jdbc3CallableStatementTest.java | 33 +++++++++++++++++++ 2 files changed, 48 insertions(+) diff --git a/pgjdbc/src/main/java/org/postgresql/core/Parser.java b/pgjdbc/src/main/java/org/postgresql/core/Parser.java index 265cd114e2..c17bf12ec9 100644 --- a/pgjdbc/src/main/java/org/postgresql/core/Parser.java +++ b/pgjdbc/src/main/java/org/postgresql/core/Parser.java @@ -1040,6 +1040,21 @@ public static JdbcCallParseInfo modifyJdbcCall(String jdbcSql, boolean stdString if (i == len && !syntaxError) { if (state == 1) { // Not an escaped syntax. + + // Detect PostgreSQL native CALL. + // (OUT parameter registration, needed for stored procedures with INOUT arguments, will fail without this) + i = 0; + while (i < len && Character.isWhitespace(jdbcSql.charAt(i))) { + i++; // skip any preceding whitespace + } + if (i < len - 5) { // 5 == length of "call" + 1 whitespace + //Check for CALL followed by whitespace + char ch = jdbcSql.charAt(i); + if ((ch == 'c' || ch == 'C') && jdbcSql.substring(i, i + 4).equalsIgnoreCase("call") + && Character.isWhitespace(jdbcSql.charAt(i + 4))) { + isFunction = true; + } + } return new JdbcCallParseInfo(sql, isFunction); } if (state != 8) { diff --git a/pgjdbc/src/test/java/org/postgresql/test/jdbc3/Jdbc3CallableStatementTest.java b/pgjdbc/src/test/java/org/postgresql/test/jdbc3/Jdbc3CallableStatementTest.java index 5764303493..f54e1a5826 100644 --- a/pgjdbc/src/test/java/org/postgresql/test/jdbc3/Jdbc3CallableStatementTest.java +++ b/pgjdbc/src/test/java/org/postgresql/test/jdbc3/Jdbc3CallableStatementTest.java @@ -11,6 +11,7 @@ import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; +import org.postgresql.core.ServerVersion; import org.postgresql.test.TestUtil; import org.postgresql.test.jdbc2.BaseTest4; import org.postgresql.util.PSQLState; @@ -84,6 +85,12 @@ public void setUp() throws Exception { + "end;'" + "LANGUAGE plpgsql VOLATILE;" ); + if (TestUtil.haveMinimumServerVersion(con, ServerVersion.v11)) { + stmt.execute( + "CREATE OR REPLACE PROCEDURE inonlyprocedure(a IN int) AS 'BEGIN NULL; END;' LANGUAGE plpgsql"); + stmt.execute( + "CREATE OR REPLACE PROCEDURE inoutprocedure(a INOUT int) AS 'BEGIN a := a + a; END;' LANGUAGE plpgsql"); + } } @Override @@ -97,6 +104,10 @@ public void tearDown() throws SQLException { stmt.execute("drop function myif(a INOUT int, b IN int)"); stmt.execute("drop function mynoparams()"); stmt.execute("drop function mynoparamsproc()"); + if (TestUtil.haveMinimumServerVersion(con, ServerVersion.v11)) { + stmt.execute("drop procedure inonlyprocedure(a IN int)"); + stmt.execute("drop procedure inoutprocedure(a INOUT int)"); + } stmt.close(); super.tearDown(); } @@ -1034,4 +1045,26 @@ public void testProcedureNoParametersWithoutParentheses() throws SQLException { TestUtil.closeQuietly(cs); } + @Test + public void testProcedureInOnlyNativeCall() throws SQLException { + assumeMinimumServerVersion(ServerVersion.v11); + assumeCallableStatementsSupported(); + CallableStatement cs = con.prepareCall("call inonlyprocedure(?)"); + cs.setInt(1, 5); + cs.execute(); + TestUtil.closeQuietly(cs); + } + + @Test + public void testProcedureInOutNativeCall() throws SQLException { + assumeMinimumServerVersion(ServerVersion.v11); + assumeCallableStatementsSupported(); + // inoutprocedure(a INOUT int) returns a*2 via the INOUT parameter + CallableStatement cs = con.prepareCall("call inoutprocedure(?)"); + cs.setInt(1, 5); + cs.registerOutParameter(1, Types.INTEGER); + cs.execute(); + assertEquals("call inoutprocedure(?) should return 10 (when input param = 5) via the INOUT parameter, but did not.", 10, cs.getInt(1)); + TestUtil.closeQuietly(cs); + } }