diff --git a/README.md b/README.md index 4fcf6a0fba..f2b9a15993 100644 --- a/README.md +++ b/README.md @@ -151,6 +151,7 @@ In addition to the standard connection parameters the driver supports a number o | cleanupSavepoints | Boolean | false | In Autosave mode the driver sets a SAVEPOINT for every query. It is possible to exhaust the server shared buffers. Setting this to true will release each SAVEPOINT at the cost of an additional round trip. | | preferQueryMode | String | extended | Specifies which mode is used to execute queries to database, possible values: extended, extendedForPrepared, extendedCacheEverything, simple | | reWriteBatchedInserts | Boolean | false | Enable optimization to rewrite and collapse compatible INSERT statements that are batched. | +| escapeSyntaxCallMode | String | select | Specifies how JDBC escape call syntax is transformed into underlying SQL (CALL/SELECT), for invoking procedures or functions (requires server version >= 11), possible values: select, callIfNoReturn, call | ## Contributing For information on how to contribute to the project see the [Contributing Guidelines](CONTRIBUTING.md) diff --git a/docs/documentation/head/connect.md b/docs/documentation/head/connect.md index e6df9ab2c5..4d16303096 100644 --- a/docs/documentation/head/connect.md +++ b/docs/documentation/head/connect.md @@ -472,6 +472,16 @@ Connection conn = DriverManager.getConnection(url); for logical replication from that database.

Parameter should be use together with `assumeMinServerVersion` with parameter >= 9.4 (backend >= 9.4)

