From 81531b6cda43a224f06dc8fb80ef073eb8d777c8 Mon Sep 17 00:00:00 2001
From: Robin Bygrave
+ * As such typically the view is defined in
+ * When using extra-ddl.xml Ebean will run the resulting DDL script after the
+ *
+ * This is used with l2 caching to invalidate the query cache. Changes to these
+ * tables invalidate the query cache for the entity based on this view.
+ * extra-ddl.xml using
+ * create or replace view ....
+ * create-all DDL (which is typically used during development) and for
+ * DB Migration will copy the scripts as repeatable migration scripts that
+ * will be run by FlywayDb (or Ebean's own migration runner) when their MD5 hash changes.
+ *
- * This does not run the migration or ddl scripts but just generates them. + * This does not run the migration or ddl scripts but just generates them. *
*{@code
*
@@ -98,7 +102,7 @@ public class DbMigration {
/**
* Set the path from the current working directory to the application resources.
- *
+ *
* This defaults to maven style 'src/main/resources'.
*/
public void setPathToResources(String pathToResources) {
@@ -169,7 +173,7 @@ public class DbMigration {
/**
* Generate the next migration xml file and associated apply and rollback sql scripts.
*
- * This does not run the migration or ddl scripts but just generates them.
+ * This does not run the migration or ddl scripts but just generates them.
*
* Example: Run for a single specific platform
* {@code
@@ -181,7 +185,7 @@ public class DbMigration {
* migration.generateMigration();
*
* }
- *
+ *
*
Example: Run migration generating DDL for multiple platforms
* {@code
*
@@ -206,6 +210,8 @@ public class DbMigration {
try {
Request request = createRequest();
+ generateExtraDdl(request);
+
String pendingVersion = generatePendingDrop();
if (pendingVersion != null) {
generatePendingDrop(request, pendingVersion);
@@ -220,6 +226,49 @@ public class DbMigration {
}
}
+ /**
+ * Generate "repeatable" migration scripts.
+ *
+ * These take scrips from extra-dll.xml (typically views) and outputs "repeatable"
+ * migration scripts (starting with "R__") to be run by FlywayDb or Ebean's own
+ * migration runner.
+ *
+ */
+ private void generateExtraDdl(Request request) throws IOException {
+
+ if (databasePlatform != null) {
+ ExtraDdl extraDdl = ExtraDdlXmlReader.read("/extra-ddl.xml");
+ if (extraDdl != null) {
+ List ddlScript = extraDdl.getDdlScript();
+ for (DdlScript script : ddlScript) {
+ if (ExtraDdlXmlReader.matchPlatform(databasePlatform.getName(), script.getPlatforms())) {
+ writeExtraDdl(request, script);
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Write (or override) the "repeatable" migration script.
+ */
+ private void writeExtraDdl(Request request, DdlScript script) throws IOException {
+
+ String fullName = repeatableMigrationName(script.getName());
+
+ logger.info("writing repeatable script {}", fullName);
+
+ File file = new File(request.migrationDir, fullName);
+ FileWriter writer = new FileWriter(file);
+ writer.write(script.getValue());
+ writer.flush();
+ writer.close();
+ }
+
+ private String repeatableMigrationName(String scriptName) {
+ return "R__" + scriptName.replace(' ', '_') + migrationConfig.getApplySuffix();
+ }
+
/**
* Generate the diff migration.
*/
@@ -319,7 +368,7 @@ public class DbMigration {
// history ddl generation (triggers, history tables etc)
DdlWrite write = new DdlWrite(new MConfiguration(), request.current);
PlatformDdlWriter writer = createDdlWriter(databasePlatform, "");
- writer.processMigration(dbMigration, write, request.migrationDir , fullVersion);
+ writer.processMigration(dbMigration, write, request.migrationDir, fullVersion);
}
writeExtraPlatformDdl(fullVersion, request.currentModel, dbMigration, request.migrationDir);
}
@@ -339,7 +388,7 @@ public class DbMigration {
/**
* Return the full version for the migration being generated.
- *
+ *
* The full version can contain a comment suffix after a "__" double underscore.
*/
private String getFullVersion(MigrationModel migrationModel, String dropsFor) {
@@ -366,7 +415,7 @@ public class DbMigration {
* Replace spaces with underscores.
*/
private String toUnderScore(String name) {
- return name.replace(' ','_');
+ return name.replace(' ', '_');
}
/**
@@ -444,7 +493,7 @@ public class DbMigration {
*/
protected File getModelDirectory(File migrationDirectory) {
String modelPath = migrationConfig.getModelPath();
- if (modelPath == null || modelPath.isEmpty()) {
+ if (modelPath == null || modelPath.isEmpty()) {
return migrationDirectory;
}
File modelDir = new File(migrationDirectory, migrationConfig.getModelPath());
diff --git a/src/main/java/com/avaje/ebean/dbmigration/DdlGenerator.java b/src/main/java/com/avaje/ebean/dbmigration/DdlGenerator.java
index ac2e6c856..58cee566f 100644
--- a/src/main/java/com/avaje/ebean/dbmigration/DdlGenerator.java
+++ b/src/main/java/com/avaje/ebean/dbmigration/DdlGenerator.java
@@ -1,9 +1,10 @@
package com.avaje.ebean.dbmigration;
+import com.avaje.ebean.Transaction;
import com.avaje.ebean.config.ServerConfig;
import com.avaje.ebean.dbmigration.model.CurrentModel;
import com.avaje.ebeaninternal.api.SpiEbeanServer;
-import com.avaje.ebeaninternal.util.JdbcClose;
+import com.avaje.ebeaninternal.extraddl.model.ExtraDdlXmlReader;
import javax.persistence.PersistenceException;
import java.io.File;
@@ -15,6 +16,7 @@ import java.io.InputStreamReader;
import java.io.LineNumberReader;
import java.io.Reader;
import java.sql.Connection;
+import java.sql.SQLException;
/**
* Controls the generation and execution of "Create All" and "Drop All" DDL scripts.
@@ -89,12 +91,19 @@ public class DdlGenerator {
public int runScript(boolean expectErrors, String content, String scriptName) {
DdlRunner runner = new DdlRunner(expectErrors, scriptName);
- // get a connection without threadLocal
- Connection connection = server.createTransaction().getConnection();
+
+ Transaction transaction = server.createTransaction();
+ Connection connection = transaction.getConnection();
try {
- return runner.runAll(content, connection);
+ int count = runner.runAll(content, connection);
+ transaction.commit();
+ return count;
+
+ } catch (SQLException e) {
+ throw new PersistenceException("Failed to run script", e);
+
} finally {
- JdbcClose.close(connection);
+ transaction.end();
}
}
@@ -112,6 +121,14 @@ public class DdlGenerator {
createAllContent = readFile(getCreateFileName());
}
runScript(false, createAllContent, getCreateFileName());
+
+ String ignoreExtraDdl = System.getProperty("ebean.ignoreExtraDdl");
+ if (!"true".equalsIgnoreCase(ignoreExtraDdl)) {
+ String extraApply = ExtraDdlXmlReader.buildExtra(server.getDatabasePlatform().getName());
+ if (extraApply != null) {
+ runScript(false, extraApply, "extra-dll");
+ }
+ }
}
protected void runInitSql() throws IOException {
diff --git a/src/main/java/com/avaje/ebean/dbmigration/DdlRunner.java b/src/main/java/com/avaje/ebean/dbmigration/DdlRunner.java
index 9b8e3124c..30eb26a0b 100644
--- a/src/main/java/com/avaje/ebean/dbmigration/DdlRunner.java
+++ b/src/main/java/com/avaje/ebean/dbmigration/DdlRunner.java
@@ -3,7 +3,6 @@ package com.avaje.ebean.dbmigration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import javax.persistence.PersistenceException;
import java.io.StringReader;
import java.sql.Connection;
import java.sql.PreparedStatement;
@@ -18,11 +17,11 @@ public class DdlRunner {
protected static final Logger logger = LoggerFactory.getLogger(DdlRunner.class);
- protected DdlParser ddlParser = new DdlParser();
+ private DdlParser ddlParser = new DdlParser();
- protected final String scriptName;
+ private final String scriptName;
- protected final boolean expectErrors;
+ private final boolean expectErrors;
/**
* Construct with a script name (for logging) and flag indicating if errors are expected.
@@ -35,7 +34,7 @@ public class DdlRunner {
/**
* Parse the content into sql statements and execute them in a transaction.
*/
- public int runAll(String content, Connection connection) {
+ public int runAll(String content, Connection connection) throws SQLException {
List statements = ddlParser.parse(new StringReader(content));
return runStatements(statements, connection);
@@ -44,31 +43,7 @@ public class DdlRunner {
/**
* Execute all the statements in a single transaction.
*/
- public int runStatements(List statements, Connection connection) {
-
- try {
- int statementCount = runStatements(expectErrors, statements, connection);
- connection.commit();
- return statementCount;
-
- } catch (Exception e) {
- rollback(connection);
- throw new PersistenceException("Error: " + e.getMessage(), e);
- }
- }
-
- private void rollback(Connection connection) {
- try {
- connection.rollback();
- } catch (SQLException e) {
- logger.error("Error trying to rollback connection", e);
- }
- }
-
- /**
- * Execute the list of statements.
- */
- private int runStatements(boolean expectErrors, List statements, Connection c) {
+ private int runStatements(List statements, Connection connection) throws SQLException {
List noDuplicates = new ArrayList();
@@ -82,7 +57,7 @@ public class DdlRunner {
for (int i = 0; i < noDuplicates.size(); i++) {
String xOfy = (i + 1) + " of " + noDuplicates.size();
- runStatement(expectErrors, xOfy, noDuplicates.get(i), c);
+ runStatement(expectErrors, xOfy, noDuplicates.get(i), connection);
}
return noDuplicates.size();
@@ -91,7 +66,7 @@ public class DdlRunner {
/**
* Execute the statement.
*/
- private void runStatement(boolean expectErrors, String oneOf, String stmt, Connection c) {
+ private void runStatement(boolean expectErrors, String oneOf, String stmt, Connection c) throws SQLException {
PreparedStatement pstmt = null;
try {
@@ -111,12 +86,12 @@ public class DdlRunner {
pstmt = c.prepareStatement(stmt);
pstmt.execute();
- } catch (Exception e) {
+ } catch (SQLException e) {
if (expectErrors) {
logger.debug(" ... ignoring error executing " + getSummary(stmt) + " error: " + e.getMessage());
} else {
String msg = "Error executing stmt[" + stmt + "] error[" + e.getMessage() + "]";
- throw new RuntimeException(msg, e);
+ throw new SQLException(msg, e);
}
} finally {
diff --git a/src/main/java/com/avaje/ebean/dbmigration/model/MigrationVersion.java b/src/main/java/com/avaje/ebean/dbmigration/model/MigrationVersion.java
index eadeba9e5..c338ccde5 100644
--- a/src/main/java/com/avaje/ebean/dbmigration/model/MigrationVersion.java
+++ b/src/main/java/com/avaje/ebean/dbmigration/model/MigrationVersion.java
@@ -7,6 +7,10 @@ import java.util.Arrays;
*/
public class MigrationVersion implements Comparable {
+ private static final int[] REPEAT_ORDERING = {Integer.MAX_VALUE};
+
+ private static final boolean[] REPEAT_UNDERSCORES = {false};
+
/**
* The raw version text.
*/
@@ -21,6 +25,19 @@ public class MigrationVersion implements Comparable {
private final String comment;
+ /**
+ * Construct for "repeatable" version.
+ */
+ private MigrationVersion(String raw, String comment) {
+ this.raw = raw;
+ this.comment = comment;
+ this.ordering = REPEAT_ORDERING;
+ this.underscores = REPEAT_UNDERSCORES;
+ }
+
+ /**
+ * Construct for "normal" version.
+ */
private MigrationVersion(String raw, int[] ordering, boolean[] underscores, String comment) {
this.raw = raw;
this.ordering = ordering;
@@ -28,6 +45,13 @@ public class MigrationVersion implements Comparable {
this.comment = comment;
}
+ /**
+ * Return true if this is a "repeatable" version.
+ */
+ public boolean isRepeatable() {
+ return ordering == REPEAT_ORDERING;
+ }
+
/**
* Return the full version.
*/
@@ -76,11 +100,15 @@ public class MigrationVersion implements Comparable {
/**
* Returns the version part of the string.
- *
+ *
* Normalised means always use '.' delimiters (no underscores).
* NextVersion means bump/increase the last version number by 1.
*/
private String formattedVersion(boolean normalised, boolean nextVersion) {
+
+ if (ordering == REPEAT_ORDERING) {
+ return "R";
+ }
StringBuilder sb = new StringBuilder();
for (int i = 0; i < ordering.length; i++) {
if (i < ordering.length - 1) {
@@ -110,8 +138,7 @@ public class MigrationVersion implements Comparable {
return (ordering[i] > other.ordering[i]) ? 1 : -1;
}
}
- // considered the same
- return 0;
+ return comment.compareTo(other.comment);
}
/**
@@ -126,6 +153,10 @@ public class MigrationVersion implements Comparable {
*/
public static MigrationVersion parse(String raw) {
+ if (raw.startsWith("V") || raw.startsWith("v")) {
+ raw = raw.substring(1);
+ }
+
String comment = "";
String value = raw;
int commentStart = raw.indexOf("__");
@@ -139,6 +170,11 @@ public class MigrationVersion implements Comparable {
String[] sections = value.split("\\.");
+ if ("r".equalsIgnoreCase(sections[0])) {
+ // a "repeatable" version (does not have a version number)
+ return new MigrationVersion(raw, comment);
+ }
+
boolean[] underscores = new boolean[sections.length];
int[] ordering = new int[sections.length];
diff --git a/src/main/java/com/avaje/ebean/dbmigration/model/visitor/VisitAllUsing.java b/src/main/java/com/avaje/ebean/dbmigration/model/visitor/VisitAllUsing.java
index 22eb83856..a2966c119 100644
--- a/src/main/java/com/avaje/ebean/dbmigration/model/visitor/VisitAllUsing.java
+++ b/src/main/java/com/avaje/ebean/dbmigration/model/visitor/VisitAllUsing.java
@@ -39,7 +39,7 @@ public class VisitAllUsing {
public void visitAllBeans() {
for (BeanDescriptor> desc : descriptors) {
- if (desc.getBaseTable() != null) {
+ if (desc.isBaseTable()) {
visitBean(desc, visitor);
}
}
diff --git a/src/main/java/com/avaje/ebean/dbmigration/runner/LocalMigrationResource.java b/src/main/java/com/avaje/ebean/dbmigration/runner/LocalMigrationResource.java
index de67d6744..a2d71aebc 100644
--- a/src/main/java/com/avaje/ebean/dbmigration/runner/LocalMigrationResource.java
+++ b/src/main/java/com/avaje/ebean/dbmigration/runner/LocalMigrationResource.java
@@ -8,6 +8,16 @@ import org.avaje.classpath.scanner.Resource;
*/
public class LocalMigrationResource implements Comparable {
+ /**
+ * Code for repeatable migrations.
+ */
+ private static final String REPEAT_TYPE = "R";
+
+ /**
+ * Code for version migrations.
+ */
+ private static final String VERSION_TYPE = "V";
+
private final MigrationVersion version;
private final String location;
@@ -27,6 +37,32 @@ public class LocalMigrationResource implements Comparable migrations;
+ private final String updateSql;
+
+ private final LinkedHashMap migrations;
private MigrationMetaRow lastMigration;
@@ -65,6 +70,7 @@ public class MigrationTable {
this.schema = null;
this.table = migrationConfig.getMetaTable();
this.insertSql = MigrationMetaRow.insertSql(table);
+ this.updateSql = MigrationMetaRow.updateSql(table);
this.scriptTransform = createScriptTransform(migrationConfig);
this.envUserName = System.getProperty("user.name");
@@ -96,16 +102,17 @@ public class MigrationTable {
}
ExternalJdbcTransaction t = new ExternalJdbcTransaction(connection);
- SqlQuery sqlQuery = server.createSqlQuery("select * from "+table+" order by id for update");
+ SqlQuery sqlQuery = server.createSqlQuery("select * from " + table + " order by id for update");
List metaRows = server.findList(sqlQuery, t);
for (SqlRow row : metaRows) {
- addMigration(new MigrationMetaRow(row));
+ MigrationMetaRow metaRow = new MigrationMetaRow(row);
+ addMigration(metaRow.getVersion(), metaRow);
}
}
- private void createTable(Connection connection) throws IOException {
+ private void createTable(Connection connection) throws IOException, SQLException {
String script = ScriptTransform.table(table, getCreateTableScript());
@@ -146,82 +153,101 @@ public class MigrationTable {
/**
* Return true if the migration ran successfully and false if the migration failed.
*/
- public boolean shouldRun(LocalMigrationResource localVersion, LocalMigrationResource priorVersion) {
+ public boolean shouldRun(LocalMigrationResource localVersion, LocalMigrationResource priorVersion) throws SQLException {
- if (priorVersion != null) {
- // check priorVersion is installed
- MigrationMetaRow existing = migrations.get(priorVersion.getVersion().normalised());
- if (existing == null) {
- logger.warn("Migration {} requires prior migration {} which has not been run", localVersion.getVersion(), priorVersion.getVersion());
+ if (priorVersion != null && !localVersion.isRepeatable()) {
+ if (!migrationExists(priorVersion)) {
+ logger.error("Migration {} requires prior migration {} which has not been run", localVersion.getVersion(), priorVersion.getVersion());
return false;
}
}
- MigrationMetaRow existing = migrations.get(localVersion.getVersion().normalised());
- if (existing == null) {
- runMigration(localVersion, priorVersion);
- return true;
-
- } else {
- // check checksum and return ok, or re-run if repeatable script?
- existing.getChecksum();
- return true;
- }
+ MigrationMetaRow existing = migrations.get(localVersion.key());
+ return runMigration(localVersion, existing);
}
/**
* Run the migration script.
+ *
+ * @param local The local migration resource
+ * @param existing The information for this migration existing in the table
+ *
+ * @return True if the migrations should continue
*/
- private void runMigration(LocalMigrationResource localVersion, LocalMigrationResource prior) {
+ private boolean runMigration(LocalMigrationResource local, MigrationMetaRow existing) throws SQLException {
- logger.debug("run migration "+localVersion.getLocation());
+ String script = convertScript(local.getContent());
+ int checksum = Checksum.calculate(script);
- String script = convertScript(localVersion.getContent());
+ if (existing != null) {
+
+ boolean matchChecksum = (existing.getChecksum() == checksum);
+
+ if (!local.isRepeatable()) {
+ if (!matchChecksum) {
+ logger.error("Checksum mismatch on migration {}", local.getLocation());
+ }
+ return true;
+
+ } else if (matchChecksum) {
+ logger.trace("... skip unchanged repeatable migration {}", local.getLocation());
+ return true;
+ }
+ }
+
+ runMigration(local, existing, script, checksum);
+ return true;
+ }
+
+ /**
+ * Run a migration script as new migration or update on existing repeatable migration.
+ */
+ private void runMigration(LocalMigrationResource local, MigrationMetaRow existing, String script, int checksum) throws SQLException {
+
+ logger.debug("run migration {}", local.getLocation());
MigrationScriptRunner run = new MigrationScriptRunner(connection);
- run.runScript(false, script, "run migration version: "+localVersion.getVersion());
+ run.runScript(false, script, "run migration version: " + local.getVersion());
+ if (existing != null) {
+ // update existing migration row
+ SqlUpdate update = server.createSqlUpdate(updateSql);
+ existing.bindUpdate(checksum, envUserName, runTime, update);
+ server.execute(update, new ExternalJdbcTransaction(connection));
- int checksum = Checksum.calculate(script);
- MigrationMetaRow metaRow = createMetaRow(localVersion, prior, checksum);
+ } else {
+ // insert new migration row
+ SqlUpdate insert = server.createSqlUpdate(insertSql);
+ MigrationMetaRow metaRow = createMetaRow(local, checksum);
+ metaRow.bindInsert(insert);
+ server.execute(insert, new ExternalJdbcTransaction(connection));
- SqlUpdate insert = server.createSqlUpdate(insertSql);
- metaRow.bindInsert(insert);
- server.execute(insert, new ExternalJdbcTransaction(connection));
-
- addMigration(metaRow);
+ addMigration(local.key(), metaRow);
+ }
}
/**
* Create the MigrationMetaRow for this migration.
*/
- private MigrationMetaRow createMetaRow(LocalMigrationResource localVersion, LocalMigrationResource prior, int checksum) {
+ private MigrationMetaRow createMetaRow(LocalMigrationResource migration, int checksum) {
int nextId = 1;
if (lastMigration != null) {
nextId = lastMigration.getId() + 1;
}
- String runVersion = localVersion.getVersion().normalised();
- String comment = getMigrationComment(localVersion);
- String priorVersion = getMigrationPriorVersion(prior);
+ String type = migration.getType();
+ String runVersion = migration.key();
+ String comment = migration.getComment();
- return new MigrationMetaRow(nextId, runVersion, priorVersion, comment, checksum, envUserName);
+ return new MigrationMetaRow(nextId, type, runVersion, comment, checksum, envUserName, runTime);
}
/**
- * Return the prior migration normalised version.
+ * Return true if the migration exists.
*/
- private String getMigrationPriorVersion(LocalMigrationResource prior) {
- return prior != null ? prior.getVersion().normalised() : "-";
- }
-
- /**
- * Return the migration comment.
- */
- private String getMigrationComment(LocalMigrationResource localVersion) {
- String comment = localVersion.getVersion().getComment();
- return comment == null || comment.isEmpty() ? "-" : comment;
+ private boolean migrationExists(LocalMigrationResource priorVersion) {
+ return migrations.containsKey(priorVersion.key());
}
/**
@@ -234,12 +260,11 @@ public class MigrationTable {
/**
* Register the successfully executed migration (to allow dependant scripts to run).
*/
- private void addMigration(MigrationMetaRow metaRow) {
+ private void addMigration(String key, MigrationMetaRow metaRow) {
lastMigration = metaRow;
- String runVersion = metaRow.getRunVersion();
- if (runVersion == null) {
+ if (metaRow.getVersion() == null) {
throw new IllegalStateException("No runVersion in db migration table row? " + metaRow);
}
- migrations.put(runVersion, metaRow);
+ migrations.put(key, metaRow);
}
}
diff --git a/src/main/java/com/avaje/ebeaninternal/api/TransactionEvent.java b/src/main/java/com/avaje/ebeaninternal/api/TransactionEvent.java
index 9b76156b7..845431bb5 100644
--- a/src/main/java/com/avaje/ebeaninternal/api/TransactionEvent.java
+++ b/src/main/java/com/avaje/ebeaninternal/api/TransactionEvent.java
@@ -108,8 +108,8 @@ public class TransactionEvent implements Serializable {
/**
* Build and return the cache changeSet.
*/
- public CacheChangeSet buildCacheChanges() {
- CacheChangeSet changeSet = new CacheChangeSet();
+ public CacheChangeSet buildCacheChanges(boolean viewInvalidation) {
+ CacheChangeSet changeSet = new CacheChangeSet(viewInvalidation);
if (eventBeans != null) {
eventBeans.notifyCache(changeSet);
}
diff --git a/src/main/java/com/avaje/ebeaninternal/extraddl/model/DdlScript.java b/src/main/java/com/avaje/ebeaninternal/extraddl/model/DdlScript.java
new file mode 100644
index 000000000..a0020f584
--- /dev/null
+++ b/src/main/java/com/avaje/ebeaninternal/extraddl/model/DdlScript.java
@@ -0,0 +1,116 @@
+
+package com.avaje.ebeaninternal.extraddl.model;
+
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlAttribute;
+import javax.xml.bind.annotation.XmlRootElement;
+import javax.xml.bind.annotation.XmlType;
+import javax.xml.bind.annotation.XmlValue;
+
+
+/**
+ * Java class for anonymous complex type.
+ *
+ *
The following schema fragment specifies the expected content contained within this class.
+ *
+ *
+ * <complexType>
+ * <simpleContent>
+ * <extension base="<http://www.w3.org/2001/XMLSchema>string">
+ * <attribute name="name" use="required" type="{http://www.w3.org/2001/XMLSchema}string" />
+ * <attribute name="platforms" type="{http://www.w3.org/2001/XMLSchema}string" />
+ * </extension>
+ * </simpleContent>
+ * </complexType>
+ *
+ *
+ *
+ */
+@XmlAccessorType(XmlAccessType.FIELD)
+@XmlType(name = "", propOrder = {
+ "value"
+})
+@XmlRootElement(name = "ddl-script")
+public class DdlScript {
+
+ @XmlValue
+ protected String value;
+ @XmlAttribute(name = "name", required = true)
+ protected String name;
+ @XmlAttribute(name = "platforms")
+ protected String platforms;
+
+ /**
+ * Gets the value of the value property.
+ *
+ * @return
+ * possible object is
+ * {@link String }
+ *
+ */
+ public String getValue() {
+ return value;
+ }
+
+ /**
+ * Sets the value of the value property.
+ *
+ * @param value
+ * allowed object is
+ * {@link String }
+ *
+ */
+ public void setValue(String value) {
+ this.value = value;
+ }
+
+ /**
+ * Gets the value of the name property.
+ *
+ * @return
+ * possible object is
+ * {@link String }
+ *
+ */
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * Sets the value of the name property.
+ *
+ * @param value
+ * allowed object is
+ * {@link String }
+ *
+ */
+ public void setName(String value) {
+ this.name = value;
+ }
+
+ /**
+ * Gets the value of the platforms property.
+ *
+ * @return
+ * possible object is
+ * {@link String }
+ *
+ */
+ public String getPlatforms() {
+ return platforms;
+ }
+
+ /**
+ * Sets the value of the platforms property.
+ *
+ * @param value
+ * allowed object is
+ * {@link String }
+ *
+ */
+ public void setPlatforms(String value) {
+ this.platforms = value;
+ }
+
+}
diff --git a/src/main/java/com/avaje/ebeaninternal/extraddl/model/ExtraDdl.java b/src/main/java/com/avaje/ebeaninternal/extraddl/model/ExtraDdl.java
new file mode 100644
index 000000000..23a9c4b9f
--- /dev/null
+++ b/src/main/java/com/avaje/ebeaninternal/extraddl/model/ExtraDdl.java
@@ -0,0 +1,71 @@
+
+package com.avaje.ebeaninternal.extraddl.model;
+
+import java.util.ArrayList;
+import java.util.List;
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlRootElement;
+import javax.xml.bind.annotation.XmlType;
+
+
+/**
+ * Java class for anonymous complex type.
+ *
+ *
The following schema fragment specifies the expected content contained within this class.
+ *
+ *
+ * <complexType>
+ * <complexContent>
+ * <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
+ * <sequence>
+ * <element ref="{http://ebean-orm.github.io/xml/ns/extraddl}ddl-script" maxOccurs="unbounded"/>
+ * </sequence>
+ * </restriction>
+ * </complexContent>
+ * </complexType>
+ *
+ *
+ *
+ */
+@XmlAccessorType(XmlAccessType.FIELD)
+@XmlType(name = "", propOrder = {
+ "ddlScript"
+})
+@XmlRootElement(name = "extra-ddl")
+public class ExtraDdl {
+
+ @XmlElement(name = "ddl-script", required = true)
+ protected List ddlScript;
+
+ /**
+ * Gets the value of the ddlScript property.
+ *
+ *
+ * This accessor method returns a reference to the live list,
+ * not a snapshot. Therefore any modification you make to the
+ * returned list will be present inside the JAXB object.
+ * This is why there is not a set method for the ddlScript property.
+ *
+ *
+ * For example, to add a new item, do as follows:
+ *
+ * getDdlScript().add(newItem);
+ *
+ *
+ *
+ *
+ * Objects of the following type(s) are allowed in the list
+ * {@link DdlScript }
+ *
+ *
+ */
+ public List getDdlScript() {
+ if (ddlScript == null) {
+ ddlScript = new ArrayList();
+ }
+ return this.ddlScript;
+ }
+
+}
diff --git a/src/main/java/com/avaje/ebeaninternal/extraddl/model/ExtraDdlXmlReader.java b/src/main/java/com/avaje/ebeaninternal/extraddl/model/ExtraDdlXmlReader.java
new file mode 100644
index 000000000..da2a231e3
--- /dev/null
+++ b/src/main/java/com/avaje/ebeaninternal/extraddl/model/ExtraDdlXmlReader.java
@@ -0,0 +1,82 @@
+package com.avaje.ebeaninternal.extraddl.model;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.xml.bind.JAXBContext;
+import javax.xml.bind.JAXBException;
+import javax.xml.bind.Unmarshaller;
+import java.io.InputStream;
+
+/**
+ * Read ExtraDdl from an XML document.
+ */
+public class ExtraDdlXmlReader {
+
+ private static final Logger logger = LoggerFactory.getLogger(ExtraDdlXmlReader.class);
+
+ /**
+ * Return the combined extra DDL that should be run given the platform name.
+ */
+ public static String buildExtra(String platformName) {
+
+ ExtraDdl read = ExtraDdlXmlReader.read("/extra-ddl.xml");
+ if (read == null) {
+ return null;
+ }
+ StringBuilder sb = new StringBuilder(300);
+ for (DdlScript script : read.getDdlScript()) {
+ if (matchPlatform(platformName, script.getPlatforms())) {
+ logger.debug("include script {}", script.getName());
+ sb.append(script.getValue()).append("\n");
+ }
+ }
+ return sb.toString();
+ }
+
+ /**
+ * Return true if the script platforms is a match/supported for the given platform.
+ * @param platformName The database platform we are generating/running DDL for
+ * @param platforms The platforms (comma delimited) this script should run for
+ */
+ public static boolean matchPlatform(String platformName, String platforms) {
+ if (platforms == null || platforms.trim().length() == 0) {
+ return true;
+ }
+ String[] names = platforms.split("[,;]");
+ for (String name : names) {
+ if (name.trim().toLowerCase().contains(platformName)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Read and return a ExtraDdl from an xml document at the given resource path.
+ */
+ public static ExtraDdl read(String resourcePath) {
+
+ InputStream is = ExtraDdlXmlReader.class.getResourceAsStream(resourcePath);
+ if (is == null) {
+ // we expect this and check for null
+ return null;
+ }
+ return read(is);
+ }
+
+ /**
+ * Read and return a ExtraDdl from an xml document.
+ */
+ public static ExtraDdl read(InputStream is) {
+
+ try {
+ JAXBContext jaxbContext = JAXBContext.newInstance(ExtraDdl.class);
+ Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
+ return (ExtraDdl) unmarshaller.unmarshal(is);
+
+ } catch (JAXBException e) {
+ throw new RuntimeException(e);
+ }
+ }
+}
diff --git a/src/main/java/com/avaje/ebeaninternal/extraddl/model/ObjectFactory.java b/src/main/java/com/avaje/ebeaninternal/extraddl/model/ObjectFactory.java
new file mode 100644
index 000000000..07009ef1a
--- /dev/null
+++ b/src/main/java/com/avaje/ebeaninternal/extraddl/model/ObjectFactory.java
@@ -0,0 +1,48 @@
+
+package com.avaje.ebeaninternal.extraddl.model;
+
+import javax.xml.bind.annotation.XmlRegistry;
+
+
+/**
+ * This object contains factory methods for each
+ * Java content interface and Java element interface
+ * generated in the com.avaje.ebeaninternal.extraddl.model package.
+ * An ObjectFactory allows you to programatically
+ * construct new instances of the Java representation
+ * for XML content. The Java representation of XML
+ * content can consist of schema derived interfaces
+ * and classes representing the binding of schema
+ * type definitions, element declarations and model
+ * groups. Factory methods for each of these are
+ * provided in this class.
+ *
+ */
+@XmlRegistry
+public class ObjectFactory {
+
+
+ /**
+ * Create a new ObjectFactory that can be used to create new instances of schema derived classes for package: com.avaje.ebeaninternal.extraddl.model
+ *
+ */
+ public ObjectFactory() {
+ }
+
+ /**
+ * Create an instance of {@link DdlScript }
+ *
+ */
+ public DdlScript createDdlScript() {
+ return new DdlScript();
+ }
+
+ /**
+ * Create an instance of {@link ExtraDdl }
+ *
+ */
+ public ExtraDdl createExtraDdl() {
+ return new ExtraDdl();
+ }
+
+}
diff --git a/src/main/java/com/avaje/ebeaninternal/extraddl/model/package-info.java b/src/main/java/com/avaje/ebeaninternal/extraddl/model/package-info.java
new file mode 100644
index 000000000..aa0783686
--- /dev/null
+++ b/src/main/java/com/avaje/ebeaninternal/extraddl/model/package-info.java
@@ -0,0 +1,2 @@
+@javax.xml.bind.annotation.XmlSchema(namespace = "http://ebean-orm.github.io/xml/ns/extraddl", elementFormDefault = javax.xml.bind.annotation.XmlNsForm.QUALIFIED)
+package com.avaje.ebeaninternal.extraddl.model;
diff --git a/src/main/java/com/avaje/ebeaninternal/server/cache/CacheChangeSet.java b/src/main/java/com/avaje/ebeaninternal/server/cache/CacheChangeSet.java
index 1c3c8fa7f..30b1dc833 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/cache/CacheChangeSet.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/cache/CacheChangeSet.java
@@ -22,9 +22,25 @@ public class CacheChangeSet {
private final Map manyChangeMap = new HashMap();
/**
- * Apply all the changes to the L2 cache.
+ * Set of "base tables" modified used to invalidate entities based on views.
*/
- public void apply() {
+ private final Set viewInvalidation = new HashSet();
+
+ private final boolean viewEntityInvalidation;
+
+ /**
+ * Construct specifying if we also need to process invalidation for entities based on views.
+ */
+ public CacheChangeSet(boolean viewEntityInvalidation) {
+ this.viewEntityInvalidation = viewEntityInvalidation;
+ }
+
+ /**
+ * Apply the changes to the L2 cache except entity/view invalidation.
+ *
+ * Return the set of table changes to process invalidation for entities based on views.
+ */
+ public Set apply() {
for (BeanDescriptor entry : queryCaches) {
entry.queryCacheClear();
}
@@ -34,6 +50,7 @@ public class CacheChangeSet {
for (CacheChange entry : manyChangeMap.values()) {
entry.apply();
}
+ return viewInvalidation;
}
/**
@@ -64,11 +81,23 @@ public class CacheChangeSet {
many(desc, manyProperty).addPut(parentId, entry);
}
+ /**
+ * On bean insert register table for view based entity invalidation.
+ */
+ public void addBeanInsert(String baseTable) {
+ if (viewEntityInvalidation) {
+ viewInvalidation.add(baseTable);
+ }
+ }
+
/**
* Remove a bean from the cache.
*/
public void addBeanRemove(BeanDescriptor desc, Object id) {
entries.add(new CacheChangeBeanRemove(desc, id));
+ if (viewEntityInvalidation) {
+ viewInvalidation.add(desc.getBaseTable());
+ }
}
/**
@@ -76,6 +105,9 @@ public class CacheChangeSet {
*/
public void addBeanUpdate(BeanDescriptor desc, Object id, Map changes, boolean updateNaturalKey, long version) {
entries.add(new CacheChangeBeanUpdate(desc, id, changes, updateNaturalKey, version));
+ if (viewEntityInvalidation) {
+ viewInvalidation.add(desc.getBaseTable());
+ }
}
/**
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 98b069dbd..056065beb 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptor.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptor.java
@@ -133,7 +133,7 @@ public class BeanDescriptor implements MetaBeanInfo, BeanType {
}
public enum EntityType {
- ORM, EMBEDDED, SQL
+ ORM, EMBEDDED, VIEW, SQL
}
/**
@@ -179,6 +179,8 @@ public class BeanDescriptor implements MetaBeanInfo, BeanType {
private final CompoundUniqueConstraint[] compoundUniqueConstraints;
+ private final String[] dependentTables;
+
/**
* The base database table.
*/
@@ -446,6 +448,7 @@ public class BeanDescriptor implements MetaBeanInfo, BeanType {
this.baseTable = InternString.intern(deploy.getBaseTable());
this.baseTableAsOf = deploy.getBaseTableAsOf();
this.baseTableVersionsBetween = deploy.getBaseTableVersionsBetween();
+ this.dependentTables = deploy.getDependentTables();
this.dbComment = deploy.getDbComment();
this.autoTunable = EntityType.ORM.equals(entityType) && (beanFinder == null);
@@ -1057,6 +1060,13 @@ public class BeanDescriptor implements MetaBeanInfo, BeanType {
return cacheHelp.isBeanCaching();
}
+ /**
+ * Return true if there is query caching for this type of bean.
+ */
+ public boolean isQueryCaching() {
+ return cacheHelp.isQueryCaching();
+ }
+
public boolean isManyPropCaching() {
return isBeanCaching();
}
@@ -2325,6 +2335,16 @@ public class BeanDescriptor implements MetaBeanInfo, BeanType {
return dbComment;
}
+ /**
+ * Return the dependent tables for a view based entity.
+ *
+ * These tables
+ *
+ */
+ public String[] getDependentTables() {
+ return dependentTables;
+ }
+
/**
* Return the base table. Only properties mapped to the base table are by
* default persisted.
@@ -2333,6 +2353,13 @@ public class BeanDescriptor implements MetaBeanInfo, BeanType {
return baseTable;
}
+ /**
+ * Return true if this type is a base table entity type.
+ */
+ public boolean isBaseTable() {
+ return baseTable != null && entityType == EntityType.ORM;
+ }
+
/**
* Return the base table to use given the query temporal mode.
*/
diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptorCacheHelp.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptorCacheHelp.java
index 383e15761..6f72ccb5e 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptorCacheHelp.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptorCacheHelp.java
@@ -98,7 +98,7 @@ final class BeanDescriptorCacheHelp {
/**
* Return true if there is currently query caching for this type of bean.
*/
- private boolean isQueryCaching() {
+ boolean isQueryCaching() {
return queryCache != null;
}
@@ -598,6 +598,7 @@ final class BeanDescriptorCacheHelp {
void handleInsert(PersistRequestBean insertRequest, CacheChangeSet changeSet) {
queryCacheClear(changeSet);
cacheDeleteImported(false, insertRequest.getEntityBean(), changeSet);
+ changeSet.addBeanInsert(desc.getBaseTable());
}
private void cacheDeleteImported(boolean clear, EntityBean entityBean, CacheChangeSet changeSet) {
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 9e3f41fab..c910ba308 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptorManager.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptorManager.java
@@ -124,7 +124,7 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
private final DocStoreFactory docStoreFactory;
private int enhancedClassCount;
-
+
private final boolean updateChangesOnly;
private final BootupClasses bootupClasses;
@@ -143,6 +143,8 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
private final Map>> tableToDescMap = new HashMap>>();
+ private final Map>> tableToViewDescMap = new HashMap>>();
+
private List> immutableDescriptorList;
private final DbIdentity dbIdentity;
@@ -174,12 +176,12 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
/**
* Map of base tables to 'with history views' used to support 'as of' queries.
*/
- private final Map asOfTableMap = new HashMap();
+ private final Map asOfTableMap = new HashMap();
/**
* Map of base tables to 'draft' tables.
*/
- private final Map draftTableMap = new HashMap();
+ private final Map draftTableMap = new HashMap();
/**
* Create for a given database dbConfig.
@@ -235,7 +237,7 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
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());
+ return (historySupport == null) ? serverConfig.getAsOfViewSuffix() : historySupport.getAsOfViewSuffix(serverConfig.getAsOfViewSuffix());
}
/**
@@ -245,7 +247,7 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
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());
+ return (historySupport == null) ? serverConfig.getAsOfViewSuffix() : historySupport.getVersionsBetweenSuffix(serverConfig.getAsOfViewSuffix());
}
@Override
@@ -300,14 +302,14 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
/**
* Return the map of base tables to draft tables.
*/
- public Map getDraftTableMap() {
+ public Map getDraftTableMap() {
return draftTableMap;
}
/**
* Deploy returning the asOfTableMap (which is required by the SQL builders).
*/
- public Map deploy() {
+ public Map deploy() {
try {
createListeners();
@@ -356,10 +358,19 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
*/
public void cacheNotify(TransactionEventTable.TableIUD tableIUD) {
- List> list = getBeanDescriptors(tableIUD.getTableName());
- if (list != null) {
- for (int i = 0; i < list.size(); i++) {
- list.get(i).cacheHandleBulkUpdate(tableIUD);
+ String tableName = tableIUD.getTableName().toLowerCase();
+ List> normalBeanTypes = tableToDescMap.get(tableName);
+ if (normalBeanTypes != null) {
+ // 'normal' entity beans based on a "base table"
+ for (int i = 0; i < normalBeanTypes.size(); i++) {
+ normalBeanTypes.get(i).cacheHandleBulkUpdate(tableIUD);
+ }
+ }
+ List> viewBeans = tableToViewDescMap.get(tableName);
+ if (viewBeans != null) {
+ // entity beans based on a "view"
+ for (int i = 0; i < viewBeans.size(); i++) {
+ viewBeans.get(i).cacheHandleBulkUpdate(tableIUD);
}
}
}
@@ -378,6 +389,21 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
return tableToDescMap.get(tableName.toLowerCase());
}
+ /**
+ * Invalidate entity beans based on views via their dependent tables.
+ */
+ public void processViewInvalidation(Set viewInvalidation) {
+
+ for (String depTable : viewInvalidation) {
+ List> list = tableToViewDescMap.get(depTable.toLowerCase());
+ if (list == null) {
+ for (int i = 0; i < list.size(); i++) {
+ list.get(i).queryCacheClear();
+ }
+ }
+ }
+ }
+
/**
* Build a map of table names to BeanDescriptors.
*
@@ -390,7 +416,6 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
String baseTable = desc.getBaseTable();
if (baseTable != null) {
baseTable = baseTable.toLowerCase();
-
List> list = tableToDescMap.get(baseTable);
if (list == null) {
list = new ArrayList>(1);
@@ -398,6 +423,22 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
}
list.add(desc);
}
+ if (desc.getEntityType() == EntityType.VIEW && desc.isQueryCaching()) {
+ // build map of tables to view entities dependent on those tables
+ // for the purpose of invalidating appropriate query caches
+ String[] dependentTables = desc.getDependentTables();
+ if (dependentTables != null && dependentTables.length > 0) {
+ for (String depTable : dependentTables) {
+ depTable = depTable.toLowerCase();
+ List> list = tableToViewDescMap.get(depTable);
+ if (list == null) {
+ list = new ArrayList>(1);
+ tableToViewDescMap.put(depTable, list);
+ }
+ list.add(desc);
+ }
+ }
+ }
}
}
@@ -487,6 +528,14 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
throw new PersistenceException(msg, source);
}
+ /**
+ * Return true if there are 'view based entities' using l2 query caching and so need
+ * to be invalidated based on changes to dependent tables.
+ */
+ public boolean requiresViewEntityCacheInvalidation() {
+ return !tableToViewDescMap.isEmpty();
+ }
+
/**
* Return an immutable list of all the BeanDescriptors.
*/
@@ -539,7 +588,7 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
* Return the bean deploy info for the given class.
*/
public DeployBeanInfo getDeploy(Class cls) {
- return (DeployBeanInfo) deplyInfoMap.get(cls);
+ return (DeployBeanInfo) deplyInfoMap.get(cls);
}
private void registerBeanDescriptor(BeanDescriptor> desc) {
@@ -651,7 +700,7 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
}
}
- @SuppressWarnings({ "unchecked", "rawtypes" })
+ @SuppressWarnings({"unchecked", "rawtypes"})
private void readEntityRelationships() {
// We only perform 'circular' checks etc after we have
@@ -667,9 +716,9 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
// Set inheritance info
for (DeployBeanInfo> info : deplyInfoMap.values()) {
- setInheritanceInfo(info);
+ setInheritanceInfo(info);
}
-
+
for (DeployBeanInfo> info : deplyInfoMap.values()) {
registerBeanDescriptor(new BeanDescriptor(this, info.getDescriptor()));
}
@@ -681,24 +730,24 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
* @param info the new inheritance info
*/
private void setInheritanceInfo(DeployBeanInfo> info) {
-
- for (DeployBeanPropertyAssocOne> oneProp : info.getDescriptor().propertiesAssocOne()) {
- if (!oneProp.isTransient()) {
- DeployBeanInfo> assoc = deplyInfoMap.get(oneProp.getTargetType());
- if (assoc != null){
- oneProp.getTableJoin().setInheritInfo(assoc.getDescriptor().getInheritInfo());
- }
- }
- }
-
- for (DeployBeanPropertyAssocMany> manyProp : info.getDescriptor().propertiesAssocMany()) {
- if (!manyProp.isTransient()) {
- DeployBeanInfo> assoc = deplyInfoMap.get(manyProp.getTargetType());
- if (assoc != null){
- manyProp.getTableJoin().setInheritInfo(assoc.getDescriptor().getInheritInfo());
- }
- }
- }
+
+ for (DeployBeanPropertyAssocOne> oneProp : info.getDescriptor().propertiesAssocOne()) {
+ if (!oneProp.isTransient()) {
+ DeployBeanInfo> assoc = deplyInfoMap.get(oneProp.getTargetType());
+ if (assoc != null) {
+ oneProp.getTableJoin().setInheritInfo(assoc.getDescriptor().getInheritInfo());
+ }
+ }
+ }
+
+ for (DeployBeanPropertyAssocMany> manyProp : info.getDescriptor().propertiesAssocMany()) {
+ if (!manyProp.isTransient()) {
+ DeployBeanInfo> assoc = deplyInfoMap.get(manyProp.getTargetType());
+ if (assoc != null) {
+ manyProp.getTableJoin().setInheritInfo(assoc.getDescriptor().getInheritInfo());
+ }
+ }
+ }
}
private void secondaryPropsJoins(DeployBeanInfo> info) {
@@ -845,7 +894,7 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
* into the order_id column on the order_lines table).
*
*/
- @SuppressWarnings({ "unchecked", "rawtypes" })
+ @SuppressWarnings({"unchecked", "rawtypes"})
private void makeUnidirectional(DeployBeanInfo> info, DeployBeanPropertyAssocMany> oneToMany) {
DeployBeanDescriptor> targetDesc = getTargetDescriptor(oneToMany);
@@ -1081,7 +1130,7 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
desc.setUpdateChangesOnly(updateChangesOnly);
beanLifecycleAdapterFactory.addLifecycleMethods(desc);
-
+
// set bean controller, finder and listener
setBeanControllerFinderListener(desc);
deplyInherit.process(desc);
@@ -1103,8 +1152,7 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
readXml(desc);
- if (!EntityType.ORM.equals(desc.getEntityType())) {
- // not using base table
+ if (EntityType.SQL == desc.getEntityType()) {
desc.setBaseTable(null, null, null);
}
@@ -1373,7 +1421,7 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
Class> beanType = desc.getBeanType();
BeanPropertiesReader reflectProps = new BeanPropertiesReader(beanType);
-
+
BeanPropertyInfo beanReflect = reflectFactory.create(beanType);
desc.setProperties(reflectProps.getProperties());
@@ -1384,7 +1432,7 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
if (isPersistentField(prop)) {
throw new IllegalStateException("Property " + propName + " not found in " + reflectProps + " for type " + beanType);
}
-
+
} else {
final int propertyIndex = pos;
prop.setPropertyIndex(propertyIndex);
@@ -1459,7 +1507,7 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
Class> beanClass = desc.getBeanType();
if (!hasEntityBeanInterface(beanClass)) {
- throw new IllegalStateException("Bean "+beanClass+" is not enhanced?");
+ throw new IllegalStateException("Bean " + beanClass + " is not enhanced?");
}
// the bean already implements EntityBean
@@ -1475,7 +1523,7 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
* enhanced or all dynamically subclassed).
*/
private void checkInheritedClasses(Class> beanClass) {
-
+
Class> superclass = beanClass.getSuperclass();
if (Object.class.equals(superclass)) {
// we got to the top of the inheritance
@@ -1490,9 +1538,9 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
// ok to stop and treat just the same as Object.class
return;
}
- throw new IllegalStateException("Super type "+superclass+" is not enhanced?");
+ throw new IllegalStateException("Super type " + superclass + " is not enhanced?");
}
-
+
// recursively continue up the inheritance hierarchy
checkInheritedClasses(superclass);
}
@@ -1502,7 +1550,7 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
* If so it is ok for it not to be enhanced.
*/
private boolean isMappedSuperWithNoProperties(Class> beanClass) {
-
+
MappedSuperclass annotation = beanClass.getAnnotation(MappedSuperclass.class);
if (annotation == null) {
return false;
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 bbe6d47d9..41da80883 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
@@ -129,6 +129,8 @@ public class DeployBeanDescriptor {
private String draftTable;
+ private String[] dependentTables;
+
private boolean historySupport;
private boolean readAuditing;
@@ -582,6 +584,13 @@ public class DeployBeanDescriptor {
return draftTable;
}
+ /**
+ * For view based entity return the dependant tables.
+ */
+ public String[] getDependentTables() {
+ return dependentTables;
+ }
+
/**
* Return the base table. Only properties mapped to the base table are by
* default persisted.
@@ -611,6 +620,15 @@ public class DeployBeanDescriptor {
return baseTableFull;
}
+ /**
+ * Set when entity is based on a view.
+ */
+ public void setView(String viewName, String[] dependentTables) {
+ this.entityType = EntityType.VIEW;
+ this.dependentTables = this.dependentTables;
+ setBaseTable(new TableName(viewName), "", "");
+ }
+
/**
* Set the base table. Only properties mapped to the base table are by default persisted.
*/
diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/AnnotationBeanTable.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/AnnotationBeanTable.java
deleted file mode 100644
index 491e5db30..000000000
--- a/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/AnnotationBeanTable.java
+++ /dev/null
@@ -1,31 +0,0 @@
-package com.avaje.ebeaninternal.server.deploy.parse;
-
-import com.avaje.ebean.config.TableName;
-import com.avaje.ebeaninternal.server.deploy.meta.DeployBeanTable;
-
-/**
- * Read the annotations for BeanTable.
- *
- * Refer to BeanTable but basically determining base table, table alias
- * and the unique id properties.
- *
- */
-public class AnnotationBeanTable extends AnnotationBase {
-
- final DeployBeanTable beanTable;
-
- public AnnotationBeanTable(DeployUtil util, DeployBeanTable beanTable){
- super(util);
- this.beanTable = beanTable;
- }
-
- /**
- * Parse the annotations.
- */
- public void parse() {
-
- TableName tableName = namingConvention.getTableName(beanTable.getBeanType());
-
- beanTable.setBaseTable(tableName.getQualifiedName());
- }
-}
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 f7050ba0e..3c23dac04 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
@@ -22,6 +22,7 @@ import com.avaje.ebean.annotation.NamedUpdate;
import com.avaje.ebean.annotation.NamedUpdates;
import com.avaje.ebean.annotation.ReadAudit;
import com.avaje.ebean.annotation.UpdateMode;
+import com.avaje.ebean.annotation.View;
import com.avaje.ebean.config.TableName;
import com.avaje.ebeaninternal.server.core.CacheOptions;
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor.EntityType;
@@ -136,13 +137,15 @@ public class AnnotationClass extends AnnotationParser {
descriptor.addCompoundUniqueConstraint(new CompoundUniqueConstraint(uc.columnNames()));
}
+ View view = cls.getAnnotation(View.class);
+ if (view != null) {
+ descriptor.setView(view.name(), view.dependentTables());
+ }
Table table = cls.getAnnotation(Table.class);
if (table != null) {
UniqueConstraint[] uniqueConstraints = table.uniqueConstraints();
- if (uniqueConstraints != null) {
- for (UniqueConstraint c : uniqueConstraints) {
- descriptor.addCompoundUniqueConstraint(new CompoundUniqueConstraint(c.columnNames()));
- }
+ for (UniqueConstraint c : uniqueConstraints) {
+ descriptor.addCompoundUniqueConstraint(new CompoundUniqueConstraint(c.columnNames()));
}
}
diff --git a/src/main/java/com/avaje/ebeaninternal/server/transaction/PostCommitProcessing.java b/src/main/java/com/avaje/ebeaninternal/server/transaction/PostCommitProcessing.java
index 733effaa0..dee275590 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/transaction/PostCommitProcessing.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/transaction/PostCommitProcessing.java
@@ -84,9 +84,9 @@ public final class PostCommitProcessing {
/**
* Notify the local part of L2 cache.
*/
- void notifyLocalCache() {
+ void notifyLocalCache(boolean viewInvalidation) {
processTableEvents(event.getEventTables());
- cacheChanges = event.buildCacheChanges();
+ cacheChanges = event.buildCacheChanges(viewInvalidation);
}
/**
@@ -149,7 +149,7 @@ public final class PostCommitProcessing {
return new Runnable() {
public void run() {
if (cacheChanges != null) {
- cacheChanges.apply();
+ manager.processViewInvalidation(cacheChanges.apply());
}
localPersistListenersNotify();
notifyCluster();
diff --git a/src/main/java/com/avaje/ebeaninternal/server/transaction/TransactionManager.java b/src/main/java/com/avaje/ebeaninternal/server/transaction/TransactionManager.java
index aa12ed99f..3d1ca8518 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/transaction/TransactionManager.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/transaction/TransactionManager.java
@@ -27,6 +27,7 @@ import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.List;
+import java.util.Set;
import java.util.concurrent.atomic.AtomicLong;
/**
@@ -104,6 +105,8 @@ public class TransactionManager {
*/
private final ChangeLogListener changeLogListener;
+ private final boolean viewInvalidation;
+
/**
* Create the TransactionManager
*/
@@ -113,6 +116,7 @@ public class TransactionManager {
this.persistBatch = config.getPersistBatch();
this.persistBatchOnCascade = config.appliedPersistBatchOnCascade();
this.beanDescriptorManager = descMgr;
+ this.viewInvalidation = descMgr.requiresViewEntityCacheInvalidation();
this.changeLogPrepare = descMgr.getChangeLogPrepare();
this.changeLogListener = descMgr.getChangeLogListener();
this.clusterManager = clusterManager;
@@ -398,7 +402,7 @@ public class TransactionManager {
}
PostCommitProcessing postCommit = new PostCommitProcessing(clusterManager, this, transaction);
- postCommit.notifyLocalCache();
+ postCommit.notifyLocalCache(viewInvalidation);
backgroundExecutor.execute(postCommit.backgroundNotify());
for (TransactionEventListener listener : transactionEventListeners) {
@@ -423,7 +427,7 @@ public class TransactionManager {
event.add(tableEvents);
PostCommitProcessing postCommit = new PostCommitProcessing(clusterManager, this, event);
- postCommit.notifyLocalCache();
+ postCommit.notifyLocalCache(viewInvalidation);
backgroundExecutor.execute(postCommit.backgroundNotify());
}
@@ -478,4 +482,13 @@ public class TransactionManager {
});
}
}
+
+ /**
+ * Invalidate the query caches for entities based on views.
+ */
+ public void processViewInvalidation(Set viewInvalidation) {
+ if (!viewInvalidation.isEmpty()) {
+ beanDescriptorManager.processViewInvalidation(viewInvalidation);
+ }
+ }
}
diff --git a/src/main/resources/ebean-extraddl-1.0.xsd b/src/main/resources/ebean-extraddl-1.0.xsd
new file mode 100644
index 000000000..b9b39bb02
--- /dev/null
+++ b/src/main/resources/ebean-extraddl-1.0.xsd
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/main/resources/migration-support/default-create-table.sql b/src/main/resources/migration-support/default-create-table.sql
index 747975589..9075c0df3 100644
--- a/src/main/resources/migration-support/default-create-table.sql
+++ b/src/main/resources/migration-support/default-create-table.sql
@@ -1,10 +1,10 @@
create table ${table} (
id integer not null,
- status varchar(10) not null,
- run_version varchar(150) not null,
- dep_version varchar(150) not null,
- comment varchar(150),
- checksum integer not null,
+ mtype varchar(1) not null,
+ mstatus varchar(10) not null,
+ mversion varchar(150) not null,
+ mcomment varchar(150),
+ mchecksum integer not null,
run_on timestamp not null,
run_by varchar(30) not null,
run_ip varchar(30),
diff --git a/src/test/java/com/avaje/ebean/EbeanServerFactory_ServerConfigStart_Test.java b/src/test/java/com/avaje/ebean/EbeanServerFactory_ServerConfigStart_Test.java
index 119609cef..3f9bdbed5 100644
--- a/src/test/java/com/avaje/ebean/EbeanServerFactory_ServerConfigStart_Test.java
+++ b/src/test/java/com/avaje/ebean/EbeanServerFactory_ServerConfigStart_Test.java
@@ -12,6 +12,8 @@ public class EbeanServerFactory_ServerConfigStart_Test {
@Test
public void test() throws InterruptedException {
+ System.setProperty("ebean.ignoreExtraDdl", "true");
+
ServerConfig config = new ServerConfig();
config.setName("h2");
config.loadFromProperties();
diff --git a/src/test/java/com/avaje/ebean/dbmigration/model/MigrationVersionTest.java b/src/test/java/com/avaje/ebean/dbmigration/model/MigrationVersionTest.java
index 08e807a6f..c1b8f73cc 100644
--- a/src/test/java/com/avaje/ebean/dbmigration/model/MigrationVersionTest.java
+++ b/src/test/java/com/avaje/ebean/dbmigration/model/MigrationVersionTest.java
@@ -2,11 +2,85 @@ package com.avaje.ebean.dbmigration.model;
import org.junit.Test;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
import static org.assertj.core.api.StrictAssertions.assertThat;
public class MigrationVersionTest {
+ @Test
+ public void sort() {
+
+ List list= new ArrayList();
+ list.add(MigrationVersion.parse("1.1__point"));
+ list.add(MigrationVersion.parse("3.0__three"));
+ list.add(MigrationVersion.parse("1.0__init"));
+ list.add(MigrationVersion.parse("R__beta"));
+ list.add(MigrationVersion.parse("R__alpha"));
+
+ Collections.sort(list);
+
+ assertThat(list.get(0).getComment()).isEqualTo("init");
+ assertThat(list.get(1).getComment()).isEqualTo("point");
+ assertThat(list.get(2).getComment()).isEqualTo("three");
+ assertThat(list.get(3).getComment()).isEqualTo("alpha");
+ assertThat(list.get(4).getComment()).isEqualTo("beta");
+ }
+
+ @Test
+ public void test_parse_when_repeatable() throws Exception {
+
+ MigrationVersion version = MigrationVersion.parse("R__Foo");
+ assertThat(version.getComment()).isEqualTo("Foo");
+ assertThat(version.normalised()).isEqualTo("R");
+ }
+
+ @Test
+ public void test_parse_when_repeatable_case() throws Exception {
+
+ MigrationVersion version = MigrationVersion.parse("r__Foo");
+ assertThat(version.isRepeatable()).isTrue();
+ assertThat(version.getComment()).isEqualTo("Foo");
+ assertThat(version.normalised()).isEqualTo("R");
+ assertThat(version.normalised()).isEqualTo("R");
+ }
+
+ @Test
+ public void test_parse_when_v_prefix() throws Exception {
+
+ MigrationVersion version = MigrationVersion.parse("v1_0__Foo");
+ assertThat(version.isRepeatable()).isFalse();
+ assertThat(version.getComment()).isEqualTo("Foo");
+ assertThat(version.normalised()).isEqualTo("1.0");
+ assertThat(version.asString()).isEqualTo("1_0");
+ assertThat(version.getRaw()).isEqualTo("1_0__Foo");
+ }
+
+ @Test
+ public void repeatable_compareTo() throws Exception {
+
+ MigrationVersion foo = MigrationVersion.parse("R__Foo");
+ MigrationVersion bar = MigrationVersion.parse("R__Bar");
+ assertThat(foo.compareTo(bar)).isGreaterThan(0);
+ assertThat(bar.compareTo(foo)).isLessThan(0);
+
+ MigrationVersion bar2 = MigrationVersion.parse("R__Bar");
+ assertThat(bar.compareTo(bar2)).isEqualTo(0);
+ }
+
+ @Test
+ public void repeatable_compareTo_when_caseDifferent() throws Exception {
+
+ MigrationVersion none = MigrationVersion.parse("R__");
+ MigrationVersion bar = MigrationVersion.parse("R__Bar");
+ MigrationVersion bar2 = MigrationVersion.parse("R__bar");
+ assertThat(none.compareTo(bar)).isLessThan(0);
+ assertThat(bar.compareTo(bar2)).isLessThan(0);
+ assertThat(none.compareTo(bar2)).isLessThan(0);
+ }
@Test
public void test_parse_getComment() throws Exception {
@@ -51,10 +125,14 @@ public class MigrationVersionTest {
MigrationVersion v0 = MigrationVersion.parse("1.1.1_2__Foo");
MigrationVersion v1 = MigrationVersion.parse("1.1.1.2_junk");
MigrationVersion v2 = MigrationVersion.parse("1.1_1.2_foo");
+ MigrationVersion v3 = MigrationVersion.parse("1.1_1.2__foo");
- assertThat(v0.compareTo(v1)).isEqualTo(0);
- assertThat(v1.compareTo(v0)).isEqualTo(0);
+ assertThat(v0.compareTo(v1)).isGreaterThan(0);
+ assertThat(v1.compareTo(v0)).isLessThan(0);
assertThat(v1.compareTo(v2)).isEqualTo(0);
+
+ assertThat(v0.compareTo(v3)).isLessThan(0);
+ assertThat(v3.compareTo(v0)).isGreaterThan(0);
}
@Test
diff --git a/src/test/java/com/avaje/ebean/dbmigration/model/build/ModelBuild_compoundKeyTest.java b/src/test/java/com/avaje/ebean/dbmigration/model/build/ModelBuild_compoundKeyTest.java
index e0da4924b..9a08860ef 100644
--- a/src/test/java/com/avaje/ebean/dbmigration/model/build/ModelBuild_compoundKeyTest.java
+++ b/src/test/java/com/avaje/ebean/dbmigration/model/build/ModelBuild_compoundKeyTest.java
@@ -24,6 +24,9 @@ import static org.assertj.core.api.Assertions.assertThat;
public class ModelBuild_compoundKeyTest extends BaseTestCase {
private SpiEbeanServer getServer() {
+
+ System.setProperty("ebean.ignoreExtraDdl", "true");
+
ServerConfig config = new ServerConfig();
config.setName("h2");
config.loadFromProperties();
@@ -38,7 +41,6 @@ public class ModelBuild_compoundKeyTest extends BaseTestCase {
config.addClass(CKeyAssoc.class);
config.addClass(CKeyParentId.class);
-
return (SpiEbeanServer) EbeanServerFactory.create(config);
}
diff --git a/src/test/java/com/avaje/ebean/dbmigration/model/build/ModelBuild_explicitSequencesTest.java b/src/test/java/com/avaje/ebean/dbmigration/model/build/ModelBuild_explicitSequencesTest.java
index a23ec6a53..49fc3ada9 100644
--- a/src/test/java/com/avaje/ebean/dbmigration/model/build/ModelBuild_explicitSequencesTest.java
+++ b/src/test/java/com/avaje/ebean/dbmigration/model/build/ModelBuild_explicitSequencesTest.java
@@ -18,6 +18,9 @@ import static org.assertj.core.api.Assertions.assertThat;
public class ModelBuild_explicitSequencesTest extends BaseTestCase {
private SpiEbeanServer getServer(boolean postgres) {
+
+ System.setProperty("ebean.ignoreExtraDdl", "true");
+
ServerConfig config = new ServerConfig();
config.setName("h2");
config.loadFromProperties();
diff --git a/src/test/java/com/avaje/ebean/event/BeanFindControllerTest.java b/src/test/java/com/avaje/ebean/event/BeanFindControllerTest.java
index d966179e2..0eaef65df 100644
--- a/src/test/java/com/avaje/ebean/event/BeanFindControllerTest.java
+++ b/src/test/java/com/avaje/ebean/event/BeanFindControllerTest.java
@@ -20,6 +20,8 @@ public class BeanFindControllerTest extends BaseTestCase {
@Test
public void test() {
+ System.setProperty("ebean.ignoreExtraDdl", "true");
+
ServerConfig config = new ServerConfig();
config.setName("h2otherfind");
diff --git a/src/test/java/com/avaje/ebean/event/BeanPersistControllerTest.java b/src/test/java/com/avaje/ebean/event/BeanPersistControllerTest.java
index 93060f864..c889510e0 100644
--- a/src/test/java/com/avaje/ebean/event/BeanPersistControllerTest.java
+++ b/src/test/java/com/avaje/ebean/event/BeanPersistControllerTest.java
@@ -71,6 +71,7 @@ public class BeanPersistControllerTest {
private EbeanServer getEbeanServer(PersistAdapter persistAdapter) {
+ System.setProperty("ebean.ignoreExtraDdl", "true");
ServerConfig config = new ServerConfig();
config.setName("h2ebasicver");
diff --git a/src/test/java/com/avaje/ebean/event/BeanPostLoadTest.java b/src/test/java/com/avaje/ebean/event/BeanPostLoadTest.java
index 83612807a..2372e3c1e 100644
--- a/src/test/java/com/avaje/ebean/event/BeanPostLoadTest.java
+++ b/src/test/java/com/avaje/ebean/event/BeanPostLoadTest.java
@@ -47,6 +47,8 @@ public class BeanPostLoadTest extends BaseTestCase {
private EbeanServer getEbeanServer() {
+ System.setProperty("ebean.ignoreExtraDdl", "true");
+
ServerConfig config = new ServerConfig();
config.setName("h2ebasicver");
diff --git a/src/test/java/com/avaje/ebeaninternal/extraddl/model/ExtraDdlXmlReaderTest.java b/src/test/java/com/avaje/ebeaninternal/extraddl/model/ExtraDdlXmlReaderTest.java
new file mode 100644
index 000000000..6f119f017
--- /dev/null
+++ b/src/test/java/com/avaje/ebeaninternal/extraddl/model/ExtraDdlXmlReaderTest.java
@@ -0,0 +1,47 @@
+package com.avaje.ebeaninternal.extraddl.model;
+
+import org.junit.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.Assert.assertNotNull;
+
+public class ExtraDdlXmlReaderTest {
+
+ @Test
+ public void read() throws Exception {
+
+ ExtraDdl read = ExtraDdlXmlReader.read("/extra-ddl.xml");
+ assertNotNull(read);
+ }
+
+ @Test
+ public void buildExtra_when_h2() {
+
+ String ddl = ExtraDdlXmlReader.buildExtra("h2");
+
+ assertThat(ddl).contains("create or replace view order_agg_vw");
+ assertThat(ddl).contains("-- h2 and postgres script");
+ assertThat(ddl).doesNotContain(" -- oracle only script");
+ }
+
+ @Test
+ public void buildExtra_when_oracle() {
+
+ String ddl = ExtraDdlXmlReader.buildExtra("oracle");
+
+ assertThat(ddl).contains("create or replace view order_agg_vw");
+ assertThat(ddl).doesNotContain("-- h2 and postgres script");
+ assertThat(ddl).contains(" -- oracle only script");
+ }
+
+ @Test
+ public void buildExtra_when_mysql() {
+
+ String ddl = ExtraDdlXmlReader.buildExtra("mysql");
+
+ assertThat(ddl).contains("create or replace view order_agg_vw");
+ assertThat(ddl).doesNotContain("-- h2 and postgres script");
+ assertThat(ddl).doesNotContain(" -- oracle only script");
+ }
+
+}
\ No newline at end of file
diff --git a/src/test/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptor_registerTest.java b/src/test/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptor_registerTest.java
index 747d35bbf..8bfc3bb6a 100644
--- a/src/test/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptor_registerTest.java
+++ b/src/test/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptor_registerTest.java
@@ -19,6 +19,8 @@ public class BeanDescriptor_registerTest {
@Test
public void testRegisterDeregister() throws Exception {
+ System.setProperty("ebean.ignoreExtraDdl", "true");
+
ServerConfig config = new ServerConfig();
config.setName("h2other");
diff --git a/src/test/java/com/avaje/tests/basic/MainDbBoolean.java b/src/test/java/com/avaje/tests/basic/MainDbBoolean.java
index 37669b177..a4929a481 100644
--- a/src/test/java/com/avaje/tests/basic/MainDbBoolean.java
+++ b/src/test/java/com/avaje/tests/basic/MainDbBoolean.java
@@ -37,6 +37,8 @@ public class MainDbBoolean {
*/
private EbeanServer createOracleEbeanServer() {
+ System.setProperty("ebean.ignoreExtraDdl", "true");
+
ServerConfig c = new ServerConfig();
c.setName("ora");
@@ -71,6 +73,8 @@ public class MainDbBoolean {
private EbeanServer createEbeanServer() {
+ System.setProperty("ebean.ignoreExtraDdl", "true");
+
ServerConfig c = new ServerConfig();
c.setName("pgtest");
diff --git a/src/test/java/com/avaje/tests/changelog/TestChangeLog.java b/src/test/java/com/avaje/tests/changelog/TestChangeLog.java
index 885a07bc7..ca3ca980a 100644
--- a/src/test/java/com/avaje/tests/changelog/TestChangeLog.java
+++ b/src/test/java/com/avaje/tests/changelog/TestChangeLog.java
@@ -57,6 +57,8 @@ public class TestChangeLog extends BaseTestCase {
private SpiEbeanServer getServer() {
+ System.setProperty("ebean.ignoreExtraDdl", "true");
+
ServerConfig config = new ServerConfig();
config.setName("h2other");
config.loadFromProperties();
diff --git a/src/test/java/com/avaje/tests/compositekeys/CreateIdExpandedFormServer.java b/src/test/java/com/avaje/tests/compositekeys/CreateIdExpandedFormServer.java
deleted file mode 100644
index 605536f55..000000000
--- a/src/test/java/com/avaje/tests/compositekeys/CreateIdExpandedFormServer.java
+++ /dev/null
@@ -1,30 +0,0 @@
-package com.avaje.tests.compositekeys;
-
-import com.avaje.ebean.EbeanServer;
-import com.avaje.ebean.EbeanServerFactory;
-import com.avaje.ebean.config.ServerConfig;
-import com.avaje.tests.model.composite.RCustomer;
-import com.avaje.tests.model.composite.RCustomerKey;
-import com.avaje.tests.model.composite.ROrder;
-import com.avaje.tests.model.composite.ROrderPK;
-
-public class CreateIdExpandedFormServer {
-
- public static EbeanServer create() {
-
-
- ServerConfig config = new ServerConfig();
- config.setName("h2");
- config.loadFromProperties();
- config.setName("modifiedH2");
-
- config.addClass(ROrder.class);
- config.addClass(ROrderPK.class);
- config.addClass(RCustomer.class);
- config.addClass(RCustomerKey.class);
-
- config.setDatabasePlatform(new ModifiedH2Platform());
-
- return EbeanServerFactory.create(config);
- }
-}
diff --git a/src/test/java/com/avaje/tests/model/view/EOrderAgg.java b/src/test/java/com/avaje/tests/model/view/EOrderAgg.java
new file mode 100644
index 000000000..f4a2d1e55
--- /dev/null
+++ b/src/test/java/com/avaje/tests/model/view/EOrderAgg.java
@@ -0,0 +1,58 @@
+package com.avaje.tests.model.view;
+
+import com.avaje.ebean.annotation.View;
+import com.avaje.tests.model.basic.Order;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.Id;
+import javax.persistence.JoinColumn;
+import javax.persistence.OneToOne;
+
+@Entity
+@View(name = "order_agg_vw", dependentTables = {"o_order", "o_order_detail"})
+public class EOrderAgg {
+
+ @Id @Column(name = "order_id")
+ Long id;
+
+ @OneToOne
+ @JoinColumn(name = "order_id")
+ Order order;
+
+ Double orderTotal;
+
+ Double shipTotal;
+
+ public Long getId() {
+ return id;
+ }
+
+ public void setId(Long id) {
+ this.id = id;
+ }
+
+ public Double getOrderTotal() {
+ return orderTotal;
+ }
+
+ public void setOrderTotal(Double orderTotal) {
+ this.orderTotal = orderTotal;
+ }
+
+ public Double getShipTotal() {
+ return shipTotal;
+ }
+
+ public void setShipTotal(Double shipTotal) {
+ this.shipTotal = shipTotal;
+ }
+
+ public Order getOrder() {
+ return order;
+ }
+
+ public void setOrder(Order order) {
+ this.order = order;
+ }
+}
diff --git a/src/test/java/com/avaje/tests/model/view/TestViewBaseEntity.java b/src/test/java/com/avaje/tests/model/view/TestViewBaseEntity.java
new file mode 100644
index 000000000..2387b9421
--- /dev/null
+++ b/src/test/java/com/avaje/tests/model/view/TestViewBaseEntity.java
@@ -0,0 +1,69 @@
+package com.avaje.tests.model.view;
+
+import com.avaje.ebean.Ebean;
+import com.avaje.ebean.Query;
+import com.avaje.tests.model.basic.Order;
+import com.avaje.tests.model.basic.OrderDetail;
+import com.avaje.tests.model.basic.ResetBasicData;
+import org.junit.Test;
+
+import java.util.List;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class TestViewBaseEntity {
+
+ @Test
+ public void fetch() {
+
+ ResetBasicData.reset();
+
+ Query query = Ebean.find(EOrderAgg.class)
+ .where().gt("orderTotal", 20)
+ .query();
+
+ List list = query.findList();
+
+ assertThat(query.getGeneratedSql()).contains("select t0.order_id c0, t0.order_total c1, t0.ship_total c2, t0.order_id c3 from order_agg_vw t0 where t0.order_total > ? ");
+ assertThat(list).isNotEmpty();
+ }
+
+ @Test
+ public void lazyLoad() {
+
+ ResetBasicData.reset();
+
+ Query query = Ebean.find(EOrderAgg.class)
+ //.fetch("order", "id")
+ .where().gt("orderTotal", 20)
+ .query();
+
+ List list = query.findList();
+ for (EOrderAgg agg : list) {
+ Order order = agg.getOrder();
+ List details = order.getDetails();
+ assertThat(details).isNotEmpty();
+ }
+ }
+
+ @Test
+ public void fetchJoin() {
+
+ ResetBasicData.reset();
+
+ Query query = Ebean.find(EOrderAgg.class)
+ .fetch("order")
+ .fetch("order.details")
+ .where().gt("orderTotal", 20)
+ .query();
+
+ List list = query.findList();
+ for (EOrderAgg agg : list) {
+ Order order = agg.getOrder();
+ List details = order.getDetails();
+ assertThat(details).isNotEmpty();
+ }
+
+ assertThat(query.getGeneratedSql()).contains("from order_agg_vw t0 left outer join o_order t1 on t1.id = t0.order_id left outer join o_customer t3 on t3.id = t1.kcustomer_id left outer join o_order_detail t2 on t2.order_id = t1.id where t2.id > 0 and t0.order_total > ?");
+ }
+}
diff --git a/src/test/java/com/avaje/tests/persistencecontext/TestPersistenceContextServerConfig.java b/src/test/java/com/avaje/tests/persistencecontext/TestPersistenceContextServerConfig.java
index 6de010b32..cb7dc971e 100644
--- a/src/test/java/com/avaje/tests/persistencecontext/TestPersistenceContextServerConfig.java
+++ b/src/test/java/com/avaje/tests/persistencecontext/TestPersistenceContextServerConfig.java
@@ -32,6 +32,8 @@ public class TestPersistenceContextServerConfig extends BaseTestCase {
static EbeanServer create() {
+ System.setProperty("ebean.ignoreExtraDdl", "true");
+
ServerConfig config = new ServerConfig();
config.setName("withPCQuery");
diff --git a/src/test/java/com/avaje/tests/readaudit/TestReadAudit.java b/src/test/java/com/avaje/tests/readaudit/TestReadAudit.java
index 2bbfe1774..07b460005 100644
--- a/src/test/java/com/avaje/tests/readaudit/TestReadAudit.java
+++ b/src/test/java/com/avaje/tests/readaudit/TestReadAudit.java
@@ -306,6 +306,8 @@ public class TestReadAudit extends BaseTestCase {
private SpiEbeanServer getServer() {
+ System.setProperty("ebean.ignoreExtraDdl", "true");
+
ServerConfig config = new ServerConfig();
config.setName("h2other");
config.loadFromProperties();
diff --git a/src/test/java/com/avaje/tests/transaction/TestAutoCommitDataSource.java b/src/test/java/com/avaje/tests/transaction/TestAutoCommitDataSource.java
index 61395f226..7365b352a 100644
--- a/src/test/java/com/avaje/tests/transaction/TestAutoCommitDataSource.java
+++ b/src/test/java/com/avaje/tests/transaction/TestAutoCommitDataSource.java
@@ -39,6 +39,8 @@ public class TestAutoCommitDataSource extends BaseTestCase {
assertTrue(connection.getAutoCommit());
connection.close();
+ System.setProperty("ebean.ignoreExtraDdl", "true");
+
ServerConfig config = new ServerConfig();
config.setName("h2autocommit");
config.loadFromProperties();
diff --git a/src/test/java/com/avaje/tests/transaction/TestExplicitTransactionMode.java b/src/test/java/com/avaje/tests/transaction/TestExplicitTransactionMode.java
index d5957589a..b33455217 100644
--- a/src/test/java/com/avaje/tests/transaction/TestExplicitTransactionMode.java
+++ b/src/test/java/com/avaje/tests/transaction/TestExplicitTransactionMode.java
@@ -39,6 +39,8 @@ public class TestExplicitTransactionMode extends BaseTestCase {
assertTrue(connection.getAutoCommit());
connection.close();
+ System.setProperty("ebean.ignoreExtraDdl", "true");
+
ServerConfig config = new ServerConfig();
config.setName("h2autocommit");
config.loadFromProperties();
diff --git a/src/test/resources/extra-ddl.xml b/src/test/resources/extra-ddl.xml
new file mode 100644
index 000000000..914d464c2
--- /dev/null
+++ b/src/test/resources/extra-ddl.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+ create or replace view order_agg_vw as
+ select d.order_id, sum(d.order_qty * d.unit_price) as order_total,
+ sum(d.ship_qty * d.unit_price) as ship_total
+ from o_order_detail d
+ group by d.order_id;
+
+
+
+
+ -- h2 and postgres script
+
+
+
+ -- oracle only script
+
+
+
diff --git a/src/test/resources/logback-test.xml b/src/test/resources/logback-test.xml
index fc96db081..e4fab19cc 100644
--- a/src/test/resources/logback-test.xml
+++ b/src/test/resources/logback-test.xml
@@ -89,7 +89,7 @@
-
+
\ No newline at end of file