From c8d01828b3a043ec8cfd11104ce54edf8d9c8284 Mon Sep 17 00:00:00 2001 From: Rob Bygrave Date: Mon, 13 Mar 2017 20:50:22 +1300 Subject: [PATCH] #991 ENH: Add forUpdateNoWait() and forUpdateSkipLocked() options ... #528 ENH: Improve exception handling --- pom.xml | 4 +- .../java/io/ebean/AcquireLockException.java | 20 +++ .../java/io/ebean/DataIntegrityException.java | 16 ++ .../java/io/ebean/DuplicateKeyException.java | 14 ++ src/main/java/io/ebean/ExpressionList.java | 21 +++ src/main/java/io/ebean/Query.java | 46 ++++++ src/main/java/io/ebean/Transaction.java | 5 +- .../config/dbplatform/DataErrorType.java | 22 +++ .../config/dbplatform/DatabasePlatform.java | 17 +- .../config/dbplatform/SqlCodeTranslator.java | 50 ++++++ .../config/dbplatform/SqlErrorCodes.java | 47 ++++++ .../dbplatform/SqlExceptionTranslator.java | 15 ++ .../config/dbplatform/db2/DB2Platform.java | 8 + .../config/dbplatform/h2/H2Platform.java | 12 +- .../dbplatform/mysql/MySqlPlatform.java | 12 +- .../dbplatform/oracle/OraclePlatform.java | 12 +- .../dbplatform/postgres/PostgresPlatform.java | 20 ++- .../sqlserver/SqlServerPlatform.java | 8 + .../ebeaninternal/api/ScopedTransaction.java | 19 ++- .../java/io/ebeaninternal/api/SpiQuery.java | 5 + .../io/ebeaninternal/api/SpiTransaction.java | 7 + .../server/core/OrmQueryEngine.java | 7 + .../server/core/OrmQueryRequest.java | 5 + .../server/core/PersistRequest.java | 34 ++-- .../server/core/PersistRequestBean.java | 28 ++-- .../expression/DefaultExpressionList.java | 15 ++ .../server/expression/JunctionExpression.java | 15 ++ .../server/persist/BatchControl.java | 66 ++++---- .../server/persist/BatchedBeanHolder.java | 2 +- .../server/persist/BatchedPstmt.java | 2 +- .../server/persist/BatchedPstmtHolder.java | 6 +- .../server/persist/BatchedSqlException.java | 22 +++ .../server/persist/dml/DmlBeanPersister.java | 27 ++-- .../persist/dml/DmlBeanPersisterFactory.java | 5 +- .../io/ebeaninternal/server/query/CQuery.java | 25 +-- .../server/query/CQueryEngine.java | 33 +++- .../server/query/DefaultOrmQueryEngine.java | 8 +- .../server/querydefn/DefaultOrmQuery.java | 44 ++++-- .../server/querydefn/OrmQueryPlanKey.java | 94 ++++++----- .../transaction/ExternalJdbcTransaction.java | 3 +- .../server/transaction/JdbcTransaction.java | 77 +++++---- .../transaction/TransactionManager.java | 13 ++ .../io/ebeaninternal/api/message.properties | 4 +- .../server/querydefn/DefaultOrmQueryTest.java | 3 +- .../server/querydefn/OrmQueryPlanKeyTest.java | 147 ++++++++++-------- src/test/java/org/tests/basic/TestQuery.java | 46 ------ .../org/tests/basic/TestQueryForUpdate.java | 79 ++++++++++ .../TestInsertDataIntegrityException.java | 28 ++++ .../tests/insert/TestInsertDuplicateKey.java | 103 ++++++++++++ 49 files changed, 985 insertions(+), 336 deletions(-) create mode 100644 src/main/java/io/ebean/AcquireLockException.java create mode 100644 src/main/java/io/ebean/DataIntegrityException.java create mode 100644 src/main/java/io/ebean/DuplicateKeyException.java create mode 100644 src/main/java/io/ebean/config/dbplatform/DataErrorType.java create mode 100644 src/main/java/io/ebean/config/dbplatform/SqlCodeTranslator.java create mode 100644 src/main/java/io/ebean/config/dbplatform/SqlErrorCodes.java create mode 100644 src/main/java/io/ebean/config/dbplatform/SqlExceptionTranslator.java create mode 100644 src/main/java/io/ebeaninternal/server/persist/BatchedSqlException.java delete mode 100644 src/test/java/org/tests/basic/TestQuery.java create mode 100644 src/test/java/org/tests/basic/TestQueryForUpdate.java create mode 100644 src/test/java/org/tests/insert/TestInsertDataIntegrityException.java create mode 100644 src/test/java/org/tests/insert/TestInsertDuplicateKey.java diff --git a/pom.xml b/pom.xml index ca06ab4cb..3e54cb0be 100644 --- a/pom.xml +++ b/pom.xml @@ -160,7 +160,7 @@ io.ebean ebean-agent - 10.1.6 + 10.1.7 test @@ -225,7 +225,7 @@ io.ebean ebean-maven-plugin - 10.1.6 + 10.1.7 test diff --git a/src/main/java/io/ebean/AcquireLockException.java b/src/main/java/io/ebean/AcquireLockException.java new file mode 100644 index 000000000..7bc7aa1b3 --- /dev/null +++ b/src/main/java/io/ebean/AcquireLockException.java @@ -0,0 +1,20 @@ +package io.ebean; + +import javax.persistence.PessimisticLockException; + +/** + * Thrown when failing to acquire a pessimistic lock. + *

+ * Typically when "select for update nowait" or "select for update" is being used and + * the lock can not be obtained (as it is held by another transaction). + *