+* **escapeSyntaxCallMode** = String + + Specifies how the driver transforms JDBC escape call syntax into underlying SQL, for invoking procedures or functions. + In `escapeSyntaxCallMode=select` mode (the default), the driver always uses a SELECT statement (allowing function invocation only). + In `escapeSyntaxCallMode=callIfNoReturn` mode, the driver uses a CALL statement (allowing procedure invocation) if there is no + return parameter specified, otherwise the driver uses a SELECT statement. + In `escapeSyntaxCallMode=call` mode, the driver always uses a CALL statement (allowing procedure invocation only). + + The default is `select` + ## Unix sockets diff --git a/pgjdbc/src/main/java/org/postgresql/PGProperty.java b/pgjdbc/src/main/java/org/postgresql/PGProperty.java index 87705c3adb..42729c9584 100644 --- a/pgjdbc/src/main/java/org/postgresql/PGProperty.java +++ b/pgjdbc/src/main/java/org/postgresql/PGProperty.java @@ -446,6 +446,19 @@ public enum PGProperty { + "from that database. " + "(backend >= 9.4)"), + /** + * Specifies how the driver transforms JDBC escape call syntax into underlying SQL, for invoking procedures or functions. (backend >= 11) + * In {@code escapeSyntaxCallMode=select} mode (the default), the driver always uses a SELECT statement (allowing function invocation only). + * In {@code escapeSyntaxCallMode=callIfNoReturn} mode, the driver uses a CALL statement (allowing procedure invocation) if there is no return parameter specified, otherwise the driver uses a SELECT statement. + * In {@code escapeSyntaxCallMode=call} mode, the driver always uses a CALL statement (allowing procedure invocation only). + */ + ESCAPE_SYNTAX_CALL_MODE("escapeSyntaxCallMode", "select", + "Specifies how the driver transforms JDBC escape call syntax into underlying SQL, for invoking procedures or functions. (backend >= 11)" + + "In escapeSyntaxCallMode=select mode (the default), the driver always uses a SELECT statement (allowing function invocation only)." + + "In escapeSyntaxCallMode=callIfNoReturn mode, the driver uses a CALL statement (allowing procedure invocation) if there is no return parameter specified, otherwise the driver uses a SELECT statement." + + "In escapeSyntaxCallMode=call mode, the driver always uses a CALL statement (allowing procedure invocation only).", + false, "select", "callIfNoReturn", "call"), + /** * Connection parameter to control behavior when * {@link Connection#setReadOnly(boolean)} is set to {@code true}. diff --git a/pgjdbc/src/main/java/org/postgresql/core/CachedQueryCreateAction.java b/pgjdbc/src/main/java/org/postgresql/core/CachedQueryCreateAction.java index df74aa2b62..d5ad5c7031 100644 --- a/pgjdbc/src/main/java/org/postgresql/core/CachedQueryCreateAction.java +++ b/pgjdbc/src/main/java/org/postgresql/core/CachedQueryCreateAction.java @@ -44,7 +44,7 @@ public CachedQuery create(Object key) throws SQLException { if (key instanceof CallableQueryKey) { JdbcCallParseInfo callInfo = Parser.modifyJdbcCall(parsedSql, queryExecutor.getStandardConformingStrings(), - queryExecutor.getServerVersionNum(), queryExecutor.getProtocolVersion()); + queryExecutor.getServerVersionNum(), queryExecutor.getProtocolVersion(), queryExecutor.getEscapeSyntaxCallMode()); parsedSql = callInfo.getSql(); isFunction = callInfo.isFunction(); } else { diff --git a/pgjdbc/src/main/java/org/postgresql/core/JdbcCallParseInfo.java b/pgjdbc/src/main/java/org/postgresql/core/JdbcCallParseInfo.java index db9f84230b..d7f70284ed 100644 --- a/pgjdbc/src/main/java/org/postgresql/core/JdbcCallParseInfo.java +++ b/pgjdbc/src/main/java/org/postgresql/core/JdbcCallParseInfo.java @@ -6,7 +6,7 @@ package org.postgresql.core; /** - * Contains parse flags from {@link Parser#modifyJdbcCall(String, boolean, int, int)}. + * Contains parse flags from {@link Parser#modifyJdbcCall(String, boolean, int, int, EscapeSyntaxCallMode)}. */ public class JdbcCallParseInfo { private final String sql; diff --git a/pgjdbc/src/main/java/org/postgresql/core/Parser.java b/pgjdbc/src/main/java/org/postgresql/core/Parser.java index 265cd114e2..a05950e77d 100644 --- a/pgjdbc/src/main/java/org/postgresql/core/Parser.java +++ b/pgjdbc/src/main/java/org/postgresql/core/Parser.java @@ -5,6 +5,7 @@ package org.postgresql.core; +import org.postgresql.jdbc.EscapeSyntaxCallMode; import org.postgresql.jdbc.EscapedFunctions2; import org.postgresql.util.GT; import org.postgresql.util.PSQLException; @@ -894,15 +895,16 @@ private static boolean subArraysEqual(final char[] arr, * [?,..])] }} into the PostgreSQL format which is {@code select (?, [?, ...]) as * result} or {@code select * from (?, [?, ...]) as result} (7.3) * - * @param jdbcSql sql text with JDBC escapes - * @param stdStrings if backslash in single quotes should be regular character or escape one - * @param serverVersion server version - * @param protocolVersion protocol version + * @param jdbcSql sql text with JDBC escapes + * @param stdStrings if backslash in single quotes should be regular character or escape one + * @param serverVersion server version + * @param protocolVersion protocol version + * @param escapeSyntaxCallMode mode specifying whether JDBC escape call syntax is transformed into a CALL/SELECT statement * @return SQL in appropriate for given server format * @throws SQLException if given SQL is malformed */ public static JdbcCallParseInfo modifyJdbcCall(String jdbcSql, boolean stdStrings, - int serverVersion, int protocolVersion) throws SQLException { + int serverVersion, int protocolVersion, EscapeSyntaxCallMode escapeSyntaxCallMode) throws SQLException { // Mini-parser for JDBC function-call syntax (only) // TODO: Merge with escape processing (and parameter parsing?) so we only parse each query once. // RE: frequently used statements are cached (see {@link org.postgresql.jdbc.PgConnection#borrowQuery}), so this "merge" is not that important. @@ -1053,8 +1055,16 @@ public static JdbcCallParseInfo modifyJdbcCall(String jdbcSql, boolean stdString PSQLState.STATEMENT_NOT_ALLOWED_IN_FUNCTION_CALL); } - String prefix = "select * from "; - String suffix = " as result"; + String prefix; + String suffix; + if (escapeSyntaxCallMode == EscapeSyntaxCallMode.SELECT || serverVersion < 110000 + || (outParamBeforeFunc && escapeSyntaxCallMode == EscapeSyntaxCallMode.CALL_IF_NO_RETURN)) { + prefix = "select * from "; + suffix = " as result"; + } else { + prefix = "call "; + suffix = ""; + } String s = jdbcSql.substring(startIndex, endIndex); int prefixLength = prefix.length(); @@ -1093,7 +1103,11 @@ public static JdbcCallParseInfo modifyJdbcCall(String jdbcSql, boolean stdString } } - sql = sb.append(suffix).toString(); + if (!suffix.isEmpty()) { + sql = sb.append(suffix).toString(); + } else { + sql = sb.toString(); + } return new JdbcCallParseInfo(sql, isFunction); } diff --git a/pgjdbc/src/main/java/org/postgresql/core/QueryExecutor.java b/pgjdbc/src/main/java/org/postgresql/core/QueryExecutor.java index a2d2382353..b5c97244dc 100644 --- a/pgjdbc/src/main/java/org/postgresql/core/QueryExecutor.java +++ b/pgjdbc/src/main/java/org/postgresql/core/QueryExecutor.java @@ -11,6 +11,7 @@ import org.postgresql.core.v3.TypeTransferModeRegistry; import org.postgresql.jdbc.AutoSave; import org.postgresql.jdbc.BatchResultHandler; +import org.postgresql.jdbc.EscapeSyntaxCallMode; import org.postgresql.jdbc.PreferQueryMode; import org.postgresql.util.HostSpec; @@ -433,6 +434,8 @@ Object createQueryKey(String sql, boolean escapeProcessing, boolean isParameteri boolean isColumnSanitiserDisabled(); + EscapeSyntaxCallMode getEscapeSyntaxCallMode(); + PreferQueryMode getPreferQueryMode(); AutoSave getAutoSave(); diff --git a/pgjdbc/src/main/java/org/postgresql/core/QueryExecutorBase.java b/pgjdbc/src/main/java/org/postgresql/core/QueryExecutorBase.java index 1a74a7f73b..82d636c4fd 100644 --- a/pgjdbc/src/main/java/org/postgresql/core/QueryExecutorBase.java +++ b/pgjdbc/src/main/java/org/postgresql/core/QueryExecutorBase.java @@ -8,6 +8,7 @@ import org.postgresql.PGNotification; import org.postgresql.PGProperty; import org.postgresql.jdbc.AutoSave; +import org.postgresql.jdbc.EscapeSyntaxCallMode; import org.postgresql.jdbc.PreferQueryMode; import org.postgresql.util.HostSpec; import org.postgresql.util.LruCache; @@ -42,6 +43,7 @@ public abstract class QueryExecutorBase implements QueryExecutor { private TransactionState transactionState; private final boolean reWriteBatchedInserts; private final boolean columnSanitiserDisabled; + private final EscapeSyntaxCallMode escapeSyntaxCallMode; private final PreferQueryMode preferQueryMode; private AutoSave autoSave; private boolean flushCacheOnDeallocate = true; @@ -68,6 +70,8 @@ protected QueryExecutorBase(PGStream pgStream, String user, this.cancelSignalTimeout = cancelSignalTimeout; this.reWriteBatchedInserts = PGProperty.REWRITE_BATCHED_INSERTS.getBoolean(info); this.columnSanitiserDisabled = PGProperty.DISABLE_COLUMN_SANITISER.getBoolean(info); + String callMode = PGProperty.ESCAPE_SYNTAX_CALL_MODE.get(info); + this.escapeSyntaxCallMode = EscapeSyntaxCallMode.of(callMode); String preferMode = PGProperty.PREFER_QUERY_MODE.get(info); this.preferQueryMode = PreferQueryMode.of(preferMode); this.autoSave = AutoSave.of(PGProperty.AUTOSAVE.get(info)); @@ -339,6 +343,11 @@ public boolean isColumnSanitiserDisabled() { return columnSanitiserDisabled; } + @Override + public EscapeSyntaxCallMode getEscapeSyntaxCallMode() { + return escapeSyntaxCallMode; + } + @Override public PreferQueryMode getPreferQueryMode() { return preferQueryMode; diff --git a/pgjdbc/src/main/java/org/postgresql/ds/common/BaseDataSource.java b/pgjdbc/src/main/java/org/postgresql/ds/common/BaseDataSource.java index 8baa1fb959..e1a6df35f6 100644 --- a/pgjdbc/src/main/java/org/postgresql/ds/common/BaseDataSource.java +++ b/pgjdbc/src/main/java/org/postgresql/ds/common/BaseDataSource.java @@ -1128,6 +1128,22 @@ public void setReplication(String replication) { PGProperty.REPLICATION.set(properties, replication); } + /** + * @return 'select', "callIfNoReturn', or 'call' + * @see PGProperty#ESCAPE_SYNTAX_CALL_MODE + */ + public String getEscapeSyntaxCallMode() { + return PGProperty.ESCAPE_SYNTAX_CALL_MODE.get(properties); + } + + /** + * @param callMode the call mode to use for JDBC escape call syntax + * @see PGProperty#ESCAPE_SYNTAX_CALL_MODE + */ + public void setEscapeSyntaxCallMode(String callMode) { + PGProperty.ESCAPE_SYNTAX_CALL_MODE.set(properties, callMode); + } + /** * @return null, 'database', or 'true * @see PGProperty#REPLICATION diff --git a/pgjdbc/src/main/java/org/postgresql/jdbc/EscapeSyntaxCallMode.java b/pgjdbc/src/main/java/org/postgresql/jdbc/EscapeSyntaxCallMode.java new file mode 100644 index 0000000000..00200b140b --- /dev/null +++ b/pgjdbc/src/main/java/org/postgresql/jdbc/EscapeSyntaxCallMode.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2019, PostgreSQL Global Development Group + * See the LICENSE file in the project root for more information. + */ + +package org.postgresql.jdbc; + +/** + *

