diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index bf03d3b50..5e3fafdd4 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -31,6 +31,10 @@ jobs: path: ~/.m2 key: build-${{ env.cache-name }} + - name: Maven version + run: mvn --version +# - name: Maven single test +# run: mvn --batch-mode clean verify -Dtest="io.ebeaninternal.server.core.DefaultServer_getReferenceTest" -DfailIfNoTests=false - name: Build with Maven - run: mvn package + run: mvn clean package diff --git a/README.md b/README.md index 30dc2c06b..a560d8c82 100644 --- a/README.md +++ b/README.md @@ -49,6 +49,3 @@ Goto [https://ebean.io/docs/](https://ebean.io/docs/) [Maven central - all related projects](http://search.maven.org/#search%7Cga%7C1%7Cebean "maven central all related projects") -## Other versions -* [![Maven Central : ebean-agent](https://maven-badges.herokuapp.com/maven-central/io.ebean/ebean-agent/badge.svg)](https://maven-badges.herokuapp.com/maven-central/io.ebean/ebean-agent) - ebean-agent -* [![Maven Central : ebean-maven-plugin](https://maven-badges.herokuapp.com/maven-central/io.ebean/ebean-maven-plugin/badge.svg)](https://maven-badges.herokuapp.com/maven-central/io.ebean/ebean-maven-plugin) - ebean-maven-plugin diff --git a/ebean-api/src/main/java/io/ebean/config/dbplatform/BasicSqlAnsiLimiter.java b/ebean-api/src/main/java/io/ebean/config/dbplatform/BasicSqlAnsiLimiter.java index 378f029fb..968d16a3f 100644 --- a/ebean-api/src/main/java/io/ebean/config/dbplatform/BasicSqlAnsiLimiter.java +++ b/ebean-api/src/main/java/io/ebean/config/dbplatform/BasicSqlAnsiLimiter.java @@ -7,13 +7,10 @@ public class BasicSqlAnsiLimiter implements BasicSqlLimiter { @Override public String limit(String dbSql, int firstRow, int maxRows) { - StringBuilder sb = new StringBuilder(50 + dbSql.length()); - sb.append(dbSql); if (firstRow > 0) { - sb.append(" ").append("offset"); - sb.append(" ").append(firstRow).append(" rows"); + sb.append(" offset ").append(firstRow).append(" rows"); } if (maxRows > 0) { sb.append(" fetch next ").append(maxRows).append(" rows only"); diff --git a/ebean-api/src/main/java/io/ebean/config/dbplatform/h2/H2Platform.java b/ebean-api/src/main/java/io/ebean/config/dbplatform/h2/H2Platform.java index cdc923824..4f81f4eb2 100644 --- a/ebean-api/src/main/java/io/ebean/config/dbplatform/h2/H2Platform.java +++ b/ebean-api/src/main/java/io/ebean/config/dbplatform/h2/H2Platform.java @@ -39,6 +39,7 @@ public class H2Platform extends DatabasePlatform { this.dbIdentity.setSupportsIdentity(true); dbTypeMap.put(DbType.UUID, new DbPlatformType("uuid", false)); + dbTypeMap.put(DbType.INTEGER, new DbPlatformType("integer", false)); } /** diff --git a/ebean-api/src/main/java/io/ebean/config/dbplatform/h2/H2SequenceIdGenerator.java b/ebean-api/src/main/java/io/ebean/config/dbplatform/h2/H2SequenceIdGenerator.java index b4e875511..5ef08c059 100644 --- a/ebean-api/src/main/java/io/ebean/config/dbplatform/h2/H2SequenceIdGenerator.java +++ b/ebean-api/src/main/java/io/ebean/config/dbplatform/h2/H2SequenceIdGenerator.java @@ -18,13 +18,12 @@ public class H2SequenceIdGenerator extends SequenceBatchIdGenerator { */ public H2SequenceIdGenerator(BackgroundExecutor be, DataSource ds, String seqName, int batchSize) { super(be, ds, seqName, batchSize); - this.baseSql = "select " + seqName + ".nextval"; + this.baseSql = "select nextval('" + seqName + "')"; this.unionBaseSql = " union " + baseSql; } @Override public String getSql(int batchSize) { - StringBuilder sb = new StringBuilder(); sb.append(baseSql); for (int i = 1; i < batchSize; i++) { diff --git a/ebean-api/src/main/java/io/ebean/config/dbplatform/oracle/Oracle11Platform.java b/ebean-api/src/main/java/io/ebean/config/dbplatform/oracle/Oracle11Platform.java index 5934b6dd4..cb32a7470 100644 --- a/ebean-api/src/main/java/io/ebean/config/dbplatform/oracle/Oracle11Platform.java +++ b/ebean-api/src/main/java/io/ebean/config/dbplatform/oracle/Oracle11Platform.java @@ -11,6 +11,7 @@ public class Oracle11Platform extends OraclePlatform { public Oracle11Platform() { super(); this.platform = Platform.ORACLE11; + this.columnAliasPrefix = "c"; this.sqlLimiter = new OracleRownumSqlLimiter(); this.basicSqlLimiter = new OracleRownumBasicLimiter(); dbIdentity.setIdType(IdType.SEQUENCE); diff --git a/ebean-api/src/main/java/io/ebean/config/dbplatform/oracle/Oracle12Platform.java b/ebean-api/src/main/java/io/ebean/config/dbplatform/oracle/Oracle12Platform.java new file mode 100644 index 000000000..479c5f229 --- /dev/null +++ b/ebean-api/src/main/java/io/ebean/config/dbplatform/oracle/Oracle12Platform.java @@ -0,0 +1,13 @@ +package io.ebean.config.dbplatform.oracle; + +/** + * Oracle 12 platform using column alias. + */ +public class Oracle12Platform extends OraclePlatform { + + public Oracle12Platform() { + super(); + //this.platform = Platform.ORACLE12; + this.columnAliasPrefix = "c"; + } +} diff --git a/ebean-api/src/main/java/io/ebean/config/dbplatform/oracle/OraclePlatform.java b/ebean-api/src/main/java/io/ebean/config/dbplatform/oracle/OraclePlatform.java index d6896ad3e..e9dd57207 100644 --- a/ebean-api/src/main/java/io/ebean/config/dbplatform/oracle/OraclePlatform.java +++ b/ebean-api/src/main/java/io/ebean/config/dbplatform/oracle/OraclePlatform.java @@ -22,7 +22,6 @@ public class OraclePlatform extends DatabasePlatform { public OraclePlatform() { super(); this.platform = Platform.ORACLE; - this.columnAliasPrefix = "c"; this.supportsDeleteTableAlias = true; this.maxTableNameLength = 30; this.maxConstraintNameLength = 30; @@ -35,11 +34,9 @@ public class OraclePlatform extends DatabasePlatform { dbIdentity.setSupportsSequence(true); dbIdentity.setSupportsIdentity(true); dbIdentity.setSupportsGetGeneratedKeys(true); - this.dbDefaultValue.setFalse("0"); this.dbDefaultValue.setTrue("1"); this.dbDefaultValue.setNow("current_timestamp"); - this.treatEmptyStringsAsNull = true; this.likeClauseRaw = "like ?"; @@ -56,7 +53,6 @@ public class OraclePlatform extends DatabasePlatform { booleanDbType = Types.INTEGER; dbTypeMap.put(DbType.BOOLEAN, new DbPlatformType("number(1)")); - dbTypeMap.put(DbType.INTEGER, new DbPlatformType("number", 10)); dbTypeMap.put(DbType.BIGINT, new DbPlatformType("number", 19)); dbTypeMap.put(DbType.REAL, new DbPlatformType("number", 19, 4)); @@ -65,12 +61,10 @@ public class OraclePlatform extends DatabasePlatform { dbTypeMap.put(DbType.TINYINT, new DbPlatformType("number", 3)); dbTypeMap.put(DbType.DECIMAL, new DbPlatformType("number", 16, 3)); dbTypeMap.put(DbType.VARCHAR, new DbPlatformType("varchar2", 255)); - dbTypeMap.put(DbType.LONGVARBINARY, new DbPlatformType("blob")); dbTypeMap.put(DbType.LONGVARCHAR, new DbPlatformType("clob")); dbTypeMap.put(DbType.VARBINARY, new DbPlatformType("raw", 255)); dbTypeMap.put(DbType.BINARY, new DbPlatformType("raw", 255)); - dbTypeMap.put(DbType.TIME, new DbPlatformType("timestamp")); } diff --git a/ebean-core-type/src/main/java/io/ebean/core/type/DataReader.java b/ebean-core-type/src/main/java/io/ebean/core/type/DataReader.java index ef61b9122..d9a1ade92 100644 --- a/ebean-core-type/src/main/java/io/ebean/core/type/DataReader.java +++ b/ebean-core-type/src/main/java/io/ebean/core/type/DataReader.java @@ -47,6 +47,8 @@ public interface DataReader { Object getObject() throws SQLException; + T getObject(Class cls) throws SQLException; + InputStream getBinaryStream() throws SQLException; /** diff --git a/ebean-core/pom.xml b/ebean-core/pom.xml index 7d898fffb..21bca35bd 100644 --- a/ebean-core/pom.xml +++ b/ebean-core/pom.xml @@ -72,7 +72,7 @@ com.h2database h2 - 1.4.199 + ${h2database.version} provided diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/core/DatabasePlatformFactory.java b/ebean-core/src/main/java/io/ebeaninternal/server/core/DatabasePlatformFactory.java index dcfca1310..96600609a 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/core/DatabasePlatformFactory.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/core/DatabasePlatformFactory.java @@ -13,6 +13,7 @@ import io.ebean.config.dbplatform.mysql.MySql55Platform; import io.ebean.config.dbplatform.mysql.MySqlPlatform; import io.ebean.config.dbplatform.nuodb.NuoDbPlatform; import io.ebean.config.dbplatform.oracle.Oracle11Platform; +import io.ebean.config.dbplatform.oracle.Oracle12Platform; import io.ebean.config.dbplatform.oracle.OraclePlatform; import io.ebean.config.dbplatform.postgres.Postgres9Platform; import io.ebean.config.dbplatform.postgres.PostgresPlatform; @@ -25,11 +26,7 @@ import io.ebeaninternal.api.DbOffline; import javax.persistence.PersistenceException; import javax.sql.DataSource; -import java.sql.Connection; -import java.sql.DatabaseMetaData; -import java.sql.PreparedStatement; -import java.sql.ResultSet; -import java.sql.SQLException; +import java.sql.*; /** * Create a DatabasePlatform from the configuration. @@ -87,6 +84,9 @@ public class DatabasePlatformFactory { if (dbName.equals("oracle11") || dbName.equals("oracle10") || dbName.equals("oracle9")) { return new Oracle11Platform(); } + if (dbName.equals("oracle12")) { + return new Oracle12Platform(); + } if (dbName.equals("oracle")) { return new OraclePlatform(); } @@ -173,7 +173,13 @@ public class DatabasePlatformFactory { } private DatabasePlatform oracleVersion(int majorVersion) { - return majorVersion < 12 ? new Oracle11Platform() : new OraclePlatform(); + if (majorVersion < 12) { + return new Oracle11Platform(); + } + if (majorVersion < 13) { + return new Oracle12Platform(); + } + return new OraclePlatform(); } private DatabasePlatform mysqlVersion(int majorVersion, int minorVersion) { diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanPropertyAssocMany.java b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanPropertyAssocMany.java index 73765d43d..09851725f 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanPropertyAssocMany.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanPropertyAssocMany.java @@ -226,7 +226,7 @@ public class BeanPropertyAssocMany extends BeanPropertyAssoc implements ST */ @Override public void addBeanToCollectionWithCreate(EntityBean parentBean, EntityBean detailBean, boolean withCheck) { - BeanCollection bc = (BeanCollection) super.getValue(parentBean); + BeanCollection bc = beanCollection(parentBean); if (bc == null) { bc = help.createEmpty(parentBean); setValue(parentBean, bc); @@ -234,6 +234,15 @@ public class BeanPropertyAssocMany extends BeanPropertyAssoc implements ST help.add(bc, detailBean, withCheck); } + private BeanCollection beanCollection(EntityBean parentBean) { + try { + return (BeanCollection) super.getValue(parentBean); + } catch (ClassCastException e) { + // fetching element collection, ok for now + return null; + } + } + /** * Return true if this is considered 'empty' from a save perspective. */ diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/parse/DeployInherit.java b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/parse/DeployInherit.java index 2dd13f993..4eb2fa640 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/parse/DeployInherit.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/parse/DeployInherit.java @@ -6,6 +6,7 @@ import io.ebeaninternal.server.deploy.InheritInfo; import io.ebeaninternal.server.deploy.meta.DeployBeanDescriptor; import javax.persistence.DiscriminatorColumn; +import javax.persistence.DiscriminatorType; import javax.persistence.DiscriminatorValue; import javax.persistence.Inheritance; import java.util.LinkedHashMap; @@ -105,8 +106,11 @@ public final class DeployInherit { if (da != null) { // lowercase the discriminator column for RawSql and JSON info.setColumnName(da.name().toLowerCase()); - info.setColumnType(da.discriminatorType()); - info.setColumnLength(da.length()); + DiscriminatorType discriminatorType = da.discriminatorType(); + info.setColumnType(discriminatorType); + if (discriminatorType == DiscriminatorType.STRING) { + info.setColumnLength(da.length()); + } info.setColumnDefn(da.columnDefinition()); } if (!info.isAbstract()) { diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/expression/platform/DbExpressionHandlerFactory.java b/ebean-core/src/main/java/io/ebeaninternal/server/expression/platform/DbExpressionHandlerFactory.java index 346535b03..110719ddd 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/expression/platform/DbExpressionHandlerFactory.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/expression/platform/DbExpressionHandlerFactory.java @@ -10,20 +10,17 @@ public final class DbExpressionHandlerFactory { * Create and return the appropriate platform specific handing of expressions. */ public static DbExpressionHandler from(DatabasePlatform databasePlatform) { - Platform platform = databasePlatform.getPlatform(); + Platform platform = databasePlatform.getPlatform().base(); switch (platform) { case H2: return new H2DbExpression(); case POSTGRES: - case POSTGRES9: return new PostgresDbExpression(); case MARIADB: return new MariaDbExpression(); - case MYSQL55: case MYSQL: return new MySqlDbExpression(); case ORACLE: - case ORACLE11: return new OracleDbExpression(); case DB2: return new Db2DbExpression(); diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/expression/platform/H2DbExpression.java b/ebean-core/src/main/java/io/ebeaninternal/server/expression/platform/H2DbExpression.java index 5eddb5d11..60cb180d3 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/expression/platform/H2DbExpression.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/expression/platform/H2DbExpression.java @@ -10,6 +10,7 @@ final class H2DbExpression extends BasicDbExpression { @Override public void bitwise(SpiExpressionRequest request, String propName, BitwiseOp operator, long flags, String compare, long match) { - bitwiseFunction(request, propName, operator, compare); + String funcName = functionName(operator); + request.append(funcName).append("(").append(propName).append(", cast(? as long)) ").append(compare).append(" cast(? as long)"); } } diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/persist/dml/InsertMeta.java b/ebean-core/src/main/java/io/ebeaninternal/server/persist/dml/InsertMeta.java index be8bf6c79..e3a89d1fa 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/persist/dml/InsertMeta.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/persist/dml/InsertMeta.java @@ -13,11 +13,8 @@ import io.ebeaninternal.server.persist.dmlbind.BindableList; import java.sql.SQLException; -import static io.ebean.annotation.Platform.MARIADB; -import static io.ebean.annotation.Platform.MYSQL; - /** - * Meta data for insert handler. The meta data is for a particular bean type. It + * Metadata for insert handler. The metadata is for a particular bean type. It * is considered immutable and is thread safe. */ final class InsertMeta { @@ -153,31 +150,25 @@ final class InsertMeta { private String genSql(boolean nullId, String table, boolean draftTable) { GenerateDmlRequest request = new GenerateDmlRequest(); request.setInsertSetMode(); - request.append("insert into ").append(table); if (nullId && noColumnsForInsert(draftTable)) { return request.append(defaultValues()).toString(); } - request.append(" ("); if (!nullId) { id.dmlAppend(request); } - if (shadowFKey != null) { shadowFKey.dmlAppend(request); } - if (discriminator != null) { discriminator.dmlAppend(request); } - if (draftTable) { all.dmlAppend(request); } else { allExcludeDraftOnly.dmlAppend(request); } - request.append(") values ("); request.append(request.getInsertBindBuffer()); request.append(")"); @@ -186,13 +177,14 @@ final class InsertMeta { private String defaultValues() { switch (platform.base()) { - case MYSQL: - case MARIADB: - return " values (default)"; - case DB2: - return " (" + id.getIdentityColumn() + ") values (default)"; - default: - return " default values"; + case MYSQL: + case MARIADB: + case ORACLE: + return " values (default)"; + case DB2: + return " (" + id.getIdentityColumn() + ") values (default)"; + default: + return " default values"; } } diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/query/CQuery.java b/ebean-core/src/main/java/io/ebeaninternal/server/query/CQuery.java index 45a5b5a11..1870c1b6f 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/query/CQuery.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/query/CQuery.java @@ -193,7 +193,7 @@ public final class CQuery implements DbReadContext, CancelableQuery, SpiProfi // set the generated sql back to the query // so its available to the user... query.setGeneratedSql(queryPlan.getSql()); - SqlTree sqlTree = queryPlan.getSqlTree(); + SqlTreePlan sqlTree = queryPlan.getSqlTree(); this.rootNode = sqlTree.getRootNode(); this.manyProperty = sqlTree.getManyProperty(); this.sql = queryPlan.getSql(); diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/query/CQueryBuilder.java b/ebean-core/src/main/java/io/ebeaninternal/server/query/CQueryBuilder.java index 41b752fe7..1d32ce994 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/query/CQueryBuilder.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/query/CQueryBuilder.java @@ -110,7 +110,7 @@ final class CQueryBuilder { sql = buildUpdateSql(request, rootTableAlias, predicates, sqlTree); } // cache the query plan - queryPlan = new CQueryPlan(request, sql, sqlTree, predicates.getLogWhereSql()); + queryPlan = new CQueryPlan(request, sql, sqlTree.plan(), predicates.getLogWhereSql()); request.putQueryPlan(queryPlan); return new CQueryUpdate(request, predicates, queryPlan); } @@ -197,7 +197,7 @@ final class CQueryBuilder { SqlTree sqlTree = createSqlTree(request, predicates); SqlLimitResponse s = buildSql(null, request, predicates, sqlTree); - queryPlan = new CQueryPlan(request, s.getSql(), sqlTree, predicates.getLogWhereSql()); + queryPlan = new CQueryPlan(request, s.getSql(), sqlTree.plan(), predicates.getLogWhereSql()); request.putQueryPlan(queryPlan); return new CQueryFetchSingleAttribute(request, predicates, queryPlan, query.isCountDistinct()); } @@ -288,7 +288,7 @@ final class CQueryBuilder { } } // cache the query plan - queryPlan = new CQueryPlan(request, sql, sqlTree, predicates.getLogWhereSql()); + queryPlan = new CQueryPlan(request, sql, sqlTree.plan(), predicates.getLogWhereSql()); request.putQueryPlan(queryPlan); return new CQueryRowCount(queryPlan, request, predicates); } @@ -343,7 +343,7 @@ final class CQueryBuilder { if (rawSql) { queryPlan = new CQueryPlanRawSql(request, res, sqlTree, predicates.getLogWhereSql()); } else { - queryPlan = new CQueryPlan(request, res, sqlTree, false, predicates.getLogWhereSql()); + queryPlan = new CQueryPlan(request, res, sqlTree.plan(), false, predicates.getLogWhereSql()); } BeanDescriptor desc = request.descriptor(); if (desc.isReadAuditing()) { diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/query/CQueryPlan.java b/ebean-core/src/main/java/io/ebeaninternal/server/query/CQueryPlan.java index bf84070f2..d07276920 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/query/CQueryPlan.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/query/CQueryPlan.java @@ -55,7 +55,7 @@ public class CQueryPlan implements SpiQueryPlan { private final String sql; private final String hash; private final String logWhereSql; - private final SqlTree sqlTree; + private final SqlTreePlan sqlTree; /** * Encrypted properties required additional binding. @@ -76,7 +76,7 @@ public class CQueryPlan implements SpiQueryPlan { /** * Create a query plan based on a OrmQueryRequest. */ - CQueryPlan(OrmQueryRequest request, SqlLimitResponse sqlRes, SqlTree sqlTree, boolean rawSql, String logWhereSql) { + CQueryPlan(OrmQueryRequest request, SqlLimitResponse sqlRes, SqlTreePlan sqlTree, boolean rawSql, String logWhereSql) { this.server = request.server(); this.dataTimeZone = server.dataTimeZone(); this.beanType = request.descriptor().type(); @@ -101,7 +101,7 @@ public class CQueryPlan implements SpiQueryPlan { /** * Create a query plan for a raw sql query. */ - CQueryPlan(OrmQueryRequest request, String sql, SqlTree sqlTree, String logWhereSql) { + CQueryPlan(OrmQueryRequest request, String sql, SqlTreePlan sqlTree, String logWhereSql) { this.server = request.server(); this.dataTimeZone = server.dataTimeZone(); this.beanType = request.descriptor().type(); @@ -249,7 +249,7 @@ public class CQueryPlan implements SpiQueryPlan { return rawSql ? planKey.getPartialKey() + "_" + hash : planKey.getPartialKey(); } - final SqlTree getSqlTree() { + final SqlTreePlan getSqlTree() { return sqlTree; } diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/query/CQueryPlanRawSql.java b/ebean-core/src/main/java/io/ebeaninternal/server/query/CQueryPlanRawSql.java index f35702ee1..935f730ec 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/query/CQueryPlanRawSql.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/query/CQueryPlanRawSql.java @@ -17,7 +17,7 @@ final class CQueryPlanRawSql extends CQueryPlan { private final int[] rsetIndexPositions; CQueryPlanRawSql(OrmQueryRequest request, SqlLimitResponse sqlRes, SqlTree sqlTree, String logWhereSql) { - super(request, sqlRes, sqlTree, true, logWhereSql); + super(request, sqlRes, sqlTree.plan(), true, logWhereSql); this.rsetIndexPositions = createIndexPositions(request, sqlTree); } diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/query/SqlTree.java b/ebean-core/src/main/java/io/ebeaninternal/server/query/SqlTree.java index 27e35c365..5b11be657 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/query/SqlTree.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/query/SqlTree.java @@ -56,6 +56,10 @@ final class SqlTree { this.noJoins = !includeJoins; } + SqlTreePlan plan() { + return new SqlTreePlan(rootNode.createLoad(), manyProperty, encryptedProps, dependentTables()); + } + /** * Return true if the query mandates SQL Distinct due to ToMany inclusion. */ @@ -123,10 +127,6 @@ final class SqlTree { return inheritanceWhereSql; } - SqlTreeRoot getRootNode() { - return (SqlTreeRoot)rootNode; - } - /** * Return the property that is associated with the many. There can only be one * per SqlSelect. This can be null. @@ -135,10 +135,6 @@ final class SqlTree { return manyProperty; } - STreeProperty[] getEncryptedProps() { - return encryptedProps; - } - /** * Return true if the query has a many join. */ @@ -153,7 +149,7 @@ final class SqlTree { /** * Return the tables that are joined in this query. */ - Set dependentTables() { + private Set dependentTables() { Set tables = new LinkedHashSet<>(); rootNode.dependentTables(tables); return tables; diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/query/SqlTreeBuilder.java b/ebean-core/src/main/java/io/ebeaninternal/server/query/SqlTreeBuilder.java index 09d8959ee..0ee056430 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/query/SqlTreeBuilder.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/query/SqlTreeBuilder.java @@ -35,7 +35,6 @@ public final class SqlTreeBuilder { private final CQueryPredicates predicates; private final boolean subQuery; private final boolean distinctOnPlatform; - private final boolean distinctNoLobs; /** * Property if resultSet contains master and detail rows. */ @@ -66,7 +65,6 @@ public final class SqlTreeBuilder { this.query = null; this.subQuery = false; this.distinctOnPlatform = false; - this.distinctNoLobs = false; this.queryDetail = queryDetail; this.predicates = predicates; this.temporalMode = SpiQuery.TemporalMode.CURRENT; @@ -99,7 +97,6 @@ public final class SqlTreeBuilder { this.predicates = predicates; this.alias = new SqlTreeAlias(request.baseTableAlias(), temporalMode); this.distinctOnPlatform = builder.isPlatformDistinctOn(); - this.distinctNoLobs = builder.isPlatformDistinctNoLobs(); String fromForUpdate = builder.fromForUpdate(query); CQueryHistorySupport historySupport = builder.getHistorySupport(query); CQueryDraftSupport draftSupport = builder.getDraftSupport(query); @@ -269,9 +266,6 @@ public final class SqlTreeBuilder { if (joinList != null) { joinList.add(selectNode); } - if (sqlDistinct && distinctNoLobs) { - selectNode.unselectLobs(); - } return selectNode; } diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/query/SqlTreeLoad.java b/ebean-core/src/main/java/io/ebeaninternal/server/query/SqlTreeLoad.java new file mode 100644 index 000000000..c5ac129b1 --- /dev/null +++ b/ebean-core/src/main/java/io/ebeaninternal/server/query/SqlTreeLoad.java @@ -0,0 +1,28 @@ +package io.ebeaninternal.server.query; + +import io.ebean.bean.EntityBean; +import io.ebean.core.type.ScalarDataReader; +import io.ebeaninternal.server.deploy.DbReadContext; + +import java.sql.SQLException; + +/** + * Tree node that loads an entity bean type. + */ +interface SqlTreeLoad { + + /** + * Load the appropriate information from the SqlSelectReader. + *

