postgresql-jdbc.git / summary / log / commit / refs
commit 0888e9355ca065ac2eae4e3085442ffd54f6dec6
Author: Jorge Solorzano <jorsol@gmail.com>
Commit: Dave Cramer <davecramer@gmail.com>
Date: Tue Oct 01 17:38:49 2019 +0000
feat: support for large update counts (JDBC 4.2) (#935)
Added support for large update counts of the JDBC 4.2 API (Java 8+)
This implementation supports:
PreparedStatement.executeLargeUpdate()
Statement.executeLargeUpdate(String sql)
Statement.getLargeUpdateCount()
Statement.executeLargeBatch()
Statement.executeLargeUpdate(String sql, int autoGeneratedKeys) *
Statement.executeLargeUpdate(String sql, int[] columnIndexes) *
Statement.executeLargeUpdate(String sql, String[] columnNames) *
.../java/org/postgresql/core/ResultHandler.java | 2 +-
.../org/postgresql/core/ResultHandlerBase.java | 2 +-
.../org/postgresql/core/ResultHandlerDelegate.java | 2 +-
.../org/postgresql/core/v3/QueryExecutorImpl.java | 22 +-
.../org/postgresql/jdbc/BatchResultHandler.java | 83 +++--
.../org/postgresql/jdbc/PgPreparedStatement.java | 11 +-
.../main/java/org/postgresql/jdbc/PgResultSet.java | 5 +-
.../main/java/org/postgresql/jdbc/PgStatement.java | 96 +++--
.../java/org/postgresql/jdbc/ResultWrapper.java | 6 +-
.../test/java/org/postgresql/test/TestUtil.java | 20 +
.../postgresql/test/jdbc42/Jdbc42TestSuite.java | 1 +
.../test/jdbc42/LargeCountJdbc42Test.java | 412 +++++++++++++++++++++
12 files changed, 588 insertions(+), 74 deletions(-)
diff --git a/pgjdbc/src/main/java/org/postgresql/core/ResultHandler.java b/pgjdbc/src/main/java/org/postgresql/core/ResultHandler.java
index 4b9e01c4..cb25afdf 100644
--- a/pgjdbc/src/main/java/org/postgresql/core/ResultHandler.java
+++ b/pgjdbc/src/main/java/org/postgresql/core/ResultHandler.java
@@ -48,7 +48,7 @@ public interface ResultHandler {
* @param insertOID for a single-row INSERT query, the OID of the newly inserted row; 0 if not
* available.
*/
- void handleCommandStatus(String status, int updateCount, long insertOID);
+ void handleCommandStatus(String status, long updateCount, long insertOID);
/**
* Called when a warning is emitted.
diff --git a/pgjdbc/src/main/java/org/postgresql/core/ResultHandlerBase.java b/pgjdbc/src/main/java/org/postgresql/core/ResultHandlerBase.java
index d16da9e8..c6dd206d 100644
--- a/pgjdbc/src/main/java/org/postgresql/core/ResultHandlerBase.java
+++ b/pgjdbc/src/main/java/org/postgresql/core/ResultHandlerBase.java
@@ -30,7 +30,7 @@ public class ResultHandlerBase implements ResultHandler {
}
@Override
- public void handleCommandStatus(String status, int updateCount, long insertOID) {
+ public void handleCommandStatus(String status, long updateCount, long insertOID) {
}
@Override
diff --git a/pgjdbc/src/main/java/org/postgresql/core/ResultHandlerDelegate.java b/pgjdbc/src/main/java/org/postgresql/core/ResultHandlerDelegate.java
index 34e96d18..926e59bc 100644
--- a/pgjdbc/src/main/java/org/postgresql/core/ResultHandlerDelegate.java
+++ b/pgjdbc/src/main/java/org/postgresql/core/ResultHandlerDelegate.java
@@ -31,7 +31,7 @@ public class ResultHandlerDelegate implements ResultHandler {
}
@Override
- public void handleCommandStatus(String status, int updateCount, long insertOID) {
+ public void handleCommandStatus(String status, long updateCount, long insertOID) {
if (delegate != null) {
delegate.handleCommandStatus(status, updateCount, insertOID);
}
diff --git a/pgjdbc/src/main/java/org/postgresql/core/v3/QueryExecutorImpl.java b/pgjdbc/src/main/java/org/postgresql/core/v3/QueryExecutorImpl.java
index 6c5cc1e6..23fcde59 100644
--- a/pgjdbc/src/main/java/org/postgresql/core/v3/QueryExecutorImpl.java
+++ b/pgjdbc/src/main/java/org/postgresql/core/v3/QueryExecutorImpl.java
@@ -51,7 +51,6 @@ import java.net.SocketException;
import java.net.SocketTimeoutException;
import java.sql.SQLException;
import java.sql.SQLWarning;
-import java.sql.Statement;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collections;
@@ -558,7 +557,8 @@ public class QueryExecutorImpl extends QueryExecutorBase {
}
}
- public void handleCommandStatus(String status, int updateCount, long insertOID) {
+ @Override
+ public void handleCommandStatus(String status, long updateCount, long insertOID) {
if (!sawBegin) {
sawBegin = true;
if (!status.equals("BEGIN")) {
@@ -600,7 +600,8 @@ public class QueryExecutorImpl extends QueryExecutorBase {
ResultHandler handler = new ResultHandlerBase() {
private boolean sawBegin = false;
- public void handleCommandStatus(String status, int updateCount, long insertOID) {
+ @Override
+ public void handleCommandStatus(String status, long updateCount, long insertOID) {
if (!sawBegin) {
if (!status.equals("BEGIN")) {
handleError(
@@ -614,6 +615,7 @@ public class QueryExecutorImpl extends QueryExecutorBase {
}
}
+ @Override
public void handleWarning(SQLWarning warning) {
// we don't want to ignore warnings and it would be tricky
// to chain them back to the connection, so since we don't
@@ -2408,7 +2410,8 @@ public class QueryExecutorImpl extends QueryExecutorBase {
// (if the fetch returns no rows, we see just a CommandStatus..)
final ResultHandler delegateHandler = handler;
handler = new ResultHandlerDelegate(delegateHandler) {
- public void handleCommandStatus(String status, int updateCount, long insertOID) {
+ @Override
+ public void handleCommandStatus(String status, long updateCount, long insertOID) {
handleResultRows(portal.getQuery(), null, new ArrayList<byte[][]>(), null);
}
};
@@ -2539,16 +2542,7 @@ public class QueryExecutorImpl extends QueryExecutorBase {
long oid = commandCompleteParser.getOid();
long count = commandCompleteParser.getRows();
- int countAsInt = 0;
- if (count > Integer.MAX_VALUE) {
- // If command status indicates that we've modified more than Integer.MAX_VALUE rows
- // then we set the result count to reflect that we cannot provide the actual number
- // due to the JDBC field being an int rather than a long.
- countAsInt = Statement.SUCCESS_NO_INFO;
- } else if (count > 0) {
- countAsInt = (int) count;
- }
- handler.handleCommandStatus(status, countAsInt, oid);
+ handler.handleCommandStatus(status, count, oid);
}
private void receiveRFQ() throws IOException {
diff --git a/pgjdbc/src/main/java/org/postgresql/jdbc/BatchResultHandler.java b/pgjdbc/src/main/java/org/postgresql/jdbc/BatchResultHandler.java
index 5cf84ff3..b88807c7 100644
--- a/pgjdbc/src/main/java/org/postgresql/jdbc/BatchResultHandler.java
+++ b/pgjdbc/src/main/java/org/postgresql/jdbc/BatchResultHandler.java
@@ -28,16 +28,17 @@ import java.util.List;
* Internal class, it is not a part of public API.
*/
public class BatchResultHandler extends ResultHandlerBase {
- private PgStatement pgStatement;
+
+ private final PgStatement pgStatement;
private int resultIndex = 0;
private final Query[] queries;
- private final int[] updateCounts;
+ private final long[] longUpdateCounts;
private final ParameterList[] parameterLists;
private final boolean expectGeneratedKeys;
private PgResultSet generatedKeys;
private int committedRows; // 0 means no rows committed. 1 means row 0 was committed, and so on
- private List<List<byte[][]>> allGeneratedRows;
+ private final List<List<byte[][]>> allGeneratedRows;
private List<byte[][]> latestGeneratedRows;
private PgResultSet latestGeneratedKeysRs;
@@ -46,11 +47,12 @@ public class BatchResultHandler extends ResultHandlerBase {
this.pgStatement = pgStatement;
this.queries = queries;
this.parameterLists = parameterLists;
- this.updateCounts = new int[queries.length];
+ this.longUpdateCounts = new long[queries.length];
this.expectGeneratedKeys = expectGeneratedKeys;
this.allGeneratedRows = !expectGeneratedKeys ? null : new ArrayList<List<byte[][]>>();
}
+ @Override
public void handleResultRows(Query fromQuery, Field[] fields, List<byte[][]> tuples,
ResultCursor cursor) {
// If SELECT, then handleCommandStatus call would just be missing
@@ -63,9 +65,8 @@ public class BatchResultHandler extends ResultHandlerBase {
try {
// If SELECT, the resulting ResultSet is not valid
// Thus it is up to handleCommandStatus to decide if resultSet is good enough
- latestGeneratedKeysRs =
- (PgResultSet) pgStatement.createResultSet(fromQuery, fields,
- new ArrayList<byte[][]>(), cursor);
+ latestGeneratedKeysRs = (PgResultSet) pgStatement.createResultSet(fromQuery, fields,
+ new ArrayList<byte[][]>(), cursor);
} catch (SQLException e) {
handleError(e);
}
@@ -73,7 +74,8 @@ public class BatchResultHandler extends ResultHandlerBase {
latestGeneratedRows = tuples;
}
- public void handleCommandStatus(String status, int updateCount, long insertOID) {
+ @Override
+ public void handleCommandStatus(String status, long updateCount, long insertOID) {
if (latestGeneratedRows != null) {
// We have DML. Decrease resultIndex that was just increased in handleResultRows
resultIndex--;
@@ -95,7 +97,7 @@ public class BatchResultHandler extends ResultHandlerBase {
}
latestGeneratedKeysRs = null;
- updateCounts[resultIndex++] = updateCount;
+ longUpdateCounts[resultIndex++] = updateCount;
}
private boolean isAutoCommit() {
@@ -125,6 +127,7 @@ public class BatchResultHandler extends ResultHandlerBase {
allGeneratedRows.clear();
}
+ @Override
public void handleWarning(SQLWarning warning) {
pgStatement.addWarning(warning);
}
@@ -132,7 +135,7 @@ public class BatchResultHandler extends ResultHandlerBase {
@Override
public void handleError(SQLException newError) {
if (getException() == null) {
- Arrays.fill(updateCounts, committedRows, updateCounts.length, Statement.EXECUTE_FAILED);
+ Arrays.fill(longUpdateCounts, committedRows, longUpdateCounts.length, Statement.EXECUTE_FAILED);
if (allGeneratedRows != null) {
allGeneratedRows.clear();
}
@@ -142,11 +145,19 @@ public class BatchResultHandler extends ResultHandlerBase {
queryString = queries[resultIndex].toString(parameterLists[resultIndex]);
}
- BatchUpdateException batchException = new BatchUpdateException(
+ BatchUpdateException batchException;
+ //#if mvn.project.property.postgresql.jdbc.spec >= "JDBC4.2"
+ batchException = new BatchUpdateException(
+ GT.tr("Batch entry {0} {1} was aborted: {2} Call getNextException to see other errors in the batch.",
+ resultIndex, queryString, newError.getMessage()),
+ newError.getSQLState(), 0, uncompressLongUpdateCount(), newError);
+ //#else
+ batchException = new BatchUpdateException(
GT.tr("Batch entry {0} {1} was aborted: {2} Call getNextException to see other errors in the batch.",
resultIndex, queryString, newError.getMessage()),
- newError.getSQLState(), uncompressUpdateCount());
- batchException.initCause(newError);
+ newError.getSQLState(), 0, uncompressUpdateCount(), newError);
+ //#endif
+
super.handleError(batchException);
}
resultIndex++;
@@ -154,18 +165,30 @@ public class BatchResultHandler extends ResultHandlerBase {
super.handleError(newError);
}
+ @Override
public void handleCompletion() throws SQLException {
updateGeneratedKeys();
SQLException batchException = getException();
if (batchException != null) {
if (isAutoCommit()) {
// Re-create batch exception since rows after exception might indeed succeed.
- BatchUpdateException newException = new BatchUpdateException(
+ BatchUpdateException newException;
+ //#if mvn.project.property.postgresql.jdbc.spec >= "JDBC4.2"
+ newException = new BatchUpdateException(
batchException.getMessage(),
- batchException.getSQLState(),
- uncompressUpdateCount()
+ batchException.getSQLState(), 0,
+ uncompressLongUpdateCount(),
+ batchException.getCause()
);
- newException.initCause(batchException.getCause());
+ //#else
+ newException = new BatchUpdateException(
+ batchException.getMessage(),
+ batchException.getSQLState(), 0,
+ uncompressUpdateCount(),
+ batchException.getCause()
+ );
+ //#endif
+
SQLException next = batchException.getNextException();
if (next != null) {
newException.setNextException(next);
@@ -181,8 +204,21 @@ public class BatchResultHandler extends ResultHandlerBase {
}
private int[] uncompressUpdateCount() {
+ long[] original = uncompressLongUpdateCount();
+ int[] copy = new int[original.length];
+ for (int i = 0; i < original.length; i++) {
+ copy[i] = original[i] > Integer.MAX_VALUE ? Statement.SUCCESS_NO_INFO : (int) original[i];
+ }
+ return copy;
+ }
+
+ public int[] getUpdateCount() {
+ return uncompressUpdateCount();
+ }
+
+ private long[] uncompressLongUpdateCount() {
if (!(queries[0] instanceof BatchedQuery)) {
- return updateCounts;
+ return longUpdateCounts;
}
int totalRows = 0;
boolean hasRewrites = false;
@@ -192,7 +228,7 @@ public class BatchResultHandler extends ResultHandlerBase {
hasRewrites |= batchSize > 1;
}
if (!hasRewrites) {
- return updateCounts;
+ return longUpdateCounts;
}
/* In this situation there is a batch that has been rewritten. Substitute
@@ -200,12 +236,12 @@ public class BatchResultHandler extends ResultHandlerBase {
* indicate successful completion for each row the driver client added
* to the batch.
*/
- int[] newUpdateCounts = new int[totalRows];
+ long[] newUpdateCounts = new long[totalRows];
int offset = 0;
for (int i = 0; i < queries.length; i++) {
Query query = queries[i];
int batchSize = query.getBatchSize();
- int superBatchResult = updateCounts[i];
+ long superBatchResult = longUpdateCounts[i];
if (batchSize == 1) {
newUpdateCounts[offset++] = superBatchResult;
continue;
@@ -221,7 +257,8 @@ public class BatchResultHandler extends ResultHandlerBase {
return newUpdateCounts;
}
- public int[] getUpdateCount() {
- return uncompressUpdateCount();
+ public long[] getLargeUpdateCount() {
+ return uncompressLongUpdateCount();
}
+
}
diff --git a/pgjdbc/src/main/java/org/postgresql/jdbc/PgPreparedStatement.java b/pgjdbc/src/main/java/org/postgresql/jdbc/PgPreparedStatement.java
index 8e66bc77..3c5ee626 100644
--- a/pgjdbc/src/main/java/org/postgresql/jdbc/PgPreparedStatement.java
+++ b/pgjdbc/src/main/java/org/postgresql/jdbc/PgPreparedStatement.java
@@ -122,9 +122,18 @@ class PgPreparedStatement extends PgStatement implements PreparedStatement {
@Override
public int executeUpdate() throws SQLException {
executeWithFlags(QueryExecutor.QUERY_NO_RESULTS);
+ checkNoResultUpdate();
+ return getUpdateCount();
+ }
- return getNoResultUpdateCount();
+ //#if mvn.project.property.postgresql.jdbc.spec >= "JDBC4.2"
+ @Override
+ public long executeLargeUpdate() throws SQLException {
+ executeWithFlags(QueryExecutor.QUERY_NO_RESULTS);
+ checkNoResultUpdate();
+ return getLargeUpdateCount();
}
+ //#endif
@Override
public boolean execute(String sql) throws SQLException {
diff --git a/pgjdbc/src/main/java/org/postgresql/jdbc/PgResultSet.java b/pgjdbc/src/main/java/org/postgresql/jdbc/PgResultSet.java
index 107e9e64..9cc490a0 100644
--- a/pgjdbc/src/main/java/org/postgresql/jdbc/PgResultSet.java
+++ b/pgjdbc/src/main/java/org/postgresql/jdbc/PgResultSet.java
@@ -1744,17 +1744,20 @@ public class PgResultSet implements ResultSet, org.postgresql.PGRefCursorResultS
public class CursorResultHandler extends ResultHandlerBase {
+ @Override
public void handleResultRows(Query fromQuery, Field[] fields, List<byte[][]> tuples,
ResultCursor cursor) {
PgResultSet.this.rows = tuples;
PgResultSet.this.cursor = cursor;
}
- public void handleCommandStatus(String status, int updateCount, long insertOID) {
+ @Override
+ public void handleCommandStatus(String status, long updateCount, long insertOID) {
handleError(new PSQLException(GT.tr("Unexpected command status: {0}.", status),
PSQLState.PROTOCOL_VIOLATION));
}
+ @Override
public void handleCompletion() throws SQLException {
SQLWarning warning = getWarning();
if (warning != null) {
diff --git a/pgjdbc/src/main/java/org/postgresql/jdbc/PgStatement.java b/pgjdbc/src/main/java/org/postgresql/jdbc/PgStatement.java
index 5dcf4c4f..3940e7a2 100644
--- a/pgjdbc/src/main/java/org/postgresql/jdbc/PgStatement.java
+++ b/pgjdbc/src/main/java/org/postgresql/jdbc/PgStatement.java
@@ -209,7 +209,7 @@ public class PgStatement implements Statement, BaseStatement {
}
@Override
- public void handleCommandStatus(String status, int updateCount, long insertOID) {
+ public void handleCommandStatus(String status, long updateCount, long insertOID) {
append(new ResultWrapper(updateCount, insertOID));
}
@@ -244,10 +244,11 @@ public class PgStatement implements Statement, BaseStatement {
@Override
public int executeUpdate(String sql) throws SQLException {
executeWithFlags(sql, QueryExecutor.QUERY_NO_RESULTS);
- return getNoResultUpdateCount();
+ checkNoResultUpdate();
+ return getUpdateCount();
}
- protected int getNoResultUpdateCount() throws SQLException {
+ protected final void checkNoResultUpdate() throws SQLException {
synchronized (this) {
checkClosed();
ResultWrapper iter = result;
@@ -255,12 +256,9 @@ public class PgStatement implements Statement, BaseStatement {
if (iter.getResultSet() != null) {
throw new PSQLException(GT.tr("A result was returned when none was expected."),
PSQLState.TOO_MANY_RESULTS);
-
}
iter = iter.getNext();
}
-
- return getUpdateCount();
}
}
@@ -470,6 +468,7 @@ public class PgStatement implements Statement, BaseStatement {
private volatile boolean isClosed = false;
+ @Override
public int getUpdateCount() throws SQLException {
synchronized (this) {
checkClosed();
@@ -477,7 +476,8 @@ public class PgStatement implements Statement, BaseStatement {
return -1;
}
- return result.getUpdateCount();
+ long count = result.getUpdateCount();
+ return count > Integer.MAX_VALUE ? Statement.SUCCESS_NO_INFO : (int) count;
}
}
@@ -739,26 +739,17 @@ public class PgStatement implements Statement, BaseStatement {
wantsGeneratedKeysAlways);
}
- public int[] executeBatch() throws SQLException {
- checkClosed();
-
- closeForNextExecution();
-
- if (batchStatements == null || batchStatements.isEmpty()) {
- return new int[0];
- }
-
+ private BatchResultHandler internalExecuteBatch() throws SQLException {
// Construct query/parameter arrays.
transformQueriesAndParameters();
// Empty arrays should be passed to toArray
// see http://shipilev.net/blog/2016/arrays-wisdom-ancients/
Query[] queries = batchStatements.toArray(new Query[0]);
- ParameterList[] parameterLists =
- batchParameters.toArray(new ParameterList[0]);
+ ParameterList[] parameterLists = batchParameters.toArray(new ParameterList[0]);
batchStatements.clear();
batchParameters.clear();
- int flags = 0;
+ int flags;
// Force a Describe before any execution? We need to do this if we're going
// to send anything dependent on the Describe results, e.g. binary parameters.
@@ -862,8 +853,18 @@ public class PgStatement implements Statement, BaseStatement {
}
}
}
+ return handler;
+ }
+
+ public int[] executeBatch() throws SQLException {
+ checkClosed();
+ closeForNextExecution();
+
+ if (batchStatements == null || batchStatements.isEmpty()) {
+ return new int[0];
+ }
- return handler.getUpdateCount();
+ return internalExecuteBatch().getUpdateCount();
}
public void cancel() throws SQLException {
@@ -1012,8 +1013,17 @@ public class PgStatement implements Statement, BaseStatement {
return forceBinaryTransfers;
}
+ //#if mvn.project.property.postgresql.jdbc.spec >= "JDBC4.2"
+ @Override
public long getLargeUpdateCount() throws SQLException {
- throw Driver.notImplemented(this.getClass(), "getLargeUpdateCount");
+ synchronized (this) {
+ checkClosed();
+ if (result == null || result.getResultSet() != null) {
+ return -1;
+ }
+
+ return result.getUpdateCount();
+ }
}
public void setLargeMaxRows(long max) throws SQLException {
@@ -1024,29 +1034,57 @@ public class PgStatement implements Statement, BaseStatement {
throw Driver.notImplemented(this.getClass(), "getLargeMaxRows");
}
+ @Override
public long[] executeLargeBatch() throws SQLException {
- throw Driver.notImplemented(this.getClass(), "executeLargeBatch");
+ checkClosed();
+ closeForNextExecution();
+
+ if (batchStatements == null || batchStatements.isEmpty()) {
+ return new long[0];
+ }
+
+ return internalExecuteBatch().getLargeUpdateCount();
}
+ @Override
public long executeLargeUpdate(String sql) throws SQLException {
- throw Driver.notImplemented(this.getClass(), "executeLargeUpdate");
+ executeWithFlags(sql, QueryExecutor.QUERY_NO_RESULTS);
+ checkNoResultUpdate();
+ return getLargeUpdateCount();
}
+ @Override
public long executeLargeUpdate(String sql, int autoGeneratedKeys) throws SQLException {
- throw Driver.notImplemented(this.getClass(), "executeLargeUpdate");
+ if (autoGeneratedKeys == Statement.NO_GENERATED_KEYS) {
+ return executeLargeUpdate(sql);
+ }
+
+ return executeLargeUpdate(sql, (String[]) null);
}
+ @Override
public long executeLargeUpdate(String sql, int[] columnIndexes) throws SQLException {
- throw Driver.notImplemented(this.getClass(), "executeLargeUpdate");
+ if (columnIndexes == null || columnIndexes.length == 0) {
+ return executeLargeUpdate(sql);
+ }
+
+ throw new PSQLException(GT.tr("Returning autogenerated keys by column index is not supported."),
+ PSQLState.NOT_IMPLEMENTED);
}
+ @Override
public long executeLargeUpdate(String sql, String[] columnNames) throws SQLException {
- throw Driver.notImplemented(this.getClass(), "executeLargeUpdate");
- }
+ if (columnNames != null && columnNames.length == 0) {
+ return executeLargeUpdate(sql);
+ }
- public long executeLargeUpdate() throws SQLException {
- throw Driver.notImplemented(this.getClass(), "executeLargeUpdate");
+ wantsGeneratedKeysOnce = true;
+ if (!executeCachedSql(sql, 0, columnNames)) {
+ // no resultset returned. What's a pity!
+ }
+ return getLargeUpdateCount();
}
+ //#endif
public boolean isClosed() throws SQLException {
return isClosed;
diff --git a/pgjdbc/src/main/java/org/postgresql/jdbc/ResultWrapper.java b/pgjdbc/src/main/java/org/postgresql/jdbc/ResultWrapper.java
index 3fb00345..df79ae78 100644
--- a/pgjdbc/src/main/java/org/postgresql/jdbc/ResultWrapper.java
+++ b/pgjdbc/src/main/java/org/postgresql/jdbc/ResultWrapper.java
@@ -21,7 +21,7 @@ public class ResultWrapper {
this.insertOID = -1;
}
- public ResultWrapper(int updateCount, long insertOID) {
+ public ResultWrapper(long updateCount, long insertOID) {
this.rs = null;
this.updateCount = updateCount;
this.insertOID = insertOID;
@@ -31,7 +31,7 @@ public class ResultWrapper {
return rs;
}
- public int getUpdateCount() {
+ public long getUpdateCount() {
return updateCount;
}
@@ -53,7 +53,7 @@ public class ResultWrapper {
}
private final ResultSet rs;
- private final int updateCount;
+ private final long updateCount;
private final long insertOID;
private ResultWrapper next;
}
diff --git a/pgjdbc/src/test/java/org/postgresql/test/TestUtil.java b/pgjdbc/src/test/java/org/postgresql/test/TestUtil.java
index 211d01eb..e3b005f0 100644
--- a/pgjdbc/src/test/java/org/postgresql/test/TestUtil.java
+++ b/pgjdbc/src/test/java/org/postgresql/test/TestUtil.java
@@ -437,6 +437,26 @@ public class TestUtil {
}
}
+ /*
+ * Helper - creates a unlogged table for use by a test.
+ * Unlogged tables works from PostgreSQL 9.1+
+ */
+ public static void createUnloggedTable(Connection con, String table, String columns)
+ throws SQLException {
+ Statement st = con.createStatement();
+ try {
+ // Drop the table
+ dropTable(con, table);
+
+ String unlogged = haveMinimumServerVersion(con, ServerVersion.v9_1) ? "UNLOGGED" : "";
+
+ // Now create the table
+ st.executeUpdate("CREATE " + unlogged + " TABLE " + table + " (" + columns + ")");
+ } finally {
+ closeQuietly(st);
+ }
+ }
+
/**
* Helper creates an enum type.
*
diff --git a/pgjdbc/src/test/java/org/postgresql/test/jdbc42/Jdbc42TestSuite.java b/pgjdbc/src/test/java/org/postgresql/test/jdbc42/Jdbc42TestSuite.java
index 6b92e46f..f3252783 100644
--- a/pgjdbc/src/test/java/org/postgresql/test/jdbc42/Jdbc42TestSuite.java
+++ b/pgjdbc/src/test/java/org/postgresql/test/jdbc42/Jdbc42TestSuite.java
@@ -18,6 +18,7 @@ import org.junit.runners.Suite.SuiteClasses;
PreparedStatementTest.class,
SetObject310Test.class,
SimpleJdbc42Test.class,
+ LargeCountJdbc42Test.class
})
public class Jdbc42TestSuite {
diff --git a/pgjdbc/src/test/java/org/postgresql/test/jdbc42/LargeCountJdbc42Test.java b/pgjdbc/src/test/java/org/postgresql/test/jdbc42/LargeCountJdbc42Test.java
new file mode 100644
index 00000000..fef33625
--- /dev/null
+++ b/pgjdbc/src/test/java/org/postgresql/test/jdbc42/LargeCountJdbc42Test.java
@@ -0,0 +1,412 @@
+/*
+ * Copyright (c) 2018, PostgreSQL Global Development Group
+ * See the LICENSE file in the project root for more information.
+ */
+
+package org.postgresql.test.jdbc42;
+
+import org.postgresql.PGProperty;
+import org.postgresql.test.TestUtil;
+import org.postgresql.test.jdbc2.BaseTest4;
+import org.postgresql.util.PSQLState;
+
+import org.junit.Assert;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.sql.PreparedStatement;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Properties;
+
+/**
+ * Test methods with small counts that return long and failure scenarios. This have two really big
+ * and slow test, they are ignored for CI but can be tested locally to check that it works.
+ */
+@RunWith(Parameterized.class)
+public class LargeCountJdbc42Test extends BaseTest4 {
+
+ private final boolean insertRewrite;
+
+ public LargeCountJdbc42Test(BinaryMode binaryMode, boolean insertRewrite) {
+ this.insertRewrite = insertRewrite;
+ setBinaryMode(binaryMode);
+ }
+
+ @Parameterized.Parameters(name = "binary = {0}, insertRewrite = {1}")
+ public static Iterable<Object[]> data() {
+ Collection<Object[]> ids = new ArrayList<>();
+ for (BinaryMode binaryMode : BinaryMode.values()) {
+ for (boolean insertRewrite : new boolean[]{false, true}) {
+ ids.add(new Object[]{binaryMode, insertRewrite});
+ }
+ }
+ return ids;
+ }
+
+ @Override
+ protected void updateProperties(Properties props) {
+ super.updateProperties(props);
+ PGProperty.REWRITE_BATCHED_INSERTS.set(props, insertRewrite);
+ }
+
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+ TestUtil.createUnloggedTable(con, "largetable", "a boolean");
+ }
+
+ @Override
+ public void tearDown() throws SQLException {
+ TestUtil.dropTable(con, "largetable");
+ TestUtil.closeDB(con);
+ }
+
+ // ********************* EXECUTE LARGE UPDATES *********************
+ // FINEST: simple execute, handler=org.postgresql.jdbc.PgStatement$StatementResultHandler@38cccef, maxRows=0, fetchSize=0, flags=21
+ // FINEST: FE=> Parse(stmt=null,query="insert into largetable select true from generate_series($1, $2)",oids={20,20})
+ // FINEST: FE=> Bind(stmt=null,portal=null,$1=<1>,$2=<2147483757>)
+ // FINEST: FE=> Describe(portal=null)
+ // FINEST: FE=> Execute(portal=null,limit=1)
+ // FINEST: FE=> Sync
+ // FINEST: <=BE ParseComplete [null]
+ // FINEST: <=BE BindComplete [unnamed]
+ // FINEST: <=BE NoData
+ // FINEST: <=BE CommandStatus(INSERT 0 2147483757)
+ // FINEST: <=BE ReadyForQuery(I)
+ // FINEST: simple execute, handler=org.postgresql.jdbc.PgStatement$StatementResultHandler@5679c6c6, maxRows=0, fetchSize=0, flags=21
+ // FINEST: FE=> Parse(stmt=null,query="delete from largetable",oids={})
+ // FINEST: FE=> Bind(stmt=null,portal=null)
+ // FINEST: FE=> Describe(portal=null)
+ // FINEST: FE=> Execute(portal=null,limit=1)
+ // FINEST: FE=> Sync
+ // FINEST: <=BE ParseComplete [null]
+ // FINEST: <=BE BindComplete [unnamed]
+ // FINEST: <=BE NoData
+ // FINEST: <=BE CommandStatus(DELETE 2147483757)
+
+ /*
+ * Test PreparedStatement.executeLargeUpdate() and Statement.executeLargeUpdate(String sql)
+ */
+ @Ignore("This is the big and SLOW test")
+ @Test
+ public void testExecuteLargeUpdateBIG() throws Exception {
+ long expected = Integer.MAX_VALUE + 110L;
+ con.setAutoCommit(false);
+ // Test PreparedStatement.executeLargeUpdate()
+ try (PreparedStatement stmt = con.prepareStatement("insert into largetable "
+ + "select true from generate_series(?, ?)")) {
+ stmt.setLong(1, 1);
+ stmt.setLong(2, 2_147_483_757L); // Integer.MAX_VALUE + 110L
+ long count = stmt.executeLargeUpdate();
+ Assert.assertEquals("PreparedStatement 110 rows more than Integer.MAX_VALUE", expected, count);
+ }
+ // Test Statement.executeLargeUpdate(String sql)
+ try (Statement stmt = con.createStatement()) {
+ long count = stmt.executeLargeUpdate("delete from largetable");
+ Assert.assertEquals("Statement 110 rows more than Integer.MAX_VALUE", expected, count);
+ }
+ con.setAutoCommit(true);
+ }
+
+ /*
+ * Test Statement.executeLargeUpdate(String sql)
+ */
+ @Test
+ public void testExecuteLargeUpdateStatementSMALL() throws Exception {
+ try (Statement stmt = con.createStatement()) {
+ long count = stmt.executeLargeUpdate("insert into largetable "
+ + "select true from generate_series(1, 1010)");
+ long expected = 1010L;
+ Assert.assertEquals("Small long return 1010L", expected, count);
+ }
+ }
+
+ /*
+ * Test PreparedStatement.executeLargeUpdate();
+ */
+ @Test
+ public void testExecuteLargeUpdatePreparedStatementSMALL() throws Exception {
+ try (PreparedStatement stmt = con.prepareStatement("insert into largetable "
+ + "select true from generate_series(?, ?)")) {
+ stmt.setLong(1, 1);
+ stmt.setLong(2, 1010L);
+ long count = stmt.executeLargeUpdate();
+ long expected = 1010L;
+ Assert.assertEquals("Small long return 1010L", expected, count);
+ }
+ }
+
+ /*
+ * Test Statement.getLargeUpdateCount();
+ */
+ @Test
+ public void testGetLargeUpdateCountStatementSMALL() throws Exception {
+ try (Statement stmt = con.createStatement()) {
+ boolean isResult = stmt.execute("insert into largetable "
+ + "select true from generate_series(1, 1010)");
+ Assert.assertFalse("False if it is an update count or there are no results", isResult);
+ long count = stmt.getLargeUpdateCount();
+ long expected = 1010L;
+ Assert.assertEquals("Small long return 1010L", expected, count);
+ }
+ }
+
+ /*
+ * Test PreparedStatement.getLargeUpdateCount();
+ */
+ @Test
+ public void testGetLargeUpdateCountPreparedStatementSMALL() throws Exception {
+ try (PreparedStatement stmt = con.prepareStatement("insert into largetable "
+ + "select true from generate_series(?, ?)")) {
+ stmt.setInt(1, 1);
+ stmt.setInt(2, 1010);
+ boolean isResult = stmt.execute();
+ Assert.assertFalse("False if it is an update count or there are no results", isResult);
+ long count = stmt.getLargeUpdateCount();
+ long expected = 1010L;
+ Assert.assertEquals("Small long return 1010L", expected, count);
+ }
+ }
+
+ /*
+ * Test fail SELECT Statement.executeLargeUpdate(String sql)
+ */
+ @Test
+ public void testExecuteLargeUpdateStatementSELECT() throws Exception {
+ try (Statement stmt = con.createStatement()) {
+ long count = stmt.executeLargeUpdate("select true from generate_series(1, 5)");
+ Assert.fail("A result was returned when none was expected. Returned: " + count);
+ } catch (SQLException e) {
+ Assert.assertEquals(PSQLState.TOO_MANY_RESULTS.getState(), e.getSQLState());
+ }
+ }
+
+ /*
+ * Test fail SELECT PreparedStatement.executeLargeUpdate();
+ */
+ @Test
+ public void testExecuteLargeUpdatePreparedStatementSELECT() throws Exception {
+ try (PreparedStatement stmt = con.prepareStatement("select true from generate_series(?, ?)")) {
+ stmt.setLong(1, 1);
+ stmt.setLong(2, 5L);
+ long count = stmt.executeLargeUpdate();
+ Assert.fail("A result was returned when none was expected. Returned: " + count);
+ } catch (SQLException e) {
+ Assert.assertEquals(PSQLState.TOO_MANY_RESULTS.getState(), e.getSQLState());
+ }
+ }
+
+ /*
+ * Test Statement.getLargeUpdateCount();
+ */
+ @Test
+ public void testGetLargeUpdateCountStatementSELECT() throws Exception {
+ try (Statement stmt = con.createStatement()) {
+ boolean isResult = stmt.execute("select true from generate_series(1, 5)");
+ Assert.assertTrue("True since this is a SELECT", isResult);
+ long count = stmt.getLargeUpdateCount();
+ long expected = -1L;
+ Assert.assertEquals("-1 if the current result is a ResultSet object", expected, count);
+ }
+ }
+
+ /*
+ * Test PreparedStatement.getLargeUpdateCount();
+ */
+ @Test
+ public void testGetLargeUpdateCountPreparedStatementSELECT() throws Exception {
+ try (PreparedStatement stmt = con.prepareStatement("select true from generate_series(?, ?)")) {
+ stmt.setLong(1, 1);
+ stmt.setLong(2, 5L);
+ boolean isResult = stmt.execute();
+ Assert.assertTrue("True since this is a SELECT", isResult);
+ long count = stmt.getLargeUpdateCount();
+ long expected = -1L;
+ Assert.assertEquals("-1 if the current result is a ResultSet object", expected, count);
+ }
+ }
+
+ // ********************* BATCH LARGE UPDATES *********************
+ // FINEST: batch execute 3 queries, handler=org.postgresql.jdbc.BatchResultHandler@3d04a311, maxRows=0, fetchSize=0, flags=21
+ // FINEST: FE=> Parse(stmt=null,query="insert into largetable select true from generate_series($1, $2)",oids={23,23})
+ // FINEST: FE=> Bind(stmt=null,portal=null,$1=<1>,$2=<200>)
+ // FINEST: FE=> Describe(portal=null)
+ // FINEST: FE=> Execute(portal=null,limit=1)
+ // FINEST: FE=> Parse(stmt=null,query="insert into largetable select true from generate_series($1, $2)",oids={23,20})
+ // FINEST: FE=> Bind(stmt=null,portal=null,$1=<1>,$2=<3000000000>)
+ // FINEST: FE=> Describe(portal=null)
+ // FINEST: FE=> Execute(portal=null,limit=1)
+ // FINEST: FE=> Parse(stmt=null,query="insert into largetable select true from generate_series($1, $2)",oids={23,23})
+ // FINEST: FE=> Bind(stmt=null,portal=null,$1=<1>,$2=<50>)
+ // FINEST: FE=> Describe(portal=null)
+ // FINEST: FE=> Execute(portal=null,limit=1)
+ // FINEST: FE=> Sync
+ // FINEST: <=BE ParseComplete [null]
+ // FINEST: <=BE BindComplete [unnamed]
+ // FINEST: <=BE NoData
+ // FINEST: <=BE CommandStatus(INSERT 0 200)
+ // FINEST: <=BE ParseComplete [null]
+ // FINEST: <=BE BindComplete [unnamed]
+ // FINEST: <=BE NoData
+ // FINEST: <=BE CommandStatus(INSERT 0 3000000000)
+ // FINEST: <=BE ParseComplete [null]
+ // FINEST: <=BE BindComplete [unnamed]
+ // FINEST: <=BE NoData
+ // FINEST: <=BE CommandStatus(INSERT 0 50)
+
+ /*
+ * Test simple PreparedStatement.executeLargeBatch();
+ */
+ @Ignore("This is the big and SLOW test")
+ @Test
+ public void testExecuteLargeBatchStatementBIG() throws Exception {
+ con.setAutoCommit(false);
+ try (PreparedStatement stmt = con.prepareStatement("insert into largetable "
+ + "select true from generate_series(?, ?)")) {
+ stmt.setInt(1, 1);
+ stmt.setInt(2, 200);
+ stmt.addBatch(); // statement one
+ stmt.setInt(1, 1);
+ stmt.setLong(2, 3_000_000_000L);
+ stmt.addBatch(); // statement two
+ stmt.setInt(1, 1);
+ stmt.setInt(2, 50);
+ stmt.addBatch(); // statement three
+ long[] actual = stmt.executeLargeBatch();
+ Assert.assertArrayEquals("Large rows inserted via 3 batch", new long[]{200L, 3_000_000_000L, 50L}, actual);
+ }
+ con.setAutoCommit(true);
+ }
+
+ /*
+ * Test simple Statement.executeLargeBatch();
+ */
+ @Test
+ public void testExecuteLargeBatchStatementSMALL() throws Exception {
+ try (Statement stmt = con.createStatement()) {
+ stmt.addBatch("insert into largetable(a) select true"); // statement one
+ stmt.addBatch("insert into largetable select false"); // statement two
+ stmt.addBatch("insert into largetable(a) values(true)"); // statement three
+ stmt.addBatch("insert into largetable values(false)"); // statement four
+ long[] actual = stmt.executeLargeBatch();
+ Assert.assertArrayEquals("Rows inserted via 4 batch", new long[]{1L, 1L, 1L, 1L}, actual);
+ }
+ }
+
+ /*
+ * Test simple PreparedStatement.executeLargeBatch();
+ */
+ @Test
+ public void testExecuteLargePreparedStatementStatementSMALL() throws Exception {
+ try (PreparedStatement stmt = con.prepareStatement("insert into largetable "
+ + "select true from generate_series(?, ?)")) {
+ stmt.setInt(1, 1);
+ stmt.setInt(2, 200);
+ stmt.addBatch(); // statement one
+ stmt.setInt(1, 1);
+ stmt.setInt(2, 100);
+ stmt.addBatch(); // statement two
+ stmt.setInt(1, 1);
+ stmt.setInt(2, 50);
+ stmt.addBatch(); // statement three
+ stmt.addBatch(); // statement four, same parms as three
+ long[] actual = stmt.executeLargeBatch();
+ Assert.assertArrayEquals("Rows inserted via 4 batch", new long[]{200L, 100L, 50L, 50L}, actual);
+ }
+ }
+
+ /*
+ * Test loop PreparedStatement.executeLargeBatch();
+ */
+ @Test
+ public void testExecuteLargePreparedStatementStatementLoopSMALL() throws Exception {
+ long[] loop = {200, 100, 50, 300, 20, 60, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096};
+ try (PreparedStatement stmt = con.prepareStatement("insert into largetable "
+ + "select true from generate_series(?, ?)")) {
+ for (long i : loop) {
+ stmt.setInt(1, 1);
+ stmt.setLong(2, i);
+ stmt.addBatch();
+ }
+ long[] actual = stmt.executeLargeBatch();
+ Assert.assertArrayEquals("Rows inserted via batch", loop, actual);
+ }
+ }
+
+ /*
+ * Test loop PreparedStatement.executeLargeBatch();
+ */
+ @Test
+ public void testExecuteLargeBatchValuesInsertSMALL() throws Exception {
+ boolean[] loop = {true, false, true, false, false, false, true, true, true, true, false, true};
+ try (PreparedStatement stmt = con.prepareStatement("insert into largetable values(?)")) {
+ for (boolean i : loop) {
+ stmt.setBoolean(1, i);
+ stmt.addBatch();
+ }
+ long[] actual = stmt.executeLargeBatch();
+ Assert.assertEquals("Rows inserted via batch", loop.length, actual.length);
+ for (long i : actual) {
+ if (insertRewrite) {
+ Assert.assertEquals(Statement.SUCCESS_NO_INFO, i);
+ } else {
+ Assert.assertEquals(1, i);
+ }
+ }
+ }
+ }
+
+ /*
+ * Test null PreparedStatement.executeLargeBatch();
+ */
+ @Test
+ public void testNullExecuteLargeBatchStatement() throws Exception {
+ try (Statement stmt = con.createStatement()) {
+ long[] actual = stmt.executeLargeBatch();
+ Assert.assertArrayEquals("addBatch() not called batchStatements is null", new long[0], actual);
+ }
+ }
+
+ /*
+ * Test empty PreparedStatement.executeLargeBatch();
+ */
+ @Test
+ public void testEmptyExecuteLargeBatchStatement() throws Exception {
+ try (Statement stmt = con.createStatement()) {
+ stmt.addBatch("");
+ stmt.clearBatch();
+ long[] actual = stmt.executeLargeBatch();
+ Assert.assertArrayEquals("clearBatch() called, batchStatements.isEmpty()", new long[0], actual);
+ }
+ }
+
+ /*
+ * Test null PreparedStatement.executeLargeBatch();
+ */
+ @Test
+ public void testNullExecuteLargeBatchPreparedStatement() throws Exception {
+ try (PreparedStatement stmt = con.prepareStatement("")) {
+ long[] actual = stmt.executeLargeBatch();
+ Assert.assertArrayEquals("addBatch() not called batchStatements is null", new long[0], actual);
+ }
+ }
+
+ /*
+ * Test empty PreparedStatement.executeLargeBatch();
+ */
+ @Test
+ public void testEmptyExecuteLargeBatchPreparedStatement() throws Exception {
+ try (PreparedStatement stmt = con.prepareStatement("")) {
+ stmt.addBatch();
+ stmt.clearBatch();
+ long[] actual = stmt.executeLargeBatch();
+ Assert.assertArrayEquals("clearBatch() called, batchStatements.isEmpty()", new long[0], actual);
+ }
+ }
+
+}
[parent: bd9485ef7b88]