From 580b51381e157f4bc3644a5258711a20c14a8365 Mon Sep 17 00:00:00 2001 From: Robin Bygrave Date: Tue, 18 Aug 2015 23:57:11 +1200 Subject: [PATCH] #384 - ENH: Add findVersionsBetween(start, end) ... including implementation support for Oracle total recall --- .../java/com/avaje/ebean/ExpressionList.java | 9 ++++++ src/main/java/com/avaje/ebean/Query.java | 14 ++++++++ .../config/dbplatform/DbHistorySupport.java | 5 +++ .../dbplatform/DbViewHistorySupport.java | 11 +++++++ .../dbplatform/OracleDbHistorySupport.java | 5 +++ .../com/avaje/ebeaninternal/api/SpiQuery.java | 15 +++++++++ .../server/deploy/BeanDescriptor.java | 4 ++- .../server/deploy/BeanDescriptorManager.java | 18 ++++++++--- .../deploy/meta/DeployBeanDescriptor.java | 15 ++++++++- .../server/deploy/parse/AnnotationClass.java | 7 ++-- .../server/deploy/parse/ReadAnnotations.java | 10 +++--- .../server/expression/JunctionExpression.java | 5 +++ .../server/query/CQueryEngine.java | 24 ++++++++++---- .../server/query/CQueryPredicates.java | 10 ++++++ .../server/querydefn/DefaultOrmQuery.java | 32 +++++++++++++++++++ .../util/DefaultExpressionList.java | 5 +++ 16 files changed, 170 insertions(+), 19 deletions(-) diff --git a/src/main/java/com/avaje/ebean/ExpressionList.java b/src/main/java/com/avaje/ebean/ExpressionList.java index d4b95560b..99ea95c76 100644 --- a/src/main/java/com/avaje/ebean/ExpressionList.java +++ b/src/main/java/com/avaje/ebean/ExpressionList.java @@ -244,6 +244,15 @@ public interface ExpressionList extends Serializable { */ List> findVersions(); + /** + * Return versions of a @History entity bean between the 2 timestamps. + *

+ * Generally this query is expected to be a find by id or unique predicates query. + * It will execute the query against the history returning the versions of the bean. + *