+ */ +public class AcquireLockException extends PessimisticLockException { + + /** + * Create with a message and cause. + */ + public AcquireLockException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/src/main/java/io/ebean/DataIntegrityException.java b/src/main/java/io/ebean/DataIntegrityException.java new file mode 100644 index 000000000..719b08d71 --- /dev/null +++ b/src/main/java/io/ebean/DataIntegrityException.java @@ -0,0 +1,16 @@ +package io.ebean; + +import javax.persistence.PersistenceException; + +/** + * Thrown when a foreign key constraint is enforced. + */ +public class DataIntegrityException extends PersistenceException { + + /** + * Create with a message and cause. + */ + public DataIntegrityException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/src/main/java/io/ebean/DuplicateKeyException.java b/src/main/java/io/ebean/DuplicateKeyException.java new file mode 100644 index 000000000..18a896370 --- /dev/null +++ b/src/main/java/io/ebean/DuplicateKeyException.java @@ -0,0 +1,14 @@ +package io.ebean; + +/** + * Thrown when a duplicate is attempted on a unique constraint. + */ +public class DuplicateKeyException extends DataIntegrityException { + + /** + * Create with a message and cause. + */ + public DuplicateKeyException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/src/main/java/io/ebean/ExpressionList.java b/src/main/java/io/ebean/ExpressionList.java index 336b86f3b..4b2ddfec4 100644 --- a/src/main/java/io/ebean/ExpressionList.java +++ b/src/main/java/io/ebean/ExpressionList.java @@ -118,6 +118,27 @@ public interface ExpressionList { */ Query asDraft(); + /** + * Execute using "for update" clause which results in the DB locking the record. + */ + Query forUpdate(); + + /** + * Execute using "for update" clause with No Wait option. + *

+ * This is typically a Postgres and Oracle only option at this stage. + *

+ */ + Query forUpdateNoWait(); + + /** + * Execute using "for update" clause with Skip Locked option. + *

+ * This is typically a Postgres and Oracle only option at this stage. + *

+ */ + Query forUpdateSkipLocked(); + /** * Execute the query including soft deleted rows. */ diff --git a/src/main/java/io/ebean/Query.java b/src/main/java/io/ebean/Query.java index 118b01262..2a1d5d3ea 100644 --- a/src/main/java/io/ebean/Query.java +++ b/src/main/java/io/ebean/Query.java @@ -194,6 +194,26 @@ import java.util.function.Predicate; */ public interface Query { + /** + * For update mode. + */ + enum ForUpdate { + /** + * Standard For update clause. + */ + BASE, + + /** + * For update with No Wait option. + */ + NOWAIT, + + /** + * For update with Skip Locked option. + */ + SKIPLOCKED + } + /** * Return the RawSql that was set to use for this query. */ @@ -1355,11 +1375,37 @@ public interface Query { */ Query setForUpdate(boolean forUpdate); + /** + * Execute using "for update" clause which results in the DB locking the record. + */ + Query forUpdate(); + + /** + * Execute using "for update" clause with "no wait" option. + *

+ * This is typically a Postgres and Oracle only option at this stage. + *

+ */ + Query forUpdateNoWait(); + + /** + * Execute using "for update" clause with "skip locked" option. + *

+ * This is typically a Postgres and Oracle only option at this stage. + *

+ */ + Query forUpdateSkipLocked(); + /** * Return true if this query has forUpdate set. */ boolean isForUpdate(); + /** + * Return the "for update" mode to use. + */ + ForUpdate getForUpdateMode(); + /** * Set root table alias. */ diff --git a/src/main/java/io/ebean/Transaction.java b/src/main/java/io/ebean/Transaction.java index ffcb8df78..ef7e45c42 100644 --- a/src/main/java/io/ebean/Transaction.java +++ b/src/main/java/io/ebean/Transaction.java @@ -5,7 +5,6 @@ import io.ebean.config.DocStoreConfig; import io.ebean.config.ServerConfig; import javax.persistence.PersistenceException; -import javax.persistence.RollbackException; import java.io.Closeable; import java.sql.Connection; @@ -67,7 +66,7 @@ public interface Transaction extends Closeable { *
  • Perform post-commit processing updating L2 cache, ElasticSearch etc
  • * */ - void commitAndContinue() throws RollbackException; + void commitAndContinue(); /** * Commit the transaction. @@ -85,7 +84,7 @@ public interface Transaction extends Closeable { *
  • Mark the transaction as "Inactive"
  • * */ - void commit() throws RollbackException; + void commit(); /** * Rollback the transaction. diff --git a/src/main/java/io/ebean/config/dbplatform/DataErrorType.java b/src/main/java/io/ebean/config/dbplatform/DataErrorType.java new file mode 100644 index 000000000..dd42c9e8c --- /dev/null +++ b/src/main/java/io/ebean/config/dbplatform/DataErrorType.java @@ -0,0 +1,22 @@ +package io.ebean.config.dbplatform; + +/** + * Specific persistence error types we wish to map. + */ +public enum DataErrorType { + + /** + * Error trying to acquire lock (e.g. failure executing select for update nowait) + */ + AcquireLock, + + /** + * Error with a duplicate primary or unique key. + */ + DuplicateKey, + + /** + * Data integrity error like an invalid foreign key. + */ + DataIntegrity +} diff --git a/src/main/java/io/ebean/config/dbplatform/DatabasePlatform.java b/src/main/java/io/ebean/config/dbplatform/DatabasePlatform.java index 7872ad6c4..ce49742f6 100644 --- a/src/main/java/io/ebean/config/dbplatform/DatabasePlatform.java +++ b/src/main/java/io/ebean/config/dbplatform/DatabasePlatform.java @@ -12,6 +12,7 @@ import io.ebean.dbmigration.ddlgeneration.platform.PlatformDdl; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import javax.persistence.PersistenceException; import javax.sql.DataSource; import java.sql.Connection; import java.sql.DatabaseMetaData; @@ -172,12 +173,21 @@ public class DatabasePlatform { protected boolean supportsNativeIlike; + protected SqlExceptionTranslator exceptionTranslator = new SqlCodeTranslator(); + /** * Instantiates a new database platform. */ public DatabasePlatform() { } + /** + * Translate the SQLException into a specific persistence exception if possible. + */ + public PersistenceException translate(String message, SQLException e) { + return exceptionTranslator.translate(message, e); + } + /** * Configure UUID Storage etc based on ServerConfig settings. */ @@ -554,17 +564,16 @@ public class DatabasePlatform { } public String completeSql(String sql, Query query) { - if (Boolean.TRUE.equals(query.isForUpdate())) { - sql = withForUpdate(sql); + if (query.isForUpdate()) { + sql = withForUpdate(sql, query.getForUpdateMode()); } return sql; } - protected String withForUpdate(String sql) { + protected String withForUpdate(String sql, Query.ForUpdate forUpdateMode) { // silently assume the database does not support the "for update" clause. logger.info("it seems your database does not support the 'for update' clause"); - return sql; } diff --git a/src/main/java/io/ebean/config/dbplatform/SqlCodeTranslator.java b/src/main/java/io/ebean/config/dbplatform/SqlCodeTranslator.java new file mode 100644 index 000000000..8db905d1d --- /dev/null +++ b/src/main/java/io/ebean/config/dbplatform/SqlCodeTranslator.java @@ -0,0 +1,50 @@ +package io.ebean.config.dbplatform; + +import io.ebean.AcquireLockException; +import io.ebean.DataIntegrityException; +import io.ebean.DuplicateKeyException; + +import javax.persistence.PersistenceException; +import java.sql.SQLException; +import java.util.Collections; +import java.util.Map; + +/** + * Translate SQLException based on SQLState codes. + */ +public class SqlCodeTranslator implements SqlExceptionTranslator { + + private final Map map; + + /** + * Create given the map of SQLState codes to error types. + */ + public SqlCodeTranslator(Map map) { + this.map = map; + } + + /** + * Create "No-op" implementation. + */ + public SqlCodeTranslator() { + this.map = Collections.emptyMap(); + } + + @Override + public PersistenceException translate(String message, SQLException e) { + + DataErrorType errorType = map.get(e.getSQLState()); + if (errorType != null) { + switch (errorType) { + case AcquireLock: + return new AcquireLockException(message, e); + case DuplicateKey: + return new DuplicateKeyException(message, e); + case DataIntegrity: + return new DataIntegrityException(message, e); + } + } + // return a generic exception + return new PersistenceException(message, e); + } +} diff --git a/src/main/java/io/ebean/config/dbplatform/SqlErrorCodes.java b/src/main/java/io/ebean/config/dbplatform/SqlErrorCodes.java new file mode 100644 index 000000000..e52462ccf --- /dev/null +++ b/src/main/java/io/ebean/config/dbplatform/SqlErrorCodes.java @@ -0,0 +1,47 @@ +package io.ebean.config.dbplatform; + +import java.util.HashMap; +import java.util.Map; + +/** + * Used to build a SQLCodeTranslator given DB platform specific codes. + */ +public class SqlErrorCodes { + + private Map map = new HashMap<>(); + + /** + * Map the codes to AcquireLockException. + */ + public SqlErrorCodes addAcquireLock(String... codes) { + return add(DataErrorType.AcquireLock, codes); + } + + /** + * Map the codes to DataIntegrityException. + */ + public SqlErrorCodes addDataIntegrity(String... codes) { + return add(DataErrorType.DataIntegrity, codes); + } + + /** + * Map the codes to DuplicateKeyException. + */ + public SqlErrorCodes addDuplicateKey(String... codes) { + return add(DataErrorType.DuplicateKey, codes); + } + + private SqlErrorCodes add(DataErrorType type, String... codes) { + for (String code : codes) { + map.put(code, type); + } + return this; + } + + /** + * Build and return the SQLCodeTranslator with the mapped codes. + */ + public SqlCodeTranslator build() { + return new SqlCodeTranslator(map); + } +} diff --git a/src/main/java/io/ebean/config/dbplatform/SqlExceptionTranslator.java b/src/main/java/io/ebean/config/dbplatform/SqlExceptionTranslator.java new file mode 100644 index 000000000..061bccfb8 --- /dev/null +++ b/src/main/java/io/ebean/config/dbplatform/SqlExceptionTranslator.java @@ -0,0 +1,15 @@ +package io.ebean.config.dbplatform; + +import javax.persistence.PersistenceException; +import java.sql.SQLException; + +/** + * Used to translate SQLExceptions to specific persistence exceptions. + */ +public interface SqlExceptionTranslator { + + /** + * Translate the given exception. + */ + PersistenceException translate(String message, SQLException e); +} diff --git a/src/main/java/io/ebean/config/dbplatform/db2/DB2Platform.java b/src/main/java/io/ebean/config/dbplatform/db2/DB2Platform.java index 58b86c027..16ca47608 100644 --- a/src/main/java/io/ebean/config/dbplatform/db2/DB2Platform.java +++ b/src/main/java/io/ebean/config/dbplatform/db2/DB2Platform.java @@ -6,6 +6,7 @@ import io.ebean.config.dbplatform.DatabasePlatform; import io.ebean.config.dbplatform.DbPlatformType; import io.ebean.config.dbplatform.DbType; import io.ebean.config.dbplatform.PlatformIdGenerator; +import io.ebean.config.dbplatform.SqlErrorCodes; import io.ebean.dbmigration.ddlgeneration.platform.DB2Ddl; import javax.sql.DataSource; @@ -27,6 +28,13 @@ public class DB2Platform extends DatabasePlatform { this.dbIdentity.setSupportsGetGeneratedKeys(true); this.dbIdentity.setSupportsSequence(true); + this.exceptionTranslator = + new SqlErrorCodes() + //.addAcquireLock("") + .addDuplicateKey("-803") + .addDataIntegrity("-407","-530","-531","-532","-543","-544","-545","-603","-667") + .build(); + booleanDbType = Types.BOOLEAN; dbTypeMap.put(DbType.REAL, new DbPlatformType("real")); dbTypeMap.put(DbType.TINYINT, new DbPlatformType("smallint")); diff --git a/src/main/java/io/ebean/config/dbplatform/h2/H2Platform.java b/src/main/java/io/ebean/config/dbplatform/h2/H2Platform.java index 6df027356..c8317ea75 100644 --- a/src/main/java/io/ebean/config/dbplatform/h2/H2Platform.java +++ b/src/main/java/io/ebean/config/dbplatform/h2/H2Platform.java @@ -2,11 +2,13 @@ package io.ebean.config.dbplatform.h2; import io.ebean.BackgroundExecutor; import io.ebean.Platform; +import io.ebean.Query; import io.ebean.config.dbplatform.DatabasePlatform; import io.ebean.config.dbplatform.DbPlatformType; import io.ebean.config.dbplatform.DbType; import io.ebean.config.dbplatform.IdType; import io.ebean.config.dbplatform.PlatformIdGenerator; +import io.ebean.config.dbplatform.SqlErrorCodes; import io.ebean.dbmigration.ddlgeneration.platform.H2Ddl; import javax.sql.DataSource; @@ -26,6 +28,13 @@ public class H2Platform extends DatabasePlatform { this.dbDefaultValue.setNow("now()"); this.columnAliasPrefix = null; + this.exceptionTranslator = + new SqlErrorCodes() + .addAcquireLock("50200") + .addDuplicateKey("23001","23505") + .addDataIntegrity("22001","22003","22012","22018","22025","23000","23002","23003","23502","23503","23506","23507","23513") + .build(); + this.dbIdentity.setIdType(IdType.IDENTITY); this.dbIdentity.setSupportsGetGeneratedKeys(true); this.dbIdentity.setSupportsSequence(true); @@ -50,7 +59,8 @@ public class H2Platform extends DatabasePlatform { } @Override - protected String withForUpdate(String sql) { + protected String withForUpdate(String sql, Query.ForUpdate forUpdateMode) { + // NOWAIT and SKIP LOCKED currently not supported with H2 return sql + " for update"; } } diff --git a/src/main/java/io/ebean/config/dbplatform/mysql/MySqlPlatform.java b/src/main/java/io/ebean/config/dbplatform/mysql/MySqlPlatform.java index 829de7982..2bbf39409 100644 --- a/src/main/java/io/ebean/config/dbplatform/mysql/MySqlPlatform.java +++ b/src/main/java/io/ebean/config/dbplatform/mysql/MySqlPlatform.java @@ -2,11 +2,13 @@ package io.ebean.config.dbplatform.mysql; import io.ebean.BackgroundExecutor; import io.ebean.Platform; +import io.ebean.Query; import io.ebean.config.dbplatform.DatabasePlatform; import io.ebean.config.dbplatform.DbPlatformType; import io.ebean.config.dbplatform.DbType; import io.ebean.config.dbplatform.IdType; import io.ebean.config.dbplatform.PlatformIdGenerator; +import io.ebean.config.dbplatform.SqlErrorCodes; import io.ebean.dbmigration.ddlgeneration.platform.MySqlDdl; import javax.sql.DataSource; @@ -40,6 +42,13 @@ public class MySqlPlatform extends DatabasePlatform { this.dbIdentity.setSupportsIdentity(true); this.dbIdentity.setSupportsSequence(false); + this.exceptionTranslator = + new SqlErrorCodes() + .addAcquireLock("1205") + .addDuplicateKey("1062") + .addDataIntegrity("630","839","840","893","1169","1215","1216","1217","1364","1451","1452","1557") + .build(); + this.openQuote = "`"; this.closeQuote = "`"; @@ -66,7 +75,8 @@ public class MySqlPlatform extends DatabasePlatform { } @Override - protected String withForUpdate(String sql) { + protected String withForUpdate(String sql, Query.ForUpdate forUpdateMode) { + // NOWAIT and SKIP LOCKED currently not supported with MySQL return sql + " for update"; } } diff --git a/src/main/java/io/ebean/config/dbplatform/oracle/OraclePlatform.java b/src/main/java/io/ebean/config/dbplatform/oracle/OraclePlatform.java index b1458f8e0..339757f79 100644 --- a/src/main/java/io/ebean/config/dbplatform/oracle/OraclePlatform.java +++ b/src/main/java/io/ebean/config/dbplatform/oracle/OraclePlatform.java @@ -2,6 +2,7 @@ package io.ebean.config.dbplatform.oracle; import io.ebean.BackgroundExecutor; import io.ebean.Platform; +import io.ebean.Query; import io.ebean.config.dbplatform.BasicSqlAnsiLimiter; import io.ebean.config.dbplatform.DatabasePlatform; import io.ebean.config.dbplatform.DbPlatformType; @@ -69,7 +70,14 @@ public class OraclePlatform extends DatabasePlatform { } @Override - protected String withForUpdate(String sql) { - return sql + " for update"; + protected String withForUpdate(String sql, Query.ForUpdate forUpdateMode) { + switch (forUpdateMode) { + case SKIPLOCKED: + return sql + " for update skip locked"; + case NOWAIT: + return sql + " for update nowait"; + default: + return sql + " for update"; + } } } diff --git a/src/main/java/io/ebean/config/dbplatform/postgres/PostgresPlatform.java b/src/main/java/io/ebean/config/dbplatform/postgres/PostgresPlatform.java index 1ec7ef667..750b4e14e 100644 --- a/src/main/java/io/ebean/config/dbplatform/postgres/PostgresPlatform.java +++ b/src/main/java/io/ebean/config/dbplatform/postgres/PostgresPlatform.java @@ -2,12 +2,14 @@ package io.ebean.config.dbplatform.postgres; import io.ebean.BackgroundExecutor; import io.ebean.Platform; +import io.ebean.Query; import io.ebean.config.ServerConfig; import io.ebean.config.dbplatform.DatabasePlatform; import io.ebean.config.dbplatform.DbPlatformType; import io.ebean.config.dbplatform.DbType; import io.ebean.config.dbplatform.IdType; import io.ebean.config.dbplatform.PlatformIdGenerator; +import io.ebean.config.dbplatform.SqlErrorCodes; import io.ebean.dbmigration.ddlgeneration.DdlHandler; import io.ebean.dbmigration.ddlgeneration.platform.PostgresDdl; @@ -42,7 +44,12 @@ public class PostgresPlatform extends DatabasePlatform { this.dbIdentity.setSupportsGetGeneratedKeys(true); this.dbIdentity.setSupportsSequence(true); - //this.columnAliasPrefix = "as c"; + this.exceptionTranslator = + new SqlErrorCodes() + .addAcquireLock("55P03") + .addDuplicateKey("23505") + .addDataIntegrity("23000","23502","23503","23514") + .build(); this.openQuote = "\""; this.closeQuote = "\""; @@ -102,7 +109,14 @@ public class PostgresPlatform extends DatabasePlatform { } @Override - protected String withForUpdate(String sql) { - return sql + " for update"; + protected String withForUpdate(String sql, Query.ForUpdate forUpdateMode) { + switch (forUpdateMode) { + case SKIPLOCKED: + return sql + " for update skip locked"; + case NOWAIT: + return sql + " for update nowait"; + default: + return sql + " for update"; + } } } diff --git a/src/main/java/io/ebean/config/dbplatform/sqlserver/SqlServerPlatform.java b/src/main/java/io/ebean/config/dbplatform/sqlserver/SqlServerPlatform.java index a3ca1e1a7..609c06042 100644 --- a/src/main/java/io/ebean/config/dbplatform/sqlserver/SqlServerPlatform.java +++ b/src/main/java/io/ebean/config/dbplatform/sqlserver/SqlServerPlatform.java @@ -6,6 +6,7 @@ import io.ebean.config.dbplatform.DatabasePlatform; import io.ebean.config.dbplatform.DbPlatformType; import io.ebean.config.dbplatform.DbType; import io.ebean.config.dbplatform.IdType; +import io.ebean.config.dbplatform.SqlErrorCodes; import io.ebean.dbmigration.ddlgeneration.platform.SqlServerDdl; import java.sql.Types; @@ -31,6 +32,13 @@ public class SqlServerPlatform extends DatabasePlatform { this.dbIdentity.setSupportsGetGeneratedKeys(true); this.dbIdentity.setSupportsIdentity(true); + this.exceptionTranslator = + new SqlErrorCodes() + .addAcquireLock("1222") + .addDuplicateKey("2601","2627") + .addDataIntegrity("544","8114","8115") + .build(); + this.openQuote = "["; this.closeQuote = "]"; diff --git a/src/main/java/io/ebeaninternal/api/ScopedTransaction.java b/src/main/java/io/ebeaninternal/api/ScopedTransaction.java index e25ccd76f..7c98280d9 100644 --- a/src/main/java/io/ebeaninternal/api/ScopedTransaction.java +++ b/src/main/java/io/ebeaninternal/api/ScopedTransaction.java @@ -1,9 +1,9 @@ package io.ebeaninternal.api; +import io.ebean.PersistBatch; import io.ebean.TransactionCallback; import io.ebean.annotation.DocStoreMode; import io.ebean.bean.PersistenceContext; -import io.ebean.PersistBatch; import io.ebean.event.changelog.BeanChange; import io.ebean.event.changelog.ChangeSet; import io.ebeaninternal.server.core.PersistDeferredRelationship; @@ -13,20 +13,20 @@ import io.ebeaninternal.server.persist.BatchControl; import io.ebeanservice.docstore.api.DocStoreTransaction; import javax.persistence.PersistenceException; -import javax.persistence.RollbackException; import java.io.IOException; import java.sql.Connection; +import java.sql.SQLException; /** * Wrapper of a ScopeTrans request and it's underlying transaction. */ public class ScopedTransaction implements SpiTransaction { - final ScopeTrans scopeTrans; + private final ScopeTrans scopeTrans; - final SpiTransaction transaction; + private final SpiTransaction transaction; - boolean committed; + private boolean committed; public ScopedTransaction(ScopeTrans scopeTrans) { this.scopeTrans = scopeTrans; @@ -34,12 +34,17 @@ public class ScopedTransaction implements SpiTransaction { } @Override - public void commitAndContinue() throws RollbackException { + public PersistenceException translate(String message, SQLException cause) { + return transaction.translate(message, cause); + } + + @Override + public void commitAndContinue() { transaction.commitAndContinue(); } @Override - public void commit() throws RollbackException { + public void commit() { scopeTrans.commitTransaction(); committed = true; } diff --git a/src/main/java/io/ebeaninternal/api/SpiQuery.java b/src/main/java/io/ebeaninternal/api/SpiQuery.java index 8f8f6fb27..6250c883a 100644 --- a/src/main/java/io/ebeaninternal/api/SpiQuery.java +++ b/src/main/java/io/ebeaninternal/api/SpiQuery.java @@ -158,6 +158,11 @@ public interface SpiQuery extends Query { */ String getNativeSql(); + /** + * Return the ForUpdate mode. + */ + ForUpdate getForUpdateMode(); + /** * Return the bean descriptor for this query. */ diff --git a/src/main/java/io/ebeaninternal/api/SpiTransaction.java b/src/main/java/io/ebeaninternal/api/SpiTransaction.java index ff51ec278..83aba831b 100644 --- a/src/main/java/io/ebeaninternal/api/SpiTransaction.java +++ b/src/main/java/io/ebeaninternal/api/SpiTransaction.java @@ -11,7 +11,9 @@ import io.ebeaninternal.server.core.PersistRequestBean; import io.ebeaninternal.server.persist.BatchControl; import io.ebeanservice.docstore.api.DocStoreTransaction; +import javax.persistence.PersistenceException; import java.sql.Connection; +import java.sql.SQLException; /** * Extends Transaction with additional API required on server. @@ -242,6 +244,11 @@ public interface SpiTransaction extends Transaction { */ void flushBatchOnRollback(); + /** + * Translate the SQLException. + */ + PersistenceException translate(String message, SQLException cause); + /** * Mark the transaction explicitly as not being query only. */ diff --git a/src/main/java/io/ebeaninternal/server/core/OrmQueryEngine.java b/src/main/java/io/ebeaninternal/server/core/OrmQueryEngine.java index 6589a6987..76c5875c8 100644 --- a/src/main/java/io/ebeaninternal/server/core/OrmQueryEngine.java +++ b/src/main/java/io/ebeaninternal/server/core/OrmQueryEngine.java @@ -4,6 +4,8 @@ import io.ebean.QueryIterator; import io.ebean.Version; import io.ebean.bean.BeanCollection; +import javax.persistence.PersistenceException; +import java.sql.SQLException; import java.util.List; /** @@ -55,4 +57,9 @@ public interface OrmQueryEngine { * Execute the query as a update statement. */ int update(OrmQueryRequest request); + + /** + * Translate the SQLException to a specific persistence exception type if possible. + */ + PersistenceException translate(OrmQueryRequest request, String bindLog, String sql, SQLException e); } diff --git a/src/main/java/io/ebeaninternal/server/core/OrmQueryRequest.java b/src/main/java/io/ebeaninternal/server/core/OrmQueryRequest.java index f1e2c361b..f31eaaadd 100644 --- a/src/main/java/io/ebeaninternal/server/core/OrmQueryRequest.java +++ b/src/main/java/io/ebeaninternal/server/core/OrmQueryRequest.java @@ -29,6 +29,7 @@ import io.ebeaninternal.server.query.CancelableQuery; import io.ebeaninternal.server.transaction.DefaultPersistenceContext; import javax.persistence.PersistenceException; +import java.sql.SQLException; import java.util.ArrayList; import java.util.Collection; import java.util.List; @@ -76,6 +77,10 @@ public final class OrmQueryRequest extends BeanRequest implements BeanQueryRe this.readOnly = query.isReadOnly(); } + public PersistenceException translate(String bindLog, String sql, SQLException e) { + return queryEngine.translate(this, bindLog, sql, e); + } + /** * Mark the transaction as not being query only. */ diff --git a/src/main/java/io/ebeaninternal/server/core/PersistRequest.java b/src/main/java/io/ebeaninternal/server/core/PersistRequest.java index 0f828c52f..cddf3db1d 100644 --- a/src/main/java/io/ebeaninternal/server/core/PersistRequest.java +++ b/src/main/java/io/ebeaninternal/server/core/PersistRequest.java @@ -4,6 +4,7 @@ import io.ebeaninternal.api.SpiEbeanServer; import io.ebeaninternal.api.SpiTransaction; import io.ebeaninternal.server.persist.BatchControl; import io.ebeaninternal.server.persist.BatchPostExecute; +import io.ebeaninternal.server.persist.BatchedSqlException; import io.ebeaninternal.server.persist.PersistExecute; /** @@ -15,14 +16,14 @@ public abstract class PersistRequest extends BeanRequest implements BatchPostExe INSERT, UPDATE, DELETE, SOFT_DELETE, DELETE_PERMANENT, UPDATESQL, CALLABLESQL } - protected boolean persistCascade; + boolean persistCascade; /** * One of INSERT, UPDATE, DELETE, UPDATESQL or CALLABLESQL. */ protected Type type; - protected final PersistExecute persistExecute; + final PersistExecute persistExecute; /** * Used by CallableSqlRequest and UpdateSqlRequest. @@ -63,24 +64,27 @@ public abstract class PersistRequest extends BeanRequest implements BatchPostExe /** * Execute the statement. */ - public int executeStatement() { + int executeStatement() { boolean batch = isBatchThisRequest(); - int rows; - BatchControl control = transaction.getBatchControl(); - if (control != null) { - rows = control.executeStatementOrBatch(this, batch); + try { + int rows; + BatchControl control = transaction.getBatchControl(); + if (control != null) { + rows = control.executeStatementOrBatch(this, batch); - } else if (batch) { - // need to create the BatchControl - control = persistExecute.createBatchControl(transaction); - rows = control.executeStatementOrBatch(this, true); - } else { - rows = executeNow(); + } else if (batch) { + // need to create the BatchControl + control = persistExecute.createBatchControl(transaction); + rows = control.executeStatementOrBatch(this, true); + } else { + rows = executeNow(); + } + return rows; + } catch (BatchedSqlException e) { + throw transaction.translate(e.getMessage(), e.getCause()); } - - return rows; } public void initTransIfRequired() { diff --git a/src/main/java/io/ebeaninternal/server/core/PersistRequestBean.java b/src/main/java/io/ebeaninternal/server/core/PersistRequestBean.java index c4057c157..fee90514e 100644 --- a/src/main/java/io/ebeaninternal/server/core/PersistRequestBean.java +++ b/src/main/java/io/ebeaninternal/server/core/PersistRequestBean.java @@ -20,6 +20,7 @@ import io.ebeaninternal.server.deploy.BeanProperty; import io.ebeaninternal.server.deploy.BeanPropertyAssocMany; import io.ebeaninternal.server.deploy.id.ImportedId; import io.ebeaninternal.server.persist.BatchControl; +import io.ebeaninternal.server.persist.BatchedSqlException; import io.ebeaninternal.server.persist.PersistExecute; import io.ebeaninternal.server.transaction.BeanPersistIdMap; import io.ebeanservice.docstore.api.DocStoreUpdate; @@ -499,10 +500,6 @@ public final class PersistRequestBean extends PersistRequest implements BeanP } } - public BeanManager getBeanManager() { - return beanManager; - } - /** * Return the BeanDescriptor for the associated bean. */ @@ -697,17 +694,20 @@ public final class PersistRequestBean extends PersistRequest implements BeanP public int executeOrQueue() { boolean batch = isBatchThisRequest(); + try { + BatchControl control = transaction.getBatchControl(); + if (control != null) { + return control.executeOrQueue(this, batch); + } + if (batch) { + control = persistExecute.createBatchControl(transaction); + return control.executeOrQueue(this, true); - BatchControl control = transaction.getBatchControl(); - if (control != null) { - return control.executeOrQueue(this, batch); - } - if (batch) { - control = persistExecute.createBatchControl(transaction); - return control.executeOrQueue(this, true); - - } else { - return executeNow(); + } else { + return executeNow(); + } + } catch (BatchedSqlException e) { + throw transaction.translate(e.getMessage(), e.getCause()); } } diff --git a/src/main/java/io/ebeaninternal/server/expression/DefaultExpressionList.java b/src/main/java/io/ebeaninternal/server/expression/DefaultExpressionList.java index 6b27483ac..d16e1a2b2 100644 --- a/src/main/java/io/ebeaninternal/server/expression/DefaultExpressionList.java +++ b/src/main/java/io/ebeaninternal/server/expression/DefaultExpressionList.java @@ -419,6 +419,21 @@ public class DefaultExpressionList implements SpiExpressionList { return query.filterMany(prop); } + @Override + public Query forUpdate() { + return query.forUpdate(); + } + + @Override + public Query forUpdateNoWait() { + return query.forUpdateNoWait(); + } + + @Override + public Query forUpdateSkipLocked() { + return query.forUpdateSkipLocked(); + } + @Override public Query select(String fetchProperties) { return query.select(fetchProperties); diff --git a/src/main/java/io/ebeaninternal/server/expression/JunctionExpression.java b/src/main/java/io/ebeaninternal/server/expression/JunctionExpression.java index 256bb62ec..c44d6dbea 100644 --- a/src/main/java/io/ebeaninternal/server/expression/JunctionExpression.java +++ b/src/main/java/io/ebeaninternal/server/expression/JunctionExpression.java @@ -419,6 +419,21 @@ class JunctionExpression implements SpiJunction, SpiExpression, Expression return exprList.findUnique(); } + @Override + public Query forUpdate() { + return exprList.forUpdate(); + } + + @Override + public Query forUpdateNoWait() { + return exprList.forUpdateNoWait(); + } + + @Override + public Query forUpdateSkipLocked() { + return exprList.forUpdateSkipLocked(); + } + /** * Path exists - for the given path in a JSON document. */ diff --git a/src/main/java/io/ebeaninternal/server/persist/BatchControl.java b/src/main/java/io/ebeaninternal/server/persist/BatchControl.java index 688753e10..56f60cfe3 100644 --- a/src/main/java/io/ebeaninternal/server/persist/BatchControl.java +++ b/src/main/java/io/ebeaninternal/server/persist/BatchControl.java @@ -5,7 +5,6 @@ import io.ebeaninternal.server.core.PersistRequest; import io.ebeaninternal.server.core.PersistRequestBean; import io.ebeaninternal.server.deploy.BeanDescriptor; -import javax.persistence.PersistenceException; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; @@ -123,7 +122,7 @@ public final class BatchControl { * to the depth. *

    */ - public int executeStatementOrBatch(PersistRequest request, boolean batch) { + public int executeStatementOrBatch(PersistRequest request, boolean batch) throws BatchedSqlException { if (!batch || (batchFlushOnMixed && !isBeansEmpty())) { // flush when mixing beans and updateSql flush(); @@ -148,7 +147,7 @@ public final class BatchControl { * immediately or queue it for batch processing later. The queue is flushedIntercept * according to the depth (object graph depth). */ - public int executeOrQueue(PersistRequestBean request, boolean batch) { + public int executeOrQueue(PersistRequestBean request, boolean batch) throws BatchedSqlException { if (!batch || (batchFlushOnMixed && !pstmtHolder.isEmpty())) { // flush when mixing beans and updateSql @@ -167,7 +166,7 @@ public final class BatchControl { /** * Add the request to the batch and return true if we should flush. */ - private boolean addToBatch(PersistRequestBean request) { + private boolean addToBatch(PersistRequestBean request) throws BatchedSqlException { BatchedBeanHolder beanHolder = getBeanHolder(request); int bufferSize = beanHolder.append(request); @@ -193,14 +192,14 @@ public final class BatchControl { /** * Flush any batched PreparedStatements. */ - protected void flushPstmtHolder() { + protected void flushPstmtHolder() throws BatchedSqlException { pstmtHolder.flush(getGeneratedKeys); } /** * Execute all the requests contained in the list. */ - protected void executeNow(ArrayList list) { + protected void executeNow(ArrayList list) throws BatchedSqlException { for (int i = 0; i < list.size(); i++) { if (i % batchSize == 0) { // hit the batch size so flush @@ -214,14 +213,14 @@ public final class BatchControl { /** * Flush without resetting the topOrder (maintains the depth info). */ - public void flush() throws PersistenceException { + public void flush() throws BatchedSqlException { flush(false); } /** * Flush with a reset the topOrder (fully empty the batch). */ - public void flushReset() throws PersistenceException { + public void flushReset() throws BatchedSqlException { flush(true); } @@ -236,31 +235,38 @@ public final class BatchControl { /** * execute all the requests currently queued or batched. */ - private void flush(boolean resetTop) throws PersistenceException { + private void flush(boolean resetTop) throws BatchedSqlException { - if (!pstmtHolder.isEmpty()) { - // Flush existing pstmts (updateSql or callableSql) - flushPstmtHolder(); - } - if (isEmpty()) { - // Nothing in queue to flush - return; - } + try { + if (!pstmtHolder.isEmpty()) { + // Flush existing pstmts (updateSql or callableSql) + flushPstmtHolder(); + } + if (isEmpty()) { + // Nothing in queue to flush + return; + } - // convert entry map to array for sorting - BatchedBeanHolder[] bsArray = getBeanHolderArray(); - // sort the entries by depth - Arrays.sort(bsArray, depthComparator); + // convert entry map to array for sorting + BatchedBeanHolder[] bsArray = getBeanHolderArray(); + // sort the entries by depth + Arrays.sort(bsArray, depthComparator); - if (transaction.isLogSummary()) { - transaction.logSummary("BatchControl flush " + Arrays.toString(bsArray)); - } - for (BatchedBeanHolder aBsArray : bsArray) { - aBsArray.executeNow(); - } + if (transaction.isLogSummary()) { + transaction.logSummary("BatchControl flush " + Arrays.toString(bsArray)); + } + for (BatchedBeanHolder aBsArray : bsArray) { + aBsArray.executeNow(); + } - if (resetTop) { - beanHoldMap.clear(); + if (resetTop) { + beanHoldMap.clear(); + } + } catch (BatchedSqlException e) { + // clear the batch on error in case we want to + // catch, rollback and continue processing + clear(); + throw e; } } @@ -268,7 +274,7 @@ public final class BatchControl { * Return an entry for the given type description. The type description is * typically the bean class name (or table name for MapBeans). */ - private BatchedBeanHolder getBeanHolder(PersistRequestBean request) { + private BatchedBeanHolder getBeanHolder(PersistRequestBean request) throws BatchedSqlException { BeanDescriptor beanDescriptor = request.getBeanDescriptor(); BatchedBeanHolder batchBeanHolder = beanHoldMap.get(beanDescriptor.getFullName()); diff --git a/src/main/java/io/ebeaninternal/server/persist/BatchedBeanHolder.java b/src/main/java/io/ebeaninternal/server/persist/BatchedBeanHolder.java index b5fed8155..4e1528b73 100644 --- a/src/main/java/io/ebeaninternal/server/persist/BatchedBeanHolder.java +++ b/src/main/java/io/ebeaninternal/server/persist/BatchedBeanHolder.java @@ -79,7 +79,7 @@ public class BatchedBeanHolder { * and then execute them. *

    */ - public void executeNow() { + public void executeNow() throws BatchedSqlException { // process the requests. Creates one or more PreparedStatements // with binding addBatch() for each request. // Note updates and deletes can result in many PreparedStatements diff --git a/src/main/java/io/ebeaninternal/server/persist/BatchedPstmt.java b/src/main/java/io/ebeaninternal/server/persist/BatchedPstmt.java index f96cebcaa..deddb087f 100644 --- a/src/main/java/io/ebeaninternal/server/persist/BatchedPstmt.java +++ b/src/main/java/io/ebeaninternal/server/persist/BatchedPstmt.java @@ -121,7 +121,7 @@ public class BatchedPstmt { list.get(index).setGeneratedKey(idValue); index++; } - } + } } } diff --git a/src/main/java/io/ebeaninternal/server/persist/BatchedPstmtHolder.java b/src/main/java/io/ebeaninternal/server/persist/BatchedPstmtHolder.java index fa68fd486..b47b41a36 100644 --- a/src/main/java/io/ebeaninternal/server/persist/BatchedPstmtHolder.java +++ b/src/main/java/io/ebeaninternal/server/persist/BatchedPstmtHolder.java @@ -79,7 +79,7 @@ public class BatchedPstmtHolder { * * @param getGeneratedKeys if true try to get generated keys for inserts */ - public void flush(boolean getGeneratedKeys) throws PersistenceException { + public void flush(boolean getGeneratedKeys) throws BatchedSqlException { SQLException firstError = null; String errorSql = null; @@ -96,7 +96,7 @@ public class BatchedPstmtHolder { } catch (SQLException ex) { SQLException next = ex.getNextException(); while (next != null) { - logger.error("Next Exception during batch execution", next); + logger.trace("Next Exception during batch execution", next); next = next.getNextException(); } @@ -122,7 +122,7 @@ public class BatchedPstmtHolder { if (firstError != null) { String msg = "Error when batch flush on sql: " + errorSql; - throw new PersistenceException(msg, firstError); + throw new BatchedSqlException(msg, firstError); } } diff --git a/src/main/java/io/ebeaninternal/server/persist/BatchedSqlException.java b/src/main/java/io/ebeaninternal/server/persist/BatchedSqlException.java new file mode 100644 index 000000000..bd8a6726c --- /dev/null +++ b/src/main/java/io/ebeaninternal/server/persist/BatchedSqlException.java @@ -0,0 +1,22 @@ +package io.ebeaninternal.server.persist; + + +import java.sql.SQLException; + +/** + * Holds the first SQLException found when executing a JDBC batch. + */ +public class BatchedSqlException extends Exception { + + private SQLException cause; + + BatchedSqlException(String message, SQLException cause) { + super(message, cause); + this.cause = cause; + } + + @Override + public SQLException getCause() { + return cause; + } +} diff --git a/src/main/java/io/ebeaninternal/server/persist/dml/DmlBeanPersister.java b/src/main/java/io/ebeaninternal/server/persist/dml/DmlBeanPersister.java index 2b876df14..0d62615f2 100644 --- a/src/main/java/io/ebeaninternal/server/persist/dml/DmlBeanPersister.java +++ b/src/main/java/io/ebeaninternal/server/persist/dml/DmlBeanPersister.java @@ -1,10 +1,10 @@ package io.ebeaninternal.server.persist.dml; +import io.ebean.config.dbplatform.DatabasePlatform; import io.ebean.util.StringHelper; import io.ebeaninternal.server.core.PersistRequestBean; import io.ebeaninternal.server.persist.BeanPersister; -import javax.persistence.PersistenceException; import java.sql.SQLException; /** @@ -18,15 +18,16 @@ import java.sql.SQLException; */ public final class DmlBeanPersister implements BeanPersister { + private final DatabasePlatform dbPlatform; + private final UpdateMeta updateMeta; private final InsertMeta insertMeta; private final DeleteMeta deleteMeta; - - public DmlBeanPersister(UpdateMeta updateMeta, InsertMeta insertMeta, DeleteMeta deleteMeta) { - + public DmlBeanPersister(DatabasePlatform dbPlatform, UpdateMeta updateMeta, InsertMeta insertMeta, DeleteMeta deleteMeta) { + this.dbPlatform = dbPlatform; this.updateMeta = updateMeta; this.insertMeta = insertMeta; this.deleteMeta = deleteMeta; @@ -37,9 +38,7 @@ public final class DmlBeanPersister implements BeanPersister { */ @Override public int delete(PersistRequestBean request) { - - DeleteHandler delete = new DeleteHandler(request, deleteMeta); - return execute(request, delete); + return execute(request, new DeleteHandler(request, deleteMeta)); } /** @@ -47,9 +46,7 @@ public final class DmlBeanPersister implements BeanPersister { */ @Override public void insert(PersistRequestBean request) { - - InsertHandler insert = new InsertHandler(request, insertMeta); - execute(request, insert); + execute(request, new InsertHandler(request, insertMeta)); } /** @@ -57,9 +54,7 @@ public final class DmlBeanPersister implements BeanPersister { */ @Override public void update(PersistRequestBean request) { - - UpdateHandler update = new UpdateHandler(request, updateMeta); - execute(request, update); + execute(request, new UpdateHandler(request, updateMeta)); } /** @@ -80,12 +75,12 @@ public final class DmlBeanPersister implements BeanPersister { } catch (SQLException e) { // log the error to the transaction log - String errMsg = StringHelper.replaceStringMulti(e.getMessage(), new String[]{"\r", "\n"}, "\\n "); - String msg = "ERROR executing DML bindLog[" + handler.getBindLog() + "] error[" + errMsg + "]"; + String errMsg = StringHelper.replaceStringMulti(e.getMessage(), new String[]{"\r", "\n"}, " "); + String msg = "Error[" + errMsg + "]"; if (request.getTransaction().isLogSummary()) { request.getTransaction().logSummary(msg); } - throw new PersistenceException(msg, e); + throw dbPlatform.translate(msg, e); } finally { if (!batched) { diff --git a/src/main/java/io/ebeaninternal/server/persist/dml/DmlBeanPersisterFactory.java b/src/main/java/io/ebeaninternal/server/persist/dml/DmlBeanPersisterFactory.java index ca3e8f8fa..a06ac1505 100644 --- a/src/main/java/io/ebeaninternal/server/persist/dml/DmlBeanPersisterFactory.java +++ b/src/main/java/io/ebeaninternal/server/persist/dml/DmlBeanPersisterFactory.java @@ -10,9 +10,12 @@ import io.ebeaninternal.server.persist.BeanPersisterFactory; */ public class DmlBeanPersisterFactory implements BeanPersisterFactory { + private final DatabasePlatform dbPlatform; + private final MetaFactory metaFactory; public DmlBeanPersisterFactory(DatabasePlatform dbPlatform) { + this.dbPlatform = dbPlatform; this.metaFactory = new MetaFactory(dbPlatform); } @@ -29,7 +32,7 @@ public class DmlBeanPersisterFactory implements BeanPersisterFactory { UpdateMeta updMeta = metaFactory.createUpdate(desc); DeleteMeta delMeta = metaFactory.createDelete(desc); InsertMeta insMeta = metaFactory.createInsert(desc); - return new DmlBeanPersister(updMeta, insMeta, delMeta); + return new DmlBeanPersister(dbPlatform, updMeta, insMeta, delMeta); } } diff --git a/src/main/java/io/ebeaninternal/server/query/CQuery.java b/src/main/java/io/ebeaninternal/server/query/CQuery.java index d7010dfe4..bf23da884 100644 --- a/src/main/java/io/ebeaninternal/server/query/CQuery.java +++ b/src/main/java/io/ebeaninternal/server/query/CQuery.java @@ -10,12 +10,10 @@ import io.ebean.bean.NodeUsageListener; import io.ebean.bean.ObjectGraphNode; import io.ebean.bean.PersistenceContext; import io.ebean.event.readaudit.ReadEvent; -import io.ebean.util.StringHelper; import io.ebeaninternal.api.SpiQuery; import io.ebeaninternal.api.SpiQuery.Mode; import io.ebeaninternal.api.SpiTransaction; import io.ebeaninternal.server.autotune.ProfilingListener; -import io.ebeaninternal.server.core.Message; import io.ebeaninternal.server.core.OrmQueryRequest; import io.ebeaninternal.server.core.SpiOrmQueryRequest; import io.ebeaninternal.server.deploy.BeanCollectionHelp; @@ -665,28 +663,7 @@ public class CQuery implements DbReadContext, CancelableQuery { * Create a PersistenceException including interesting information like the bindLog and sql used. */ PersistenceException createPersistenceException(SQLException e) { - - return createPersistenceException(e, getTransaction(), bindLog, sql); - } - - /** - * Create a PersistenceException including interesting information like the bindLog and sql used. - */ - static PersistenceException createPersistenceException(SQLException e, SpiTransaction t, String bindLog, String sql) { - - if (t.isLogSummary()) { - // log the error to the transaction log - String errMsg = StringHelper.replaceStringMulti(e.getMessage(), new String[]{"\r", "\n"}, "\\n "); - String msg = "ERROR executing query: bindLog[" + bindLog + "] error[" + errMsg + "]"; - t.logSummary(msg); - } - - // ensure 'rollback' is logged if queryOnly transaction - t.getConnection(); - - // build a decent error message for the exception - String m = Message.msg("fetch.sqlerror", e.getMessage(), bindLog, sql); - return new PersistenceException(m, e); + return request.translate(bindLog, sql, e); } /** diff --git a/src/main/java/io/ebeaninternal/server/query/CQueryEngine.java b/src/main/java/io/ebeaninternal/server/query/CQueryEngine.java index d6eb541ba..7b6e495af 100644 --- a/src/main/java/io/ebeaninternal/server/query/CQueryEngine.java +++ b/src/main/java/io/ebeaninternal/server/query/CQueryEngine.java @@ -8,8 +8,11 @@ import io.ebean.bean.EntityBean; import io.ebean.bean.ObjectGraphNode; import io.ebean.config.ServerConfig; import io.ebean.config.dbplatform.DatabasePlatform; +import io.ebean.util.StringHelper; import io.ebeaninternal.api.SpiQuery; +import io.ebeaninternal.api.SpiTransaction; import io.ebeaninternal.server.core.DiffHelp; +import io.ebeaninternal.server.core.Message; import io.ebeaninternal.server.core.OrmQueryRequest; import io.ebeaninternal.server.deploy.BeanDescriptor; import io.ebeaninternal.server.lib.util.Str; @@ -18,6 +21,7 @@ import io.ebeaninternal.server.transaction.TransactionManager; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import javax.persistence.PersistenceException; import java.sql.SQLException; import java.util.Collections; import java.util.LinkedHashMap; @@ -45,7 +49,10 @@ public class CQueryEngine { private final CQueryHistorySupport historySupport; + private final DatabasePlatform dbPlatform; + public CQueryEngine(ServerConfig serverConfig, DatabasePlatform dbPlatform, Binder binder, Map asOfTableMapping, Map draftTableMap) { + this.dbPlatform = dbPlatform; this.defaultFetchSizeFindEach = serverConfig.getJdbcFetchSizeFindEach(); this.defaultFetchSizeFindList = serverConfig.getJdbcFetchSizeFindList(); this.forwardOnlyHintOnFindIterate = dbPlatform.isForwardOnlyHintOnFindIterate(); @@ -83,7 +90,7 @@ public class CQueryEngine { return rows; } catch (SQLException e) { - throw CQuery.createPersistenceException(e, request.getTransaction(), query.getBindLog(), query.getGeneratedSql()); + throw translate(request, query.getBindLog(), query.getGeneratedSql(), e); } } @@ -109,10 +116,30 @@ public class CQueryEngine { return list; } catch (SQLException e) { - throw CQuery.createPersistenceException(e, request.getTransaction(), rcQuery.getBindLog(), rcQuery.getGeneratedSql()); + throw translate(request, rcQuery.getBindLog(), rcQuery.getGeneratedSql(), e); } } + /** + * Translate the SQLException into a PersistenceException. + */ + PersistenceException translate(OrmQueryRequest request, String bindLog, String sql, SQLException e) { + SpiTransaction t = request.getTransaction(); + if (t.isLogSummary()) { + // log the error to the transaction log + String errMsg = StringHelper.replaceStringMulti(e.getMessage(), new String[]{"\r", "\n"}, "\\n "); + String msg = "ERROR executing query, bindLog[" + bindLog + "] error[" + errMsg + "]"; + t.logSummary(msg); + } + + // ensure 'rollback' is logged if queryOnly transaction + t.getConnection(); + + // build a decent error message for the exception + String m = Message.msg("fetch.sqlerror", e.getMessage(), bindLog, sql); + return dbPlatform.translate(m, e); + } + /** * Build and execute the find Id's query. */ @@ -155,7 +182,7 @@ public class CQueryEngine { return count; } catch (SQLException e) { - throw CQuery.createPersistenceException(e, request.getTransaction(), rcQuery.getBindLog(), rcQuery.getGeneratedSql()); + throw translate(request, rcQuery.getBindLog(), rcQuery.getGeneratedSql(), e); } } diff --git a/src/main/java/io/ebeaninternal/server/query/DefaultOrmQueryEngine.java b/src/main/java/io/ebeaninternal/server/query/DefaultOrmQueryEngine.java index 806977ce2..b3db79734 100644 --- a/src/main/java/io/ebeaninternal/server/query/DefaultOrmQueryEngine.java +++ b/src/main/java/io/ebeaninternal/server/query/DefaultOrmQueryEngine.java @@ -11,6 +11,8 @@ import io.ebeaninternal.server.core.OrmQueryEngine; import io.ebeaninternal.server.core.OrmQueryRequest; import io.ebeaninternal.server.deploy.BeanDescriptor; +import javax.persistence.PersistenceException; +import java.sql.SQLException; import java.util.Collection; import java.util.List; @@ -28,10 +30,14 @@ public class DefaultOrmQueryEngine implements OrmQueryEngine { * Create the Finder. */ public DefaultOrmQueryEngine(CQueryEngine queryEngine) { - this.queryEngine = queryEngine; } + @Override + public PersistenceException translate(OrmQueryRequest request, String bindLog, String sql, SQLException e) { + return queryEngine.translate(request, bindLog, sql, e); + } + /** * Flushes the jdbc batch by default unless explicitly turned off on the transaction. */ diff --git a/src/main/java/io/ebeaninternal/server/querydefn/DefaultOrmQuery.java b/src/main/java/io/ebeaninternal/server/querydefn/DefaultOrmQuery.java index 61b0585e5..c4cb36b69 100644 --- a/src/main/java/io/ebeaninternal/server/querydefn/DefaultOrmQuery.java +++ b/src/main/java/io/ebeaninternal/server/querydefn/DefaultOrmQuery.java @@ -206,9 +206,9 @@ public class DefaultOrmQuery implements SpiQuery { private Boolean autoTune; /** - * Allow to fetch a record "for update" which should lock it on read + * For update mode. */ - private boolean forUpdate; + private ForUpdate forUpdate; private boolean singleAttribute; @@ -579,7 +579,7 @@ public class DefaultOrmQuery implements SpiQuery { // includes joins and we use - delete ... where id in (...) maxRows = 0; firstRow = 0; - forUpdate = false; + forUpdate = null; rootTableAlias = "${RTA}"; // alias we remove later setSelectId(); } @@ -791,11 +791,6 @@ public class DefaultOrmQuery implements SpiQuery { return autoTune; } - @Override - public boolean isForUpdate() { - return forUpdate; - } - @Override public void setDefaultRawSqlIfRequired() { if (beanDescriptor.isRawSqlBased() && rawSql == null) { @@ -811,11 +806,42 @@ public class DefaultOrmQuery implements SpiQuery { @Override public DefaultOrmQuery setForUpdate(boolean forUpdate) { - this.forUpdate = forUpdate; + this.forUpdate = (forUpdate) ? ForUpdate.BASE : null; this.excludeBeanCache = true; return this; } + @Override + public DefaultOrmQuery forUpdate() { + return setForUpdateWithMode(ForUpdate.BASE); + } + + @Override + public DefaultOrmQuery forUpdateNoWait() { + return setForUpdateWithMode(ForUpdate.NOWAIT); + } + + @Override + public DefaultOrmQuery forUpdateSkipLocked() { + return setForUpdateWithMode(ForUpdate.SKIPLOCKED); + } + + private DefaultOrmQuery setForUpdateWithMode(ForUpdate mode) { + this.forUpdate = mode; + this.excludeBeanCache = true; + return this; + } + + @Override + public boolean isForUpdate() { + return forUpdate != null; + } + + @Override + public ForUpdate getForUpdateMode() { + return forUpdate; + } + @Override public ProfilingListener getProfilingListener() { return profilingListener; diff --git a/src/main/java/io/ebeaninternal/server/querydefn/OrmQueryPlanKey.java b/src/main/java/io/ebeaninternal/server/querydefn/OrmQueryPlanKey.java index eee0b6e4c..68b2b9aac 100644 --- a/src/main/java/io/ebeaninternal/server/querydefn/OrmQueryPlanKey.java +++ b/src/main/java/io/ebeaninternal/server/querydefn/OrmQueryPlanKey.java @@ -1,6 +1,7 @@ package io.ebeaninternal.server.querydefn; import io.ebean.OrderBy; +import io.ebean.Query; import io.ebean.RawSql; import io.ebeaninternal.api.BindParams; import io.ebeaninternal.api.CQueryPlanKey; @@ -14,61 +15,69 @@ import io.ebeaninternal.server.deploy.TableJoin; */ class OrmQueryPlanKey implements CQueryPlanKey { - private final String m2mIncludeTable; - private final String orderByAsSting; private final SpiExpression where; private final SpiExpression having; private final RawSql.Key rawSqlKey; - private final boolean hasIdValue; - private final SpiQuery.Type type; private final int maxRows; private final int firstRow; - private final boolean disableLazyLoading; - private final boolean distinct; - private final boolean sqlDistinct; - private final String mapKey; - private final SpiQuery.TemporalMode temporalMode; - private final boolean forUpdate; - private final String rootTableAlias; private final OrmUpdateProperties updateProperties; private final int planHash; private final int bindCount; + private final String options; - OrmQueryPlanKey(TableJoin m2mIncludeTable, SpiQuery.Type type, OrmQueryDetail detail, int maxRows, int firstRow, boolean disableLazyLoading, OrderBy orderBy, boolean distinct, boolean sqlDistinct, String mapKey, Object id, BindParams bindParams, SpiExpression whereExpressions, SpiExpression havingExpressions, SpiQuery.TemporalMode temporalMode, boolean forUpdate, String rootTableAlias, RawSql rawSql, OrmUpdateProperties updateProperties) { + OrmQueryPlanKey(TableJoin m2mIncludeTable, SpiQuery.Type type, OrmQueryDetail detail, int maxRows, int firstRow, boolean disableLazyLoading, + OrderBy orderBy, boolean distinct, boolean sqlDistinct, String mapKey, Object id, BindParams bindParams, + SpiExpression whereExpressions, SpiExpression havingExpressions, SpiQuery.TemporalMode temporalMode, + Query.ForUpdate forUpdate, String rootTableAlias, RawSql rawSql, OrmUpdateProperties updateProperties) { - this.m2mIncludeTable = m2mIncludeTable == null ? null : m2mIncludeTable.getTable(); - this.type = type; + StringBuilder sb = new StringBuilder(300); + if (type != null) { + sb.append("t:").append(type.ordinal()); + } + if (temporalMode != SpiQuery.TemporalMode.CURRENT) { + sb.append(",temp:").append(temporalMode.ordinal()); + } + if (forUpdate != null) { + sb.append(",forUpd:").append(forUpdate.ordinal()); + } + if (id != null) { + sb.append(",id:"); + } + if (distinct) { + sb.append(",dist:"); + } + if (sqlDistinct) { + sb.append(",sqlD:"); + } + if (disableLazyLoading) { + sb.append(",disLazy:"); + } + if (rootTableAlias != null) { + sb.append(",root:").append(rootTableAlias); + } + if (orderBy != null) { + sb.append(",orderBy:").append(orderBy.toStringFormat()); + } + if (m2mIncludeTable != null) { + sb.append(",m2m:").append(m2mIncludeTable.getTable()); + } + if (mapKey != null) { + sb.append(",mapKey:").append(mapKey); + } + this.options = sb.toString(); this.maxRows = maxRows; this.firstRow = firstRow; - this.disableLazyLoading = disableLazyLoading; - this.orderByAsSting = (orderBy == null) ? null : orderBy.toStringFormat(); - this.distinct = distinct; - this.sqlDistinct = sqlDistinct; - this.mapKey = mapKey; - this.hasIdValue = (id != null); this.where = (whereExpressions == null) ? null : whereExpressions.copyForPlanKey(); this.having = (havingExpressions == null) ? null : havingExpressions.copyForPlanKey(); - this.temporalMode = temporalMode; - this.forUpdate = forUpdate; - this.rootTableAlias = rootTableAlias; this.updateProperties = updateProperties; this.rawSqlKey = (rawSql == null) ? null : rawSql.getKey(); // exclude bind values and things unrelated to the sql being generated HashQueryPlanBuilder builder = new HashQueryPlanBuilder(); - - builder.add((type == null ? 0 : type.ordinal() + 1)); - builder.add(distinct).add(sqlDistinct); + builder.add(options.hashCode()); builder.add(firstRow).add(maxRows); - builder.add(orderBy).add(forUpdate); - builder.add(mapKey); - builder.add(disableLazyLoading); - builder.add(hasIdValue); - builder.add(temporalMode); builder.add(rawSqlKey == null ? 0 : rawSqlKey.hashCode()); - builder.add(this.m2mIncludeTable); - builder.add(rootTableAlias); if (detail != null) { detail.queryPlanHash(builder); @@ -111,23 +120,10 @@ class OrmQueryPlanKey implements CQueryPlanKey { if (bindCount != that.bindCount) return false; if (maxRows != that.maxRows) return false; if (firstRow != that.firstRow) return false; - if (disableLazyLoading != that.disableLazyLoading) return false; - if (distinct != that.distinct) return false; - if (sqlDistinct != that.sqlDistinct) return false; - if (forUpdate != that.forUpdate) return false; - if (hasIdValue != that.hasIdValue) return false; - if (type != that.type) return false; - if (temporalMode != that.temporalMode) return false; - if (m2mIncludeTable != null ? !m2mIncludeTable.equals(that.m2mIncludeTable) : that.m2mIncludeTable != null) - return false; - if (orderByAsSting != null ? !orderByAsSting.equals(that.orderByAsSting) : that.orderByAsSting != null) - return false; + if (!options.equals(that.options)) return false; if (where != null ? !where.isSameByPlan(that.where) : that.where != null) return false; if (having != null ? !having.isSameByPlan(that.having) : that.having != null) return false; - if (updateProperties != null ? !updateProperties.isSameByPlan(that.updateProperties) : that.updateProperties != null) - return false; - if (rawSqlKey != null ? !rawSqlKey.equals(that.rawSqlKey) : that.rawSqlKey != null) return false; - if (mapKey != null ? !mapKey.equals(that.mapKey) : that.mapKey != null) return false; - return rootTableAlias != null ? rootTableAlias.equals(that.rootTableAlias) : that.rootTableAlias == null; + if (updateProperties != null ? !updateProperties.isSameByPlan(that.updateProperties) : that.updateProperties != null) return false; + return rawSqlKey != null ? rawSqlKey.equals(that.rawSqlKey) : that.rawSqlKey == null; } } diff --git a/src/main/java/io/ebeaninternal/server/transaction/ExternalJdbcTransaction.java b/src/main/java/io/ebeaninternal/server/transaction/ExternalJdbcTransaction.java index 25cfb588f..47b635ce0 100644 --- a/src/main/java/io/ebeaninternal/server/transaction/ExternalJdbcTransaction.java +++ b/src/main/java/io/ebeaninternal/server/transaction/ExternalJdbcTransaction.java @@ -1,7 +1,6 @@ package io.ebeaninternal.server.transaction; import javax.persistence.PersistenceException; -import javax.persistence.RollbackException; import java.sql.Connection; /** @@ -43,7 +42,7 @@ public class ExternalJdbcTransaction extends JdbcTransaction { *

    */ @Override - public void commit() throws RollbackException { + public void commit() { throw new PersistenceException("This is an external transaction so must be committed externally"); } diff --git a/src/main/java/io/ebeaninternal/server/transaction/JdbcTransaction.java b/src/main/java/io/ebeaninternal/server/transaction/JdbcTransaction.java index ab94c25ee..9d611ce54 100644 --- a/src/main/java/io/ebeaninternal/server/transaction/JdbcTransaction.java +++ b/src/main/java/io/ebeaninternal/server/transaction/JdbcTransaction.java @@ -15,6 +15,7 @@ import io.ebeaninternal.server.core.PersistRequest; import io.ebeaninternal.server.core.PersistRequestBean; import io.ebeaninternal.server.lib.util.Str; import io.ebeaninternal.server.persist.BatchControl; +import io.ebeaninternal.server.persist.BatchedSqlException; import io.ebeanservice.docstore.api.DocStoreTransaction; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -620,28 +621,45 @@ public class JdbcTransaction implements SpiTransaction { @Override public void flushBatchOnCollection() { if (batchOnCascadeSet) { - if (batchControl != null) { - if (logger.isTraceEnabled()) { - logger.trace("... flushBatchOnCollection"); - } - batchControl.flushReset(); - } + batchFlushReset(); // restore the previous batch mode of NONE batchMode = PersistBatch.NONE; } } + private void batchFlush() { + if (batchControl != null) { + try { + batchControl.flush(); + } catch (BatchedSqlException e) { + throw translate(e.getMessage(), e.getCause()); + } + } + } + + private void batchFlushReset() { + if (batchControl != null) { + try { + batchControl.flushReset(); + } catch (BatchedSqlException e) { + throw translate(e.getMessage(), e.getCause()); + } + } + } + + public PersistenceException translate(String message, SQLException cause) { + if (manager != null) { + return manager.translate(message, cause); + } + return new PersistenceException(message, cause); + } + /** * Flush after completing persist cascade. */ @Override public void flushBatchOnCascade() { - if (batchControl != null) { - if (logger.isTraceEnabled()) { - logger.trace("... flushBatchOnCascade"); - } - batchControl.flushReset(); - } + batchFlushReset(); // restore the previous batch mode batchMode = oldBatchMode; } @@ -670,21 +688,13 @@ public class JdbcTransaction implements SpiTransaction { // escalate up to batch mode for this request (and cascade) oldBatchMode = batchMode; batchMode = PersistBatch.ALL; - if (batchControl != null) { - // flush with reset so that this request goes into it's own batch buffer - batchControl.flushReset(); - } + batchFlushReset(); // skip using jdbc batch for the top level bean (no gain there) request.setSkipBatchForTopLevel(); return true; } - if (batchControl != null && !batchControl.isEmpty()) { - if (logger.isTraceEnabled()) { - logger.trace("... flush from batchOnCascade "); - } - batchControl.flushReset(); - } + batchFlushReset(); return false; } @@ -732,9 +742,7 @@ public class JdbcTransaction implements SpiTransaction { * Flush the JDBC batch and execute derived relationship statements if necessary. */ private void internalBatchFlush() { - if (batchControl != null) { - batchControl.flush(); - } + batchFlush(); if (deferredList != null) { for (PersistDeferredRelationship deferred : deferredList) { deferred.execute(this); @@ -935,7 +943,7 @@ public class JdbcTransaction implements SpiTransaction { *

    */ @Override - public void commitAndContinue() throws RollbackException { + public void commitAndContinue() { if (rollbackOnly) { return; } @@ -951,7 +959,7 @@ public class JdbcTransaction implements SpiTransaction { } catch (Exception e) { doRollback(e); - throw new RollbackException(e); + throw wrapIfNeeded(e); } } @@ -959,7 +967,7 @@ public class JdbcTransaction implements SpiTransaction { * Commit the transaction. */ @Override - public void commit() throws RollbackException { + public void commit() { if (rollbackOnly) { rollback(); return; @@ -976,13 +984,24 @@ public class JdbcTransaction implements SpiTransaction { } catch (Exception e) { doRollback(e); - throw new RollbackException(e); + throw wrapIfNeeded(e); } finally { deactivate(); } } + /** + * Try to keep specific exceptions and otherwise wrap as RollbackException. + */ + private RuntimeException wrapIfNeeded(Exception e) { + if (e instanceof PersistenceException) { + // keep more specific exception if we have it + return (PersistenceException)e; + } + return new RollbackException(e); + } + /** * Notify the transaction manager. */ diff --git a/src/main/java/io/ebeaninternal/server/transaction/TransactionManager.java b/src/main/java/io/ebeaninternal/server/transaction/TransactionManager.java index 3300b7a32..3c38374d2 100644 --- a/src/main/java/io/ebeaninternal/server/transaction/TransactionManager.java +++ b/src/main/java/io/ebeaninternal/server/transaction/TransactionManager.java @@ -3,6 +3,7 @@ package io.ebeaninternal.server.transaction; import io.ebean.BackgroundExecutor; import io.ebean.config.CurrentTenantProvider; import io.ebean.PersistBatch; +import io.ebean.config.dbplatform.DatabasePlatform; import io.ebean.config.dbplatform.DatabasePlatform.OnQueryOnly; import io.ebean.event.changelog.ChangeLogListener; import io.ebean.event.changelog.ChangeLogPrepare; @@ -19,8 +20,10 @@ import io.ebeanservice.docstore.api.DocStoreUpdates; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import javax.persistence.PersistenceException; import javax.sql.DataSource; import java.sql.Connection; +import java.sql.SQLException; import java.util.List; import java.util.Set; @@ -100,11 +103,14 @@ public class TransactionManager { private final TransactionFactory transactionFactory; + private final DatabasePlatform databasePlatform; + /** * Create the TransactionManager */ public TransactionManager(TransactionManagerOptions options) { + this.databasePlatform = options.config.getDatabasePlatform(); this.skipCacheAfterWrite = options.config.isSkipCacheAfterWrite(); this.localL2Caching = options.localL2Caching; this.persistBatch = options.config.getPersistBatch(); @@ -132,6 +138,13 @@ public class TransactionManager { } } + /** + * Translate the SQLException into a specific exception if possible based on the DB platform. + */ + public PersistenceException translate(String message, SQLException cause) { + return databasePlatform.translate(message, cause); + } + public void shutdown(boolean shutdownDataSource, boolean deregisterDriver) { if (shutdownDataSource) { dataSourceSupplier.shutdown(deregisterDriver); diff --git a/src/main/resources/io/ebeaninternal/api/message.properties b/src/main/resources/io/ebeaninternal/api/message.properties index ac455a28f..0a54fe207 100644 --- a/src/main/resources/io/ebeaninternal/api/message.properties +++ b/src/main/resources/io/ebeaninternal/api/message.properties @@ -41,8 +41,8 @@ fetch.bind.error=error binding parameter [{0}][{1}] fetch.bind.datatype=Datatype [{0}] not handled for parameter [{1}][{2}] fetch.bind.datatype2=Datatype for [{0}] not handled. -fetch.sqlerror=Query threw SQLException:{0} \r\nBind values:[{1}] \r\nQuery was:\r\n{2} \r\n\r\n -fetch.error=Query threw SQLException:{0} Query was:\r\n{1}\r\n\r\n +fetch.sqlerror=Query threw SQLException:{0} Bind values:[{1}] Query was:{2} +fetch.error=Query threw SQLException:{0} Query was:{1} fetch.limit.orderby=You must specify an OrderBy if limiting the ResultSet fetch.many.depth=Many property [{0}] can not be included due to the wrong depth. diff --git a/src/test/java/io/ebeaninternal/server/querydefn/DefaultOrmQueryTest.java b/src/test/java/io/ebeaninternal/server/querydefn/DefaultOrmQueryTest.java index 451353372..427539c80 100644 --- a/src/test/java/io/ebeaninternal/server/querydefn/DefaultOrmQueryTest.java +++ b/src/test/java/io/ebeaninternal/server/querydefn/DefaultOrmQueryTest.java @@ -1,6 +1,7 @@ package io.ebeaninternal.server.querydefn; +import io.ebean.BaseTestCase; import io.ebean.Ebean; import org.tests.model.basic.Customer; import org.tests.model.basic.Order; @@ -8,7 +9,7 @@ import org.junit.Test; import static org.assertj.core.api.Assertions.assertThat; -public class DefaultOrmQueryTest { +public class DefaultOrmQueryTest extends BaseTestCase { @Test public void when_forUpdate_then_excludeFromBeanCache() { diff --git a/src/test/java/io/ebeaninternal/server/querydefn/OrmQueryPlanKeyTest.java b/src/test/java/io/ebeaninternal/server/querydefn/OrmQueryPlanKeyTest.java index 69ae03464..b0aece47b 100644 --- a/src/test/java/io/ebeaninternal/server/querydefn/OrmQueryPlanKeyTest.java +++ b/src/test/java/io/ebeaninternal/server/querydefn/OrmQueryPlanKeyTest.java @@ -2,6 +2,7 @@ package io.ebeaninternal.server.querydefn; import io.ebean.OrderBy; +import io.ebean.Query; import io.ebeaninternal.api.SpiExpression; import io.ebeaninternal.api.SpiExpressionList; import io.ebeaninternal.api.SpiQuery; @@ -21,8 +22,8 @@ public class OrmQueryPlanKeyTest extends BaseExpressionTest { @Test public void equals_when_defaults() { - OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null, null); - OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null, null); + OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, null, null, null, null); + OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, null, null, null, null); assertSame(key1, key2); } @@ -32,8 +33,8 @@ public class OrmQueryPlanKeyTest extends BaseExpressionTest { TableJoin tableJoin = tableJoin("table", "id", "customer_id"); - OrmQueryPlanKey key1 = new OrmQueryPlanKey(tableJoin, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null, null); - OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null, null); + OrmQueryPlanKey key1 = new OrmQueryPlanKey(tableJoin, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, null, null, null, null); + OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, null, null, null, null); assertDifferent(key1, key2); } @@ -44,8 +45,8 @@ public class OrmQueryPlanKeyTest extends BaseExpressionTest { TableJoin tableJoin1 = tableJoin("one", "id", "customer_id"); TableJoin tableJoin2 = tableJoin("two", "id", "customer_id"); - OrmQueryPlanKey key1 = new OrmQueryPlanKey(tableJoin1, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null, null); - OrmQueryPlanKey key2 = new OrmQueryPlanKey(tableJoin2, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null, null); + OrmQueryPlanKey key1 = new OrmQueryPlanKey(tableJoin1, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, null, null, null, null); + OrmQueryPlanKey key2 = new OrmQueryPlanKey(tableJoin2, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, null, null, null, null); assertDifferent(key1, key2); } @@ -56,8 +57,8 @@ public class OrmQueryPlanKeyTest extends BaseExpressionTest { TableJoin tableJoin1 = tableJoin("one", "id", "customer_id"); TableJoin tableJoin2 = tableJoin("one", "id", "customer_id"); - OrmQueryPlanKey key1 = new OrmQueryPlanKey(tableJoin1, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null, null); - OrmQueryPlanKey key2 = new OrmQueryPlanKey(tableJoin2, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null, null); + OrmQueryPlanKey key1 = new OrmQueryPlanKey(tableJoin1, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, null, null, null, null); + OrmQueryPlanKey key2 = new OrmQueryPlanKey(tableJoin2, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, null, null, null, null); assertSame(key1, key2); } @@ -73,8 +74,8 @@ public class OrmQueryPlanKeyTest extends BaseExpressionTest { @Test public void equals_when_diffQueryType() { - OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.LIST, null, 0, 0, false, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null, null); - OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null, null); + OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.LIST, null, 0, 0, false, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, null, null, null, null); + OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, null, null, null, null); assertDifferent(key1, key2); } @@ -82,8 +83,8 @@ public class OrmQueryPlanKeyTest extends BaseExpressionTest { @Test public void equals_when_firstRowsDifferent() { - OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 10, false, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null, null); - OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null, null); + OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 10, false, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, null, null, null, null); + OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, null, null, null, null); assertDifferent(key1, key2); } @@ -91,8 +92,8 @@ public class OrmQueryPlanKeyTest extends BaseExpressionTest { @Test public void equals_when_maxRowsDifferent() { - OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 10, 0, false, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null, null); - OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null, null); + OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 10, 0, false, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, null, null, null, null); + OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, null, null, null, null); assertDifferent(key1, key2); } @@ -100,8 +101,8 @@ public class OrmQueryPlanKeyTest extends BaseExpressionTest { @Test public void equals_when_firstRowsMaxRowsSame() { - OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 10, 20, false, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null, null); - OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 10, 20, false, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null, null); + OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 10, 20, false, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, null, null, null, null); + OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 10, 20, false, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, null, null, null, null); assertSame(key1, key2); } @@ -109,8 +110,8 @@ public class OrmQueryPlanKeyTest extends BaseExpressionTest { @Test public void equals_when_diffDisableLazyLoading() { - OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, true, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null, null); - OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null, null); + OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, true, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, null, null, null, null); + OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, null, null, null, null); assertDifferent(key1, key2); } @@ -119,8 +120,8 @@ public class OrmQueryPlanKeyTest extends BaseExpressionTest { public void equals_when_diffOrderByNull() { OrderBy o1 = new OrderBy<>("id"); - OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, o1, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null, null); - OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null, null); + OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, o1, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, null, null, null, null); + OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, null, null, null, null); assertDifferent(key1, key2); } @@ -130,107 +131,121 @@ public class OrmQueryPlanKeyTest extends BaseExpressionTest { OrderBy o1 = new OrderBy<>("id, name"); OrderBy o2 = new OrderBy<>("id, name"); - OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, o1, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null, null); - OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, o2, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null, null); + OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, o1, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, null, null, null, null); + OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, o2, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, null, null, null, null); assertSame(key1, key2); } @Test public void equals_when_diffDistinct() { - OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, true, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null, null); - OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null, null); + OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, true, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, null, null, null, null); + OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, null, null, null, null); assertDifferent(key1, key2); } @Test public void equals_when_sameDistinct() { - OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, true, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null, null); - OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, true, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null, null); + OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, true, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, null, null, null, null); + OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, true, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, null, null, null, null); assertSame(key1, key2); } @Test public void equals_when_diffSqlDistinct() { - OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, true, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null, null); - OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null, null); + OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, true, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, null, null, null, null); + OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, null, null, null, null); assertDifferent(key1, key2); } @Test public void equals_when_sameSqlDistinct() { - OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, true, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null, null); - OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, true, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null, null); + OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, true, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, null, null, null, null); + OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, true, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, null, null, null, null); assertSame(key1, key2); } @Test public void equals_when_diffMapKeyNull() { - OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, "mapKey", null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null, null); - OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null, null); + OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, "mapKey", null, null, null, null, SpiQuery.TemporalMode.CURRENT, null, null, null, null); + OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, null, null, null, null); assertDifferent(key1, key2); } @Test public void equals_when_diffMapKey() { - OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, "mapKey", null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null, null); - OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, "diff", null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null, null); + OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, "mapKey", null, null, null, null, SpiQuery.TemporalMode.CURRENT, null, null, null, null); + OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, "diff", null, null, null, null, SpiQuery.TemporalMode.CURRENT, null, null, null, null); assertDifferent(key1, key2); } @Test public void equals_when_sameMapKey() { - OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, "mapKey", null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null, null); - OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, "mapKey", null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null, null); + OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, "mapKey", null, null, null, null, SpiQuery.TemporalMode.CURRENT, null, null, null, null); + OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, "mapKey", null, null, null, null, SpiQuery.TemporalMode.CURRENT, null, null, null, null); assertSame(key1, key2); } @Test public void equals_when_diffIdNull() { - OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, null, 42, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null, null); - OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null, null); + OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, null, 42, null, null, null, SpiQuery.TemporalMode.CURRENT, null, null, null, null); + OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, null, null, null, null); assertDifferent(key1, key2); } @Test public void equals_when_idBothGiven() { - OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, null, 42, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null, null); - OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, null, 23, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null, null); + OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, null, 42, null, null, null, SpiQuery.TemporalMode.CURRENT, null, null, null, null); + OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, null, 23, null, null, null, SpiQuery.TemporalMode.CURRENT, null, null, null, null); assertSame(key1, key2); } @Test public void equals_when_diffTemporalMode() { - OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null, null); - OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.DRAFT, false, null, null, null); + OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, null, null, null, null); + OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.DRAFT, null, null, null, null); assertDifferent(key1, key2); } @Test public void equals_when_diffForUpdate() { - OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, true, null, null, null); - OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null, null); + OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, Query.ForUpdate.BASE, null, null, null); + OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, null, null, null, null); + assertDifferent(key1, key2); + } + + @Test + public void equals_when_diffForUpdate_NoWait() { + OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, Query.ForUpdate.BASE, null, null, null); + OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, Query.ForUpdate.NOWAIT, null, null, null); + assertDifferent(key1, key2); + } + + @Test + public void equals_when_diffForUpdate_SkipLocked() { + OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, Query.ForUpdate.BASE, null, null, null); + OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, Query.ForUpdate.SKIPLOCKED, null, null, null); assertDifferent(key1, key2); } @Test public void equals_when_diffRootAliasNull() { - OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, "rootAlias", null, null); - OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null, null); + OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, null, "rootAlias", null, null); + OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, null, null, null, null); assertDifferent(key1, key2); } @Test public void equals_when_diffRootAlias() { - OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, "rootAlias", null, null); - OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, "diff", null, null); + OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, null, "rootAlias", null, null); + OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, null, "diff", null, null); assertDifferent(key1, key2); } @Test public void equals_when_sameRootAlias() { - OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, "rootAlias", null, null); - OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, "rootAlias", null, null); + OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, null, "rootAlias", null, null); + OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, null, "rootAlias", null, null); assertSame(key1, key2); } @@ -257,8 +272,8 @@ public class OrmQueryPlanKeyTest extends BaseExpressionTest { SpiExpressionList list1 = list_id_eq_42(); SpiExpressionList list2 = list_id_eq_43(); - OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, null, null, null, list1, null, SpiQuery.TemporalMode.CURRENT, false, null, null, null); - OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, null, null, null, list2, null, SpiQuery.TemporalMode.CURRENT, false, null, null, null); + OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, null, null, null, list1, null, SpiQuery.TemporalMode.CURRENT, null, null, null, null); + OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, null, null, null, list2, null, SpiQuery.TemporalMode.CURRENT, null, null, null, null); assertSame(key1, key2); } @@ -269,8 +284,8 @@ public class OrmQueryPlanKeyTest extends BaseExpressionTest { SpiExpressionList where1 = list_id_eq_42(); SpiExpressionList where2 = list_id_eq_42_and_name_eq_rob(); - OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, null, null, null, where1, null, SpiQuery.TemporalMode.DRAFT, false, null, null, null); - OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, null, null, null, where2, null, SpiQuery.TemporalMode.DRAFT, false, null, null, null); + OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, null, null, null, where1, null, SpiQuery.TemporalMode.DRAFT, null, null, null, null); + OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, null, null, null, where2, null, SpiQuery.TemporalMode.DRAFT, null, null, null, null); assertDifferent(key1, key2); } @@ -279,8 +294,8 @@ public class OrmQueryPlanKeyTest extends BaseExpressionTest { SpiExpressionList list1 = list_id_eq_42(); - OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, null, null, null, list1, null, SpiQuery.TemporalMode.CURRENT, false, null, null, null); - OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null, null); + OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, null, null, null, list1, null, SpiQuery.TemporalMode.CURRENT, null, null, null, null); + OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, null, null, null, null); assertDifferent(key1, key2); } @@ -289,8 +304,8 @@ public class OrmQueryPlanKeyTest extends BaseExpressionTest { SpiExpressionList list1 = list_id_eq_42(); - OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null, null); - OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, null, null, null, list1, null, SpiQuery.TemporalMode.CURRENT, false, null, null, null); + OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, null, null, null, null); + OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, null, null, null, list1, null, SpiQuery.TemporalMode.CURRENT, null, null, null, null); assertDifferent(key1, key2); } @@ -300,8 +315,8 @@ public class OrmQueryPlanKeyTest extends BaseExpressionTest { SpiExpression having1 = list_id_eq_42().copyForPlanKey(); SpiExpression having2 = list_id_eq_42_and_name_eq_rob().copyForPlanKey(); - OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, null, null, null, null, having1, SpiQuery.TemporalMode.CURRENT, false, null, null, null); - OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, null, null, null, null, having2, SpiQuery.TemporalMode.CURRENT, false, null, null, null); + OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, null, null, null, null, having1, SpiQuery.TemporalMode.CURRENT, null, null, null, null); + OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, null, null, null, null, having2, SpiQuery.TemporalMode.CURRENT, null, null, null, null); assertDifferent(key1, key2); } @@ -311,8 +326,8 @@ public class OrmQueryPlanKeyTest extends BaseExpressionTest { SpiExpression having1 = list_id_eq_42().copyForPlanKey(); SpiExpression having2 = list_id_eq_42().copyForPlanKey(); - OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, null, null, null, null, having1, SpiQuery.TemporalMode.CURRENT, false, null, null, null); - OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, null, null, null, null, having2, SpiQuery.TemporalMode.CURRENT, false, null, null, null); + OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, null, null, null, null, having1, SpiQuery.TemporalMode.CURRENT, null, null, null, null); + OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, null, null, null, null, having2, SpiQuery.TemporalMode.CURRENT, null, null, null, null); assertSame(key1, key2); } @@ -321,8 +336,8 @@ public class OrmQueryPlanKeyTest extends BaseExpressionTest { SpiExpression having1 = list_id_eq_42().copyForPlanKey(); - OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, null, null, null, null, having1, SpiQuery.TemporalMode.CURRENT, false, null, null, null); - OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null, null); + OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, null, null, null, null, having1, SpiQuery.TemporalMode.CURRENT, null, null, null, null); + OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, null, null, null, null); assertDifferent(key1, key2); } @@ -331,8 +346,8 @@ public class OrmQueryPlanKeyTest extends BaseExpressionTest { SpiExpression having1 = list_id_eq_42().copyForPlanKey(); - OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, false, null, null, null); - OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, null, null, null, null, having1, SpiQuery.TemporalMode.CURRENT, false, null, null, null); + OrmQueryPlanKey key1 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, null, null, null, null, null, SpiQuery.TemporalMode.CURRENT, null, null, null, null); + OrmQueryPlanKey key2 = new OrmQueryPlanKey(null, SpiQuery.Type.BEAN, null, 0, 0, false, null, false, false, null, null, null, null, having1, SpiQuery.TemporalMode.CURRENT, null, null, null, null); assertDifferent(key1, key2); } diff --git a/src/test/java/org/tests/basic/TestQuery.java b/src/test/java/org/tests/basic/TestQuery.java deleted file mode 100644 index 887ebe61e..000000000 --- a/src/test/java/org/tests/basic/TestQuery.java +++ /dev/null @@ -1,46 +0,0 @@ -package org.tests.basic; - -import io.ebean.BaseTestCase; -import io.ebean.Ebean; -import io.ebean.Query; -import org.tests.model.basic.Order; -import org.tests.model.basic.ResetBasicData; -import org.junit.Assert; -import org.junit.Test; - -public class TestQuery extends BaseTestCase { - - @Test - public void testCountOrderBy() { - - ResetBasicData.reset(); - - Query query = Ebean.find(Order.class).setAutoTune(false).order().asc("orderDate") - .order().desc("id"); - // .orderBy("orderDate"); - - int rc = query.findList().size(); - Assert.assertTrue(rc > 0); - // String generatedSql = query.getGeneratedSql(); - // Assert.assertFalse(generatedSql.contains("order by")); - - } - - public void testForUpdate() { - ResetBasicData.reset(); - - Query query = Ebean.find(Order.class).setAutoTune(false).setForUpdate(false) - .setMaxRows(1).order().asc("orderDate").order().desc("id"); - - int rc = query.findList().size(); - Assert.assertTrue(rc > 0); - Assert.assertTrue(!query.getGeneratedSql().toLowerCase().contains("for update")); - - query = Ebean.find(Order.class).setAutoTune(false).setForUpdate(true).setMaxRows(1).order() - .asc("orderDate").order().desc("id"); - - rc = query.findList().size(); - Assert.assertTrue(rc > 0); - Assert.assertTrue(query.getGeneratedSql().toLowerCase().contains("for update")); - } -} diff --git a/src/test/java/org/tests/basic/TestQueryForUpdate.java b/src/test/java/org/tests/basic/TestQueryForUpdate.java new file mode 100644 index 000000000..c2902c8a6 --- /dev/null +++ b/src/test/java/org/tests/basic/TestQueryForUpdate.java @@ -0,0 +1,79 @@ +package org.tests.basic; + +import io.ebean.AcquireLockException; +import io.ebean.BaseTestCase; +import io.ebean.Ebean; +import io.ebean.EbeanServer; +import io.ebean.Query; +import io.ebean.Transaction; +import org.junit.Test; +import org.tests.model.basic.Customer; +import org.tests.model.basic.ResetBasicData; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.Assert.assertTrue; + +public class TestQueryForUpdate extends BaseTestCase { + + @Test + public void testForUpdate() { + + if (isH2() || isPostgres()) { + ResetBasicData.reset(); + + Query query = Ebean.find(Customer.class) + .forUpdate() + .setMaxRows(1) + .order().desc("id"); + + query.findList(); + assertThat(sqlOf(query)).contains("for update"); + } + } + + @Test + public void testForUpdate_noWait() { + + if (isPostgres() || isOracle()) { + ResetBasicData.reset(); + + EbeanServer server = Ebean.getDefaultServer(); + + Ebean.beginTransaction(); + try { + Query query = Ebean.find(Customer.class) + .forUpdateNoWait() + .setMaxRows(1) + .order().desc("id"); + + List list = query.findList(); + Customer first = list.get(0); + + assertThat(sqlOf(query)).contains("for update nowait"); + + // create a 2nd transaction to test that the + // row is locked and we can't acquire it + Transaction txn2 = server.createTransaction(); + try { + logger.info("... attempt another acquire using 2nd transaction"); + Query query2 = + server.find(Customer.class) + .where().idEq(first.getId()) + .forUpdateNoWait(); + + server.findUnique(query2, txn2); + assertTrue(false); // never get here + } catch (AcquireLockException e) { + logger.info("... got AcquireLockException " + e); + } finally { + txn2.end(); + } + + } finally { + Ebean.endTransaction(); + } + } + } +} diff --git a/src/test/java/org/tests/insert/TestInsertDataIntegrityException.java b/src/test/java/org/tests/insert/TestInsertDataIntegrityException.java new file mode 100644 index 000000000..94518ddce --- /dev/null +++ b/src/test/java/org/tests/insert/TestInsertDataIntegrityException.java @@ -0,0 +1,28 @@ +package org.tests.insert; + +import io.ebean.BaseTestCase; +import io.ebean.DataIntegrityException; +import io.ebean.Ebean; +import org.junit.Test; +import org.tests.model.basic.Customer; +import org.tests.model.basic.Order; +import org.tests.model.basic.ResetBasicData; + +public class TestInsertDataIntegrityException extends BaseTestCase { + + + @Test(expected = DataIntegrityException.class) + public void insert_invalidForeignKey() { + + ResetBasicData.reset(); + + // an invalid foreign key value + Customer invalidCustomer = Ebean.getReference(Customer.class, 900000); + + Order order = new Order(); + order.setStatus(Order.Status.NEW); + order.setCustomer(invalidCustomer); + + Ebean.save(order); + } +} diff --git a/src/test/java/org/tests/insert/TestInsertDuplicateKey.java b/src/test/java/org/tests/insert/TestInsertDuplicateKey.java new file mode 100644 index 000000000..83ff17ad0 --- /dev/null +++ b/src/test/java/org/tests/insert/TestInsertDuplicateKey.java @@ -0,0 +1,103 @@ +package org.tests.insert; + +import io.ebean.BaseTestCase; +import io.ebean.DuplicateKeyException; +import io.ebean.Ebean; +import io.ebean.annotation.Transactional; +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.tests.model.draftable.Document; + +import java.sql.SQLException; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +public class TestInsertDuplicateKey extends BaseTestCase { + + private static final Logger log = LoggerFactory.getLogger(TestInsertDuplicateKey.class); + + @Test(expected = DuplicateKeyException.class) + public void insert_duplicateKey() { + + Document doc1 = new Document(); + doc1.setTitle("ThisIsAUniqueKey"); + doc1.setBody("one"); + + doc1.save(); + + Document doc2 = new Document(); + doc2.setTitle("ThisIsAUniqueKey"); + doc2.setBody("clashes with doc1"); + + doc2.save(); + } + + @Transactional(batchSize = 100) + @Test(expected = DuplicateKeyException.class) + public void insertBatch_duplicateKey() { + + Document doc1 = new Document(); + doc1.setTitle("ThisIsASecondUniqueKey"); + doc1.setBody("one"); + + doc1.save(); + + Document doc2 = new Document(); + doc2.setTitle("ThisIsASecondUniqueKey"); + doc2.setBody("clash when batch flushed"); + + doc2.save(); + } + + @Test + public void insertBatch_duplicateKey_catchAndContinue() { + + insertTheBatch_duplicateKey_catchAndContinue(); + + List found = Ebean.getDefaultServer() + .find(Document.class) + .asDraft() + .where().startsWith("body", "insertTheBatch_duplicateKey_catchAndContinue") + .findList(); + + assertThat(found).hasSize(1); + assertThat(found.get(0).getTitle()).isEqualTo("ThisIsAThirdUniqueKey"); + assertThat(found.get(0).getBody()).isEqualTo("insertTheBatch_duplicateKey_catchAndContinue-1"); + } + + @Transactional//(batchSize = 100) + private void insertTheBatch_duplicateKey_catchAndContinue() { + + Document doc1 = new Document(); + doc1.setTitle("ThisIsAThirdUniqueKey"); + doc1.setBody("insertTheBatch_duplicateKey_catchAndContinue-1"); + doc1.save(); + + try { + Document doc2 = new Document(); + doc2.setTitle("ThisIsAThirdUniqueKey"); + doc2.setBody("insertTheBatch_duplicateKey_catchAndContinue-2"); + doc2.save(); + + // flush at this point, fails + Ebean.getDefaultServer().currentTransaction().flushBatch(); + } catch (DuplicateKeyException e) { + log.info("duplicate failed but just continue" + e.getMessage()); + try { + // typically we would use transaction.commitAndContinue() + // ... this is a rollback and continue type scenario + // ... more sensible to use a second transaction that do this + Ebean.getDefaultServer().currentTransaction().getConnection().rollback(); + } catch (SQLException e1) { + e1.printStackTrace(); + } + } + + Document doc0 = new Document(); + doc0.setTitle("ThisIsAThirdUniqueKey"); + doc0.setBody("insertTheBatch_duplicateKey_catchAndContinue-1"); + doc0.save(); + } +}