Compare commits

..
Author SHA1 Message Date
Robin Bygrave 7eb443859e [maven-release-plugin] prepare release avaje-ebeanorm-6.15.1 2016-01-08 00:21:44 +13:00
Robin Bygrave 8b39788ae3 Bump pom to 6.15.1-SNAPSHOT 2016-01-08 00:20:27 +13:00
Robin Bygrave 1ec132b7dd #508 - Upgrading to 6.13.5 causes "No suitable driver found for jdbc:mysql" error 2016-01-08 00:18:20 +13:00
Robin Bygrave 343a975073 #520 - SPI - Deprecate SpiEbeanPlugin in favor of SpiServerPlugin. - Deprecate SpiEbeanPlugin 2016-01-07 23:24:57 +13:00
Robin Bygrave 944f7bd086 #520 - SPI - Deprecate SpiEbeanPlugin in favor of SpiServerPlugin. - Add ServiceLoading for SpiServerPlugin 2016-01-07 16:45:24 +13:00
Robin Bygrave 3639c2a540 #518 - Refactor: Remove getDdlGenerator() from SpiEbeanServer. 2016-01-07 14:27:27 +13:00
Robin Bygrave a9cc8ed702 #517 - Refactor DdlGenerator such that it is not an SpiEbeanPlugin 2016-01-07 14:22:37 +13:00
Robin Bygrave 0351a15067 #516 - DdlParser for seed and init sql is not ignoring sql comment lines (starting with --) 2016-01-07 14:03:17 +13:00
Robin Bygrave c7300a1707 #246 - ENH: Add ebean.ddl.initsql=.... to support executing a sql script prior to running the create all DDL - was - When the scripts generates the evolution, it should generate the create schema too. 2016-01-07 13:12:43 +13:00
Robin Bygrave c48008c4b2 #515 - ENH: Add ebean.ddl.seedSql=xxx.sql ... such that a sql script will execute to insert seed data typically for testing 2016-01-07 12:48:55 +13:00
Robin Bygrave e5d8804591 #514 - ENH: Add ebean.ddl.createOnly=true ... such that all the drop table statements are skipped (for running tests against H2 usually) 2016-01-07 10:06:35 +13:00
Robin Bygrave f6dc294bc2 No effective change - update public project url in pom 2016-01-05 21:38:25 +13:00
Robin Bygrave 692baf8bb6 [maven-release-plugin] prepare for next development iteration 2016-01-05 21:28:03 +13:00
Robin Bygrave e1c00f3856 [maven-release-plugin] prepare release avaje-ebeanorm-6.14.1 2016-01-05 21:27:41 +13:00
Robin Bygrave cf173d0695 Bump pom to 6.14.1-SNAPSHOT 2016-01-05 21:26:20 +13:00
Robin Bygrave a3798c6ca3 #506 - Changes to DB Migration, support run on startup and external version numbering - suffix/subdirectory refactor 2016-01-05 21:25:07 +13:00
Robin Bygrave 7bcf1da823 #506 - Changes to DB Migration, support run on startup and external version numbering - suffix/subdirectory refactor 2016-01-05 21:21:16 +13:00
Rob Bygrave 2ccd8dabeb Merge pull request #510 from apuckey/master
Added support for custom database functions that have multiple arguments
2016-01-05 10:25:19 +13:00
Robin Bygrave f9800dd59e #509 - ExpressList.notIn method does not work 2016-01-05 09:11:14 +13:00
Robin Bygrave fb3e8b9fc4 #511 - PagedList getTotalRowCount() includes soft deleted rows in its count 2016-01-05 07:37:17 +13:00
Antony Puckey 18be5989c7 Added support for custom database functions that have multiple arguments
seperated by commas inside brackets.
2015-12-29 11:06:22 +10:00
Robin Bygrave be91e3f3f7 #506 - Changes to DB Migration, support run on startup and external version numbering 2015-12-24 16:41:06 +13:00
Robin Bygrave 03a9aa3dec #507 - PropertiesWrapper support null enum values with getEnum() 2015-12-24 15:23:25 +13:00
Robin Bygrave a2a301beda No effective change - remove commented out lines 2015-12-23 09:13:31 +13:00
Robin Bygrave 695e4f653b No effective change - typo in comment 2015-12-23 07:38:58 +13:00
Robin Bygrave e84c75929c #505 - Remove unused method - EntityBean._ebean_createCopy(); 2015-12-22 17:04:51 +13:00
Robin Bygrave 7592178b6b No effective change - update tests to ensure query does not hit L2 cache 2015-12-22 17:02:35 +13:00
Robin Bygrave 70d65b734f #504 - @Draft treated as transient such that it doesn't invoke lazy loading in 4.8.1 enhancement agent 2015-12-22 16:48:42 +13:00
Robin Bygrave f9a3dfeda0 #503 - @DraftDirty should be marked as not null automatically 2015-12-22 07:52:05 +13:00
Robin Bygrave eb85931fe2 #500 - Change raw(String, Object[]) expression to varargs raw(String, Object...) 2015-12-16 08:11:32 +13:00
Robin Bygrave 1745334737 No effective change - add extra test 2015-12-16 08:06:12 +13:00
Robin Bygrave ea92ddfd89 [maven-release-plugin] prepare for next development iteration 2015-12-15 15:46:21 +13:00
Robin Bygrave 3400928798 [maven-release-plugin] prepare release avaje-ebeanorm-6.13.5 2015-12-15 15:45:55 +13:00
Robin Bygrave 4e665fd66f #499 - DDL issue for entity bean that is both @Draftable with @History - draft only columns included in history table 2015-12-15 15:44:22 +13:00
Robin Bygrave 09cd2c12e2 Add tests for @Draftable from example application 2015-12-15 15:25:19 +13:00
Robin Bygrave e3e45929f0 #498 - @Draftable - lazy loading after stateless update results in - EntityNotFoundException: Bean not found during lazy load or refresh 2015-12-15 15:15:20 +13:00
Robin Bygrave 2caeafaaa2 #498 - @Draftable - lazy loading after stateless update results in - EntityNotFoundException: Bean not found during lazy load or refresh 2015-12-15 14:38:39 +13:00
Robin Bygrave 5cd21de174 #496 - @EnumValue is being trimmed, may result in unexpected behaviour with CHAR columns for some database systems 2015-12-14 17:21:01 +13:00
Robin Bygrave 78df595861 [maven-release-plugin] prepare for next development iteration 2015-12-11 10:28:21 +13:00
58 changed files with 2310 additions and 502 deletions
+4 -4
View File
@@ -9,11 +9,11 @@
<groupId>org.avaje.ebeanorm</groupId>
<artifactId>avaje-ebeanorm</artifactId>
<version>6.13.4</version>
<version>6.15.1</version>
<packaging>jar</packaging>
<name>avaje-ebeanorm</name>
<url>http://www.avaje.org</url>
<url>http://ebean-orm.github.io/</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
@@ -152,7 +152,7 @@
<dependency>
<groupId>org.avaje.ebeanorm</groupId>
<artifactId>avaje-ebeanorm-agent</artifactId>
<version>4.7.1</version>
<version>4.8.1</version>
<scope>test</scope>
</dependency>
@@ -219,7 +219,7 @@
<plugin>
<groupId>org.avaje.ebeanorm</groupId>
<artifactId>avaje-ebeanorm-mavenenhancer</artifactId>
<version>4.7.1</version>
<version>4.8.1</version>
<executions>
<!-- Not going to enhance Model bean -->
<execution>
@@ -45,7 +45,7 @@ final class DRawSqlColumnsParser {
String colInfo = sqlSelect.substring(start, pos++);
colInfo = colInfo.trim();
String[] split = colInfo.split(" ");
String[] split = colInfo.split("\\s(?=[^\\)]*(?:\\(|$))");
if (split.length > 1) {
ArrayList<String> tmp = new ArrayList<String>(split.length);
for (int i = 0; i < split.length; i++) {
@@ -82,11 +82,13 @@ final class DRawSqlColumnsParser {
private void nextComma() {
boolean inQuote = false;
int inbrackets = 0;
while (pos < end) {
char c = sqlSelect.charAt(pos);
if (c == '\'') {
inQuote = !inQuote;
} else if (!inQuote && c == ',') {
if (c == '\'') inQuote = !inQuote;
else if (c == '(') inbrackets++;
else if (c == ')') inbrackets--;
else if (!inQuote && inbrackets == 0 && c == ',') {
return;
}
pos++;
@@ -785,7 +785,7 @@ public interface ExpressionList<T> extends Serializable {
* qualified) will still be translated to their physical name.
* </p>
*/
ExpressionList<T> raw(String raw, Object[] values);
ExpressionList<T> raw(String raw, Object... values);
/**
* Add raw expression with no parameters.
@@ -73,16 +73,6 @@ public interface EntityBean extends Serializable {
*/
EntityBeanIntercept _ebean_intercept();
/**
* Create a copy of this entity bean.
* <p>
* This occurs when a bean is changed. The copy represents the bean as it was
* initially (oldValues) before any changes where made. This is used for
* optimistic concurrency control.
* </p>
*/
Object _ebean_createCopy();
/**
* Set the value of a field of an entity bean of this type.
* <p>
@@ -1,39 +1,393 @@
package com.avaje.ebean.config;
import com.avaje.ebean.EbeanServer;
import com.avaje.ebean.config.dbplatform.DbPlatformName;
import com.avaje.ebean.dbmigration.DbMigration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Configuration for the DB migration processing.
*/
public class DbMigrationConfig {
protected static final Logger logger = LoggerFactory.getLogger(DbMigrationConfig.class);
/**
* The database platform to generate migration DDL for.
*/
protected DbPlatformName platform;
/**
* Set to true if the DB migration should be generated on server start.
*/
protected boolean generate;
/**
* Set to true to suppress the output of the rollback script.
*/
protected boolean suppressRollback;
/**
* The migration version name (typically FlywayDb compatible).
* <p>
* Example: 1.1.1_2
* <p>
* The version is expected to be the combination of the current pom version plus
* a 'feature' id. The combined version must be unique and ordered to work with
* FlywayDb so each developer sets a unique version so that the migration script
* generated is unique (typically just prior to being submitted as a merge request).
*/
protected String version;
/**
* Description text that can be appended to the version to become the ddl script file name.
* <p>
* So if the name is "a foo table" then the ddl script file could be:
* "1.1.1_2__a-foo-table.sql"
* <p>
* When the DB migration relates to a git feature (merge request) then this description text
* is a short description of the feature.
*/
protected String name;
/**
* Resource path for the migration xml and sql.
* Typically you would change 'app' to be a better/more unique.
*/
private String resourcePath = "dbmigration/app";
protected String migrationPath = "dbmigration";
/**
* Subdirectory the model xml files go into.
*/
protected String modelPath = "model";
/**
* Subdirectory the drop ddl scripts go into.
*/
protected String dropPath = "drop";
/**
* Subdirectory the rollback ddl scripts go into.
*/
protected String rollbackPath = "rollback";
/**
* Apply script suffix.
*/
protected String applySuffix = ".sql";
/**
* Default drop script suffix to ddl so that it isn't picked up by FlywayDb.
*/
protected String dropSuffix = ".drop.ddl";
/**
* Default rollback script suffix to ddl so that it isn't picked up by FlywayDb.
*/
protected String rollbackSuffix = ".rollback.ddl";
protected String modelSuffix = ".model.xml";
/**
* Return the DB platform to generate migration DDL for.
*
* We typically need to explicitly specify this as migration can often be generated
* when running against H2.
*/
public DbPlatformName getPlatform() {
return platform;
}
/**
* Set the DB platform to generate migration DDL for.
*/
public void setPlatform(DbPlatformName platform) {
this.platform = platform;
}
/**
* Return the resource path for db migrations.
*/
public String getResourcePath() {
return resourcePath;
public String getMigrationPath() {
return migrationPath;
}
/**
* Set the resource path for db migrations.
* <p>
* Typically this would be something like "dbmigration/myapp" where myapp gives it a
* unique resource path in the case there are multiple EbeanServer applications in the
* single classpath.
* The default of "dbmigration" is reasonable in most cases. You may look to set this
* to be something like "dbmigration/myapp" where myapp gives it a unique resource path
* in the case there are multiple EbeanServer applications in the single classpath.
* </p>
*/
public void setResourcePath(String resourcePath) {
this.resourcePath = resourcePath;
public void setMigrationPath(String migrationPath) {
this.migrationPath = migrationPath;
}
/**
* Return the relative path for the model files (defaults to model).
*/
public String getModelPath() {
return modelPath;
}
/**
* Set the relative path for the model files.
*/
public void setModelPath(String modelPath) {
this.modelPath = modelPath;
}
/**
* Return the relative path for the drop ddl scripts (defaults to drop).
*/
public String getDropPath() {
return dropPath;
}
/**
* Set the relative path for the drop ddl scripts (defaults to drop).
*/
public void setDropPath(String dropPath) {
this.dropPath = dropPath;
}
/**
* Return the relative path for the rollback ddl scripts (defaults to rollback).
*/
public String getRollbackPath() {
return rollbackPath;
}
/**
* Set the relative path for the rollback ddl scripts (defaults to rollback).
*/
public void setRollbackPath(String rollbackPath) {
this.rollbackPath = rollbackPath;
}
/**
* Return the model suffix (defaults to model.xml)
*/
public String getModelSuffix() {
return modelSuffix;
}
/**
* Set the model suffix.
*/
public void setModelSuffix(String modelSuffix) {
this.modelSuffix = modelSuffix;
}
/**
* Return true if the rollback script should not be output.
*/
public boolean isSuppressRollback() {
return suppressRollback;
}
/**
* Set to true to suppress the output of the rollback script.
*/
public void setSuppressRollback(boolean suppressRollback) {
this.suppressRollback = suppressRollback;
}
/**
* Return the apply script suffix (defaults to sql).
*/
public String getApplySuffix() {
return applySuffix;
}
/**
* Set the apply script suffix (defaults to sql).
*/
public void setApplySuffix(String applySuffix) {
this.applySuffix = applySuffix;
}
/**
* Return the drop script suffix (defaults to ddl so that it isn't picked up by FlywayDb).
*/
public String getDropSuffix() {
return dropSuffix;
}
/**
* Set the drop script suffix (defaults to ddl so that it isn't picked up by FlywayDb).
*/
public void setDropSuffix(String dropSuffix) {
this.dropSuffix = dropSuffix;
}
/**
* Return the rollback script suffix (defaults to ddl so that it isn't picked up by FlywayDb).
*/
public String getRollbackSuffix() {
return rollbackSuffix;
}
/**
* Set the rollback script suffix (defaults to ddl so that it isn't picked up by FlywayDb).
*/
public void setRollbackSuffix(String rollbackSuffix) {
this.rollbackSuffix = rollbackSuffix;
}
/**
* Set the migration version.
* <p>
* Note that version set via System property or environment variable <code>ddl.migration.version</code> takes precedence.
*/
public void setVersion(String version) {
this.version = version;
}
/**
* Set the migration name.
* <p>
* Note that name set via System property or environment variable <code>ddl.migration.name</code> takes precedence.
*/
public void setName(String name) {
this.name = name;
}
/**
* Set the model, rollback and drop paths to be empty such that all the migration files are generated
* into a single directory.
*/
public void singleDirectory() {
this.dropPath = "";
this.rollbackPath = "";
this.modelPath = "";
}
/**
* Load the settings from the PropertiesWrapper.
*/
public void loadSettings(PropertiesWrapper properties) {
resourcePath = properties.get("migration.resourcePath", resourcePath);
migrationPath = properties.get("migration.migrationPath", migrationPath);
if (properties.getBoolean("migration.singleDirectory", false)) {
singleDirectory();
} else {
modelPath = properties.get("migration.modelPath", modelPath);
rollbackPath = properties.get("migration.rollbackPath", rollbackPath);
dropPath = properties.get("migration.dropPath", dropPath);
}
applySuffix = properties.get("migration.applySuffix", applySuffix);
dropSuffix = properties.get("migration.dropSuffix", dropSuffix);
rollbackSuffix = properties.get("migration.rollbackSuffix", rollbackSuffix);
modelSuffix = properties.get("migration.modelSuffix", modelSuffix);
platform = properties.getEnum(DbPlatformName.class, "migration.platform", platform);
suppressRollback = properties.getBoolean("migration.suppressRollback", suppressRollback);
generate = properties.getBoolean("migration.generate", generate);
version = properties.get("migration.version", version);
name = properties.get("migration.name", name);
}
/**
* Return true if the migration should be generated.
* <p>
* It is expected that when an environment variable <code>ddl.migration.enabled</code>
* is set to <code>true</code> then the DB migration will generate the migration DDL.
* </p>
*/
public boolean isGenerateOnStart() {
// environment properties take precedence
String envGenerate = readEnvironment("ddl.migration.generate");
if (envGenerate != null) {
return "true".equalsIgnoreCase(envGenerate.trim());
}
return generate;
}
/**
* Called by EbeanServer on start.
*
* <p>
* If enabled this generates the migration xml and DDL scripts.
* </p>
*/
public void generateOnStart(EbeanServer server) {
if (isGenerateOnStart()) {
if (platform == null) {
logger.warn("No platform set for migration DDL generation");
} else {
// generate the migration xml and platform specific DDL
DbMigration migration = new DbMigration(server);
migration.setPlatform(platform);
try {
migration.generateMigration();
} catch (Exception e) {
throw new RuntimeException("Error generating DB migration", e);
}
}
}
}
/**
* Return the migration version (typically FlywayDb compatible).
* <p>
* Example: 1.1.1_2
* <p>
* The version is expected to be the combination of the current pom version plus
* a 'feature' id. The combined version must be unique and ordered to work with
* FlywayDb so each developer sets a unique version so that the migration script
* generated is unique (typically just prior to being submitted as a merge request).
*/
public String getVersion() {
String envVersion = readEnvironment("ddl.migration.version");
if (!isEmpty(envVersion)) {
return envVersion.trim();
}
return version;
}
/**
* Return the migration name which is short description text that can be appended to
* the migration version to become the ddl script file name.
* <p>
* So if the name is "a foo table" then the ddl script file could be:
* "1.1.1_2__a-foo-table.sql"
* </p>
* <p>
* When the DB migration relates to a git feature (merge request) then this description text
* is a short description of the feature.
* </p>
*/
public String getName() {
String envName = readEnvironment("ddl.migration.name");
if (!isEmpty(envName)) {
return envName.trim();
}
return name;
}
/**
* Return the system or environment property.
*/
protected String readEnvironment(String key) {
String val = System.getProperty(key);
if (val == null) {
val = System.getenv(key);
}
return val;
}
/**
* Return true if the string is null or empty.
*/
protected boolean isEmpty(String val) {
return val == null || val.trim().isEmpty();
}
}
@@ -141,8 +141,8 @@ public class PropertiesWrapper {
* Return a Enum property value.
*/
public <T extends Enum<T>> T getEnum(Class<T> enumType, String key, T defaultValue) {
String level = get(key, defaultValue.name());
return Enum.valueOf(enumType, level.toUpperCase());
String level = get(key, null);
return (level == null) ? defaultValue : Enum.valueOf(enumType, level.toUpperCase());
}
}
@@ -212,6 +212,12 @@ public class ServerConfig {
private boolean ddlRun;
private boolean ddlCreateOnly;
private String ddlInitSql;
private String ddlSeedSql;
private boolean useJtaTransactionManager;
/**
@@ -1529,19 +1535,81 @@ public class ServerConfig {
}
/**
* Set to true to run the DDL generation on startup.
* Set to true to generate the "create all" DDL on startup.
*
* Typically we want this on when we are running tests locally (and often using H2)
* and we want to create the full DB schema from scratch to run tests.
*/
public void setDdlGenerate(boolean ddlGenerate) {
this.ddlGenerate = ddlGenerate;
}
/**
* Set to true to run the generated DDL on startup.
* Set to true to run the generated "create all DDL" on startup.
*
* Typically we want this on when we are running tests locally (and often using H2)
* and we want to create the full DB schema from scratch to run tests.
*/
public void setDdlRun(boolean ddlRun) {
this.ddlRun = ddlRun;
}
/**
* Return true if the "drop all ddl" should be skipped.
*
* Typically we want to do this when using H2 (in memory) as our test database and the drop statements
* are not required so skipping the drop table statements etc makes it faster with less noise in the logs.
*/
public boolean isDdlCreateOnly() {
return ddlCreateOnly;
}
/**
* Set to true if the "drop all ddl" should be skipped.
*
* Typically we want to do this when using H2 (in memory) as our test database and the drop statements
* are not required so skipping the drop table statements etc makes it faster with less noise in the logs.
*/
public void setDdlCreateOnly(boolean ddlCreateOnly) {
this.ddlCreateOnly = ddlCreateOnly;
}
/**
* Return SQL script to execute after the "create all" DDL has been run.
* <p>
* Typically this is a sql script that inserts test seed data when running tests.
* Place a sql script in src/test/resources that inserts test seed data.
* </p>
*/
public String getDdlSeedSql() {
return ddlSeedSql;
}
/**
* Set a SQL script to execute after the "create all" DDL has been run.
* <p>
* Typically this is a sql script that inserts test seed data when running tests.
* Place a sql script in src/test/resources that inserts test seed data.
* </p>
*/
public void setDdlSeedSql(String ddlSeedSql) {
this.ddlSeedSql = ddlSeedSql;
}
/**
* Return a SQL script to execute before the "create all" DDL has been run.
*/
public String getDdlInitSql() {
return ddlInitSql;
}
/**
* Set a SQL script to execute before the "create all" DDL has been run.
*/
public void setDdlInitSql(String ddlInitSql) {
this.ddlInitSql = ddlInitSql;
}
/**
* Return true if the DDL should be generated.
*/
@@ -2232,6 +2300,9 @@ public class ServerConfig {
ddlGenerate = p.getBoolean("ddl.generate", ddlGenerate);
ddlRun = p.getBoolean("ddl.run", ddlRun);
ddlCreateOnly = p.getBoolean("ddl.createOnly", ddlCreateOnly);
ddlInitSql = p.get("ddl.initSql", ddlInitSql);
ddlSeedSql = p.get("ddl.seedSql", ddlSeedSql);
classes = getClasses(p);
}
@@ -21,8 +21,8 @@ import com.avaje.ebean.dbmigration.model.CurrentModel;
import com.avaje.ebean.dbmigration.model.MConfiguration;
import com.avaje.ebean.dbmigration.model.MigrationModel;
import com.avaje.ebean.dbmigration.model.ModelContainer;
import com.avaje.ebean.dbmigration.model.PlatformDdlWriter;
import com.avaje.ebean.dbmigration.model.ModelDiff;
import com.avaje.ebean.dbmigration.model.PlatformDdlWriter;
import com.avaje.ebeaninternal.api.SpiEbeanServer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -57,6 +57,13 @@ public class DbMigration {
protected static final Logger logger = LoggerFactory.getLogger(DbMigration.class);
private static final String initialVersion = "1.0";
/**
* Set to true if DbMigration run with online EbeanServer instance.
*/
protected final boolean online;
protected SpiEbeanServer server;
protected DbMigrationConfig migrationConfig;
@@ -71,7 +78,19 @@ public class DbMigration {
protected DbConstraintNaming constraintNaming;
/**
* Create for offline migration generation.
*/
public DbMigration() {
this.online = false;
}
/**
* Create using online EbeanServer.
*/
public DbMigration(EbeanServer server) {
this.online = true;
setServer(server);
}
/**
@@ -125,7 +144,9 @@ public class DbMigration {
*/
public void setPlatform(DatabasePlatform databasePlatform) {
this.databasePlatform = databasePlatform;
DbOffline.setPlatform(databasePlatform.getName());
if (!online) {
DbOffline.setPlatform(databasePlatform.getName());
}
}
/**
@@ -175,16 +196,19 @@ public class DbMigration {
public void generateMigration() throws IOException {
// use this flag to stop other plugins like full DDL generation
DbOffline.setRunningMigration();
if (!online) {
DbOffline.setRunningMigration();
}
setDefaults();
try {
MigrationModel migrationModel = new MigrationModel(migrationConfig.getResourcePath());
ModelContainer migrated = migrationModel.read();
int nextMajorVersion = migrationModel.getNextMajorVersion();
logger.info("next migration version {}", nextMajorVersion);
File migrationDir = getMigrationDirectory();
File modelDir = getModelDirectory(migrationDir);
MigrationModel migrationModel = new MigrationModel(modelDir, migrationConfig.getModelSuffix());
ModelContainer migrated = migrationModel.read();
CurrentModel currentModel = new CurrentModel(server, constraintNaming);
ModelContainer current = currentModel.read();
@@ -200,46 +224,89 @@ public class DbMigration {
// there were actually changes to write
Migration dbMigration = diff.getMigration();
File writePath = getWritePath();
logger.info("migration writing version {} to {}", nextMajorVersion, writePath.getAbsolutePath());
writeMigrationXml(dbMigration, writePath, nextMajorVersion);
String fullVersion = getFullVersion(migrationModel);
if (databasePlatform != null) {
// writer needs the current model to provide table/column details for
// history ddl generation (triggers, history tables etc)
DdlWrite write = new DdlWrite(new MConfiguration(), currentModel.read());
PlatformDdlWriter writer = new PlatformDdlWriter(databasePlatform, serverConfig);
writer.processMigration(dbMigration, write, writePath, nextMajorVersion);
logger.info("generating migration:{}", fullVersion);
if (!writeMigrationXml(dbMigration, modelDir, fullVersion)) {
logger.warn("migration already exists, not generating DDL");
} else {
if (databasePlatform != null) {
// writer needs the current model to provide table/column details for
// history ddl generation (triggers, history tables etc)
DdlWrite write = new DdlWrite(new MConfiguration(), currentModel.read());
PlatformDdlWriter writer = createDdlWriter(databasePlatform, "");
writer.processMigration(dbMigration, write, migrationDir , fullVersion);
}
writeExtraPlatformDdl(fullVersion, currentModel, dbMigration, migrationDir);
}
writeExtraPlatformDdl(nextMajorVersion, currentModel, dbMigration, writePath);
} finally {
DbOffline.reset();
if (!online) {
DbOffline.reset();
}
}
}
/**
* Return the full version for the migration being generated.
*/
private String getFullVersion(MigrationModel migrationModel) {
String version = migrationConfig.getVersion();
if (version == null) {
version = migrationModel.getNextVersion(initialVersion);
}
String fullVersion = version;
String name = migrationConfig.getName();
if (name != null) {
fullVersion += "__" + toUnderScore(name);
}
return fullVersion;
}
/**
* Replace spaces with underscores.
*/
private String toUnderScore(String name) {
return name.replace(' ','_');
}
/**
* Write any extra platform ddl.
*/
protected void writeExtraPlatformDdl(int nextMajorVersion, CurrentModel currentModel, Migration dbMigration, File writePath) throws IOException {
protected void writeExtraPlatformDdl(String fullVersion, CurrentModel currentModel, Migration dbMigration, File writePath) throws IOException {
for (Pair pair : platforms) {
DdlWrite platformBuffer = new DdlWrite(new MConfiguration(), currentModel.read());
PlatformDdlWriter platformWriter = new PlatformDdlWriter(pair.platform, serverConfig, pair.prefix);
platformWriter.processMigration(dbMigration, platformBuffer, writePath, nextMajorVersion);
PlatformDdlWriter platformWriter = createDdlWriter(pair);
platformWriter.processMigration(dbMigration, platformBuffer, writePath, fullVersion);
}
}
private PlatformDdlWriter createDdlWriter(Pair pair) {
return createDdlWriter(pair.platform, pair.prefix);
}
private PlatformDdlWriter createDdlWriter(DatabasePlatform platform, String prefix) {
return new PlatformDdlWriter(platform, serverConfig, prefix, migrationConfig);
}
/**
* Write the migration xml.
*/
protected void writeMigrationXml(Migration dbMigration, File resourcePath, int migrationVersion) {
protected boolean writeMigrationXml(Migration dbMigration, File resourcePath, String fullVersion) {
File file = new File(resourcePath, "v"+migrationVersion+".0.xml");
String modelFile = fullVersion + migrationConfig.getModelSuffix();
File file = new File(resourcePath, modelFile);
if (file.exists()) {
return false;
}
MigrationXmlWriter xmlWriter = new MigrationXmlWriter();
xmlWriter.write(dbMigration, file);
return true;
}
/**
@@ -260,14 +327,13 @@ public class DbMigration {
/**
* Return the file path to write the xml and sql to.
*/
protected File getWritePath() {
protected File getMigrationDirectory() {
// path to src/main/resources in typical maven project
File resourceRootDir = new File(pathToResources);
String resourcePath = migrationConfig.getMigrationPath();
String resourcePath = migrationConfig.getResourcePath();
// expect to be a path to something like - src/main/resources/dbmigration/myapp
// expect to be a path to something like - src/main/resources/dbmigration/model
File path = new File(resourceRootDir, resourcePath);
if (!path.exists()) {
if (!path.mkdirs()) {
@@ -277,6 +343,21 @@ public class DbMigration {
return path;
}
/**
* Return the model directory (relative to the migration directory).
*/
protected File getModelDirectory(File migrationDirectory) {
String modelPath = migrationConfig.getModelPath();
if (modelPath == null || modelPath.isEmpty()) {
return migrationDirectory;
}
File modelDir = new File(migrationDirectory, migrationConfig.getModelPath());
if (!modelDir.exists() && !modelDir.mkdirs()) {
logger.debug("Unable to ensure migration model directory exists at {}", modelDir.getAbsolutePath());
}
return modelDir;
}
/**
* Return the DatabasePlatform given the platform key.
*/
@@ -1,47 +1,43 @@
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.SpiEbeanPlugin;
import com.avaje.ebeaninternal.api.SpiEbeanServer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.persistence.PersistenceException;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.LineNumberReader;
import java.io.StringReader;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.io.Reader;
/**
* Controls the generation of DDL and potentially runs the resulting scripts.
* Controls the generation and execution of "Create All" and "Drop All" DDL scripts.
*
* Typically the "Create All" DDL is executed for running tests etc and has nothing to do
* with DB Migration (diff based) DDL.
*/
public class DdlGenerator implements SpiEbeanPlugin {
public class DdlGenerator {
private static final Logger logger = LoggerFactory.getLogger(DdlGenerator.class);
private final SpiEbeanServer server;
private SpiEbeanServer server;
private boolean generateDdl;
private boolean runDdl;
private final boolean generateDdl;
private final boolean runDdl;
private final boolean createOnly;
private CurrentModel currentModel;
private String dropContent;
private String createContent;
public void setup(SpiEbeanServer server, ServerConfig serverConfig) {
public DdlGenerator(SpiEbeanServer server, ServerConfig serverConfig) {
this.server = server;
this.generateDdl = serverConfig.isDdlGenerate();
this.runDdl = serverConfig.isDdlRun();
this.createOnly = serverConfig.isDdlCreateOnly();
}
/**
@@ -58,9 +54,11 @@ public class DdlGenerator implements SpiEbeanPlugin {
/**
* Generate the DDL drop and create scripts if the properties have been set.
*/
public void generateDdl() {
protected void generateDdl() {
if (generateDdl) {
writeDrop(getDropFileName());
if (!createOnly) {
writeDrop(getDropFileName());
}
writeCreate(getCreateFileName());
}
}
@@ -68,18 +66,14 @@ public class DdlGenerator implements SpiEbeanPlugin {
/**
* Run the DDL drop and DDL create scripts if properties have been set.
*/
public void runDdl() {
protected void runDdl() {
if (runDdl) {
try {
if (dropContent == null) {
dropContent = readFile(getDropFileName());
}
if (createContent == null) {
createContent = readFile(getCreateFileName());
}
runScript(true, dropContent);
runScript(false, createContent);
runInitSql();
runDropSql();
runCreateSql();
runSeedSql();
} catch (IOException e) {
String msg = "Error reading drop/create script from file system";
@@ -88,6 +82,53 @@ public class DdlGenerator implements SpiEbeanPlugin {
}
}
protected void runDropSql() throws IOException {
if (!createOnly) {
if (dropContent == null) {
dropContent = readFile(getDropFileName());
}
runScript(true, dropContent, getDropFileName());
}
}
protected void runCreateSql() throws IOException {
if (createContent == null) {
createContent = readFile(getCreateFileName());
}
runScript(false, createContent, getCreateFileName());
}
protected void runInitSql() throws IOException {
runResourceScript(server.getServerConfig().getDdlInitSql());
}
protected void runSeedSql() throws IOException {
runResourceScript(server.getServerConfig().getDdlSeedSql());
}
protected void runResourceScript(String sqlScript) throws IOException {
if (sqlScript != null) {
InputStream is = getClassLoader().getResourceAsStream(sqlScript);
if (is != null) {
DdlRunner runner = new DdlRunner(false, sqlScript);
String content = readContent(new InputStreamReader(is));
runner.runAll(content, server);
}
}
}
/**
* Return the classLoader to use to read sql scripts as resources.
*/
protected ClassLoader getClassLoader() {
ClassLoader cl = Thread.currentThread().getContextClassLoader();
if (cl == null) {
cl = this.getClassLoader();
}
return cl;
}
protected void writeDrop(String dropFile) {
try {
@@ -108,7 +149,7 @@ public class DdlGenerator implements SpiEbeanPlugin {
}
}
public String generateDropDdl() {
protected String generateDropDdl() {
try {
dropContent = currentModel().getDropDdl();
@@ -118,7 +159,7 @@ public class DdlGenerator implements SpiEbeanPlugin {
}
}
public String generateCreateDdl() {
protected String generateCreateDdl() {
try {
createContent = currentModel().getCreateDdl();
@@ -163,201 +204,33 @@ public class DdlGenerator implements SpiEbeanPlugin {
return null;
}
return readContent(new FileReader(f));
}
protected String readContent(Reader reader) throws IOException {
StringBuilder buf = new StringBuilder();
FileReader fr = new FileReader(f);
LineNumberReader lr = new LineNumberReader(fr);
LineNumberReader lineReader = new LineNumberReader(reader);
try {
String s;
while ((s = lr.readLine()) != null) {
while ((s = lineReader.readLine()) != null) {
buf.append(s).append("\n");
}
} finally {
lr.close();
}
return buf.toString();
return buf.toString();
} finally {
lineReader.close();
}
}
/**
* Execute all the DDL statements in the script.
*/
public void runScript(boolean expectErrors, String content) {
public int runScript(boolean expectErrors, String content, String scriptName) {
StringReader sr = new StringReader(content);
List<String> statements = parseStatements(sr);
Transaction t = server.createTransaction();
try {
Connection connection = t.getConnection();
logger.info("Running DDL");
runStatements(expectErrors, statements, connection);
logger.info("Running DDL Complete");
t.commit();
} catch (Exception e) {
throw new PersistenceException("Error: " + e.getMessage(), e);
} finally {
t.end();
}
DdlRunner runner = new DdlRunner(expectErrors, scriptName);
return runner.runAll(content, server);
}
/**
* Execute the list of statements.
*/
private void runStatements(boolean expectErrors, List<String> statements, Connection c) {
List<String> noDuplicates = new ArrayList<String>();
for (String statement : statements) {
if (!noDuplicates.contains(statement)) {
noDuplicates.add(statement);
}
}
for (int i = 0; i < noDuplicates.size(); i++) {
String xOfy = (i + 1) + " of " + noDuplicates.size();
runStatement(expectErrors, xOfy, noDuplicates.get(i), c);
}
}
/**
* Execute the statement.
*/
private void runStatement(boolean expectErrors, String oneOf, String stmt, Connection c) {
PreparedStatement pstmt = null;
try {
// trim and remove trailing ; or /
stmt = stmt.trim();
if (stmt.endsWith(";")) {
stmt = stmt.substring(0, stmt.length() - 1);
} else if (stmt.endsWith("/")) {
stmt = stmt.substring(0, stmt.length() - 1);
}
logger.info("executing " + oneOf + " " + getSummary(stmt));
pstmt = c.prepareStatement(stmt);
pstmt.execute();
} catch (Exception e) {
if (expectErrors) {
logger.info(" ... ignoring error executing " + getSummary(stmt) + " error: " + e.getMessage());
} else {
String msg = "Error executing stmt[" + stmt + "] error[" + e.getMessage() + "]";
throw new RuntimeException(msg, e);
}
} finally {
if (pstmt != null) {
try {
pstmt.close();
} catch (SQLException e) {
logger.error("Error closing pstmt", e);
}
}
}
}
/**
* Local utility used to detect the end of statements / separate statements.
* This is often just the semicolon character but for trigger/procedures this
* detects the $$ demarcation used in the history DDL generation for MySql and
* Postgres.
*/
static class StatementsSeparator {
ArrayList<String> statements = new ArrayList<String>();
boolean trimDelimiter;
boolean inDbProcedure;
StringBuilder sb = new StringBuilder();
void lineContainsDollars(String line) {
if (inDbProcedure) {
if (trimDelimiter) {
line = line.replace("$$","");
}
endOfStatement(line);
} else {
// MySql style delimiter needs to be trimmed/removed
trimDelimiter = line.equals("delimiter $$");
if (!trimDelimiter) {
sb.append(line).append(" ");
}
}
inDbProcedure = !inDbProcedure;
}
void endOfStatement(String line) {
// end of Db procedure
sb.append(line);
statements.add(sb.toString().trim());
sb = new StringBuilder();
}
void nextLine(String line) {
if (line.contains("$$")) {
lineContainsDollars(line);
return;
}
if (inDbProcedure) {
sb.append(line).append(" ");
return;
}
int semiPos = line.indexOf(';');
if (semiPos == -1) {
sb.append(line).append(" ");
} else if (semiPos == line.length() - 1) {
// semicolon at end of line
endOfStatement(line);
} else {
// semicolon in middle of line
String preSemi = line.substring(0, semiPos);
endOfStatement(preSemi);
sb.append(line.substring(semiPos + 1));
}
}
}
/**
* Break up the sql in reader into a list of statements using the semi-colon
* character;
*/
protected List<String> parseStatements(StringReader reader) {
try {
BufferedReader br = new BufferedReader(reader);
StatementsSeparator statements = new StatementsSeparator();
String s;
while ((s = br.readLine()) != null) {
s = s.trim();
statements.nextLine(s);
}
return statements.statements;
} catch (IOException e) {
throw new PersistenceException(e);
}
}
private String getSummary(String s) {
if (s.length() > 80) {
return s.substring(0, 80).trim() + "...";
}
return s;
}
}
@@ -0,0 +1,110 @@
package com.avaje.ebean.dbmigration;
import javax.persistence.PersistenceException;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.List;
/**
* Parses string content into separate SQL/DDL statements.
*/
public class DdlParser {
/**
* Break up the sql in reader into a list of statements using the semi-colon and $$ delimiters;
*/
public List<String> parse(StringReader reader) {
try {
BufferedReader br = new BufferedReader(reader);
StatementsSeparator statements = new StatementsSeparator();
String s;
while ((s = br.readLine()) != null) {
s = s.trim();
statements.nextLine(s);
}
return statements.statements;
} catch (IOException e) {
throw new PersistenceException(e);
}
}
/**
* Local utility used to detect the end of statements / separate statements.
* This is often just the semicolon character but for trigger/procedures this
* detects the $$ demarcation used in the history DDL generation for MySql and
* Postgres.
*/
static class StatementsSeparator {
ArrayList<String> statements = new ArrayList<String>();
boolean trimDelimiter;
boolean inDbProcedure;
StringBuilder sb = new StringBuilder();
void lineContainsDollars(String line) {
if (inDbProcedure) {
if (trimDelimiter) {
line = line.replace("$$","");
}
endOfStatement(line);
} else {
// MySql style delimiter needs to be trimmed/removed
trimDelimiter = line.equals("delimiter $$");
if (!trimDelimiter) {
sb.append(line).append(" ");
}
}
inDbProcedure = !inDbProcedure;
}
void endOfStatement(String line) {
// end of Db procedure
sb.append(line);
statements.add(sb.toString().trim());
sb = new StringBuilder();
}
void nextLine(String line) {
if (line.contains("$$")) {
lineContainsDollars(line);
return;
}
if (sb.length() == 0 && (line.isEmpty() || line.startsWith("--"))) {
// ignore leading empty lines and sql comments
return;
}
if (inDbProcedure) {
sb.append(line).append(" ");
return;
}
int semiPos = line.indexOf(';');
if (semiPos == -1) {
sb.append(line).append(" ");
} else if (semiPos == line.length() - 1) {
// semicolon at end of line
endOfStatement(line);
} else {
// semicolon in middle of line
String preSemi = line.substring(0, semiPos);
endOfStatement(preSemi);
sb.append(line.substring(semiPos + 1));
}
}
}
}
@@ -0,0 +1,138 @@
package com.avaje.ebean.dbmigration;
import com.avaje.ebean.Transaction;
import com.avaje.ebeaninternal.api.SpiEbeanServer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.persistence.PersistenceException;
import java.io.StringReader;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
/**
* Runs DDL scripts.
*/
public class DdlRunner {
protected static final Logger logger = LoggerFactory.getLogger(DdlRunner.class);
protected DdlParser ddlParser = new DdlParser();
protected final String scriptName;
protected final boolean expectErrors;
/**
* Construct with a script name (for logging) and flag indicating if errors are expected.
*/
public DdlRunner(boolean expectErrors, String scriptName) {
this.expectErrors = expectErrors;
this.scriptName = scriptName;
}
/**
* Parse the content into sql statements and execute them in a transaction.
*/
public int runAll(String content, SpiEbeanServer server) {
List<String> statements = ddlParser.parse(new StringReader(content));
return runStatements(statements, server);
}
/**
* Execute all the statements in a single transaction.
*/
public int runStatements(List<String> statements, SpiEbeanServer server) {
Transaction t = server.createTransaction();
try {
int statementCount = runStatements(expectErrors, statements, t.getConnection());
t.commit();
return statementCount;
} catch (Exception e) {
throw new PersistenceException("Error: " + e.getMessage(), e);
} finally {
t.end();
}
}
/**
* Execute the list of statements.
*/
private int runStatements(boolean expectErrors, List<String> statements, Connection c) {
List<String> noDuplicates = new ArrayList<String>();
for (String statement : statements) {
if (!noDuplicates.contains(statement)) {
noDuplicates.add(statement);
}
}
logger.info("Executing {} - {} statements", scriptName, noDuplicates.size());
for (int i = 0; i < noDuplicates.size(); i++) {
String xOfy = (i + 1) + " of " + noDuplicates.size();
runStatement(expectErrors, xOfy, noDuplicates.get(i), c);
}
return noDuplicates.size();
}
/**
* Execute the statement.
*/
private void runStatement(boolean expectErrors, String oneOf, String stmt, Connection c) {
PreparedStatement pstmt = null;
try {
// trim and remove trailing ; or /
stmt = stmt.trim();
if (stmt.endsWith(";")) {
stmt = stmt.substring(0, stmt.length() - 1);
} else if (stmt.endsWith("/")) {
stmt = stmt.substring(0, stmt.length() - 1);
}
if (logger.isDebugEnabled()) {
logger.debug("executing " + oneOf + " " + getSummary(stmt));
}
pstmt = c.prepareStatement(stmt);
pstmt.execute();
} catch (Exception 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);
}
} finally {
if (pstmt != null) {
try {
pstmt.close();
} catch (SQLException e) {
logger.error("Error closing pstmt", e);
}
}
}
}
private String getSummary(String s) {
if (s.length() > 80) {
return s.substring(0, 80).trim() + "...";
}
return s;
}
}
@@ -160,8 +160,10 @@ public abstract class DbTriggerBasedHistoryDdl implements PlatformHistoryDdl {
Collection<MColumn> cols = table.getColumns().values();
for (MColumn column : cols) {
writeColumnDefinition(apply, column.getName(), column.getType());
apply.append(",").newLine();
if (!column.isDraftOnly()) {
writeColumnDefinition(apply, column.getName(), column.getType());
apply.append(",").newLine();
}
}
writeColumnDefinition(apply, sysPeriodStart, sysPeriodType);
apply.append(",").newLine();
@@ -6,6 +6,9 @@ import com.avaje.ebean.dbmigration.migration.Migration;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Unmarshaller;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
/**
@@ -15,19 +18,6 @@ public class MigrationXmlReader {
private static final MigrationXmlReader INSTANCE = new MigrationXmlReader();
/**
* Read and return a Migration from an xml document at the given resource path.
*/
public static Migration readMaybe(String resourcePath) {
InputStream is = MigrationXmlReader.class.getResourceAsStream(resourcePath);
if (is == null) {
return null;
}
return INSTANCE.read(is);
}
/**
* Read and return a Migration from an xml document at the given resource path.
*/
@@ -41,10 +31,27 @@ public class MigrationXmlReader {
return INSTANCE.read(is);
}
/**
* Read and return a Migration from a migration xml file.
*/
public static Migration read(File migrationFile) {
try {
FileInputStream is = new FileInputStream(migrationFile);
try {
return read(is);
} finally {
is.close();
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
/**
* Read and return a Migration from an xml document.
*/
public Migration read(InputStream is) {
public static Migration read(InputStream is) {
try {
JAXBContext jaxbContext = JAXBContext.newInstance(Migration.class);
@@ -1,12 +1,13 @@
package com.avaje.ebean.dbmigration.model;
import com.avaje.ebean.dbmigration.migration.Migration;
import com.avaje.ebean.dbmigration.migrationreader.MigrationXmlReader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.LinkedHashSet;
import java.util.Set;
import java.io.File;
import java.io.FileFilter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* Build the model from the series of migrations.
@@ -17,26 +18,15 @@ public class MigrationModel {
private final ModelContainer model = new ModelContainer();
private final Set<String> readVersions = new LinkedHashSet<String>();
private final File modelDirectory;
private final String resourcePath;
private final String modelSuffix;
int nextMajorVersion;
private MigrationVersion lastVersion;
public MigrationModel(String resourcePath) {
this.resourcePath = normaliseResourcePath(resourcePath);
}
private String normaliseResourcePath(String resourcePath) {
if (resourcePath.endsWith("/")) {
// trim trailing slash
resourcePath = resourcePath.substring(0, resourcePath.length()-1);
}
if (resourcePath.startsWith("/")) {
// trim leading slash
resourcePath = resourcePath.substring(1);
}
return resourcePath;
public MigrationModel(File modelDirectory, String modelSuffix) {
this.modelDirectory = modelDirectory;
this.modelSuffix = modelSuffix;
}
/**
@@ -46,57 +36,41 @@ public class MigrationModel {
public ModelContainer read() {
readMigrations();
logger.info("read versions {}", readVersions);
return model;
}
/**
* Return the set of versions that were read.
*/
public Set<String> getReadVersions() {
return readVersions;
}
public int getNextMajorVersion() {
return nextMajorVersion;
}
private void readMigrations() {
for (int majorVersion = 1; majorVersion < 100; majorVersion++) {
if (!readMinorVersions(majorVersion)){
// no major.0 version so stopping
nextMajorVersion = majorVersion;
return;
// find all the migration xml files
File[] xmlFiles = modelDirectory.listFiles(new FileFilter() {
@Override
public boolean accept(File pathname) {
return pathname.getName().toLowerCase().endsWith(modelSuffix);
}
});
List<MigrationResource> resources = new ArrayList<MigrationResource>();
for (File xmlFile: xmlFiles) {
resources.add(new MigrationResource(xmlFile));
}
// sort into version order before applying
Collections.sort(resources);
for (MigrationResource migrationResource: resources) {
logger.debug("read {}", migrationResource);
model.apply(migrationResource.read());
}
// remember the last version
if (!resources.isEmpty()) {
lastVersion = resources.get(resources.size() - 1).getVersion();
}
}
private boolean readMinorVersions(int majorVersion) {
public String getNextVersion(String initialVersion) {
for (int minorVersion = 0; minorVersion < 100; minorVersion++) {
if (!readMigration(majorVersion, minorVersion)) {
// continue reading next major if minorVersion 0 was read
return (minorVersion > 0);
}
}
return true;
return lastVersion == null ? initialVersion : lastVersion.nextVersion();
}
private boolean readMigration(int majorVersion, int minorVersion) {
String version = majorVersion+"."+minorVersion;
String path = "/"+resourcePath+"/v"+version+".xml";
Migration migration = MigrationXmlReader.readMaybe(path);
if (migration == null) {
logger.debug("... no migration at path:{}", path);
return false;
}
readVersions.add(version);
logger.trace("... read migration v{}", version);
model.apply(migration);
return true;
}
}
@@ -0,0 +1,51 @@
package com.avaje.ebean.dbmigration.model;
import com.avaje.ebean.dbmigration.migration.Migration;
import com.avaje.ebean.dbmigration.migrationreader.MigrationXmlReader;
import java.io.File;
/**
* Migration XML resource that holds the changes to be applied.
*/
public class MigrationResource implements Comparable<MigrationResource> {
private final File migrationFile;
private final MigrationVersion version;
/**
* Construct with a migration xml file.
*/
public MigrationResource(File migrationFile) {
this.migrationFile = migrationFile;
this.version = MigrationVersion.parse(migrationFile.getName());
}
public String toString() {
return migrationFile.getName();
}
/**
* Return the version associated with this resource.
*/
public MigrationVersion getVersion() {
return version;
}
/**
* Read and return the migration from the resource.
*/
public Migration read() {
return MigrationXmlReader.read(migrationFile);
}
/**
* Compare by underlying version.
*/
@Override
public int compareTo(MigrationResource other) {
return version.compareTo(other.version);
}
}
@@ -0,0 +1,86 @@
package com.avaje.ebean.dbmigration.model;
/**
* The version of a migration used so that migrations are processed in order.
*/
public class MigrationVersion implements Comparable<MigrationVersion> {
/**
* The raw version text.
*/
private final String raw;
/**
* The ordering parts.
*/
private final int[] ordering;
private MigrationVersion(String raw, int[] ordering) {
this.raw = raw;
this.ordering = ordering;
}
public String toString() {
return raw;
}
public String nextVersion() {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < ordering.length; i++) {
if (i < ordering.length -1 ) {
sb.append(ordering[i]).append(".");
} else {
sb.append(ordering[i]+1);
}
}
return sb.toString();
}
@Override
public int compareTo(MigrationVersion other) {
int otherLength = other.ordering.length;
for (int i = 0; i < ordering.length; i++) {
if (i >= otherLength) {
// considered greater
return 1;
}
if (ordering[i] != other.ordering[i]) {
return (ordering[i] > other.ordering[i]) ? 1 : -1;
}
}
// considered the same
return 0;
}
/**
* Parse the raw version string into a MigrationVersion.
*/
public static MigrationVersion parse(String raw) {
String value = raw.replace("__",".");
value = value.replace('_','.');
String[] sections = value.split("\\.");
int[] ordering = new int[sections.length];
int stopIndex = 0;
for (int i = 0; i < sections.length; i++) {
try {
ordering[i] = Integer.parseInt(sections[i]);
stopIndex++;
} catch (NumberFormatException e) {
// stop parsing
break;
}
}
int[] actualOrder = new int[stopIndex];
System.arraycopy(ordering, 0, actualOrder, 0, stopIndex);
return new MigrationVersion(raw, actualOrder);
}
}
@@ -1,5 +1,6 @@
package com.avaje.ebean.dbmigration.model;
import com.avaje.ebean.config.DbMigrationConfig;
import com.avaje.ebean.config.ServerConfig;
import com.avaje.ebean.config.dbplatform.DatabasePlatform;
import com.avaje.ebean.dbmigration.ddlgeneration.DdlHandler;
@@ -24,20 +25,19 @@ public class PlatformDdlWriter {
private final String platformPrefix;
public PlatformDdlWriter(DatabasePlatform platform, ServerConfig serverConfig) {
this(platform, serverConfig, "");
}
private final DbMigrationConfig config;
public PlatformDdlWriter(DatabasePlatform platform, ServerConfig serverConfig, String platformPrefix) {
public PlatformDdlWriter(DatabasePlatform platform, ServerConfig serverConfig, String platformPrefix, DbMigrationConfig config) {
this.platform = platform;
this.serverConfig = serverConfig;
this.platformPrefix = platformPrefix;
this.config = config;
}
/**
* Write the migration as platform specific ddl.
*/
public void processMigration(Migration dbMigration, DdlWrite write, File writePath, int nextMajorVersion) throws IOException {
public void processMigration(Migration dbMigration, DdlWrite write, File writePath, String fullVersion) throws IOException {
DdlHandler handler = handler();
@@ -49,16 +49,16 @@ public class PlatformDdlWriter {
}
handler.generateExtra(write);
writePlatformDdl(write, writePath, nextMajorVersion);
writePlatformDdl(write, writePath, fullVersion);
}
/**
* Write the ddl files.
*/
protected void writePlatformDdl(DdlWrite write, File resourcePath, int migrationVersion) throws IOException {
protected void writePlatformDdl(DdlWrite write, File resourcePath, String fullVersion) throws IOException {
if (!write.isApplyEmpty()) {
FileWriter applyWriter = createWriter(resourcePath, migrationVersion, "apply.sql");
FileWriter applyWriter = createWriter(resourcePath, fullVersion, "", config.getApplySuffix());
try {
writeApplyDdl(applyWriter, write);
applyWriter.flush();
@@ -66,8 +66,8 @@ public class PlatformDdlWriter {
applyWriter.close();
}
if (!write.isApplyRollbackEmpty()) {
FileWriter applyRollbackWriter = createWriter(resourcePath, migrationVersion, "applyRollback.sql");
if (!config.isSuppressRollback() && !write.isApplyRollbackEmpty()) {
FileWriter applyRollbackWriter = createWriter(resourcePath, fullVersion, config.getRollbackPath(), config.getRollbackSuffix());
try {
writeApplyRollbackDdl(applyRollbackWriter, write);
applyRollbackWriter.flush();
@@ -78,7 +78,7 @@ public class PlatformDdlWriter {
}
if (!write.isDropEmpty()) {
FileWriter dropWriter = createWriter(resourcePath, migrationVersion, "drop.sql");
FileWriter dropWriter = createWriter(resourcePath, fullVersion, config.getDropPath(), config.getDropSuffix());
try {
writeDropDdl(dropWriter, write);
dropWriter.flush();
@@ -88,12 +88,28 @@ public class PlatformDdlWriter {
}
}
protected FileWriter createWriter(File resourcePath, int migrationVersion, String suffix) throws IOException {
protected FileWriter createWriter(File path, String fullVersion, String subPath, String suffix) throws IOException {
File applyFile = new File(resourcePath, "v" + migrationVersion + ".0-" + platformPrefix + suffix);
String fileName = fullVersion;
if (!platformPrefix.isEmpty()) {
fileName += "-"+platformPrefix;
}
if (subPath != null && !subPath.isEmpty()) {
path = subPath(path, subPath);
}
fileName += suffix;
File applyFile = new File(path, fileName);
return new FileWriter(applyFile);
}
protected File subPath(File path, String suffix) {
File subPath = new File(path, suffix);
if (!subPath.exists()) {
subPath.mkdirs();
}
return subPath;
}
/**
* Write the 'Apply' DDL buffers to the writer.
*/
@@ -23,7 +23,7 @@ public class ClassUtil {
/**
* Load a class taking into account a context class loader (if present).
*/
private static Class<?> forName(String name) throws ClassNotFoundException {
public static Class<?> forName(String name) throws ClassNotFoundException {
return new ClassLoadContext().forName(name);
}
@@ -7,6 +7,7 @@ import com.avaje.ebean.config.ServerConfig;
*
* author: Richard Vowles - http://gplus.to/RichardVowles
*/
@Deprecated
public interface SpiEbeanPlugin {
/**
@@ -66,11 +66,6 @@ public interface SpiEbeanServer extends EbeanServer, BeanLoader, BeanCollectionL
*/
PersistenceContextScope getPersistenceContextScope(SpiQuery<?> query);
/**
* Return the DDL generator.
*/
DdlGenerator getDdlGenerator();
/**
* Clear the query execution statistics.
*/
@@ -401,8 +401,10 @@ public class DefaultBeanLoader {
ebi.setPersistenceContext(pc);
}
boolean draft = desc.isDraftInstance(bean);
if (embeddedOwnerIndex == -1) {
if (SpiQuery.Mode.LAZYLOAD_BEAN.equals(mode) && desc.isBeanCaching()) {
if (!draft && SpiQuery.Mode.LAZYLOAD_BEAN.equals(mode) && desc.isBeanCaching()) {
// lazy loading and the bean cache is active
if (desc.cacheBeanLoad(bean, ebi, id)) {
return;
@@ -415,10 +417,12 @@ public class DefaultBeanLoader {
SpiQuery<?> query = (SpiQuery<?>) server.createQuery(desc.getBeanType());
query.setLazyLoadProperty(ebi.getLazyLoadProperty());
if (draft) {
query.asDraft();
}
if (embeddedOwnerIndex > -1) {
String embeddedBeanPropertyName = ebi.getProperty(embeddedOwnerIndex);
query.select("id," + embeddedBeanPropertyName);
query.select("id," + ebi.getProperty(embeddedOwnerIndex));
}
// don't collect AutoTune usage profiling information
@@ -47,8 +47,6 @@ public class DefaultContainer implements SpiContainer {
private final JndiDataSourceLookup jndiDataSourceFactory;
// private final AtomicInteger serverId = new AtomicInteger(1);
public DefaultContainer(ContainerConfig containerConfig) {
this.clusterManager = new ClusterManager(containerConfig);
@@ -116,7 +114,6 @@ public class DefaultContainer implements SpiContainer {
ServerCacheManager cacheManager = getCacheManager(serverConfig);
// int uniqueServerId = serverId.incrementAndGet();
SpiBackgroundExecutor bgExecutor = createBackgroundExecutor(serverConfig);
XmlConfigLoader xmlConfigLoader = new XmlConfigLoader(null);
@@ -128,19 +125,6 @@ public class DefaultContainer implements SpiContainer {
cacheManager.init(server);
// if (serverConfig.isRegisterJmxMBeans()) {
// MBeanServer mbeanServer;
// ArrayList<?> list = MBeanServerFactory.findMBeanServer(null);
// if (list.size() == 0) {
// // probably not running in a server
// mbeanServer = MBeanServerFactory.createMBeanServer();
// } else {
// // use the first MBeanServer
// mbeanServer = (MBeanServer) list.get(0);
// }
// server.registerMBeans(mbeanServer, uniqueServerId);
// }
// generate and run DDL if required
// if there are any other tasks requiring action in their plugins, do them as well
if (!DbOffline.isRunningMigration()) {
@@ -9,6 +9,7 @@ import com.avaje.ebean.bean.ObjectGraphNode;
import com.avaje.ebean.bean.PersistenceContext;
import com.avaje.ebean.bean.PersistenceContext.WithOption;
import com.avaje.ebean.cache.ServerCacheManager;
import com.avaje.ebean.config.DbMigrationConfig;
import com.avaje.ebean.config.EncryptKeyManager;
import com.avaje.ebean.config.ServerConfig;
import com.avaje.ebean.config.dbplatform.DatabasePlatform;
@@ -141,7 +142,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
private final List<SpiServerPlugin> serverPlugins;
private DdlGenerator ddlGenerator;
private final DdlGenerator ddlGenerator;
private final ExpressionFactory expressionFactory;
@@ -234,6 +235,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
this.beanLoader = new DefaultBeanLoader(this);
this.jsonContext = config.createJsonContext(this);
this.serverPlugins = config.getPlugins();
this.ddlGenerator = new DdlGenerator(this, serverConfig);
// load normal plugins late and call setup on all
loadAndInitializePlugins(config.getServerConfig());
@@ -260,18 +262,6 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
for (SpiEbeanPlugin plugin : ServiceLoader.load(SpiEbeanPlugin.class)) {
spiPlugins.add(plugin);
plugin.setup(this, config);
if (plugin instanceof DdlGenerator) {
// backwards compatible
ddlGenerator = (DdlGenerator) plugin;
}
}
if (ddlGenerator == null) {
// ServiceLoader not finding ddlGenerator (typically OSGi)
ddlGenerator = new DdlGenerator();
spiPlugins.add(ddlGenerator);
ddlGenerator.setup(this, config);
}
ebeanPlugins = Collections.unmodifiableList(spiPlugins);
@@ -288,6 +278,9 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
* Execute all the plugins with an online flag indicating the DB is up or not.
*/
public void executePlugins(boolean online) {
ddlGenerator.execute(online);
for (SpiEbeanPlugin plugin : ebeanPlugins) {
plugin.execute(online);
}
@@ -336,10 +329,6 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
return expressionFactory;
}
public DdlGenerator getDdlGenerator() {
return ddlGenerator;
}
@Override
public AutoTune getAutoTune() {
return autoTuneService;
@@ -373,6 +362,10 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
* Start any services after registering with the ClusterManager.
*/
public void start() {
DbMigrationConfig migrationConfig = serverConfig.getMigrationConfig();
if (migrationConfig != null) {
migrationConfig.generateOnStart(this);
}
}
/**
@@ -54,6 +54,7 @@ import javax.sql.DataSource;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.ServiceLoader;
/**
* Used to extend the ServerConfig with additional objects used to configure and
@@ -165,6 +166,14 @@ public class InternalConfiguration {
* Return the list of plugins we collected during construction.
*/
public List<SpiServerPlugin> getPlugins() {
// find additional plugins via ServiceLoader ...
for (SpiServerPlugin plugin : ServiceLoader.load(SpiServerPlugin.class)) {
if (!plugins.contains(plugin)) {
plugins.add(plugin);
}
}
return plugins;
}
@@ -461,7 +461,7 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
public boolean isHardDeleteDraft() {
if (type == Type.DELETE && beanDescriptor.isDraftable() && !beanDescriptor.isDraftableElement()) {
// deleting a top level draftable bean
if (!beanDescriptor.isDraftInstance(entityBean)) {
if (beanDescriptor.isLiveInstance(entityBean)) {
throw new PersistenceException("Explicit Delete is not allowed on a 'live' bean - only draft beans");
}
return true;
@@ -474,7 +474,7 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
* Save or Update is not allowed to execute using 'live' beans - must use publish().
*/
public void checkDraft() {
if (beanDescriptor.isDraftable() && !beanDescriptor.isDraftInstance(entityBean)) {
if (beanDescriptor.isDraftable() && beanDescriptor.isLiveInstance(entityBean)) {
throw new PersistenceException("Save or update is not allowed on a 'live' bean - only draft beans");
}
}
@@ -754,7 +754,9 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
intercept.setLoadedProperty(i);
}
beanDescriptor.setEmbeddedOwner(entityBean);
beanDescriptor.setDraft(entityBean);
if (!publish) {
beanDescriptor.setDraft(entityBean);
}
}
public boolean isReference() {
@@ -2024,14 +2024,25 @@ public class BeanDescriptor<T> implements MetaBeanInfo, SpiBeanType<T> {
}
/**
* Return true if the bean is considered a 'draft' instance.
* Return true if the bean is considered a 'draft' instance (not 'live').
*/
public boolean isDraftInstance(EntityBean entityBean) {
if (draft != null) {
return Boolean.TRUE == draft.getValue(entityBean);
}
// no draft property - so just ignore the check / return true
return true;
// no draft property - so return false
return false;
}
/**
* Return true if the bean is draftable and considered a 'live' instance.
*/
public boolean isLiveInstance(EntityBean entityBean) {
if (draft != null) {
return Boolean.FALSE == draft.getValue(entityBean);
}
// no draft property - so return false
return false;
}
/**
@@ -877,6 +877,7 @@ public class DeployBeanProperty {
public void setDraftDirty() {
this.draftOnly = true;
this.draftDirty = true;
this.nullable = false;
}
public boolean isDraftDirty() {
@@ -1,13 +1,13 @@
package com.avaje.ebeaninternal.server.expression;
import java.util.Collection;
import com.avaje.ebean.bean.EntityBean;
import com.avaje.ebean.event.BeanQueryRequest;
import com.avaje.ebeaninternal.api.HashQueryPlanBuilder;
import com.avaje.ebeaninternal.api.SpiExpressionRequest;
import com.avaje.ebeaninternal.server.el.ElPropertyValue;
import java.util.Collection;
class InExpression extends AbstractExpression {
private static final long serialVersionUID = 3150665801693551260L;
@@ -54,10 +54,8 @@ class InExpression extends AbstractExpression {
public void addSql(SpiExpressionRequest request) {
if (values.length == 0) {
if (!not) {
// 'no match' for in empty collection
request.append("1=0");
}
String expr = not ? "1=1" : "1=0";
request.append(expr);
return;
}
@@ -572,7 +572,7 @@ abstract class JunctionExpression<T> implements Junction<T>, SpiExpression, Expr
}
@Override
public ExpressionList<T> raw(String raw, Object[] values) {
public ExpressionList<T> raw(String raw, Object... values) {
return exprList.raw(raw, values);
}
@@ -1,5 +1,12 @@
package com.avaje.ebeaninternal.server.lib.sql;
import com.avaje.ebean.config.DataSourceConfig;
import com.avaje.ebeaninternal.api.ClassUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.persistence.PersistenceException;
import javax.sql.DataSource;
import java.io.PrintWriter;
import java.sql.Connection;
import java.sql.DriverManager;
@@ -12,15 +19,6 @@ import java.util.Map.Entry;
import java.util.Properties;
import java.util.Set;
import javax.persistence.PersistenceException;
import javax.sql.DataSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.avaje.ebean.config.DataSourceConfig;
import com.avaje.ebeaninternal.api.ClassUtil;
/**
* A robust DataSource.
* <p>
@@ -244,6 +242,13 @@ public class DataSourcePool implements DataSource {
private void initialise() throws SQLException {
// Ensure database driver is loaded
try {
ClassUtil.forName(this.databaseDriver);
} catch (Throwable e) {
throw new PersistenceException("Problem loading Database Driver [" + this.databaseDriver + "]: " + e.getMessage(), e);
}
String transIsolation = TransactionIsolation.getLevelDescription(transactionIsolation);
//noinspection StringBufferReplaceableByString
StringBuilder sb = new StringBuilder(70);
@@ -529,6 +529,9 @@ public class DefaultOrmQuery<T> implements SpiQuery<T> {
if (detail != null) {
copy.detail = detail.copy();
}
if (temporalMode == TemporalMode.DRAFT) {
copy.temporalMode = TemporalMode.DRAFT;
}
copy.firstRow = firstRow;
copy.maxRows = maxRows;
@@ -553,7 +553,7 @@ public final class DefaultTypeManager implements TypeManager, KnownImmutable {
maxValueLen = Math.max(maxValueLen, value.length());
Object enumValue = Enum.valueOf(enumType, name.trim());
beanDbMap.add(enumValue, value.trim());
beanDbMap.add(enumValue, value);
}
if (dbColumnLength == 0 && !integerType) {
@@ -466,7 +466,7 @@ public class ClassPathSearch implements ClassPathSearchService {
}
/**
* If a jarfile with a manifest claspath return that.
* If a jarfile with a manifest classpath return that.
*/
private static List<URI> getClassPathFromManifest(File jarFile, Manifest manifest) {
@@ -696,7 +696,7 @@ public class DefaultExpressionList<T> implements SpiExpressionList<T> {
}
@Override
public ExpressionList<T> raw(String raw, Object[] values) {
public ExpressionList<T> raw(String raw, Object... values) {
add(expr.raw(raw, values));
return this;
}
@@ -1 +0,0 @@
com.avaje.ebean.dbmigration.DdlGenerator
@@ -89,7 +89,42 @@ public class TestRawSqlColumnParsing extends TestCase {
}
public void test_withDatabaseFunction() {
ColumnMapping columnMapping = DRawSqlColumnsParser.parse("a a0,b b1, MONTH(MAKEDATE(2015, 241)) m2 , d d3 , e e4 ");
Map<String, Column> mapping = columnMapping.mapping();
assertEquals(5, mapping.size());
Column c = mapping.get("a");
assertEquals("a",c.getDbColumn());
assertEquals(0, c.getIndexPos());
assertEquals("a0",c.getPropertyName());
c = mapping.get("b");
assertEquals("b",c.getDbColumn());
assertEquals(1, c.getIndexPos());
assertEquals("b1",c.getPropertyName());
c = mapping.get("MONTH(MAKEDATE(2015, 241))");
assertEquals("MONTH(MAKEDATE(2015, 241))",c.getDbColumn());
assertEquals(2, c.getIndexPos());
assertEquals("m2",c.getPropertyName());
c = mapping.get("d");
assertEquals("d",c.getDbColumn());
assertEquals(3, c.getIndexPos());
assertEquals("d3",c.getPropertyName());
c = mapping.get("e");
assertEquals("e",c.getDbColumn());
assertEquals(4, c.getIndexPos());
assertEquals("e4",c.getPropertyName());
}
public void test_withAsAlias() {
ColumnMapping columnMapping = DRawSqlColumnsParser.parse("a as a0,'b' b1, \"c(blah)\" as c2 , d as d3 , e as e4 ");
@@ -17,6 +17,6 @@ public class DbMigrationConfigTest {
DbMigrationConfig migrationConfig = config.getMigrationConfig();
assertThat(migrationConfig.getResourcePath()).isEqualTo("dbmigration/myapp");
assertThat(migrationConfig.getMigrationPath()).isEqualTo("dbmigration/myapp");
}
}
@@ -1,5 +1,6 @@
package com.avaje.ebean.config;
import com.avaje.ebean.config.dbplatform.DbPlatformName;
import org.junit.Test;
import java.util.Properties;
@@ -17,6 +18,18 @@ public class PropertiesWrapperTest {
assertEquals("myserver", pw.getServerName());
}
@Test
public void testGetEnum() {
Properties properties = new Properties();
properties.put("platform","postgres");
PropertiesWrapper pw = new PropertiesWrapper("pref", "myserver", properties);
assertEquals(DbPlatformName.POSTGRES, pw.getEnum(DbPlatformName.class, "platform", DbPlatformName.H2));
assertEquals(DbPlatformName.H2, pw.getEnum(DbPlatformName.class, "junk", DbPlatformName.H2));
assertNull(pw.getEnum(DbPlatformName.class, "junk", null));
}
@Test
public void testTrimPropertyValues() {
@@ -0,0 +1,58 @@
package com.avaje.ebean.dbmigration;
import org.junit.Test;
import java.io.StringReader;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
public class DdlParserTest {
DdlParser parser = new DdlParser();
@Test
public void parse_ignoresEmptyLines() throws Exception {
List<String> stmts = parser.parse(new StringReader("\n\none;\n\ntwo;\n\n"));
assertThat(stmts).hasSize(2);
assertThat(stmts).contains("one;","two;");
}
@Test
public void parse_ignoresComments_whenFirst() throws Exception {
List<String> stmts = parser.parse(new StringReader("-- comment\ntwo;"));
assertThat(stmts).hasSize(1);
assertThat(stmts).contains("two;");
}
@Test
public void parse_ignoresEmptyLines_whenFirst() throws Exception {
List<String> stmts = parser.parse(new StringReader("\n\n-- comment\ntwo;\n\n"));
assertThat(stmts).hasSize(1);
assertThat(stmts).contains("two;");
}
@Test
public void parse_inlineEmptyLines_replacedWithSpace() throws Exception {
List<String> stmts = parser.parse(new StringReader("\n\n-- comment\none\ntwo;\n\n"));
assertThat(stmts).hasSize(1);
assertThat(stmts).contains("one two;");
}
@Test
public void parse_ignoresComments() throws Exception {
List<String> stmts = parser.parse(new StringReader("one;\n-- comment\ntwo;"));
assertThat(stmts).hasSize(2);
assertThat(stmts).contains("one;","two;");
}
}
@@ -1,39 +0,0 @@
package com.avaje.ebean.dbmigration.model;
import org.junit.Test;
import static org.assertj.core.api.Assertions.*;
public class MigrationModelTest {
@Test
public void testRead() throws Exception {
MigrationModel migrationModel = new MigrationModel("dbmigration/app1");
ModelContainer model = migrationModel.read();
assertThat(migrationModel.getReadVersions()).contains("1.0","1.1","2.0");
assertThat(model.getTable("v10_table")).isNotNull();
}
@Test
public void testRead_leadingSlash() throws Exception {
MigrationModel migrationModel = new MigrationModel("/dbmigration/app1");
ModelContainer model = migrationModel.read();
assertThat(migrationModel.getReadVersions()).contains("1.0","1.1","2.0");
assertThat(model.getTable("v10_table")).isNotNull();
}
@Test
public void testRead_trailingSlash() throws Exception {
MigrationModel migrationModel = new MigrationModel("/dbmigration/app1/");
ModelContainer model = migrationModel.read();
assertThat(migrationModel.getReadVersions()).contains("1.0","1.1","2.0");
assertThat(model.getTable("v10_table")).isNotNull();
}
}
@@ -0,0 +1,47 @@
package com.avaje.ebean.dbmigration.model;
import org.junit.Test;
import static org.assertj.core.api.StrictAssertions.assertThat;
public class MigrationVersionTest {
@Test
public void testParse() throws Exception {
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");
assertThat(v0.compareTo(v1)).isEqualTo(0);
assertThat(v1.compareTo(v0)).isEqualTo(0);
assertThat(v1.compareTo(v2)).isEqualTo(0);
}
@Test
public void testNextVersion() {
assertThat(MigrationVersion.parse("2").nextVersion()).isEqualTo("3");
assertThat(MigrationVersion.parse("1.0").nextVersion()).isEqualTo("1.1");
assertThat(MigrationVersion.parse("2.0.b34").nextVersion()).isEqualTo("2.1");
assertThat(MigrationVersion.parse("1.1.1_2__Foo").nextVersion()).isEqualTo("1.1.1.3");
assertThat(MigrationVersion.parse("1.1.1.2_junk").nextVersion()).isEqualTo("1.1.1.3");
}
@Test
public void testCompareTo() throws Exception {
MigrationVersion v1 = MigrationVersion.parse("1.1.1.2_junk");
MigrationVersion v2 = MigrationVersion.parse("2.1_1.2_junk");
MigrationVersion v3 = MigrationVersion.parse("1.2_1.2_junk");
MigrationVersion v4 = MigrationVersion.parse("1.1_1.3_junk");
MigrationVersion v5 = MigrationVersion.parse("1.1.1.1_junk");
assertThat(v1.compareTo(v2)).isEqualTo(-1);
assertThat(v1.compareTo(v3)).isEqualTo(-1);
assertThat(v1.compareTo(v4)).isEqualTo(-1);
assertThat(v1.compareTo(v5)).isEqualTo(1);
}
}
@@ -0,0 +1,17 @@
package com.avaje.ebean.server.type;
import com.avaje.ebean.annotation.EnumValue;
/**
* Enum test when DB CHAR column used with spaces.
*/
public enum MyDayOfWeek {
@EnumValue("MONDAY ") MONDAY,
@EnumValue("TUESDAY ") TUESDAY,
@EnumValue("WEDNESDAY") WEDNESDAY,
@EnumValue("THURSDAY ") THURSDAY,
@EnumValue("FRIDAY ") FRIDAY,
@EnumValue("SATURDAY ") SATURDAY,
@EnumValue("SUNDAY ") SUNDAY
}
@@ -1,34 +1,55 @@
package com.avaje.ebean.server.type;
import java.sql.Types;
import org.junit.Assert;
import org.junit.Test;
import com.avaje.ebean.BaseTestCase;
import com.avaje.ebean.config.ServerConfig;
import com.avaje.ebean.config.dbplatform.H2Platform;
import com.avaje.ebeaninternal.server.core.BootupClasses;
import com.avaje.ebeaninternal.server.type.CtCompoundType;
import com.avaje.ebeaninternal.server.type.DefaultTypeManager;
import com.avaje.ebeaninternal.server.type.RsetDataReader;
import com.avaje.ebeaninternal.server.type.ScalarDataReader;
import com.avaje.ebeaninternal.server.type.ScalarType;
import com.avaje.ebeaninternal.server.type.reflect.CheckImmutableResponse;
import com.avaje.tests.model.ivo.CMoney;
import com.avaje.tests.model.ivo.ExhangeCMoneyRate;
import com.avaje.tests.model.ivo.Money;
import org.junit.Assert;
import org.junit.Test;
import java.sql.SQLException;
import java.sql.Types;
import static org.assertj.core.api.Assertions.assertThat;
public class TestTypeManager extends BaseTestCase {
@Test
public void testEnumWithChar() throws SQLException {
DefaultTypeManager typeManager = createTypeManager();
ScalarType<?> dayOfWeekType = typeManager.createEnumScalarType(MyDayOfWeek.class);
Object val = dayOfWeekType.read(new DummyDataReader("MONDAY "));
assertThat(val).isEqualTo(MyDayOfWeek.MONDAY);
val = dayOfWeekType.read(new DummyDataReader("TUESDAY "));
assertThat(val).isEqualTo(MyDayOfWeek.TUESDAY);
val = dayOfWeekType.read(new DummyDataReader("WEDNESDAY"));
assertThat(val).isEqualTo(MyDayOfWeek.WEDNESDAY);
val = dayOfWeekType.read(new DummyDataReader("THURSDAY "));
assertThat(val).isEqualTo(MyDayOfWeek.THURSDAY);
val = dayOfWeekType.read(new DummyDataReader("FRIDAY "));
assertThat(val).isEqualTo(MyDayOfWeek.FRIDAY);
}
@Test
public void test() {
ServerConfig serverConfig = new ServerConfig();
serverConfig.setDatabasePlatform(new H2Platform());
BootupClasses bootupClasses = new BootupClasses();
DefaultTypeManager typeManager = new DefaultTypeManager(serverConfig, bootupClasses);
DefaultTypeManager typeManager = createTypeManager();
CheckImmutableResponse checkImmutable = typeManager.checkImmutable(Money.class);
Assert.assertTrue(checkImmutable.isImmutable());
@@ -50,4 +71,31 @@ public class TestTypeManager extends BaseTestCase {
}
private DefaultTypeManager createTypeManager() {
ServerConfig serverConfig = new ServerConfig();
serverConfig.setDatabasePlatform(new H2Platform());
BootupClasses bootupClasses = new BootupClasses();
return new DefaultTypeManager(serverConfig, bootupClasses);
}
/**
* Test double DataReader implementation.
*/
private static class DummyDataReader extends RsetDataReader {
String val;
public DummyDataReader(String val) {
super(null);
this.val = val;
}
@Override
public String getString() throws SQLException {
return val;
}
}
}
@@ -82,11 +82,6 @@ public class TDSpiEbeanServer implements SpiEbeanServer {
return null;
}
@Override
public DdlGenerator getDdlGenerator() {
return null;
}
@Override
public ReadAuditLogger getReadAuditLogger() {
return null;
@@ -2,23 +2,37 @@ package com.avaje.tests.basic;
import java.util.List;
import org.junit.Assert;
import com.avaje.ebean.Query;
import org.junit.Test;
import com.avaje.ebean.BaseTestCase;
import com.avaje.ebean.Ebean;
import com.avaje.tests.model.basic.Order;
import static org.assertj.core.api.StrictAssertions.assertThat;
import static org.junit.Assert.assertEquals;
public class TestInEmpty extends BaseTestCase {
@Test
public void test() {
public void test_in_empty() {
List<Order> list = Ebean.find(Order.class).where().gt("id", 0).in("id", new Object[0])
.findList();
Query<Order> query = Ebean.find(Order.class).where().in("id", new Object[0]).gt("id", 0)
.query();
Assert.assertEquals(0, list.size());
List<Order> list = query.findList();
assertThat(query.getGeneratedSql()).contains("1=0");
assertEquals(0, list.size());
}
@Test
public void test_notIn_empty() {
Query<Order> query = Ebean.find(Order.class).where().notIn("id", new Object[0]).gt("id", 0)
.query();
query.findList();
assertThat(query.getGeneratedSql()).contains("1=1");
}
}
@@ -0,0 +1,299 @@
package com.avaje.tests.draftable;
import com.avaje.ebean.BaseTestCase;
import com.avaje.ebean.Ebean;
import com.avaje.ebean.EbeanServer;
import com.avaje.ebean.Query;
import com.avaje.tests.model.draftable.Doc;
import com.avaje.tests.model.draftable.Link;
import org.assertj.core.api.StrictAssertions;
import org.avaje.ebeantest.LoggedSqlCollector;
import org.junit.Test;
import javax.persistence.PersistenceException;
import java.sql.Timestamp;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.Assert.assertTrue;
public class DocLinkTest extends BaseTestCase {
@Test
public void testLazyLoadOnDraftProperty() {
Link link1 = new Link("something");
link1.save();
Ebean.getDefaultServer().publish(Link.class, link1.getId());
Link link = Ebean.find(Link.class)
.setId(link1.getId())
.select("name")
.findUnique();
assertThat(link).isNotNull();
LoggedSqlCollector.start();
// no lazy loading is invoked as draft property considered @Transient
assertThat(link.isDraft()).isFalse();
List<String> loggedSql = LoggedSqlCollector.stop();
assertThat(loggedSql).isEmpty();
}
@Test
public void testUpdate_whenNotPublished() {
Link link1 = new Link("update");
StrictAssertions.assertThat(link1.isDraft()).isFalse();
link1.save();
StrictAssertions.assertThat(link1.isDraft()).isTrue();
// perform stateless update
Link linkUpdate = new Link();
linkUpdate.setId(link1.getId());
linkUpdate.setComment("stateless update");
linkUpdate.setDraft(true);
linkUpdate.update();
// invoke lazy loading on the updated bean
// automatically set asDraft() on lazy loading query
linkUpdate.getLocation();
Ebean.deletePermanent(linkUpdate);
}
@Test
public void testDelete_whenNotPublished() {
Link link1 = new Link("Ld1");
StrictAssertions.assertThat(link1.isDraft()).isFalse();
link1.save();
StrictAssertions.assertThat(link1.isDraft()).isTrue();
link1.setComment("some change");
link1.save();
Ebean.delete(link1);
}
@Test
public void testDeletePermanent_whenPublished2() {
Link link1 = new Link("Ld2");
link1.save();
Ebean.getDefaultServer().publish(Link.class, link1.getId());
Link link = Ebean.find(Link.class).setId(link1.getId()).asDraft().findUnique();
Ebean.deletePermanent(link);
}
@Test
public void testDeleteLivePermanent_throwsException() {
Link link1 = new Link("Ld2");
link1.save();
Link live = Ebean.getDefaultServer().publish(Link.class, link1.getId());
try {
Ebean.deletePermanent(live);
assertTrue("never get here",false);
} catch (PersistenceException e) {
// assert nice message when trying to delete live bean
assertThat(e.getMessage().contains("Explicit Delete is not allowed on a 'live' bean - only draft beans"));
}
}
@Test
public void testDelete_whenPublished() {
Link link1 = new Link("Ld2");
link1.save();
EbeanServer server = Ebean.getDefaultServer();
server.publish(Link.class, link1.getId());
link1 = Ebean.find(Link.class).setId(link1.getId()).asDraft().findUnique();
StrictAssertions.assertThat(link1.isDraft()).isTrue();
// this is a soft delete (no automatic publish here, only updates draft)
link1.delete();
Link live = Ebean.find(Link.class).setId(link1.getId()).findUnique();
assertThat(live).isNotNull();
StrictAssertions.assertThat(live.isDraft()).isFalse();
StrictAssertions.assertThat(live.isDeleted()).isFalse(); // soft delete state not published yet
// this is a permanent delete (effectively has automatic publish)
server.deletePermanent(link1);
live = Ebean.find(Link.class).setId(link1.getId()).findUnique();
assertThat(live).isNull();
}
@Test
public void testUpdateLive_throwsException() {
Link link1 = new Link("forUpdateLive");
link1.save();
Link live = Ebean.getDefaultServer().publish(Link.class, link1.getId());
live.setComment("foo");
// Expect a nice
try {
live.save();
assertTrue("Never get here",false);
} catch (PersistenceException e) {
// we want to assert the message is nice and meaningful (and not a optimistic locking exception etc)
assertThat(e.getMessage()).contains("Save or update is not allowed on a 'live' bean - only draft beans");
}
}
@Test
public void testDirtyState() {
Timestamp when = new Timestamp(System.currentTimeMillis());
String comment = "Really interesting";
Link link1 = new Link("Ls1");
link1.setComment(comment);
link1.setWhenPublish(when);
link1.save();
Link draft1 = Ebean.find(Link.class).setId(link1.getId()).asDraft().findUnique();
StrictAssertions.assertThat(draft1.isDirty()).isTrue();
EbeanServer server = Ebean.getDefaultServer();
Link linkLive = server.publish(Link.class, link1.getId(), null);
StrictAssertions.assertThat(linkLive.getComment()).isEqualTo(comment);
StrictAssertions.assertThat(linkLive.getWhenPublish()).isEqualTo(when);
Link draft1b = Ebean.find(Link.class).setId(link1.getId()).asDraft().findUnique();
StrictAssertions.assertThat(draft1b.isDirty()).isFalse();
StrictAssertions.assertThat(draft1b.getComment()).isNull();
StrictAssertions.assertThat(draft1b.getWhenPublish()).isNull();
}
@Test
public void testSave() {
Link link1 = new Link("LinkOne");
link1.save();
Link link2 = new Link("LinkTwo");
link2.save();
Link link3 = new Link("LinkThree");
link3.save();
EbeanServer server = Ebean.getDefaultServer();
server.publish(Link.class, link1.getId(), null);
server.publish(Link.class, link2.getId(), null);
server.publish(Link.class, link3.getId(), null);
Doc doc1 = new Doc("DocOne");
doc1.getLinks().add(link1);
doc1.getLinks().add(link2);
doc1.save();
Doc draftDoc1 = server.find(Doc.class)
.setId(doc1.getId())
.asDraft()
.findUnique();
assertThat(draftDoc1.getLinks()).hasSize(2);
Doc liveDoc1 = server.publish(Doc.class, doc1.getId(), null);
assertThat(liveDoc1.getLinks()).hasSize(2);
assertThat(liveDoc1.getLinks()).extracting("id").contains(link1.getId(), link2.getId());
draftDoc1.getLinks().remove(0);
draftDoc1.getLinks().add(link3);
draftDoc1.save();
// publish with insert and delete of Links M2M relationship
Doc liveDoc2 = server.publish(Doc.class, doc1.getId(), null);
assertThat(liveDoc2.getLinks()).hasSize(2);
assertThat(liveDoc2.getLinks()).extracting("id").contains(link2.getId(), link3.getId());
// delete the draft and live beans (with associated children)
draftDoc1.delete();
}
@Test
public void testDraftRestore() {
Link link1 = new Link("Ldr1");
link1.setLocation("firstLocation");
link1.save();
EbeanServer server = Ebean.getDefaultServer();
Link live = server.publish(Link.class, link1.getId(), null);
StrictAssertions.assertThat(live.isDraft()).isFalse();
Link draftLink = Ebean.find(Link.class)
.setId(link1.getId())
.asDraft()
.findUnique();
draftLink.setLocation("secondLocation");
draftLink.save();
server.draftRestore(Link.class, link1.getId(), null);
draftLink = Ebean.find(Link.class)
.setId(link1.getId())
.asDraft()
.findUnique();
StrictAssertions.assertThat(draftLink.getLocation()).isEqualTo("firstLocation");
}
@Test
public void testDraftRestoreViaQuery() {
Link link1 = new Link("Ldr1");
link1.setLocation("firstLocation");
link1.setComment("Banana");
link1.save();
EbeanServer server = Ebean.getDefaultServer();
server.publish(Link.class, link1.getId(), null);
Link draftLink = Ebean.find(Link.class)
.setId(link1.getId())
.asDraft()
.findUnique();
draftLink.setLocation("secondLocation");
draftLink.setComment("A good change");
draftLink.save();
Query<Link> query = server.find(Link.class).where().eq("id", link1.getId()).query();
List<Link> links = server.draftRestore(query);
assertThat(links).hasSize(1);
StrictAssertions.assertThat(links.get(0).getLocation()).isEqualTo("firstLocation");
StrictAssertions.assertThat(links.get(0).isDirty()).isEqualTo(false);
StrictAssertions.assertThat(links.get(0).getComment()).isNull();
}
}
@@ -0,0 +1,58 @@
package com.avaje.tests.draftable;
import com.avaje.ebean.Ebean;
import com.avaje.ebean.EbeanServer;
import com.avaje.ebean.PagedList;
import com.avaje.ebean.Query;
import com.avaje.tests.model.draftable.Link;
import org.junit.Test;
import java.util.ArrayList;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
public class LinkQueryPublishTest {
@Test
public void testPublishViaQuery() {
Link link1 = new Link("L1");
link1.save();
Link link2 = new Link("L2");
link2.save();
Link link3 = new Link("L3");
link3.save();
EbeanServer server = Ebean.getDefaultServer();
List<Object> ids = new ArrayList<Object>();
ids.add(link1.getId());
ids.add(link2.getId());
ids.add(link3.getId());
PagedList<Link> pagedList =
server.find(Link.class).asDraft()
.where().idIn(ids)
.setMaxRows(10)
.findPagedList();
assertThat(pagedList.getTotalRowCount()).isEqualTo(3);
Query<Link> pubQuery = server.find(Link.class)
.where().idIn(ids)
.order().asc("id");
List<Link> pubList = server.publish(pubQuery);
assertThat(pubList).hasSize(3);
assertThat(pubList).extracting("id").contains(link1.getId(), link2.getId(), link3.getId());
}
}
@@ -0,0 +1,116 @@
package com.avaje.tests.draftable;
import com.avaje.ebean.Ebean;
import com.avaje.ebean.EbeanServer;
import com.avaje.tests.model.draftable.Document;
import com.avaje.tests.model.draftable.DocumentMedia;
import com.avaje.tests.model.draftable.Organisation;
import org.assertj.core.api.StrictAssertions;
import org.jetbrains.annotations.NotNull;
import org.junit.Test;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
public class OrganisationTest {
@Test
public void testSave() {
Organisation org = new Organisation("OrgOne");
org.save();
assertNotNull(org.getId());
Document doc = new Document();
doc.setTitle("NewTitle");
doc.setOrganisation(org);
doc.setBody("Hello");
doc.save();
doc.setBody("Change content");
doc.save();
EbeanServer server = Ebean.getDefaultServer();
Document draftDoc = server.find(Document.class)
.asDraft()
.setId(doc.getId())
.findUnique();
assertNotNull(draftDoc);
Document liveDoc = server.find(Document.class)
.setId(doc.getId())
.findUnique();
assertNull(liveDoc);
server.publish(Document.class, doc.getId(), null);
doc.setTitle("Mod1");
doc.save();
server.publish(Document.class, doc.getId(), null);
}
@Test
public void testSaveWithCascade() {
Organisation org = new Organisation("Org2");
org.save();
Document doc = new Document();
doc.setTitle("Title1");
doc.setOrganisation(org);
doc.setBody("Body1");
doc.getMedia().add(createMedia("media1"));
doc.getMedia().add(createMedia("media2"));
doc.save();
EbeanServer server = Ebean.getDefaultServer();
server.publish(Document.class, doc.getId(), null);
Document fetchDoc = Ebean.find(Document.class).setId(doc.getId()).asDraft().findUnique();
List<DocumentMedia> media = fetchDoc.getMedia();
assertThat(media.size()).isEqualTo(2);
// // delete one of the 'child' @DraftElement rows ...
// SqlUpdate sqlUpdate = Ebean.createSqlUpdate("delete from document_media_draft where id = ?");
// sqlUpdate.setParameter(1, doc.getMedia().get(0).getId());
// sqlUpdate.execute();
doc.getMedia().get(1).setDescription("mod");
doc.getMedia().add(createMedia("media3"));
doc.getMedia().remove(0);
doc.setBody("Body2");
doc.save();
// publish will perform an insert, update and delete on child DocumentMedia
// during the publish below with media1 being deleted
Document liveBean = server.publish(Document.class, doc.getId(), null);
StrictAssertions.assertThat(liveBean.getBody()).isEqualTo("Body2");
StrictAssertions.assertThat(liveBean.getMedia().size()).isEqualTo(2);
assertThat(liveBean.getMedia()).extracting("name").contains("media2","media3");
}
@NotNull
private DocumentMedia createMedia(String name) {
DocumentMedia media = new DocumentMedia();
media.setName(name);
return media;
}
}
@@ -0,0 +1,58 @@
package com.avaje.tests.model.draftable;
import com.avaje.ebean.Model;
import com.avaje.ebean.annotation.WhenCreated;
import com.avaje.ebean.annotation.WhenModified;
import javax.persistence.Id;
import javax.persistence.MappedSuperclass;
import javax.persistence.Version;
import java.sql.Timestamp;
@MappedSuperclass
public class BaseDomain extends Model {
@Id
Long id;
@Version
Long version;
@WhenCreated
Timestamp whenCreated;
@WhenModified
Timestamp whenModified;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public Long getVersion() {
return version;
}
public void setVersion(Long version) {
this.version = version;
}
public Timestamp getWhenCreated() {
return whenCreated;
}
public void setWhenCreated(Timestamp whenCreated) {
this.whenCreated = whenCreated;
}
public Timestamp getWhenModified() {
return whenModified;
}
public void setWhenModified(Timestamp whenModified) {
this.whenModified = whenModified;
}
}
@@ -0,0 +1,39 @@
package com.avaje.tests.model.draftable;
import com.avaje.ebean.annotation.Draftable;
import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.ManyToMany;
import java.util.List;
@Draftable
@Entity
public class Doc extends BaseDomain {
String name;
@ManyToMany(cascade = CascadeType.ALL)
List<Link> links;
public Doc(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public List<Link> getLinks() {
return links;
}
public void setLinks(List<Link> links) {
this.links = links;
}
}
@@ -0,0 +1,75 @@
package com.avaje.tests.model.draftable;
import com.avaje.ebean.annotation.DraftOnly;
import com.avaje.ebean.annotation.Draftable;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
import java.sql.Timestamp;
import java.util.List;
@Draftable
@Entity
public class Document extends BaseDomain {
@Column(unique = true)
String title;
String body;
@DraftOnly
Timestamp whenPublish;
@ManyToOne
Organisation organisation;
/**
* Relationship to draftable elements.
*/
//@PrivateOwned
@OneToMany(mappedBy = "document")//, cascade = CascadeType.ALL)
List<DocumentMedia> media;
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getBody() {
return body;
}
public void setBody(String body) {
this.body = body;
}
public Organisation getOrganisation() {
return organisation;
}
public void setOrganisation(Organisation organisation) {
this.organisation = organisation;
}
public List<DocumentMedia> getMedia() {
return media;
}
public void setMedia(List<DocumentMedia> media) {
this.media = media;
}
public Timestamp getWhenPublish() {
return whenPublish;
}
public void setWhenPublish(Timestamp whenPublish) {
this.whenPublish = whenPublish;
}
}
@@ -0,0 +1,45 @@
package com.avaje.tests.model.draftable;
import com.avaje.ebean.annotation.DraftableElement;
import javax.persistence.Entity;
import javax.persistence.ManyToOne;
/**
* 'Owned' by @Draftable root.
*/
@DraftableElement
@Entity
public class DocumentMedia extends BaseDomain {
@ManyToOne
Document document;
String name;
String description;
public Document getDocument() {
return document;
}
public void setDocument(Document document) {
this.document = document;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
}
@@ -0,0 +1,126 @@
package com.avaje.tests.model.draftable;
import com.avaje.ebean.annotation.Draft;
import com.avaje.ebean.annotation.DraftDirty;
import com.avaje.ebean.annotation.DraftReset;
import com.avaje.ebean.annotation.Draftable;
import com.avaje.ebean.annotation.History;
import com.avaje.ebean.annotation.SoftDelete;
import javax.persistence.Entity;
import javax.persistence.ManyToMany;
import java.sql.Timestamp;
import java.util.List;
@History
@Draftable
@Entity
public class Link extends BaseDomain {
@SoftDelete
boolean deleted;
String name;
String location;
/**
* Draft reset to null on publish.
*/
@DraftReset
Timestamp whenPublish;
/**
* Draft reset to null on publish.
*/
@DraftReset
String comment;
/**
* Indicates if the instance is a 'draft' or 'live' bean.
*/
@Draft
boolean draft;
/**
* Indicates if the draft has modifications that have not been published
* to 'live'. This is automatically set when a draft is saved.
*/
@DraftDirty
boolean dirty;
@ManyToMany(mappedBy = "links")
List<Doc> docs;
public Link(String name) {
this.name = name;
}
public Link() {
}
public boolean isDraft() {
return draft;
}
public void setDraft(boolean draft) {
this.draft = draft;
}
public boolean isDeleted() {
return deleted;
}
public void setDeleted(boolean deleted) {
this.deleted = deleted;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getLocation() {
return location;
}
public void setLocation(String location) {
this.location = location;
}
public List<Doc> getDocs() {
return docs;
}
public void setDocs(List<Doc> docs) {
this.docs = docs;
}
public boolean isDirty() {
return dirty;
}
public void setDirty(boolean dirty) {
this.dirty = dirty;
}
public Timestamp getWhenPublish() {
return whenPublish;
}
public void setWhenPublish(Timestamp whenPublish) {
this.whenPublish = whenPublish;
}
public String getComment() {
return comment;
}
public void setComment(String comment) {
this.comment = comment;
}
}
@@ -0,0 +1,21 @@
package com.avaje.tests.model.draftable;
import javax.persistence.Entity;
@Entity
public class Organisation extends BaseDomain {
String name;
public Organisation(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
@@ -82,6 +82,7 @@ public class TestQueryFindIterate extends BaseTestCase {
.select("status, orderDate")
.fetch("customer", "name")
.where().gt("id",0).le("id",10)
.setUseCache(false)
.findIterate();
try {
@@ -15,7 +15,25 @@ public class TestWhereRawClause extends BaseTestCase {
ResetBasicData.reset();
Ebean.find(OrderDetail.class).where().not(Expr.eq("id", 1)).raw("orderQty < shipQty")
Ebean.find(OrderDetail.class)
.where()
.not(Expr.eq("id", 1))
.raw("orderQty < shipQty")
.findList();
}
@Test
public void testRawWithBindParams() {
ResetBasicData.reset();
Ebean.find(OrderDetail.class)
.where()
.ne("id", 42)
.raw("orderQty < ?", 100)
.gt("id", 1)
.raw("unitPrice > ? and product.id > ?", 2, 3)
.findList();
}
@@ -40,7 +40,9 @@ public class TestWhereAnnotation extends BaseTestCase {
LoggedSqlCollector.start();
List<Customer> customers = Ebean.find(Customer.class).findList();
List<Customer> customers = Ebean.find(Customer.class)
.setUseCache(false)
.findList();
List<Order> orders = customers.get(0).getOrders();
orders.size();
+5 -3
View File
@@ -41,7 +41,7 @@ ebean.cacheWarmingDelay=-1
##ebean.transaction.rollbackOnChecked=false
ebean.migration.appName=myapp
ebean.migration.resourcePath=dbmigration/myapp
ebean.migration.migrationPath=dbmigration/myapp
## -------------------------------------------------------------
@@ -117,6 +117,8 @@ datasource.pg.databaseDriver=org.postgresql.Driver
datasource.ms.username=test
datasource.ms.password=test
datasource.ms.databaseUrl=jdbc:sqlserver://192.168.1.70:1433;databaseName=test
datasource.ms.databaseUrl=jdbc:sqlserver://192.168.1.67:1433;databaseName=test
datasource.ms.databaseDriver=com.microsoft.sqlserver.jdbc.SQLServerDriver
#datasource.ms.databaseUrl=jdbc:jtds:sqlserver://192.168.1.67:1433/test
#datasource.ms.databaseDriver=net.sourceforge.jtds.jdbc.Driver
#datasource.ms.heartbeatsql=select 1