+ */ + List> findVersionsBetween(Timestamp start, Timestamp end); + /** * Add some filter predicate expressions to the many property. */ diff --git a/src/main/java/com/avaje/ebean/Query.java b/src/main/java/com/avaje/ebean/Query.java index 65cf369de..18076529f 100644 --- a/src/main/java/com/avaje/ebean/Query.java +++ b/src/main/java/com/avaje/ebean/Query.java @@ -723,12 +723,26 @@ public interface Query extends Serializable { /** * Return versions of a @History entity bean. *

+ * Note that this query will work against view based history implementations + * but not sql2011 standards based implementations that require a start and + * end timestamp to be specified. + *

+ *

* Generally this query is expected to be a find by id or unique predicates query. * It will execute the query against the history returning the versions of the bean. *

*/ List> findVersions(); + /** + * Return versions of a @History entity bean between the 2 timestamps. + *

+ * Generally this query is expected to be a find by id or unique predicates query. + * It will execute the query against the history returning the versions of the bean. + *

+ */ + List> findVersionsBetween(Timestamp start, Timestamp end); + /** * Return the count of entities this query should return. *

diff --git a/src/main/java/com/avaje/ebean/config/dbplatform/DbHistorySupport.java b/src/main/java/com/avaje/ebean/config/dbplatform/DbHistorySupport.java index fbd17114e..173b4ba93 100644 --- a/src/main/java/com/avaje/ebean/config/dbplatform/DbHistorySupport.java +++ b/src/main/java/com/avaje/ebean/config/dbplatform/DbHistorySupport.java @@ -31,6 +31,11 @@ public interface DbHistorySupport { */ String getAsOfViewSuffix(String asOfViewSuffix); + /** + * Return the 'versions between timestamp' suffix. + */ + String getVersionsBetweenSuffix(String asOfViewSuffix); + /** * Return the 'as of' predicate added for the given table alias. * diff --git a/src/main/java/com/avaje/ebean/config/dbplatform/DbViewHistorySupport.java b/src/main/java/com/avaje/ebean/config/dbplatform/DbViewHistorySupport.java index 3fe900179..210027c66 100644 --- a/src/main/java/com/avaje/ebean/config/dbplatform/DbViewHistorySupport.java +++ b/src/main/java/com/avaje/ebean/config/dbplatform/DbViewHistorySupport.java @@ -28,6 +28,17 @@ public abstract class DbViewHistorySupport implements DbHistorySupport { return asOfViewSuffix; } + /** + * Returns the configured view suffix (same as getAsOfViewSuffix()). + * + * @param asOfViewSuffix the configured view suffix (typically "_with_history"). + */ + @Override + public String getVersionsBetweenSuffix(String asOfViewSuffix) { + // just return the configured asOfViewSuffix (using the view for versions between query) + return asOfViewSuffix; + } + /** * Return 2 if we have effective start and effective end as 2 columns. * Note that for postgres we can use a single range type so that returns 1. diff --git a/src/main/java/com/avaje/ebean/config/dbplatform/OracleDbHistorySupport.java b/src/main/java/com/avaje/ebean/config/dbplatform/OracleDbHistorySupport.java index 0ebd68738..e81263aa8 100644 --- a/src/main/java/com/avaje/ebean/config/dbplatform/OracleDbHistorySupport.java +++ b/src/main/java/com/avaje/ebean/config/dbplatform/OracleDbHistorySupport.java @@ -13,6 +13,11 @@ public class OracleDbHistorySupport extends DbStandardHistorySupport { return " as of TIMESTAMP ?"; } + @Override + public String getVersionsBetweenSuffix(String asOfViewSuffix) { + return " versions between timestamp ? and ?"; + } + /** * Returns the Oracle specific effective start column. */ diff --git a/src/main/java/com/avaje/ebeaninternal/api/SpiQuery.java b/src/main/java/com/avaje/ebeaninternal/api/SpiQuery.java index 6d023cd06..ec54a63a1 100644 --- a/src/main/java/com/avaje/ebeaninternal/api/SpiQuery.java +++ b/src/main/java/com/avaje/ebeaninternal/api/SpiQuery.java @@ -145,6 +145,21 @@ public interface SpiQuery extends Query { */ TemporalMode getTemporalMode(); + /** + * Return true if this is a find versions between query. + */ + boolean isVersionsBetween(); + + /** + * Return the find versions start timestamp. + */ + Timestamp getVersionStart(); + + /** + * Return the find versions end timestamp. + */ + Timestamp getVersionEnd(); + /** * Return true if this is a 'As Of' query. */ diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptor.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptor.java index d0a6a343b..754c02a00 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptor.java +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptor.java @@ -133,6 +133,7 @@ public class BeanDescriptor implements MetaBeanInfo { */ private final String baseTable; private final String baseTableAsOf; + private final String baseTableVersionsBetween; private final boolean historySupport; /** @@ -354,6 +355,7 @@ public class BeanDescriptor implements MetaBeanInfo { this.historySupport = deploy.isHistorySupport(); this.baseTable = InternString.intern(deploy.getBaseTable()); this.baseTableAsOf = deploy.getBaseTableAsOf(); + this.baseTableVersionsBetween = deploy.getBaseTableVersionsBetween(); this.autoFetchTunable = EntityType.ORM.equals(entityType) && (beanFinder == null); // helper object used to derive lists of properties @@ -1686,7 +1688,7 @@ public class BeanDescriptor implements MetaBeanInfo { */ public String getBaseTable(SpiQuery.TemporalMode mode) { switch (mode) { - case VERSIONS: return baseTableAsOf; + case VERSIONS: return baseTableVersionsBetween; case AS_OF: return baseTableAsOf; default: return baseTable; } diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptorManager.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptorManager.java index 07894e988..1778907c9 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptorManager.java +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptorManager.java @@ -176,7 +176,8 @@ public class BeanDescriptorManager implements BeanDescriptorMap { this.eagerFetchLobs = serverConfig.isEagerFetchLobs(); this.asOfViewSuffix = getAsOfViewSuffix(databasePlatform, serverConfig); - this.readAnnotations = new ReadAnnotations(config.getGeneratedPropertyFactory(), asOfViewSuffix); + String versionsBetweenSuffix = getVersionsBetweenSuffix(databasePlatform, serverConfig); + this.readAnnotations = new ReadAnnotations(config.getGeneratedPropertyFactory(), asOfViewSuffix, versionsBetweenSuffix); this.bootupClasses = config.getBootupClasses(); this.createProperties = config.getDeployCreateProperties(); this.namingConvention = serverConfig.getNamingConvention(); @@ -201,8 +202,7 @@ public class BeanDescriptorManager implements BeanDescriptorMap { } /** - * Return the AsOfViewSuffix allowing the DbHistorySupport to override the value in - * the case where there is no view (Oracle, DB2, MS SQL Server). + * Return the AsOfViewSuffix based on the DbHistorySupport. */ private String getAsOfViewSuffix(DatabasePlatform databasePlatform, ServerConfig serverConfig) { @@ -211,6 +211,16 @@ public class BeanDescriptorManager implements BeanDescriptorMap { return (historySupport == null ) ? serverConfig.getAsOfViewSuffix() : historySupport.getAsOfViewSuffix(serverConfig.getAsOfViewSuffix()); } + /** + * Return the versions between timestamp suffix based on the DbHistorySupport. + */ + private String getVersionsBetweenSuffix(DatabasePlatform databasePlatform, ServerConfig serverConfig) { + + DbHistorySupport historySupport = databasePlatform.getHistorySupport(); + // with historySupport returns a simple view suffix or the sql2011 versions between timestamp suffix + return (historySupport == null ) ? serverConfig.getAsOfViewSuffix() : historySupport.getVersionsBetweenSuffix(serverConfig.getAsOfViewSuffix()); + } + public BeanDescriptor getBeanDescriptorById(String descriptorId) { return idDescMap.get(descriptorId); } @@ -1029,7 +1039,7 @@ public class BeanDescriptorManager implements BeanDescriptorMap { if (!EntityType.ORM.equals(desc.getEntityType())) { // not using base table - desc.setBaseTable(null, null); + desc.setBaseTable(null, null, null); } // mark transient properties diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/meta/DeployBeanDescriptor.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/meta/DeployBeanDescriptor.java index e344a6a98..83f2acad9 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/deploy/meta/DeployBeanDescriptor.java +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/meta/DeployBeanDescriptor.java @@ -115,6 +115,8 @@ public class DeployBeanDescriptor { private String baseTableAsOf; + private String baseTableVersionsBetween; + private boolean historySupport; private TableName baseTableFull; @@ -464,10 +466,20 @@ public class DeployBeanDescriptor { return baseTable; } + /** + * Return the base table with as of suffix. + */ public String getBaseTableAsOf() { return baseTableAsOf; } + /** + * Return the base table with versions between suffix. + */ + public String getBaseTableVersionsBetween() { + return baseTableVersionsBetween; + } + /** * Return the base table with full structure. */ @@ -478,10 +490,11 @@ public class DeployBeanDescriptor { /** * Set the base table. Only properties mapped to the base table are by default persisted. */ - public void setBaseTable(TableName baseTableFull, String asOfSuffix) { + public void setBaseTable(TableName baseTableFull, String asOfSuffix, String versionsBetweenSuffix) { this.baseTableFull = baseTableFull; this.baseTable = baseTableFull == null ? null : baseTableFull.getQualifiedName(); this.baseTableAsOf = baseTable + asOfSuffix; + this.baseTableVersionsBetween = baseTable + versionsBetweenSuffix; } public void sortProperties() { diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/AnnotationClass.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/AnnotationClass.java index 651ca8a43..947241b1a 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/AnnotationClass.java +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/AnnotationClass.java @@ -29,9 +29,12 @@ public class AnnotationClass extends AnnotationParser { private final String asOfViewSuffix; - public AnnotationClass(DeployBeanInfo info, boolean validationAnnotations, String asOfViewSuffix) { + private final String versionsBetweenSuffix; + + public AnnotationClass(DeployBeanInfo info, boolean validationAnnotations, String asOfViewSuffix, String versionsBetweenSuffix) { super(info, validationAnnotations); this.asOfViewSuffix = asOfViewSuffix; + this.versionsBetweenSuffix = versionsBetweenSuffix; } /** @@ -52,7 +55,7 @@ public class AnnotationClass extends AnnotationParser { // default the TableName using NamingConvention. TableName tableName = namingConvention.getTableName(descriptor.getBeanType()); - descriptor.setBaseTable(tableName, asOfViewSuffix); + descriptor.setBaseTable(tableName, asOfViewSuffix, versionsBetweenSuffix); } } diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/ReadAnnotations.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/ReadAnnotations.java index 33ec3a759..e9b1b122f 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/ReadAnnotations.java +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/ReadAnnotations.java @@ -21,6 +21,8 @@ public class ReadAnnotations { */ private final String asOfViewSuffix; + private final String versionsBetweenSuffix; + /** * True if the javax validation annotations are present in the classpath. */ @@ -31,9 +33,10 @@ public class ReadAnnotations { */ private final boolean jacksonAnnotations; - public ReadAnnotations(GeneratedPropertyFactory generatedPropFactory, String asOfViewSuffix) { + public ReadAnnotations(GeneratedPropertyFactory generatedPropFactory, String asOfViewSuffix, String versionsBetweenSuffix) { this.generatedPropFactory = generatedPropFactory; this.asOfViewSuffix = asOfViewSuffix; + this.versionsBetweenSuffix = versionsBetweenSuffix; this.javaxValidationAnnotations = ClassUtil.isJavaxValidationAnnotationsPresent(); this.jacksonAnnotations = ClassUtil.isJacksonAnnotationsPresent(); } @@ -48,12 +51,11 @@ public class ReadAnnotations { public void readInitial(DeployBeanInfo info, boolean eagerFetchLobs) { try { - new AnnotationClass(info, javaxValidationAnnotations, asOfViewSuffix).parse(); + new AnnotationClass(info, javaxValidationAnnotations, asOfViewSuffix, versionsBetweenSuffix).parse(); new AnnotationFields(generatedPropFactory, info, javaxValidationAnnotations, jacksonAnnotations, eagerFetchLobs).parse(); } catch (RuntimeException e) { - String msg = "Error reading annotations for " + info; - throw new RuntimeException(msg, e); + throw new RuntimeException("Error reading annotations for " + info, e); } } diff --git a/src/main/java/com/avaje/ebeaninternal/server/expression/JunctionExpression.java b/src/main/java/com/avaje/ebeaninternal/server/expression/JunctionExpression.java index 359895463..10ce12a86 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/expression/JunctionExpression.java +++ b/src/main/java/com/avaje/ebeaninternal/server/expression/JunctionExpression.java @@ -228,6 +228,11 @@ abstract class JunctionExpression implements Junction, SpiExpression, Expr return exprList.findVersions(); } + @Override + public List> findVersionsBetween(Timestamp start, Timestamp end) { + return exprList.findVersionsBetween(start, end); + } + @Override public Query apply(PathProperties pathProperties) { return exprList.apply(pathProperties); diff --git a/src/main/java/com/avaje/ebeaninternal/server/query/CQueryEngine.java b/src/main/java/com/avaje/ebeaninternal/server/query/CQueryEngine.java index 08504ad0f..73bd7cf49 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/query/CQueryEngine.java +++ b/src/main/java/com/avaje/ebeaninternal/server/query/CQueryEngine.java @@ -173,6 +173,12 @@ public class CQueryEngine { SpiQuery query = request.getQuery(); + if (query.isVersionsBetween() && !historySupport.isBindAtFromClause()) { + // just add as normal predicates using the lower bound + query.where().gt(getSysPeriodLower(query), query.getVersionStart()); + query.where().lt(getSysPeriodLower(query), query.getVersionEnd()); + } + // order by id asc, lower sys period desc query.orderBy().asc(request.getBeanDescriptor().getIdProperty().getName()); query.orderBy().desc(getSysPeriodLower(query)); @@ -207,14 +213,18 @@ public class CQueryEngine { BeanDescriptor descriptor = request.getBeanDescriptor(); - Version current = versions.get(0); - for (int i = 1; i < versions.size(); i++) { - Version next = versions.get(i); - deriveVersionDiff(current, next, descriptor); - current = next; + if (!versions.isEmpty()) { + Version current = versions.get(0); + if (versions.size() > 1) { + for (int i = 1; i < versions.size(); i++) { + Version next = versions.get(i); + deriveVersionDiff(current, next, descriptor); + current = next; + } + } + // put an empty map into the last one + current.setDiff(new LinkedHashMap()); } - // put an empty map into the last one - current.setDiff(new LinkedHashMap()); } private void deriveVersionDiff(Version current, Version prior, BeanDescriptor descriptor) { diff --git a/src/main/java/com/avaje/ebeaninternal/server/query/CQueryPredicates.java b/src/main/java/com/avaje/ebeaninternal/server/query/CQueryPredicates.java index d60a02403..c0dd5851e 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/query/CQueryPredicates.java +++ b/src/main/java/com/avaje/ebeaninternal/server/query/CQueryPredicates.java @@ -131,6 +131,16 @@ public class CQueryPredicates { StringBuilder bindLog = new StringBuilder(); + if (query.isVersionsBetween() && binder.isBindAsOfWithFromClause()) { + // sql2011 based versions between timestamp syntax + Timestamp start = query.getVersionStart(); + Timestamp end = query.getVersionEnd(); + bindLog.append("between ").append(start).append(" and ").append(end); + binder.bindObject(dataBind, start); + binder.bindObject(dataBind, end); + bindLog.append(", "); + } + List historyTableAlias = query.getAsOfTableAlias(); if (historyTableAlias != null && binder.isBindAsOfWithFromClause()) { // bind the asOf value for each table alias as part of the from/join clauses 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 0007f1667..a313db2e5 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/querydefn/DefaultOrmQuery.java +++ b/src/main/java/com/avaje/ebeaninternal/server/querydefn/DefaultOrmQuery.java @@ -170,6 +170,9 @@ public class DefaultOrmQuery implements SpiQuery { private TemporalMode temporalMode = TemporalMode.CURRENT; + private Timestamp versionsStart; + private Timestamp versionsEnd; + private int bufferFetchSizeHint; private boolean usageProfiling = true; @@ -714,6 +717,7 @@ public class DefaultOrmQuery implements SpiQuery { builder.add(disableLazyLoading); builder.add(id != null); builder.add(asOf != null); + builder.add(versionsStart != null); builder.add(rawSql == null ? 0 : rawSql.queryHash()); builder.add(includeTableJoin != null ? includeTableJoin.queryHash() : 0); builder.add(rootTableAlias); @@ -785,6 +789,8 @@ public class DefaultOrmQuery implements SpiQuery { hc = hc * 31 + (havingExpressions == null ? 0 : havingExpressions.queryBindHash()); hc = hc * 31 + (bindParams == null ? 0 : bindParams.queryBindHash()); hc = hc * 31 + (asOf == null ? 0 : asOf.hashCode()); + hc = hc * 31 + (versionsStart == null ? 0 : versionsStart.hashCode()); + hc = hc * 31 + (versionsEnd == null ? 0 : versionsEnd.hashCode()); return hc; } @@ -843,6 +849,21 @@ public class DefaultOrmQuery implements SpiQuery { return maxRows > 0 || firstRow > 0; } + @Override + public boolean isVersionsBetween() { + return versionsStart != null; + } + + @Override + public Timestamp getVersionStart() { + return versionsStart; + } + + @Override + public Timestamp getVersionEnd() { + return versionsEnd; + } + public Boolean isReadOnly() { return readOnly; } @@ -957,6 +978,17 @@ public class DefaultOrmQuery implements SpiQuery { return server.findVersions(this, null); } + @Override + public List> findVersionsBetween(Timestamp start, Timestamp end) { + if (start == null || end == null) { + throw new IllegalArgumentException("start and end must not be null"); + } + this.temporalMode = TemporalMode.VERSIONS; + this.versionsStart = start; + this.versionsEnd = end; + return server.findVersions(this, null); + } + public QueryIterator findIterate() { return server.findIterate(this, null); } diff --git a/src/main/java/com/avaje/ebeaninternal/util/DefaultExpressionList.java b/src/main/java/com/avaje/ebeaninternal/util/DefaultExpressionList.java index ce1386529..8ab8f66d4 100644 --- a/src/main/java/com/avaje/ebeaninternal/util/DefaultExpressionList.java +++ b/src/main/java/com/avaje/ebeaninternal/util/DefaultExpressionList.java @@ -118,6 +118,11 @@ public class DefaultExpressionList implements SpiExpressionList { return query.findVersions(); } + @Override + public List> findVersionsBetween(Timestamp start, Timestamp end) { + return query.findVersionsBetween(start, end); + } + @Override public ExpressionList where() { return query.where();