+ * At a high level this actually controls the reading of the data from the + * jdbc resultSet and putting it into the bean etc. + *

+ */ + EntityBean load(DbReadContext ctx, EntityBean localBean, EntityBean contextBean) throws SQLException; + + /** + * Return the reader for the single attribute query. + */ + ScalarDataReader getSingleAttributeReader(); + +} diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/query/SqlTreeLoadBean.java b/ebean-core/src/main/java/io/ebeaninternal/server/query/SqlTreeLoadBean.java new file mode 100644 index 000000000..516a00537 --- /dev/null +++ b/ebean-core/src/main/java/io/ebeaninternal/server/query/SqlTreeLoadBean.java @@ -0,0 +1,373 @@ +package io.ebeaninternal.server.query; + +import io.ebean.bean.BeanCollection; +import io.ebean.bean.EntityBean; +import io.ebean.bean.EntityBeanIntercept; +import io.ebean.bean.PersistenceContext; +import io.ebean.core.type.ScalarDataReader; +import io.ebeaninternal.api.SpiQuery; +import io.ebeaninternal.api.SpiQuery.Mode; +import io.ebeaninternal.server.deploy.DbReadContext; +import io.ebeaninternal.server.deploy.InheritInfo; +import io.ebeaninternal.server.deploy.id.IdBinder; + +import java.sql.SQLException; +import java.util.Map; + +/** + * Normal bean included in the query. + */ +class SqlTreeLoadBean implements SqlTreeLoad { + + final STreeType desc; + final IdBinder idBinder; + final SqlTreeLoad[] children; + private final boolean partialObject; + private final STreeProperty[] properties; + private final STreePropertyAssoc nodeBeanProp; + final boolean readId; + private final boolean readIdNormal; + private final boolean disableLazyLoad; + private final InheritInfo inheritInfo; + final String prefix; + private final Map pathMap; + final STreePropertyAssocMany lazyLoadParent; + private final SpiQuery.TemporalMode temporalMode; + private final boolean temporalVersions; + final IdBinder lazyLoadParentIdBinder; + + SqlTreeLoadBean(SqlTreeNodeBean node) { + this.lazyLoadParent = node.lazyLoadParent; + this.lazyLoadParentIdBinder = node.lazyLoadParentIdBinder; + this.prefix = node.prefix; + this.desc = node.desc; + this.inheritInfo = desc.inheritInfo(); + this.idBinder = desc.idBinder(); + this.temporalMode = node.temporalMode; + this.temporalVersions = node.temporalVersions; + this.nodeBeanProp = node.nodeBeanProp; + this.readId = node.readId;//!aggregationRoot && withId && desc.hasId(); + this.readIdNormal = readId && !temporalVersions; + this.disableLazyLoad = node.disableLazyLoad;// disableLazyLoad || !readIdNormal || desc.isRawSqlBased(); + this.partialObject = node.partialObject;//props.isPartialObject(); + this.properties = node.properties;//props.getProps(); + this.pathMap = node.pathMap;//createPathMap(prefix, desc); + this.children = node.createLoadChildren(); + } + + boolean isRoot() { + return false; + } + + @Override + public final ScalarDataReader getSingleAttributeReader() { + if (properties == null || properties.length == 0) { + // if we have no property ask first children (in a distinct select with join) + if (children.length == 0) { + // expected to be a findIds query + return desc.idBinder().getBeanProperty(); + } + return children[0].getSingleAttributeReader(); + } + if (properties[0] instanceof STreePropertyAssocOne) { + STreePropertyAssocOne assocOne = (STreePropertyAssocOne)properties[0]; + if (assocOne.isAssocId()) { + return assocOne.idReader(); + } + } + return properties[0]; + } + + /** + * Load that takes into account inheritance. + */ + private final class LoadInherit extends Load { + + private LoadInherit(DbReadContext ctx, EntityBean parentBean) { + super(ctx, parentBean); + } + + @Override + void initBeanType() throws SQLException { + InheritInfo localInfo = readId ? inheritInfo.readType(ctx) : desc.inheritInfo(); + if (localInfo == null) { + // the bean must be null + localIdBinder = idBinder; + localDesc = desc; + } else { + localBean = localInfo.createEntityBean(); + localType = localInfo.getType(); + localIdBinder = localInfo.getIdBinder(); + localDesc = localInfo.desc(); + } + } + + @Override + void loadProperties() { + // take account of inheritance + for (STreeProperty property : properties) { + localDesc.inheritanceLoad(sqlBeanLoad, property, ctx); + } + } + } + + /** + * Load a bean instance. + */ + class Load { + + final DbReadContext ctx; + final EntityBean parentBean; + + Object lazyLoadParentId; + Class localType; + STreeType localDesc; + IdBinder localIdBinder; + EntityBean localBean; + + Mode queryMode; + PersistenceContext persistenceContext; + Object id; + EntityBean contextBean; + SqlBeanLoad sqlBeanLoad; + boolean lazyLoadMany; + + private Load(DbReadContext ctx, EntityBean parentBean) { + this.ctx = ctx; + this.parentBean = parentBean; + } + + private void initLazyParent() throws SQLException { + if (lazyLoadParentIdBinder != null) { + lazyLoadParentId = lazyLoadParentIdBinder.read(ctx); + } + } + + void initBeanType() throws SQLException { + localDesc = desc; + localBean = desc.createEntityBean(); + localIdBinder = idBinder; + } + + private void initPersistenceContext() { + queryMode = ctx.getQueryMode(); + persistenceContext = (!readIdNormal) ? null : ctx.getPersistenceContext(); + } + + private void readId() throws SQLException { + if (readId) { + id = localIdBinder.readSet(ctx, localBean); + if (id == null) { + readIdNullBean(); + } else if (!temporalVersions) { + readIdBean(); + } + } + } + + private void readIdBean() { + // check the PersistenceContext to see if the bean already exists + contextBean = (EntityBean) localDesc.contextPutIfAbsent(persistenceContext, id, localBean); + if (contextBean == null) { + // bean just added to the persistenceContext + contextBean = localBean; + } else { + // bean already exists in persistenceContext + + if (queryMode.isLoadContextBean()) { + // if explicitly set loadContextBean to true, then reload + localBean = contextBean; + } else if (!contextBean._ebean_getIntercept().isFullyLoadedBean()) { + // reload if contextBean is partial object + localBean = contextBean; + // and switch to lazyLoad query mode in order not to overwrite + // existing properties in SqlBeanLoad::load + queryMode = Mode.LAZYLOAD_BEAN; + } else { + // ignore the DB data... + localBean = null; + } + } + } + + private void readIdNullBean() { + // bean must be null... + localBean = null; + // ... but there may exist as reference bean in parent which has to be marked as deleted. + if (parentBean != null && nodeBeanProp instanceof STreePropertyAssocOne) { + contextBean = ((STreePropertyAssocOne)nodeBeanProp).getValueAsEntityBean(parentBean); + if (contextBean != null) { + desc.markAsDeleted(contextBean); + } + } + } + + private void initSqlLoadBean() { + ctx.setCurrentPrefix(prefix, pathMap); + ctx.propagateState(localBean); + sqlBeanLoad = new SqlBeanLoad(ctx, localType, localBean, queryMode); + } + + void loadProperties() { + for (STreeProperty property : properties) { + property.load(sqlBeanLoad); + } + } + + private void loadChildren() throws SQLException { + if (localBean == null && queryMode == Mode.LAZYLOAD_MANY) { + // batch lazy load many into existing contextBean + localBean = contextBean; + lazyLoadMany = true; + } + for (SqlTreeLoad child : children) { + child.load(ctx, localBean, contextBean); + } + } + + private boolean isLazyLoadManyRoot() { + return queryMode == Mode.LAZYLOAD_MANY && isRoot(); + } + + private EntityBean getContextBean() { + return contextBean; + } + + private void postLoad() { + if (!lazyLoadMany && localBean != null) { + ctx.setCurrentPrefix(prefix, pathMap); + if (readIdNormal) { + createListProxies(); + } + if (temporalMode == SpiQuery.TemporalMode.DRAFT) { + localDesc.setDraft(localBean); + } + localDesc.postLoad(localBean); + + EntityBeanIntercept ebi = localBean._ebean_getIntercept(); + ebi.setPersistenceContext(persistenceContext); + if (Mode.LAZYLOAD_BEAN == queryMode) { + // Lazy Load does not reset the dirty state + ebi.setLoadedLazy(); + } else if (readId) { + // normal bean loading + ebi.setLoaded(); + } + + if (disableLazyLoad) { + // bean does not have an Id or is SqlSelect based + ebi.setDisableLazyLoad(true); + } else if (partialObject) { + if (readId) { + // register for lazy loading + ctx.register(null, ebi); + } + } else { + ebi.setFullyLoadedBean(true); + } + + if (ctx.isAutoTuneProfiling() && !disableLazyLoad) { + // collect autoTune profiling for this bean... + ctx.profileBean(ebi, prefix); + } + } + } + + /** + * Create lazy loading proxies for the Many's except for the one that is + * included in the actual query. + */ + private void createListProxies() { + STreePropertyAssocMany fetchedMany = ctx.getManyProperty(); + boolean forceNewReference = queryMode == Mode.REFRESH_BEAN; + for (STreePropertyAssocMany many : localDesc.propsMany()) { + if (many != fetchedMany) { + // create a proxy for the many (deferred fetching) + BeanCollection ref = many.createReference(localBean, forceNewReference); + if (ref != null) { + if (disableLazyLoad) { + ref.setDisableLazyLoad(true); + } + if (!ref.isRegisteredWithLoadContext()) { + ctx.register(many.asMany(), ref); + } + } + } + } + } + + private void setBeanToParent() { + if (parentBean != null) { + // set this back to the parentBean + nodeBeanProp.setValue(parentBean, contextBean); + } + } + + private EntityBean complete() { + if (!readIdNormal) { + // a bean with no Id (never found in context) + if (lazyLoadParentId != null) { + ctx.setLazyLoadedChildBean(localBean, lazyLoadParentId); + } + return localBean; + } else { + if (lazyLoadParentId != null) { + ctx.setLazyLoadedChildBean(contextBean, lazyLoadParentId); + } + return contextBean; + } + } + + private void initialise() throws SQLException { + initLazyParent(); + initBeanType(); + initPersistenceContext(); + readId(); + initSqlLoadBean(); + loadProperties(); + loadChildren(); + } + + /** + * Perform the load returning the loaded bean. + */ + final EntityBean perform() throws SQLException { + initialise(); + if (isLazyLoadManyRoot()) { + return getContextBean(); + } + postLoad(); + setBeanToParent(); + return complete(); + } + + /** + * Return true if this bean was already in the context. If already in the + * context we need to check if it is already contained in the collection. + */ + final boolean isContextBean() { + return localBean == null; + } + } + + /** + * read the properties from the resultSet. + */ + @Override + public EntityBean load(DbReadContext ctx, EntityBean parentBean, EntityBean contextParent) throws SQLException { + return createLoad(ctx, parentBean).perform(); + } + + /** + * Create the loader with or without inheritance. + */ + final Load createLoad(DbReadContext ctx, EntityBean parentBean) { + return (inheritInfo != null) ? new LoadInherit(ctx, parentBean) : new Load(ctx, parentBean); + } + + @Override + public String toString() { + return "SqlTreeLoadBean: " + desc; + } + +} diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/query/SqlTreeLoadManyRoot.java b/ebean-core/src/main/java/io/ebeaninternal/server/query/SqlTreeLoadManyRoot.java new file mode 100644 index 000000000..3622e770e --- /dev/null +++ b/ebean-core/src/main/java/io/ebeaninternal/server/query/SqlTreeLoadManyRoot.java @@ -0,0 +1,30 @@ +package io.ebeaninternal.server.query; + +import io.ebean.bean.EntityBean; +import io.ebeaninternal.server.deploy.DbReadContext; + +import java.sql.SQLException; + +final class SqlTreeLoadManyRoot extends SqlTreeLoadBean { + + private final STreePropertyAssocMany manyProp; + + SqlTreeLoadManyRoot(SqlTreeNodeManyRoot node) { + super(node); + this.manyProp = node.manyProp; + } + + @Override + public EntityBean load(DbReadContext cquery, EntityBean parentBean, EntityBean contextParent) throws SQLException { + // pass in null for parentBean because added to a collection rather than set to the parentBean + Load load = createLoad(cquery, null); + EntityBean detailBean = load.perform(); + if (contextParent != null) { + // Add to the collection and initialise collection if needed + // A null detailBean may initialise an empty collection + // Check for bean existing in collection based on load.isContextBean() + manyProp.addBeanToCollectionWithCreate(contextParent, detailBean, load.isContextBean()); + } + return detailBean; + } +} diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/query/SqlTreeLoadRoot.java b/ebean-core/src/main/java/io/ebeaninternal/server/query/SqlTreeLoadRoot.java new file mode 100644 index 000000000..d1792ecbe --- /dev/null +++ b/ebean-core/src/main/java/io/ebeaninternal/server/query/SqlTreeLoadRoot.java @@ -0,0 +1,43 @@ +package io.ebeaninternal.server.query; + +import io.ebean.Version; +import io.ebean.bean.EntityBean; +import io.ebeaninternal.server.deploy.DbReadContext; + +import java.sql.SQLException; +import java.sql.Timestamp; + +/** + * Represents the root node of the Sql Tree. + */ +final class SqlTreeLoadRoot extends SqlTreeLoadBean implements SqlTreeRoot { + + SqlTreeLoadRoot(SqlTreeNodeBean node) { + super(node); + } + + @Override + protected boolean isRoot() { + return true; + } + + @Override + public EntityBean load(DbReadContext ctx) throws SQLException { + return load(ctx, null, null); + } + + /** + * Read the version bean. + */ + @Override + @SuppressWarnings("unchecked") + public Version loadVersion(DbReadContext ctx) throws SQLException { + // read the sys period lower and upper bounds + // these are always the first 2 columns in the resultSet + Timestamp start = ctx.getDataReader().getTimestamp(); + Timestamp end = ctx.getDataReader().getTimestamp(); + T bean = (T) load(ctx, null, null); + return new Version<>(bean, start, end); + } + +} diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/query/SqlTreeNode.java b/ebean-core/src/main/java/io/ebeaninternal/server/query/SqlTreeNode.java index 7930e968a..54632d9ca 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/query/SqlTreeNode.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/query/SqlTreeNode.java @@ -1,13 +1,8 @@ package io.ebeaninternal.server.query; -import io.ebean.Version; -import io.ebean.bean.EntityBean; -import io.ebean.core.type.ScalarDataReader; import io.ebeaninternal.api.SpiQuery; -import io.ebeaninternal.server.deploy.DbReadContext; import io.ebeaninternal.server.deploy.DbSqlContext; -import java.sql.SQLException; import java.util.List; import java.util.Set; @@ -64,25 +59,11 @@ interface SqlTreeNode { */ void addSoftDeletePredicate(SpiQuery query); - /** - * Load the appropriate information from the SqlSelectReader. - *

- * At a high level this actually controls the reading of the data from the - * jdbc resultSet and putting it into the bean etc. - *

- */ - EntityBean load(DbReadContext ctx, EntityBean localBean, EntityBean contextBean) throws SQLException; - /** * Return true if the query has a many join. */ boolean hasMany(); - /** - * Return the reader for the single attribute query. - */ - ScalarDataReader getSingleAttributeReader(); - /** * Return true if the query is known to only have a single property selected. */ @@ -94,7 +75,7 @@ interface SqlTreeNode { void dependentTables(Set tables); /** - * Unselect lobs (for distinct queries on DB2 and Oracle). + * Create the loader for this node. */ - default void unselectLobs() {}; + SqlTreeLoad createLoad(); } diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/query/SqlTreeNodeBean.java b/ebean-core/src/main/java/io/ebeaninternal/server/query/SqlTreeNodeBean.java index 8198ae816..4836fa3fd 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/query/SqlTreeNodeBean.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/query/SqlTreeNodeBean.java @@ -1,33 +1,20 @@ package io.ebeaninternal.server.query; -import io.ebean.bean.BeanCollection; -import io.ebean.bean.EntityBean; -import io.ebean.bean.EntityBeanIntercept; -import io.ebean.bean.PersistenceContext; -import io.ebean.core.type.DataReader; -import io.ebean.core.type.ScalarDataReader; -import io.ebean.core.type.ScalarType; import io.ebean.util.SplitName; import io.ebeaninternal.api.SpiQuery; -import io.ebeaninternal.api.SpiQuery.Mode; -import io.ebeaninternal.server.deploy.DbReadContext; import io.ebeaninternal.server.deploy.DbSqlContext; import io.ebeaninternal.server.deploy.InheritInfo; import io.ebeaninternal.server.deploy.TableJoin; import io.ebeaninternal.server.deploy.id.IdBinder; -import java.sql.SQLException; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; +import java.util.*; /** * Normal bean included in the query. */ class SqlTreeNodeBean implements SqlTreeNode { + private static final SqlTreeLoad[] NO_LOAD_CHILDREN = new SqlTreeLoad[0]; private static final SqlTreeNode[] NO_CHILDREN = new SqlTreeNode[0]; final STreeType desc; @@ -39,33 +26,33 @@ class SqlTreeNodeBean implements SqlTreeNode { /** * Set to true if this is a partial object fetch. */ - private boolean partialObject; - private STreeProperty[] properties; + final boolean partialObject; + final STreeProperty[] properties; /** * Extra where clause added by Where annotation on associated many. */ - private final String extraWhere; - private final STreePropertyAssoc nodeBeanProp; + final String extraWhere; + final STreePropertyAssoc nodeBeanProp; /** * False if report bean and has no id property. */ final boolean readId; - private final boolean readIdNormal; - private final boolean disableLazyLoad; - private final InheritInfo inheritInfo; + final boolean readIdNormal; + final boolean disableLazyLoad; + final InheritInfo inheritInfo; final String prefix; - private final Map pathMap; + final Map pathMap; final STreePropertyAssocMany lazyLoadParent; - private final SpiQuery.TemporalMode temporalMode; - private final boolean temporalVersions; - private final IdBinder lazyLoadParentIdBinder; + final SpiQuery.TemporalMode temporalMode; + final boolean temporalVersions; + final IdBinder lazyLoadParentIdBinder; String baseTableAlias; /** * Table alias set if this bean node includes a join to a intersection * table and that table has history support. */ - private boolean intersectionAsOfTableAlias; - private final boolean aggregation; + boolean intersectionAsOfTableAlias; + final boolean aggregation; /** * Construct for leaf node. @@ -111,8 +98,23 @@ class SqlTreeNodeBean implements SqlTreeNode { pathMap = createPathMap(prefix, desc); } - boolean isRoot() { - return false; + @Override + public SqlTreeLoad createLoad() { + return new SqlTreeLoadBean(this); + } + + protected SqlTreeLoad[] createLoadChildren() { + if (children.length == 0) { + return NO_LOAD_CHILDREN; + } + List loadChildren = new ArrayList<>(children.length); + for (SqlTreeNode child : children) { + SqlTreeLoad load = child.createLoad(); + if (load != null) { + loadChildren.add(load); + } + } + return loadChildren.toArray(new SqlTreeLoad[0]); } @Override @@ -120,25 +122,6 @@ class SqlTreeNodeBean implements SqlTreeNode { return properties != null && properties.length == 1 && children.length == 0; } - @Override - public final ScalarDataReader getSingleAttributeReader() { - if (properties == null || properties.length == 0) { - // if we have no property ask first children (in a distinct select with join) - if (children.length == 0) { - // expected to be a findIds query - return desc.idBinder().getBeanProperty(); - } - return children[0].getSingleAttributeReader(); - } - if (properties[0] instanceof STreePropertyAssocOne) { - STreePropertyAssocOne assocOne = (STreePropertyAssocOne)properties[0]; - if (assocOne.isAssocId()) { - return assocOne.idReader(); - } - } - return properties[0]; - } - private Map createPathMap(String prefix, STreeType desc) { HashMap m = new HashMap<>(); for (STreePropertyAssocMany many : desc.propsMany()) { @@ -176,293 +159,6 @@ class SqlTreeNodeBean implements SqlTreeNode { } } - /** - * Load that takes into account inheritance. - */ - private final class LoadInherit extends Load { - - private LoadInherit(DbReadContext ctx, EntityBean parentBean) { - super(ctx, parentBean); - } - - @Override - void initBeanType() throws SQLException { - InheritInfo localInfo = readId ? inheritInfo.readType(ctx) : desc.inheritInfo(); - if (localInfo == null) { - // the bean must be null - localIdBinder = idBinder; - localDesc = desc; - } else { - localBean = localInfo.createEntityBean(); - localType = localInfo.getType(); - localIdBinder = localInfo.getIdBinder(); - localDesc = localInfo.desc(); - } - } - - @Override - void loadProperties() { - // take account of inheritance - for (STreeProperty property : properties) { - localDesc.inheritanceLoad(sqlBeanLoad, property, ctx); - } - } - } - - /** - * Load a bean instance. - */ - class Load { - - final DbReadContext ctx; - final EntityBean parentBean; - - Object lazyLoadParentId; - Class localType; - STreeType localDesc; - IdBinder localIdBinder; - EntityBean localBean; - - Mode queryMode; - PersistenceContext persistenceContext; - Object id; - EntityBean contextBean; - SqlBeanLoad sqlBeanLoad; - boolean lazyLoadMany; - - private Load(DbReadContext ctx, EntityBean parentBean) { - this.ctx = ctx; - this.parentBean = parentBean; - } - - private void initLazyParent() throws SQLException { - if (lazyLoadParentIdBinder != null) { - lazyLoadParentId = lazyLoadParentIdBinder.read(ctx); - } - } - - void initBeanType() throws SQLException { - localDesc = desc; - localBean = desc.createEntityBean(); - localIdBinder = idBinder; - } - - private void initPersistenceContext() { - queryMode = ctx.getQueryMode(); - persistenceContext = (!readIdNormal) ? null : ctx.getPersistenceContext(); - } - - private void readId() throws SQLException { - if (readId) { - id = localIdBinder.readSet(ctx, localBean); - if (id == null) { - readIdNullBean(); - } else if (!temporalVersions) { - readIdBean(); - } - } - } - - private void readIdBean() { - // check the PersistenceContext to see if the bean already exists - contextBean = (EntityBean) localDesc.contextPutIfAbsent(persistenceContext, id, localBean); - if (contextBean == null) { - // bean just added to the persistenceContext - contextBean = localBean; - } else { - // bean already exists in persistenceContext - - if (queryMode.isLoadContextBean()) { - // if explicitly set loadContextBean to true, then reload - localBean = contextBean; - } else if (!contextBean._ebean_getIntercept().isFullyLoadedBean()) { - // reload if contextBean is partial object - localBean = contextBean; - // and switch to lazyLoad query mode in order not to overwrite - // existing properties in SqlBeanLoad::load - queryMode = Mode.LAZYLOAD_BEAN; - } else { - // ignore the DB data... - localBean = null; - } - } - } - - private void readIdNullBean() { - // bean must be null... - localBean = null; - // ... but there may exist as reference bean in parent which has to be marked as deleted. - if (parentBean != null && nodeBeanProp instanceof STreePropertyAssocOne) { - contextBean = ((STreePropertyAssocOne)nodeBeanProp).getValueAsEntityBean(parentBean); - if (contextBean != null) { - desc.markAsDeleted(contextBean); - } - } - } - - private void initSqlLoadBean() { - ctx.setCurrentPrefix(prefix, pathMap); - ctx.propagateState(localBean); - sqlBeanLoad = new SqlBeanLoad(ctx, localType, localBean, queryMode); - } - - void loadProperties() { - for (STreeProperty property : properties) { - property.load(sqlBeanLoad); - } - } - - private void loadChildren() throws SQLException { - if (localBean == null && queryMode == Mode.LAZYLOAD_MANY) { - // batch lazy load many into existing contextBean - localBean = contextBean; - lazyLoadMany = true; - } - for (SqlTreeNode child : children) { - child.load(ctx, localBean, contextBean); - } - } - - private boolean isLazyLoadManyRoot() { - return queryMode == Mode.LAZYLOAD_MANY && isRoot(); - } - - private EntityBean getContextBean() { - return contextBean; - } - - private void postLoad() { - if (!lazyLoadMany && localBean != null) { - ctx.setCurrentPrefix(prefix, pathMap); - if (readIdNormal) { - createListProxies(); - } - if (temporalMode == SpiQuery.TemporalMode.DRAFT) { - localDesc.setDraft(localBean); - } - localDesc.postLoad(localBean); - - EntityBeanIntercept ebi = localBean._ebean_getIntercept(); - ebi.setPersistenceContext(persistenceContext); - if (Mode.LAZYLOAD_BEAN == queryMode) { - // Lazy Load does not reset the dirty state - ebi.setLoadedLazy(); - } else if (readId) { - // normal bean loading - ebi.setLoaded(); - } - - if (disableLazyLoad) { - // bean does not have an Id or is SqlSelect based - ebi.setDisableLazyLoad(true); - } else if (partialObject) { - if (readId) { - // register for lazy loading - ctx.register(null, ebi); - } - } else { - ebi.setFullyLoadedBean(true); - } - - if (ctx.isAutoTuneProfiling() && !disableLazyLoad) { - // collect autoTune profiling for this bean... - ctx.profileBean(ebi, prefix); - } - } - } - - /** - * Create lazy loading proxies for the Many's except for the one that is - * included in the actual query. - */ - private void createListProxies() { - STreePropertyAssocMany fetchedMany = ctx.getManyProperty(); - boolean forceNewReference = queryMode == Mode.REFRESH_BEAN; - for (STreePropertyAssocMany many : localDesc.propsMany()) { - if (many != fetchedMany) { - // create a proxy for the many (deferred fetching) - BeanCollection ref = many.createReference(localBean, forceNewReference); - if (ref != null) { - if (disableLazyLoad) { - ref.setDisableLazyLoad(true); - } - if (!ref.isRegisteredWithLoadContext()) { - ctx.register(many.asMany(), ref); - } - } - } - } - } - - private void setBeanToParent() { - if (parentBean != null) { - // set this back to the parentBean - nodeBeanProp.setValue(parentBean, contextBean); - } - } - - private EntityBean complete() { - if (!readIdNormal) { - // a bean with no Id (never found in context) - if (lazyLoadParentId != null) { - ctx.setLazyLoadedChildBean(localBean, lazyLoadParentId); - } - return localBean; - } else { - if (lazyLoadParentId != null) { - ctx.setLazyLoadedChildBean(contextBean, lazyLoadParentId); - } - return contextBean; - } - } - - private void initialise() throws SQLException { - initLazyParent(); - initBeanType(); - initPersistenceContext(); - readId(); - initSqlLoadBean(); - loadProperties(); - loadChildren(); - } - - /** - * Perform the load returning the loaded bean. - */ - final EntityBean perform() throws SQLException { - initialise(); - if (isLazyLoadManyRoot()) { - return getContextBean(); - } - postLoad(); - setBeanToParent(); - return complete(); - } - - /** - * Return true if this bean was already in the context. If already in the - * context we need to check if it is already contained in the collection. - */ - final boolean isContextBean() { - return localBean == null; - } - } - - /** - * read the properties from the resultSet. - */ - @Override - public EntityBean load(DbReadContext ctx, EntityBean parentBean, EntityBean contextParent) throws SQLException { - return createLoad(ctx, parentBean).perform(); - } - - /** - * Create the loader with or without inheritance. - */ - final Load createLoad(DbReadContext ctx, EntityBean parentBean) { - return (inheritInfo != null) ? new LoadInherit(ctx, parentBean) : new Load(ctx, parentBean); - } - @Override public final void appendGroupBy(DbSqlContext ctx, boolean subQuery) { ctx.pushJoin(prefix); @@ -703,32 +399,4 @@ class SqlTreeNodeBean implements SqlTreeNode { } return false; } - - @Override - public void unselectLobs() { - if (children != null) { - for (SqlTreeNode child : children) { - child.unselectLobs(); - } - } - if (hasLob()) { - List lst = new ArrayList<>(); - for (STreeProperty prop : properties) { - if (!prop.isDbLob()) { - lst.add(prop); - } - } - properties = lst.toArray(new STreeProperty[0]); - partialObject = true; - } - } - - private boolean hasLob() { - for (STreeProperty prop : properties) { - if (prop.isDbLob()) { - return true; - } - } - return false; - } } diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/query/SqlTreeNodeExtraJoin.java b/ebean-core/src/main/java/io/ebeaninternal/server/query/SqlTreeNodeExtraJoin.java index e7ca5d9ab..42d2395b5 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/query/SqlTreeNodeExtraJoin.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/query/SqlTreeNodeExtraJoin.java @@ -38,6 +38,11 @@ final class SqlTreeNodeExtraJoin implements SqlTreeNode { this.manyJoin = assocBeanProperty instanceof STreePropertyAssocMany; } + @Override + public SqlTreeLoad createLoad() { + return null; + } + @Override public boolean isSingleProperty() { return false; @@ -73,11 +78,6 @@ final class SqlTreeNodeExtraJoin implements SqlTreeNode { // nothing to do here } - @Override - public ScalarType getSingleAttributeReader() { - throw new IllegalStateException("No expected"); - } - /** * Return true if the extra join is a many join. *

@@ -184,14 +184,6 @@ final class SqlTreeNodeExtraJoin implements SqlTreeNode { public void appendWhere(DbSqlContext ctx) { } - /** - * Does nothing. - */ - @Override - public EntityBean load(DbReadContext ctx, EntityBean localBean, EntityBean parentBean) { - return null; - } - @Override public boolean hasMany() { return manyJoin; diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/query/SqlTreeNodeFormulaWhereJoin.java b/ebean-core/src/main/java/io/ebeaninternal/server/query/SqlTreeNodeFormulaWhereJoin.java index 06a71133a..502c28a6d 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/query/SqlTreeNodeFormulaWhereJoin.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/query/SqlTreeNodeFormulaWhereJoin.java @@ -33,13 +33,13 @@ final class SqlTreeNodeFormulaWhereJoin implements SqlTreeNode { } @Override - public boolean isSingleProperty() { - return true; + public SqlTreeLoad createLoad() { + return null; } @Override - public ScalarType getSingleAttributeReader() { - throw new IllegalStateException("No expected"); + public boolean isSingleProperty() { + return true; } @Override @@ -98,12 +98,6 @@ final class SqlTreeNodeFormulaWhereJoin implements SqlTreeNode { // nothing to do here } - @Override - public EntityBean load(DbReadContext ctx, EntityBean localBean, EntityBean parentBean) { - // nothing to do here - return null; - } - @Override public boolean hasMany() { return true; diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/query/SqlTreeNodeManyRoot.java b/ebean-core/src/main/java/io/ebeaninternal/server/query/SqlTreeNodeManyRoot.java index c030e96bc..a8b323533 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/query/SqlTreeNodeManyRoot.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/query/SqlTreeNodeManyRoot.java @@ -1,16 +1,13 @@ package io.ebeaninternal.server.query; -import io.ebean.bean.EntityBean; import io.ebeaninternal.api.SpiQuery; -import io.ebeaninternal.server.deploy.DbReadContext; import io.ebeaninternal.server.deploy.DbSqlContext; -import java.sql.SQLException; import java.util.List; final class SqlTreeNodeManyRoot extends SqlTreeNodeBean { - private final STreePropertyAssocMany manyProp; + final STreePropertyAssocMany manyProp; SqlTreeNodeManyRoot(String prefix, STreePropertyAssocMany prop, SqlTreeProperties props, List myList, boolean withId, SpiQuery.TemporalMode temporalMode, boolean disableLazyLoad) { @@ -19,22 +16,13 @@ final class SqlTreeNodeManyRoot extends SqlTreeNodeBean { } @Override - public boolean hasMany() { - return true; + public SqlTreeLoad createLoad() { + return new SqlTreeLoadManyRoot(this); } @Override - public EntityBean load(DbReadContext cquery, EntityBean parentBean, EntityBean contextParent) throws SQLException { - // pass in null for parentBean because added to a collection rather than set to the parentBean - SqlTreeNodeBean.Load load = createLoad(cquery, null); - EntityBean detailBean = load.perform(); - if (contextParent != null) { - // Add to the collection and initialise collection if needed - // A null detailBean may initialise an empty collection - // Check for bean existing in collection based on load.isContextBean() - manyProp.addBeanToCollectionWithCreate(contextParent, detailBean, load.isContextBean()); - } - return detailBean; + public boolean hasMany() { + return true; } /** diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/query/SqlTreeNodeManyWhereJoin.java b/ebean-core/src/main/java/io/ebeaninternal/server/query/SqlTreeNodeManyWhereJoin.java index 73c7a18b2..cb2ba54e7 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/query/SqlTreeNodeManyWhereJoin.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/query/SqlTreeNodeManyWhereJoin.java @@ -1,10 +1,7 @@ package io.ebeaninternal.server.query; -import io.ebean.bean.EntityBean; -import io.ebean.core.type.ScalarType; import io.ebean.util.SplitName; import io.ebeaninternal.api.SpiQuery; -import io.ebeaninternal.server.deploy.DbReadContext; import io.ebeaninternal.server.deploy.DbSqlContext; import io.ebeaninternal.server.deploy.TableJoin; @@ -37,13 +34,13 @@ final class SqlTreeNodeManyWhereJoin implements SqlTreeNode { } @Override - public boolean isSingleProperty() { - return false; + public SqlTreeLoad createLoad() { + return null; } @Override - public ScalarType getSingleAttributeReader() { - throw new IllegalStateException("No expected"); + public boolean isSingleProperty() { + return false; } @Override @@ -132,12 +129,6 @@ final class SqlTreeNodeManyWhereJoin implements SqlTreeNode { // nothing to do here } - @Override - public EntityBean load(DbReadContext ctx, EntityBean localBean, EntityBean parentBean) { - // nothing to do here - return null; - } - @Override public boolean hasMany() { return true; diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/query/SqlTreeNodeRoot.java b/ebean-core/src/main/java/io/ebeaninternal/server/query/SqlTreeNodeRoot.java index a58c51cc5..c25024624 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/query/SqlTreeNodeRoot.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/query/SqlTreeNodeRoot.java @@ -1,21 +1,16 @@ package io.ebeaninternal.server.query; -import io.ebean.Version; -import io.ebean.bean.EntityBean; import io.ebeaninternal.api.SpiQuery; -import io.ebeaninternal.server.deploy.DbReadContext; import io.ebeaninternal.server.deploy.DbSqlContext; import io.ebeaninternal.server.deploy.TableJoin; -import java.sql.SQLException; -import java.sql.Timestamp; import java.util.List; import java.util.Set; /** * Represents the root node of the Sql Tree. */ -final class SqlTreeNodeRoot extends SqlTreeNodeBean implements SqlTreeRoot { +final class SqlTreeNodeRoot extends SqlTreeNodeBean { private final TableJoin includeJoin; private final boolean sqlDistinct; @@ -34,27 +29,8 @@ final class SqlTreeNodeRoot extends SqlTreeNodeBean implements SqlTreeRoot { } @Override - protected boolean isRoot() { - return true; - } - - @Override - public EntityBean load(DbReadContext ctx) throws SQLException { - return load(ctx, null, null); - } - - /** - * Read the version bean. - */ - @Override - @SuppressWarnings("unchecked") - public Version loadVersion(DbReadContext ctx) throws SQLException { - // read the sys period lower and upper bounds - // these are always the first 2 columns in the resultSet - Timestamp start = ctx.getDataReader().getTimestamp(); - Timestamp end = ctx.getDataReader().getTimestamp(); - T bean = (T) load(ctx, null, null); - return new Version<>(bean, start, end); + public SqlTreeLoad createLoad() { + return new SqlTreeLoadRoot(this); } @Override diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/query/SqlTreePlan.java b/ebean-core/src/main/java/io/ebeaninternal/server/query/SqlTreePlan.java new file mode 100644 index 000000000..7a0e929ef --- /dev/null +++ b/ebean-core/src/main/java/io/ebeaninternal/server/query/SqlTreePlan.java @@ -0,0 +1,41 @@ +package io.ebeaninternal.server.query; + +import java.util.Set; + +final class SqlTreePlan { + + private final SqlTreeLoad rootNode; + private final STreePropertyAssocMany manyProperty; + private final STreeProperty[] encryptedProps; + private final Set dependentTables; + + SqlTreePlan(SqlTreeLoad rootNode, STreePropertyAssocMany manyProperty, STreeProperty[] encryptedProps, Set dependentTables) { + this.rootNode = rootNode; + this.manyProperty = manyProperty; + this.encryptedProps = encryptedProps; + this.dependentTables = dependentTables; + } + + SqlTreeRoot getRootNode() { + return (SqlTreeRoot)rootNode; + } + + /** + * Return the property that is associated with the many. There can only be one + * per SqlSelect. This can be null. + */ + STreePropertyAssocMany getManyProperty() { + return manyProperty; + } + + STreeProperty[] getEncryptedProps() { + return encryptedProps; + } + + /** + * Return the tables that are joined in this query. + */ + Set dependentTables() { + return dependentTables; + } +} diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/type/EnumToDbIntegerMap.java b/ebean-core/src/main/java/io/ebeaninternal/server/type/EnumToDbIntegerMap.java index 2e2d2cc31..5f34e31fe 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/type/EnumToDbIntegerMap.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/type/EnumToDbIntegerMap.java @@ -34,6 +34,14 @@ final class EnumToDbIntegerMap extends EnumToDbValueMap { addInternal(beanValue, dbValue, name); } + @Override + public Object getBeanValue(Object dbValue) { + if (dbValue instanceof String) { + return super.getBeanValue(Integer.parseInt(dbValue.toString())); + } + return super.getBeanValue(dbValue); + } + @Override public EnumToDbIntegerMap add(Object beanValue, String stringDbValue, String name) { try { diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/type/EnumToDbValueMap.java b/ebean-core/src/main/java/io/ebeaninternal/server/type/EnumToDbValueMap.java index 92ebc9334..5ff0f9ed6 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/type/EnumToDbValueMap.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/type/EnumToDbValueMap.java @@ -116,7 +116,8 @@ abstract class EnumToDbValueMap { /** * Return the Bean value given the DB value. */ - public Object getBeanValue(T dbValue) { + @SuppressWarnings("SuspiciousMethodCalls") + public Object getBeanValue(Object dbValue) { if (dbValue == null) { return null; } @@ -125,8 +126,7 @@ abstract class EnumToDbValueMap { beanValue = nameMap.get(dbValue); } if (beanValue == null && !allowNulls) { - String msg = "Bean value for " + dbValue + " not found in " + valueMap; - throw new IllegalArgumentException(msg); + throw new IllegalArgumentException("Bean value for " + dbValue + " not found in " + valueMap); } return beanValue; } diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/type/RsetDataReader.java b/ebean-core/src/main/java/io/ebeaninternal/server/type/RsetDataReader.java index 9262237d8..43c947316 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/type/RsetDataReader.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/type/RsetDataReader.java @@ -73,6 +73,11 @@ public class RsetDataReader implements DataReader { return rset.getObject(pos()); } + @Override + public T getObject(Class cls) throws SQLException { + return rset.getObject(pos(), cls); + } + @Override public BigDecimal getBigDecimal() throws SQLException { return rset.getBigDecimal(pos()); diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/type/ScalarTypeEnumStandard.java b/ebean-core/src/main/java/io/ebeaninternal/server/type/ScalarTypeEnumStandard.java index 8545bc9a7..969bfd016 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/type/ScalarTypeEnumStandard.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/type/ScalarTypeEnumStandard.java @@ -189,7 +189,12 @@ final class ScalarTypeEnumStandard { if (dbValue == null || dbValue instanceof Enum) { return dbValue; } - int ordinal = (Integer) dbValue; + int ordinal; + if (dbValue instanceof Integer) { + ordinal = (Integer) dbValue; + } else { + ordinal = Integer.parseInt(dbValue.toString()); + } if (ordinal < 0 || ordinal >= enumArray.length) { String m = "Unexpected ordinal [" + ordinal + "] out of range [" + enumArray.length + "]"; throw new IllegalStateException(m); diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/type/ScalarTypeJodaLocalDate.java b/ebean-core/src/main/java/io/ebeaninternal/server/type/ScalarTypeJodaLocalDate.java index a7f385442..eb10a80bb 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/type/ScalarTypeJodaLocalDate.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/type/ScalarTypeJodaLocalDate.java @@ -1,11 +1,14 @@ package io.ebeaninternal.server.type; import io.ebean.config.JsonConfig; +import io.ebean.core.type.DataBinder; +import io.ebean.core.type.DataReader; import io.ebeaninternal.server.core.BasicTypeConverter; import org.joda.time.DateTimeZone; import org.joda.time.LocalDate; import java.sql.Date; +import java.sql.SQLException; import java.sql.Types; /** @@ -40,7 +43,7 @@ final class ScalarTypeJodaLocalDate extends ScalarTypeBaseDate { @SuppressWarnings("deprecation") @Override public Date convertToDate(LocalDate value) { - return new Date(value .getYear() - 1900, value .getMonthOfYear() - 1, value .getDayOfMonth()); + return new Date(value.getYear() - 1900, value.getMonthOfYear() - 1, value.getDayOfMonth()); } @Override @@ -54,9 +57,23 @@ final class ScalarTypeJodaLocalDate extends ScalarTypeBaseDate { @Override public LocalDate toBeanType(Object value) { if (value instanceof java.util.Date) { - return LocalDate.fromDateFields((java.util.Date)value); + return LocalDate.fromDateFields((java.util.Date) value); } return (LocalDate) value; } + @Override + public void bind(DataBinder binder, LocalDate value) throws SQLException { + if (value == null) { + binder.setNull(Types.DATE); + } else { + binder.setObject(java.time.LocalDate.of(value.getYear(), value.getMonthOfYear(), value.getDayOfMonth())); + } + } + + @Override + public LocalDate read(DataReader reader) throws SQLException { + java.time.LocalDate jtDate = reader.getObject(java.time.LocalDate.class); + return jtDate == null ? null : new org.joda.time.LocalDate(jtDate.getYear(), jtDate.getMonthValue(), jtDate.getDayOfMonth()); + } } diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/type/ScalarTypeLocalDate.java b/ebean-core/src/main/java/io/ebeaninternal/server/type/ScalarTypeLocalDate.java index 630fe73a3..1e298ecac 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/type/ScalarTypeLocalDate.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/type/ScalarTypeLocalDate.java @@ -1,9 +1,12 @@ package io.ebeaninternal.server.type; import io.ebean.config.JsonConfig; +import io.ebean.core.type.DataBinder; +import io.ebean.core.type.DataReader; import io.ebeaninternal.server.core.BasicTypeConverter; import java.sql.Date; +import java.sql.SQLException; import java.sql.Timestamp; import java.sql.Types; import java.time.LocalDate; @@ -61,4 +64,18 @@ final class ScalarTypeLocalDate extends ScalarTypeBaseDate { return (LocalDate) value; } + @Override + public void bind(DataBinder binder, LocalDate value) throws SQLException { + if (value == null) { + binder.setNull(Types.DATE); + } else { + binder.setObject(value); + } + } + + @Override + public LocalDate read(DataReader reader) throws SQLException { + return reader.getObject(LocalDate.class); + } + } diff --git a/ebean-ddl-generator/pom.xml b/ebean-ddl-generator/pom.xml index 4f29e4a99..6aee8c126 100644 --- a/ebean-ddl-generator/pom.xml +++ b/ebean-ddl-generator/pom.xml @@ -22,7 +22,7 @@ io.ebean ebean-migration - 12.12.1 + 12.13.0 diff --git a/ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/DefaultDbMigration.java b/ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/DefaultDbMigration.java index 00a1d9ad5..8b596e05e 100644 --- a/ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/DefaultDbMigration.java +++ b/ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/DefaultDbMigration.java @@ -470,23 +470,22 @@ public class DefaultDbMigration implements DbMigration { private void generateExtraDdl(File migrationDir, DatabasePlatform dbPlatform, boolean tablePartitioning) throws IOException { if (dbPlatform != null) { if (tablePartitioning && includeBuiltInPartitioning) { - generateExtraDdlFor(migrationDir, dbPlatform, ExtraDdlXmlReader.readBuiltinTablePartitioning(), true); + + generateExtraDdlFor(migrationDir, dbPlatform, ExtraDdlXmlReader.readBuiltinTablePartitioning(), false); } + // skip built-in migration stored procedures based on isUseMigrationStoredProcedures generateExtraDdlFor(migrationDir, dbPlatform, ExtraDdlXmlReader.readBuiltin(), true); generateExtraDdlFor(migrationDir, dbPlatform, ExtraDdlXmlReader.read(), false); } } - private void generateExtraDdlFor(File migrationDir, DatabasePlatform dbPlatform, ExtraDdl extraDdl, boolean isBuiltin) throws IOException { + + private void generateExtraDdlFor(File migrationDir, DatabasePlatform dbPlatform, ExtraDdl extraDdl, boolean checkSkip) throws IOException { if (extraDdl != null) { List ddlScript = extraDdl.getDdlScript(); for (DdlScript script : ddlScript) { if (!script.isDrop() && matchPlatform(dbPlatform.getPlatform(), script.getPlatforms())) { - if (script.isInit()) { - if (!isBuiltin || dbPlatform.isUseMigrationStoredProcedures()) { - writeExtraDdl(migrationDir, script); - } - } else { + if (!checkSkip || dbPlatform.isUseMigrationStoredProcedures()) { writeExtraDdl(migrationDir, script); } } diff --git a/ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/ddlgeneration/platform/H2Ddl.java b/ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/ddlgeneration/platform/H2Ddl.java index 13298faeb..542bc695a 100644 --- a/ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/ddlgeneration/platform/H2Ddl.java +++ b/ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/ddlgeneration/platform/H2Ddl.java @@ -22,6 +22,12 @@ public class H2Ddl extends PlatformDdl { @Override protected String convertArrayType(String logicalArrayType) { - return "array"; + int pos = logicalArrayType.indexOf('['); + if (pos == -1) { + return logicalArrayType; + } else { + // trim of the fallback varchar length + return logicalArrayType.substring(0, pos) + " array"; + } } } diff --git a/ebean-ddl-generator/src/test/java/io/ebeaninternal/dbmigration/ddlgeneration/BaseDdlHandlerTest.java b/ebean-ddl-generator/src/test/java/io/ebeaninternal/dbmigration/ddlgeneration/BaseDdlHandlerTest.java index 8de6ac22b..87c48bb85 100644 --- a/ebean-ddl-generator/src/test/java/io/ebeaninternal/dbmigration/ddlgeneration/BaseDdlHandlerTest.java +++ b/ebean-ddl-generator/src/test/java/io/ebeaninternal/dbmigration/ddlgeneration/BaseDdlHandlerTest.java @@ -107,7 +107,7 @@ public class BaseDdlHandlerTest extends BaseTestCase { write = new DdlWrite(); h2Handler().generate(write, Helper.getAlterTableAddDbArrayColumnWithLength()); - assertThat(write.apply().getBuffer()).isEqualTo("alter table foo add column dbarray_ninety array;\n\n"); + assertThat(write.apply().getBuffer()).isEqualTo("alter table foo add column dbarray_ninety varchar array;\n\n"); write = new DdlWrite(); sqlserverHandler().generate(write, Helper.getAlterTableAddDbArrayColumnWithLength()); @@ -127,7 +127,7 @@ public class BaseDdlHandlerTest extends BaseTestCase { write = new DdlWrite(); h2Handler().generate(write, Helper.getAlterTableAddDbArrayColumnIntegerWithLength()); - assertThat(write.apply().getBuffer()).isEqualTo("alter table foo add column dbarray_integer array;\n\n"); + assertThat(write.apply().getBuffer()).isEqualTo("alter table foo add column dbarray_integer integer array;\n\n"); write = new DdlWrite(); sqlserverHandler().generate(write, Helper.getAlterTableAddDbArrayColumnIntegerWithLength()); diff --git a/ebean-ddl-generator/src/test/java/io/ebeaninternal/dbmigration/ddlgeneration/platform/PlatformDdl_AlterColumnTest.java b/ebean-ddl-generator/src/test/java/io/ebeaninternal/dbmigration/ddlgeneration/platform/PlatformDdl_AlterColumnTest.java index 5da1a25a8..3141eef74 100644 --- a/ebean-ddl-generator/src/test/java/io/ebeaninternal/dbmigration/ddlgeneration/platform/PlatformDdl_AlterColumnTest.java +++ b/ebean-ddl-generator/src/test/java/io/ebeaninternal/dbmigration/ddlgeneration/platform/PlatformDdl_AlterColumnTest.java @@ -57,10 +57,10 @@ public class PlatformDdl_AlterColumnTest { @Test public void convertArrayType_h2() { - assertThat(h2Ddl.convertArrayType("varchar[](90)")).isEqualTo("array"); - assertThat(h2Ddl.convertArrayType("integer[](60)")).isEqualTo("array"); - assertThat(h2Ddl.convertArrayType("varchar[]")).isEqualTo("array"); - assertThat(h2Ddl.convertArrayType("integer[]")).isEqualTo("array"); + assertThat(h2Ddl.convertArrayType("varchar[](90)")).isEqualTo("varchar array"); + assertThat(h2Ddl.convertArrayType("integer[](60)")).isEqualTo("integer array"); + assertThat(h2Ddl.convertArrayType("varchar[]")).isEqualTo("varchar array"); + assertThat(h2Ddl.convertArrayType("integer[]")).isEqualTo("integer array"); } @Test diff --git a/ebean-ddl-generator/src/test/java/io/ebeaninternal/dbmigration/ddlgeneration/platform/PlatformTypeConverterTest.java b/ebean-ddl-generator/src/test/java/io/ebeaninternal/dbmigration/ddlgeneration/platform/PlatformTypeConverterTest.java index 45dbd60b6..25a4621d5 100644 --- a/ebean-ddl-generator/src/test/java/io/ebeaninternal/dbmigration/ddlgeneration/platform/PlatformTypeConverterTest.java +++ b/ebean-ddl-generator/src/test/java/io/ebeaninternal/dbmigration/ddlgeneration/platform/PlatformTypeConverterTest.java @@ -61,7 +61,7 @@ public class PlatformTypeConverterTest { assertThat(converter.convert("clob")).isEqualTo("clob"); assertThat(converter.convert("blob")).isEqualTo("blob"); assertThat(converter.convert("tinyint")).isEqualTo("tinyint"); - assertThat(converter.convert("integer(8)")).isEqualTo("integer(8)"); + assertThat(converter.convert("integer(8)")).isEqualTo("integer"); assertThat(converter.convert("funky")).isEqualTo("funky"); // unknown } diff --git a/ebean-ddl-generator/src/test/resources/mysql/procedures/model/1.0__initial.model.xml b/ebean-ddl-generator/src/test/resources/mysql/procedures/model/1.0__initial.model.xml new file mode 100644 index 000000000..4cb2aea38 --- /dev/null +++ b/ebean-ddl-generator/src/test/resources/mysql/procedures/model/1.0__initial.model.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/ebean-ddl-generator/src/test/resources/mysql/procedures/model/1.1.model.xml b/ebean-ddl-generator/src/test/resources/mysql/procedures/model/1.1.model.xml new file mode 100644 index 000000000..27ec6c42a --- /dev/null +++ b/ebean-ddl-generator/src/test/resources/mysql/procedures/model/1.1.model.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/ebean-ddl-generator/src/test/resources/mysql/procedures/model/1.2__dropsFor_1.1.model.xml b/ebean-ddl-generator/src/test/resources/mysql/procedures/model/1.2__dropsFor_1.1.model.xml new file mode 100644 index 000000000..7b89fb0e0 --- /dev/null +++ b/ebean-ddl-generator/src/test/resources/mysql/procedures/model/1.2__dropsFor_1.1.model.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/ebean-ddl-generator/src/test/resources/mysql/procedures/mysql/1.0__initial.sql b/ebean-ddl-generator/src/test/resources/mysql/procedures/mysql/1.0__initial.sql new file mode 100644 index 000000000..9e44107be --- /dev/null +++ b/ebean-ddl-generator/src/test/resources/mysql/procedures/mysql/1.0__initial.sql @@ -0,0 +1,11 @@ +-- Migrationscripts for ebean unittest +-- apply changes +create table migtest_e_basic ( + id integer auto_increment not null, + status varchar(1), + status2 varchar(1) default 'N' not null, + name varchar(127), + description varchar(127), + constraint pk_migtest_e_basic primary key (id) +); + diff --git a/ebean-ddl-generator/src/test/resources/mysql/procedures/mysql/1.2__dropsFor_1.1.sql b/ebean-ddl-generator/src/test/resources/mysql/procedures/mysql/1.2__dropsFor_1.1.sql new file mode 100644 index 000000000..38b133086 --- /dev/null +++ b/ebean-ddl-generator/src/test/resources/mysql/procedures/mysql/1.2__dropsFor_1.1.sql @@ -0,0 +1,6 @@ +-- Migrationscripts for ebean unittest +-- apply changes +CALL usp_ebean_drop_column('migtest_e_basic', 'status2'); + +CALL usp_ebean_drop_column('migtest_e_basic', 'description'); + diff --git a/ebean-ddl-generator/src/test/resources/mysql/procedures/mysql/I__create_procs.sql b/ebean-ddl-generator/src/test/resources/mysql/procedures/mysql/I__create_procs.sql new file mode 100644 index 000000000..d13ec2708 --- /dev/null +++ b/ebean-ddl-generator/src/test/resources/mysql/procedures/mysql/I__create_procs.sql @@ -0,0 +1,48 @@ +-- Inital script to create stored procedures etc for mysql platform +DROP PROCEDURE IF EXISTS usp_ebean_drop_foreign_keys; + +delimiter $$ +-- +-- PROCEDURE: usp_ebean_drop_foreign_keys TABLE, COLUMN +-- deletes all constraints and foreign keys referring to TABLE.COLUMN +-- +CREATE PROCEDURE usp_ebean_drop_foreign_keys(IN p_table_name VARCHAR(255), IN p_column_name VARCHAR(255)) +BEGIN +DECLARE done INT DEFAULT FALSE; +DECLARE c_fk_name CHAR(255); +DECLARE curs CURSOR FOR SELECT CONSTRAINT_NAME from information_schema.KEY_COLUMN_USAGE +WHERE TABLE_SCHEMA = DATABASE() and TABLE_NAME = p_table_name and COLUMN_NAME = p_column_name +AND REFERENCED_TABLE_NAME IS NOT NULL; +DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE; + +OPEN curs; + +read_loop: LOOP +FETCH curs INTO c_fk_name; +IF done THEN +LEAVE read_loop; +END IF; +SET @sql = CONCAT('ALTER TABLE ', p_table_name, ' DROP FOREIGN KEY ', c_fk_name); +PREPARE stmt FROM @sql; +EXECUTE stmt; +END LOOP; + +CLOSE curs; +END +$$ + +DROP PROCEDURE IF EXISTS usp_ebean_drop_column; + +delimiter $$ +-- +-- PROCEDURE: usp_ebean_drop_column TABLE, COLUMN +-- deletes the column and ensures that all indices and constraints are dropped first +-- +CREATE PROCEDURE usp_ebean_drop_column(IN p_table_name VARCHAR(255), IN p_column_name VARCHAR(255)) +BEGIN +CALL usp_ebean_drop_foreign_keys(p_table_name, p_column_name); +SET @sql = CONCAT('ALTER TABLE ', p_table_name, ' DROP COLUMN ', p_column_name); +PREPARE stmt FROM @sql; +EXECUTE stmt; +END +$$ diff --git a/ebean-ddl-generator/src/test/resources/mysql/procedures/mysql/idx_mysql.migrations b/ebean-ddl-generator/src/test/resources/mysql/procedures/mysql/idx_mysql.migrations new file mode 100644 index 000000000..1cb8a087c --- /dev/null +++ b/ebean-ddl-generator/src/test/resources/mysql/procedures/mysql/idx_mysql.migrations @@ -0,0 +1,4 @@ +1835064798, I__create_procs.sql +1968521526, 1.0__initial.sql +-728933533, 1.2__dropsFor_1.1.sql + diff --git a/ebean-externalmapping-xml/pom.xml b/ebean-externalmapping-xml/pom.xml index 0a48de156..3586864cd 100644 --- a/ebean-externalmapping-xml/pom.xml +++ b/ebean-externalmapping-xml/pom.xml @@ -89,7 +89,7 @@ - io.ebean.tile:enhancement:12.11.3 + io.ebean.tile:enhancement:12.14.1 io.avaje.tile:moditech-module:1.0 diff --git a/ebean-kotlin/pom.xml b/ebean-kotlin/pom.xml index fda0777a5..3b3817d54 100644 --- a/ebean-kotlin/pom.xml +++ b/ebean-kotlin/pom.xml @@ -12,7 +12,7 @@ 1.8 1.8 - 12.13.0 + 12.14.1 1.6.0 diff --git a/ebean-postgis/pom.xml b/ebean-postgis/pom.xml index ac4553b77..caa1ccf81 100644 --- a/ebean-postgis/pom.xml +++ b/ebean-postgis/pom.xml @@ -105,7 +105,7 @@ true - io.ebean.tile:enhancement:12.11.3 + io.ebean.tile:enhancement:12.14.1 io.avaje.tile:moditech-module:1.0 diff --git a/ebean-querybean/pom.xml b/ebean-querybean/pom.xml index 6ecbfafad..210e2249e 100644 --- a/ebean-querybean/pom.xml +++ b/ebean-querybean/pom.xml @@ -100,7 +100,7 @@ true - io.ebean.tile:enhancement:12.11.3 + io.ebean.tile:enhancement:12.14.1 io.avaje.tile:moditech-module:1.0 diff --git a/ebean-querybean/src/test/resources/application-test.properties b/ebean-querybean/src/test/resources/application-test.properties index 76f2d661f..fef41029d 100644 --- a/ebean-querybean/src/test/resources/application-test.properties +++ b/ebean-querybean/src/test/resources/application-test.properties @@ -6,8 +6,8 @@ datasource.default=h2 datasource.h2.username=sa datasource.h2.password= -datasource.h2.databaseUrl=jdbc:h2:mem:tests +datasource.h2.url=jdbc:h2:mem:tests;NON_KEYWORDS=KEY datasource.pg.username=sa datasource.pg.password= -datasource.pg.databaseUrl=jdbc:h2:mem:tests +datasource.pg.url=jdbc:h2:mem:tests;NON_KEYWORDS=KEY diff --git a/ebean-redis/pom.xml b/ebean-redis/pom.xml index e811c97ad..f33064c74 100644 --- a/ebean-redis/pom.xml +++ b/ebean-redis/pom.xml @@ -80,7 +80,7 @@ true - io.ebean.tile:enhancement:12.11.3 + io.ebean.tile:enhancement:12.14.1 io.avaje.tile:moditech-module:1.0 diff --git a/ebean-test/pom.xml b/ebean-test/pom.xml index 37f61c236..ab395230c 100644 --- a/ebean-test/pom.xml +++ b/ebean-test/pom.xml @@ -16,7 +16,7 @@ org.slf4j slf4j-api - 1.7.30 + 1.7.32 provided @@ -56,7 +56,7 @@ io.ebean ebean-test-docker - 4.3 + 4.4 @@ -76,7 +76,7 @@ com.h2database h2 - 1.4.199 + ${h2database.version} @@ -249,7 +249,7 @@ true - io.ebean.tile:enhancement:12.11.3 + io.ebean.tile:enhancement:12.14.1 io.avaje.tile:moditech-module:1.0 diff --git a/ebean-test/src/main/java/io/ebean/test/config/platform/ClickHouseSetup.java b/ebean-test/src/main/java/io/ebean/test/config/platform/ClickHouseSetup.java index 4dcb472b4..a2a6ca0e0 100644 --- a/ebean-test/src/main/java/io/ebean/test/config/platform/ClickHouseSetup.java +++ b/ebean-test/src/main/java/io/ebean/test/config/platform/ClickHouseSetup.java @@ -11,7 +11,7 @@ class ClickHouseSetup implements PlatformSetup { config.setDefaultPort(8123); config.setUsername("default"); config.setPassword(""); - config.setUrl("jdbc:clickhouse://localhost:${port}/${databaseName}"); + config.setUrl("jdbc:clickhouse://${host}:${port}/${databaseName}"); config.setDriver("ru.yandex.clickhouse.ClickHouseDriver"); config.datasourceDefaults(); diff --git a/ebean-test/src/main/java/io/ebean/test/config/platform/CockroachSetup.java b/ebean-test/src/main/java/io/ebean/test/config/platform/CockroachSetup.java index 879f895ee..b68ca912e 100644 --- a/ebean-test/src/main/java/io/ebean/test/config/platform/CockroachSetup.java +++ b/ebean-test/src/main/java/io/ebean/test/config/platform/CockroachSetup.java @@ -11,7 +11,7 @@ class CockroachSetup implements PlatformSetup { config.setDefaultPort(26257); config.setUsername("root"); config.setPassword(""); - config.setUrl("jdbc:postgresql://localhost:${port}/${databaseName}?sslmode=disable"); + config.setUrl("jdbc:postgresql://${host}:${port}/${databaseName}?sslmode=disable"); config.setDriver("org.postgresql.Driver"); config.datasourceDefaults(); diff --git a/ebean-test/src/main/java/io/ebean/test/config/platform/Config.java b/ebean-test/src/main/java/io/ebean/test/config/platform/Config.java index cefae4ce4..117c04a18 100644 --- a/ebean-test/src/main/java/io/ebean/test/config/platform/Config.java +++ b/ebean-test/src/main/java/io/ebean/test/config/platform/Config.java @@ -4,8 +4,8 @@ import io.ebean.config.DatabaseConfig; import io.ebean.datasource.DataSourceConfig; import org.slf4j.Logger; import org.slf4j.LoggerFactory; - import java.io.File; +import java.util.Locale; import java.util.Properties; /** @@ -26,11 +26,9 @@ class Config { private final String db; private final String platform; private String dockerPlatform; - private String databaseName; private final Properties properties; - private int port; private String url; @@ -38,12 +36,10 @@ class Config { private String schema; private String username; private String password; - private final DatabaseConfig config; - private boolean containerDropCreate; - private final Properties dockerProperties = new Properties(); + private final DockerHost dockerHost = new DockerHost(); Config(String db, String platform, String databaseName, DatabaseConfig config) { this.db = db; @@ -231,11 +227,17 @@ class Config { void setUrl(String urlPattern) { String val = getPlatformKey("url", urlPattern); + val = val.replace("${host}", host()); val = val.replace("${port}", String.valueOf(port)); val = val.replace("${databaseName}", databaseName); this.url = val; } + String host() { + String explicitDockerHost = getKey("dockerHost", null); + return getKey("host", dockerHost.dockerHost(explicitDockerHost)); + } + /** * Append to the connection URL. */ @@ -322,7 +324,6 @@ class Config { void setDockerVersion(String version) { String val = getPlatformKey("version", version); dockerProperties.setProperty(dockerKey("version"), val); - if (containerDropCreate) { dockerProperties.setProperty(dockerKey("startMode"), "dropCreate"); } @@ -369,7 +370,10 @@ class Config { } private void initDockerProperties() { - + if (dockerHost.runningInDocker()) { + // tell ebean-docker-test we are not using localhost (for jdbc DB setup commands) + dockerProperties.setProperty(dockerKey("host"), dockerHost.dockerHost()); + } dockerProperties.setProperty(dockerKey("port"), String.valueOf(port)); dockerProperties.setProperty(dockerKey("dbName"), databaseName); if (schema != null) { diff --git a/ebean-test/src/main/java/io/ebean/test/config/platform/Db2Setup.java b/ebean-test/src/main/java/io/ebean/test/config/platform/Db2Setup.java index fd65472ef..70fa4fc2c 100644 --- a/ebean-test/src/main/java/io/ebean/test/config/platform/Db2Setup.java +++ b/ebean-test/src/main/java/io/ebean/test/config/platform/Db2Setup.java @@ -11,7 +11,7 @@ class Db2Setup implements PlatformSetup { config.setDefaultPort(50000); config.setUsernameDefault(); config.setPasswordDefault(); - config.setUrl("jdbc:db2://localhost:${port}/${databaseName}"); + config.setUrl("jdbc:db2://${host}:${port}/${databaseName}"); config.setDriver("com.ibm.db2.jcc.DB2Driver"); config.datasourceDefaults(); diff --git a/ebean-test/src/main/java/io/ebean/test/config/platform/DockerHost.java b/ebean-test/src/main/java/io/ebean/test/config/platform/DockerHost.java new file mode 100644 index 000000000..5e3f0ce94 --- /dev/null +++ b/ebean-test/src/main/java/io/ebean/test/config/platform/DockerHost.java @@ -0,0 +1,55 @@ +package io.ebean.test.config.platform; + +import java.io.File; +import java.util.Locale; + +/** + * Helper to detect if running inside docker and determine host name for that case. + */ +class DockerHost { + + private final boolean runningInDocker; + private String dockerHost; + + DockerHost() { + runningInDocker = initInDocker(); + } + + boolean runningInDocker() { + return runningInDocker; + } + + String dockerHost() { + return dockerHost; + } + + String dockerHost(String explicitHost) { + if (!runningInDocker) { + return "localhost"; + } + dockerHost = explicitHost != null ? explicitHost : defaultDockerHost(); + return dockerHost; + } + + /** + * Return true if running inside a docker container (we are using docker in docker). + */ + boolean initInDocker() { + return new File("/.dockerenv").exists(); + } + + /** + * Return the default host name to use when running in docker. + *

+ * Can instead be explicitly specified via ebean.test.dockerHost. + */ + String defaultDockerHost() { + String os = System.getProperty("os.name", "generic").toLowerCase(Locale.ENGLISH); + if (os.contains("mac") || os.contains("darwin") || os.contains("win")) { + return "host.docker.internal"; + } else { + return "172.17.0.1"; + } + } + +} diff --git a/ebean-test/src/main/java/io/ebean/test/config/platform/HanaSetup.java b/ebean-test/src/main/java/io/ebean/test/config/platform/HanaSetup.java index 8ad014b70..a5137686f 100644 --- a/ebean-test/src/main/java/io/ebean/test/config/platform/HanaSetup.java +++ b/ebean-test/src/main/java/io/ebean/test/config/platform/HanaSetup.java @@ -20,7 +20,7 @@ class HanaSetup implements PlatformSetup { config.setUsername("SYSTEM"); config.setPassword("HXEHana1"); config.setDatabaseName("HXE"); - config.setUrl("jdbc:sap://localhost:${port}/?databaseName=${databaseName}"); + config.setUrl("jdbc:sap://${host}:${port}/?databaseName=${databaseName}"); String schema = config.getSchema(); if (schema != null && !schema.equals(config.getUsername())) { config.urlAppend("¤tSchema=" + schema); diff --git a/ebean-test/src/main/java/io/ebean/test/config/platform/MariaDBSetup.java b/ebean-test/src/main/java/io/ebean/test/config/platform/MariaDBSetup.java index 4e242d4b0..f2ff22cc4 100644 --- a/ebean-test/src/main/java/io/ebean/test/config/platform/MariaDBSetup.java +++ b/ebean-test/src/main/java/io/ebean/test/config/platform/MariaDBSetup.java @@ -13,7 +13,7 @@ class MariaDBSetup implements PlatformSetup { config.setDefaultPort(defaultPort); config.setUsernameDefault(); config.setPasswordDefault(); - config.setUrl("jdbc:mariadb://localhost:${port}/${databaseName}?useLegacyDatetimeCode=false"); + config.setUrl("jdbc:mariadb://${host}:${port}/${databaseName}?useLegacyDatetimeCode=false"); config.datasourceDefaults(); return dockerProperties(config); diff --git a/ebean-test/src/main/java/io/ebean/test/config/platform/MySqlSetup.java b/ebean-test/src/main/java/io/ebean/test/config/platform/MySqlSetup.java index 3939ffbec..c865c41b7 100644 --- a/ebean-test/src/main/java/io/ebean/test/config/platform/MySqlSetup.java +++ b/ebean-test/src/main/java/io/ebean/test/config/platform/MySqlSetup.java @@ -13,7 +13,7 @@ class MySqlSetup implements PlatformSetup { config.setDefaultPort(defaultPort); config.setUsernameDefault(); config.setPasswordDefault(); - config.setUrl("jdbc:mysql://localhost:${port}/${databaseName}"); + config.setUrl("jdbc:mysql://${host}:${port}/${databaseName}"); config.setDriver(defaultDriver()); config.datasourceDefaults(); diff --git a/ebean-test/src/main/java/io/ebean/test/config/platform/PostgisSetup.java b/ebean-test/src/main/java/io/ebean/test/config/platform/PostgisSetup.java index 8180dc425..bd5a38387 100644 --- a/ebean-test/src/main/java/io/ebean/test/config/platform/PostgisSetup.java +++ b/ebean-test/src/main/java/io/ebean/test/config/platform/PostgisSetup.java @@ -20,7 +20,7 @@ class PostgisSetup implements PlatformSetup { config.setUsernameDefault(); config.setPasswordDefault(); config.setDriver("org.postgis.DriverWrapperLW"); - config.setUrl("jdbc:postgresql_lwgis://localhost:${port}/${databaseName}"); + config.setUrl("jdbc:postgresql_lwgis://${host}:${port}/${databaseName}"); String schema = config.getSchema(); if (schema != null && !schema.equals(config.getUsername())) { diff --git a/ebean-test/src/main/java/io/ebean/test/config/platform/PostgresSetup.java b/ebean-test/src/main/java/io/ebean/test/config/platform/PostgresSetup.java index 8e9045408..e5d1206f7 100644 --- a/ebean-test/src/main/java/io/ebean/test/config/platform/PostgresSetup.java +++ b/ebean-test/src/main/java/io/ebean/test/config/platform/PostgresSetup.java @@ -13,7 +13,7 @@ class PostgresSetup implements PlatformSetup { config.setDefaultPort(defaultPort); config.setUsernameDefault(); config.setPasswordDefault(); - config.setUrl("jdbc:postgresql://localhost:${port}/${databaseName}"); + config.setUrl("jdbc:postgresql://${host}:${port}/${databaseName}"); String schema = config.getSchema(); if (schema != null && !schema.equals(config.getUsername())) { @@ -44,7 +44,7 @@ class PostgresSetup implements PlatformSetup { config.setDefaultPort(defaultPort); config.setExtraUsernameDefault(); config.setExtraDbPasswordDefault(); - config.setUrl("jdbc:postgresql://localhost:${port}/${databaseName}"); + config.setUrl("jdbc:postgresql://${host}:${port}/${databaseName}"); config.setDriver("org.postgresql.Driver"); config.extraDatasourceDefaults(); } diff --git a/ebean-test/src/main/java/io/ebean/test/config/platform/RedisSetup.java b/ebean-test/src/main/java/io/ebean/test/config/platform/RedisSetup.java index 70580bd81..c8fc01671 100644 --- a/ebean-test/src/main/java/io/ebean/test/config/platform/RedisSetup.java +++ b/ebean-test/src/main/java/io/ebean/test/config/platform/RedisSetup.java @@ -8,10 +8,14 @@ import java.util.Properties; class RedisSetup { static void run(Properties properties) { - String version = properties.getProperty("ebean.test.redis"); version = properties.getProperty("ebean.test.redis.version", version); if (version != null) { + DockerHost dockerHost = new DockerHost(); + if (dockerHost.runningInDocker()) { + String host = dockerHost.dockerHost(properties.getProperty("ebean.test.dockerHost")); + properties.setProperty("redis.host", host); + } RedisConfig redisConfig = new RedisConfig(version, properties); RedisContainer container = new RedisContainer(redisConfig); container.start(); diff --git a/ebean-test/src/main/java/io/ebean/test/config/platform/SqlServerSetup.java b/ebean-test/src/main/java/io/ebean/test/config/platform/SqlServerSetup.java index 09b76323e..3428fb43e 100644 --- a/ebean-test/src/main/java/io/ebean/test/config/platform/SqlServerSetup.java +++ b/ebean-test/src/main/java/io/ebean/test/config/platform/SqlServerSetup.java @@ -13,7 +13,7 @@ class SqlServerSetup implements PlatformSetup { config.setDefaultPort(1433); config.setUsernameDefault(); config.setPassword("SqlS3rv#r"); - config.setUrl("jdbc:sqlserver://localhost:${port};databaseName=${databaseName};sendTimeAsDateTime=false"); + config.setUrl("jdbc:sqlserver://${host}:${port};databaseName=${databaseName};sendTimeAsDateTime=false"); config.setDriver("com.microsoft.sqlserver.jdbc.SQLServerDriver"); config.datasourceDefaults(); diff --git a/ebean-test/src/test/java/io/ebean/config/PlatformNoGeneratedKeysTest.java b/ebean-test/src/test/java/io/ebean/config/PlatformNoGeneratedKeysTest.java index 9d600fc56..6c57e56bb 100644 --- a/ebean-test/src/test/java/io/ebean/config/PlatformNoGeneratedKeysTest.java +++ b/ebean-test/src/test/java/io/ebean/config/PlatformNoGeneratedKeysTest.java @@ -101,7 +101,7 @@ public class PlatformNoGeneratedKeysTest { config.setDatabasePlatform(platform); config.getDataSourceConfig().setUsername("sa"); config.getDataSourceConfig().setPassword(""); - config.getDataSourceConfig().setUrl("jdbc:h2:mem:withPCQuery;"); + config.getDataSourceConfig().setUrl("jdbc:h2:mem:withPCQuery;MODE=LEGACY"); config.getDataSourceConfig().setDriver("org.h2.Driver"); config.setDisableLazyLoading(true); diff --git a/ebean-test/src/test/java/io/ebean/config/dbplatform/OraclePlatformTest.java b/ebean-test/src/test/java/io/ebean/config/dbplatform/OraclePlatformTest.java index cf6b513ee..47d8fa0dd 100644 --- a/ebean-test/src/test/java/io/ebean/config/dbplatform/OraclePlatformTest.java +++ b/ebean-test/src/test/java/io/ebean/config/dbplatform/OraclePlatformTest.java @@ -2,28 +2,34 @@ package io.ebean.config.dbplatform; import io.ebean.config.PlatformConfig; import io.ebean.config.dbplatform.oracle.Oracle11Platform; +import io.ebean.config.dbplatform.oracle.Oracle12Platform; import io.ebean.config.dbplatform.oracle.OraclePlatform; import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; -public class OraclePlatformTest { +class OraclePlatformTest { @Test - public void columnAliasPrefix_Oracle11Platform() { + void columnAliasPrefix_Oracle11Platform() { Oracle11Platform platform11 = new Oracle11Platform(); assertThat(platform11.columnAliasPrefix).isEqualTo("c"); } @Test - public void columnAliasPrefix_OraclePlatform() { - OraclePlatform platform = new OraclePlatform(); - assertThat(platform.columnAliasPrefix).isEqualTo("c"); + void columnAliasPrefix_Oracle12Platform() { + Oracle12Platform platform12 = new Oracle12Platform(); + assertThat(platform12.columnAliasPrefix).isEqualTo("c"); } @Test - public void uuid_default() { + void columnAliasPrefix_OraclePlatform() { + OraclePlatform platform = new OraclePlatform(); + assertThat(platform.columnAliasPrefix).isNull(); + } + @Test + void uuid_default() { OraclePlatform platform = new OraclePlatform(); platform.configure(new PlatformConfig(), false); DbPlatformType dbType = platform.getDbTypeMap().get(DbPlatformType.UUID); @@ -31,8 +37,7 @@ public class OraclePlatformTest { } @Test - public void uuid_as_binary() { - + void uuid_as_binary() { OraclePlatform platform = new OraclePlatform(); PlatformConfig config = new PlatformConfig(); config.setDbUuid(PlatformConfig.DbUuid.AUTO_BINARY); diff --git a/ebean-test/src/test/java/io/ebean/test/config/platform/DockerHostTest.java b/ebean-test/src/test/java/io/ebean/test/config/platform/DockerHostTest.java new file mode 100644 index 000000000..0be8a5d43 --- /dev/null +++ b/ebean-test/src/test/java/io/ebean/test/config/platform/DockerHostTest.java @@ -0,0 +1,87 @@ +package io.ebean.test.config.platform; + +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class DockerHostTest { + + @Test + void runningInDocker_when_false_alwaysUseLocalhost() { + DockerHost dockerHost = new DockerHost(); + assertFalse(dockerHost.runningInDocker()); + assertEquals("localhost", dockerHost.dockerHost("foo")); + } + + @Test + void runningInDocker_when_true_useExplicit() { + TDDockerHost dockerHost = new TDDockerHost(); + assertTrue(dockerHost.runningInDocker()); + + assertEquals("my-host", dockerHost.dockerHost("my-host")); + } + + @Disabled + @Test + void runningInDocker_when_trueAndLinux_useDefault() { + TDDockerHost dockerHost = new TDDockerHost(); + assertTrue(dockerHost.runningInDocker()); + + assertEquals("172.17.0.1", dockerHost.dockerHost(null)); + } + + @Test + void runningInDocker_when_windowsDefault() { + TDDockerHost dockerHost = new TDDockerHost(); + assertTrue(dockerHost.runningInDocker()); + + String origName = System.getProperty("os.name"); + System.setProperty("os.name", "win"); + try { + assertEquals("host.docker.internal",dockerHost.defaultDockerHost()); + assertEquals("host.docker.internal", dockerHost.dockerHost(null)); + } finally { + System.setProperty("os.name", origName); + } + } + + @Test + void runningInDocker_when_macDefault() { + TDDockerHost dockerHost = new TDDockerHost(); + assertTrue(dockerHost.runningInDocker()); + + String origName = System.getProperty("os.name"); + System.setProperty("os.name", "mac"); + try { + assertEquals("host.docker.internal",dockerHost.defaultDockerHost()); + assertEquals("host.docker.internal", dockerHost.dockerHost(null)); + } finally { + System.setProperty("os.name", origName); + } + } + + + @Test + void runningInDocker_when_linuxDefault() { + TDDockerHost dockerHost = new TDDockerHost(); + assertTrue(dockerHost.runningInDocker()); + + String origName = System.getProperty("os.name"); + System.setProperty("os.name", "linux"); + try { + assertEquals("172.17.0.1",dockerHost.defaultDockerHost()); + assertEquals("172.17.0.1", dockerHost.dockerHost(null)); + } finally { + System.setProperty("os.name", origName); + } + } + + static class TDDockerHost extends DockerHost { + + @Override + boolean initInDocker() { + return true; + } + } +} diff --git a/ebean-test/src/test/java/io/ebeaninternal/server/core/DefaultServer_getReferenceTest.java b/ebean-test/src/test/java/io/ebeaninternal/server/core/DefaultServer_getReferenceTest.java index 8adb94898..a7dbc0165 100644 --- a/ebean-test/src/test/java/io/ebeaninternal/server/core/DefaultServer_getReferenceTest.java +++ b/ebean-test/src/test/java/io/ebeaninternal/server/core/DefaultServer_getReferenceTest.java @@ -24,8 +24,8 @@ public class DefaultServer_getReferenceTest extends BaseTestCase { ResetBasicData.reset(); DB.execute(() -> { - Customer loaded = DB.find(Customer.class, 1); - Customer reference = DB.reference(Customer.class, 1); + Customer loaded = DB.find(Customer.class).where().eq("name", "Rob").findOne(); + Customer reference = DB.reference(Customer.class, loaded.getId()); assertThat(loaded).isSameAs(reference); }); } diff --git a/ebean-test/src/test/java/io/ebeaninternal/server/deploy/BeanPropertyAssocManyTest.java b/ebean-test/src/test/java/io/ebeaninternal/server/deploy/BeanPropertyAssocManyTest.java index c63966f21..eadc01968 100644 --- a/ebean-test/src/test/java/io/ebeaninternal/server/deploy/BeanPropertyAssocManyTest.java +++ b/ebean-test/src/test/java/io/ebeaninternal/server/deploy/BeanPropertyAssocManyTest.java @@ -1,6 +1,7 @@ package io.ebeaninternal.server.deploy; import io.ebean.BaseTestCase; +import io.ebean.DB; import io.ebean.bean.BeanCollection; import io.ebean.bean.EntityBean; import io.ebean.common.BeanList; @@ -66,11 +67,13 @@ public class BeanPropertyAssocManyTest extends BaseTestCase { ResetBasicData.reset(); - List customerIds = new ArrayList<>(); - customerIds.add(1L); - customerIds.add(2L); + List ids = DB.find(Customer.class).orderBy("id").setMaxRows(2).findIds(); - List contactIdsForOne = contacts().findIdsByParentId(1L, null, null, null, true); + List customerIds = new ArrayList<>(); + customerIds.add(ids.get(0)); + customerIds.add(ids.get(1)); + + List contactIdsForOne = contacts().findIdsByParentId(ids.get(0), null, null, null, true); List contactIdsForMultiple = contacts().findIdsByParentId(null, customerIds, null, null, true); diff --git a/ebean-test/src/test/java/io/ebeaninternal/server/type/ScalarTypeLocalDateTest.java b/ebean-test/src/test/java/io/ebeaninternal/server/type/ScalarTypeLocalDateTest.java index 076bad958..da18e2a3c 100644 --- a/ebean-test/src/test/java/io/ebeaninternal/server/type/ScalarTypeLocalDateTest.java +++ b/ebean-test/src/test/java/io/ebeaninternal/server/type/ScalarTypeLocalDateTest.java @@ -47,6 +47,14 @@ public class ScalarTypeLocalDateTest { assertTrue(o instanceof Date); } + @Test + public void olderDates_ToFromJdbcType() { + LocalDate localDate = LocalDate.of(1850,12,1); + Object o = type.toJdbcType(localDate); + LocalDate localDate1 = type.toBeanType(o); + assertThat(localDate1).isEqualTo(localDate); + } + @Test public void testToBeanType() { diff --git a/ebean-test/src/test/java/org/tests/batchinsert/TestBatchSaveWithGetBeanId.java b/ebean-test/src/test/java/org/tests/batchinsert/TestBatchSaveWithGetBeanId.java index 7239731d5..7495f8560 100644 --- a/ebean-test/src/test/java/org/tests/batchinsert/TestBatchSaveWithGetBeanId.java +++ b/ebean-test/src/test/java/org/tests/batchinsert/TestBatchSaveWithGetBeanId.java @@ -6,13 +6,20 @@ import io.ebean.Database; import io.ebean.annotation.IgnorePlatform; import io.ebean.annotation.Platform; import io.ebean.annotation.Transactional; +import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.tests.model.basic.Customer; +import org.tests.model.basic.ResetBasicData; import static org.junit.jupiter.api.Assertions.assertNotNull; public class TestBatchSaveWithGetBeanId extends BaseTestCase { + @BeforeAll + static void before() { + ResetBasicData.reset(); + } + /** * Making this transaction with batchSize means that the insert * below does not occur immediately ... and the getBeanId() @@ -22,7 +29,6 @@ public class TestBatchSaveWithGetBeanId extends BaseTestCase { @Test @IgnorePlatform(Platform.HANA) // HANA doesn't support insert batching public void test() { - Database server = DB.getDefault(); Customer model = new Customer(); model.setName("foo"); diff --git a/ebean-test/src/test/java/org/tests/defaultvalues/TestDefaults.java b/ebean-test/src/test/java/org/tests/defaultvalues/TestDefaults.java index f66e7d9cf..9822a68ff 100644 --- a/ebean-test/src/test/java/org/tests/defaultvalues/TestDefaults.java +++ b/ebean-test/src/test/java/org/tests/defaultvalues/TestDefaults.java @@ -26,7 +26,7 @@ public class TestDefaults extends BaseTestCase { final List current = LoggedSql.collect(); assertThat(current).isNotEmpty(); - if (isMySql() || isMariaDB()) { + if (isMySql() || isMariaDB() || isOracle()) { assertThat(current.get(0)).contains("insert into defaults_model_draft values (default);"); } else if (isDb2()) { assertThat(current.get(0)).contains("insert into defaults_model_draft (id) values (default)"); diff --git a/ebean-test/src/test/java/org/tests/inheritance/abstrakt/AbstractBaseBlock.java b/ebean-test/src/test/java/org/tests/inheritance/abstrakt/AbstractBaseBlock.java index c4c3d0202..ecd0c2e39 100644 --- a/ebean-test/src/test/java/org/tests/inheritance/abstrakt/AbstractBaseBlock.java +++ b/ebean-test/src/test/java/org/tests/inheritance/abstrakt/AbstractBaseBlock.java @@ -3,7 +3,7 @@ package org.tests.inheritance.abstrakt; import javax.persistence.*; @Entity -@Table(name = "block") +@Table(name = "blockt") @Inheritance @DiscriminatorColumn(name = "case_type", discriminatorType = DiscriminatorType.INTEGER) public abstract class AbstractBaseBlock { diff --git a/ebean-test/src/test/java/org/tests/insert/TestInsertCollection.java b/ebean-test/src/test/java/org/tests/insert/TestInsertCollection.java index 0d3679d91..773bffb02 100644 --- a/ebean-test/src/test/java/org/tests/insert/TestInsertCollection.java +++ b/ebean-test/src/test/java/org/tests/insert/TestInsertCollection.java @@ -2,8 +2,10 @@ package org.tests.insert; import io.ebean.BaseTestCase; import io.ebean.DB; +import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.tests.model.basic.Customer; +import org.tests.model.basic.ResetBasicData; import java.util.ArrayList; import java.util.List; @@ -12,9 +14,13 @@ import static org.junit.jupiter.api.Assertions.*; public class TestInsertCollection extends BaseTestCase { + @BeforeAll + static void before() { + ResetBasicData.reset(); + } + @Test public void test() { - Customer cust1 = new Customer(); cust1.setName("jim"); diff --git a/ebean-test/src/test/java/org/tests/model/basic/ResetBasicData.java b/ebean-test/src/test/java/org/tests/model/basic/ResetBasicData.java index 6a01db649..819bc406c 100644 --- a/ebean-test/src/test/java/org/tests/model/basic/ResetBasicData.java +++ b/ebean-test/src/test/java/org/tests/model/basic/ResetBasicData.java @@ -2,6 +2,7 @@ package org.tests.model.basic; import io.ebean.DB; import io.ebean.Database; +import io.ebean.Transaction; import org.tests.model.basic.Order.Status; import java.sql.Date; @@ -47,6 +48,7 @@ public class ResetBasicData { // the test rely on the products being in there return; } + Transaction.current().setBatchMode(false); //me.deleteAll(); me.insertCountries(); me.insertProducts(); @@ -56,100 +58,55 @@ public class ResetBasicData { } - public void deleteAll() { - DB.execute(() -> { + private void insertCountries() { + Country c = new Country(); + c.setCode("NZ"); + c.setName("New Zealand"); + server.save(c); - // orm update use bean name and bean properties - server.sqlUpdate("delete from o_cached_bean_child").execute(); - server.sqlUpdate("delete from o_cached_bean_country").execute(); - server.sqlUpdate("delete from o_cached_bean").execute(); - - server.createUpdate(OrderShipment.class, "delete from orderShipment").execute(); - - server.createUpdate(OrderDetail.class, "delete from orderDetail").execute(); - - server.createUpdate(Order.class, "delete from order").execute(); - - server.createUpdate(Contact.class, "delete from contact").execute(); - - server.createUpdate(Customer.class, "delete from Customer").execute(); - - server.createUpdate(Address.class, "delete from address").execute(); - - // sql update uses table and column names - server.sqlUpdate("delete from o_country").execute(); - server.sqlUpdate("delete from o_product").execute(); - - }); + Country au = new Country(); + au.setCode("AU"); + au.setName("Australia"); + server.save(au); } - public void insertCountries() { + private void insertProducts() { + Product p = new Product(); + p.setName("Chair"); + p.setSku("C001"); + server.save(p); - if (server.find(Country.class).findCount() > 0) { - return; - } + p = new Product(); + p.setName("Desk"); + p.setSku("DSK1"); + server.save(p); - server.execute(() -> { - Country c = new Country(); - c.setCode("NZ"); - c.setName("New Zealand"); - server.save(c); + p = new Product(); + p.setName("Computer"); + p.setSku("C002"); + server.save(p); - Country au = new Country(); - au.setCode("AU"); - au.setName("Australia"); - server.save(au); - }); + p = new Product(); + p.setName("Printer"); + p.setSku("C003"); + server.save(p); } + private void insertTestCustAndOrders() { + Customer cust1 = insertCustomer("Rob"); + Customer cust2 = insertCustomerNoAddress(); + insertCustomerFiona(); + insertCustomerNoContacts("NocCust"); - public void insertProducts() { - - if (server.find(Product.class).findCount() > 0) { - return; - } - server.execute(() -> { - Product p = new Product(); - p.setName("Chair"); - p.setSku("C001"); - server.save(p); - - p = new Product(); - p.setName("Desk"); - p.setSku("DSK1"); - server.save(p); - - p = new Product(); - p.setName("Computer"); - p.setSku("C002"); - server.save(p); - - p = new Product(); - p.setName("Printer"); - p.setSku("C003"); - server.save(p); - }); - } - - public void insertTestCustAndOrders() { - - DB.execute(() -> { - Customer cust1 = insertCustomer("Rob"); - Customer cust2 = insertCustomerNoAddress(); - insertCustomerFiona(); - insertCustomerNoContacts("NocCust"); - - createOrder1(cust1); - createOrder2(cust2); - createOrder3(cust1); - createOrder4(cust1); - createOrder5(cust2); - }); + createOrder1(cust1); + createOrder2(cust2); + createOrder3(cust1); + createOrder4(cust1); + createOrder5(cust2); } public static Customer createCustAndOrder(String custName) { - ResetBasicData me = new ResetBasicData(); Customer cust1 = insertCustomer(custName); me.createOrder1(cust1); @@ -157,7 +114,6 @@ public class ResetBasicData { } public static Order createOrderCustAndOrder(String custName) { - ResetBasicData me = new ResetBasicData(); Customer cust1 = insertCustomer(custName); return me.createOrder1(cust1); @@ -166,10 +122,8 @@ public class ResetBasicData { private static int contactEmailNum = 1; private Customer insertCustomerFiona() { - Customer c = createCustomer("Fiona", "12 Apple St", "West Coast Rd", 1, "2009-08-31"); c.setStatus(Customer.Status.ACTIVE); - c.addContact(createContact("Fiona", "Black")); c.addContact(createContact("Tracy", "Red")); @@ -185,22 +139,18 @@ public class ResetBasicData { } private Customer insertCustomerNoContacts(String name) { - Customer c = createCustomer("Roger", "15 Kumera Way", "Bos town", 1, "2010-04-10"); c.setName(name); c.setStatus(Customer.Status.ACTIVE); - DB.save(c); return c; } private Customer insertCustomerNoAddress() { - Customer c = new Customer(); c.setName("Cust NoAddress"); c.setStatus(Customer.Status.NEW); c.addContact(createContact("Jack", "Black")); - DB.save(c); return c; } @@ -216,7 +166,6 @@ public class ResetBasicData { } public static Customer createCustomer(String name, String shippingStreet, String billingStreet, int contactSuffix, String annDate) { - Customer c = new Customer(); c.setName(name); c.setStatus(Customer.Status.NEW); @@ -251,17 +200,14 @@ public class ResetBasicData { c.setBillingAddress(billingAddr); } - return c; } private Order createOrder1(Customer customer) { - Product product1 = DB.reference(Product.class, 1); Product product2 = DB.reference(Product.class, 2); Product product3 = DB.reference(Product.class, 3); - Order order = new Order(); order.setCustomer(customer); order.setOrderDate(Date.valueOf("2018-07-01")); @@ -271,16 +217,12 @@ public class ResetBasicData { details.add(new OrderDetail(product2, 3, 1.10)); details.add(new OrderDetail(product3, 1, 2.00)); order.setDetails(details); - - order.addShipment(new OrderShipment()); - DB.save(order); return order; } private void createOrder2(Customer customer) { - Product product1 = DB.reference(Product.class, 1); Order order = new Order(); @@ -291,14 +233,11 @@ public class ResetBasicData { List details = new ArrayList<>(); details.add(new OrderDetail(product1, 4, 10.50)); order.setDetails(details); - order.addShipment(new OrderShipment()); - DB.save(order); } private void createOrder3(Customer customer) { - Product product1 = DB.reference(Product.class, 1); Product product3 = DB.reference(Product.class, 3); @@ -312,30 +251,23 @@ public class ResetBasicData { details.add(new OrderDetail(product3, 40, 2.10)); details.add(new OrderDetail(product1, 5, 10.00)); order.setDetails(details); - order.addShipment(new OrderShipment()); - DB.save(order); } private void createOrder4(Customer customer) { - Order order = new Order(); order.setCustomer(customer); order.setOrderDate(Date.valueOf("2018-07-04")); - order.addShipment(new OrderShipment()); - DB.save(order); } private void createOrder5(Customer customer) { - Order order = new Order(); order.setCustomer(customer); order.setOrderDate(Date.valueOf("2018-06-28")); order.addShipment(new OrderShipment()); - DB.save(order); } } diff --git a/ebean-test/src/test/java/org/tests/model/elementcollection/EcblPerson2.java b/ebean-test/src/test/java/org/tests/model/elementcollection/EcblPerson2.java new file mode 100644 index 000000000..7ce82c393 --- /dev/null +++ b/ebean-test/src/test/java/org/tests/model/elementcollection/EcblPerson2.java @@ -0,0 +1,61 @@ +package org.tests.model.elementcollection; + +import io.ebean.annotation.Cache; + +import javax.persistence.*; +import java.util.ArrayList; +import java.util.List; + +@Cache +@Entity +public class EcblPerson2 { + + @Id + long id; + + String name; + + @ElementCollection + @CollectionTable(joinColumns = @JoinColumn(name = "person_id")) + List phoneNumbers = new ArrayList<>(); + + @Version + long version; + + @Override + public String toString() { + return "person id:" + id + " name:" + name + " phs:" + phoneNumbers; + } + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public List getPhoneNumbers() { + return phoneNumbers; + } + + public void setPhoneNumbers(List phoneNumbers) { + this.phoneNumbers = phoneNumbers; + } + + public long getVersion() { + return version; + } + + public void setVersion(long version) { + this.version = version; + } +} diff --git a/ebean-test/src/test/java/org/tests/model/elementcollection/TestElementCollectionEmbeddedListCache.java b/ebean-test/src/test/java/org/tests/model/elementcollection/TestElementCollectionEmbeddedListCache.java index f3ec4b24b..80e2d9bd1 100644 --- a/ebean-test/src/test/java/org/tests/model/elementcollection/TestElementCollectionEmbeddedListCache.java +++ b/ebean-test/src/test/java/org/tests/model/elementcollection/TestElementCollectionEmbeddedListCache.java @@ -9,10 +9,10 @@ import java.util.List; import static org.assertj.core.api.Assertions.assertThat; -public class TestElementCollectionEmbeddedListCache extends BaseTestCase { +class TestElementCollectionEmbeddedListCache extends BaseTestCase { @Test - public void test() { + void test() { EcblPerson person = new EcblPerson("CacheL"); person.getPhoneNumbers().add(new EcPhone("64", "021","1234")); @@ -86,12 +86,10 @@ public class TestElementCollectionEmbeddedListCache extends BaseTestCase { assertThat(four.getPhoneNumbers().toString()).contains("61-07-11"); assertThat(four.getPhoneNumbers()).hasSize(1); - DB.delete(four); sql = LoggedSql.collect(); assertThat(sql).hasSize(2); - LoggedSql.stop(); } } diff --git a/ebean-test/src/test/java/org/tests/model/elementcollection/TestElementCollectionEmbeddedListCache2.java b/ebean-test/src/test/java/org/tests/model/elementcollection/TestElementCollectionEmbeddedListCache2.java new file mode 100644 index 000000000..1a361c33b --- /dev/null +++ b/ebean-test/src/test/java/org/tests/model/elementcollection/TestElementCollectionEmbeddedListCache2.java @@ -0,0 +1,99 @@ +package org.tests.model.elementcollection; + +import io.ebean.BaseTestCase; +import io.ebean.DB; +import io.ebean.test.LoggedSql; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +class TestElementCollectionEmbeddedListCache2 extends BaseTestCase { + + @Test + void test() { + + EcblPerson2 person = new EcblPerson2(); + person.setName("CacheL"); + person.getPhoneNumbers().add(new EcPhone("64", "021","1234")); + person.getPhoneNumbers().add(new EcPhone("64","021","4321")); + DB.save(person); + + EcblPerson2 one = DB.find(EcblPerson2.class) + .setId(person.getId()) + .fetch("phoneNumbers") + .findOne(); + + LoggedSql.start(); + + one.getPhoneNumbers().size(); + + List sql = LoggedSql.collect(); + assertThat(sql).isEmpty(); + + EcblPerson2 two = DB.find(EcblPerson2.class ) + .setId(person.getId()) + .findOne(); + + two.getPhoneNumbers().size(); + assertThat(two.getPhoneNumbers().toString()).contains("64-021-1234", "64-021-4321"); + + sql = LoggedSql.collect(); + assertThat(sql).isEmpty(); // cache hit + + two.getPhoneNumbers().add(new EcPhone("61", "07", "11")); + two.getPhoneNumbers().remove(1); + + DB.save(two); + + sql = LoggedSql.collect(); + if (isPersistBatchOnCascade()) { + assertThat(sql).hasSize(5); // update of collection only + assertSql(sql.get(0)).contains("delete from ecbl_person2_phone_numbers where person_id=?"); + assertSqlBind(sql.get(1)); + assertSql(sql.get(2)).contains("insert into ecbl_person2_phone_numbers (person_id,country_code,area,phnum) values (?,?,?,?)"); + assertSqlBind(sql, 3, 4); + } else { + assertThat(sql).hasSize(3); // update of collection only + assertSql(sql.get(0)).contains("delete from ecbl_person2_phone_numbers where person_id=?"); + assertSql(sql.get(1)).contains("insert into ecbl_person2_phone_numbers (person_id,country_code,area,phnum) values (?,?,?,?)"); + assertSql(sql.get(2)).contains("insert into ecbl_person2_phone_numbers (person_id,country_code,area,phnum) values (?,?,?,?)"); + } + + EcblPerson2 three = DB.find(EcblPerson2.class ) + .setId(person.getId()) + .findOne(); + + assertThat(three.getPhoneNumbers().toString()).contains("61-07-11", "64-021-1234"); + assertThat(three.getPhoneNumbers()).hasSize(2); + + sql = LoggedSql.collect(); + assertThat(sql).isEmpty(); // cache hit + + + three.setName("mod-3"); + three.getPhoneNumbers().remove(0); + + DB.save(three); + + sql = LoggedSql.collect(); + assertThat(sql).hasSize(5); + + EcblPerson2 four = DB.find(EcblPerson2.class ) + .setId(person.getId()) + .findOne(); + + assertThat(four.getPhoneNumbers().toString()).contains("61-07-11"); + assertThat(four.getPhoneNumbers()).hasSize(1); + + + DB.delete(four); + sql = LoggedSql.collect(); + assertThat(sql).hasSize(2); + + + LoggedSql.stop(); + } +} diff --git a/ebean-test/src/test/java/org/tests/model/embedded/EmbArrayMaster.java b/ebean-test/src/test/java/org/tests/model/embedded/EmbArrayMaster.java new file mode 100644 index 000000000..09ff8312f --- /dev/null +++ b/ebean-test/src/test/java/org/tests/model/embedded/EmbArrayMaster.java @@ -0,0 +1,31 @@ +package org.tests.model.embedded; + +import io.ebean.annotation.DbArray; + +import javax.persistence.*; +import java.util.List; + +@Entity +public class EmbArrayMaster { + + @Embeddable + public static class EmbArrayDetail { + @DbArray + List vals; + + public EmbArrayDetail(List vals) { + this.vals = vals; + } + } + + @Id + int id; + + @ElementCollection + @CollectionTable(name = "test_array_detail", joinColumns = {@JoinColumn(name = "master_id")}) + List details; + + public EmbArrayMaster(List details) { + this.details = details; + } +} diff --git a/ebean-test/src/test/java/org/tests/model/embedded/TestEmbeddedDbArray.java b/ebean-test/src/test/java/org/tests/model/embedded/TestEmbeddedDbArray.java new file mode 100644 index 000000000..6bf121032 --- /dev/null +++ b/ebean-test/src/test/java/org/tests/model/embedded/TestEmbeddedDbArray.java @@ -0,0 +1,21 @@ +package org.tests.model.embedded; + +import io.ebean.DB; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +import static java.util.Collections.emptyList; +import static java.util.Collections.singletonList; + +class TestEmbeddedDbArray { + + /** + * Failing test case for #2477 + */ + @Disabled + @Test + void testArrayInsert() { + EmbArrayMaster t = new EmbArrayMaster(singletonList(new EmbArrayMaster.EmbArrayDetail(emptyList()))); + DB.insert(t); + } +} diff --git a/ebean-test/src/test/java/org/tests/model/m2m/MnyNode.java b/ebean-test/src/test/java/org/tests/model/m2m/MnyNode.java index edbd87cc0..fabb58040 100644 --- a/ebean-test/src/test/java/org/tests/model/m2m/MnyNode.java +++ b/ebean-test/src/test/java/org/tests/model/m2m/MnyNode.java @@ -33,7 +33,7 @@ public class MnyNode { joinColumns = @JoinColumn(name = "from_id", referencedColumnName = "id"), inverseJoinColumns = @JoinColumn(name = "to_id", referencedColumnName = "id")) @Where(clause = "${mta}.flags & 1 != 0") - @Where(clause = "BITAND(${mta}.flags, 1) != 0", platforms = Platform.H2) + @Where(clause = "BITAND(${mta}.flags, 1) != 0", platforms = {Platform.H2, Platform.ORACLE}) List bit1Relations; @ManyToMany @@ -41,7 +41,7 @@ public class MnyNode { joinColumns = @JoinColumn(name = "to_id", referencedColumnName = "id"), inverseJoinColumns = @JoinColumn(name = "from_id", referencedColumnName = "id")) @Where(clause = "${mta}.flags & 1 != 0") - @Where(clause = "BITAND(${mta}.flags, 1) != 0", platforms = Platform.H2) + @Where(clause = "BITAND(${mta}.flags, 1) != 0", platforms = {Platform.H2, Platform.ORACLE}) List bit1ReverseRelations; @ManyToMany @@ -49,7 +49,7 @@ public class MnyNode { joinColumns = @JoinColumn(name = "from_id", referencedColumnName = "id"), inverseJoinColumns = @JoinColumn(name = "to_id", referencedColumnName = "id")) @Where(clause = "${mta}.flags & 2 != 0") - @Where(clause = "BITAND(${mta}.flags, 2) != 0", platforms = Platform.H2) + @Where(clause = "BITAND(${mta}.flags, 2) != 0", platforms = {Platform.H2, Platform.ORACLE}) List bit2Relations; @ManyToMany @@ -57,7 +57,7 @@ public class MnyNode { joinColumns = @JoinColumn(name = "to_id", referencedColumnName = "id"), inverseJoinColumns = @JoinColumn(name = "from_id", referencedColumnName = "id")) @Where(clause = "${mta}.flags & 2 != 0") - @Where(clause = "BITAND(${mta}.flags, 2) != 0", platforms = Platform.H2) + @Where(clause = "BITAND(${mta}.flags, 2) != 0", platforms = {Platform.H2, Platform.ORACLE}) List bit2ReverseRelations; @ManyToMany diff --git a/ebean-test/src/test/java/org/tests/query/other/TestQuerySingleAttribute.java b/ebean-test/src/test/java/org/tests/query/other/TestQuerySingleAttribute.java index 592b10942..69d6ffa59 100644 --- a/ebean-test/src/test/java/org/tests/query/other/TestQuerySingleAttribute.java +++ b/ebean-test/src/test/java/org/tests/query/other/TestQuerySingleAttribute.java @@ -131,7 +131,7 @@ public class TestQuerySingleAttribute extends BaseTestCase { Query query = DB.find(Customer.class) .select("name") - .where().eq("id", 1).query(); + .where().eq("name", "Rob").query(); String name = query.findSingleAttribute(); @@ -248,7 +248,7 @@ public class TestQuerySingleAttribute extends BaseTestCase { String name = DB.find(Customer.class) .select("name") - .where().eq("id", 1) + .where().eq("name", "Rob") .findSingleAttribute(); assertThat(name).isNotNull(); @@ -646,7 +646,7 @@ public class TestQuerySingleAttribute extends BaseTestCase { .findSingleAttributeList(); assertThat(list1.get(0)).isInstanceOf(CountedValue.class); //assertThat(list1.toString()).isEqualTo("[1: Tracy, 3: Jim1, 1: Jack, 3: Fred1, 1: Fiona, 3: Bugs1]"); - + query = DB.find(Contact.class).select("firstName"); list1 = query .setCountDistinct(CountDistinctOrder.NO_ORDERING) diff --git a/ebean-test/src/test/java/org/tests/rawsql/inherit/ParentQueryTest.java b/ebean-test/src/test/java/org/tests/rawsql/inherit/ParentQueryTest.java index b3e879e71..5e45b8130 100644 --- a/ebean-test/src/test/java/org/tests/rawsql/inherit/ParentQueryTest.java +++ b/ebean-test/src/test/java/org/tests/rawsql/inherit/ParentQueryTest.java @@ -12,14 +12,12 @@ import java.util.ArrayList; import java.util.List; import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; -public class ParentQueryTest extends BaseTestCase { +class ParentQueryTest extends BaseTestCase { @Test - public void QueryParentCollectionFetch() { - + void queryParentCollectionFetch() { List exampleData = new ArrayList<>(); exampleData.add(new Data(0)); exampleData.add(new Data(1)); @@ -40,7 +38,7 @@ public class ParentQueryTest extends BaseTestCase { List partial = DB.find(Parent.class).where().ge("val", 1001).findList(); assertNotNull(partial.get(0).getData()); assertThat(partial.get(0).getMore()).startsWith("PQT-"); - assertEquals(partial.get(0).getData().get(0).getVal().intValue(), 0); + assertThat(partial.get(0).getData()).hasSize(3); } } diff --git a/ebean-test/src/test/java/org/tests/update/TestSqlUpdateUpsert.java b/ebean-test/src/test/java/org/tests/update/TestSqlUpdateUpsert.java index 9cd59bf23..515540ea3 100644 --- a/ebean-test/src/test/java/org/tests/update/TestSqlUpdateUpsert.java +++ b/ebean-test/src/test/java/org/tests/update/TestSqlUpdateUpsert.java @@ -39,7 +39,7 @@ public class TestSqlUpdateUpsert extends BaseTestCase { .setParameter("online", false); Object key2 = sqlUpdate2.executeGetKey(); - assertThat(key2).isNull(); + assertThat(key2).isEqualTo(key); EPersonOnline found2 = DB.find(EPersonOnline.class).where().eq("email", email).findOne(); diff --git a/ebean-test/src/test/java/org/tests/update/TestStatelessUpdate.java b/ebean-test/src/test/java/org/tests/update/TestStatelessUpdate.java index e8243bdd1..d03da5ffb 100644 --- a/ebean-test/src/test/java/org/tests/update/TestStatelessUpdate.java +++ b/ebean-test/src/test/java/org/tests/update/TestStatelessUpdate.java @@ -3,11 +3,13 @@ package org.tests.update; import io.ebean.DB; import io.ebean.TransactionalTestCase; import io.ebean.test.LoggedSql; +import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.tests.model.basic.Contact; import org.tests.model.basic.Customer; import org.tests.model.basic.EBasic; import org.tests.model.basic.EBasic.Status; +import org.tests.model.basic.ResetBasicData; import javax.persistence.EntityNotFoundException; import java.util.*; @@ -17,6 +19,11 @@ import static org.junit.jupiter.api.Assertions.*; public class TestStatelessUpdate extends TransactionalTestCase { + @BeforeAll + static void before() { + ResetBasicData.reset(); + } + @Test public void test() { diff --git a/ebean-test/src/test/java/org/tests/update/TestUpdatePartial.java b/ebean-test/src/test/java/org/tests/update/TestUpdatePartial.java index 978ef0589..e4fecaed8 100644 --- a/ebean-test/src/test/java/org/tests/update/TestUpdatePartial.java +++ b/ebean-test/src/test/java/org/tests/update/TestUpdatePartial.java @@ -3,14 +3,21 @@ package org.tests.update; import io.ebean.BaseTestCase; import io.ebean.DB; import io.ebean.SqlRow; +import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.tests.model.basic.Customer; +import org.tests.model.basic.ResetBasicData; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; public class TestUpdatePartial extends BaseTestCase { + @BeforeAll + static void before() { + ResetBasicData.reset(); + } + @Test public void test() { diff --git a/ebean-test/src/test/resources/ebean.properties b/ebean-test/src/test/resources/ebean.properties index 402facaab..d7e1debae 100644 --- a/ebean-test/src/test/resources/ebean.properties +++ b/ebean-test/src/test/resources/ebean.properties @@ -72,7 +72,7 @@ datasource.db.databaseDriver=org.h2.Driver datasource.h2.username=sa datasource.h2.password= -datasource.h2.url=jdbc:h2:mem:testsMem;DB_CLOSE_ON_EXIT=FALSE +datasource.h2.url=jdbc:h2:mem:testsMem;DB_CLOSE_ON_EXIT=FALSE;NON_KEYWORDS=KEY,VALUE datasource.h2.poolListener=org.tests.basic.MyTestDataSourcePoolListener #datasource.h2.minConnections=1 #datasource.h2.maxConnections=25 diff --git a/ebean-test/src/test/resources/test-ebean.properties b/ebean-test/src/test/resources/test-ebean.properties index 2405e5104..436499c1b 100644 --- a/ebean-test/src/test/resources/test-ebean.properties +++ b/ebean-test/src/test/resources/test-ebean.properties @@ -8,5 +8,5 @@ ebean.jodaLocalTimeMode=normal datasource.someotherdb.username=sa datasource.someotherdb.password= -datasource.someotherdb.databaseUrl=jdbc:h2:mem:someotherdb +datasource.someotherdb.databaseUrl=jdbc:h2:mem:someotherdb;NON_KEYWORDS=KEY,VALUE datasource.someotherdb.databaseDriver=org.h2.Driver diff --git a/pom.xml b/pom.xml index 235236630..d7b921e32 100644 --- a/pom.xml +++ b/pom.xml @@ -40,13 +40,14 @@ false 2.12.1 + 2.1.210 1.2 1.1 - 12.12.1 + 12.13.1 4.2 7.3 - 12.13.0-FOC4 - 12.12.3 + 12.14.1 + 12.14.1 @@ -86,7 +87,7 @@ com.h2database h2 - 1.4.199 + ${h2database.version} test diff --git a/tests/pom.xml b/tests/pom.xml index 617902bb0..af0755687 100644 --- a/tests/pom.xml +++ b/tests/pom.xml @@ -4,8 +4,8 @@ org.avaje java8-oss - 2.3 - + 3.3 + io.ebean diff --git a/tests/test-java16/pom.xml b/tests/test-java16/pom.xml index 7fd576605..7ac83574a 100644 --- a/tests/test-java16/pom.xml +++ b/tests/test-java16/pom.xml @@ -20,7 +20,7 @@ io.ebean ebean - 12.14.1-FOC1-SNAPSHOT + 12.14.2-SNAPSHOT @@ -32,7 +32,7 @@ io.ebean ebean-test - 12.14.1-FOC1-SNAPSHOT + 12.14.2-SNAPSHOT test @@ -58,7 +58,7 @@ io.ebean querybean-generator - 12.14.1-SNAPSHOT + 12.14.2-SNAPSHOT diff --git a/tests/test-kotlin/pom.xml b/tests/test-kotlin/pom.xml index 49177ad16..c4138a900 100644 --- a/tests/test-kotlin/pom.xml +++ b/tests/test-kotlin/pom.xml @@ -6,7 +6,7 @@ org.avaje java11-oss - 2.3 + 3.4 @@ -19,7 +19,7 @@ 11 11 - 12.13.0 + 12.14.1 1.6.0 2.12.1 true @@ -36,14 +36,14 @@ io.ebean ebean-test - 12.14.1-FOC1-SNAPSHOT + 12.14.2-SNAPSHOT test io.ebean ebean-core - 12.14.1-FOC1-SNAPSHOT + 12.14.2-SNAPSHOT test