Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New "escapeSyntaxCallMode" connection property #1560

Merged
merged 1 commit into from Nov 26, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Expand Up @@ -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)
Expand Down
10 changes: 10 additions & 0 deletions docs/documentation/head/connect.md
Expand Up @@ -472,6 +472,16 @@ Connection conn = DriverManager.getConnection(url);
for logical replication from that database. <p>Parameter should be use together with
`assumeMinServerVersion` with parameter >= 9.4 (backend >= 9.4)</p>

* **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`


<a name="unix sockets"></a>
## Unix sockets
Expand Down
13 changes: 13 additions & 0 deletions pgjdbc/src/main/java/org/postgresql/PGProperty.java
Expand Up @@ -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 &gt;= 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}.
Expand Down
Expand Up @@ -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 {
Expand Down
Expand Up @@ -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;
Expand Down
30 changes: 22 additions & 8 deletions pgjdbc/src/main/java/org/postgresql/core/Parser.java
Expand Up @@ -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;
Expand Down Expand Up @@ -894,15 +895,16 @@ private static boolean subArraysEqual(final char[] arr,
* [?,..])] }} into the PostgreSQL format which is {@code select <some_function> (?, [?, ...]) as
* result} or {@code select * from <some_function> (?, [?, ...]) 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.
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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);
}

Expand Down
3 changes: 3 additions & 0 deletions pgjdbc/src/main/java/org/postgresql/core/QueryExecutor.java
Expand Up @@ -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;

Expand Down Expand Up @@ -433,6 +434,8 @@ Object createQueryKey(String sql, boolean escapeProcessing, boolean isParameteri

boolean isColumnSanitiserDisabled();

EscapeSyntaxCallMode getEscapeSyntaxCallMode();

PreferQueryMode getPreferQueryMode();

AutoSave getAutoSave();
Expand Down
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand All @@ -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));
Expand Down Expand Up @@ -339,6 +343,11 @@ public boolean isColumnSanitiserDisabled() {
return columnSanitiserDisabled;
}

@Override
public EscapeSyntaxCallMode getEscapeSyntaxCallMode() {
return escapeSyntaxCallMode;
}

@Override
public PreferQueryMode getPreferQueryMode() {
return preferQueryMode;
Expand Down
16 changes: 16 additions & 0 deletions pgjdbc/src/main/java/org/postgresql/ds/common/BaseDataSource.java
Expand Up @@ -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
Expand Down
38 changes: 38 additions & 0 deletions 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;

/**
* <p>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.</p>
*
* @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;
}
}
23 changes: 18 additions & 5 deletions pgjdbc/src/test/java/org/postgresql/core/ParserTest.java
Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand Down
@@ -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();
}

}