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 a8127ac4f..fbd17114e 100644 --- a/src/main/java/com/avaje/ebean/config/dbplatform/DbHistorySupport.java +++ b/src/main/java/com/avaje/ebean/config/dbplatform/DbHistorySupport.java @@ -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. *
- * 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. *
*/ 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); diff --git a/src/main/java/com/avaje/ebean/config/dbplatform/DbStandardHistorySupport.java b/src/main/java/com/avaje/ebean/config/dbplatform/DbStandardHistorySupport.java new file mode 100644 index 000000000..6d98b48c9 --- /dev/null +++ b/src/main/java/com/avaje/ebean/config/dbplatform/DbStandardHistorySupport.java @@ -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; + } +} diff --git a/src/main/java/com/avaje/ebean/config/dbplatform/DbViewHistorySupport.java b/src/main/java/com/avaje/ebean/config/dbplatform/DbViewHistorySupport.java new file mode 100644 index 000000000..3fe900179 --- /dev/null +++ b/src/main/java/com/avaje/ebean/config/dbplatform/DbViewHistorySupport.java @@ -0,0 +1,71 @@ +package com.avaje.ebean.config.dbplatform; + +/** + * Database view based implementation of DbHistorySupport. + *+ * These implementations have explicit history tables, a view to union the + * base table and history table and triggers to maintain the history. + *
+ */ +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"; + } +} diff --git a/src/main/java/com/avaje/ebean/config/dbplatform/MySqlHistorySupport.java b/src/main/java/com/avaje/ebean/config/dbplatform/MySqlHistorySupport.java index cc36972ef..616b7bfd1 100644 --- a/src/main/java/com/avaje/ebean/config/dbplatform/MySqlHistorySupport.java +++ b/src/main/java/com/avaje/ebean/config/dbplatform/MySqlHistorySupport.java @@ -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"; - } } diff --git a/src/main/java/com/avaje/ebean/config/dbplatform/Oracle10Platform.java b/src/main/java/com/avaje/ebean/config/dbplatform/Oracle10Platform.java index 6e639405c..43a2c6c1a 100644 --- a/src/main/java/com/avaje/ebean/config/dbplatform/Oracle10Platform.java +++ b/src/main/java/com/avaje/ebean/config/dbplatform/Oracle10Platform.java @@ -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 diff --git a/src/main/java/com/avaje/ebean/config/dbplatform/OracleDbHistorySupport.java b/src/main/java/com/avaje/ebean/config/dbplatform/OracleDbHistorySupport.java new file mode 100644 index 000000000..0ebd68738 --- /dev/null +++ b/src/main/java/com/avaje/ebean/config/dbplatform/OracleDbHistorySupport.java @@ -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"; + } + +} diff --git a/src/main/java/com/avaje/ebean/config/dbplatform/PostgresHistorySupport.java b/src/main/java/com/avaje/ebean/config/dbplatform/PostgresHistorySupport.java index 5357a7fe8..347cc7954 100644 --- a/src/main/java/com/avaje/ebean/config/dbplatform/PostgresHistorySupport.java +++ b/src/main/java/com/avaje/ebean/config/dbplatform/PostgresHistorySupport.java @@ -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 + ")"; } } diff --git a/src/main/java/com/avaje/ebeaninternal/server/core/InternalConfiguration.java b/src/main/java/com/avaje/ebeaninternal/server/core/InternalConfiguration.java index ec63c87a0..227f16435 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/core/InternalConfiguration.java +++ b/src/main/java/com/avaje/ebeaninternal/server/core/InternalConfiguration.java @@ -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()); } /** 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 904b8425d..07894e988 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptorManager.java +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptorManager.java @@ -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); } diff --git a/src/main/java/com/avaje/ebeaninternal/server/persist/Binder.java b/src/main/java/com/avaje/ebeaninternal/server/persist/Binder.java index ea28fd606..e2aeab3a5 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/persist/Binder.java +++ b/src/main/java/com/avaje/ebeaninternal/server/persist/Binder.java @@ -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. */ diff --git a/src/main/java/com/avaje/ebeaninternal/server/query/CQueryBuilder.java b/src/main/java/com/avaje/ebeaninternal/server/query/CQueryBuilder.java index 8b0298a5f..5cb8ed158 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/query/CQueryBuilder.java +++ b/src/main/java/com/avaje/ebeaninternal/server/query/CQueryBuilder.java @@ -403,9 +403,10 @@ public class CQueryBuilder { } List