Skip to content
Permalink
Browse files

feat: support execute statements via simple 'Q' command

To execute a query in simple mode, QueryExecutor.QUERY_EXECUTE_AS_SIMPLE flag should be used.
Alternatively, preferQueryMode can be used to control the desired execution mode.

preferQueryMode can have the following values:
  extended (default): send all queries as extended (parse, bind, exec)
  simple: send all queries as simple 'Q'
  extendedForPrepared: send prepared statements as extended, and non-prepared statements as 'Q'

Note: simple execution mode does not support binary formats, it does not support statement metadata, etc.

Other changes: support timestamp, timestamptz, date, time, timetz, point, box, and uuid types in simple execution mode

closes #558
closes #618
  • Loading branch information
vlsi committed Aug 13, 2016
1 parent c3d8571 commit 232569c6bd9eb625fc70ebe642d35c8256726a16
Showing with 1,269 additions and 447 deletions.
  1. +13 −0 .travis.yml
  2. +5 −0 .travis/travis_build.sh
  3. +1 −1 codecov.yml
  4. +11 −0 pgjdbc/src/main/java/org/postgresql/PGConnection.java
  5. +13 −0 pgjdbc/src/main/java/org/postgresql/PGProperty.java
  6. +5 −1 pgjdbc/src/main/java/org/postgresql/core/BaseQueryKey.java
  7. +2 −2 pgjdbc/src/main/java/org/postgresql/core/BaseStatement.java
  8. +10 −0 pgjdbc/src/main/java/org/postgresql/core/CachedQuery.java
  9. +10 −3 pgjdbc/src/main/java/org/postgresql/core/CachedQueryCreateAction.java
  10. +9 −0 pgjdbc/src/main/java/org/postgresql/core/CallableQueryKey.java
  11. +4 −2 pgjdbc/src/main/java/org/postgresql/core/NativeQuery.java
  12. +40 −19 pgjdbc/src/main/java/org/postgresql/core/Parser.java
  13. +20 −2 pgjdbc/src/main/java/org/postgresql/core/QueryExecutor.java
  14. +36 −8 pgjdbc/src/main/java/org/postgresql/core/QueryExecutorBase.java
  15. +10 −0 pgjdbc/src/main/java/org/postgresql/core/QueryWithReturningColumnsKey.java
  16. +7 −0 pgjdbc/src/main/java/org/postgresql/core/ResultHandler.java
  17. +62 −0 pgjdbc/src/main/java/org/postgresql/core/ResultHandlerDelegate.java
  18. +6 −1 pgjdbc/src/main/java/org/postgresql/core/SetupQueryRunner.java
  19. +55 −21 pgjdbc/src/main/java/org/postgresql/core/v3/BatchedQuery.java
  20. +3 −1 pgjdbc/src/main/java/org/postgresql/core/v3/ExecuteRequest.java
  21. +132 −69 pgjdbc/src/main/java/org/postgresql/core/v3/QueryExecutorImpl.java
  22. +32 −0 pgjdbc/src/main/java/org/postgresql/core/v3/SimpleParameterList.java
  23. +17 −0 pgjdbc/src/main/java/org/postgresql/ds/common/BaseDataSource.java
  24. +36 −11 pgjdbc/src/main/java/org/postgresql/jdbc/BatchResultHandler.java
  25. +9 −0 pgjdbc/src/main/java/org/postgresql/jdbc/PgConnection.java
  26. +11 −12 pgjdbc/src/main/java/org/postgresql/jdbc/PgPreparedStatement.java
  27. +4 −0 pgjdbc/src/main/java/org/postgresql/jdbc/PgResultSet.java
  28. +72 −20 pgjdbc/src/main/java/org/postgresql/jdbc/PgStatement.java
  29. +35 −0 pgjdbc/src/main/java/org/postgresql/jdbc/PreferQueryMode.java
  30. +4 −0 pgjdbc/src/main/java/org/postgresql/jdbc/TimestampUtils.java
  31. +1 −1 pgjdbc/src/main/java/org/postgresql/util/PGbytea.java
  32. +0 −3 pgjdbc/src/test/java/org/postgresql/jdbc/DeepBatchedInsertStatementTest.java
  33. +23 −0 pgjdbc/src/test/java/org/postgresql/test/TestUtil.java
  34. +2 −1 pgjdbc/src/test/java/org/postgresql/test/extensions/ExtensionsTestSuite.java
  35. +22 −9 pgjdbc/src/test/java/org/postgresql/test/extensions/HStoreTest.java
  36. +2 −1 pgjdbc/src/test/java/org/postgresql/test/jdbc2/ArrayTest.java
  37. +11 −0 pgjdbc/src/test/java/org/postgresql/test/jdbc2/BaseTest.java
  38. +16 −0 pgjdbc/src/test/java/org/postgresql/test/jdbc2/BaseTest4.java
  39. +9 −6 pgjdbc/src/test/java/org/postgresql/test/jdbc2/BatchExecuteTest.java
  40. +34 −0 pgjdbc/src/test/java/org/postgresql/test/jdbc2/BatchedInsertReWriteEnabledTest.java
  41. +37 −12 pgjdbc/src/test/java/org/postgresql/test/jdbc2/CallableStmtTest.java
  42. +9 −10 pgjdbc/src/test/java/org/postgresql/test/jdbc2/Jdbc2TestSuite.java
  43. +14 −16 pgjdbc/src/test/java/org/postgresql/test/jdbc2/PGTimeTest.java
  44. +0 −14 pgjdbc/src/test/java/org/postgresql/test/jdbc2/PreparedStatementBinaryTest.java
  45. +63 −6 pgjdbc/src/test/java/org/postgresql/test/jdbc2/PreparedStatementTest.java
  46. +20 −15 pgjdbc/src/test/java/org/postgresql/test/jdbc2/RefCursorTest.java
  47. +37 −16 pgjdbc/src/test/java/org/postgresql/test/jdbc2/ResultSetMetaDataTest.java
  48. +40 −15 pgjdbc/src/test/java/org/postgresql/test/jdbc2/ResultSetTest.java
  49. +15 −12 pgjdbc/src/test/java/org/postgresql/test/jdbc2/ServerCursorTest.java
  50. +31 −11 pgjdbc/src/test/java/org/postgresql/test/jdbc2/ServerPreparedStmtTest.java
  51. +1 −1 pgjdbc/src/test/java/org/postgresql/test/jdbc2/StatementTest.java
  52. +30 −17 pgjdbc/src/test/java/org/postgresql/test/jdbc2/UpdateableResultTest.java
  53. +53 −10 pgjdbc/src/test/java/org/postgresql/test/jdbc3/Jdbc3CallableStatementTest.java
  54. +3 −3 pgjdbc/src/test/java/org/postgresql/test/jdbc3/Jdbc3TestSuite.java
  55. +30 −23 pgjdbc/src/test/java/org/postgresql/test/jdbc3/ParameterMetaDataTest.java
  56. +22 −17 pgjdbc/src/test/java/org/postgresql/test/jdbc3/TypesTest.java
  57. +6 −1 pgjdbc/src/test/java/org/postgresql/test/jdbc4/ArrayTest.java
  58. +26 −15 pgjdbc/src/test/java/org/postgresql/test/jdbc4/BinaryStreamTest.java
  59. +18 −18 pgjdbc/src/test/java/org/postgresql/test/jdbc4/BinaryTest.java
  60. +3 −3 pgjdbc/src/test/java/org/postgresql/test/jdbc4/Jdbc4TestSuite.java
  61. +17 −18 pgjdbc/src/test/java/org/postgresql/test/jdbc4/UUIDTest.java
