mirror of
https://github.com/ebean-orm/ebean.git
synced 2026-10-24 10:38:57 +00:00
#382 - ENH: Add support for @History using Oracle Total Recall ... for 'as of' queries
This commit is contained in:
@@ -5,14 +5,32 @@ 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).
|
||||
*/
|
||||
boolean isBindWithFromClause();
|
||||
|
||||
/**
|
||||
* Return the number of columns bound in a 'As Of' predicate.
|
||||
* <p>
|
||||
* Typically this is 2 but 1 for postgres using it's range type.
|
||||
* This is 1 for more standard sql2011 style and Postgres which has the
|
||||
* special range type and 2 for view based solutions with 2 columns such as
|
||||
* MySql.
|
||||
* </p>
|
||||
*/
|
||||
int getBindCount();
|
||||
|
||||
/**
|
||||
* For sql2011 style this ignores the passed in view suffix and returns something
|
||||
* like the ' as of timestamp ?' clause to be appended after the base table name.
|
||||
*
|
||||
* @param asOfViewSuffix the configured view suffix (typically "_with_history").
|
||||
* @return The suffix appended after the base table name in the from and join clauses.
|
||||
*/
|
||||
String getAsOfViewSuffix(String asOfViewSuffix);
|
||||
|
||||
/**
|
||||
* Return the 'as of' predicate added for the given table alias.
|
||||
*
|
||||
@@ -26,7 +44,7 @@ public interface DbHistorySupport {
|
||||
* Return the column for the system period lower bound that will be included in findVersions() queries.
|
||||
*
|
||||
* @param tableAlias the table alias which will typically be 't0'
|
||||
* @param sysPeriod the name of the sys_period column
|
||||
* @param sysPeriod the name of the sys_period column
|
||||
*/
|
||||
String getSysPeriodLower(String tableAlias, String sysPeriod);
|
||||
|
||||
@@ -34,7 +52,7 @@ public interface DbHistorySupport {
|
||||
* Return the column for the system period upper bound that will be included in findVersions() queries.
|
||||
*
|
||||
* @param tableAlias the table alias which will typically be 't0'
|
||||
* @param sysPeriod the name of the sys_period column
|
||||
* @param sysPeriod the name of the sys_period column
|
||||
*/
|
||||
String getSysPeriodUpper(String tableAlias, String sysPeriod);
|
||||
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
package com.avaje.ebean.config.dbplatform;
|
||||
|
||||
/**
|
||||
* SQL2011 based history support using 'as of timestamp' type clause appended as part of the the from or join clause.
|
||||
*/
|
||||
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() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return 1 as the bind count (not 2 for effective start and effective end columns).
|
||||
*/
|
||||
@Override
|
||||
public int getBindCount() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return null - not used for sql2011 based history.
|
||||
*/
|
||||
@Override
|
||||
public String getAsOfPredicate(String tableAlias, String sysPeriod) {
|
||||
// not used for sql2011 based history
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
package com.avaje.ebean.config.dbplatform;
|
||||
|
||||
/**
|
||||
* Database view based implementation of DbHistorySupport.
|
||||
* <p>
|
||||
* These implementations have explicit history tables, a view to union the
|
||||
* base table and history table and triggers to maintain the history.
|
||||
* </p>
|
||||
*/
|
||||
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() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the configured view suffix.
|
||||
*
|
||||
* @param asOfViewSuffix the configured view suffix (typically "_with_history").
|
||||
*/
|
||||
@Override
|
||||
public String getAsOfViewSuffix(String asOfViewSuffix) {
|
||||
// just return the configured suffix
|
||||
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.
|
||||
*/
|
||||
@Override
|
||||
public int getBindCount() {
|
||||
return 2;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the 'as of' predicate clause appended to the end of the normal query predicates.
|
||||
*/
|
||||
@Override
|
||||
public String getAsOfPredicate(String asOfTableAlias, String asOfSysPeriod) {
|
||||
|
||||
// (sys_period_start < ? and (sys_period_end is null or sys_period_end > ?));
|
||||
return "(" + asOfTableAlias + "." + asOfSysPeriod + "_start" + " < ? and (" + asOfTableAlias + "." + asOfSysPeriod + "_end" + " is null or " + asOfTableAlias + "." + asOfSysPeriod + "_end" + " > ?))";
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the lower bound column prepended with the table alias.
|
||||
*
|
||||
* @param tableAlias the table alias
|
||||
* @param sysPeriod the name of the sys_period column
|
||||
*/
|
||||
@Override
|
||||
public String getSysPeriodLower(String tableAlias, String sysPeriod) {
|
||||
return tableAlias + "." + sysPeriod + "_start";
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the upper bound column prepended with the table alias.
|
||||
*
|
||||
* @param tableAlias the table alias
|
||||
* @param sysPeriod the name of the sys_period column
|
||||
*/
|
||||
@Override
|
||||
public String getSysPeriodUpper(String tableAlias, String sysPeriod) {
|
||||
return tableAlias + "." + sysPeriod + "_end";
|
||||
}
|
||||
}
|
||||
@@ -3,33 +3,7 @@ package com.avaje.ebean.config.dbplatform;
|
||||
/**
|
||||
* Runtime support for @History with MySql.
|
||||
*/
|
||||
public class MySqlHistorySupport implements DbHistorySupport {
|
||||
public class MySqlHistorySupport extends DbViewHistorySupport {
|
||||
|
||||
@Override
|
||||
public int getBindCount() {
|
||||
return 2;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAsOfPredicate(String asOfTableAlias, String asOfSysPeriod) {
|
||||
|
||||
StringBuilder sb = new StringBuilder(90);
|
||||
sb.append("(");
|
||||
sb.append(asOfTableAlias).append(".").append(asOfSysPeriod).append("_start").append(" < ? and (");
|
||||
sb.append(asOfTableAlias).append(".").append(asOfSysPeriod).append("_end").append(" is null or ");
|
||||
sb.append(asOfTableAlias).append(".").append(asOfSysPeriod).append("_end").append(" > ?))");
|
||||
|
||||
// (sys_period_start < ? and (sys_period_end is null or sys_period_end > ?));
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getSysPeriodLower(String tableAlias, String sysPeriod) {
|
||||
return tableAlias+"."+sysPeriod+"_start";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getSysPeriodUpper(String tableAlias, String sysPeriod) {
|
||||
return tableAlias+"."+sysPeriod+"_end";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ public class Oracle10Platform extends DatabasePlatform {
|
||||
this.dbEncrypt = new Oracle10DbEncrypt();
|
||||
this.sqlLimiter = new RownumSqlLimiter();
|
||||
this.platformDdl = new Oracle10Ddl(this.dbTypeMap, this.dbIdentity);
|
||||
this.historySupport = new OracleDbHistorySupport();
|
||||
|
||||
// Not using getGeneratedKeys as instead we will
|
||||
// batch load sequences which enables JDBC batch execution
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
package com.avaje.ebean.config.dbplatform;
|
||||
|
||||
/**
|
||||
* Oracle Total recall based history support.
|
||||
*/
|
||||
public class OracleDbHistorySupport extends DbStandardHistorySupport {
|
||||
|
||||
/**
|
||||
* Return the ' as of timestamp ?' clause appended after the table name.
|
||||
*/
|
||||
@Override
|
||||
public String getAsOfViewSuffix(String asOfViewSuffix) {
|
||||
return " as of TIMESTAMP ?";
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Oracle specific effective start column.
|
||||
*/
|
||||
@Override
|
||||
public String getSysPeriodLower(String tableAlias, String sysPeriod) {
|
||||
return "versions_starttime";
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Oracle specific effective end column.
|
||||
*/
|
||||
@Override
|
||||
public String getSysPeriodUpper(String tableAlias, String sysPeriod) {
|
||||
return "versions_endtime";
|
||||
}
|
||||
|
||||
}
|
||||
@@ -3,10 +3,10 @@ package com.avaje.ebean.config.dbplatform;
|
||||
/**
|
||||
* Postgres support for history features.
|
||||
*/
|
||||
public class PostgresHistorySupport implements DbHistorySupport {
|
||||
public class PostgresHistorySupport extends DbViewHistorySupport {
|
||||
|
||||
/**
|
||||
* Return 1 as we are using the range type and hence don't need 2 bind variables.
|
||||
* Return 1 as we are using the postgres range type and hence don't need 2 bind variables.
|
||||
*/
|
||||
@Override
|
||||
public int getBindCount() {
|
||||
@@ -24,19 +24,16 @@ public class PostgresHistorySupport implements DbHistorySupport {
|
||||
|
||||
// for Postgres we are using the 'timestamp with timezone range' data type
|
||||
// as our sys_period column so hence the predicate below
|
||||
//noinspection StringBufferReplaceableByString
|
||||
StringBuilder sb = new StringBuilder(40);
|
||||
sb.append(asOfTableAlias).append(".").append(asOfSysPeriod).append(" @> ?::timestamptz");
|
||||
return sb.toString();
|
||||
return asOfTableAlias + "." + asOfSysPeriod + " @> ?::timestamptz";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getSysPeriodLower(String tableAlias, String sysPeriod) {
|
||||
return "lower("+tableAlias+"."+sysPeriod+")";
|
||||
return "lower(" + tableAlias + "." + sysPeriod + ")";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getSysPeriodUpper(String tableAlias, String sysPeriod) {
|
||||
return "upper("+tableAlias+"."+sysPeriod+")";
|
||||
return "upper(" + tableAlias + "." + sysPeriod + ")";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -122,7 +122,7 @@ public class InternalConfiguration {
|
||||
|
||||
DatabasePlatform databasePlatform = serverConfig.getDatabasePlatform();
|
||||
|
||||
this.binder = new Binder(typeManager, getAsOfBindCount(databasePlatform));
|
||||
this.binder = getBinder(typeManager, databasePlatform);
|
||||
this.cQueryEngine = new CQueryEngine(databasePlatform, binder, asOfTableMapping, serverConfig.getAsOfSysPeriod());
|
||||
|
||||
ExternalTransactionManager externalTransactionManager = serverConfig.getExternalTransactionManager();
|
||||
@@ -142,9 +142,12 @@ public class InternalConfiguration {
|
||||
/**
|
||||
* For 'As Of' queries return the number of bind variables per predicate.
|
||||
*/
|
||||
private int getAsOfBindCount(DatabasePlatform databasePlatform) {
|
||||
private Binder getBinder(TypeManager typeManager, DatabasePlatform databasePlatform) {
|
||||
DbHistorySupport historySupport = databasePlatform.getHistorySupport();
|
||||
return historySupport == null ? 0 : historySupport.getBindCount();
|
||||
if (historySupport == null) {
|
||||
return new Binder(typeManager, 0, false);
|
||||
}
|
||||
return new Binder(typeManager, historySupport.getBindCount(), historySupport.isBindWithFromClause());
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -12,6 +12,7 @@ import com.avaje.ebean.config.EncryptKeyManager;
|
||||
import com.avaje.ebean.config.NamingConvention;
|
||||
import com.avaje.ebean.config.ServerConfig;
|
||||
import com.avaje.ebean.config.dbplatform.DatabasePlatform;
|
||||
import com.avaje.ebean.config.dbplatform.DbHistorySupport;
|
||||
import com.avaje.ebean.config.dbplatform.DbIdentity;
|
||||
import com.avaje.ebean.config.dbplatform.IdGenerator;
|
||||
import com.avaje.ebean.config.dbplatform.IdType;
|
||||
@@ -174,7 +175,7 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
|
||||
this.idBinderFactory = new IdBinderFactory(databasePlatform.isIdInExpandedForm());
|
||||
this.eagerFetchLobs = serverConfig.isEagerFetchLobs();
|
||||
|
||||
this.asOfViewSuffix = serverConfig.getAsOfViewSuffix();
|
||||
this.asOfViewSuffix = getAsOfViewSuffix(databasePlatform, serverConfig);
|
||||
this.readAnnotations = new ReadAnnotations(config.getGeneratedPropertyFactory(), asOfViewSuffix);
|
||||
this.bootupClasses = config.getBootupClasses();
|
||||
this.createProperties = config.getDeployCreateProperties();
|
||||
@@ -199,6 +200,17 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
|
||||
this.transientProperties = new TransientProperties();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the AsOfViewSuffix allowing the DbHistorySupport to override the value in
|
||||
* the case where there is no view (Oracle, DB2, MS SQL Server).
|
||||
*/
|
||||
private String getAsOfViewSuffix(DatabasePlatform databasePlatform, ServerConfig serverConfig) {
|
||||
|
||||
DbHistorySupport historySupport = databasePlatform.getHistorySupport();
|
||||
// with historySupport returns a simple view suffix or the sql2011 as of timestamp suffix
|
||||
return (historySupport == null ) ? serverConfig.getAsOfViewSuffix() : historySupport.getAsOfViewSuffix(serverConfig.getAsOfViewSuffix());
|
||||
}
|
||||
|
||||
public BeanDescriptor<?> getBeanDescriptorById(String descriptorId) {
|
||||
return idDescMap.get(descriptorId);
|
||||
}
|
||||
|
||||
@@ -28,12 +28,15 @@ public class Binder {
|
||||
|
||||
private final int asOfBindCount;
|
||||
|
||||
private final boolean bindAsOfWithFromClause;
|
||||
|
||||
/**
|
||||
* Set the PreparedStatement with which to bind variables to.
|
||||
*/
|
||||
public Binder(TypeManager typeManager, int asOfBindCount) {
|
||||
public Binder(TypeManager typeManager, int asOfBindCount, boolean bindAsOfWithFromClause) {
|
||||
this.typeManager = typeManager;
|
||||
this.asOfBindCount = asOfBindCount;
|
||||
this.bindAsOfWithFromClause = bindAsOfWithFromClause;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -43,6 +46,15 @@ public class Binder {
|
||||
return asOfBindCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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).
|
||||
*/
|
||||
public boolean isBindAsOfWithFromClause() {
|
||||
return bindAsOfWithFromClause;
|
||||
}
|
||||
|
||||
/**
|
||||
* Bind the values to the Prepared Statement.
|
||||
*/
|
||||
|
||||
@@ -403,9 +403,10 @@ public class CQueryBuilder {
|
||||
}
|
||||
|
||||
List<String> asOfTableAlias = query.getAsOfTableAlias();
|
||||
if (asOfTableAlias != null) {
|
||||
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 {
|
||||
|
||||
@@ -30,22 +30,42 @@ public class CQueryHistorySupport {
|
||||
this.sysPeriod = sysPeriod;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the bind of 'as of' timestamp occurs with the from clause
|
||||
* rather than at the end.
|
||||
*/
|
||||
public boolean isBindAtFromClause() {
|
||||
return dbHistorySupport.isBindWithFromClause();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the 'as of' history view for the given base table.
|
||||
*/
|
||||
public String getAsOfView(String table) {
|
||||
return asOfTableMap.get(table);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the lower bound column.
|
||||
*/
|
||||
public String getSysPeriodLower(String tableAlias) {
|
||||
|
||||
return dbHistorySupport.getSysPeriodLower(tableAlias, sysPeriod);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the upper bound column.
|
||||
*/
|
||||
public String getSysPeriodUpper(String tableAlias) {
|
||||
|
||||
return dbHistorySupport.getSysPeriodUpper(tableAlias, sysPeriod);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the predicate appended to the end of the query.
|
||||
*
|
||||
* Note used for Oracle total recall etc with the more standard approach.
|
||||
*/
|
||||
public String getAsOfPredicate(String tableAlias) {
|
||||
|
||||
return dbHistorySupport.getAsOfPredicate(tableAlias, sysPeriod);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -131,6 +131,18 @@ public class CQueryPredicates {
|
||||
|
||||
StringBuilder bindLog = new StringBuilder();
|
||||
|
||||
List<String> historyTableAlias = query.getAsOfTableAlias();
|
||||
if (historyTableAlias != null && binder.isBindAsOfWithFromClause()) {
|
||||
// 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();
|
||||
bindLog.append("asOf ").append(asOf);
|
||||
for (int i = 0; i < historyTableAlias.size() * binder.getAsOfBindCount(); i++) {
|
||||
binder.bindObject(dataBind, asOf);
|
||||
}
|
||||
bindLog.append(", ");
|
||||
}
|
||||
|
||||
if (idValue != null) {
|
||||
// this is a find by id type query...
|
||||
request.getBeanDescriptor().bindId(dataBind, idValue);
|
||||
@@ -143,7 +155,6 @@ public class CQueryPredicates {
|
||||
}
|
||||
|
||||
if (whereExprBindValues != null) {
|
||||
|
||||
for (int i = 0; i < whereExprBindValues.size(); i++) {
|
||||
Object bindValue = whereExprBindValues.get(i);
|
||||
bindValue = binder.bindObject(dataBind, bindValue);
|
||||
@@ -155,7 +166,6 @@ public class CQueryPredicates {
|
||||
}
|
||||
|
||||
if (filterManyExprBindValues != null) {
|
||||
|
||||
for (int i = 0; i < filterManyExprBindValues.size(); i++) {
|
||||
Object bindValue = filterManyExprBindValues.get(i);
|
||||
bindValue = binder.bindObject(dataBind, bindValue);
|
||||
@@ -166,9 +176,8 @@ public class CQueryPredicates {
|
||||
}
|
||||
}
|
||||
|
||||
List<String> historyTableAlias = query.getAsOfTableAlias();
|
||||
if (historyTableAlias != null) {
|
||||
// bind the asAt value for each table alias
|
||||
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();
|
||||
bindLog.append(" asOf ").append(asOf);
|
||||
|
||||
Reference in New Issue
Block a user