From e1f7aea61fb074fc70e8cab1879e7d3b48226e64 Mon Sep 17 00:00:00 2001 From: Greg Nancarrow Date: Tue, 26 Nov 2019 19:51:48 +1100 Subject: [PATCH] feat: add new "escapeSyntaxCallMode" connection property The "escapeSyntaxCallMode" connection property allows you to specify how the driver transforms JDBC escape call syntax into underlying SQL, for invoking procedures and functions. The possible values of this property are: select, callIfNoReturn, or call 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). Prior to the addition of this connection property, the driver always used a SELECT statement for JDBC escape call syntax, which results in the following error when attempting to invoke a procedure: ERROR: xxxx is a procedure Hint: To call a procedure, use CALL --- README.md | 1 + docs/documentation/head/connect.md | 10 +++ .../main/java/org/postgresql/PGProperty.java | 13 +++ .../core/CachedQueryCreateAction.java | 2 +- .../postgresql/core/JdbcCallParseInfo.java | 2 +- .../main/java/org/postgresql/core/Parser.java | 30 +++++-- .../org/postgresql/core/QueryExecutor.java | 3 + .../postgresql/core/QueryExecutorBase.java | 9 ++ .../postgresql/ds/common/BaseDataSource.java | 16 ++++ .../postgresql/jdbc/EscapeSyntaxCallMode.java | 38 +++++++++ .../java/org/postgresql/core/ParserTest.java | 23 +++-- .../jdbc3/EscapeSyntaxCallModeBaseTest.java | 43 ++++++++++ ...scapeSyntaxCallModeCallIfNoReturnTest.java | 84 ++++++++++++++++++ .../jdbc3/EscapeSyntaxCallModeCallTest.java | 85 +++++++++++++++++++ .../jdbc3/EscapeSyntaxCallModeSelectTest.java | 80 +++++++++++++++++ .../postgresql/test/jdbc3/Jdbc3TestSuite.java | 3 + 16 files changed, 427 insertions(+), 15 deletions(-) create mode 100644 pgjdbc/src/main/java/org/postgresql/jdbc/EscapeSyntaxCallMode.java create mode 100644 pgjdbc/src/test/java/org/postgresql/test/jdbc3/EscapeSyntaxCallModeBaseTest.java create mode 100644 pgjdbc/src/test/java/org/postgresql/test/jdbc3/EscapeSyntaxCallModeCallIfNoReturnTest.java create mode 100644 pgjdbc/src/test/java/org/postgresql/test/jdbc3/EscapeSyntaxCallModeCallTest.java create mode 100644 pgjdbc/src/test/java/org/postgresql/test/jdbc3/EscapeSyntaxCallModeSelectTest.java 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,