Specifies whether a SELECT/CALL statement is used for the underlying SQL for JDBC escape call syntax: 'select' means to + * always use SELECT, 'callIfNoReturn' means to use CALL if there is no return parameter (otherwise use SELECT), and 'call' means + * to always use CALL.

+ * + * @see org.postgresql.PGProperty#ESCAPE_SYNTAX_CALL_MODE + */ +public enum EscapeSyntaxCallMode { + SELECT("select"), + CALL_IF_NO_RETURN("callIfNoReturn"), + CALL("call"); + + private final String value; + + EscapeSyntaxCallMode(String value) { + this.value = value; + } + + public static EscapeSyntaxCallMode of(String mode) { + for (EscapeSyntaxCallMode escapeSyntaxCallMode : values()) { + if (escapeSyntaxCallMode.value.equals(mode)) { + return escapeSyntaxCallMode; + } + } + return SELECT; + } + + public String value() { + return value; + } +} diff --git a/pgjdbc/src/test/java/org/postgresql/core/ParserTest.java b/pgjdbc/src/test/java/org/postgresql/core/ParserTest.java index bd5aadc12e..34c8ca3a67 100644 --- a/pgjdbc/src/test/java/org/postgresql/core/ParserTest.java +++ b/pgjdbc/src/test/java/org/postgresql/core/ParserTest.java @@ -8,6 +8,8 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; +import org.postgresql.jdbc.EscapeSyntaxCallMode; + import org.junit.Assert; import org.junit.Ignore; import org.junit.Test; @@ -136,11 +138,22 @@ public void testEscapeProcessing() throws Exception { @Test public void testModifyJdbcCall() throws SQLException { - assertEquals("select * from pack_getValue(?) as result", Parser.modifyJdbcCall("{ ? = call pack_getValue}", true, ServerVersion.v9_6.getVersionNum(), 3).getSql()); - assertEquals("select * from pack_getValue(?,?) as result", Parser.modifyJdbcCall("{ ? = call pack_getValue(?) }", true, ServerVersion.v9_6.getVersionNum(), 3).getSql()); - assertEquals("select * from pack_getValue(?) as result", Parser.modifyJdbcCall("{ ? = call pack_getValue()}", true, ServerVersion.v9_6.getVersionNum(), 3).getSql()); - assertEquals("select * from pack_getValue(?,?,?,?) as result", Parser.modifyJdbcCall("{ ? = call pack_getValue(?,?,?) }", true, ServerVersion.v9_6.getVersionNum(), 3).getSql()); - assertEquals("select * from lower(?,?) as result", Parser.modifyJdbcCall("{ ? = call lower(?)}", true, ServerVersion.v9_6.getVersionNum(), 3).getSql()); + assertEquals("select * from pack_getValue(?) as result", Parser.modifyJdbcCall("{ ? = call pack_getValue}", true, ServerVersion.v9_6.getVersionNum(), 3, EscapeSyntaxCallMode.SELECT).getSql()); + assertEquals("select * from pack_getValue(?,?) as result", Parser.modifyJdbcCall("{ ? = call pack_getValue(?) }", true, ServerVersion.v9_6.getVersionNum(), 3, EscapeSyntaxCallMode.SELECT).getSql()); + assertEquals("select * from pack_getValue(?) as result", Parser.modifyJdbcCall("{ ? = call pack_getValue()}", true, ServerVersion.v9_6.getVersionNum(), 3, EscapeSyntaxCallMode.SELECT).getSql()); + assertEquals("select * from pack_getValue(?,?,?,?) as result", Parser.modifyJdbcCall("{ ? = call pack_getValue(?,?,?) }", true, ServerVersion.v9_6.getVersionNum(), 3, EscapeSyntaxCallMode.SELECT).getSql()); + assertEquals("select * from lower(?,?) as result", Parser.modifyJdbcCall("{ ? = call lower(?)}", true, ServerVersion.v9_6.getVersionNum(), 3, EscapeSyntaxCallMode.SELECT).getSql()); + assertEquals("select * from lower(?,?) as result", Parser.modifyJdbcCall("{ ? = call lower(?)}", true, ServerVersion.v9_6.getVersionNum(), 3, EscapeSyntaxCallMode.CALL_IF_NO_RETURN).getSql()); + assertEquals("select * from lower(?,?) as result", Parser.modifyJdbcCall("{ ? = call lower(?)}", true, ServerVersion.v9_6.getVersionNum(), 3, EscapeSyntaxCallMode.CALL).getSql()); + assertEquals("select * from lower(?,?) as result", Parser.modifyJdbcCall("{call lower(?,?)}", true, ServerVersion.v9_6.getVersionNum(), 3, EscapeSyntaxCallMode.SELECT).getSql()); + assertEquals("select * from lower(?,?) as result", Parser.modifyJdbcCall("{call lower(?,?)}", true, ServerVersion.v9_6.getVersionNum(), 3, EscapeSyntaxCallMode.CALL_IF_NO_RETURN).getSql()); + assertEquals("select * from lower(?,?) as result", Parser.modifyJdbcCall("{call lower(?,?)}", true, ServerVersion.v9_6.getVersionNum(), 3, EscapeSyntaxCallMode.CALL).getSql()); + assertEquals("select * from lower(?,?) as result", Parser.modifyJdbcCall("{ ? = call lower(?)}", true, ServerVersion.v11.getVersionNum(), 3, EscapeSyntaxCallMode.SELECT).getSql()); + assertEquals("select * from lower(?,?) as result", Parser.modifyJdbcCall("{ ? = call lower(?)}", true, ServerVersion.v11.getVersionNum(), 3, EscapeSyntaxCallMode.CALL_IF_NO_RETURN).getSql()); + assertEquals("call lower(?,?)", Parser.modifyJdbcCall("{ ? = call lower(?)}", true, ServerVersion.v11.getVersionNum(), 3, EscapeSyntaxCallMode.CALL).getSql()); + assertEquals("select * from lower(?,?) as result", Parser.modifyJdbcCall("{call lower(?,?)}", true, ServerVersion.v11.getVersionNum(), 3, EscapeSyntaxCallMode.SELECT).getSql()); + assertEquals("call lower(?,?)", Parser.modifyJdbcCall("{call lower(?,?)}", true, ServerVersion.v11.getVersionNum(), 3, EscapeSyntaxCallMode.CALL_IF_NO_RETURN).getSql()); + assertEquals("call lower(?,?)", Parser.modifyJdbcCall("{call lower(?,?)}", true, ServerVersion.v11.getVersionNum(), 3, EscapeSyntaxCallMode.CALL).getSql()); } @Test diff --git a/pgjdbc/src/test/java/org/postgresql/test/jdbc3/EscapeSyntaxCallModeBaseTest.java b/pgjdbc/src/test/java/org/postgresql/test/jdbc3/EscapeSyntaxCallModeBaseTest.java new file mode 100644 index 0000000000..ae2db304e1 --- /dev/null +++ b/pgjdbc/src/test/java/org/postgresql/test/jdbc3/EscapeSyntaxCallModeBaseTest.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2019, PostgreSQL Global Development Group + * See the LICENSE file in the project root for more information. + */ + +package org.postgresql.test.jdbc3; + +import org.postgresql.core.ServerVersion; +import org.postgresql.test.TestUtil; +import org.postgresql.test.jdbc2.BaseTest4; + +import java.sql.SQLException; +import java.sql.Statement; + +public class EscapeSyntaxCallModeBaseTest extends BaseTest4 { + + @Override + public void setUp() throws Exception { + super.setUp(); + Statement stmt = con.createStatement(); + stmt.execute( + "CREATE OR REPLACE FUNCTION myiofunc(a INOUT int, b OUT int) AS 'BEGIN b := a; a := 1; END;' LANGUAGE plpgsql"); + stmt.execute( + "CREATE OR REPLACE FUNCTION mysumfunc(a int, b int) returns int AS 'BEGIN return a + b; END;' LANGUAGE plpgsql"); + if (TestUtil.haveMinimumServerVersion(con, ServerVersion.v11)) { + stmt.execute( + "CREATE OR REPLACE PROCEDURE myioproc(a INOUT int, b INOUT int) AS 'BEGIN b := a; a := 1; END;' LANGUAGE plpgsql"); + } + } + + @Override + public void tearDown() throws SQLException { + Statement stmt = con.createStatement(); + stmt.execute("drop function myiofunc(a INOUT int, b OUT int) "); + stmt.execute("drop function mysumfunc(a int, b int) "); + if (TestUtil.haveMinimumServerVersion(con, ServerVersion.v11)) { + stmt.execute("drop procedure myioproc(a INOUT int, b INOUT int) "); + } + stmt.close(); + super.tearDown(); + } + +} diff --git a/pgjdbc/src/test/java/org/postgresql/test/jdbc3/EscapeSyntaxCallModeCallIfNoReturnTest.java b/pgjdbc/src/test/java/org/postgresql/test/jdbc3/EscapeSyntaxCallModeCallIfNoReturnTest.java new file mode 100644 index 0000000000..3e629b48df --- /dev/null +++ b/pgjdbc/src/test/java/org/postgresql/test/jdbc3/EscapeSyntaxCallModeCallIfNoReturnTest.java @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2019, PostgreSQL Global Development Group + * See the LICENSE file in the project root for more information. + */ + +package org.postgresql.test.jdbc3; + +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import org.postgresql.PGProperty; +import org.postgresql.core.ServerVersion; +import org.postgresql.jdbc.EscapeSyntaxCallMode; +import org.postgresql.util.PSQLState; + +import org.junit.Test; + +import java.sql.CallableStatement; +import java.sql.SQLException; +import java.sql.Types; +import java.util.Properties; + +public class EscapeSyntaxCallModeCallIfNoReturnTest extends EscapeSyntaxCallModeBaseTest { + + @Override + protected void updateProperties(Properties props) { + super.updateProperties(props); + PGProperty.ESCAPE_SYNTAX_CALL_MODE.set(props, EscapeSyntaxCallMode.CALL_IF_NO_RETURN.value()); + } + + @Test + public void testInvokeFunction() throws Throwable { + // escapeSyntaxCallMode=callIfNoReturn will cause a CALL statement to be used for the JDBC escape call + // syntax used below (since no return parameter is specified). "myiofunc" is a function, so the + // attempted invocation should fail. + assumeCallableStatementsSupported(); + assumeMinimumServerVersion(ServerVersion.v11); + CallableStatement cs = con.prepareCall("{ call myiofunc(?,?) }"); + cs.registerOutParameter(1, Types.INTEGER); + cs.registerOutParameter(2, Types.INTEGER); + cs.setInt(1, 10); + try { + cs.execute(); + fail("Should throw an exception"); + } catch (SQLException ex) { + assertTrue(ex.getSQLState().equalsIgnoreCase(PSQLState.WRONG_OBJECT_TYPE.getState())); + } + } + + @Test + public void testInvokeFunctionHavingReturnParameter() throws Throwable { + // escapeSyntaxCallMode=callIfNoReturn will cause a SELECT statement to be used for the JDBC escape call + // syntax used below (since a return parameter is specified). "mysumfunc" is a function, so the + // invocation should succeed. + assumeCallableStatementsSupported(); + CallableStatement cs = con.prepareCall("{ ? = call mysumfunc(?,?) }"); + cs.registerOutParameter(1, Types.INTEGER); + cs.setInt(2, 10); + cs.setInt(3, 20); + cs.execute(); + int ret = cs.getInt(1); + assertTrue("Expected mysumproc(10,20) to return 30 but returned " + ret, ret == 30); + } + + @Test + public void testInvokeProcedure() throws Throwable { + // escapeSyntaxCallMode=callIfNoReturn will cause a CALL statement to be used for the JDBC escape call + // syntax used below (since there is no return parameter specified). "myioproc" is a procedure, so the + // invocation should succeed. + assumeCallableStatementsSupported(); + assumeMinimumServerVersion(ServerVersion.v11); + CallableStatement cs = con.prepareCall("{call myioproc(?,?)}"); + cs.registerOutParameter(1, Types.INTEGER); + cs.registerOutParameter(2, Types.INTEGER); + cs.setInt(1, 10); + cs.setInt(2, 20); + cs.execute(); + // Expected output: a==1 (param 1), b==10 (param 2) + int a = cs.getInt(1); + int b = cs.getInt(2); + assertTrue("Expected myioproc() to return output parameter values 1,10 but returned " + a + "," + b, (a == 1 && b == 10)); + } + +} diff --git a/pgjdbc/src/test/java/org/postgresql/test/jdbc3/EscapeSyntaxCallModeCallTest.java b/pgjdbc/src/test/java/org/postgresql/test/jdbc3/EscapeSyntaxCallModeCallTest.java new file mode 100644 index 0000000000..f575506ca1 --- /dev/null +++ b/pgjdbc/src/test/java/org/postgresql/test/jdbc3/EscapeSyntaxCallModeCallTest.java @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2019, PostgreSQL Global Development Group + * See the LICENSE file in the project root for more information. + */ + +package org.postgresql.test.jdbc3; + +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import org.postgresql.PGProperty; +import org.postgresql.core.ServerVersion; +import org.postgresql.jdbc.EscapeSyntaxCallMode; +import org.postgresql.util.PSQLState; + +import org.junit.Test; + +import java.sql.CallableStatement; +import java.sql.SQLException; +import java.sql.Types; +import java.util.Properties; + +public class EscapeSyntaxCallModeCallTest extends EscapeSyntaxCallModeBaseTest { + + @Override + protected void updateProperties(Properties props) { + super.updateProperties(props); + PGProperty.ESCAPE_SYNTAX_CALL_MODE.set(props, EscapeSyntaxCallMode.CALL.value()); + } + + @Test + public void testInvokeFunction() throws Throwable { + // escapeSyntaxCallMode=call will cause a CALL statement to be used for the JDBC escape call + // syntax used below. "myiofunc" is a function, so the attempted invocation should fail. + assumeCallableStatementsSupported(); + assumeMinimumServerVersion(ServerVersion.v11); + CallableStatement cs = con.prepareCall("{ call myiofunc(?,?) }"); + cs.registerOutParameter(1, Types.INTEGER); + cs.registerOutParameter(2, Types.INTEGER); + cs.setInt(1, 10); + try { + cs.execute(); + fail("Should throw an exception"); + } catch (SQLException ex) { + assertTrue(ex.getSQLState().equalsIgnoreCase(PSQLState.WRONG_OBJECT_TYPE.getState())); + } + } + + @Test + public void testInvokeFunctionHavingReturnParameter() throws Throwable { + // escapeSyntaxCallMode=call will cause a CALL statement to be used for the JDBC escape call + // syntax used below. "mysumfunc" is a function, so the attempted invocation should fail. + assumeCallableStatementsSupported(); + assumeMinimumServerVersion(ServerVersion.v11); + CallableStatement cs = con.prepareCall("{ ? = call mysumfunc(?,?) }"); + cs.registerOutParameter(1, Types.INTEGER); + cs.setInt(2, 10); + cs.setInt(3, 20); + try { + cs.execute(); + fail("Should throw an exception"); + } catch (SQLException ex) { + assertTrue(ex.getSQLState().equalsIgnoreCase(PSQLState.WRONG_OBJECT_TYPE.getState())); + } + } + + @Test + public void testInvokeProcedure() throws Throwable { + // escapeSyntaxCallMode=call will cause a CALL statement to be used for the JDBC escape call + // syntax used below. "myioproc" is a procedure, so the invocation should succeed. + assumeCallableStatementsSupported(); + assumeMinimumServerVersion(ServerVersion.v11); + CallableStatement cs = con.prepareCall("{call myioproc(?,?)}"); + cs.registerOutParameter(1, Types.INTEGER); + cs.registerOutParameter(2, Types.INTEGER); + cs.setInt(1, 10); + cs.setInt(2, 20); + cs.execute(); + // Expected output: a==1 (param 1), b==10 (param 2) + int a = cs.getInt(1); + int b = cs.getInt(2); + assertTrue("Expected myioproc() to return output parameter values 1,10 but returned " + a + "," + b, (a == 1 && b == 10)); + } + +} diff --git a/pgjdbc/src/test/java/org/postgresql/test/jdbc3/EscapeSyntaxCallModeSelectTest.java b/pgjdbc/src/test/java/org/postgresql/test/jdbc3/EscapeSyntaxCallModeSelectTest.java new file mode 100644 index 0000000000..b36ff4ffd2 --- /dev/null +++ b/pgjdbc/src/test/java/org/postgresql/test/jdbc3/EscapeSyntaxCallModeSelectTest.java @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2019, PostgreSQL Global Development Group + * See the LICENSE file in the project root for more information. + */ + +package org.postgresql.test.jdbc3; + +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import org.postgresql.PGProperty; +import org.postgresql.core.ServerVersion; +import org.postgresql.jdbc.EscapeSyntaxCallMode; +import org.postgresql.util.PSQLState; + +import org.junit.Test; + +import java.sql.CallableStatement; +import java.sql.SQLException; +import java.sql.Types; +import java.util.Properties; + +public class EscapeSyntaxCallModeSelectTest extends EscapeSyntaxCallModeBaseTest { + + @Override + protected void updateProperties(Properties props) { + super.updateProperties(props); + PGProperty.ESCAPE_SYNTAX_CALL_MODE.set(props, EscapeSyntaxCallMode.SELECT.value()); + } + + @Test + public void testInvokeFunction() throws Throwable { + // escapeSyntaxCallMode=select will cause a SELECT statement to be used for the JDBC escape call + // syntax used below. "myiofunc" is a function, so the invocation should succeed. + assumeCallableStatementsSupported(); + CallableStatement cs = con.prepareCall("{ call myiofunc(?,?) }"); + cs.registerOutParameter(1, Types.INTEGER); + cs.registerOutParameter(2, Types.INTEGER); + cs.setInt(1, 10); + cs.execute(); + // Expected output: a==1 (param 1), b==10 (param 2) + int a = cs.getInt(1); + int b = cs.getInt(2); + assertTrue("Expected myiofunc() to return output parameter values 1,10 but returned " + a + "," + b, (a == 1 && b == 10)); + } + + @Test + public void testInvokeFunctionHavingReturnParameter() throws Throwable { + // escapeSyntaxCallMode=select will cause a SELECT statement to be used for the JDBC escape call + // syntax used below. "mysumfunc" is a function, so the invocation should succeed. + assumeCallableStatementsSupported(); + CallableStatement cs = con.prepareCall("{ ? = call mysumfunc(?,?) }"); + cs.registerOutParameter(1, Types.INTEGER); + cs.setInt(2, 10); + cs.setInt(3, 20); + cs.execute(); + int ret = cs.getInt(1); + assertTrue("Expected mysumfunc(10,20) to return 30 but returned " + ret, ret == 30); + } + + @Test + public void testInvokeProcedure() throws Throwable { + // escapeSyntaxCallMode=select will cause a SELECT statement to be used for the JDBC escape call + // syntax used below. "myioproc" is a procedure, so the attempted invocation should fail. + assumeCallableStatementsSupported(); + assumeMinimumServerVersion(ServerVersion.v11); + CallableStatement cs = con.prepareCall("{call myioproc(?,?)}"); + cs.registerOutParameter(1, Types.INTEGER); + cs.registerOutParameter(2, Types.INTEGER); + cs.setInt(1, 10); + cs.setInt(2, 20); + try { + cs.execute(); + fail("Should throw an exception"); + } catch (SQLException ex) { + assertTrue(ex.getSQLState().equalsIgnoreCase(PSQLState.WRONG_OBJECT_TYPE.getState())); + } + } + +} diff --git a/pgjdbc/src/test/java/org/postgresql/test/jdbc3/Jdbc3TestSuite.java b/pgjdbc/src/test/java/org/postgresql/test/jdbc3/Jdbc3TestSuite.java index 5e41f7bddd..2693cf7f0a 100644 --- a/pgjdbc/src/test/java/org/postgresql/test/jdbc3/Jdbc3TestSuite.java +++ b/pgjdbc/src/test/java/org/postgresql/test/jdbc3/Jdbc3TestSuite.java @@ -16,6 +16,9 @@ CompositeQueryParseTest.class, CompositeTest.class, DatabaseMetaDataTest.class, + EscapeSyntaxCallModeCallTest.class, + EscapeSyntaxCallModeCallIfNoReturnTest.class, + EscapeSyntaxCallModeSelectTest.class, GeneratedKeysTest.class, Jdbc3BlobTest.class, Jdbc3CallableStatementTest.class,