@@ -108,6 +108,19 @@ matrix:
env:
- PG_VERSION=9.4
- JDK=9
- jdk: oraclejdk8
addons:
postgresql: "9.4"
env:
- PG_VERSION=9.4
- QUERY_MODE=simple
- COVERAGE=Y
- jdk: oraclejdk8
addons:
postgresql: "9.4"
env:
- PG_VERSION=9.4
- QUERY_MODE=extendedForPrepared
- jdk: oraclejdk8
addons:
postgresql: "9.4"
@@ -17,6 +17,11 @@ then
MVN_ARGS="$MVN_ARGS -DwaffleEnabled=false -DosgiEnabled=false -DexcludePackageNames=org.postgresql.osgi:org.postgresql.sspi"
fi

if [[ "x${QUERY_MODE}" == *"x"* ]];
then
MVN_ARGS="$MVN_ARGS -DpreferQueryMode=$QUERY_MODE"
fi

if [[ "${COVERAGE}" == *"Y"* ]];
then
MVN_PROFILES="$MVN_PROFILES,coverage"
@@ -1,6 +1,6 @@
codecov:
notify:
after_n_builds: 7
after_n_builds: 8
require_ci_to_pass: false
comment:
layout: header, changes, diff
@@ -10,6 +10,7 @@

