diff --git a/src/main/java/com/avaje/ebean/DRawSqlParser.java b/src/main/java/com/avaje/ebean/DRawSqlParser.java index 4353403d7..33622f356 100644 --- a/src/main/java/com/avaje/ebean/DRawSqlParser.java +++ b/src/main/java/com/avaje/ebean/DRawSqlParser.java @@ -71,7 +71,7 @@ class DRawSqlParser { preFrom = trimSelectKeyword(preFrom); - return new Sql(sql.hashCode(), preFrom, preWhere, whereExprAnd, preHaving, havingExprAnd, orderByPrefix, orderBySql, (distinctPos > -1)); + return new Sql(sql, preFrom, preWhere, whereExprAnd, preHaving, havingExprAnd, orderByPrefix, orderBySql, (distinctPos > -1)); } /** diff --git a/src/main/java/com/avaje/ebean/RawSql.java b/src/main/java/com/avaje/ebean/RawSql.java index 51760d84f..048532af9 100644 --- a/src/main/java/com/avaje/ebean/RawSql.java +++ b/src/main/java/com/avaje/ebean/RawSql.java @@ -206,6 +206,14 @@ public final class RawSql implements Serializable { return sql; } + /** + * Return the key; + */ + public Key getKey() { + boolean parsed = sql != null && sql.parsed; + String unParsedSql = (sql == null) ? "" : sql.unparsedSql; + return new Key(parsed, unParsedSql, columnMapping); + } /** * Return the resultSet if this is a ResultSet based RawSql. @@ -221,16 +229,6 @@ public final class RawSql implements Serializable { return columnMapping; } - /** - * Return the hash for this query. - */ - public int queryHash() { - if (resultSet != null) { - return 31 * columnMapping.queryHash(); - } - return 31 * sql.queryHash() + columnMapping.queryHash(); - } - /** * Represents the sql part of the query. For parsed RawSql the sql is broken * up so that Ebean can insert extra WHERE and HAVING expressions into the @@ -260,13 +258,10 @@ public final class RawSql implements Serializable { private final boolean distinct; - private final int queryHashCode; - /** * Construct for unparsed SQL. */ protected Sql(String unparsedSql) { - this.queryHashCode = unparsedSql.hashCode(); this.parsed = false; this.unparsedSql = unparsedSql; this.preFrom = null; @@ -282,12 +277,11 @@ public final class RawSql implements Serializable { /** * Construct for parsed SQL. */ - protected Sql(int queryHashCode, String preFrom, String preWhere, boolean andWhereExpr, + protected Sql(String unparsedSql, String preFrom, String preWhere, boolean andWhereExpr, String preHaving, boolean andHavingExpr, String orderByPrefix, String orderBy, boolean distinct) { - this.queryHashCode = queryHashCode; + this.unparsedSql = unparsedSql; this.parsed = true; - this.unparsedSql = null; this.preFrom = preFrom; this.preHaving = preHaving; this.preWhere = preWhere; @@ -298,13 +292,6 @@ public final class RawSql implements Serializable { this.distinct = distinct; } - /** - * Return a hash for this query. - */ - public int queryHash() { - return queryHashCode; - } - public String toString() { if (!parsed) { return "unparsed[" + unparsedSql + "]"; @@ -406,13 +393,10 @@ public final class RawSql implements Serializable { private final boolean immutable; - private final int queryHashCode; - /** * Construct from parsed sql where the columns have been identified. */ protected ColumnMapping(List columns) { - this.queryHashCode = 0; this.immutable = false; this.parsed = true; this.propertyMap = null; @@ -428,7 +412,6 @@ public final class RawSql implements Serializable { * Construct for unparsed sql. */ protected ColumnMapping() { - this.queryHashCode = 0; this.immutable = false; this.parsed = false; this.propertyMap = null; @@ -443,17 +426,13 @@ public final class RawSql implements Serializable { this.immutable = false; this.parsed = false; this.propertyMap = null; - //this.propertyColumnMap = null; this.dbColumnMap = new LinkedHashMap(); - - int hc = 31; + int pos = 0; for (String prop : propertyNames) { - hc = 31 * hc + prop.hashCode(); dbColumnMap.put(prop, new Column(pos++, prop, null, prop)); } propertyColumnMap = dbColumnMap; - this.queryHashCode = hc; } /** @@ -464,20 +443,28 @@ public final class RawSql implements Serializable { this.parsed = parsed; this.dbColumnMap = dbColumnMap; - int hc = ColumnMapping.class.getName().hashCode(); - HashMap pcMap = new HashMap(); HashMap pMap = new HashMap(); for (Column c : dbColumnMap.values()) { pMap.put(c.getPropertyName(), c.getDbColumn()); pcMap.put(c.getPropertyName(), c); - hc = 31 * hc + ((c.getPropertyName() == null) ? 0 : c.getPropertyName().hashCode()); - hc = 31 * hc + ((c.getDbColumn() == null) ? 0 : c.getDbColumn().hashCode()); } this.propertyMap = Collections.unmodifiableMap(pMap); this.propertyColumnMap = Collections.unmodifiableMap(pcMap); - this.queryHashCode = hc; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + ColumnMapping that = (ColumnMapping) o; + return dbColumnMap.equals(that.dbColumnMap); + } + + @Override + public int hashCode() { + return dbColumnMap.hashCode(); } /** @@ -521,16 +508,6 @@ public final class RawSql implements Serializable { } } - /** - * Return the query hash for this column mapping. - */ - public int queryHash() { - if (queryHashCode == 0) { - throw new RuntimeException("Bug: queryHashCode == 0"); - } - return queryHashCode; - } - /** * Returns true if the Columns where supplied by parsing the sql select * clause. @@ -648,6 +625,27 @@ public final class RawSql implements Serializable { } } + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + Column that = (Column) o; + if (indexPos != that.indexPos) return false; + if (!dbColumn.equals(that.dbColumn)) return false; + if (dbAlias != null ? !dbAlias.equals(that.dbAlias) : that.dbAlias != null) return false; + return propertyName != null ? propertyName.equals(that.propertyName) : that.propertyName == null; + } + + @Override + public int hashCode() { + int result = indexPos; + result = 31 * result + dbColumn.hashCode(); + result = 31 * result + (dbAlias != null ? dbAlias.hashCode() : 0); + result = 31 * result + (propertyName != null ? propertyName.hashCode() : 0); + return result; + } + public String toString() { return dbColumn + "->" + propertyName; } @@ -699,4 +697,39 @@ public final class RawSql implements Serializable { } } } + + /** + * A key for the RawSql object using for the query plan. + */ + public static final class Key { + + private final boolean parsed; + private final ColumnMapping columnMapping; + private final String unParsedSql; + + Key(boolean parsed, String unParsedSql, ColumnMapping columnMapping) { + this.parsed = parsed; + this.unParsedSql = unParsedSql; + this.columnMapping = columnMapping; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + Key that = (Key) o; + return parsed == that.parsed + && columnMapping.equals(that.columnMapping) + && unParsedSql.equals(that.unParsedSql); + } + + @Override + public int hashCode() { + int result = (parsed ? 1 : 0); + result = 31 * result + columnMapping.hashCode(); + result = 31 * result + unParsedSql.hashCode(); + return result; + } + } } diff --git a/src/main/java/com/avaje/ebeaninternal/server/querydefn/DefaultOrmQuery.java b/src/main/java/com/avaje/ebeaninternal/server/querydefn/DefaultOrmQuery.java index 0c07ea385..83c6b4f1e 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/querydefn/DefaultOrmQuery.java +++ b/src/main/java/com/avaje/ebeaninternal/server/querydefn/DefaultOrmQuery.java @@ -760,18 +760,9 @@ public class DefaultOrmQuery implements SpiQuery { */ CQueryPlanKey createQueryPlanKey() { - DefaultExpressionList where = null; - DefaultExpressionList having = null; - if (whereExpressions != null) { - where = whereExpressions.copyForPlanKey(); - } - if (havingExpressions != null) { - having = havingExpressions.copyForPlanKey(); - } - queryPlanKey = new OrmQueryPlanKey(includeTableJoin, type, detail, maxRows, firstRow, disableLazyLoading, rawWhereClause, orderBy, query, additionalWhere, additionalHaving, - distinct, sqlDistinct, mapKey, id, bindParams, where, having, + distinct, sqlDistinct, mapKey, id, bindParams, whereExpressions, havingExpressions, temporalMode, forUpdate, rootTableAlias, rawSql); return queryPlanKey; diff --git a/src/main/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryPlanKey.java b/src/main/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryPlanKey.java index 7f7f7a41e..616d8b962 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryPlanKey.java +++ b/src/main/java/com/avaje/ebeaninternal/server/querydefn/OrmQueryPlanKey.java @@ -17,12 +17,10 @@ public class OrmQueryPlanKey implements CQueryPlanKey { private final TableJoin includeTableJoin; private final String orderByAsSting; private final OrmQueryDetail detail; - private final BindParams bindParams; - private final SpiExpression whereExpressions; - private final SpiExpression havingExpressions; - private final RawSql rawSql; + private final SpiExpression where; + private final SpiExpression having; + private final RawSql.Key rawSqlKey; private final boolean hasIdValue; - private final SpiQuery.Type type; private final int maxRows; private final int firstRow; @@ -58,13 +56,12 @@ public class OrmQueryPlanKey implements CQueryPlanKey { this.sqlDistinct = sqlDistinct; this.mapKey = mapKey; this.hasIdValue = (id != null); - this.bindParams = bindParams; - this.whereExpressions = whereExpressions; - this.havingExpressions = havingExpressions; + this.where = (whereExpressions == null) ? null : whereExpressions.copyForPlanKey(); + this.having = (havingExpressions == null) ? null : havingExpressions.copyForPlanKey(); this.temporalMode = temporalMode; this.forUpdate = forUpdate; this.rootTableAlias = rootTableAlias; - this.rawSql = rawSql; + this.rawSqlKey = (rawSql == null) ? null : rawSql.getKey(); // exclude bind values and things unrelated to the sql being generated HashQueryPlanBuilder builder = new HashQueryPlanBuilder(); @@ -78,7 +75,7 @@ public class OrmQueryPlanKey implements CQueryPlanKey { builder.add(disableLazyLoading); builder.add(hasIdValue); builder.add(temporalMode); - builder.add(rawSql == null ? 0 : rawSql.queryHash()); + builder.add(rawSqlKey == null ? 0 : rawSqlKey.hashCode()); builder.add(includeTableJoin != null ? includeTableJoin.queryHash() : 0); builder.add(rootTableAlias); @@ -88,11 +85,11 @@ public class OrmQueryPlanKey implements CQueryPlanKey { if (bindParams != null) { bindParams.buildQueryPlanHash(builder); } - if (whereExpressions != null) { - whereExpressions.queryPlanHash(builder); + if (where != null) { + where.queryPlanHash(builder); } - if (havingExpressions != null) { - havingExpressions.queryPlanHash(builder); + if (having != null) { + having.queryPlanHash(builder); } this.planHash = builder.getPlanHash(); @@ -118,11 +115,9 @@ public class OrmQueryPlanKey implements CQueryPlanKey { if (planHash != that.planHash) return false; if (bindCount != that.bindCount) return false; - if (maxRows != that.maxRows) return false; if (firstRow != that.firstRow) return false; if (disableLazyLoading != that.disableLazyLoading) return false; - if (distinct != that.distinct) return false; if (sqlDistinct != that.sqlDistinct) return false; if (forUpdate != that.forUpdate) return false; @@ -131,13 +126,11 @@ public class OrmQueryPlanKey implements CQueryPlanKey { if (temporalMode != that.temporalMode) return false; if (includeTableJoin != null ? !includeTableJoin.equals(that.includeTableJoin) : that.includeTableJoin != null) return false; if (orderByAsSting != null ? !orderByAsSting.equals(that.orderByAsSting) : that.orderByAsSting != null) return false; - - if (whereExpressions != null ? !whereExpressions.isSameByPlan(that.whereExpressions) : that.whereExpressions != null) return false; - if (havingExpressions != null ? !havingExpressions.isSameByPlan(that.havingExpressions) : that.havingExpressions != null) return false; + if (where != null ? !where.isSameByPlan(that.where) : that.where != null) return false; + if (having != null ? !having.isSameByPlan(that.having) : that.having != null) return false; + if (rawSqlKey != null ? !rawSqlKey.equals(that.rawSqlKey) : that.rawSqlKey != null) return false; // if (detail != null ? !detail.equals(that.detail) : that.detail != null) return false; -// if (bindParams != null ? !bindParams.equals(that.bindParams) : that.bindParams != null) return false; -// if (rawSql != null ? !rawSql.equals(that.rawSql) : that.rawSql != null) return false; if (rawWhereClause != null ? !rawWhereClause.equals(that.rawWhereClause) : that.rawWhereClause != null) return false; if (query != null ? !query.equals(that.query) : that.query != null) return false; diff --git a/src/test/java/com/avaje/ebean/ColumnMappingTest.java b/src/test/java/com/avaje/ebean/ColumnMappingTest.java new file mode 100644 index 000000000..6c77bd2da --- /dev/null +++ b/src/test/java/com/avaje/ebean/ColumnMappingTest.java @@ -0,0 +1,65 @@ +package com.avaje.ebean; + +import org.junit.Test; + +import java.util.Arrays; + +import static org.assertj.core.api.StrictAssertions.assertThat; + +public class ColumnMappingTest { + + RawSql.ColumnMapping.Column col(int indexPos, String dbColumn, String dbAlias) { + return new RawSql.ColumnMapping.Column(indexPos, dbColumn, dbAlias); + } + + RawSql.ColumnMapping mapping(RawSql.ColumnMapping.Column... cols) { + return new RawSql.ColumnMapping(Arrays.asList(cols)); + } + + @Test + public void equals_same() { + + RawSql.ColumnMapping mapping1 = mapping(col(1, "id", null), col(2, "name", null)); + RawSql.ColumnMapping mapping2 = mapping(col(1, "id", null), col(2, "name", null)); + + assertSame(mapping1, mapping2); + } + + @Test + public void equals_diffPropertyName() { + + RawSql.ColumnMapping mapping1 = mapping(col(1, "id", null), col(2, "name", null)); + RawSql.ColumnMapping mapping2 = mapping(col(1, "id", null), col(2, "diff", null)); + + assertDifferent(mapping1, mapping2); + } + + @Test + public void equals_moreColumns() { + + RawSql.ColumnMapping mapping1 = mapping(col(1, "id", null), col(2, "name", null)); + RawSql.ColumnMapping mapping2 = mapping(col(1, "id", null), col(2, "name", null), col(2, "diff", null)); + + assertDifferent(mapping1, mapping2); + } + + + @Test + public void equals_lessColumns() { + + RawSql.ColumnMapping mapping1 = mapping(col(1, "id", null), col(2, "name", null)); + RawSql.ColumnMapping mapping2 = mapping(col(1, "id", null)); + + assertDifferent(mapping1, mapping2); + } + + private void assertSame(Object key, Object key1) { + assertThat(key).isEqualTo(key1); + assertThat(key.hashCode()).isEqualTo(key1.hashCode()); + } + + private void assertDifferent(Object key, Object key1) { + assertThat(key).isNotEqualTo(key1); + assertThat(key.hashCode()).isNotEqualTo(key1.hashCode()); + } +} \ No newline at end of file diff --git a/src/test/java/com/avaje/ebean/ColumnTest.java b/src/test/java/com/avaje/ebean/ColumnTest.java new file mode 100644 index 000000000..d8c9f7d19 --- /dev/null +++ b/src/test/java/com/avaje/ebean/ColumnTest.java @@ -0,0 +1,58 @@ +package com.avaje.ebean; + + +import org.junit.Test; + +import static org.assertj.core.api.StrictAssertions.assertThat; + +public class ColumnTest { + + RawSql.ColumnMapping.Column col(int indexPos, String dbColumn, String dbAlias) { + return new RawSql.ColumnMapping.Column(indexPos, dbColumn, dbAlias); + } + + @Test + public void equals_when_same() { + assertSame(col(1, "name", null),col(1, "name", null)); + } + + @Test + public void equals_when_same_withAlias() { + assertSame(col(1, "t0.name", "t0"),col(1, "t0.name", "t0")); + } + + @Test + public void equals_when_diffIndex() { + assertDifferent(col(1, "name", null),col(2, "name", null)); + } + + @Test + public void equals_when_diffProperty() { + assertDifferent(col(1, "name", null),col(1, "diffName", null)); + } + + @Test + public void equals_when_diffAlias() { + assertDifferent(col(1, "t1.name", "t1"),col(1, "t1.name", "t0")); + } + + @Test + public void equals_when_diffAliasNullLast() { + assertDifferent(col(1, "t1.name", "t1"),col(1, "t1.name", null)); + } + @Test + public void equals_when_diffAliasNullFirst() { + assertDifferent(col(1, "t1.name", null),col(1, "t1.name", "t1")); + } + + + private void assertSame(Object key, Object key1) { + assertThat(key).isEqualTo(key1); + assertThat(key.hashCode()).isEqualTo(key1.hashCode()); + } + + private void assertDifferent(Object key, Object key1) { + assertThat(key).isNotEqualTo(key1); + assertThat(key.hashCode()).isNotEqualTo(key1.hashCode()); + } +} \ No newline at end of file diff --git a/src/test/java/com/avaje/ebean/RawSqlKeyTest.java b/src/test/java/com/avaje/ebean/RawSqlKeyTest.java new file mode 100644 index 000000000..c618a2d27 --- /dev/null +++ b/src/test/java/com/avaje/ebean/RawSqlKeyTest.java @@ -0,0 +1,64 @@ +package com.avaje.ebean; + + +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class RawSqlKeyTest { + + @Test + public void equals_when_sameParsedSql() { + + RawSql.Key key = RawSqlBuilder.parse("select id from customer").create().getKey(); + RawSql.Key key1 = RawSqlBuilder.parse("select id from customer").create().getKey(); + + assertSame(key, key1); + } + + @Test + public void equals_when_diffParsedSql() { + + RawSql.Key key = RawSqlBuilder.parse("select id from customer").create().getKey(); + RawSql.Key key1 = RawSqlBuilder.parse("select name from customer").create().getKey(); + + assertDifferent(key, key1); + } + + @Test + public void equals_when_sameColumnMapping() { + + RawSql.Key key = RawSqlBuilder.parse("select id from customer").columnMapping("id","b").create().getKey(); + RawSql.Key key1 = RawSqlBuilder.parse("select id from customer").columnMapping("id","b").create().getKey(); + + assertSame(key, key1); + } + + @Test + public void equals_when_diffColumnMapping() { + + RawSql.Key key = RawSqlBuilder.parse("select a from customer").columnMapping("a","b").create().getKey(); + RawSql.Key key1 = RawSqlBuilder.parse("select a from customer").columnMapping("a","c").create().getKey(); + + assertDifferent(key, key1); + } + + @Test + public void equals_when_parseToUnpased() { + + RawSql.Key key = RawSqlBuilder.parse("select a from customer").columnMapping("a","b").create().getKey(); + RawSql.Key key1 = RawSqlBuilder.unparsed("select a from customer").columnMapping("a","c").create().getKey(); + + assertDifferent(key, key1); + } + + private void assertSame(RawSql.Key key, RawSql.Key key1) { + assertThat(key).isEqualTo(key1); + assertThat(key.hashCode()).isEqualTo(key1.hashCode()); + } + + private void assertDifferent(RawSql.Key key, RawSql.Key key1) { + assertThat(key).isNotEqualTo(key1); + assertThat(key.hashCode()).isNotEqualTo(key1.hashCode()); + } +} \ No newline at end of file