From 81531b6cda43a224f06dc8fb80ef073eb8d777c8 Mon Sep 17 00:00:00 2001 From: Robin Bygrave Date: Thu, 28 Apr 2016 20:19:05 +1200 Subject: [PATCH] #671 - ENH: Add support for @View and extra-ddl.xml to nicely support entity beans based on views --- pom.xml | 2 +- .../java/com/avaje/ebean/annotation/View.java | 38 +++++ .../avaje/ebean/dbmigration/DbMigration.java | 65 ++++++++- .../avaje/ebean/dbmigration/DdlGenerator.java | 27 +++- .../avaje/ebean/dbmigration/DdlRunner.java | 43 ++---- .../dbmigration/model/MigrationVersion.java | 42 +++++- .../model/visitor/VisitAllUsing.java | 2 +- .../runner/LocalMigrationResource.java | 43 ++++++ .../dbmigration/runner/MigrationMetaRow.java | 60 +++++--- .../runner/MigrationScriptRunner.java | 3 +- .../dbmigration/runner/MigrationTable.java | 129 +++++++++------- .../ebeaninternal/api/TransactionEvent.java | 4 +- .../extraddl/model/DdlScript.java | 116 +++++++++++++++ .../extraddl/model/ExtraDdl.java | 71 +++++++++ .../extraddl/model/ExtraDdlXmlReader.java | 82 +++++++++++ .../extraddl/model/ObjectFactory.java | 48 ++++++ .../extraddl/model/package-info.java | 2 + .../server/cache/CacheChangeSet.java | 36 ++++- .../server/deploy/BeanDescriptor.java | 29 +++- .../deploy/BeanDescriptorCacheHelp.java | 3 +- .../server/deploy/BeanDescriptorManager.java | 138 ++++++++++++------ .../deploy/meta/DeployBeanDescriptor.java | 18 +++ .../deploy/parse/AnnotationBeanTable.java | 31 ---- .../server/deploy/parse/AnnotationClass.java | 11 +- .../transaction/PostCommitProcessing.java | 6 +- .../transaction/TransactionManager.java | 17 ++- src/main/resources/ebean-extraddl-1.0.xsd | 26 ++++ .../default-create-table.sql | 10 +- ...nServerFactory_ServerConfigStart_Test.java | 2 + .../model/MigrationVersionTest.java | 82 ++++++++++- .../build/ModelBuild_compoundKeyTest.java | 4 +- .../ModelBuild_explicitSequencesTest.java | 3 + .../ebean/event/BeanFindControllerTest.java | 2 + .../event/BeanPersistControllerTest.java | 1 + .../avaje/ebean/event/BeanPostLoadTest.java | 2 + .../extraddl/model/ExtraDdlXmlReaderTest.java | 47 ++++++ .../deploy/BeanDescriptor_registerTest.java | 2 + .../com/avaje/tests/basic/MainDbBoolean.java | 4 + .../avaje/tests/changelog/TestChangeLog.java | 2 + .../CreateIdExpandedFormServer.java | 30 ---- .../com/avaje/tests/model/view/EOrderAgg.java | 58 ++++++++ .../tests/model/view/TestViewBaseEntity.java | 69 +++++++++ .../TestPersistenceContextServerConfig.java | 2 + .../avaje/tests/readaudit/TestReadAudit.java | 2 + .../transaction/TestAutoCommitDataSource.java | 2 + .../TestExplicitTransactionMode.java | 2 + src/test/resources/extra-ddl.xml | 22 +++ src/test/resources/logback-test.xml | 2 +- 48 files changed, 1187 insertions(+), 255 deletions(-) create mode 100644 src/main/java/com/avaje/ebean/annotation/View.java create mode 100644 src/main/java/com/avaje/ebeaninternal/extraddl/model/DdlScript.java create mode 100644 src/main/java/com/avaje/ebeaninternal/extraddl/model/ExtraDdl.java create mode 100644 src/main/java/com/avaje/ebeaninternal/extraddl/model/ExtraDdlXmlReader.java create mode 100644 src/main/java/com/avaje/ebeaninternal/extraddl/model/ObjectFactory.java create mode 100644 src/main/java/com/avaje/ebeaninternal/extraddl/model/package-info.java delete mode 100644 src/main/java/com/avaje/ebeaninternal/server/deploy/parse/AnnotationBeanTable.java create mode 100644 src/main/resources/ebean-extraddl-1.0.xsd create mode 100644 src/test/java/com/avaje/ebeaninternal/extraddl/model/ExtraDdlXmlReaderTest.java delete mode 100644 src/test/java/com/avaje/tests/compositekeys/CreateIdExpandedFormServer.java create mode 100644 src/test/java/com/avaje/tests/model/view/EOrderAgg.java create mode 100644 src/test/java/com/avaje/tests/model/view/TestViewBaseEntity.java create mode 100644 src/test/resources/extra-ddl.xml diff --git a/pom.xml b/pom.xml index de5b01c10..2379035f0 100644 --- a/pom.xml +++ b/pom.xml @@ -9,7 +9,7 @@ org.avaje.ebeanorm avaje-ebeanorm - 7.8.2-SNAPSHOT + 7.9.1-SNAPSHOT jar avaje-ebeanorm diff --git a/src/main/java/com/avaje/ebean/annotation/View.java b/src/main/java/com/avaje/ebean/annotation/View.java new file mode 100644 index 000000000..0b41ba2b9 --- /dev/null +++ b/src/main/java/com/avaje/ebean/annotation/View.java @@ -0,0 +1,38 @@ +package com.avaje.ebean.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Annotate an entity bean with @View to indicates the bean is based on a view. + *

+ * As such typically the view is defined in extra-ddl.xml using + * create or replace view .... + *

+ *

+ * When using extra-ddl.xml Ebean will run the resulting DDL script after the + * 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. + *

+ */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +public @interface View { + + /** + * The name of the view this entity bean is based on. + */ + String name(); + + /** + * Tables this view is dependent on. + *

+ * 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. + *

+ */ + String[] dependentTables() default {}; +} diff --git a/src/main/java/com/avaje/ebean/dbmigration/DbMigration.java b/src/main/java/com/avaje/ebean/dbmigration/DbMigration.java index 69a9c047f..59297de2c 100644 --- a/src/main/java/com/avaje/ebean/dbmigration/DbMigration.java +++ b/src/main/java/com/avaje/ebean/dbmigration/DbMigration.java @@ -25,10 +25,14 @@ import com.avaje.ebean.dbmigration.model.ModelContainer; import com.avaje.ebean.dbmigration.model.ModelDiff; import com.avaje.ebean.dbmigration.model.PlatformDdlWriter; import com.avaje.ebeaninternal.api.SpiEbeanServer; +import com.avaje.ebeaninternal.extraddl.model.DdlScript; +import com.avaje.ebeaninternal.extraddl.model.ExtraDdl; +import com.avaje.ebeaninternal.extraddl.model.ExtraDdlXmlReader; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.File; +import java.io.FileWriter; import java.io.IOException; import java.util.ArrayList; import java.util.List; @@ -42,7 +46,7 @@ import java.util.List; * and drop objects (drop tables, drop columns). *

*

- * 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