diff --git a/pgjdbc/src/main/java/org/postgresql/core/ResultHandler.java b/pgjdbc/src/main/java/org/postgresql/core/ResultHandler.java index 4b9e01c469..cb25afdf7e 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 @@ void handleResultRows(Query fromQuery, Field[] fields, List tuples, * @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 d16da9e83b..c6dd206d08 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 void handleResultRows(Query fromQuery, Field[] fields, List tup } @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 34e96d189a..926e59bc1d 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 void handleResultRows(Query fromQuery, Field[] fields, List tup } @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 6c5cc1e6db..23fcde5980 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.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 void handleResultRows(Query fromQuery, Field[] fields, List tup } } - 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 void doSubprotocolBegin() throws SQLException { 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 void handleCommandStatus(String status, int updateCount, long insertOID) } } + @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 synchronized void fetch(ResultCursor cursor, ResultHandler handler, int f // (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(), null); } }; @@ -2539,16 +2542,7 @@ private void interpretCommandStatus(String status, ResultHandler handler) { 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 5cf84ff3fb..b88807c748 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 @@ * 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> allGeneratedRows; + private final List> allGeneratedRows; private List 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>(); } + @Override public void handleResultRows(Query fromQuery, Field[] fields, List tuples, ResultCursor cursor) { // If SELECT, then handleCommandStatus call would just be missing @@ -63,9 +65,8 @@ public void handleResultRows(Query fromQuery, Field[] fields, List tup 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(), cursor); + latestGeneratedKeysRs = (PgResultSet) pgStatement.createResultSet(fromQuery, fields, + new ArrayList(), cursor); } catch (SQLException e) { handleError(e); } @@ -73,7 +74,8 @@ public void handleResultRows(Query fromQuery, Field[] fields, List tup 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 void handleCommandStatus(String status, int updateCount, long insertOID) } latestGeneratedKeysRs = null; - updateCounts[resultIndex++] = updateCount; + longUpdateCounts[resultIndex++] = updateCount; } private boolean isAutoCommit() { @@ -125,6 +127,7 @@ private void updateGeneratedKeys() { allGeneratedRows.clear(); } + @Override public void handleWarning(SQLWarning warning) { pgStatement.addWarning(warning); } @@ -132,7 +135,7 @@ public void handleWarning(SQLWarning warning) { @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 void handleError(SQLException newError) { 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 void handleError(SQLException newError) { 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 ResultSet getGeneratedKeys() { } 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 @@ private int[] uncompressUpdateCount() { 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 @@ private int[] uncompressUpdateCount() { * 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 @@ private int[] uncompressUpdateCount() { 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 8e66bc779d..3c5ee62687 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 @@ public int executeUpdate(String sql) throws SQLException { @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 107e9e642a..9cc490a00e 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 @@ private void updateRowBuffer() throws SQLException { public class CursorResultHandler extends ResultHandlerBase { + @Override public void handleResultRows(Query fromQuery, Field[] fields, List 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 5dcf4c4f79..3940e7a2c7 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 void handleResultRows(Query fromQuery, Field[] fields, List tup } @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 @@ protected ResultSet getSingleResultSet() throws SQLException { @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 @@ protected int getNoResultUpdateCount() throws SQLException { 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 void setCursorName(String name) throws SQLException { private volatile boolean isClosed = false; + @Override public int getUpdateCount() throws SQLException { synchronized (this) { checkClosed(); @@ -477,7 +476,8 @@ public int getUpdateCount() throws SQLException { return -1; } - return result.getUpdateCount(); + long count = result.getUpdateCount(); + return count > Integer.MAX_VALUE ? Statement.SUCCESS_NO_INFO : (int) count; } } @@ -739,26 +739,17 @@ protected BatchResultHandler createBatchHandler(Query[] queries, 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 int[] executeBatch() throws SQLException { } } } + 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 @@ protected boolean getForceBinaryTransfer() { 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 long getLargeMaxRows() throws SQLException { 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 3fb0034527..df79ae7873 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 ResultWrapper(ResultSet rs) { 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 ResultSet getResultSet() { return rs; } - public int getUpdateCount() { + public long getUpdateCount() { return updateCount; } @@ -53,7 +53,7 @@ public void append(ResultWrapper newResult) { } 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 211d01eb3f..e3b005f060 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 static void createTempTable(Connection con, String table, String columns) } } + /* + * 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 6b92e46f83..f3252783c5 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 @@ 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 0000000000..fef3362566 --- /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 data() { + Collection 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); + } + } + +}