import org.postgresql.copy.CopyManager;
import org.postgresql.fastpath.Fastpath;
import org.postgresql.jdbc.PreferQueryMode;
import org.postgresql.largeobject.LargeObjectManager;
import org.postgresql.util.PGobject;

@@ -167,4 +168,14 @@
* @throws SQLException if something goes wrong
*/
String escapeLiteral(String literal) throws SQLException;

/**
* Returns true if the connection is configured to use "simple 'Q' execute" commands only
* When running in simple protocol only, certain features are not available: callable statements,
* partial result set fetch, bytea type, etc.
* The list of supported features is subject to change.
*
* @return true if the connection is configured to use "simple 'Q' execute" commands only
*/
PreferQueryMode getPreferQueryMode();
}
@@ -356,6 +356,19 @@
HOST_RECHECK_SECONDS("hostRecheckSeconds", "10",
"Specifies period (seconds) after host statuses are checked again in case they have changed"),

/**
* Specifies which mode is used to execute queries to database: simple means ('Q' execute, no parse, no bind, text mode only),
* extended means always use bind/execute messages, extendedForPrepared means extended for prepared statements only,
* extendedCacheEveryting means use extended protocol and try cache every statement (including Statement.execute(String sql)) in a query cache.
*
* This mode is meant for debugging purposes and/or for cases when extended protocol cannot be used (e.g. logical replication protocol)
*/
PREFER_QUERY_MODE("preferQueryMode", "extended",
"Specifies which mode is used to execute queries to database: simple means ('Q' execute, no parse, no bind, text mode only), "
+ "extended means always use bind/execute messages, extendedForPrepared means extended for prepared statements only, "
+ "extendedCacheEveryting means use extended protocol and try cache every statement (including Statement.execute(String sql)) in a query cache.", false,
"extended", "extendedForPrepared", "extendedCacheEveryting", "simple"),

/**
* Configure optimization to enable batch insert re-writing.
*/
@@ -19,7 +19,11 @@

@Override
public String toString() {
return sql;
return "BaseQueryKey{"
+ "sql='" + sql + '\''
+ ", isParameterized=" + isParameterized
+ ", escapeProcessing=" + escapeProcessing
+ '}';
}

@Override
@@ -58,13 +58,13 @@ ResultSet createResultSet(Query originalQuery, Field[] fields, List<byte[][]> tu
/**
* Execute a query, passing additional query flags.
*
* @param query the query to execute (native to PostgreSQL)
* @param cachedQuery the query to execute (native to PostgreSQL)
* @param flags additional {@link QueryExecutor} flags for execution; these are bitwise-ORed into
* the default flags.
* @return true if there is a result set
* @throws SQLException if something goes wrong.
*/
boolean executeWithFlags(Query query, int flags) throws SQLException;
boolean executeWithFlags(CachedQuery cachedQuery, int flags) throws SQLException;

/**
* Execute a prepared query, passing additional query flags.
@@ -62,4 +62,14 @@ public long getSize() {
return queryLength * 2 /* original query and native sql */
+ 100L /* entry in hash map, CachedQuery wrapper, etc */;
}

