#752 - Refactor: SQL generation for @History support with Postgres, MySql (views/triggers based) such that it more closely aligns with SQL 2011 based SQL

This commit is contained in:
Robin Bygrave
2016-06-23 11:33:01 +12:00
parent f8bda05b37
commit 2185c8a886
17 changed files with 148 additions and 103 deletions
@@ -6,11 +6,13 @@ package com.avaje.ebean.config.dbplatform;
public interface DbHistorySupport {
/**
* Return true if the 'As of' predicate is part of the from clause
* (more standard sql2011). So true for Oracle total recall and false
* for Postgres and MySql (where we use views and history tables).
* Return true if the implementation is SQL2011 standards based.
* <p>
* Non standards based means we need to add additional predicates into the
* JOIN ON clause and add an additional predicate for the base table.
* </p>
*/
boolean isBindWithFromClause();
boolean isStandardsBased();
/**
* Return the number of columns bound in a 'As Of' predicate.
@@ -5,11 +5,8 @@ package com.avaje.ebean.config.dbplatform;
*/
public abstract class DbStandardHistorySupport implements DbHistorySupport {
/**
* Return true as with sql2011 the 'as of timestamp' clause included in from or join clause.
*/
@Override
public boolean isBindWithFromClause() {
public boolean isStandardsBased() {
return true;
}
@@ -9,11 +9,8 @@ package com.avaje.ebean.config.dbplatform;
*/
public abstract class DbViewHistorySupport implements DbHistorySupport {
/**
* Return false for view based implementations where we append extra 'as of' predicates to the end.
*/
@Override
public boolean isBindWithFromClause() {
public boolean isStandardsBased() {
return false;
}
@@ -223,14 +223,14 @@ public interface SpiQuery<T> extends Query<T> {
Timestamp getAsOf();
/**
* Add a table alias for a @History entity involved in a 'As Of' query.
* Increment the counter of tables used in 'As Of' query.
*/
void addAsOfTableAlias(String tableAlias);
void incrementAsOfTableCount();
/**
* Return the list of table alias involved in a 'As Of' query that have @History support.
* Return the table alias used for the base table.
*/
List<String> getAsOfTableAlias();
int getAsOfTableCount();
void addSoftDeletePredicate(String softDeletePredicate);
@@ -235,7 +235,7 @@ public class InternalConfiguration {
if (historySupport == null) {
return new Binder(typeManager, 0, false, jsonHandler, dataTimeZone);
}
return new Binder(typeManager, historySupport.getBindCount(), historySupport.isBindWithFromClause(), jsonHandler, dataTimeZone);
return new Binder(typeManager, historySupport.getBindCount(), historySupport.isStandardsBased(), jsonHandler, dataTimeZone);
}
/**
@@ -513,4 +513,11 @@ public final class OrmQueryRequest<T> extends BeanRequest implements BeanQueryRe
public boolean isAuditReads() {
return !query.isDisableReadAudit() && beanDescriptor.isReadAuditing();
}
/**
* Return the base table alias for this query.
*/
public String getBaseTableAlias() {
return query.getAlias() == null ? beanDescriptor.getBaseTableAlias() : query.getAlias();
}
}
@@ -33,7 +33,7 @@ public class Binder {
private final int asOfBindCount;
private final boolean bindAsOfWithFromClause;
private final boolean asOfStandardsBased;
private final DbExpressionHandler dbExpressionHandler;
@@ -42,12 +42,12 @@ public class Binder {
/**
* Set the PreparedStatement with which to bind variables to.
*/
public Binder(TypeManager typeManager, int asOfBindCount, boolean bindAsOfWithFromClause,
public Binder(TypeManager typeManager, int asOfBindCount, boolean asOfStandardsBased,
DbExpressionHandler dbExpressionHandler, DataTimeZone dataTimeZone) {
this.typeManager = typeManager;
this.asOfBindCount = asOfBindCount;
this.bindAsOfWithFromClause = bindAsOfWithFromClause;
this.asOfStandardsBased = asOfStandardsBased;
this.dbExpressionHandler = dbExpressionHandler;
this.dataTimeZone = dataTimeZone;
}
@@ -60,12 +60,10 @@ public class Binder {
}
/**
* Return true if the 'as of' predicates are in the from/join clause in which case the timestamp is
* bound early (before all the other predicates ala Oracle). Return false if the 'as of' predicates are
* appended to the end of the predicates and the timestamp is bound last (Postgres, MySql).
* Return true if the 'as of' history support is SQL2011 standards based.
*/
public boolean isBindAsOfWithFromClause() {
return bindAsOfWithFromClause;
public boolean isAsOfStandardsBased() {
return asOfStandardsBased;
}
/**
@@ -472,12 +472,14 @@ public class CQueryBuilder {
hasWhere = true;
}
int asOfCount = query.getAsOfTableCount();
if (asOfCount > 0 && !historySupport.isStandardsBased()) {
hasWhere = appendWhere(hasWhere, sb);
sb.append(historySupport.getAsOfPredicate(request.getBaseTableAlias()));
}
if (request.isFindById() || query.getId() != null) {
if (hasWhere) {
sb.append(" and ");
} else {
sb.append(" where ");
}
appendWhere(hasWhere, sb);
BeanDescriptor<?> desc = request.getBeanDescriptor();
String idSql = desc.getIdBinderIdSql();
@@ -515,24 +517,6 @@ public class CQueryBuilder {
sb.append(dbFilterMany);
}
List<String> asOfTableAlias = query.getAsOfTableAlias();
if (asOfTableAlias != null && !historySupport.isBindAtFromClause()) {
// append the effective date predicates for each table alias
// that maps to a @History entity involved in this query
// Do this when history using separate tables/views (PG, MySql etc)
if (!hasWhere) {
sb.append(" where ");
} else {
sb.append("and ");
}
for (int i = 0; i < asOfTableAlias.size(); i++) {
if (i > 0) {
sb.append(" and ");
}
sb.append(historySupport.getAsOfPredicate(asOfTableAlias.get(i)));
}
}
if (!query.isIncludeSoftDeletes()) {
List<String> softDeletePredicates = query.getSoftDeletePredicates();
if (softDeletePredicates != null) {
@@ -565,6 +549,18 @@ public class CQueryBuilder {
}
/**
* Append where or and based on the hasWhere flag.
*/
private boolean appendWhere(boolean hasWhere, StringBuilder sb) {
if (hasWhere) {
sb.append(" and ");
} else {
sb.append(" where ");
}
return true;
}
/**
* Convert the dbOrderBy clause to be safe for adding to select. This is done when 'distinct' is
* used.
@@ -206,7 +206,7 @@ public class CQueryEngine {
SpiQuery<T> query = request.getQuery();
String sysPeriodLower = getSysPeriodLower(query);
if (query.isVersionsBetween() && !historySupport.isBindAtFromClause()) {
if (query.isVersionsBetween() && !historySupport.isStandardsBased()) {
// just add as normal predicates using the lower bound
query.where().gt(sysPeriodLower, query.getVersionStart());
query.where().lt(sysPeriodLower, query.getVersionEnd());
@@ -31,11 +31,10 @@ public class CQueryHistorySupport {
}
/**
* Return true if the bind of 'as of' timestamp occurs with the from clause
* rather than at the end.
* Return true if the underlying history support is standards based.
*/
public boolean isBindAtFromClause() {
return dbHistorySupport.isBindWithFromClause();
public boolean isStandardsBased() {
return dbHistorySupport.isStandardsBased();
}
/**
@@ -126,7 +126,7 @@ public class CQueryPredicates {
updateProperties.bind(binder, dataBind);
}
if (query.isVersionsBetween() && binder.isBindAsOfWithFromClause()) {
if (query.isVersionsBetween() && binder.isAsOfStandardsBased()) {
// sql2011 based versions between timestamp syntax
Timestamp start = query.getVersionStart();
Timestamp end = query.getVersionEnd();
@@ -136,13 +136,13 @@ public class CQueryPredicates {
dataBind.append(", ");
}
List<String> historyTableAlias = query.getAsOfTableAlias();
if (historyTableAlias != null && binder.isBindAsOfWithFromClause()) {
int asOfTableCount = query.getAsOfTableCount();
if (asOfTableCount > 0) {
// bind the asOf value for each table alias as part of the from/join clauses
// there is one effective date predicate per table alias
Timestamp asOf = query.getAsOf();
dataBind.append("asOf ").append(asOf);
for (int i = 0; i < historyTableAlias.size() * binder.getAsOfBindCount(); i++) {
for (int i = 0; i < asOfTableCount * binder.getAsOfBindCount(); i++) {
binder.bindObject(dataBind, asOf);
}
dataBind.append(", ");
@@ -167,16 +167,6 @@ public class CQueryPredicates {
filterMany.bind(dataBind);
}
if (historyTableAlias != null && !binder.isBindAsOfWithFromClause()) {
// bind the asOf value for each table alias after all the normal predicates
// there is one effective date predicate per table alias
Timestamp asOf = query.getAsOf();
dataBind.append(" asOf ").append(asOf);
for (int i = 0; i < historyTableAlias.size() * binder.getAsOfBindCount(); i++) {
binder.bindObject(dataBind, asOf);
}
}
if (having != null) {
having.bind(dataBind);
}
@@ -106,8 +106,8 @@ public class DefaultDbSqlContext implements DbSqlContext {
tableJoins.add(joinKey);
sb.append(" ");
sb.append(type);
sb.append(" ").append(type);
boolean addAsOfOnClause = false;
if (draftSupport != null) {
appendTable(table, draftSupport.getDraftTable(table));
@@ -117,32 +117,32 @@ public class DefaultDbSqlContext implements DbSqlContext {
} else {
// check if there is an associated history table and if so
// use the unionAll view - we expect an additional predicate to match
appendTable(table, historySupport.getAsOfView(table));
String asOfView = historySupport.getAsOfView(table);
appendTable(table, asOfView);
if (asOfView != null) {
addAsOfOnClause = !historySupport.isStandardsBased();
}
}
sb.append(a2);
sb.append(" on ");
for (int i = 0; i < cols.length; i++) {
TableJoinColumn pair = cols[i];
if (i > 0) {
sb.append(" and ");
}
sb.append(a2);
sb.append(".").append(pair.getForeignDbColumn());
sb.append(a2).append(".").append(pair.getForeignDbColumn());
sb.append(" = ");
sb.append(a1);
sb.append(".").append(pair.getLocalDbColumn());
sb.append(a1).append(".").append(pair.getLocalDbColumn());
}
// add on any inheritance where clause
if (inheritance != null && inheritance.length() > 0) {
sb.append(" and ");
sb.append(a2);
sb.append(".");
sb.append(inheritance);
sb.append(" and ").append(a2).append(".").append(inheritance);
}
if (addAsOfOnClause) {
sb.append(" and ").append(historySupport.getAsOfPredicate(a2));
}
sb.append(" ");
@@ -111,7 +111,7 @@ public class SqlTreeBuilder {
this.queryDetail = query.getDetail();
this.predicates = predicates;
this.alias = new SqlTreeAlias(request.getQuery().getAlias() == null ? request.getBeanDescriptor().getBaseTableAlias() : request.getQuery().getAlias());
this.alias = new SqlTreeAlias(request.getBaseTableAlias());
this.ctx = new DefaultDbSqlContext(alias, tableAliasPlaceHolder, columnAliasPrefix, !subQuery, historySupport, draftSupport);
}
@@ -81,7 +81,7 @@ public class SqlTreeNodeBean implements SqlTreeNode {
* Table alias set if this bean node includes a join to a intersection
* table and that table has history support.
*/
protected String intersectionAsOfTableAlias;
private boolean intersectionAsOfTableAlias;
/**
* Construct for Raw SQL.
@@ -501,11 +501,10 @@ public class SqlTreeNodeBean implements SqlTreeNode {
// if history on this bean type add it's alias
// for each alias we add an effect date predicate
if (desc.isHistorySupport()) {
query.addAsOfTableAlias(baseTableAlias);
query.incrementAsOfTableCount();
}
if (intersectionAsOfTableAlias != null) {
// adds the 'as of' predicate for this intersection table
query.addAsOfTableAlias(intersectionAsOfTableAlias);
if (intersectionAsOfTableAlias) {
query.incrementAsOfTableCount();
}
for (int i = 0; i < children.length; i++) {
children[i].addAsOfTableAlias(query);
@@ -531,7 +530,7 @@ public class SqlTreeNodeBean implements SqlTreeNode {
TableJoin manyToManyJoin = manyProp.getIntersectionTableJoin();
manyToManyJoin.addJoin(joinType, parentAlias, alias2, ctx);
if (!manyProp.isExcludedFromHistory()) {
intersectionAsOfTableAlias = alias2;
intersectionAsOfTableAlias = true;
}
return nodeBeanProp.addJoin(joinType, alias2, alias, ctx);
@@ -144,10 +144,7 @@ public class DefaultOrmQuery<T> implements SpiQuery<T> {
private DefaultExpressionList<T> havingExpressions;
/**
* The list of table alias associated with @History entity beans.
*/
private List<String> asOfTableAlias;
private int asOfTableCount;
/**
* Set for flashback style 'as of' query.
@@ -281,21 +278,14 @@ public class DefaultOrmQuery<T> implements SpiQuery<T> {
return softDeletePredicates;
}
/**
* This table alias is for a @History entity involved in the query and as
* such we need to add a 'as of predicate' to the query using this alias.
*/
@Override
public void addAsOfTableAlias(String tableAlias) {
if (asOfTableAlias == null) {
asOfTableAlias = new ArrayList<String>();
}
asOfTableAlias.add(tableAlias);
public void incrementAsOfTableCount() {
asOfTableCount++;
}
@Override
public List<String> getAsOfTableAlias() {
return asOfTableAlias;
public int getAsOfTableCount() {
return asOfTableCount;
}
@Override