From f02a552cdfdddedad0362d1dc86601c92e448784 Mon Sep 17 00:00:00 2001 From: rob bygrave Date: Wed, 4 Oct 2017 23:03:12 +1300 Subject: [PATCH] #1150 - Refactor convert RawSql and RawSqlBuilder into interfaces and push the parsers into io.ebeaninternal --- src/main/java/io/ebean/Query.java | 9 +- src/main/java/io/ebean/RawSql.java | 562 +----------------- src/main/java/io/ebean/RawSqlBuilder.java | 66 +- src/main/java/io/ebean/XServiceProvider.java | 30 + .../io/ebean/plugin/SpiRawSqlService.java | 27 + .../java/io/ebeaninternal/api/SpiQuery.java | 6 + .../server/core/DefaultServer.java | 44 +- .../server/deploy/BeanDescriptor.java | 8 +- .../deploy/meta/DeployBeanDescriptor.java | 10 +- .../server/deploy/parse/DeployBeanInfo.java | 3 +- .../server/query/CQueryBuilder.java | 13 +- .../server/query/CQueryBuilderRawSql.java | 8 +- .../server/query/CQueryPlanRawSql.java | 2 +- .../server/query/CQueryPredicates.java | 4 +- .../server/querydefn/DefaultOrmQuery.java | 36 +- .../server/querydefn/OrmQueryPlanKey.java | 6 +- .../ebeaninternal/server/rawsql/DRawSql.java | 65 ++ .../server/rawsql/DRawSqlBuilder.java | 43 ++ .../server/rawsql}/DRawSqlColumnsParser.java | 4 +- .../server/rawsql}/DRawSqlParser.java | 4 +- .../server/rawsql/DRawSqlService.java | 31 + .../server/rawsql/SpiRawSql.java | 536 +++++++++++++++++ .../services/io.ebean.plugin.SpiRawSqlService | 1 + src/test/java/io/ebean/ColumnMappingTest.java | 66 -- src/test/java/io/ebean/ColumnTest.java | 6 +- src/test/java/io/ebean/RawSqlKeyTest.java | 66 -- .../server/rawsql/ColumnMappingTest.java | 65 ++ .../server/rawsql/RawSqlKeyTest.java | 74 +++ .../server/rawsql}/TestRawSqlBuilder.java | 83 ++- .../rawsql}/TestRawSqlBuilderDistinct.java | 9 +- .../rawsql}/TestRawSqlColumnParsing.java | 6 +- .../server/rawsql/TestRawSqlParsing.java | 4 +- .../tests/rawsql/TestRawSqlWithResultSet.java | 3 +- 33 files changed, 994 insertions(+), 906 deletions(-) create mode 100644 src/main/java/io/ebean/XServiceProvider.java create mode 100644 src/main/java/io/ebean/plugin/SpiRawSqlService.java create mode 100644 src/main/java/io/ebeaninternal/server/rawsql/DRawSql.java create mode 100644 src/main/java/io/ebeaninternal/server/rawsql/DRawSqlBuilder.java rename src/main/java/io/{ebean => ebeaninternal/server/rawsql}/DRawSqlColumnsParser.java (96%) rename src/main/java/io/{ebean => ebeaninternal/server/rawsql}/DRawSqlParser.java (98%) create mode 100644 src/main/java/io/ebeaninternal/server/rawsql/DRawSqlService.java create mode 100644 src/main/java/io/ebeaninternal/server/rawsql/SpiRawSql.java create mode 100644 src/main/resources/META-INF/services/io.ebean.plugin.SpiRawSqlService delete mode 100644 src/test/java/io/ebean/ColumnMappingTest.java delete mode 100644 src/test/java/io/ebean/RawSqlKeyTest.java create mode 100644 src/test/java/io/ebeaninternal/server/rawsql/ColumnMappingTest.java create mode 100644 src/test/java/io/ebeaninternal/server/rawsql/RawSqlKeyTest.java rename src/test/java/io/{ebean => ebeaninternal/server/rawsql}/TestRawSqlBuilder.java (73%) rename src/test/java/io/{ebean => ebeaninternal/server/rawsql}/TestRawSqlBuilderDistinct.java (62%) rename src/test/java/io/{ebean => ebeaninternal/server/rawsql}/TestRawSqlColumnParsing.java (96%) diff --git a/src/main/java/io/ebean/Query.java b/src/main/java/io/ebean/Query.java index 9d383ed8d..60a0c341f 100644 --- a/src/main/java/io/ebean/Query.java +++ b/src/main/java/io/ebean/Query.java @@ -215,11 +215,6 @@ public interface Query { SKIPLOCKED } - /** - * Return the RawSql that was set to use for this query. - */ - RawSql getRawSql(); - /** * Set RawSql to use for this query. */ @@ -1328,14 +1323,14 @@ public interface Query { * Set the {@link CacheMode} to use the query for executing this query. */ Query setUseQueryCache(CacheMode useQueryCache); - + /** * Calls {@link #setUseQueryCache(CacheMode)} with ON or OFF. */ default Query setUseQueryCache(boolean enabled) { return setUseQueryCache(enabled ? CacheMode.ON : CacheMode.OFF); } - + /** * Set to true if this query should execute against the doc store. *

diff --git a/src/main/java/io/ebean/RawSql.java b/src/main/java/io/ebean/RawSql.java index 937161e2a..0c52bb83e 100644 --- a/src/main/java/io/ebean/RawSql.java +++ b/src/main/java/io/ebean/RawSql.java @@ -169,566 +169,6 @@ import java.util.Map; * Note that lazy loading also works with object graphs built with RawSql. *