@Override
public String toString() {
return "CachedQuery{"
+ "executeCount=" + executeCount
+ ", query=" + query
+ ", isFunction=" + isFunction
+ ", outParmBeforeFunc=" + outParmBeforeFunc
+ '}';
}
}
@@ -26,11 +26,18 @@ public CachedQueryCreateAction(QueryExecutor queryExecutor) {

@Override
public CachedQuery create(Object key) throws SQLException {
String parsedSql = key.toString();
assert key instanceof String || key instanceof BaseQueryKey
: "Query key should be String or BaseQueryKey. Given " + key.getClass() + ", sql: "
+ parsedSql;
BaseQueryKey queryKey = key instanceof BaseQueryKey ? (BaseQueryKey) key : null;
+ String.valueOf(key);
BaseQueryKey queryKey;
String parsedSql;
if (key instanceof BaseQueryKey) {
queryKey = (BaseQueryKey) key;
parsedSql = queryKey.sql;
} else {
queryKey = null;
parsedSql = (String) key;
}
if (key instanceof String || queryKey.escapeProcessing) {
parsedSql =
Parser.replaceProcessing(parsedSql, true, queryExecutor.getStandardConformingStrings());
@@ -18,6 +18,15 @@ public CallableQueryKey(String sql) {
super(sql, true, true);
}

@Override
public String toString() {
return "CallableQueryKey{"
+ "sql='" + sql + '\''
+ ", isParameterized=" + isParameterized
+ ", escapeProcessing=" + escapeProcessing
+ '}';
}

@Override
public int hashCode() {
return super.hashCode() * 31;
@@ -20,6 +20,7 @@
public final String nativeSql;
public final int[] bindPositions;
public final SqlCommand command;
public final boolean multiStatement;

static {
for (int i = 1; i < BIND_NAMES.length; i++) {
@@ -28,13 +29,14 @@
}

public NativeQuery(String nativeSql, SqlCommand dml) {
this(nativeSql, NO_BINDS, dml);
this(nativeSql, NO_BINDS, true, dml);
}

public NativeQuery(String nativeSql, int[] bindPositions, SqlCommand dml) {
public NativeQuery(String nativeSql, int[] bindPositions, boolean multiStatement, SqlCommand dml) {
this.nativeSql = nativeSql;
this.bindPositions =
bindPositions == null || bindPositions.length == 0 ? NO_BINDS : bindPositions;
this.multiStatement = multiStatement;
this.command = dml;
}

@@ -41,6 +41,7 @@
* @param isBatchedReWriteConfigured whether re-write optimization is enabled
* @param returningColumnNames for simple insert, update, delete add returning with given column names
* @return list of native queries
* @throws SQLException if unable to add returning clause (invalid column names)
*/
public static List<NativeQuery> parseJdbcSql(String query, boolean standardConformingStrings,
boolean withParameters, boolean splitStatements,
@@ -66,7 +67,10 @@
int valuesBraceClosePosition = -1;
boolean isInsertPresent = false;
boolean isReturningPresent = false;
boolean isReturningPresentPrev = false;
SqlCommandType currentCommandType = SqlCommandType.BLANK;
SqlCommandType prevCommandType = SqlCommandType.BLANK;
int numberOfStatements = 0;

boolean whitespaceOnly = true;
int keyWordCount = 0;
@@ -129,36 +133,44 @@
break;

case ';':
if (inParen == 0 && splitStatements) {
if (inParen == 0) {
if (!whitespaceOnly) {
numberOfStatements++;
nativeSql.append(aChars, fragmentStart, i - fragmentStart);
whitespaceOnly = true;
}
fragmentStart = i + 1;
if (nativeSql.length() > 0) {
if (nativeQueries == null) {
nativeQueries = new ArrayList<NativeQuery>();
}

if (addReturning(nativeSql, currentCommandType, returningColumnNames, isReturningPresent)) {
isReturningPresent = true;
}

nativeQueries.add(new NativeQuery(nativeSql.toString(),
toIntArray(bindPositions), SqlCommand.createStatementTypeInfo(
currentCommandType, isBatchedReWriteConfigured, valuesBraceOpenPosition,
valuesBraceClosePosition,
isReturningPresent, nativeQueries.size())));
}
// Prepare for next query
if (bindPositions != null) {
bindPositions.clear();
if (splitStatements) {
if (nativeQueries == null) {
nativeQueries = new ArrayList<NativeQuery>();
}

nativeQueries.add(new NativeQuery(nativeSql.toString(),
toIntArray(bindPositions), false,
SqlCommand.createStatementTypeInfo(
currentCommandType, isBatchedReWriteConfigured, valuesBraceOpenPosition,
valuesBraceClosePosition,
isReturningPresent, nativeQueries.size())));
}
}
nativeSql.setLength(0);
prevCommandType = currentCommandType;
isReturningPresentPrev = isReturningPresent;
currentCommandType = SqlCommandType.BLANK;
isReturningPresent = false;
valuesBraceOpenPosition = -1;
valuesBraceClosePosition = -1;
if (splitStatements) {
// Prepare for next query
if (bindPositions != null) {
bindPositions.clear();
}
nativeSql.setLength(0);
valuesBraceOpenPosition = -1;
valuesBraceClosePosition = -1;
}
}
break;

@@ -210,7 +222,7 @@
}
}
}
if (!isValuesFound || bindPositions == null) {
if (!isValuesFound) {
isCurrentReWriteCompatible = false;
}
if (!isCurrentReWriteCompatible) {
@@ -220,6 +232,14 @@

if (fragmentStart < aChars.length && !whitespaceOnly) {
nativeSql.append(aChars, fragmentStart, aChars.length - fragmentStart);
} else {
if (numberOfStatements > 1) {
isReturningPresent = false;
currentCommandType = SqlCommandType.BLANK;
} else if (numberOfStatements == 1) {
isReturningPresent = isReturningPresentPrev;
currentCommandType = prevCommandType;
}
}

if (nativeSql.length() == 0) {
@@ -231,7 +251,8 @@
}

NativeQuery lastQuery = new NativeQuery(nativeSql.toString(),
toIntArray(bindPositions), SqlCommand.createStatementTypeInfo(currentCommandType,
toIntArray(bindPositions), !splitStatements,
SqlCommand.createStatementTypeInfo(currentCommandType,
isBatchedReWriteConfigured, valuesBraceOpenPosition, valuesBraceClosePosition,
isReturningPresent, (nativeQueries == null ? 0 : nativeQueries.size())));

@@ -13,6 +13,7 @@
import org.postgresql.copy.CopyOperation;
import org.postgresql.core.v3.TypeTransferModeRegistry;
import org.postgresql.jdbc.BatchResultHandler;
import org.postgresql.jdbc.PreferQueryMode;
import org.postgresql.util.HostSpec;

import java.sql.SQLException;
@@ -28,8 +29,8 @@
* provides:
*
* <ul>
* <li>factory methods for Query objects ({@link #createSimpleQuery} and
* {@link #createParameterizedQuery})
* <li>factory methods for Query objects ({@link #createSimpleQuery(String)} and
* {@link #createQuery(String, boolean, boolean, String...)})
* <li>execution methods for created Query objects (
* {@link #execute(Query, ParameterList, ResultHandler, int, int, int)} for single queries and
* {@link #execute(Query[], ParameterList[], BatchResultHandler, int, int, int)} for batches of queries)
@@ -113,6 +114,13 @@
*/
int QUERY_NO_BINARY_TRANSFER = 256;

/**
* Execute the query via simple 'Q' command (not parse, bind, exec, but simple execute).
* This sends query text on each execution, however it supports sending multiple queries
* separated with ';' as a single command.
*/
int QUERY_EXECUTE_AS_SIMPLE = 1024;

/**
* Execute a Query, passing results to a provided ResultHandler.
*
@@ -169,6 +177,7 @@ void execute(Query[] queries, ParameterList[] parameterLists, BatchResultHandler
*
* @param sql the SQL for the query to create
* @return a new Query object
* @throws SQLException if something goes wrong
*/
Query createSimpleQuery(String sql) throws SQLException;

@@ -178,6 +187,13 @@ CachedQuery createQuery(String sql, boolean escapeProcessing, boolean isParamete
String... columnNames)
throws SQLException;

Object createQueryKey(String sql, boolean escapeProcessing, boolean isParameterized,
String... columnNames);

CachedQuery createQueryByKey(Object key) throws SQLException;

CachedQuery borrowQueryByKey(Object key) throws SQLException;

CachedQuery borrowQuery(String sql) throws SQLException;

CachedQuery borrowCallableQuery(String sql) throws SQLException;
@@ -390,4 +406,6 @@ CachedQuery createQuery(String sql, boolean escapeProcessing, boolean isParamete
String getApplicationName();

boolean isColumnSanitiserDisabled();

PreferQueryMode getPreferQueryMode();
}

0 comments on commit 232569c

Please sign in to comment.
You can’t perform that action at this time.