*/ -public final class RawSql implements Serializable { +public interface RawSql { - private static final long serialVersionUID = 1L; - - private final ResultSet resultSet; - - private final Sql sql; - - private final ColumnMapping columnMapping; - - /** - * Construct with a ResultSet and properties that the columns map to. - *

- * The properties listed in the propertyNames must be in the same order as the columns in the - * resultSet. - *

- * When a query executes this RawSql object then it will close the resultSet. - */ - public RawSql(ResultSet resultSet, String... propertyNames) { - this.resultSet = resultSet; - this.sql = null; - this.columnMapping = new ColumnMapping(propertyNames); - } - - protected RawSql(ResultSet resultSet, Sql sql, ColumnMapping columnMapping) { - this.resultSet = resultSet; - this.sql = sql; - this.columnMapping = columnMapping; - } - - /** - * Return the Sql either unparsed or in parsed (broken up) form. - */ - public Sql getSql() { - 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. - */ - public ResultSet getResultSet() { - return resultSet; - } - - /** - * Return the column mapping for the SQL columns to bean properties. - */ - public ColumnMapping getColumnMapping() { - return columnMapping; - } - - /** - * 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 - * SQL. - */ - public static final class Sql implements Serializable { - - private static final long serialVersionUID = 1L; - - private final boolean parsed; - - private final String unparsedSql; - - private final String preFrom; - - private final String preWhere; - - private final boolean andWhereExpr; - - private final String preHaving; - - private final boolean andHavingExpr; - - private final String orderByPrefix; - - private final String orderBy; - - private final boolean distinct; - - /** - * Construct for unparsed SQL. - */ - protected Sql(String unparsedSql) { - this.parsed = false; - this.unparsedSql = unparsedSql; - this.preFrom = null; - this.preHaving = null; - this.preWhere = null; - this.andHavingExpr = false; - this.andWhereExpr = false; - this.orderByPrefix = null; - this.orderBy = null; - this.distinct = false; - } - - /** - * Construct for parsed SQL. - */ - protected Sql(String unparsedSql, String preFrom, String preWhere, boolean andWhereExpr, - String preHaving, boolean andHavingExpr, String orderByPrefix, String orderBy, boolean distinct) { - - this.unparsedSql = unparsedSql; - this.parsed = true; - this.preFrom = preFrom; - this.preHaving = preHaving; - this.preWhere = preWhere; - this.andHavingExpr = andHavingExpr; - this.andWhereExpr = andWhereExpr; - this.orderByPrefix = orderByPrefix; - this.orderBy = orderBy; - this.distinct = distinct; - } - - @Override - public String toString() { - if (!parsed) { - return "unparsed[" + unparsedSql + "]"; - } - return "select[" + preFrom + "] preWhere[" + preWhere + "] preHaving[" + preHaving + "] orderBy[" + orderBy + "]"; - } - - public boolean isDistinct() { - return distinct; - } - - /** - * Return true if the SQL is left completely unmodified. - *

- * This means Ebean can't add WHERE or HAVING expressions into the query - - * it will be left completely unmodified. - *

- */ - public boolean isParsed() { - return parsed; - } - - /** - * Return the SQL when it is unparsed. - */ - public String getUnparsedSql() { - return unparsedSql; - } - - /** - * Return the SQL prior to FROM clause. - */ - public String getPreFrom() { - return preFrom; - } - - /** - * Return the SQL prior to WHERE clause. - */ - public String getPreWhere() { - return preWhere; - } - - /** - * Return true if there is already a WHERE clause and any extra where - * expressions start with AND. - */ - public boolean isAndWhereExpr() { - return andWhereExpr; - } - - /** - * Return the SQL prior to HAVING clause. - */ - public String getPreHaving() { - return preHaving; - } - - /** - * Return true if there is already a HAVING clause and any extra having - * expressions start with AND. - */ - public boolean isAndHavingExpr() { - return andHavingExpr; - } - - /** - * Return the 'order by' keywords. - * This can contain additional keywords, for example 'order siblings by' as Oracle syntax. - */ - public String getOrderByPrefix() { - return (orderByPrefix == null) ? "order by" : orderByPrefix; - } - - /** - * Return the SQL ORDER BY clause. - */ - public String getOrderBy() { - return orderBy; - } - - } - - /** - * Defines the column mapping for raw sql DB columns to bean properties. - */ - public static final class ColumnMapping implements Serializable { - - private static final long serialVersionUID = 1L; - - private final LinkedHashMap dbColumnMap; - - private final Map propertyMap; - - private final Map propertyColumnMap; - - private final boolean parsed; - - private final boolean immutable; - - /** - * Construct from parsed sql where the columns have been identified. - */ - protected ColumnMapping(List columns) { - this.immutable = false; - this.parsed = true; - this.propertyMap = null; - this.propertyColumnMap = null; - this.dbColumnMap = new LinkedHashMap<>(); - for (Column c : columns) { - dbColumnMap.put(c.getDbColumnKey(), c); - } - } - - /** - * Construct for unparsed sql. - */ - protected ColumnMapping() { - this.immutable = false; - this.parsed = false; - this.propertyMap = null; - this.propertyColumnMap = null; - this.dbColumnMap = new LinkedHashMap<>(); - } - - /** - * Construct for ResultSet use. - */ - protected ColumnMapping(String... propertyNames) { - this.immutable = false; - this.parsed = false; - this.propertyMap = null; - this.dbColumnMap = new LinkedHashMap<>(); - - int pos = 0; - for (String prop : propertyNames) { - dbColumnMap.put(prop, new Column(pos++, prop, null, prop)); - } - propertyColumnMap = dbColumnMap; - } - - /** - * Construct an immutable ColumnMapping based on collected information. - */ - protected ColumnMapping(boolean parsed, LinkedHashMap dbColumnMap) { - this.immutable = true; - this.parsed = parsed; - this.dbColumnMap = dbColumnMap; - - HashMap pcMap = new HashMap<>(); - HashMap pMap = new HashMap<>(); - - for (Column c : dbColumnMap.values()) { - pMap.put(c.getPropertyName(), c.getDbColumn()); - pcMap.put(c.getPropertyName(), c); - } - this.propertyMap = Collections.unmodifiableMap(pMap); - this.propertyColumnMap = Collections.unmodifiableMap(pcMap); - } - - @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(); - } - - /** - * Return true if the property is mapped. - */ - public boolean contains(String property) { - return this.propertyColumnMap.containsKey(property); - } - - /** - * Creates an immutable copy of this ColumnMapping. - * - * @throws IllegalStateException when a propertyName has not been defined for a column. - */ - protected ColumnMapping createImmutableCopy() { - - for (Column c : dbColumnMap.values()) { - c.checkMapping(); - } - - return new ColumnMapping(parsed, dbColumnMap); - } - - protected void columnMapping(String dbColumn, String propertyName) { - - if (immutable) { - throw new IllegalStateException("Should never happen"); - } - if (!parsed) { - int pos = dbColumnMap.size(); - dbColumnMap.put(dbColumn, new Column(pos, dbColumn, null, propertyName)); - } else { - Column column = dbColumnMap.get(dbColumn); - if (column == null) { - String msg = "DB Column [" + dbColumn + "] not found in mapping. Expecting one of [" + dbColumnMap.keySet() + "]"; - throw new IllegalArgumentException(msg); - } - column.setPropertyName(propertyName); - } - } - - /** - * Returns true if the Columns where supplied by parsing the sql select - * clause. - *

- * In the case where the columns where parsed then we can do extra checks on - * the column mapping such as, is the column a valid one in the sql and - * whether all the columns in the sql have been mapped. - *

- */ - public boolean isParsed() { - return parsed; - } - - /** - * Return the number of columns in this column mapping. - */ - public int size() { - return dbColumnMap.size(); - } - - /** - * Return the column mapping. - */ - protected Map mapping() { - return dbColumnMap; - } - - /** - * Return the mapping by DB column. - */ - public Map getMapping() { - return propertyMap; - } - - /** - * Return the index position by bean property name. - */ - public int getIndexPosition(String property) { - Column c = propertyColumnMap.get(property); - return c == null ? -1 : c.getIndexPos(); - } - - /** - * Return an iterator of the Columns. - */ - public Iterator getColumns() { - return dbColumnMap.values().iterator(); - } - - /** - * Modify any column mappings with the given table alias to have the path prefix. - *

- * For example modify all mappings with table alias "c" to have the path prefix "customer". - *

- *

- * For the "Root type" you don't need to specify a tableAliasMapping. - *

- */ - public void tableAliasMapping(String tableAlias, String path) { - - String startMatch = tableAlias + "."; - for (Map.Entry entry : dbColumnMap.entrySet()) { - if (entry.getKey().startsWith(startMatch)) { - entry.getValue().tableAliasMapping(path); - } - } - } - - /** - * A Column of the RawSql that is mapped to a bean property (or ignored). - */ - public static class Column implements Serializable { - - private static final long serialVersionUID = 1L; - private final int indexPos; - private final String dbColumn; - - private final String dbAlias; - - private String propertyName; - - /** - * Construct a Column. - */ - public Column(int indexPos, String dbColumn, String dbAlias) { - this(indexPos, dbColumn, dbAlias, derivePropertyName(dbAlias, dbColumn)); - } - - private Column(int indexPos, String dbColumn, String dbAlias, String propertyName) { - this.indexPos = indexPos; - this.dbColumn = dbColumn; - this.dbAlias = dbAlias; - if (propertyName == null && dbAlias != null) { - this.propertyName = dbAlias; - } else { - this.propertyName = propertyName; - } - } - - protected static String derivePropertyName(String dbAlias, String dbColumn) { - if (dbAlias != null) { - return CamelCaseHelper.toCamelFromUnderscore(dbAlias); - } - int dotPos = dbColumn.indexOf('.'); - if (dotPos > -1) { - dbColumn = dbColumn.substring(dotPos + 1); - } - return CamelCaseHelper.toCamelFromUnderscore(dbColumn); - } - - private void checkMapping() { - if (propertyName == null) { - String msg = "No propertyName defined (Column mapping) for dbColumn [" + dbColumn + "]"; - throw new IllegalStateException(msg); - } - } - - @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 = 92821 * result + dbColumn.hashCode(); - result = 92821 * result + (dbAlias != null ? dbAlias.hashCode() : 0); - result = 92821 * result + (propertyName != null ? propertyName.hashCode() : 0); - return result; - } - - @Override - public String toString() { - return dbColumn + "->" + propertyName; - } - - /** - * Return the index position of this column. - */ - public int getIndexPos() { - return indexPos; - } - - /** - * Return the DB column alias if specified otherwise DB column. - * This is used as the key for mapping a column to a logical property. - */ - public String getDbColumnKey() { - return (dbAlias != null) ? dbAlias : dbColumn; - } - - /** - * Return the DB column name including table alias (if it has one). - */ - public String getDbColumn() { - return dbColumn; - } - - /** - * Return the bean property this column is mapped to. - */ - public String getPropertyName() { - return propertyName; - } - - /** - * Set the property name mapped to this db column. - */ - private void setPropertyName(String propertyName) { - this.propertyName = propertyName; - } - - /** - * Prepend the path to the property name. - *

- * For example if path is "customer" then "name" becomes "customer.name". - */ - public void tableAliasMapping(String path) { - if (path != null) { - propertyName = path + "." + propertyName; - } - } - } - } - - /** - * 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 = 92821 * result + columnMapping.hashCode(); - result = 92821 * result + unParsedSql.hashCode(); - return result; - } - } } diff --git a/src/main/java/io/ebean/RawSqlBuilder.java b/src/main/java/io/ebean/RawSqlBuilder.java index cc05bdfdb..4cebe5832 100644 --- a/src/main/java/io/ebean/RawSqlBuilder.java +++ b/src/main/java/io/ebean/RawSqlBuilder.java @@ -1,8 +1,5 @@ package io.ebean; -import io.ebean.RawSql.ColumnMapping; -import io.ebean.RawSql.Sql; - import java.sql.ResultSet; /** @@ -14,18 +11,7 @@ import java.sql.ResultSet; * * @see RawSql */ -public class RawSqlBuilder { - - /** - * Special property name assigned to a DB column that should be ignored. - */ - public static final String IGNORE_COLUMN = "$$_IGNORE_COLUMN_$$"; - - private final ResultSet resultSet; - - private final Sql sql; - - private final ColumnMapping columnMapping; +public interface RawSqlBuilder { /** * Create and return a RawSql object based on the resultSet and list of properties the columns in @@ -34,8 +20,8 @@ public class RawSqlBuilder { * The properties listed in the propertyNames must be in the same order as the columns in the * resultSet. */ - public static RawSql resultSet(ResultSet resultSet, String... propertyNames) { - return new RawSql(resultSet, propertyNames); + static RawSql resultSet(ResultSet resultSet, String... propertyNames) { + return XServiceProvider.get().resultSet(resultSet, propertyNames); } /** @@ -43,10 +29,8 @@ public class RawSqlBuilder { * modified - so no additional WHERE or HAVING expressions can be added to * this query. */ - public static RawSqlBuilder unparsed(String sql) { - - Sql s = new Sql(sql); - return new RawSqlBuilder(s, new ColumnMapping()); + static RawSqlBuilder unparsed(String sql) { + return XServiceProvider.get().unparsed(sql); } /** @@ -62,19 +46,8 @@ public class RawSqlBuilder { * correct column names are entered into the mapping. *

*/ - public static RawSqlBuilder parse(String sql) { - - Sql sql2 = DRawSqlParser.parse(sql); - String select = sql2.getPreFrom(); - - ColumnMapping mapping = DRawSqlColumnsParser.parse(select); - return new RawSqlBuilder(sql2, mapping); - } - - private RawSqlBuilder(Sql sql, ColumnMapping columnMapping) { - this.sql = sql; - this.columnMapping = columnMapping; - this.resultSet = null; + static RawSqlBuilder parse(String sql) { + return XServiceProvider.get().parsed(sql); } /** @@ -87,17 +60,12 @@ public class RawSqlBuilder { * @param dbColumn the DB column that we are mapping to a bean property * @param propertyName the bean property that we are mapping the DB column to. */ - public RawSqlBuilder columnMapping(String dbColumn, String propertyName) { - columnMapping.columnMapping(dbColumn, propertyName); - return this; - } + RawSqlBuilder columnMapping(String dbColumn, String propertyName); /** * Ignore this DB column. It is not mapped to any bean property. */ - public RawSqlBuilder columnMappingIgnore(String dbColumn) { - return columnMapping(dbColumn, IGNORE_COLUMN); - } + RawSqlBuilder columnMappingIgnore(String dbColumn); /** * Modify any column mappings with the given table alias to have the path prefix. @@ -108,24 +76,12 @@ public class RawSqlBuilder { * For the "Root type" you don't need to specify a tableAliasMapping. *

*/ - public RawSqlBuilder tableAliasMapping(String tableAlias, String path) { - columnMapping.tableAliasMapping(tableAlias, path); - return this; - } + RawSqlBuilder tableAliasMapping(String tableAlias, String path); /** * Create the immutable RawSql object. Do this after all the column mapping * has been defined. */ - public RawSql create() { - return new RawSql(resultSet, sql, columnMapping.createImmutableCopy()); - } - - /** - * Return the internal parsed Sql object (for testing). - */ - protected Sql getSql() { - return sql; - } + RawSql create(); } diff --git a/src/main/java/io/ebean/XServiceProvider.java b/src/main/java/io/ebean/XServiceProvider.java new file mode 100644 index 000000000..6e7ce4d98 --- /dev/null +++ b/src/main/java/io/ebean/XServiceProvider.java @@ -0,0 +1,30 @@ +package io.ebean; + +import io.ebean.plugin.SpiRawSqlService; + +import java.util.Iterator; +import java.util.ServiceLoader; + +/** + * Lookup internal services. + */ +class XServiceProvider { + + private static SpiRawSqlService builder = init(); + + private static SpiRawSqlService init() { + + Iterator loader = ServiceLoader.load(SpiRawSqlService.class).iterator(); + if (loader.hasNext()) { + return loader.next(); + } + throw new IllegalStateException("No service implementation found for SpiRawSqlService?"); + } + + /** + * Return the RawSqlService implementation. + */ + static SpiRawSqlService get() { + return builder; + } +} diff --git a/src/main/java/io/ebean/plugin/SpiRawSqlService.java b/src/main/java/io/ebean/plugin/SpiRawSqlService.java new file mode 100644 index 000000000..67254a6f2 --- /dev/null +++ b/src/main/java/io/ebean/plugin/SpiRawSqlService.java @@ -0,0 +1,27 @@ +package io.ebean.plugin; + +import io.ebean.RawSql; +import io.ebean.RawSqlBuilder; + +import java.sql.ResultSet; + +/** + * Service provided for parsing and column mapping raw SQL queries. + */ +public interface SpiRawSqlService { + + /** + * Create based on a JDBC ResultSet. + */ + RawSql resultSet(ResultSet resultSet, String... propertyNames); + + /** + * Parse the SQL determining column mapping. + */ + RawSqlBuilder parsed(String sql); + + /** + * Unparsed SQL so explicit column mapping expected. + */ + RawSqlBuilder unparsed(String sql); +} diff --git a/src/main/java/io/ebeaninternal/api/SpiQuery.java b/src/main/java/io/ebeaninternal/api/SpiQuery.java index 8a4022096..69123520d 100644 --- a/src/main/java/io/ebeaninternal/api/SpiQuery.java +++ b/src/main/java/io/ebeaninternal/api/SpiQuery.java @@ -20,6 +20,7 @@ import io.ebeaninternal.server.query.CancelableQuery; import io.ebeaninternal.server.querydefn.NaturalKeyBindParam; import io.ebeaninternal.server.querydefn.OrmQueryDetail; import io.ebeaninternal.server.querydefn.OrmUpdateProperties; +import io.ebeaninternal.server.rawsql.SpiRawSql; import java.sql.Timestamp; import java.util.List; @@ -170,6 +171,11 @@ public interface SpiQuery extends Query { */ BeanDescriptor getBeanDescriptor(); + /** + * Return the RawSql that was set to use for this query. + */ + SpiRawSql getRawSql(); + /** * Return true if this query should be executed against the doc store. */ diff --git a/src/main/java/io/ebeaninternal/server/core/DefaultServer.java b/src/main/java/io/ebeaninternal/server/core/DefaultServer.java index 34087d0f9..3a5013b24 100644 --- a/src/main/java/io/ebeaninternal/server/core/DefaultServer.java +++ b/src/main/java/io/ebeaninternal/server/core/DefaultServer.java @@ -1,30 +1,8 @@ package io.ebeaninternal.server.core; -import io.ebean.AutoTune; -import io.ebean.BackgroundExecutor; -import io.ebean.BeanState; -import io.ebean.CallableSql; -import io.ebean.DocumentStore; -import io.ebean.ExpressionFactory; -import io.ebean.Filter; -import io.ebean.FutureIds; -import io.ebean.FutureList; -import io.ebean.FutureRowCount; -import io.ebean.PagedList; -import io.ebean.PersistenceContextScope; -import io.ebean.Query; -import io.ebean.QueryIterator; -import io.ebean.RawSql; -import io.ebean.SqlQuery; -import io.ebean.SqlRow; -import io.ebean.SqlUpdate; -import io.ebean.Transaction; -import io.ebean.TransactionCallback; -import io.ebean.TxScope; -import io.ebean.Update; -import io.ebean.UpdateQuery; -import io.ebean.ValuePair; -import io.ebean.Version; +import io.ebean.*; +import io.ebean.annotation.TxIsolation; +import io.ebean.annotation.TxType; import io.ebean.bean.BeanCollection; import io.ebean.bean.CallStack; import io.ebean.bean.EntityBean; @@ -37,12 +15,11 @@ import io.ebean.common.CopyOnFirstWriteList; import io.ebean.config.CurrentTenantProvider; import io.ebean.config.EncryptKeyManager; import io.ebean.config.ServerConfig; -import io.ebean.config.TenantMode; -import io.ebean.config.dbplatform.DatabasePlatform; -import io.ebeaninternal.dbmigration.DdlGenerator; -import io.ebean.event.BeanPersistController; import io.ebean.config.SlowQueryEvent; import io.ebean.config.SlowQueryListener; +import io.ebean.config.TenantMode; +import io.ebean.config.dbplatform.DatabasePlatform; +import io.ebean.event.BeanPersistController; import io.ebean.event.readaudit.ReadAuditLogger; import io.ebean.event.readaudit.ReadAuditPrepare; import io.ebean.meta.MetaInfoManager; @@ -53,6 +30,7 @@ import io.ebean.text.csv.CsvReader; import io.ebean.text.json.JsonContext; import io.ebeaninternal.api.*; import io.ebeaninternal.api.SpiQuery.Type; +import io.ebeaninternal.dbmigration.DdlGenerator; import io.ebeaninternal.dbmigration.ddlgeneration.DdlHandler; import io.ebeaninternal.server.autotune.AutoTuneService; import io.ebeaninternal.server.core.timezone.DataTimeZone; @@ -65,9 +43,9 @@ import io.ebeaninternal.server.grammer.EqlParser; import io.ebeaninternal.server.lib.ShutdownManager; import io.ebeaninternal.server.query.CQuery; import io.ebeaninternal.server.query.CQueryEngine; +import io.ebeaninternal.server.query.CallableQueryCount; import io.ebeaninternal.server.query.CallableQueryIds; import io.ebeaninternal.server.query.CallableQueryList; -import io.ebeaninternal.server.query.CallableQueryCount; import io.ebeaninternal.server.query.LimitOffsetPagedList; import io.ebeaninternal.server.query.QueryFutureIds; import io.ebeaninternal.server.query.QueryFutureList; @@ -76,6 +54,7 @@ import io.ebeaninternal.server.querydefn.DefaultOrmQuery; import io.ebeaninternal.server.querydefn.DefaultOrmUpdate; import io.ebeaninternal.server.querydefn.DefaultRelationalQuery; import io.ebeaninternal.server.querydefn.DefaultUpdateQuery; +import io.ebeaninternal.server.rawsql.SpiRawSql; import io.ebeaninternal.server.text.csv.TCsvReader; import io.ebeaninternal.server.transaction.DefaultPersistenceContext; import io.ebeaninternal.server.transaction.RemoteTransactionEvent; @@ -84,8 +63,6 @@ import io.ebeaninternal.server.transaction.TransactionScopeManager; import io.ebeaninternal.util.ParamTypeHelper; import io.ebeaninternal.util.ParamTypeHelper.TypeInfo; import io.ebeanservice.docstore.api.DocStoreIntegration; -import io.ebean.annotation.TxIsolation; -import io.ebean.annotation.TxType; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -93,7 +70,6 @@ import javax.persistence.NonUniqueResultException; import javax.persistence.OptimisticLockException; import javax.persistence.PersistenceException; import javax.sql.DataSource; - import java.util.Arrays; import java.util.Collection; import java.util.Iterator; @@ -1003,7 +979,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer { if (named != null) { return createQuery(beanType, named); } - RawSql rawSql = desc.getNamedRawSql(namedQuery); + SpiRawSql rawSql = desc.getNamedRawSql(namedQuery); if (rawSql != null) { DefaultOrmQuery query = createQuery(beanType); query.setRawSql(rawSql); diff --git a/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptor.java b/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptor.java index 606409061..858ae67a5 100644 --- a/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptor.java +++ b/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptor.java @@ -3,7 +3,6 @@ package io.ebeaninternal.server.deploy; import io.ebean.OrderBy; import io.ebean.PersistenceContextScope; import io.ebean.Query; -import io.ebean.RawSql; import io.ebean.SqlUpdate; import io.ebean.Transaction; import io.ebean.ValuePair; @@ -64,6 +63,7 @@ import io.ebeaninternal.server.query.CQueryPlan; import io.ebeaninternal.server.query.CQueryPlanStats.Snapshot; import io.ebeaninternal.server.query.SplitName; import io.ebeaninternal.server.querydefn.OrmQueryDetail; +import io.ebeaninternal.server.rawsql.SpiRawSql; import io.ebeaninternal.server.text.json.ReadJson; import io.ebeaninternal.server.text.json.SpiJsonWriter; import io.ebeaninternal.server.type.DataBind; @@ -110,7 +110,7 @@ public class BeanDescriptor implements MetaBeanInfo, BeanType { private final ConcurrentHashMap> comparatorCache = new ConcurrentHashMap<>(); - private final Map namedRawSql; + private final Map namedRawSql; private final Map namedQuery; @@ -1112,7 +1112,7 @@ public class BeanDescriptor implements MetaBeanInfo, BeanType { /** * Return the named RawSql query. */ - public RawSql getNamedRawSql(String named) { + public SpiRawSql getNamedRawSql(String named) { return namedRawSql.get(named); } @@ -2932,7 +2932,7 @@ public class BeanDescriptor implements MetaBeanInfo, BeanType { if (idProperty != null && !idProperty.isEmbedded()) { OrderBy orderBy = query.getOrderBy(); if (orderBy == null || orderBy.isEmpty()) { - RawSql rawSql = query.getRawSql(); + SpiRawSql rawSql = query.getRawSql(); if (rawSql != null) { query.order(rawSql.getSql().getOrderBy()); } diff --git a/src/main/java/io/ebeaninternal/server/deploy/meta/DeployBeanDescriptor.java b/src/main/java/io/ebeaninternal/server/deploy/meta/DeployBeanDescriptor.java index ef378a1ba..87420edf7 100644 --- a/src/main/java/io/ebeaninternal/server/deploy/meta/DeployBeanDescriptor.java +++ b/src/main/java/io/ebeaninternal/server/deploy/meta/DeployBeanDescriptor.java @@ -1,6 +1,5 @@ package io.ebeaninternal.server.deploy.meta; -import io.ebean.RawSql; import io.ebean.annotation.Cache; import io.ebean.annotation.DocStore; import io.ebean.annotation.DocStoreMode; @@ -30,6 +29,7 @@ import io.ebeaninternal.server.deploy.IndexDefinition; import io.ebeaninternal.server.deploy.InheritInfo; import io.ebeaninternal.server.deploy.parse.DeployBeanInfo; import io.ebeaninternal.server.idgen.UuidIdGenerator; +import io.ebeaninternal.server.rawsql.SpiRawSql; import javax.persistence.Entity; import javax.persistence.MappedSuperclass; @@ -49,7 +49,7 @@ public class DeployBeanDescriptor { private static final Map EMPTY_NAMED_QUERY = new HashMap<>(); - private static final Map EMPTY_RAW_MAP = new HashMap<>(); + private static final Map EMPTY_RAW_MAP = new HashMap<>(); private static class PropOrder implements Comparator { @@ -75,7 +75,7 @@ public class DeployBeanDescriptor { */ private LinkedHashMap propMap = new LinkedHashMap<>(); - private Map namedRawSql; + private Map namedRawSql; private Map namedQuery; @@ -1100,14 +1100,14 @@ public class DeployBeanDescriptor { /** * Return the named RawSql queries. */ - public Map getNamedRawSql() { + public Map getNamedRawSql() { return (namedRawSql != null) ? namedRawSql : EMPTY_RAW_MAP; } /** * Add a named RawSql from ebean.xml file. */ - public void addRawSql(String name, RawSql rawSql) { + public void addRawSql(String name, SpiRawSql rawSql) { if (namedRawSql == null) { namedRawSql = new HashMap<>(); } diff --git a/src/main/java/io/ebeaninternal/server/deploy/parse/DeployBeanInfo.java b/src/main/java/io/ebeaninternal/server/deploy/parse/DeployBeanInfo.java index ebffb6f3c..f84a9dd69 100644 --- a/src/main/java/io/ebeaninternal/server/deploy/parse/DeployBeanInfo.java +++ b/src/main/java/io/ebeaninternal/server/deploy/parse/DeployBeanInfo.java @@ -1,6 +1,7 @@ package io.ebeaninternal.server.deploy.parse; import io.ebean.RawSql; +import io.ebeaninternal.server.rawsql.SpiRawSql; import io.ebeaninternal.server.deploy.meta.DeployBeanDescriptor; import io.ebeaninternal.server.deploy.meta.DeployBeanPropertyAssocOne; import io.ebeaninternal.server.deploy.meta.DeployTableJoin; @@ -81,7 +82,7 @@ public class DeployBeanInfo { * Add named RawSql from ebean.xml. */ public void addRawSql(String name, RawSql rawSql) { - descriptor.addRawSql(name, rawSql); + descriptor.addRawSql(name, (SpiRawSql)rawSql); } /** diff --git a/src/main/java/io/ebeaninternal/server/query/CQueryBuilder.java b/src/main/java/io/ebeaninternal/server/query/CQueryBuilder.java index 144ab495c..9af904c6a 100644 --- a/src/main/java/io/ebeaninternal/server/query/CQueryBuilder.java +++ b/src/main/java/io/ebeaninternal/server/query/CQueryBuilder.java @@ -1,10 +1,8 @@ package io.ebeaninternal.server.query; -import io.ebean.annotation.Platform; import io.ebean.RawSql; -import io.ebean.RawSql.ColumnMapping; -import io.ebean.RawSql.ColumnMapping.Column; import io.ebean.RawSqlBuilder; +import io.ebean.annotation.Platform; import io.ebean.config.dbplatform.DatabasePlatform; import io.ebean.config.dbplatform.SqlLimitRequest; import io.ebean.config.dbplatform.SqlLimitResponse; @@ -23,6 +21,9 @@ import io.ebeaninternal.server.el.ElPropertyValue; import io.ebeaninternal.server.persist.Binder; import io.ebeaninternal.server.querydefn.OrmQueryDetail; import io.ebeaninternal.server.querydefn.OrmQueryLimitRequest; +import io.ebeaninternal.server.rawsql.SpiRawSql; +import io.ebeaninternal.server.rawsql.SpiRawSql.ColumnMapping; +import io.ebeaninternal.server.rawsql.SpiRawSql.ColumnMapping.Column; import javax.persistence.PersistenceException; import java.sql.Connection; @@ -395,7 +396,7 @@ class CQueryBuilder { if (path != null) { propertyNames.add(path); } else { - propertyNames.add(RawSqlBuilder.IGNORE_COLUMN); + propertyNames.add(SpiRawSql.IGNORE_COLUMN); } } @@ -418,9 +419,9 @@ class CQueryBuilder { // convert list of columns into (tree like) PathProperties Iterator it = columnMapping.getColumns(); while (it.hasNext()) { - RawSql.ColumnMapping.Column column = it.next(); + SpiRawSql.ColumnMapping.Column column = it.next(); String propertyName = column.getPropertyName(); - if (!RawSqlBuilder.IGNORE_COLUMN.equals(propertyName)) { + if (!SpiRawSql.IGNORE_COLUMN.equals(propertyName)) { ElPropertyValue el = descriptor.getElGetValue(propertyName); if (el == null && propertyName.endsWith("Id")) { // try default naming convention for foreign key columns diff --git a/src/main/java/io/ebeaninternal/server/query/CQueryBuilderRawSql.java b/src/main/java/io/ebeaninternal/server/query/CQueryBuilderRawSql.java index 10b69eba1..2a84760b6 100644 --- a/src/main/java/io/ebeaninternal/server/query/CQueryBuilderRawSql.java +++ b/src/main/java/io/ebeaninternal/server/query/CQueryBuilderRawSql.java @@ -1,6 +1,6 @@ package io.ebeaninternal.server.query; -import io.ebean.RawSql; +import io.ebeaninternal.server.rawsql.SpiRawSql; import io.ebean.config.dbplatform.DatabasePlatform; import io.ebean.config.dbplatform.SqlLimitResponse; import io.ebean.config.dbplatform.SqlLimiter; @@ -24,7 +24,7 @@ class CQueryBuilderRawSql { /** * Build the full SQL Select statement for the request. */ - SqlLimitResponse buildSql(OrmQueryRequest request, CQueryPredicates predicates, RawSql.Sql rsql) { + SqlLimitResponse buildSql(OrmQueryRequest request, CQueryPredicates predicates, SpiRawSql.Sql rsql) { if (rsql == null) { // this is a ResultSet based RawSql query - just use some placeholder for the SQL @@ -60,7 +60,7 @@ class CQueryBuilderRawSql { } } - private String buildMainQuery(String orderBy, OrmQueryRequest request, CQueryPredicates predicates, RawSql.Sql sql) { + private String buildMainQuery(String orderBy, OrmQueryRequest request, CQueryPredicates predicates, SpiRawSql.Sql sql) { StringBuilder sb = new StringBuilder(); sb.append(sql.getPreFrom()); @@ -137,7 +137,7 @@ class CQueryBuilderRawSql { return s == null || s.isEmpty(); } - private String getOrderBy(CQueryPredicates predicates, RawSql.Sql sql) { + private String getOrderBy(CQueryPredicates predicates, SpiRawSql.Sql sql) { String orderBy = predicates.getDbOrderBy(); if (orderBy != null) { return orderBy; diff --git a/src/main/java/io/ebeaninternal/server/query/CQueryPlanRawSql.java b/src/main/java/io/ebeaninternal/server/query/CQueryPlanRawSql.java index 43b7ff92b..0c7e8c3a0 100644 --- a/src/main/java/io/ebeaninternal/server/query/CQueryPlanRawSql.java +++ b/src/main/java/io/ebeaninternal/server/query/CQueryPlanRawSql.java @@ -1,6 +1,6 @@ package io.ebeaninternal.server.query; -import io.ebean.RawSql.ColumnMapping; +import io.ebeaninternal.server.rawsql.SpiRawSql.ColumnMapping; import io.ebean.config.dbplatform.SqlLimitResponse; import io.ebeaninternal.server.core.OrmQueryRequest; import io.ebeaninternal.server.type.DataReader; diff --git a/src/main/java/io/ebeaninternal/server/query/CQueryPredicates.java b/src/main/java/io/ebeaninternal/server/query/CQueryPredicates.java index 4ca81ddcd..b49f65a59 100644 --- a/src/main/java/io/ebeaninternal/server/query/CQueryPredicates.java +++ b/src/main/java/io/ebeaninternal/server/query/CQueryPredicates.java @@ -1,6 +1,6 @@ package io.ebeaninternal.server.query; -import io.ebean.RawSql; +import io.ebeaninternal.server.rawsql.SpiRawSql; import io.ebeaninternal.api.BindParams; import io.ebeaninternal.api.SpiExpressionList; import io.ebeaninternal.api.SpiQuery; @@ -210,7 +210,7 @@ public class CQueryPredicates { // RawSql query hit cached query plan. Need to convert // named parameters into positioned parameters so that // the named parameters are bound - RawSql.Sql sql = query.getRawSql().getSql(); + SpiRawSql.Sql sql = query.getRawSql().getSql(); String s = sql.isParsed() ? sql.getPreWhere() : sql.getUnparsedSql(); BindParamsParser.parse(bindParams, s); } diff --git a/src/main/java/io/ebeaninternal/server/querydefn/DefaultOrmQuery.java b/src/main/java/io/ebeaninternal/server/querydefn/DefaultOrmQuery.java index 11cd893d0..34548edd5 100644 --- a/src/main/java/io/ebeaninternal/server/querydefn/DefaultOrmQuery.java +++ b/src/main/java/io/ebeaninternal/server/querydefn/DefaultOrmQuery.java @@ -1,23 +1,7 @@ package io.ebeaninternal.server.querydefn; -import io.ebean.CacheMode; -import io.ebean.EbeanServer; -import io.ebean.Expression; -import io.ebean.ExpressionFactory; -import io.ebean.ExpressionList; -import io.ebean.FetchConfig; -import io.ebean.FetchPath; -import io.ebean.FutureIds; -import io.ebean.FutureList; -import io.ebean.FutureRowCount; -import io.ebean.OrderBy; +import io.ebean.*; import io.ebean.OrderBy.Property; -import io.ebean.PagedList; -import io.ebean.PersistenceContextScope; -import io.ebean.Query; -import io.ebean.QueryIterator; -import io.ebean.RawSql; -import io.ebean.Version; import io.ebean.bean.CallStack; import io.ebean.bean.ObjectGraphNode; import io.ebean.bean.ObjectGraphOrigin; @@ -25,16 +9,7 @@ import io.ebean.bean.PersistenceContext; import io.ebean.event.BeanQueryRequest; import io.ebean.event.readaudit.ReadEvent; import io.ebean.plugin.BeanType; -import io.ebeaninternal.api.BindParams; -import io.ebeaninternal.api.CQueryPlanKey; -import io.ebeaninternal.api.HashQuery; -import io.ebeaninternal.api.ManyWhereJoins; -import io.ebeaninternal.api.SpiExpression; -import io.ebeaninternal.api.SpiExpressionList; -import io.ebeaninternal.api.SpiExpressionValidation; -import io.ebeaninternal.api.SpiNamedParam; -import io.ebeaninternal.api.SpiQuery; -import io.ebeaninternal.api.SpiQuerySecondary; +import io.ebeaninternal.api.*; import io.ebeaninternal.server.autotune.ProfilingListener; import io.ebeaninternal.server.deploy.BeanDescriptor; import io.ebeaninternal.server.deploy.BeanPropertyAssocMany; @@ -43,6 +18,7 @@ import io.ebeaninternal.server.expression.DefaultExpressionList; import io.ebeaninternal.server.expression.SimpleExpression; import io.ebeaninternal.server.query.CancelableQuery; import io.ebeaninternal.server.query.NativeSqlQueryPlanKey; +import io.ebeaninternal.server.rawsql.SpiRawSql; import java.sql.Timestamp; import java.util.ArrayList; @@ -244,7 +220,7 @@ public class DefaultOrmQuery implements SpiQuery { private ManyWhereJoins manyWhereJoins; - private RawSql rawSql; + private SpiRawSql rawSql; private boolean useDocStore; @@ -373,13 +349,13 @@ public class DefaultOrmQuery implements SpiQuery { } @Override - public RawSql getRawSql() { + public SpiRawSql getRawSql() { return rawSql; } @Override public DefaultOrmQuery setRawSql(RawSql rawSql) { - this.rawSql = rawSql; + this.rawSql = (SpiRawSql)rawSql; return this; } diff --git a/src/main/java/io/ebeaninternal/server/querydefn/OrmQueryPlanKey.java b/src/main/java/io/ebeaninternal/server/querydefn/OrmQueryPlanKey.java index 721d21b1e..88e060e85 100644 --- a/src/main/java/io/ebeaninternal/server/querydefn/OrmQueryPlanKey.java +++ b/src/main/java/io/ebeaninternal/server/querydefn/OrmQueryPlanKey.java @@ -2,7 +2,7 @@ package io.ebeaninternal.server.querydefn; import io.ebean.OrderBy; import io.ebean.Query; -import io.ebean.RawSql; +import io.ebeaninternal.server.rawsql.SpiRawSql; import io.ebeaninternal.api.BindParams; import io.ebeaninternal.api.CQueryPlanKey; import io.ebeaninternal.api.SpiExpression; @@ -14,7 +14,7 @@ import io.ebeaninternal.server.deploy.TableJoin; */ class OrmQueryPlanKey implements CQueryPlanKey { - private final RawSql.Key rawSqlKey; + private final SpiRawSql.Key rawSqlKey; private final int maxRows; private final int firstRow; private final int planHash; @@ -23,7 +23,7 @@ class OrmQueryPlanKey implements CQueryPlanKey { OrmQueryPlanKey(String discValue, TableJoin m2mIncludeTable, SpiQuery.Type type, OrmQueryDetail detail, int maxRows, int firstRow, boolean disableLazyLoading, OrderBy orderBy, boolean distinct, boolean sqlDistinct, String mapKey, Object id, BindParams bindParams, SpiExpression whereExpressions, SpiExpression havingExpressions, SpiQuery.TemporalMode temporalMode, - Query.ForUpdate forUpdate, String rootTableAlias, RawSql rawSql, OrmUpdateProperties updateProperties) { + Query.ForUpdate forUpdate, String rootTableAlias, SpiRawSql rawSql, OrmUpdateProperties updateProperties) { StringBuilder sb = new StringBuilder(300); if (type != null) { diff --git a/src/main/java/io/ebeaninternal/server/rawsql/DRawSql.java b/src/main/java/io/ebeaninternal/server/rawsql/DRawSql.java new file mode 100644 index 000000000..9f7e6627d --- /dev/null +++ b/src/main/java/io/ebeaninternal/server/rawsql/DRawSql.java @@ -0,0 +1,65 @@ +package io.ebeaninternal.server.rawsql; + +import java.sql.ResultSet; + +/** + * Default implementation of SpiRawSql. + */ +public final class DRawSql implements SpiRawSql { + + private final ResultSet resultSet; + + private final Sql sql; + + private final ColumnMapping columnMapping; + + /** + * Construct with a ResultSet and properties that the columns map to. + */ + public DRawSql(ResultSet resultSet, String... propertyNames) { + this.resultSet = resultSet; + this.sql = null; + this.columnMapping = new ColumnMapping(propertyNames); + } + + protected DRawSql(ResultSet resultSet, Sql sql, ColumnMapping columnMapping) { + this.resultSet = resultSet; + this.sql = sql; + this.columnMapping = columnMapping; + } + + /** + * Return the Sql either unparsed or in parsed (broken up) form. + */ + @Override + public Sql getSql() { + return sql; + } + + /** + * Return the key; + */ + @Override + public Key getKey() { + boolean parsed = sql != null && sql.isParsed(); + String unParsedSql = (sql == null) ? "" : sql.getUnparsedSql(); + return new Key(parsed, unParsedSql, columnMapping); + } + + /** + * Return the resultSet if this is a ResultSet based RawSql. + */ + @Override + public ResultSet getResultSet() { + return resultSet; + } + + /** + * Return the column mapping for the SQL columns to bean properties. + */ + @Override + public ColumnMapping getColumnMapping() { + return columnMapping; + } + +} diff --git a/src/main/java/io/ebeaninternal/server/rawsql/DRawSqlBuilder.java b/src/main/java/io/ebeaninternal/server/rawsql/DRawSqlBuilder.java new file mode 100644 index 000000000..40160e873 --- /dev/null +++ b/src/main/java/io/ebeaninternal/server/rawsql/DRawSqlBuilder.java @@ -0,0 +1,43 @@ +package io.ebeaninternal.server.rawsql; + +import io.ebean.RawSql; +import io.ebean.RawSqlBuilder; + +import java.sql.ResultSet; + +public class DRawSqlBuilder implements RawSqlBuilder { + + private final ResultSet resultSet; + + private final SpiRawSql.Sql sql; + + private final SpiRawSql.ColumnMapping columnMapping; + + DRawSqlBuilder(SpiRawSql.Sql sql, SpiRawSql.ColumnMapping columnMapping) { + this.sql = sql; + this.columnMapping = columnMapping; + this.resultSet = null; + } + + @Override + public RawSqlBuilder columnMapping(String dbColumn, String propertyName) { + columnMapping.columnMapping(dbColumn, propertyName); + return this; + } + + @Override + public RawSqlBuilder columnMappingIgnore(String dbColumn) { + return columnMapping(dbColumn, SpiRawSql.IGNORE_COLUMN); + } + + @Override + public RawSqlBuilder tableAliasMapping(String tableAlias, String path) { + columnMapping.tableAliasMapping(tableAlias, path); + return this; + } + + @Override + public RawSql create() { + return new DRawSql(resultSet, sql, columnMapping.createImmutableCopy()); + } +} diff --git a/src/main/java/io/ebean/DRawSqlColumnsParser.java b/src/main/java/io/ebeaninternal/server/rawsql/DRawSqlColumnsParser.java similarity index 96% rename from src/main/java/io/ebean/DRawSqlColumnsParser.java rename to src/main/java/io/ebeaninternal/server/rawsql/DRawSqlColumnsParser.java index 4439585fe..6e17ed5a9 100644 --- a/src/main/java/io/ebean/DRawSqlColumnsParser.java +++ b/src/main/java/io/ebeaninternal/server/rawsql/DRawSqlColumnsParser.java @@ -1,6 +1,6 @@ -package io.ebean; +package io.ebeaninternal.server.rawsql; -import io.ebean.RawSql.ColumnMapping; +import io.ebeaninternal.server.rawsql.SpiRawSql.ColumnMapping; import java.util.regex.Pattern; import javax.persistence.PersistenceException; diff --git a/src/main/java/io/ebean/DRawSqlParser.java b/src/main/java/io/ebeaninternal/server/rawsql/DRawSqlParser.java similarity index 98% rename from src/main/java/io/ebean/DRawSqlParser.java rename to src/main/java/io/ebeaninternal/server/rawsql/DRawSqlParser.java index 83325f6b7..a653a074e 100644 --- a/src/main/java/io/ebean/DRawSqlParser.java +++ b/src/main/java/io/ebeaninternal/server/rawsql/DRawSqlParser.java @@ -1,6 +1,6 @@ -package io.ebean; +package io.ebeaninternal.server.rawsql; -import io.ebean.RawSql.Sql; +import io.ebeaninternal.server.rawsql.SpiRawSql.Sql; import io.ebeaninternal.server.querydefn.SimpleTextParser; /** diff --git a/src/main/java/io/ebeaninternal/server/rawsql/DRawSqlService.java b/src/main/java/io/ebeaninternal/server/rawsql/DRawSqlService.java new file mode 100644 index 000000000..12207fc1a --- /dev/null +++ b/src/main/java/io/ebeaninternal/server/rawsql/DRawSqlService.java @@ -0,0 +1,31 @@ +package io.ebeaninternal.server.rawsql; + +import io.ebean.RawSql; +import io.ebean.RawSqlBuilder; +import io.ebean.plugin.SpiRawSqlService; + +import java.sql.ResultSet; + +public class DRawSqlService implements SpiRawSqlService { + + @Override + public RawSql resultSet(ResultSet resultSet, String... propertyNames) { + return new DRawSql(resultSet, propertyNames); + } + + @Override + public RawSqlBuilder parsed(String sql) { + + SpiRawSql.Sql sql2 = DRawSqlParser.parse(sql); + String select = sql2.getPreFrom(); + + SpiRawSql.ColumnMapping mapping = DRawSqlColumnsParser.parse(select); + return new DRawSqlBuilder(sql2, mapping); + } + + @Override + public RawSqlBuilder unparsed(String sql) { + SpiRawSql.Sql s = new SpiRawSql.Sql(sql); + return new DRawSqlBuilder(s, new SpiRawSql.ColumnMapping()); + } +} diff --git a/src/main/java/io/ebeaninternal/server/rawsql/SpiRawSql.java b/src/main/java/io/ebeaninternal/server/rawsql/SpiRawSql.java new file mode 100644 index 000000000..e84ab7d94 --- /dev/null +++ b/src/main/java/io/ebeaninternal/server/rawsql/SpiRawSql.java @@ -0,0 +1,536 @@ +package io.ebeaninternal.server.rawsql; + +import io.ebean.RawSql; +import io.ebean.util.CamelCaseHelper; + +import java.io.Serializable; +import java.sql.ResultSet; +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +/** + * Internal service API for Raw Sql. + */ +public interface SpiRawSql extends RawSql { + + /** + * Special property name assigned to a DB column that should be ignored. + */ + String IGNORE_COLUMN = "$$_IGNORE_COLUMN_$$"; + + SpiRawSql.Sql getSql(); + + SpiRawSql.Key getKey(); + + ResultSet getResultSet(); + + SpiRawSql.ColumnMapping getColumnMapping(); + + + /** + * 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 + * SQL. + */ + final class Sql implements Serializable { + + private static final long serialVersionUID = 1L; + + private final boolean parsed; + + private final String unparsedSql; + + private final String preFrom; + + private final String preWhere; + + private final boolean andWhereExpr; + + private final String preHaving; + + private final boolean andHavingExpr; + + private final String orderByPrefix; + + private final String orderBy; + + private final boolean distinct; + + /** + * Construct for unparsed SQL. + */ + protected Sql(String unparsedSql) { + this.parsed = false; + this.unparsedSql = unparsedSql; + this.preFrom = null; + this.preHaving = null; + this.preWhere = null; + this.andHavingExpr = false; + this.andWhereExpr = false; + this.orderByPrefix = null; + this.orderBy = null; + this.distinct = false; + } + + /** + * Construct for parsed SQL. + */ + protected Sql(String unparsedSql, String preFrom, String preWhere, boolean andWhereExpr, + String preHaving, boolean andHavingExpr, String orderByPrefix, String orderBy, boolean distinct) { + + this.unparsedSql = unparsedSql; + this.parsed = true; + this.preFrom = preFrom; + this.preHaving = preHaving; + this.preWhere = preWhere; + this.andHavingExpr = andHavingExpr; + this.andWhereExpr = andWhereExpr; + this.orderByPrefix = orderByPrefix; + this.orderBy = orderBy; + this.distinct = distinct; + } + + @Override + public String toString() { + if (!parsed) { + return "unparsed[" + unparsedSql + "]"; + } + return "select[" + preFrom + "] preWhere[" + preWhere + "] preHaving[" + preHaving + "] orderBy[" + orderBy + "]"; + } + + public boolean isDistinct() { + return distinct; + } + + /** + * Return true if the SQL is left completely unmodified. + *

+ * This means Ebean can't add WHERE or HAVING expressions into the query - + * it will be left completely unmodified. + *

+ */ + public boolean isParsed() { + return parsed; + } + + /** + * Return the SQL when it is unparsed. + */ + public String getUnparsedSql() { + return unparsedSql; + } + + /** + * Return the SQL prior to FROM clause. + */ + public String getPreFrom() { + return preFrom; + } + + /** + * Return the SQL prior to WHERE clause. + */ + public String getPreWhere() { + return preWhere; + } + + /** + * Return true if there is already a WHERE clause and any extra where + * expressions start with AND. + */ + public boolean isAndWhereExpr() { + return andWhereExpr; + } + + /** + * Return the SQL prior to HAVING clause. + */ + public String getPreHaving() { + return preHaving; + } + + /** + * Return true if there is already a HAVING clause and any extra having + * expressions start with AND. + */ + public boolean isAndHavingExpr() { + return andHavingExpr; + } + + /** + * Return the 'order by' keywords. + * This can contain additional keywords, for example 'order siblings by' as Oracle syntax. + */ + public String getOrderByPrefix() { + return (orderByPrefix == null) ? "order by" : orderByPrefix; + } + + /** + * Return the SQL ORDER BY clause. + */ + public String getOrderBy() { + return orderBy; + } + + } + + /** + * Defines the column mapping for raw sql DB columns to bean properties. + */ + final class ColumnMapping implements Serializable { + + private static final long serialVersionUID = 1L; + + private final LinkedHashMap dbColumnMap; + + private final Map propertyMap; + + private final Map propertyColumnMap; + + private final boolean parsed; + + private final boolean immutable; + + /** + * Construct from parsed sql where the columns have been identified. + */ + protected ColumnMapping(List columns) { + this.immutable = false; + this.parsed = true; + this.propertyMap = null; + this.propertyColumnMap = null; + this.dbColumnMap = new LinkedHashMap<>(); + for (Column c : columns) { + dbColumnMap.put(c.getDbColumnKey(), c); + } + } + + /** + * Construct for unparsed sql. + */ + protected ColumnMapping() { + this.immutable = false; + this.parsed = false; + this.propertyMap = null; + this.propertyColumnMap = null; + this.dbColumnMap = new LinkedHashMap<>(); + } + + /** + * Construct for ResultSet use. + */ + protected ColumnMapping(String... propertyNames) { + this.immutable = false; + this.parsed = false; + this.propertyMap = null; + this.dbColumnMap = new LinkedHashMap<>(); + + int pos = 0; + for (String prop : propertyNames) { + dbColumnMap.put(prop, new Column(pos++, prop, null, prop)); + } + propertyColumnMap = dbColumnMap; + } + + /** + * Construct an immutable ColumnMapping based on collected information. + */ + protected ColumnMapping(boolean parsed, LinkedHashMap dbColumnMap) { + this.immutable = true; + this.parsed = parsed; + this.dbColumnMap = dbColumnMap; + + HashMap pcMap = new HashMap<>(); + HashMap pMap = new HashMap<>(); + + for (Column c : dbColumnMap.values()) { + pMap.put(c.getPropertyName(), c.getDbColumn()); + pcMap.put(c.getPropertyName(), c); + } + this.propertyMap = Collections.unmodifiableMap(pMap); + this.propertyColumnMap = Collections.unmodifiableMap(pcMap); + } + + @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(); + } + + /** + * Return true if the property is mapped. + */ + public boolean contains(String property) { + return this.propertyColumnMap.containsKey(property); + } + + /** + * Creates an immutable copy of this ColumnMapping. + * + * @throws IllegalStateException when a propertyName has not been defined for a column. + */ + protected ColumnMapping createImmutableCopy() { + + for (Column c : dbColumnMap.values()) { + c.checkMapping(); + } + + return new ColumnMapping(parsed, dbColumnMap); + } + + protected void columnMapping(String dbColumn, String propertyName) { + + if (immutable) { + throw new IllegalStateException("Should never happen"); + } + if (!parsed) { + int pos = dbColumnMap.size(); + dbColumnMap.put(dbColumn, new Column(pos, dbColumn, null, propertyName)); + } else { + Column column = dbColumnMap.get(dbColumn); + if (column == null) { + String msg = "DB Column [" + dbColumn + "] not found in mapping. Expecting one of [" + dbColumnMap.keySet() + "]"; + throw new IllegalArgumentException(msg); + } + column.setPropertyName(propertyName); + } + } + + /** + * Returns true if the Columns where supplied by parsing the sql select + * clause. + *

+ * In the case where the columns where parsed then we can do extra checks on + * the column mapping such as, is the column a valid one in the sql and + * whether all the columns in the sql have been mapped. + *

+ */ + public boolean isParsed() { + return parsed; + } + + /** + * Return the number of columns in this column mapping. + */ + public int size() { + return dbColumnMap.size(); + } + + /** + * Return the column mapping. + */ + protected Map mapping() { + return dbColumnMap; + } + + /** + * Return the mapping by DB column. + */ + public Map getMapping() { + return propertyMap; + } + + /** + * Return the index position by bean property name. + */ + public int getIndexPosition(String property) { + Column c = propertyColumnMap.get(property); + return c == null ? -1 : c.getIndexPos(); + } + + /** + * Return an iterator of the Columns. + */ + public Iterator getColumns() { + return dbColumnMap.values().iterator(); + } + + /** + * Modify any column mappings with the given table alias to have the path prefix. + *

+ * For example modify all mappings with table alias "c" to have the path prefix "customer". + *

+ *

+ * For the "Root type" you don't need to specify a tableAliasMapping. + *

+ */ + public void tableAliasMapping(String tableAlias, String path) { + + String startMatch = tableAlias + "."; + for (Map.Entry entry : dbColumnMap.entrySet()) { + if (entry.getKey().startsWith(startMatch)) { + entry.getValue().tableAliasMapping(path); + } + } + } + + /** + * A Column of the RawSql that is mapped to a bean property (or ignored). + */ + public static class Column implements Serializable { + + private static final long serialVersionUID = 1L; + private final int indexPos; + private final String dbColumn; + + private final String dbAlias; + + private String propertyName; + + /** + * Construct a Column. + */ + public Column(int indexPos, String dbColumn, String dbAlias) { + this(indexPos, dbColumn, dbAlias, derivePropertyName(dbAlias, dbColumn)); + } + + private Column(int indexPos, String dbColumn, String dbAlias, String propertyName) { + this.indexPos = indexPos; + this.dbColumn = dbColumn; + this.dbAlias = dbAlias; + if (propertyName == null && dbAlias != null) { + this.propertyName = dbAlias; + } else { + this.propertyName = propertyName; + } + } + + protected static String derivePropertyName(String dbAlias, String dbColumn) { + if (dbAlias != null) { + return CamelCaseHelper.toCamelFromUnderscore(dbAlias); + } + int dotPos = dbColumn.indexOf('.'); + if (dotPos > -1) { + dbColumn = dbColumn.substring(dotPos + 1); + } + return CamelCaseHelper.toCamelFromUnderscore(dbColumn); + } + + private void checkMapping() { + if (propertyName == null) { + String msg = "No propertyName defined (Column mapping) for dbColumn [" + dbColumn + "]"; + throw new IllegalStateException(msg); + } + } + + @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 = 92821 * result + dbColumn.hashCode(); + result = 92821 * result + (dbAlias != null ? dbAlias.hashCode() : 0); + result = 92821 * result + (propertyName != null ? propertyName.hashCode() : 0); + return result; + } + + @Override + public String toString() { + return dbColumn + "->" + propertyName; + } + + /** + * Return the index position of this column. + */ + public int getIndexPos() { + return indexPos; + } + + /** + * Return the DB column alias if specified otherwise DB column. + * This is used as the key for mapping a column to a logical property. + */ + public String getDbColumnKey() { + return (dbAlias != null) ? dbAlias : dbColumn; + } + + /** + * Return the DB column name including table alias (if it has one). + */ + public String getDbColumn() { + return dbColumn; + } + + /** + * Return the bean property this column is mapped to. + */ + public String getPropertyName() { + return propertyName; + } + + /** + * Set the property name mapped to this db column. + */ + private void setPropertyName(String propertyName) { + this.propertyName = propertyName; + } + + /** + * Prepend the path to the property name. + *

+ * For example if path is "customer" then "name" becomes "customer.name". + */ + public void tableAliasMapping(String path) { + if (path != null) { + propertyName = path + "." + propertyName; + } + } + } + } + + /** + * A key for the RawSql object using for the query plan. + */ + 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 = 92821 * result + columnMapping.hashCode(); + result = 92821 * result + unParsedSql.hashCode(); + return result; + } + } +} diff --git a/src/main/resources/META-INF/services/io.ebean.plugin.SpiRawSqlService b/src/main/resources/META-INF/services/io.ebean.plugin.SpiRawSqlService new file mode 100644 index 000000000..f505dbcf5 --- /dev/null +++ b/src/main/resources/META-INF/services/io.ebean.plugin.SpiRawSqlService @@ -0,0 +1 @@ +io.ebeaninternal.server.rawsql.DRawSqlService diff --git a/src/test/java/io/ebean/ColumnMappingTest.java b/src/test/java/io/ebean/ColumnMappingTest.java deleted file mode 100644 index d8c44c5d2..000000000 --- a/src/test/java/io/ebean/ColumnMappingTest.java +++ /dev/null @@ -1,66 +0,0 @@ -package io.ebean; - -import io.ebean.RawSql; -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()); - } -} diff --git a/src/test/java/io/ebean/ColumnTest.java b/src/test/java/io/ebean/ColumnTest.java index 57e7ed694..c8889896e 100644 --- a/src/test/java/io/ebean/ColumnTest.java +++ b/src/test/java/io/ebean/ColumnTest.java @@ -1,15 +1,15 @@ package io.ebean; -import io.ebean.RawSql; +import io.ebeaninternal.server.rawsql.SpiRawSql; 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); + SpiRawSql.ColumnMapping.Column col(int indexPos, String dbColumn, String dbAlias) { + return new SpiRawSql.ColumnMapping.Column(indexPos, dbColumn, dbAlias); } @Test diff --git a/src/test/java/io/ebean/RawSqlKeyTest.java b/src/test/java/io/ebean/RawSqlKeyTest.java deleted file mode 100644 index c798ecd3f..000000000 --- a/src/test/java/io/ebean/RawSqlKeyTest.java +++ /dev/null @@ -1,66 +0,0 @@ -package io.ebean; - - -import io.ebean.RawSql; -import io.ebean.RawSqlBuilder; -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()); - } -} diff --git a/src/test/java/io/ebeaninternal/server/rawsql/ColumnMappingTest.java b/src/test/java/io/ebeaninternal/server/rawsql/ColumnMappingTest.java new file mode 100644 index 000000000..527c1cf93 --- /dev/null +++ b/src/test/java/io/ebeaninternal/server/rawsql/ColumnMappingTest.java @@ -0,0 +1,65 @@ +package io.ebeaninternal.server.rawsql; + +import org.junit.Test; + +import java.util.Arrays; + +import static org.assertj.core.api.StrictAssertions.assertThat; + +public class ColumnMappingTest { + + SpiRawSql.ColumnMapping.Column col(int indexPos, String dbColumn, String dbAlias) { + return new SpiRawSql.ColumnMapping.Column(indexPos, dbColumn, dbAlias); + } + + SpiRawSql.ColumnMapping mapping(SpiRawSql.ColumnMapping.Column... cols) { + return new SpiRawSql.ColumnMapping(Arrays.asList(cols)); + } + + @Test + public void equals_same() { + + SpiRawSql.ColumnMapping mapping1 = mapping(col(1, "id", null), col(2, "name", null)); + SpiRawSql.ColumnMapping mapping2 = mapping(col(1, "id", null), col(2, "name", null)); + + assertSame(mapping1, mapping2); + } + + @Test + public void equals_diffPropertyName() { + + SpiRawSql.ColumnMapping mapping1 = mapping(col(1, "id", null), col(2, "name", null)); + SpiRawSql.ColumnMapping mapping2 = mapping(col(1, "id", null), col(2, "diff", null)); + + assertDifferent(mapping1, mapping2); + } + + @Test + public void equals_moreColumns() { + + SpiRawSql.ColumnMapping mapping1 = mapping(col(1, "id", null), col(2, "name", null)); + SpiRawSql.ColumnMapping mapping2 = mapping(col(1, "id", null), col(2, "name", null), col(2, "diff", null)); + + assertDifferent(mapping1, mapping2); + } + + + @Test + public void equals_lessColumns() { + + SpiRawSql.ColumnMapping mapping1 = mapping(col(1, "id", null), col(2, "name", null)); + SpiRawSql.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()); + } +} diff --git a/src/test/java/io/ebeaninternal/server/rawsql/RawSqlKeyTest.java b/src/test/java/io/ebeaninternal/server/rawsql/RawSqlKeyTest.java new file mode 100644 index 000000000..54efa28c5 --- /dev/null +++ b/src/test/java/io/ebeaninternal/server/rawsql/RawSqlKeyTest.java @@ -0,0 +1,74 @@ +package io.ebeaninternal.server.rawsql; + + +import io.ebean.RawSql; +import io.ebean.RawSqlBuilder; +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class RawSqlKeyTest { + + private SpiRawSql.Key key(String sqlStatement) { + return ((SpiRawSql) RawSqlBuilder.parse(sqlStatement).create()).getKey(); + } + + private SpiRawSql.Key key(RawSql rawSql) { + return ((SpiRawSql)rawSql).getKey(); + } + + @Test + public void equals_when_sameParsedSql() { + + SpiRawSql.Key key = key("select id from customer"); + SpiRawSql.Key key1 = key("select id from customer"); + + assertSame(key, key1); + } + + @Test + public void equals_when_diffParsedSql() { + + SpiRawSql.Key key = key("select id from customer"); + SpiRawSql.Key key1 = key("select name from customer"); + + assertDifferent(key, key1); + } + + @Test + public void equals_when_sameColumnMapping() { + + SpiRawSql.Key key = key(RawSqlBuilder.parse("select id from customer").columnMapping("id", "b").create()); + SpiRawSql.Key key1 = key(RawSqlBuilder.parse("select id from customer").columnMapping("id", "b").create()); + + assertSame(key, key1); + } + + @Test + public void equals_when_diffColumnMapping() { + + SpiRawSql.Key key = key(RawSqlBuilder.parse("select a from customer").columnMapping("a", "b").create()); + SpiRawSql.Key key1 = key(RawSqlBuilder.parse("select a from customer").columnMapping("a", "c").create()); + + assertDifferent(key, key1); + } + + @Test + public void equals_when_parseToUnpased() { + + SpiRawSql.Key key = key(RawSqlBuilder.parse("select a from customer").columnMapping("a", "b").create()); + SpiRawSql.Key key1 = key(RawSqlBuilder.unparsed("select a from customer").columnMapping("a", "c").create()); + + assertDifferent(key, key1); + } + + private void assertSame(SpiRawSql.Key key, SpiRawSql.Key key1) { + assertThat(key).isEqualTo(key1); + assertThat(key.hashCode()).isEqualTo(key1.hashCode()); + } + + private void assertDifferent(SpiRawSql.Key key, SpiRawSql.Key key1) { + assertThat(key).isNotEqualTo(key1); + assertThat(key.hashCode()).isNotEqualTo(key1.hashCode()); + } +} diff --git a/src/test/java/io/ebean/TestRawSqlBuilder.java b/src/test/java/io/ebeaninternal/server/rawsql/TestRawSqlBuilder.java similarity index 73% rename from src/test/java/io/ebean/TestRawSqlBuilder.java rename to src/test/java/io/ebeaninternal/server/rawsql/TestRawSqlBuilder.java index 62a02615a..f8f0bd0f0 100644 --- a/src/test/java/io/ebean/TestRawSqlBuilder.java +++ b/src/test/java/io/ebeaninternal/server/rawsql/TestRawSqlBuilder.java @@ -1,6 +1,12 @@ -package io.ebean; +package io.ebeaninternal.server.rawsql; -import io.ebean.RawSql.Sql; +import io.ebean.BaseTestCase; +import io.ebean.Ebean; +import io.ebean.Query; +import io.ebean.RawSql; +import io.ebean.RawSqlBuilder; +import io.ebeaninternal.server.rawsql.SpiRawSql.Sql; +import org.assertj.core.api.StrictAssertions; import org.tests.model.basic.Customer; import org.tests.model.basic.ResetBasicData; import org.tests.model.rawsql.ERawSqlAggBean; @@ -14,19 +20,23 @@ public class TestRawSqlBuilder extends BaseTestCase { @Test public void testDeriveProperty() { - assertThat(RawSql.ColumnMapping.Column.derivePropertyName("item_total", "some_other")).isEqualTo("itemTotal"); - assertThat(RawSql.ColumnMapping.Column.derivePropertyName(null, "some_other")).isEqualTo("someOther"); - assertThat(RawSql.ColumnMapping.Column.derivePropertyName(null, "alias.some_other")).isEqualTo("someOther"); - assertThat(RawSql.ColumnMapping.Column.derivePropertyName(null, "alias.someOther")).isEqualTo("someOther"); - assertThat(RawSql.ColumnMapping.Column.derivePropertyName(null, "some")).isEqualTo("some"); - assertThat(RawSql.ColumnMapping.Column.derivePropertyName(null, "someOther")).isEqualTo("someOther"); + StrictAssertions.assertThat(SpiRawSql.ColumnMapping.Column.derivePropertyName("item_total", "some_other")).isEqualTo("itemTotal"); + assertThat(SpiRawSql.ColumnMapping.Column.derivePropertyName(null, "some_other")).isEqualTo("someOther"); + assertThat(SpiRawSql.ColumnMapping.Column.derivePropertyName(null, "alias.some_other")).isEqualTo("someOther"); + assertThat(SpiRawSql.ColumnMapping.Column.derivePropertyName(null, "alias.someOther")).isEqualTo("someOther"); + assertThat(SpiRawSql.ColumnMapping.Column.derivePropertyName(null, "some")).isEqualTo("some"); + assertThat(SpiRawSql.ColumnMapping.Column.derivePropertyName(null, "someOther")).isEqualTo("someOther"); + } + + private Sql getSql(String sqlStatement) { + RawSql r = RawSqlBuilder.parse(sqlStatement).create(); + return ((SpiRawSql)r).getSql(); } @Test public void testSimple() { - RawSqlBuilder r = RawSqlBuilder.parse("select id from t_cust"); - Sql sql = r.getSql(); + Sql sql = getSql("select id from t_cust"); assertEquals("id", sql.getPreFrom()); assertEquals("from t_cust", sql.getPreWhere()); assertEquals("", sql.getPreHaving()); @@ -36,8 +46,7 @@ public class TestRawSqlBuilder extends BaseTestCase { @Test public void testWithNewLineCharacters() { - RawSqlBuilder r = RawSqlBuilder.parse("select\n id from\n o_customer"); - Sql sql = r.getSql(); + Sql sql = getSql("select\n id from\n o_customer"); assertEquals("id", sql.getPreFrom()); assertEquals("from o_customer", sql.getPreWhere()); @@ -46,7 +55,7 @@ public class TestRawSqlBuilder extends BaseTestCase { ResetBasicData.reset(); - RawSql rawSql = r.create(); + RawSql rawSql = RawSqlBuilder.parse("select\n id from\n o_customer").create(); Ebean.find(Customer.class) .setRawSql(rawSql) @@ -56,8 +65,8 @@ public class TestRawSqlBuilder extends BaseTestCase { @Test public void testWithWhere() { - RawSqlBuilder r = RawSqlBuilder.parse("select id from t_cust where id > ?"); - Sql sql = r.getSql(); + Sql sql = getSql("select id from t_cust where id > ?"); + assertEquals("id", sql.getPreFrom()); assertEquals("from t_cust where id > ?", sql.getPreWhere()); assertEquals("", sql.getPreHaving()); @@ -67,24 +76,21 @@ public class TestRawSqlBuilder extends BaseTestCase { @Test public void testWithOrder() { - RawSqlBuilder r = RawSqlBuilder.parse("select id from t_cust where id > ? order by id desc"); - Sql sql = r.getSql(); + Sql sql = getSql("select id from t_cust where id > ? order by id desc"); + assertEquals("id", sql.getPreFrom()); assertEquals("from t_cust where id > ?", sql.getPreWhere()); assertEquals("", sql.getPreHaving()); assertEquals("order by", sql.getOrderByPrefix()); assertEquals("id desc", sql.getOrderBy()); - r = RawSqlBuilder.parse("select id from t_cust order by id desc"); - sql = r.getSql(); + sql = getSql("select id from t_cust order by id desc"); assertEquals("id", sql.getPreFrom()); assertEquals("from t_cust", sql.getPreWhere()); assertEquals("", sql.getPreHaving()); assertEquals("id desc", sql.getOrderBy()); - r = RawSqlBuilder - .parse("select id, sum(x) from t_cust where id > ? group by id order by id desc"); - sql = r.getSql(); + sql = getSql("select id, sum(x) from t_cust where id > ? group by id order by id desc"); assertEquals("id, sum(x)", sql.getPreFrom()); assertEquals("from t_cust where id > ?", sql.getPreWhere()); assertEquals("group by id", sql.getPreHaving()); @@ -94,9 +100,7 @@ public class TestRawSqlBuilder extends BaseTestCase { @Test public void testWithHaving() { - RawSqlBuilder r = RawSqlBuilder - .parse("select id, sum(x) from t_cust where id > ? group by id having sum(x) > ? order by id desc"); - Sql sql = r.getSql(); + Sql sql = getSql("select id, sum(x) from t_cust where id > ? group by id having sum(x) > ? order by id desc"); assertEquals("id, sum(x)", sql.getPreFrom()); assertEquals("from t_cust where id > ?", sql.getPreWhere()); assertEquals("group by id having sum(x) > ?", sql.getPreHaving()); @@ -104,9 +108,7 @@ public class TestRawSqlBuilder extends BaseTestCase { assertEquals("id desc", sql.getOrderBy()); // no where - r = RawSqlBuilder - .parse("select id, sum(x) from t_cust group by id having sum(x) > ? order by id desc"); - sql = r.getSql(); + sql = getSql("select id, sum(x) from t_cust group by id having sum(x) > ? order by id desc"); assertEquals("id, sum(x)", sql.getPreFrom()); assertEquals("from t_cust", sql.getPreWhere()); assertEquals("group by id having sum(x) > ?", sql.getPreHaving()); @@ -114,8 +116,7 @@ public class TestRawSqlBuilder extends BaseTestCase { assertEquals("id desc", sql.getOrderBy()); // no where, no order by - r = RawSqlBuilder.parse("select id, sum(x) from t_cust group by id having sum(x) > ?"); - sql = r.getSql(); + sql = getSql("select id, sum(x) from t_cust group by id having sum(x) > ?"); assertEquals("id, sum(x)", sql.getPreFrom()); assertEquals("from t_cust", sql.getPreWhere()); assertEquals("group by id having sum(x) > ?", sql.getPreHaving()); @@ -123,9 +124,7 @@ public class TestRawSqlBuilder extends BaseTestCase { assertEquals("order by", sql.getOrderByPrefix()); // no order by - r = RawSqlBuilder - .parse("select id, sum(x) from t_cust where id > ? group by id having sum(x) > ?"); - sql = r.getSql(); + sql = getSql("select id, sum(x) from t_cust where id > ? group by id having sum(x) > ?"); assertEquals("id, sum(x)", sql.getPreFrom()); assertEquals("from t_cust where id > ?", sql.getPreWhere()); assertEquals("group by id having sum(x) > ?", sql.getPreHaving()); @@ -140,15 +139,11 @@ public class TestRawSqlBuilder extends BaseTestCase { public void testWithOrderSiblingsByName() { String s = "SELECT ID, DESCRIPTION, NAME, PARENT_ID FROM SOME_TABLE WHERE lower(NAME) like :name START WITH ID = :parentId CONNECT BY PRIOR ID = PARENT_ID order siblings by NAME"; - - RawSql rawSql = RawSqlBuilder.parse(s).create(); - - Sql sql = rawSql.getSql(); + Sql sql = getSql(s); assertEquals("ID, DESCRIPTION, NAME, PARENT_ID", sql.getPreFrom()); assertEquals("order siblings by", sql.getOrderByPrefix()); assertEquals("NAME", sql.getOrderBy()); assertEquals("FROM SOME_TABLE WHERE lower(NAME) like :name START WITH ID = :parentId CONNECT BY PRIOR ID = PARENT_ID", sql.getPreWhere()); - } @@ -164,13 +159,13 @@ public class TestRawSqlBuilder extends BaseTestCase { "order by o.id, d.id asc"; - RawSql rawSql = RawSqlBuilder.parse(rs) + SpiRawSql rawSql = (SpiRawSql)RawSqlBuilder.parse(rs) .tableAliasMapping("c", "customer") .tableAliasMapping("d", "details") .tableAliasMapping("p", "details.product") .create(); - RawSql.ColumnMapping columnMapping = rawSql.getColumnMapping(); + SpiRawSql.ColumnMapping columnMapping = rawSql.getColumnMapping(); assertEquals(0, columnMapping.getIndexPosition("id")); assertEquals(1, columnMapping.getIndexPosition("status")); assertEquals(2, columnMapping.getIndexPosition("customer.id")); @@ -194,8 +189,8 @@ public class TestRawSqlBuilder extends BaseTestCase { RawSqlBuilder builder = RawSqlBuilder.parse(rs); - RawSql rawSql = builder.create(); - RawSql.ColumnMapping columnMapping = rawSql.getColumnMapping(); + SpiRawSql rawSql = (SpiRawSql)builder.create(); + SpiRawSql.ColumnMapping columnMapping = rawSql.getColumnMapping(); assertEquals(0, columnMapping.getIndexPosition("id")); assertEquals(1, columnMapping.getIndexPosition("status")); @@ -221,9 +216,9 @@ public class TestRawSqlBuilder extends BaseTestCase { " from o_order d" + " group by DATE_TRUNC('DAY', d.order_date)"; - RawSql rawSql = RawSqlBuilder.parse(sql).create(); + SpiRawSql rawSql = (SpiRawSql)RawSqlBuilder.parse(sql).create(); - RawSql.ColumnMapping columnMapping = rawSql.getColumnMapping(); + SpiRawSql.ColumnMapping columnMapping = rawSql.getColumnMapping(); assertEquals(0, columnMapping.getIndexPosition("day")); assertEquals(1, columnMapping.getIndexPosition("total")); diff --git a/src/test/java/io/ebean/TestRawSqlBuilderDistinct.java b/src/test/java/io/ebeaninternal/server/rawsql/TestRawSqlBuilderDistinct.java similarity index 62% rename from src/test/java/io/ebean/TestRawSqlBuilderDistinct.java rename to src/test/java/io/ebeaninternal/server/rawsql/TestRawSqlBuilderDistinct.java index e11526efb..146b6d5e0 100644 --- a/src/test/java/io/ebean/TestRawSqlBuilderDistinct.java +++ b/src/test/java/io/ebeaninternal/server/rawsql/TestRawSqlBuilderDistinct.java @@ -1,7 +1,8 @@ -package io.ebean; +package io.ebeaninternal.server.rawsql; -import io.ebean.RawSql.Sql; +import io.ebean.RawSql; import io.ebean.RawSqlBuilder; +import io.ebeaninternal.server.rawsql.SpiRawSql.Sql; import junit.framework.TestCase; import org.junit.Assert; @@ -9,8 +10,8 @@ public class TestRawSqlBuilderDistinct extends TestCase { public void testDistinct() { - RawSqlBuilder r = RawSqlBuilder.parse("select distinct id, name from t_cust"); - Sql sql = r.getSql(); + RawSql r = RawSqlBuilder.parse("select distinct id, name from t_cust").create(); + Sql sql = ((SpiRawSql)r).getSql(); Assert.assertEquals("id, name", sql.getPreFrom()); Assert.assertEquals("from t_cust", sql.getPreWhere()); Assert.assertEquals("", sql.getPreHaving()); diff --git a/src/test/java/io/ebean/TestRawSqlColumnParsing.java b/src/test/java/io/ebeaninternal/server/rawsql/TestRawSqlColumnParsing.java similarity index 96% rename from src/test/java/io/ebean/TestRawSqlColumnParsing.java rename to src/test/java/io/ebeaninternal/server/rawsql/TestRawSqlColumnParsing.java index 7faa92e26..47c74ebd7 100644 --- a/src/test/java/io/ebean/TestRawSqlColumnParsing.java +++ b/src/test/java/io/ebeaninternal/server/rawsql/TestRawSqlColumnParsing.java @@ -1,7 +1,7 @@ -package io.ebean; +package io.ebeaninternal.server.rawsql; -import io.ebean.RawSql.ColumnMapping; -import io.ebean.RawSql.ColumnMapping.Column; +import io.ebeaninternal.server.rawsql.SpiRawSql.ColumnMapping; +import io.ebeaninternal.server.rawsql.SpiRawSql.ColumnMapping.Column; import junit.framework.TestCase; import java.util.Map; diff --git a/src/test/java/io/ebeaninternal/server/rawsql/TestRawSqlParsing.java b/src/test/java/io/ebeaninternal/server/rawsql/TestRawSqlParsing.java index 814aa66f9..3d59d2033 100644 --- a/src/test/java/io/ebeaninternal/server/rawsql/TestRawSqlParsing.java +++ b/src/test/java/io/ebeaninternal/server/rawsql/TestRawSqlParsing.java @@ -1,7 +1,7 @@ package io.ebeaninternal.server.rawsql; import io.ebean.RawSql; -import io.ebean.RawSql.Sql; +import io.ebeaninternal.server.rawsql.SpiRawSql.Sql; import io.ebean.RawSqlBuilder; import junit.framework.TestCase; @@ -20,7 +20,7 @@ public class TestRawSqlParsing extends TestCase { //.columnMapping("sum(order_qty*unit_price)","totalAmount") .create(); - Sql rs = rawSql.getSql(); + Sql rs = ((SpiRawSql)rawSql).getSql(); String s = rs.toString(); assertTrue(s, s.contains("[order_id, sum")); diff --git a/src/test/java/org/tests/rawsql/TestRawSqlWithResultSet.java b/src/test/java/org/tests/rawsql/TestRawSqlWithResultSet.java index eea1eb313..238d203a2 100644 --- a/src/test/java/org/tests/rawsql/TestRawSqlWithResultSet.java +++ b/src/test/java/org/tests/rawsql/TestRawSqlWithResultSet.java @@ -4,6 +4,7 @@ import io.ebean.BaseTestCase; import io.ebean.Ebean; import io.ebean.FetchConfig; import io.ebean.RawSql; +import io.ebean.RawSqlBuilder; import io.ebean.Transaction; import org.tests.model.basic.Customer; import org.tests.model.basic.ResetBasicData; @@ -33,7 +34,7 @@ public class TestRawSqlWithResultSet extends BaseTestCase { // ResultSet will be closed by Ebean ResultSet resultSet = pstmt.executeQuery(); - RawSql rawSql = new RawSql(resultSet, "id", "name", "billingAddress.id"); + RawSql rawSql = RawSqlBuilder.resultSet(resultSet, "id", "name", "billingAddress.id"); List list = Ebean.find(Customer.class) .setRawSql(rawSql)