mirror of
https://github.com/ebean-orm/ebean.git
synced 2024-04-21 10:51:47 +00:00
Compare commits
61
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
87a7d4958e | ||
|
|
511ebd8a29 | ||
|
|
11f4c8d7a3 | ||
|
|
d417a92843 | ||
|
|
4bd5e52df3 | ||
|
|
788a200d16 | ||
|
|
e18d2004dc | ||
|
|
b326724139 | ||
|
|
fc0b47342b | ||
|
|
ec1a68a133 | ||
|
|
8ca12521d7 | ||
|
|
2362ef4130 | ||
|
|
7e233edcc3 | ||
|
|
a8b98282a1 | ||
|
|
b4506aa718 | ||
|
|
848a3b8888 | ||
|
|
67c711d75f | ||
|
|
3d62f58930 | ||
|
|
a30948d38d | ||
|
|
a8232d2d8b | ||
|
|
d4b8f23a5a | ||
|
|
bcf2a44187 | ||
|
|
f3102ba02c | ||
|
|
0351eab3a7 | ||
|
|
409e2b75ce | ||
|
|
dc968f16bf | ||
|
|
bbbb6482fb | ||
|
|
001968fcf8 | ||
|
|
6abcb186a4 | ||
|
|
f250447fbe | ||
|
|
bfa86f241b | ||
|
|
19459b71b4 | ||
|
|
5e97c3bbbc | ||
|
|
986eadd023 | ||
|
|
c6787d0901 | ||
|
|
3792e06859 | ||
|
|
c77446ba24 | ||
|
|
dba245c517 | ||
|
|
489eb483c0 | ||
|
|
11301846be | ||
|
|
d022e4aec8 | ||
|
|
aa788edb68 | ||
|
|
288adcdf68 | ||
|
|
ddb07ca202 | ||
|
|
524b592e82 | ||
|
|
0124f0f254 | ||
|
|
eddd6a36d6 | ||
|
|
91e309d770 | ||
|
|
5726410f6f | ||
|
|
054f7dc86f | ||
|
|
06b5ebc14e | ||
|
|
f6ee82428f | ||
|
|
e1d45dd79d | ||
|
|
08c455279f | ||
|
|
32dcbc6703 | ||
|
|
c9e733df4e | ||
|
|
606e1e6d3a | ||
|
|
fcb50ea13a | ||
|
|
5b26e77cfd | ||
|
|
9fc5d8a201 | ||
|
|
ba78c5e904 |
@@ -5,16 +5,7 @@
|
||||
|
||||
Ebean ORM
|
||||
==============
|
||||
[http://ebean-orm.github.io/](http://ebean-orm.github.io/ "Ebean ORM's Website")
|
||||
|
||||
|
||||
Maven Dependency
|
||||
----------------
|
||||
<dependency>
|
||||
<groupId>org.avaje.ebeanorm</groupId>
|
||||
<artifactId>avaje-ebeanorm</artifactId>
|
||||
<version>4.7.3</version>
|
||||
</dependency>
|
||||
[https://ebean-orm.github.io/](http://ebean-orm.github.io/ "Ebean ORM's Website")
|
||||
|
||||
|
||||
[Maven central - avaje-ebeanorm](http://search.maven.org/#search%7Cgav%7C1%7Cg%3A%22org.avaje.ebeanorm%22%20AND%20a%3A%22avaje-ebeanorm%22 "maven central ebeanorm")
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
|
||||
<groupId>org.avaje.ebeanorm</groupId>
|
||||
<artifactId>avaje-ebeanorm</artifactId>
|
||||
<version>6.7.1</version>
|
||||
<version>6.10.3</version>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>avaje-ebeanorm</name>
|
||||
@@ -61,6 +61,14 @@
|
||||
<version>1.7.7</version>
|
||||
</dependency>
|
||||
|
||||
<!-- provided scope for Spring boot loader classpath scanning support -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-loader</artifactId>
|
||||
<version>1.2.7.RELEASE</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- provided scope for JsonNode support -->
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
@@ -109,7 +117,7 @@
|
||||
<dependency>
|
||||
<groupId>org.postgresql</groupId>
|
||||
<artifactId>postgresql</artifactId>
|
||||
<version>9.4-1201-jdbc41</version>
|
||||
<version>9.4-1202-jdbc41</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
@@ -147,12 +155,14 @@
|
||||
<version>4.7.1</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
|
||||
<!-- Provided scope so that the H2HistoryTrigger can live in Ebean core
|
||||
and not require a separate module for it -->
|
||||
<dependency>
|
||||
<groupId>com.h2database</groupId>
|
||||
<artifactId>h2</artifactId>
|
||||
<version>1.4.182</version>
|
||||
<scope>test</scope>
|
||||
<version>1.4.189</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
@@ -218,7 +228,7 @@
|
||||
<configuration>
|
||||
<classSource>target/test-classes</classSource>
|
||||
<packages>com.avaje.tests.**</packages>
|
||||
<transformArgs>debug=2</transformArgs>
|
||||
<transformArgs>debug=1</transformArgs>
|
||||
</configuration>
|
||||
<goals>
|
||||
<goal>enhance</goal>
|
||||
@@ -263,11 +273,11 @@
|
||||
<artifactId>maven-javadoc-plugin</artifactId>
|
||||
<version>2.9.1</version>
|
||||
<configuration>
|
||||
<doctitle>Ebean 4</doctitle>
|
||||
<doctitle>Ebean 6</doctitle>
|
||||
<overview>src/main/java/com/avaje/ebean/overview.html</overview>
|
||||
<source>1.8</source>
|
||||
<doclet>org.avaje.doclet.PygmentsDoclet</doclet>
|
||||
<excludePackageNames>com.avaje.ebeaninternal.*:com.avaje.ebean.util</excludePackageNames>
|
||||
<excludePackageNames>com.avaje.ebeaninternal.*:com.avaje.ebean.util:com.avaje.ebean.dbmigration.ddlgeneration:com.avaje.ebean.dbmigration.migration:com.avaje.ebean.dbmigration.migrationreader:com.avaje.ebean.dbmigration.model</excludePackageNames>
|
||||
<docletArtifact>
|
||||
<groupId>org.avaje</groupId>
|
||||
<artifactId>pygments-doclet</artifactId>
|
||||
|
||||
@@ -38,6 +38,33 @@ public interface BeanState {
|
||||
*/
|
||||
void setDisableLazyLoad(boolean disableLazyLoading);
|
||||
|
||||
/**
|
||||
* Set the loaded state of the property given it's name.
|
||||
*
|
||||
* <p>
|
||||
* Typically this would be used to set the loaded state of a property
|
||||
* to false to ensure that the specific property is excluded from a
|
||||
* stateless update.
|
||||
* </p>
|
||||
*
|
||||
* <pre>{@code
|
||||
*
|
||||
* // populate a bean via say JSON
|
||||
* User user = ...;
|
||||
*
|
||||
* // set loaded state on the email property to false so that
|
||||
* // the email property is not included in a stateless update
|
||||
* Ebean.getBeanState(user).setPropertyLoaded("email", false);
|
||||
*
|
||||
* user.update();
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
*
|
||||
* This will throw an IllegalArgumentException if the property is unknown.
|
||||
*/
|
||||
void setPropertyLoaded(String propertyName, boolean loaded);
|
||||
|
||||
/**
|
||||
* For partially populated beans returns the properties that are loaded on the
|
||||
* bean.
|
||||
|
||||
@@ -36,6 +36,51 @@ import java.util.Map;
|
||||
*/
|
||||
public interface ExpressionFactory {
|
||||
|
||||
/**
|
||||
* Path exists - for the given path in a JSON document.
|
||||
*/
|
||||
Expression jsonExists(String propertyName, String path);
|
||||
|
||||
/**
|
||||
* Path does not exist - for the given path in a JSON document.
|
||||
*/
|
||||
Expression jsonNotExists(String propertyName, String path);
|
||||
|
||||
/**
|
||||
* Equal to - for the given path in a JSON document.
|
||||
*/
|
||||
Expression jsonEqualTo(String propertyName, String path, Object val);
|
||||
|
||||
/**
|
||||
* Not Equal to - for the given path in a JSON document.
|
||||
*/
|
||||
Expression jsonNotEqualTo(String propertyName, String path, Object val);
|
||||
|
||||
/**
|
||||
* Greater than - for the given path in a JSON document.
|
||||
*/
|
||||
Expression jsonGreaterThan(String propertyName, String path, Object val);
|
||||
|
||||
/**
|
||||
* Greater than or equal to - for the given path in a JSON document.
|
||||
*/
|
||||
Expression jsonGreaterOrEqual(String propertyName, String path, Object val);
|
||||
|
||||
/**
|
||||
* Less than - for the given path in a JSON document.
|
||||
*/
|
||||
Expression jsonLessThan(String propertyName, String path, Object val);
|
||||
|
||||
/**
|
||||
* Less than or equal to - for the given path in a JSON document.
|
||||
*/
|
||||
Expression jsonLessOrEqualTo(String propertyName, String path, Object val);
|
||||
|
||||
/**
|
||||
* Between - for the given path in a JSON document.
|
||||
*/
|
||||
Expression jsonBetween(String propertyName, String path, Object lowerValue, Object upperValue);
|
||||
|
||||
/**
|
||||
* Equal To - property equal to the given value.
|
||||
*/
|
||||
|
||||
@@ -345,12 +345,125 @@ public interface ExpressionList<T> extends Serializable {
|
||||
*/
|
||||
ExpressionList<T> where();
|
||||
|
||||
/**
|
||||
* Path exists - for the given path in a JSON document.
|
||||
*
|
||||
* <pre>{@code
|
||||
*
|
||||
* where().jsonExists("content", "path.other")
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* @param propertyName the property that holds a JSON document
|
||||
* @param path the nested path in the JSON document in dot notation
|
||||
*/
|
||||
ExpressionList<T> jsonExists(String propertyName, String path);
|
||||
|
||||
/**
|
||||
* Path does not exist - for the given path in a JSON document.
|
||||
*
|
||||
* <pre>{@code
|
||||
*
|
||||
* where().jsonNotExists("content", "path.other")
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* @param propertyName the property that holds a JSON document
|
||||
* @param path the nested path in the JSON document in dot notation
|
||||
*/
|
||||
ExpressionList<T> jsonNotExists(String propertyName, String path);
|
||||
|
||||
/**
|
||||
* Equal to expression for the value at the given path in the JSON document.
|
||||
*
|
||||
* <pre>{@code
|
||||
*
|
||||
* where().jsonEqualTo("content", "path.other", 34)
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* @param propertyName the property that holds a JSON document
|
||||
* @param path the nested path in the JSON document in dot notation
|
||||
* @param value the value used to test against the document path's value
|
||||
*/
|
||||
ExpressionList<T> jsonEqualTo(String propertyName, String path, Object value);
|
||||
|
||||
/**
|
||||
* Not Equal to - for the given path in a JSON document.
|
||||
*
|
||||
* <pre>{@code
|
||||
*
|
||||
* where().jsonNotEqualTo("content", "path.other", 34)
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* @param propertyName the property that holds a JSON document
|
||||
* @param path the nested path in the JSON document in dot notation
|
||||
* @param value the value used to test against the document path's value
|
||||
*/
|
||||
ExpressionList<T> jsonNotEqualTo(String propertyName, String path, Object value);
|
||||
|
||||
/**
|
||||
* Greater than - for the given path in a JSON document.
|
||||
*
|
||||
* <pre>{@code
|
||||
*
|
||||
* where().jsonGreaterThan("content", "path.other", 34)
|
||||
*
|
||||
* }</pre>
|
||||
*/
|
||||
ExpressionList<T> jsonGreaterThan(String propertyName, String path, Object value);
|
||||
|
||||
/**
|
||||
* Greater than or equal to - for the given path in a JSON document.
|
||||
*
|
||||
* <pre>{@code
|
||||
*
|
||||
* where().jsonGreaterOrEqual("content", "path.other", 34)
|
||||
*
|
||||
* }</pre>
|
||||
*/
|
||||
ExpressionList<T> jsonGreaterOrEqual(String propertyName, String path, Object value);
|
||||
|
||||
/**
|
||||
* Less than - for the given path in a JSON document.
|
||||
*
|
||||
* <pre>{@code
|
||||
*
|
||||
* where().jsonLessThan("content", "path.other", 34)
|
||||
*
|
||||
* }</pre>
|
||||
*/
|
||||
ExpressionList<T> jsonLessThan(String propertyName, String path, Object value);
|
||||
|
||||
/**
|
||||
* Less than or equal to - for the given path in a JSON document.
|
||||
*
|
||||
* <pre>{@code
|
||||
*
|
||||
* where().jsonLessOrEqualTo("content", "path.other", 34)
|
||||
*
|
||||
* }</pre>
|
||||
*/
|
||||
ExpressionList<T> jsonLessOrEqualTo(String propertyName, String path, Object value);
|
||||
|
||||
/**
|
||||
* Between - for the given path in a JSON document.
|
||||
*
|
||||
* <pre>{@code
|
||||
*
|
||||
* where().jsonBetween("content", "orderDate", lowerDateTime, upperDateTime)
|
||||
*
|
||||
* }</pre>
|
||||
*/
|
||||
ExpressionList<T> jsonBetween(String propertyName, String path, Object lowerValue, Object upperValue);
|
||||
|
||||
/**
|
||||
* Add an Expression to the list.
|
||||
* <p>
|
||||
* This returns the list so that add() can be chained.
|
||||
* </p>
|
||||
*
|
||||
*
|
||||
* <pre>{@code
|
||||
*
|
||||
* Query<Customer> query = Ebean.find(Customer.class);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.avaje.ebean;
|
||||
|
||||
import com.avaje.ebean.bean.EntityBean;
|
||||
import com.avaje.ebean.text.PathProperties;
|
||||
import com.avaje.ebean.util.ClassUtil;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
@@ -193,7 +194,31 @@ public abstract class Model {
|
||||
public void markAsDirty() {
|
||||
db().markAsDirty(this);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Mark the property as unset or 'not loaded'.
|
||||
* <p>
|
||||
* This would be used to specify a property that we did not wish to include in a stateless update.
|
||||
* </p>
|
||||
* <pre>{@code
|
||||
*
|
||||
* // populate an entity bean from JSON or whatever
|
||||
* User user = ...;
|
||||
*
|
||||
* // mark the email property as 'unset' so that it is not
|
||||
* // included in a 'stateless update'
|
||||
* user.markPropertyUnset("email");
|
||||
*
|
||||
* user.update();
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* @param propertyName the name of the property on the bean to be marked as 'unset'
|
||||
*/
|
||||
public void markPropertyUnset(String propertyName) {
|
||||
((EntityBean)this)._ebean_getIntercept().setPropertyLoaded(propertyName, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert or update this entity depending on its state.
|
||||
*
|
||||
|
||||
@@ -198,6 +198,18 @@ public final class OrderBy<T> implements Serializable {
|
||||
return list.hashCode();
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the orderBy removing any current order by properties.
|
||||
* <p>
|
||||
* This is intended to be used when some code creates a query with a
|
||||
* 'default' order by clause and some other code may clear the 'default'
|
||||
* order by clause and replace.
|
||||
* </p>
|
||||
*/
|
||||
public void clear() {
|
||||
list.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* A property and its ascending descending order.
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.avaje.ebean.annotation;
|
||||
|
||||
/**
|
||||
* Specify the DB storage type used to with <code>@DbEnumValue</code>.
|
||||
*/
|
||||
public enum DbEnumType {
|
||||
|
||||
/**
|
||||
* Store values as database INTEGER.
|
||||
*/
|
||||
INTEGER,
|
||||
|
||||
/**
|
||||
* Store values as database VARCHAR.
|
||||
*/
|
||||
VARCHAR
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package com.avaje.ebean.annotation;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Specify a method on an Enum that returns the value that should be stored in the DB.
|
||||
* <p>
|
||||
* This is the preferred option for mapping Enum's to DB values (preferred over the JPA
|
||||
* standard @Enumerated and Ebean's @EnumValue annotations).
|
||||
* </p>
|
||||
* <h3>Example:</h3>
|
||||
* <pre>{@code
|
||||
*
|
||||
* public enum Status {
|
||||
* NEW("N"),
|
||||
* ACTIVE("A"),
|
||||
* INACTIVE("I");
|
||||
*
|
||||
* String dbValue;
|
||||
* Status(String dbValue) {
|
||||
* this.dbValue = dbValue;
|
||||
* }
|
||||
*
|
||||
* @DbEnumValue
|
||||
* public String getValue() {
|
||||
* return dbValue;
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* }</pre>
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.METHOD)
|
||||
public @interface DbEnumValue {
|
||||
|
||||
/**
|
||||
* Specify the database type used to store the values (VARCHAR or INTEGER).
|
||||
*/
|
||||
DbEnumType storage() default DbEnumType.VARCHAR;
|
||||
|
||||
}
|
||||
@@ -25,7 +25,7 @@ import java.lang.annotation.Target;
|
||||
* }</pre>
|
||||
* <p>
|
||||
* This is an alternative to using the JPA standard approach or Ebean's
|
||||
* {@link EnumMapping} annotation.
|
||||
* {@link DbEnumValue} annotation.
|
||||
* </p>
|
||||
* <p>
|
||||
* Note that if all the EnumValue values are parsable as Integers then Ebean
|
||||
|
||||
@@ -449,7 +449,18 @@ public final class EntityBeanIntercept implements Serializable {
|
||||
public int getPropertyLength() {
|
||||
return owner._ebean_getPropertyNames().length;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set the loaded state of the property given it's name.
|
||||
*/
|
||||
public void setPropertyLoaded(String propertyName, boolean loaded) {
|
||||
int position = findProperty(propertyName);
|
||||
if (position == -1) {
|
||||
throw new IllegalArgumentException("Property "+propertyName+" not found");
|
||||
}
|
||||
loadedProps[position] = loaded;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the property to be treated as unloaded. Used for properties initialised in default
|
||||
* constructor.
|
||||
|
||||
@@ -45,13 +45,29 @@ public class DbConstraintNaming {
|
||||
protected String ckPrefix = "ck_";
|
||||
protected String ckSuffix = "";
|
||||
|
||||
protected boolean lowerCaseNames = true;
|
||||
|
||||
protected MaxLength maxLength;
|
||||
|
||||
protected DbConstraintNormalise normalise = new DbConstraintNormalise();
|
||||
protected DbConstraintNormalise normalise;
|
||||
|
||||
/**
|
||||
* Construct using default of lower case for both table and column names.
|
||||
*/
|
||||
public DbConstraintNaming() {
|
||||
this(true, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct specifying if lower case should be used (for both table and column names).
|
||||
*/
|
||||
public DbConstraintNaming(boolean lowerCase) {
|
||||
this(lowerCase, lowerCase);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct specifying if lower case should be used for both table and column names.
|
||||
*/
|
||||
public DbConstraintNaming(boolean lowerCaseTableNames, boolean lowerCaseColumnNames) {
|
||||
this.normalise = new DbConstraintNormalise(lowerCaseTableNames, lowerCaseColumnNames);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -167,7 +183,6 @@ public class DbConstraintNaming {
|
||||
* quoted identifier characters (",',[,] etc).
|
||||
*/
|
||||
public String normaliseTable(String tableName) {
|
||||
|
||||
return normalise.normaliseTable(tableName);
|
||||
}
|
||||
|
||||
@@ -175,18 +190,20 @@ public class DbConstraintNaming {
|
||||
* Normalise the column name by removing any quoted identifier characters (",',[,] etc).
|
||||
*/
|
||||
public String normaliseColumn(String tableName) {
|
||||
|
||||
return normalise.normaliseColumn(tableName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Lower case the table or column name checking for quoted identifiers.
|
||||
* Lower case the table name checking for quoted identifiers.
|
||||
*/
|
||||
public String lowerName(String tableName) {
|
||||
if (lowerCaseNames && normalise.notQuoted(tableName)) {
|
||||
return tableName.toLowerCase();
|
||||
}
|
||||
return tableName;
|
||||
public String lowerTableName(String tableName) {
|
||||
return normalise.lowerTableName(tableName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Lower case the column name checking for quoted identifiers.
|
||||
*/
|
||||
public String lowerColumnName(String name) {
|
||||
return normalise.lowerColumnName(name);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,11 +8,17 @@ public class DbConstraintNormalise {
|
||||
|
||||
protected final String[] quotedIdentifiers;
|
||||
|
||||
protected boolean lowerCaseTables = true;
|
||||
protected final boolean lowerCaseTables;
|
||||
|
||||
protected boolean lowerCaseColumns = true;
|
||||
protected final boolean lowerCaseColumns;
|
||||
|
||||
public DbConstraintNormalise() {
|
||||
this(true, true);
|
||||
}
|
||||
|
||||
public DbConstraintNormalise(boolean lowerCaseTables, boolean lowerCaseColumns) {
|
||||
this.lowerCaseTables = lowerCaseTables;
|
||||
this.lowerCaseColumns = lowerCaseColumns;
|
||||
this.quotedIdentifiers = new String[]{"\"", "'", "[", "]", "`"};
|
||||
}
|
||||
|
||||
@@ -46,6 +52,26 @@ public class DbConstraintNormalise {
|
||||
return columnName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Lower case the table name checking for quoted identifiers.
|
||||
*/
|
||||
public String lowerTableName(String tableName) {
|
||||
if (lowerCaseTables && notQuoted(tableName)) {
|
||||
return tableName.toLowerCase();
|
||||
}
|
||||
return tableName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Lower case the column name checking for quoted identifiers.
|
||||
*/
|
||||
public String lowerColumnName(String name) {
|
||||
if (lowerCaseColumns && notQuoted(name)) {
|
||||
return name.toLowerCase();
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Trim off the platform quoted identifier quotes like [ ' and ".
|
||||
*/
|
||||
|
||||
@@ -18,6 +18,14 @@ public final class PropertyMap implements Serializable {
|
||||
|
||||
private final LinkedHashMap<String, String> map = new LinkedHashMap<String, String>();
|
||||
|
||||
/**
|
||||
* Return properties loaded from test-ebean.properties.
|
||||
*/
|
||||
public static Properties testProperties() {
|
||||
PropertyMap propertyMap = PropertyMapLoader.loadTestProperties();
|
||||
return (propertyMap == null) ? new Properties() : propertyMap.asProperties();
|
||||
}
|
||||
|
||||
public static Properties defaultProperties() {
|
||||
PropertyMap propertyMap = PropertyMapLoader.loadGlobalProperties();
|
||||
return (propertyMap == null) ? new Properties() : propertyMap.asProperties();
|
||||
|
||||
@@ -14,17 +14,34 @@ final class PropertyMapLoader {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(PropertyMapLoader.class);
|
||||
|
||||
/**
|
||||
* Load the <code>test-ebean.properties</code>.
|
||||
*/
|
||||
public static PropertyMap loadTestProperties() {
|
||||
return load(null, "test-ebean.properties", false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the ebean.properties (and test-ebean.properties if present).
|
||||
*/
|
||||
public static PropertyMap loadGlobalProperties() {
|
||||
|
||||
boolean loadTestProperties = false;
|
||||
String fileName = System.getenv("EBEAN_PROPS_FILE");
|
||||
if (fileName == null) {
|
||||
fileName = System.getProperty("ebean.props.file");
|
||||
if (fileName == null) {
|
||||
loadTestProperties = true;
|
||||
fileName = "ebean.properties";
|
||||
}
|
||||
}
|
||||
|
||||
return load(null, fileName);
|
||||
PropertyMap map = load(null, fileName, true);
|
||||
if (loadTestProperties) {
|
||||
// load test properties if present in classpath
|
||||
load(map, "test-ebean.properties", false);
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -35,11 +52,13 @@ final class PropertyMapLoader {
|
||||
* @param fileName
|
||||
* the name of the properties file to load.
|
||||
*/
|
||||
public static PropertyMap load(PropertyMap p, String fileName) {
|
||||
public static PropertyMap load(PropertyMap p, String fileName, boolean errorOnNull) {
|
||||
|
||||
InputStream is = findInputStream(fileName);
|
||||
if (is == null) {
|
||||
logger.error(fileName + " not found");
|
||||
if (errorOnNull) {
|
||||
logger.error(fileName + " not found");
|
||||
}
|
||||
return p;
|
||||
} else {
|
||||
return load(p, is);
|
||||
|
||||
@@ -234,7 +234,12 @@ public class ServerConfig {
|
||||
* Indicates that Ebean should use autoCommit friendly Transactions and TransactionManager.
|
||||
*/
|
||||
private boolean autoCommitMode;
|
||||
|
||||
|
||||
/**
|
||||
* Set to true if transaction begin should be started with explicit statement.
|
||||
*/
|
||||
private boolean explicitTransactionBeginMode;
|
||||
|
||||
/**
|
||||
* The data source JNDI name if using a JNDI DataSource.
|
||||
*/
|
||||
@@ -276,9 +281,9 @@ public class ServerConfig {
|
||||
private boolean updatesDeleteMissingChildren = true;
|
||||
|
||||
/**
|
||||
* Setting to indicate if UUID should be stored as binary(16) or varchar(40).
|
||||
* Setting to indicate if UUID should be stored as binary(16) or varchar(40) or native DB type (for H2 and Postgres).
|
||||
*/
|
||||
private boolean uuidStoreAsBinary;
|
||||
private DbUuid dbUuid = DbUuid.AUTO;
|
||||
|
||||
|
||||
private List<BeanFindController> findControllers = new ArrayList<BeanFindController>();
|
||||
@@ -1220,6 +1225,28 @@ public class ServerConfig {
|
||||
this.autoCommitMode = autoCommitMode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if transaction begin should be started with explicit statement.
|
||||
*/
|
||||
public boolean isExplicitTransactionBeginMode() {
|
||||
return explicitTransactionBeginMode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set to true if transaction begin should be started with explicit statement.
|
||||
* <p>
|
||||
* This works for H2 and Postgres but not for Oracle - only use this if you first name
|
||||
* is Daryl or you have explicitly talked to Rob about this feature.
|
||||
* </p>
|
||||
* <p>
|
||||
* This is generally not expected to be turned on but instead allow transactions to start
|
||||
* implicitly which is generally the standard approach.
|
||||
* </p>
|
||||
*/
|
||||
public void setExplicitTransactionBeginMode(boolean explicitTransactionBeginMode) {
|
||||
this.explicitTransactionBeginMode = explicitTransactionBeginMode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a value used to represent TRUE in the database.
|
||||
* <p>
|
||||
@@ -1439,19 +1466,18 @@ public class ServerConfig {
|
||||
this.dbEncrypt = dbEncrypt;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return true if UUID should be stored as binary(16) (as opposed to varchar(40)).
|
||||
* Return the DB type used to store UUID.
|
||||
*/
|
||||
public boolean isUuidStoreAsBinary() {
|
||||
return uuidStoreAsBinary;
|
||||
public DbUuid getDbUuid() {
|
||||
return dbUuid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set to true if UUID should be stored as binary(16) (as opposed to varchar(40)).
|
||||
* Set the DB type used to store UUID.
|
||||
*/
|
||||
public void setUuidStoreAsBinary(boolean uuidStoreAsBinary) {
|
||||
this.uuidStoreAsBinary = uuidStoreAsBinary;
|
||||
public void setDbUuid(DbUuid dbUuid) {
|
||||
this.dbUuid = dbUuid;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2014,6 +2040,21 @@ public class ServerConfig {
|
||||
loadSettings(p);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load settings from test-ebean.properties and do nothing if the properties is not found.
|
||||
* <p>
|
||||
* This is typically used when test-ebean.properties is put into the test class path and used
|
||||
* to configure Ebean for running tests.
|
||||
* </p>
|
||||
*/
|
||||
public void loadTestProperties() {
|
||||
Properties properties = PropertyMap.testProperties();
|
||||
if (!properties.isEmpty()) {
|
||||
PropertiesWrapper p = new PropertiesWrapper("ebean", name, properties);
|
||||
loadSettings(p);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the properties that we used for configuration and were set via a call to loadFromProperties().
|
||||
*/
|
||||
@@ -2022,8 +2063,11 @@ public class ServerConfig {
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private <T> T createInstance(PropertiesWrapper p, Class<T> pluginType, String key) {
|
||||
private <T> T createInstance(PropertiesWrapper p, Class<T> pluginType, String key, T instance) {
|
||||
|
||||
if (instance != null) {
|
||||
return instance;
|
||||
}
|
||||
String classname = p.get(key, null);
|
||||
return classname == null ? null : (T) ClassUtil.newInstance(classname);
|
||||
}
|
||||
@@ -2067,18 +2111,19 @@ public class ServerConfig {
|
||||
}
|
||||
loadDataSourceSettings(p);
|
||||
|
||||
explicitTransactionBeginMode = p.getBoolean("explicitTransactionBeginMode", explicitTransactionBeginMode);
|
||||
autoCommitMode = p.getBoolean("autoCommitMode", autoCommitMode);
|
||||
useJtaTransactionManager = p.getBoolean("useJtaTransactionManager", useJtaTransactionManager);
|
||||
|
||||
currentUserProvider = createInstance(p, CurrentUserProvider.class, "currentUserProvider");
|
||||
disableClasspathSearch = p.getBoolean("disableClasspathSearch", disableClasspathSearch);
|
||||
databasePlatform = createInstance(p, DatabasePlatform.class, "databasePlatform");
|
||||
encryptKeyManager = createInstance(p, EncryptKeyManager.class, "encryptKeyManager");
|
||||
encryptDeployManager = createInstance(p, EncryptDeployManager.class, "encryptDeployManager");
|
||||
encryptor = createInstance(p, Encryptor.class, "encryptor");
|
||||
dbEncrypt = createInstance(p, DbEncrypt.class, "dbEncrypt");
|
||||
serverCacheFactory = createInstance(p, ServerCacheFactory.class, "serverCacheFactory");
|
||||
serverCacheManager = createInstance(p, ServerCacheManager.class, "serverCacheManager");
|
||||
currentUserProvider = createInstance(p, CurrentUserProvider.class, "currentUserProvider", currentUserProvider);
|
||||
databasePlatform = createInstance(p, DatabasePlatform.class, "databasePlatform", databasePlatform);
|
||||
encryptKeyManager = createInstance(p, EncryptKeyManager.class, "encryptKeyManager", encryptKeyManager);
|
||||
encryptDeployManager = createInstance(p, EncryptDeployManager.class, "encryptDeployManager", encryptDeployManager);
|
||||
encryptor = createInstance(p, Encryptor.class, "encryptor", encryptor);
|
||||
dbEncrypt = createInstance(p, DbEncrypt.class, "dbEncrypt", dbEncrypt);
|
||||
serverCacheFactory = createInstance(p, ServerCacheFactory.class, "serverCacheFactory", serverCacheFactory);
|
||||
serverCacheManager = createInstance(p, ServerCacheManager.class, "serverCacheManager", serverCacheManager);
|
||||
cacheWarmingDelay = p.getInt("cacheWarmingDelay", cacheWarmingDelay);
|
||||
classPathReaderClassName = p.get("classpathreader");
|
||||
|
||||
@@ -2124,7 +2169,10 @@ public class ServerConfig {
|
||||
databaseBooleanTrue = p.get("databaseBooleanTrue", databaseBooleanTrue);
|
||||
databaseBooleanFalse = p.get("databaseBooleanFalse", databaseBooleanFalse);
|
||||
databasePlatformName = p.get("databasePlatformName", databasePlatformName);
|
||||
uuidStoreAsBinary = p.getBoolean("uuidStoreAsBinary", uuidStoreAsBinary);
|
||||
dbUuid = p.getEnum(DbUuid.class, "dbuuid", dbUuid);
|
||||
if (p.getBoolean("uuidStoreAsBinary", false)) {
|
||||
dbUuid = DbUuid.BINARY;
|
||||
}
|
||||
localTimeWithNanos = p.getBoolean("localTimeWithNanos", localTimeWithNanos);
|
||||
|
||||
lazyLoadBatchSize = p.getInt("lazyLoadBatchSize", lazyLoadBatchSize);
|
||||
@@ -2146,7 +2194,7 @@ public class ServerConfig {
|
||||
|
||||
private NamingConvention createNamingConvention(PropertiesWrapper properties, NamingConvention namingConvention) {
|
||||
|
||||
NamingConvention nc = createInstance(properties, NamingConvention.class, "namingconvention");
|
||||
NamingConvention nc = createInstance(properties, NamingConvention.class, "namingconvention", null);
|
||||
return (nc != null) ? nc : namingConvention;
|
||||
}
|
||||
|
||||
@@ -2246,4 +2294,25 @@ public class ServerConfig {
|
||||
public void setExpressionEqualsWithNullAsNoop(boolean expressionEqualsWithNullAsNoop) {
|
||||
this.expressionEqualsWithNullAsNoop = expressionEqualsWithNullAsNoop;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify how UUID is stored.
|
||||
*/
|
||||
public enum DbUuid {
|
||||
|
||||
/**
|
||||
* Store using native UUID in H2 and Postgres.
|
||||
*/
|
||||
AUTO,
|
||||
|
||||
/**
|
||||
* Store using DB VARCHAR.
|
||||
*/
|
||||
VARCHAR,
|
||||
|
||||
/**
|
||||
* Store using DB BINARY.
|
||||
*/
|
||||
BINARY
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,18 +1,16 @@
|
||||
package com.avaje.ebean.config.dbplatform;
|
||||
|
||||
import java.sql.Types;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
import com.avaje.ebean.BackgroundExecutor;
|
||||
import com.avaje.ebean.Query;
|
||||
|
||||
import com.avaje.ebean.config.ServerConfig;
|
||||
import com.avaje.ebean.dbmigration.ddlgeneration.DdlHandler;
|
||||
import com.avaje.ebean.dbmigration.ddlgeneration.platform.PlatformDdl;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
import java.sql.Types;
|
||||
|
||||
/**
|
||||
* Database platform specific settings.
|
||||
*/
|
||||
@@ -54,28 +52,33 @@ public class DatabasePlatform {
|
||||
*/
|
||||
protected OnQueryOnly onQueryOnly = OnQueryOnly.ROLLBACK;
|
||||
|
||||
/**
|
||||
* The open quote used by quoted identifiers.
|
||||
/**
|
||||
* The open quote used by quoted identifiers.
|
||||
*/
|
||||
protected String openQuote = "\"";
|
||||
|
||||
/**
|
||||
* The close quote used by quoted identifiers.
|
||||
* The close quote used by quoted identifiers.
|
||||
*/
|
||||
protected String closeQuote = "\"";
|
||||
|
||||
/**
|
||||
* For limit/offset, row_number etc limiting of SQL queries.
|
||||
/**
|
||||
* For limit/offset, row_number etc limiting of SQL queries.
|
||||
*/
|
||||
protected SqlLimiter sqlLimiter = new LimitOffsetSqlLimiter();
|
||||
|
||||
/**
|
||||
* Mapping of JDBC to Database types.
|
||||
/**
|
||||
* Mapping of JDBC to Database types.
|
||||
*/
|
||||
protected DbTypeMap dbTypeMap = new DbTypeMap();
|
||||
|
||||
/**
|
||||
* Defines DB identity/sequence features.
|
||||
* Set to true if the DB has native UUID type support.
|
||||
*/
|
||||
protected boolean nativeUuidType;
|
||||
|
||||
/**
|
||||
* Defines DB identity/sequence features.
|
||||
*/
|
||||
protected DbIdentity dbIdentity = new DbIdentity();
|
||||
|
||||
@@ -84,28 +87,28 @@ public class DatabasePlatform {
|
||||
*/
|
||||
protected DbHistorySupport historySupport;
|
||||
|
||||
/**
|
||||
* The JDBC type to map booleans to (by default).
|
||||
/**
|
||||
* The JDBC type to map booleans to (by default).
|
||||
*/
|
||||
protected int booleanDbType = Types.BOOLEAN;
|
||||
|
||||
/**
|
||||
* The JDBC type to map Blob to.
|
||||
/**
|
||||
* The JDBC type to map Blob to.
|
||||
*/
|
||||
protected int blobDbType = Types.BLOB;
|
||||
|
||||
/**
|
||||
* The JDBC type to map Clob to.
|
||||
/**
|
||||
* The JDBC type to map Clob to.
|
||||
*/
|
||||
protected int clobDbType = Types.CLOB;
|
||||
|
||||
/**
|
||||
* For Oracle treat empty strings as null.
|
||||
/**
|
||||
* For Oracle treat empty strings as null.
|
||||
*/
|
||||
protected boolean treatEmptyStringsAsNull;
|
||||
|
||||
/**
|
||||
* The database platform name.
|
||||
/**
|
||||
* The database platform name.
|
||||
*/
|
||||
protected String name = "generic";
|
||||
|
||||
@@ -124,7 +127,7 @@ public class DatabasePlatform {
|
||||
* The like clause. Can be overridden to disable default escape character.
|
||||
*/
|
||||
protected String likeClause = "like ?";
|
||||
|
||||
|
||||
protected DbEncrypt dbEncrypt;
|
||||
|
||||
protected boolean idInExpandedForm;
|
||||
@@ -219,16 +222,12 @@ public class DatabasePlatform {
|
||||
|
||||
/**
|
||||
* Return a DB Sequence based IdGenerator.
|
||||
*
|
||||
* @param be
|
||||
* the BackgroundExecutor that can be used to load the sequence if
|
||||
* desired
|
||||
* @param ds
|
||||
* the DataSource
|
||||
* @param seqName
|
||||
* the name of the sequence
|
||||
* @param batchSize
|
||||
* the number of sequences that should be loaded
|
||||
*
|
||||
* @param be the BackgroundExecutor that can be used to load the sequence if
|
||||
* desired
|
||||
* @param ds the DataSource
|
||||
* @param seqName the name of the sequence
|
||||
* @param batchSize the number of sequences that should be loaded
|
||||
*/
|
||||
public IdGenerator createSequenceIdGenerator(BackgroundExecutor be, DataSource ds, String seqName, int batchSize) {
|
||||
return null;
|
||||
@@ -276,9 +275,16 @@ public class DatabasePlatform {
|
||||
this.historySupport = historySupport;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the DB supports native UUID.
|
||||
*/
|
||||
public boolean isNativeUuidType() {
|
||||
return nativeUuidType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the mapping of JDBC to DB types.
|
||||
*
|
||||
*
|
||||
* @return the db type map
|
||||
*/
|
||||
public DbTypeMap getDbTypeMap() {
|
||||
@@ -315,7 +321,7 @@ public class DatabasePlatform {
|
||||
|
||||
/**
|
||||
* Return the close quote for quoted identifiers.
|
||||
*
|
||||
*
|
||||
* @return the close quote
|
||||
*/
|
||||
public String getCloseQuote() {
|
||||
@@ -324,7 +330,7 @@ public class DatabasePlatform {
|
||||
|
||||
/**
|
||||
* Return the open quote for quoted identifiers.
|
||||
*
|
||||
*
|
||||
* @return the open quote
|
||||
*/
|
||||
public String getOpenQuote() {
|
||||
@@ -333,7 +339,7 @@ public class DatabasePlatform {
|
||||
|
||||
/**
|
||||
* Return the JDBC type used to store booleans.
|
||||
*
|
||||
*
|
||||
* @return the boolean db type
|
||||
*/
|
||||
public int getBooleanDbType() {
|
||||
@@ -363,7 +369,7 @@ public class DatabasePlatform {
|
||||
|
||||
/**
|
||||
* Return true if empty strings should be treated as null.
|
||||
*
|
||||
*
|
||||
* @return true, if checks if is treat empty strings as null
|
||||
*/
|
||||
public boolean isTreatEmptyStringsAsNull() {
|
||||
@@ -399,7 +405,7 @@ public class DatabasePlatform {
|
||||
|
||||
/**
|
||||
* Return the DB identity/sequence features for this platform.
|
||||
*
|
||||
*
|
||||
* @return the db identity
|
||||
*/
|
||||
public DbIdentity getDbIdentity() {
|
||||
@@ -412,7 +418,7 @@ public class DatabasePlatform {
|
||||
* <p>
|
||||
* Basically add the clauses for limit/offset, rownum, row_number().
|
||||
* </p>
|
||||
*
|
||||
*
|
||||
* @return the sql limiter
|
||||
*/
|
||||
public SqlLimiter getSqlLimiter() {
|
||||
@@ -421,15 +427,12 @@ public class DatabasePlatform {
|
||||
|
||||
/**
|
||||
* Convert backticks to the platform specific open quote and close quote
|
||||
*
|
||||
* <p>
|
||||
* Specific plugins may implement this method to cater for platform specific
|
||||
* naming rules.
|
||||
* </p>
|
||||
*
|
||||
* @param dbName
|
||||
* the db name
|
||||
*
|
||||
*
|
||||
* @param dbName the db name
|
||||
* @return the string
|
||||
*/
|
||||
public String convertQuotedIdentifiers(String dbName) {
|
||||
|
||||
@@ -8,6 +8,11 @@ package com.avaje.ebean.config.dbplatform;
|
||||
*/
|
||||
public class DbType {
|
||||
|
||||
/**
|
||||
* DB native UUID type (H2 and Postgres).
|
||||
*/
|
||||
public static final int UUID = 5010;
|
||||
|
||||
/**
|
||||
* Type to map Map content to Postgres HSTORE.
|
||||
*/
|
||||
|
||||
@@ -49,6 +49,8 @@ public class DbTypeMap {
|
||||
lookup.put("TIME", Types.TIME);
|
||||
lookup.put("TIMESTAMP", Types.TIMESTAMP);
|
||||
|
||||
lookup.put("UUID", DbType.UUID);
|
||||
|
||||
// Not standard java.sql.Types
|
||||
// logical JSON storage types
|
||||
lookup.put("JSON", DbType.JSON);
|
||||
@@ -101,6 +103,9 @@ public class DbTypeMap {
|
||||
put(Types.BLOB, new DbType("blob"));
|
||||
put(Types.CLOB, new DbType("clob"));
|
||||
|
||||
// DB native UUID support (H2 and Postgres)
|
||||
put(DbType.UUID, new DbType("uuid"));
|
||||
|
||||
if (logicalTypes) {
|
||||
// keep it logical for 2 layer DDL generation
|
||||
put(DbType.HSTORE, new DbType("hstore"));
|
||||
|
||||
@@ -55,7 +55,7 @@ public abstract class DbViewHistorySupport implements DbHistorySupport {
|
||||
public String getAsOfPredicate(String asOfTableAlias, String asOfSysPeriod) {
|
||||
|
||||
// (sys_period_start < ? and (sys_period_end is null or sys_period_end > ?));
|
||||
return "(" + asOfTableAlias + "." + asOfSysPeriod + "_start" + " < ? and (" + asOfTableAlias + "." + asOfSysPeriod + "_end" + " is null or " + asOfTableAlias + "." + asOfSysPeriod + "_end" + " > ?))";
|
||||
return "(" + asOfTableAlias + "." + asOfSysPeriod + "_start" + " <= ? and (" + asOfTableAlias + "." + asOfSysPeriod + "_end" + " is null or " + asOfTableAlias + "." + asOfSysPeriod + "_end" + " > ?))";
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
package com.avaje.ebean.config.dbplatform;
|
||||
|
||||
/**
|
||||
* Runtime support for @History with H2.
|
||||
*/
|
||||
public class H2HistorySupport extends DbViewHistorySupport {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
package com.avaje.ebean.config.dbplatform;
|
||||
|
||||
import org.h2.api.Trigger;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Timestamp;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* H2 database trigger used to populate history tables to support the @History feature.
|
||||
*/
|
||||
public class H2HistoryTrigger implements Trigger {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(H2HistoryTrigger.class);
|
||||
|
||||
/**
|
||||
* Hardcoding the column and history table suffix for now. Not sure how to get that
|
||||
* configuration into the trigger instance nicely as it is instantiated by H2.
|
||||
*/
|
||||
private static final String SYS_PERIOD_START = "SYS_PERIOD_START";
|
||||
private static final String SYS_PERIOD_END = "SYS_PERIOD_END";
|
||||
private static final String HISTORY_SUFFIX = "_history";
|
||||
|
||||
/**
|
||||
* SQL to insert into the history table.
|
||||
*/
|
||||
private String insertHistorySql;
|
||||
|
||||
/**
|
||||
* Position of SYS_PERIOD_START column in the Object[].
|
||||
*/
|
||||
private int effectStartPosition;
|
||||
|
||||
/**
|
||||
* Position of SYS_PERIOD_END column in the Object[].
|
||||
*/
|
||||
private int effectEndPosition;
|
||||
|
||||
@Override
|
||||
public void init(Connection conn, String schemaName, String triggerName, String tableName, boolean before, int type) throws SQLException {
|
||||
|
||||
// get the columns for the table
|
||||
ResultSet rs = conn.getMetaData().getColumns(null, schemaName, tableName, null);
|
||||
|
||||
// build the insert into history table SQL
|
||||
StringBuilder insertSql = new StringBuilder(150);
|
||||
insertSql.append("insert into ").append(tableName).append(HISTORY_SUFFIX).append(" (");
|
||||
|
||||
int count = 0;
|
||||
List<String> columns = new ArrayList<String>();
|
||||
while (rs.next()) {
|
||||
if (++count > 1) {
|
||||
insertSql.append(",");
|
||||
}
|
||||
String columnName = rs.getString("COLUMN_NAME");
|
||||
if (columnName.equalsIgnoreCase(SYS_PERIOD_START)) {
|
||||
this.effectStartPosition = count - 1;
|
||||
} else if (columnName.equalsIgnoreCase(SYS_PERIOD_END)) {
|
||||
this.effectEndPosition = count - 1;
|
||||
}
|
||||
insertSql.append(columnName);
|
||||
columns.add(columnName);
|
||||
}
|
||||
insertSql.append(") values (");
|
||||
for (int i = 0; i < count; i++) {
|
||||
if (i > 0) {
|
||||
insertSql.append(",");
|
||||
}
|
||||
insertSql.append("?");
|
||||
}
|
||||
insertSql.append(");");
|
||||
|
||||
this.insertHistorySql = insertSql.toString();
|
||||
logger.debug("History table insert sql: {}", insertHistorySql);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fire(Connection connection, Object[] oldRow, Object[] newRow) throws SQLException {
|
||||
|
||||
if (oldRow != null) {
|
||||
// a delete or update event
|
||||
Timestamp now = new Timestamp(System.currentTimeMillis());
|
||||
oldRow[effectEndPosition] = now;
|
||||
if (newRow != null) {
|
||||
// update event. Set the effective start timestamp to now.
|
||||
newRow[effectStartPosition] = now;
|
||||
}
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("History insert: {}", Arrays.toString(oldRow));
|
||||
}
|
||||
insertIntoHistory(connection, oldRow);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert the data into the history table.
|
||||
*/
|
||||
private void insertIntoHistory(Connection connection, Object[] oldRow) throws SQLException {
|
||||
|
||||
PreparedStatement stmt = connection.prepareStatement(insertHistorySql);
|
||||
try {
|
||||
for (int i = 0; i < oldRow.length; i++) {
|
||||
stmt.setObject(i + 1, oldRow[i]);
|
||||
}
|
||||
stmt.executeUpdate();
|
||||
} finally {
|
||||
stmt.close();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws SQLException {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove() throws SQLException {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -15,6 +15,8 @@ public class H2Platform extends DatabasePlatform {
|
||||
this.name = "h2";
|
||||
this.dbEncrypt = new H2DbEncrypt();
|
||||
this.platformDdl = new H2Ddl(this.dbTypeMap, dbIdentity);
|
||||
this.historySupport = new H2HistorySupport();
|
||||
this.nativeUuidType = true;
|
||||
|
||||
// only support getGeneratedKeys with non-batch JDBC
|
||||
// so generally use SEQUENCE instead of IDENTITY for H2
|
||||
|
||||
@@ -5,5 +5,4 @@ package com.avaje.ebean.config.dbplatform;
|
||||
*/
|
||||
public class MySqlHistorySupport extends DbViewHistorySupport {
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -23,10 +23,10 @@ public class PostgresPlatform extends DatabasePlatform {
|
||||
// OnQueryOnly.CLOSE as a performance optimisation on Postgres
|
||||
this.onQueryOnly = OnQueryOnly.CLOSE;
|
||||
this.likeClause = "like ? escape''";
|
||||
|
||||
this.selectCountWithAlias = true;
|
||||
this.blobDbType = Types.LONGVARBINARY;
|
||||
this.clobDbType = Types.VARCHAR;
|
||||
this.nativeUuidType = true;
|
||||
|
||||
this.dbEncrypt = new PostgresDbEncrypt();
|
||||
this.historySupport = new PostgresHistorySupport();
|
||||
|
||||
@@ -263,6 +263,74 @@ public class DdlGenerator implements SpiEbeanPlugin {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
@@ -271,35 +339,16 @@ public class DdlGenerator implements SpiEbeanPlugin {
|
||||
|
||||
try {
|
||||
BufferedReader br = new BufferedReader(reader);
|
||||
StatementsSeparator statements = new StatementsSeparator();
|
||||
|
||||
ArrayList<String> statements = new ArrayList<String>();
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
String s;
|
||||
while ((s = br.readLine()) != null) {
|
||||
s = s.trim();
|
||||
int semiPos = s.indexOf(';');
|
||||
if (semiPos == -1) {
|
||||
sb.append(s).append(" ");
|
||||
|
||||
} else if (semiPos == s.length() - 1) {
|
||||
// semicolon at end of line
|
||||
sb.append(s);
|
||||
statements.add(sb.toString().trim());
|
||||
sb = new StringBuilder();
|
||||
|
||||
} else {
|
||||
// semicolon in middle of line
|
||||
String preSemi = s.substring(0, semiPos);
|
||||
sb.append(preSemi);
|
||||
statements.add(sb.toString().trim());
|
||||
sb = new StringBuilder();
|
||||
sb.append(s.substring(semiPos + 1));
|
||||
|
||||
}
|
||||
statements.nextLine(s);
|
||||
}
|
||||
|
||||
return statements;
|
||||
return statements.statements;
|
||||
|
||||
} catch (IOException e) {
|
||||
throw new PersistenceException(e);
|
||||
}
|
||||
|
||||
+14
-11
@@ -98,7 +98,7 @@ public class BaseTableDdl implements TableDdl {
|
||||
|
||||
reset();
|
||||
|
||||
String tableName = lowerName(createTable.getName());
|
||||
String tableName = lowerTableName(createTable.getName());
|
||||
List<Column> columns = createTable.getColumn();
|
||||
List<Column> pk = determinePrimaryKeyColumns(columns);
|
||||
|
||||
@@ -248,7 +248,7 @@ public class BaseTableDdl implements TableDdl {
|
||||
|
||||
protected void writeForeignKey(DdlWrite write, String fkName, String tableName, String[] columns, String refTable, String[] refColumns, String indexName) throws IOException {
|
||||
|
||||
tableName = lowerName(tableName);
|
||||
tableName = lowerTableName(tableName);
|
||||
DdlBuffer fkeyBuffer = write.applyForeignKeys();
|
||||
alterTableAddForeignKey(fkeyBuffer, fkName, tableName, columns, refTable, refColumns);
|
||||
|
||||
@@ -282,7 +282,7 @@ public class BaseTableDdl implements TableDdl {
|
||||
if (i > 0) {
|
||||
buffer.append(",");
|
||||
}
|
||||
buffer.append(lowerName(columns[i].trim()));
|
||||
buffer.append(lowerColumnName(columns[i].trim()));
|
||||
}
|
||||
buffer.append(")");
|
||||
}
|
||||
@@ -367,7 +367,7 @@ public class BaseTableDdl implements TableDdl {
|
||||
buffer.append(",").newLine();
|
||||
buffer.append(" constraint ").append(uqName).append(" unique ");
|
||||
buffer.append("(");
|
||||
buffer.append(lowerName(column.getName()));
|
||||
buffer.append(lowerColumnName(column.getName()));
|
||||
buffer.append(")");
|
||||
}
|
||||
|
||||
@@ -401,14 +401,17 @@ public class BaseTableDdl implements TableDdl {
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the table or column name to lower case.
|
||||
* <p>
|
||||
* This is passed up to the platformDdl to override as desired.
|
||||
* Generally lower case with underscore is a good cross database
|
||||
* choice for column/table names.
|
||||
* Convert the table lower case.
|
||||
*/
|
||||
protected String lowerName(String name) {
|
||||
return naming.lowerName(name);
|
||||
protected String lowerTableName(String name) {
|
||||
return naming.lowerTableName(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the column name to lower case.
|
||||
*/
|
||||
protected String lowerColumnName(String name) {
|
||||
return naming.lowerColumnName(name);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+1
-1
@@ -176,7 +176,7 @@ public abstract class DbTriggerBasedHistoryDdl implements PlatformHistoryDdl {
|
||||
|
||||
String platformType = platformDdl.convert(type, false);
|
||||
buffer.append(" ");
|
||||
buffer.append(platformDdl.lowerName(columnName), 29);
|
||||
buffer.append(platformDdl.lowerColumnName(columnName), 29);
|
||||
buffer.append(platformType);
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ public class H2Ddl extends PlatformDdl {
|
||||
|
||||
public H2Ddl(DbTypeMap platformTypes, DbIdentity dbIdentity) {
|
||||
super(platformTypes, dbIdentity);
|
||||
this.historyDdl = new H2HistoryDdl();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
package com.avaje.ebean.dbmigration.ddlgeneration.platform;
|
||||
|
||||
import com.avaje.ebean.config.dbplatform.H2HistoryTrigger;
|
||||
import com.avaje.ebean.dbmigration.ddlgeneration.DdlBuffer;
|
||||
import com.avaje.ebean.dbmigration.ddlgeneration.DdlWrite;
|
||||
import com.avaje.ebean.dbmigration.model.MTable;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* H2 history support using DB triggers to maintain a history table.
|
||||
*/
|
||||
public class H2HistoryDdl extends DbTriggerBasedHistoryDdl {
|
||||
|
||||
private static final String TRIGGER_CLASS = H2HistoryTrigger.class.getName();
|
||||
|
||||
public H2HistoryDdl() {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void dropTriggers(DdlBuffer buffer, String baseTable) throws IOException {
|
||||
|
||||
buffer.append("drop trigger ").append(updateTriggerName(baseTable)).endOfStatement();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void createTriggers(DdlWrite writer, MTable table) throws IOException {
|
||||
|
||||
String baseTableName = table.getName();
|
||||
String historyTableName = historyTableName(baseTableName);
|
||||
List<String> includedColumns = includedColumnNames(table);
|
||||
|
||||
DdlBuffer apply = writer.applyHistory();
|
||||
|
||||
addCreateTrigger(apply, updateTriggerName(baseTableName), baseTableName, historyTableName, includedColumns);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void regenerateHistoryTriggers(DdlWrite writer, MTable table, HistoryTableUpdate update) throws IOException {
|
||||
|
||||
String baseTableName = table.getName();
|
||||
String historyTableName = historyTableName(baseTableName);
|
||||
List<String> includedColumns = includedColumnNames(table);
|
||||
|
||||
DdlBuffer apply = writer.applyHistory();
|
||||
|
||||
apply.append("-- Regenerated ").newLine();
|
||||
apply.append("-- changes: ").append(update.description()).newLine();
|
||||
|
||||
dropTriggers(apply, baseTableName);
|
||||
addCreateTrigger(apply, updateTriggerName(baseTableName), baseTableName, historyTableName, includedColumns);
|
||||
|
||||
// put a reverted version into the rollback buffer
|
||||
update.toRevertedColumns(includedColumns);
|
||||
|
||||
DdlBuffer rollback = writer.rollback();
|
||||
rollback.append("-- Revert regenerated ").newLine();
|
||||
rollback.append("-- revert changes: ").append(update.description()).newLine();
|
||||
dropTriggers(rollback, baseTableName);
|
||||
addCreateTrigger(rollback, updateTriggerName(baseTableName), baseTableName, historyTableName, includedColumns);
|
||||
}
|
||||
|
||||
private void addCreateTrigger(DdlBuffer apply, String triggerName, String baseTable, String historyTable, List<String> includedColumns) throws IOException {
|
||||
|
||||
// Note that this does not take into account the historyTable name (excepts _history suffix) and
|
||||
// does not take into account excluded columns (all columns included in history)
|
||||
apply
|
||||
.append("create trigger ").append(triggerName).append(" before update,delete on ").append(baseTable)
|
||||
.append(" for each row call \"" + TRIGGER_CLASS + "\";").newLine();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -136,7 +136,7 @@ public class PlatformDdl {
|
||||
String platformType = convert(column.getType(), identityColumn);
|
||||
|
||||
buffer.append(" ");
|
||||
buffer.append(lowerName(column.getName()), 29);
|
||||
buffer.append(lowerColumnName(column.getName()), 29);
|
||||
buffer.append(platformType);
|
||||
if (isTrue(column.isNotnull()) || isTrue(column.isPrimaryKey())) {
|
||||
buffer.append(" not null");
|
||||
@@ -254,7 +254,7 @@ public class PlatformDdl {
|
||||
appendColumns(columns, buffer);
|
||||
buffer
|
||||
.append(" references ")
|
||||
.append(lowerName(refTable));
|
||||
.append(lowerTableName(refTable));
|
||||
appendColumns(refColumns, buffer);
|
||||
appendWithSpace(foreignKeyRestrict, buffer);
|
||||
|
||||
@@ -346,7 +346,7 @@ public class PlatformDdl {
|
||||
if (i > 0) {
|
||||
buffer.append(",");
|
||||
}
|
||||
buffer.append(lowerName(columns[i].trim()));
|
||||
buffer.append(lowerColumnName(columns[i].trim()));
|
||||
}
|
||||
buffer.append(")");
|
||||
}
|
||||
@@ -358,16 +358,26 @@ public class PlatformDdl {
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the table or column name to lower case.
|
||||
* Convert the table to lower case.
|
||||
* <p>
|
||||
* This is passed up to the platformDdl to override as desired.
|
||||
* Generally lower case with underscore is a good cross database
|
||||
* Override as desired. Generally lower case with underscore is a good cross database
|
||||
* choice for column/table names.
|
||||
*/
|
||||
protected String lowerName(String name) {
|
||||
return naming.lowerName(name);
|
||||
protected String lowerTableName(String name) {
|
||||
return naming.lowerTableName(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the column name to lower case.
|
||||
* <p>
|
||||
* Override as desired. Generally lower case with underscore is a good cross database
|
||||
* choice for column/table names.
|
||||
*/
|
||||
protected String lowerColumnName(String name) {
|
||||
return naming.lowerColumnName(name);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Null safe Boolean true test.
|
||||
*/
|
||||
|
||||
+3
-3
@@ -40,7 +40,7 @@ public class PostgresHistoryDdl extends DbTriggerBasedHistoryDdl {
|
||||
.endOfStatement();
|
||||
|
||||
if (whenCreatedColumn != null) {
|
||||
apply.append("update table ").append(baseTableName).append(" set ")
|
||||
apply.append("update ").append(baseTableName).append(" set ")
|
||||
.append(sysPeriod).append(" = tstzrange(").append(whenCreatedColumn).append(", null)").endOfStatement();
|
||||
}
|
||||
}
|
||||
@@ -81,12 +81,12 @@ public class PostgresHistoryDdl extends DbTriggerBasedHistoryDdl {
|
||||
appendInsertIntoHistory(apply, historyTable, includedColumns);
|
||||
apply
|
||||
.append(" NEW.").append(sysPeriod).append(" = tstzrange(CURRENT_TIMESTAMP,null);").newLine()
|
||||
.append(" return new;").newLine().newLine();
|
||||
.append(" return new;").newLine();
|
||||
apply
|
||||
.append(" elsif (TG_OP = 'DELETE') then").newLine();
|
||||
appendInsertIntoHistory(apply, historyTable, includedColumns);
|
||||
apply
|
||||
.append(" return old;").newLine().newLine();
|
||||
.append(" return old;").newLine();
|
||||
apply
|
||||
.append(" end if;").newLine()
|
||||
.append("end;").newLine()
|
||||
|
||||
@@ -14,7 +14,7 @@ import java.util.List;
|
||||
*/
|
||||
public class MCompoundForeignKey {
|
||||
|
||||
private final String name;
|
||||
private String name;
|
||||
private final String referenceTable;
|
||||
private final List<String> columns = new ArrayList<String>();
|
||||
private final List<String> referenceColumns = new ArrayList<String>();
|
||||
@@ -47,6 +47,28 @@ public class MCompoundForeignKey {
|
||||
return fk;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a counter to the foreign key and index names to avoid duplication.
|
||||
*/
|
||||
public void addNameSuffix(int counter) {
|
||||
this.name = name + "_" + counter;
|
||||
this.indexName = indexName + "_" + counter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the foreign key name.
|
||||
*/
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the index name.
|
||||
*/
|
||||
public String getIndexName() {
|
||||
return indexName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the columns making up the foreign key in order.
|
||||
*/
|
||||
|
||||
@@ -14,6 +14,7 @@ import com.avaje.ebean.dbmigration.migration.UniqueConstraint;
|
||||
import java.math.BigInteger;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
@@ -482,4 +483,32 @@ public class MTable {
|
||||
return (value == 0) ? null : BigInteger.valueOf(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if there are duplicate foreign keys.
|
||||
* <p>
|
||||
* This can occur when an ManyToMany relates back to itself.
|
||||
* </p>
|
||||
*/
|
||||
public void checkDuplicateForeignKeys() {
|
||||
|
||||
if (hasDuplicateForeignKeys()) {
|
||||
int counter = 1;
|
||||
for (MCompoundForeignKey fk : compoundKeys) {
|
||||
fk.addNameSuffix(counter++);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the foreign key names are not unique.
|
||||
*/
|
||||
private boolean hasDuplicateForeignKeys() {
|
||||
Set<String> fkNames = new HashSet<String>();
|
||||
for (MCompoundForeignKey fk : compoundKeys) {
|
||||
if (!fkNames.add(fk.getName())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -213,8 +213,8 @@ public class ModelContainer {
|
||||
/**
|
||||
* Add a table (typically from reading EbeanServer meta data).
|
||||
*/
|
||||
public void addTable(MTable table) {
|
||||
tables.put(table.getName(), table);
|
||||
public MTable addTable(MTable table) {
|
||||
return tables.put(table.getName(), table);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -67,8 +67,8 @@ public class ModelBuildContext {
|
||||
return maxLength(constraintNaming.checkConstraintName(tableName, columnName), checkCount);
|
||||
}
|
||||
|
||||
public void addTable(MTable table) {
|
||||
model.addTable(table);
|
||||
public MTable addTable(MTable table) {
|
||||
return model.addTable(table);
|
||||
}
|
||||
|
||||
public void addIndex(String indexName, String tableName, String columnName) {
|
||||
|
||||
+7
-1
@@ -35,7 +35,11 @@ public class ModelBuildIntersectionTable {
|
||||
public void build() {
|
||||
|
||||
intersectionTable = createTable();
|
||||
ctx.addTable(intersectionTable);
|
||||
MTable existingTable = ctx.addTable(intersectionTable);
|
||||
if (existingTable != null) {
|
||||
throw new IllegalStateException("Property " + manyProp.getFullBeanName() + " has duplicate ManyToMany intersection table " + intersectionTable.getName()
|
||||
+ ". Please use @JoinTable to define unique table to use");
|
||||
}
|
||||
|
||||
buildFkConstraints();
|
||||
}
|
||||
@@ -47,6 +51,8 @@ public class ModelBuildIntersectionTable {
|
||||
|
||||
BeanDescriptor<?> targetDesc = manyProp.getTargetDescriptor();
|
||||
buildFkConstraints(targetDesc, tableJoin.columns(), false);
|
||||
|
||||
intersectionTable.checkDuplicateForeignKeys();
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
/**
|
||||
* Generation DDL migration scripts based on changes to the model.
|
||||
* <p>
|
||||
* You can use <code>DbMigration</code> to compare the entity bean model to its prior state
|
||||
* and generate DDL for the differences - adding tables, columns etc.
|
||||
* </p>
|
||||
*/
|
||||
package com.avaje.ebean.dbmigration;
|
||||
@@ -0,0 +1,13 @@
|
||||
/**
|
||||
* Provides a built in change log mechanism and can audit changes.
|
||||
* <p>
|
||||
* Although you can build something similar using existing event adapters such as <code>BeanPersistController</code>
|
||||
* this is built for purpose to reduce the effort and provide optimal performance for auditing or logging changes.
|
||||
* </p>
|
||||
* <p>
|
||||
* By default you can annotate beans with <code>@ChangeLog</code> and associated change events are logged by default
|
||||
* in a JSON form with appropriate auditing attributes such as who made the changes and ip address of the user etc
|
||||
* via implementation of <code>ChangeLogPrepare</code>
|
||||
* </p>
|
||||
*/
|
||||
package com.avaje.ebean.event.changelog;
|
||||
@@ -0,0 +1,8 @@
|
||||
/**
|
||||
* Provides Auditing of read events including queries and L2 cache.
|
||||
* <p>
|
||||
* Provides a built support for supplied an audit of all the 'read events' for beans annotated
|
||||
* with <code>@ReadAudit</code>
|
||||
* </p>
|
||||
*/
|
||||
package com.avaje.ebean.event.readaudit;
|
||||
@@ -0,0 +1,8 @@
|
||||
/**
|
||||
* Provides a API for plugins.
|
||||
* <p>
|
||||
* Plugins can get and read meta data about the entity beans and enhance or utilise the
|
||||
* functionality of the EbeanServer.
|
||||
* </p>
|
||||
*/
|
||||
package com.avaje.ebean.plugin;
|
||||
@@ -59,11 +59,6 @@ public class ScopedTransaction implements SpiTransaction {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void endQueryOnly() {
|
||||
transaction.endQueryOnly();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getLogPrefix() {
|
||||
return transaction.getLogPrefix();
|
||||
|
||||
@@ -2,6 +2,7 @@ package com.avaje.ebeaninternal.api;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import com.avaje.ebeaninternal.server.core.JsonExpressionHandler;
|
||||
import com.avaje.ebeaninternal.server.core.SpiOrmQueryRequest;
|
||||
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
|
||||
|
||||
@@ -10,6 +11,11 @@ import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
|
||||
*/
|
||||
public interface SpiExpressionRequest {
|
||||
|
||||
/**
|
||||
* Return the DB specific JSON expression handler.
|
||||
*/
|
||||
JsonExpressionHandler getJsonHander();
|
||||
|
||||
/**
|
||||
* Parse the logical property name to the deployment name.
|
||||
*/
|
||||
|
||||
@@ -19,11 +19,6 @@ import com.avaje.ebeaninternal.server.persist.BatchControl;
|
||||
*/
|
||||
public interface SpiTransaction extends Transaction {
|
||||
|
||||
/**
|
||||
* End the transaction when had query only use.
|
||||
*/
|
||||
void endQueryOnly();
|
||||
|
||||
/**
|
||||
* Return the string prefix with the transactin id and label used in logging.
|
||||
*/
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
package com.avaje.ebeaninternal.server.core;
|
||||
|
||||
import java.beans.PropertyChangeListener;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import com.avaje.ebean.BeanState;
|
||||
import com.avaje.ebean.ValuePair;
|
||||
import com.avaje.ebean.bean.EntityBean;
|
||||
import com.avaje.ebean.bean.EntityBeanIntercept;
|
||||
|
||||
import java.beans.PropertyChangeListener;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Default implementation of BeanState.
|
||||
*/
|
||||
@@ -23,9 +23,13 @@ public class DefaultBeanState implements BeanState {
|
||||
this.intercept = entityBean._ebean_getIntercept();
|
||||
}
|
||||
|
||||
public boolean isReference() {
|
||||
return intercept.isReference();
|
||||
}
|
||||
public void setPropertyLoaded(String propertyName, boolean loaded) {
|
||||
intercept.setPropertyLoaded(propertyName, loaded);
|
||||
}
|
||||
|
||||
public boolean isReference() {
|
||||
return intercept.isReference();
|
||||
}
|
||||
|
||||
public boolean isNew() {
|
||||
return intercept.isNew();
|
||||
|
||||
@@ -1301,6 +1301,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
|
||||
SpiOrmQueryRequest<T> request = createQueryRequest(Type.DELETE, query, t);
|
||||
try {
|
||||
request.initTransIfRequired();
|
||||
request.markNotQueryOnly();
|
||||
return request.delete();
|
||||
} finally {
|
||||
request.endTransIfRequired();
|
||||
|
||||
@@ -39,6 +39,7 @@ import com.avaje.ebeaninternal.server.readaudit.DefaultReadAuditPrepare;
|
||||
import com.avaje.ebeaninternal.server.text.json.DJsonContext;
|
||||
import com.avaje.ebeaninternal.server.transaction.AutoCommitTransactionManager;
|
||||
import com.avaje.ebeaninternal.server.transaction.DefaultTransactionScopeManager;
|
||||
import com.avaje.ebeaninternal.server.transaction.ExplicitTransactionManager;
|
||||
import com.avaje.ebeaninternal.server.transaction.ExternalTransactionScopeManager;
|
||||
import com.avaje.ebeaninternal.server.transaction.JtaTransactionManager;
|
||||
import com.avaje.ebeaninternal.server.transaction.TransactionManager;
|
||||
@@ -208,11 +209,29 @@ public class InternalConfiguration {
|
||||
* For 'As Of' queries return the number of bind variables per predicate.
|
||||
*/
|
||||
private Binder getBinder(TypeManager typeManager, DatabasePlatform databasePlatform) {
|
||||
|
||||
JsonExpressionHandler jsonHandler = getJsonExpressionHandler(databasePlatform);
|
||||
|
||||
DbHistorySupport historySupport = databasePlatform.getHistorySupport();
|
||||
if (historySupport == null) {
|
||||
return new Binder(typeManager, 0, false);
|
||||
return new Binder(typeManager, 0, false, jsonHandler);
|
||||
}
|
||||
return new Binder(typeManager, historySupport.getBindCount(), historySupport.isBindWithFromClause());
|
||||
return new Binder(typeManager, historySupport.getBindCount(), historySupport.isBindWithFromClause(), jsonHandler);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the JSON expression handler for the given database platform.
|
||||
*/
|
||||
private JsonExpressionHandler getJsonExpressionHandler(DatabasePlatform databasePlatform) {
|
||||
|
||||
String name = databasePlatform.getName();
|
||||
if ("postgres".equalsIgnoreCase(name)) {
|
||||
return new PostgresJsonExpression();
|
||||
}
|
||||
if ("oracle".equalsIgnoreCase(name)) {
|
||||
return new OracleJsonExpression();
|
||||
}
|
||||
return new NotSupportedJsonExpression();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -220,6 +239,10 @@ public class InternalConfiguration {
|
||||
*/
|
||||
private TransactionManager createTransactionManager() {
|
||||
|
||||
if (serverConfig.isExplicitTransactionBeginMode()) {
|
||||
return new ExplicitTransactionManager(clusterManager, backgroundExecutor, serverConfig, beanDescriptorManager, this.getBootupClasses());
|
||||
}
|
||||
|
||||
if (isAutoCommitMode()) {
|
||||
return new AutoCommitTransactionManager(clusterManager, backgroundExecutor, serverConfig, beanDescriptorManager, this.getBootupClasses());
|
||||
}
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.avaje.ebeaninternal.server.core;
|
||||
|
||||
import com.avaje.ebeaninternal.api.SpiExpressionRequest;
|
||||
import com.avaje.ebeaninternal.server.expression.Op;
|
||||
|
||||
/**
|
||||
* Adds the db platform specific json expression.
|
||||
*/
|
||||
public interface JsonExpressionHandler {
|
||||
|
||||
|
||||
/**
|
||||
* Write the db platform specific json expression.
|
||||
*/
|
||||
void addSql(SpiExpressionRequest request, String propName, String path, Op operator, Object value);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package com.avaje.ebeaninternal.server.core;
|
||||
|
||||
import com.avaje.ebeaninternal.api.SpiExpressionRequest;
|
||||
import com.avaje.ebeaninternal.server.expression.Op;
|
||||
|
||||
/**
|
||||
* Not supported JSON expression handler.
|
||||
*/
|
||||
public class NotSupportedJsonExpression implements JsonExpressionHandler {
|
||||
|
||||
@Override
|
||||
public void addSql(SpiExpressionRequest request, String propName, String path, Op operator, Object value) {
|
||||
throw new RuntimeException("JSON expressions only supported on Postgres and Oracle");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.avaje.ebeaninternal.server.core;
|
||||
|
||||
import com.avaje.ebeaninternal.api.SpiExpressionRequest;
|
||||
import com.avaje.ebeaninternal.server.expression.Op;
|
||||
|
||||
/**
|
||||
* Postgres JSON expression handler
|
||||
*/
|
||||
public class OracleJsonExpression implements JsonExpressionHandler {
|
||||
|
||||
@Override
|
||||
public void addSql(SpiExpressionRequest request, String propName, String path, Op operator, Object value) {
|
||||
|
||||
if (operator == Op.EXISTS) {
|
||||
request.append("json_exists(").append(propName).append(", '$.").append(path).append("')");
|
||||
} else if (operator == Op.NOT_EXISTS) {
|
||||
request.append("not json_exists(").append(propName).append(", '$.").append(path).append("')");
|
||||
} else {
|
||||
request.append("json_value(").append(propName).append(", '$.").append(path).append("')");
|
||||
request.append(operator.bind());
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,7 @@ import javax.persistence.PersistenceException;
|
||||
|
||||
import com.avaje.ebean.*;
|
||||
import com.avaje.ebean.bean.BeanCollection;
|
||||
import com.avaje.ebean.bean.EntityBean;
|
||||
import com.avaje.ebean.bean.PersistenceContext;
|
||||
import com.avaje.ebean.event.BeanFindController;
|
||||
import com.avaje.ebean.event.BeanQueryRequest;
|
||||
@@ -71,6 +72,13 @@ public final class OrmQueryRequest<T> extends BeanRequest implements BeanQueryRe
|
||||
this.readOnly = query.isReadOnly();
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark the transaction as not being query only.
|
||||
*/
|
||||
@Override
|
||||
public void markNotQueryOnly() {
|
||||
transaction.markNotQueryOnly();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the database platform like clause.
|
||||
@@ -151,6 +159,14 @@ public final class OrmQueryRequest<T> extends BeanRequest implements BeanQueryRe
|
||||
return persistenceContext;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the bean to the persistence context.
|
||||
*/
|
||||
public void persistenceContextAdd(EntityBean bean) {
|
||||
Object id = beanDescriptor.getId(bean);
|
||||
persistenceContext.put(id, bean);
|
||||
}
|
||||
|
||||
/**
|
||||
* This will create a local (readOnly) transaction if no current transaction
|
||||
* exists.
|
||||
@@ -210,7 +226,7 @@ public final class OrmQueryRequest<T> extends BeanRequest implements BeanQueryRe
|
||||
*/
|
||||
public void endTransIfRequired() {
|
||||
if (createdTransaction) {
|
||||
transaction.endQueryOnly();
|
||||
transaction.commit();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
package com.avaje.ebeaninternal.server.core;
|
||||
|
||||
import com.avaje.ebeaninternal.api.SpiExpressionRequest;
|
||||
import com.avaje.ebeaninternal.server.expression.Op;
|
||||
|
||||
/**
|
||||
* Postgres JSON expression handler
|
||||
*/
|
||||
public class PostgresJsonExpression implements JsonExpressionHandler {
|
||||
|
||||
@Override
|
||||
public void addSql(SpiExpressionRequest request, String propName, String path, Op operator, Object value) {
|
||||
|
||||
StringBuilder sb = new StringBuilder(50);
|
||||
String[] paths = path.split("\\.");
|
||||
if (paths.length == 1) {
|
||||
// (t0.content ->> 'title') = 'Some value'
|
||||
sb.append("(").append(propName).append(" ->> '").append(path).append("')");
|
||||
|
||||
} else {
|
||||
// (t0.content #>> '{path,inner}') = 'Some value'
|
||||
sb.append("(").append(propName).append(" #>> '{");
|
||||
for (int i = 0; i < paths.length; i++) {
|
||||
if (i > 0) {
|
||||
sb.append(",");
|
||||
}
|
||||
sb.append(paths[i]);
|
||||
}
|
||||
sb.append("}')");
|
||||
}
|
||||
|
||||
request.append(castType(sb.toString(), value));
|
||||
request.append(operator.bind());
|
||||
}
|
||||
|
||||
/**
|
||||
* Postgres CAST the type if necessary as text values always returned from the json operators used.
|
||||
*/
|
||||
private String castType(String expression, Object value) {
|
||||
|
||||
if (value == null) {
|
||||
// for exists and not-exists expressions
|
||||
return expression;
|
||||
}
|
||||
|
||||
// Postgres cast of returned text value
|
||||
if (isIntegerType(value)) {
|
||||
return expression+"::INTEGER";
|
||||
}
|
||||
if (isNumberType(value)) {
|
||||
return expression+"::DECIMAL";
|
||||
}
|
||||
if (isBooleanType(value)) {
|
||||
return expression+"::BOOLEAN";
|
||||
}
|
||||
|
||||
return expression;
|
||||
}
|
||||
|
||||
private boolean isBooleanType(Object value) {
|
||||
return (value instanceof Boolean);
|
||||
}
|
||||
|
||||
private boolean isIntegerType(Object value) {
|
||||
return (value instanceof Integer) || (value instanceof Long);
|
||||
}
|
||||
|
||||
private boolean isNumberType(Object value) {
|
||||
return (value instanceof Number);
|
||||
}
|
||||
}
|
||||
@@ -59,7 +59,7 @@ public final class RelationalQueryRequest {
|
||||
*/
|
||||
public void endTransIfRequired() {
|
||||
if (createdTransaction) {
|
||||
trans.endQueryOnly();
|
||||
trans.commit();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -112,4 +112,8 @@ public interface SpiOrmQueryRequest<T> {
|
||||
*/
|
||||
String getDBLikeClause();
|
||||
|
||||
/**
|
||||
* Mark the underlying transaction as not being query only.
|
||||
*/
|
||||
void markNotQueryOnly();
|
||||
}
|
||||
+29
-14
@@ -73,7 +73,31 @@ public class DeployBeanPropertyLists {
|
||||
|
||||
this.propertyMap = new LinkedHashMap<String, BeanProperty>();
|
||||
|
||||
// see if there is a discriminator property we should add
|
||||
String discriminatorColumn = null;
|
||||
BeanProperty discProperty = null;
|
||||
|
||||
InheritInfo inheritInfo = deploy.getInheritInfo();
|
||||
if (inheritInfo != null) {
|
||||
// Create a BeanProperty for the discriminator column to support
|
||||
// using RawSql queries with inheritance
|
||||
discriminatorColumn = inheritInfo.getDiscriminatorColumn();
|
||||
DeployBeanProperty discDeployProp = new DeployBeanProperty(deploy, String.class, new ScalarTypeString(), null);
|
||||
discDeployProp.setDiscriminator();
|
||||
discDeployProp.setName(discriminatorColumn);
|
||||
discDeployProp.setDbColumn(discriminatorColumn);
|
||||
|
||||
// only register it in the propertyMap. This might not be used if
|
||||
// an explicit property is mapped to the discriminator on the bean
|
||||
discProperty = new BeanProperty(desc, discDeployProp);
|
||||
}
|
||||
|
||||
for (DeployBeanProperty prop : deploy.propertiesAll()) {
|
||||
if (discriminatorColumn != null && discriminatorColumn.equals(prop.getDbColumn())) {
|
||||
// we have an explicit property mapped to the discriminator column
|
||||
prop.setDiscriminator();
|
||||
discProperty = null;
|
||||
}
|
||||
BeanProperty beanProp = createBeanProperty(owner, prop);
|
||||
propertyMap.put(beanProp.getName(), beanProp);
|
||||
}
|
||||
@@ -84,21 +108,12 @@ public class DeployBeanPropertyLists {
|
||||
allocateToList(prop);
|
||||
}
|
||||
|
||||
InheritInfo inheritInfo = deploy.getInheritInfo();
|
||||
if (inheritInfo != null) {
|
||||
// Create a BeanProperty for the discriminator column to support
|
||||
// using RawSql queries with inheritance
|
||||
String discriminatorColumn = inheritInfo.getDiscriminatorColumn();
|
||||
DeployBeanProperty discDeployProp = new DeployBeanProperty(deploy, String.class, new ScalarTypeString(), null);
|
||||
discDeployProp.setDiscriminator();
|
||||
discDeployProp.setName(discriminatorColumn);
|
||||
discDeployProp.setDbColumn(discriminatorColumn);
|
||||
|
||||
// create the discriminator BeanProperty and only register it in the propertyMap
|
||||
BeanProperty dprop = new BeanProperty(desc, discDeployProp);
|
||||
propertyMap.put(dprop.getName(), dprop);
|
||||
if (discProperty != null) {
|
||||
// put the discriminator property into the property map only
|
||||
// (after the real properties have been organised into their lists)
|
||||
propertyMap.put(discProperty.getName(), discProperty);
|
||||
}
|
||||
|
||||
|
||||
List<DeployTableJoin> deployTableJoins = deploy.getTableJoins();
|
||||
tableJoins = new TableJoin[deployTableJoins.size()];
|
||||
for (int i = 0; i < deployTableJoins.size(); i++) {
|
||||
|
||||
@@ -76,6 +76,8 @@ public class AnnotationFields extends AnnotationParser {
|
||||
*/
|
||||
private void readAssocOne(DeployBeanProperty prop) {
|
||||
|
||||
readJsonAnnotations(prop);
|
||||
|
||||
Id id = get(prop, Id.class);
|
||||
if (id != null) {
|
||||
prop.setId();
|
||||
@@ -117,25 +119,7 @@ public class AnnotationFields extends AnnotationParser {
|
||||
readColumn(column, prop);
|
||||
}
|
||||
|
||||
if (jacksonAnnotationsPresent) {
|
||||
com.fasterxml.jackson.annotation.JsonIgnore jsonIgnore = get(prop, com.fasterxml.jackson.annotation.JsonIgnore.class);
|
||||
if (jsonIgnore != null) {
|
||||
prop.setJsonSerialize(!jsonIgnore.value());
|
||||
prop.setJsonDeserialize(!jsonIgnore.value());
|
||||
}
|
||||
}
|
||||
|
||||
Expose expose = get(prop, Expose.class);
|
||||
if (expose != null) {
|
||||
prop.setJsonSerialize(expose.serialize());
|
||||
prop.setJsonDeserialize(expose.deserialize());
|
||||
}
|
||||
|
||||
JsonIgnore jsonIgnore = get(prop, JsonIgnore.class);
|
||||
if (jsonIgnore != null) {
|
||||
prop.setJsonSerialize(jsonIgnore.serialize());
|
||||
prop.setJsonDeserialize(jsonIgnore.deserialize());
|
||||
}
|
||||
readJsonAnnotations(prop);
|
||||
|
||||
if (prop.getDbColumn() == null) {
|
||||
// No @Column annotation or @Column.name() not set
|
||||
@@ -291,6 +275,28 @@ public class AnnotationFields extends AnnotationParser {
|
||||
}
|
||||
}
|
||||
|
||||
private void readJsonAnnotations(DeployBeanProperty prop) {
|
||||
if (jacksonAnnotationsPresent) {
|
||||
com.fasterxml.jackson.annotation.JsonIgnore jsonIgnore = get(prop, com.fasterxml.jackson.annotation.JsonIgnore.class);
|
||||
if (jsonIgnore != null) {
|
||||
prop.setJsonSerialize(!jsonIgnore.value());
|
||||
prop.setJsonDeserialize(!jsonIgnore.value());
|
||||
}
|
||||
}
|
||||
|
||||
Expose expose = get(prop, Expose.class);
|
||||
if (expose != null) {
|
||||
prop.setJsonSerialize(expose.serialize());
|
||||
prop.setJsonDeserialize(expose.deserialize());
|
||||
}
|
||||
|
||||
JsonIgnore jsonIgnore = get(prop, JsonIgnore.class);
|
||||
if (jsonIgnore != null) {
|
||||
prop.setJsonSerialize(jsonIgnore.serialize());
|
||||
prop.setJsonDeserialize(jsonIgnore.deserialize());
|
||||
}
|
||||
}
|
||||
|
||||
private boolean hasRelationshipItem(DeployBeanProperty prop) {
|
||||
return get(prop, OneToMany.class) != null ||
|
||||
get(prop, ManyToOne.class) != null ||
|
||||
|
||||
@@ -111,8 +111,8 @@ public class DeployUtil {
|
||||
}
|
||||
ScalarType<?> scalarType = typeManager.getScalarType(enumType);
|
||||
if (scalarType == null) {
|
||||
// see if it has a Mapping in avaje.properties
|
||||
scalarType = typeManager.createEnumScalarType(enumType);
|
||||
// look for @DbEnumValue or @EnumValue annotations etc
|
||||
scalarType = typeManager.createEnumScalarType((Class<? extends Enum<?>>)enumType);
|
||||
if (scalarType == null) {
|
||||
// use JPA normal Enum type (without mapping)
|
||||
EnumType type = enumerated != null ? enumerated.value() : null;
|
||||
|
||||
+1
-1
@@ -211,7 +211,7 @@ public class DefaultExampleExpression implements SpiExpression, ExampleExpressio
|
||||
// exclude the zero values typically to weed out
|
||||
// primitive int and long that initialise to 0
|
||||
if (includeZeros || !isZero(value)) {
|
||||
list.add(new SimpleExpression(propName, SimpleExpression.Op.EQ, value));
|
||||
list.add(new SimpleExpression(propName, Op.EQ, value));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+46
-10
@@ -1,9 +1,5 @@
|
||||
package com.avaje.ebeaninternal.server.expression;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import com.avaje.ebean.ExampleExpression;
|
||||
import com.avaje.ebean.Expression;
|
||||
import com.avaje.ebean.ExpressionFactory;
|
||||
@@ -15,6 +11,10 @@ import com.avaje.ebean.bean.EntityBean;
|
||||
import com.avaje.ebeaninternal.api.SpiExpressionFactory;
|
||||
import com.avaje.ebeaninternal.api.SpiQuery;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Default Expression factory for creating standard expressions.
|
||||
*/
|
||||
@@ -36,6 +36,42 @@ public class DefaultExpressionFactory implements SpiExpressionFactory {
|
||||
return "sql";
|
||||
}
|
||||
|
||||
public Expression jsonExists(String propertyName, String path) {
|
||||
return new JsonPathExpression(propertyName, path, Op.EXISTS, null);
|
||||
}
|
||||
|
||||
public Expression jsonNotExists(String propertyName, String path) {
|
||||
return new JsonPathExpression(propertyName, path, Op.NOT_EXISTS, null);
|
||||
}
|
||||
|
||||
public Expression jsonEqualTo(String propertyName, String path, Object value) {
|
||||
return new JsonPathExpression(propertyName, path, Op.EQ, value);
|
||||
}
|
||||
|
||||
public Expression jsonNotEqualTo(String propertyName, String path, Object value) {
|
||||
return new JsonPathExpression(propertyName, path, Op.NOT_EQ, value);
|
||||
}
|
||||
|
||||
public Expression jsonGreaterThan(String propertyName, String path, Object value) {
|
||||
return new JsonPathExpression(propertyName, path, Op.GT, value);
|
||||
}
|
||||
|
||||
public Expression jsonGreaterOrEqual(String propertyName, String path, Object value) {
|
||||
return new JsonPathExpression(propertyName, path, Op.GT_EQ, value);
|
||||
}
|
||||
|
||||
public Expression jsonLessThan(String propertyName, String path, Object value) {
|
||||
return new JsonPathExpression(propertyName, path, Op.LT, value);
|
||||
}
|
||||
|
||||
public Expression jsonLessOrEqualTo(String propertyName, String path, Object value) {
|
||||
return new JsonPathExpression(propertyName, path, Op.LT_EQ, value);
|
||||
}
|
||||
|
||||
public Expression jsonBetween(String propertyName, String path, Object lowerValue, Object upperValue) {
|
||||
return new JsonPathExpression(propertyName, path, lowerValue, upperValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Equal To - property equal to the given value.
|
||||
*/
|
||||
@@ -43,7 +79,7 @@ public class DefaultExpressionFactory implements SpiExpressionFactory {
|
||||
if (value == null) {
|
||||
return equalsWithNullAsNoop ? NoopExpression.INSTANCE : isNull(propertyName);
|
||||
}
|
||||
return new SimpleExpression(propertyName, SimpleExpression.Op.EQ, value);
|
||||
return new SimpleExpression(propertyName, Op.EQ, value);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -53,7 +89,7 @@ public class DefaultExpressionFactory implements SpiExpressionFactory {
|
||||
if (value == null) {
|
||||
return equalsWithNullAsNoop ? NoopExpression.INSTANCE : isNotNull(propertyName);
|
||||
}
|
||||
return new SimpleExpression(propertyName, SimpleExpression.Op.NOT_EQ, value);
|
||||
return new SimpleExpression(propertyName, Op.NOT_EQ, value);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -88,7 +124,7 @@ public class DefaultExpressionFactory implements SpiExpressionFactory {
|
||||
*/
|
||||
public Expression gt(String propertyName, Object value) {
|
||||
|
||||
return new SimpleExpression(propertyName, SimpleExpression.Op.GT, value);
|
||||
return new SimpleExpression(propertyName, Op.GT, value);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -97,7 +133,7 @@ public class DefaultExpressionFactory implements SpiExpressionFactory {
|
||||
*/
|
||||
public Expression ge(String propertyName, Object value) {
|
||||
|
||||
return new SimpleExpression(propertyName, SimpleExpression.Op.GT_EQ, value);
|
||||
return new SimpleExpression(propertyName, Op.GT_EQ, value);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -105,7 +141,7 @@ public class DefaultExpressionFactory implements SpiExpressionFactory {
|
||||
*/
|
||||
public Expression lt(String propertyName, Object value) {
|
||||
|
||||
return new SimpleExpression(propertyName, SimpleExpression.Op.LT, value);
|
||||
return new SimpleExpression(propertyName, Op.LT, value);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -113,7 +149,7 @@ public class DefaultExpressionFactory implements SpiExpressionFactory {
|
||||
*/
|
||||
public Expression le(String propertyName, Object value) {
|
||||
|
||||
return new SimpleExpression(propertyName, SimpleExpression.Op.LT_EQ, value);
|
||||
return new SimpleExpression(propertyName, Op.LT_EQ, value);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
package com.avaje.ebeaninternal.server.expression;
|
||||
|
||||
import com.avaje.ebean.event.BeanQueryRequest;
|
||||
import com.avaje.ebeaninternal.api.HashQueryPlanBuilder;
|
||||
import com.avaje.ebeaninternal.api.SpiExpressionRequest;
|
||||
|
||||
/**
|
||||
* Generally speaking tests the value at a given path in the JSON document.
|
||||
* <p>
|
||||
* Supports the usual operators (equal to, greater than etc).
|
||||
* </p>
|
||||
* <p>
|
||||
* The value passed in is expected to be a valid JSON type so string, number, boolean.
|
||||
* </p>
|
||||
*/
|
||||
class JsonPathExpression extends AbstractExpression {
|
||||
|
||||
/**
|
||||
* The path in the JSON document in dot notation form.
|
||||
*/
|
||||
protected final String path;
|
||||
|
||||
/**
|
||||
* The expression operator.
|
||||
*/
|
||||
protected final Op operator;
|
||||
|
||||
/**
|
||||
* The bind value used to compare against the document path value.
|
||||
*/
|
||||
protected final Object value;
|
||||
|
||||
/**
|
||||
* For Between this is the upper bind value.
|
||||
*/
|
||||
protected final Object upperValue;
|
||||
|
||||
/**
|
||||
* Construct for Operator (not BETWEEN though).
|
||||
*/
|
||||
JsonPathExpression(String propertyName, String path, Op operator, Object value) {
|
||||
super(propertyName);
|
||||
this.path = path;
|
||||
this.operator = operator;
|
||||
this.value = value;
|
||||
this.upperValue = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct for BETWEEN expression.
|
||||
*/
|
||||
JsonPathExpression(String propertyName, String path, Object value, Object upperValue) {
|
||||
super(propertyName);
|
||||
this.path = path;
|
||||
this.operator = Op.BETWEEN;
|
||||
this.value = value;
|
||||
this.upperValue = upperValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void queryAutoTuneHash(HashQueryPlanBuilder builder) {
|
||||
builder.add(JsonPathExpression.class).add(propName).add(path).add(operator);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void queryPlanHash(BeanQueryRequest<?> request, HashQueryPlanBuilder builder) {
|
||||
queryAutoTuneHash(builder);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int queryBindHash() {
|
||||
int hc = (value == null) ? 0 : value.hashCode();
|
||||
hc = (upperValue == null) ? hc : hc * 31 + upperValue.hashCode();
|
||||
return hc;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addSql(SpiExpressionRequest request) {
|
||||
|
||||
// Use DB specific expression handling (Postgres and Oracle supported)
|
||||
request.getJsonHander().addSql(request, propName, path, operator, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addBindValues(SpiExpressionRequest request) {
|
||||
|
||||
if (value != null) {
|
||||
// value is null for EXISTS/NOT EXISTS
|
||||
request.addBindValue(value);
|
||||
}
|
||||
if (upperValue != null) {
|
||||
// upperValue only for BETWEEN operator
|
||||
request.addBindValue(upperValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -313,6 +313,78 @@ abstract class JunctionExpression<T> implements Junction<T>, SpiExpression, Expr
|
||||
return exprList.findUnique();
|
||||
}
|
||||
|
||||
/**
|
||||
* Path exists - for the given path in a JSON document.
|
||||
*/
|
||||
@Override
|
||||
public ExpressionList<T> jsonExists(String propertyName, String path) {
|
||||
return exprList.jsonExists(propertyName, path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Path does not exist - for the given path in a JSON document.
|
||||
*/
|
||||
@Override
|
||||
public ExpressionList<T> jsonNotExists(String propertyName, String path){
|
||||
return exprList.jsonNotExists(propertyName, path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Equal to - for the value at the given path in the JSON document.
|
||||
*/
|
||||
@Override
|
||||
public ExpressionList<T> jsonEqualTo(String propertyName, String path, Object value){
|
||||
return exprList.jsonEqualTo(propertyName, path, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Not Equal to - for the given path in a JSON document.
|
||||
*/
|
||||
@Override
|
||||
public ExpressionList<T> jsonNotEqualTo(String propertyName, String path, Object val){
|
||||
return exprList.jsonNotEqualTo(propertyName, path, val);
|
||||
}
|
||||
|
||||
/**
|
||||
* Greater than - for the given path in a JSON document.
|
||||
*/
|
||||
@Override
|
||||
public ExpressionList<T> jsonGreaterThan(String propertyName, String path, Object val){
|
||||
return exprList.jsonGreaterThan(propertyName, path, val);
|
||||
}
|
||||
|
||||
/**
|
||||
* Greater than or equal to - for the given path in a JSON document.
|
||||
*/
|
||||
@Override
|
||||
public ExpressionList<T> jsonGreaterOrEqual(String propertyName, String path, Object val){
|
||||
return exprList.jsonGreaterOrEqual(propertyName, path, val);
|
||||
}
|
||||
|
||||
/**
|
||||
* Less than - for the given path in a JSON document.
|
||||
*/
|
||||
@Override
|
||||
public ExpressionList<T> jsonLessThan(String propertyName, String path, Object val){
|
||||
return exprList.jsonLessThan(propertyName, path, val);
|
||||
}
|
||||
|
||||
/**
|
||||
* Less than or equal to - for the given path in a JSON document.
|
||||
*/
|
||||
@Override
|
||||
public ExpressionList<T> jsonLessOrEqualTo(String propertyName, String path, Object val){
|
||||
return exprList.jsonLessOrEqualTo(propertyName, path, val);
|
||||
}
|
||||
|
||||
/**
|
||||
* Between - for the given path in a JSON document.
|
||||
*/
|
||||
@Override
|
||||
public ExpressionList<T> jsonBetween(String propertyName, String path, Object lowerValue, Object upperValue){
|
||||
return exprList.jsonBetween(propertyName, path, lowerValue, upperValue);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExpressionList<T> ge(String propertyName, Object value) {
|
||||
return exprList.ge(propertyName, value);
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
package com.avaje.ebeaninternal.server.expression;
|
||||
|
||||
/**
|
||||
* Simple operators - equals, greater than, less than etc.
|
||||
*/
|
||||
public enum Op {
|
||||
|
||||
/**
|
||||
* Exists (JSON).
|
||||
*/
|
||||
EXISTS(" is not null "),
|
||||
|
||||
/**
|
||||
* Not Exists (JSON).
|
||||
*/
|
||||
NOT_EXISTS(" is null "),
|
||||
|
||||
/**
|
||||
* Between (JSON).
|
||||
*/
|
||||
BETWEEN(" between ? and ? "),
|
||||
|
||||
/**
|
||||
* Equal to
|
||||
*/
|
||||
EQ(" = ? "),
|
||||
|
||||
/**
|
||||
* Not equal to.
|
||||
*/
|
||||
NOT_EQ(" <> ? "),
|
||||
|
||||
/**
|
||||
*
|
||||
* Less than.
|
||||
*/
|
||||
|
||||
LT(" < ? "),
|
||||
|
||||
/**
|
||||
* Less than or equal to.
|
||||
*/
|
||||
LT_EQ(" <= ? "),
|
||||
|
||||
/**
|
||||
* Greater than.
|
||||
*/
|
||||
GT(" > ? "),
|
||||
|
||||
/**
|
||||
* Greater than or equal to.
|
||||
*/
|
||||
GT_EQ(" >= ? ");
|
||||
|
||||
final String exp;
|
||||
|
||||
Op(String exp) {
|
||||
this.exp = exp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the bind expression include JDBC ? bind placeholder.
|
||||
*/
|
||||
public String bind() {
|
||||
return exp;
|
||||
}
|
||||
}
|
||||
@@ -10,20 +10,6 @@ public class SimpleExpression extends AbstractExpression {
|
||||
|
||||
private static final long serialVersionUID = -382881395755603790L;
|
||||
|
||||
enum Op {
|
||||
EQ(" = ? "), NOT_EQ(" <> ? "), LT(" < ? "), LT_EQ(" <= ? "), GT(" > ? "), GT_EQ(" >= ? ");
|
||||
|
||||
final String exp;
|
||||
|
||||
Op(String exp) {
|
||||
this.exp = exp;
|
||||
}
|
||||
|
||||
public String bind() {
|
||||
return exp;
|
||||
}
|
||||
}
|
||||
|
||||
private final Op type;
|
||||
|
||||
private final Object value;
|
||||
|
||||
@@ -64,12 +64,16 @@ public final class ShutdownManager {
|
||||
|
||||
/**
|
||||
* Deregister the Shutdown hook.
|
||||
* <p>
|
||||
* In calling this method it is expected that application code will invoke
|
||||
* the shutdown() method.
|
||||
* </p>
|
||||
* <p>
|
||||
* For running in a Servlet Container a redeploy will cause a shutdown, and
|
||||
* for that case we need to make sure the shutdown hook is deregistered.
|
||||
* </p>
|
||||
*/
|
||||
protected static void deregisterShutdownHook() {
|
||||
public static void deregisterShutdownHook() {
|
||||
synchronized (servers) {
|
||||
try {
|
||||
Runtime.getRuntime().removeShutdownHook(shutdownHook);
|
||||
@@ -87,7 +91,10 @@ public final class ShutdownManager {
|
||||
protected static void registerShutdownHook() {
|
||||
synchronized (servers) {
|
||||
try {
|
||||
Runtime.getRuntime().addShutdownHook(shutdownHook);
|
||||
String value = System.getProperty("ebean.registerShutdownHook");
|
||||
if (value == null || !value.trim().equalsIgnoreCase("false")) {
|
||||
Runtime.getRuntime().addShutdownHook(shutdownHook);
|
||||
}
|
||||
} catch (IllegalStateException ex) {
|
||||
if (!ex.getMessage().equals("Shutdown in progress")) {
|
||||
throw ex;
|
||||
|
||||
@@ -9,7 +9,9 @@ import java.util.List;
|
||||
|
||||
import javax.persistence.PersistenceException;
|
||||
|
||||
import com.avaje.ebean.config.dbplatform.DbType;
|
||||
import com.avaje.ebeaninternal.api.BindParams;
|
||||
import com.avaje.ebeaninternal.server.core.JsonExpressionHandler;
|
||||
import com.avaje.ebeaninternal.server.core.Message;
|
||||
import com.avaje.ebeaninternal.server.type.DataBind;
|
||||
import com.avaje.ebeaninternal.server.type.ScalarType;
|
||||
@@ -30,13 +32,16 @@ public class Binder {
|
||||
|
||||
private final boolean bindAsOfWithFromClause;
|
||||
|
||||
private final JsonExpressionHandler jsonExpressionHandler;
|
||||
|
||||
/**
|
||||
* Set the PreparedStatement with which to bind variables to.
|
||||
*/
|
||||
public Binder(TypeManager typeManager, int asOfBindCount, boolean bindAsOfWithFromClause) {
|
||||
public Binder(TypeManager typeManager, int asOfBindCount, boolean bindAsOfWithFromClause, JsonExpressionHandler jsonExpressionHandler) {
|
||||
this.typeManager = typeManager;
|
||||
this.asOfBindCount = asOfBindCount;
|
||||
this.bindAsOfWithFromClause = bindAsOfWithFromClause;
|
||||
this.jsonExpressionHandler = jsonExpressionHandler;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -303,6 +308,11 @@ public class Binder {
|
||||
b.setBytes((byte[]) data);
|
||||
break;
|
||||
|
||||
case DbType.UUID:
|
||||
// native UUID support in H2 and Postgres
|
||||
b.setObject(data);
|
||||
break;
|
||||
|
||||
case java.sql.Types.OTHER:
|
||||
b.setObject(data);
|
||||
break;
|
||||
@@ -375,4 +385,11 @@ public class Binder {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the JsonExpressionHandler specific to the database.
|
||||
*/
|
||||
public JsonExpressionHandler getJsonExpressionHandler() {
|
||||
return jsonExpressionHandler;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -643,6 +643,15 @@ public final class DefaultPersister implements Persister {
|
||||
private boolean isCascade() {
|
||||
return cascade;
|
||||
}
|
||||
|
||||
public void modifyListenReset(BeanCollection<?> c) {
|
||||
if (insertedParent) {
|
||||
// after insert set the modify listening mode
|
||||
// for private owned etc
|
||||
c.setModifyListening(many.getModifyListenMode());
|
||||
}
|
||||
c.modifyReset();
|
||||
}
|
||||
}
|
||||
|
||||
private void saveMany(SaveManyPropRequest saveMany, boolean insertMode) {
|
||||
@@ -684,6 +693,7 @@ public final class DefaultPersister implements Persister {
|
||||
|
||||
BeanCollection<?> c = (BeanCollection<?>) details;
|
||||
Set<?> modifyRemovals = c.getModifyRemovals();
|
||||
saveMany.modifyListenReset(c);
|
||||
if (modifyRemovals != null && !modifyRemovals.isEmpty()) {
|
||||
|
||||
SpiTransaction t = saveMany.getTransaction();
|
||||
@@ -908,6 +918,10 @@ public final class DefaultPersister implements Persister {
|
||||
t.depth(+1);
|
||||
|
||||
if (additions != null && !additions.isEmpty()) {
|
||||
// ensure any cascade batch has been flushed prior
|
||||
// to inserting into the intersection table
|
||||
t.flushBatch();
|
||||
|
||||
for (Object other : additions) {
|
||||
EntityBean otherBean = (EntityBean) other;
|
||||
// the object from the 'other' side of the ManyToMany
|
||||
@@ -933,6 +947,10 @@ public final class DefaultPersister implements Persister {
|
||||
}
|
||||
}
|
||||
if (deletions != null && !deletions.isEmpty()) {
|
||||
// ensure any cascade batch has been flushed prior
|
||||
// to inserting into the intersection table
|
||||
t.flushBatch();
|
||||
|
||||
for (Object other : deletions) {
|
||||
EntityBean otherDelete = (EntityBean) other;
|
||||
// the object from the 'other' side of the ManyToMany
|
||||
|
||||
@@ -433,6 +433,7 @@ public class CQuery<T> implements DbReadContext, CancelableQuery {
|
||||
} else {
|
||||
// nextBean set to previously read currentBean
|
||||
nextBean = currentBean;
|
||||
request.persistenceContextAdd(nextBean);
|
||||
// check the current row we have just moved to
|
||||
if (checkForDifferentBean()) {
|
||||
return true;
|
||||
|
||||
@@ -302,7 +302,7 @@ public class CQueryPredicates {
|
||||
|
||||
SpiExpressionList<?> whereExp = query.getWhereExpressions();
|
||||
if (whereExp != null) {
|
||||
DefaultExpressionRequest whereReq = new DefaultExpressionRequest(request, deployParser);
|
||||
DefaultExpressionRequest whereReq = new DefaultExpressionRequest(request, deployParser, binder);
|
||||
whereExprBindValues = whereExp.buildBindValues(whereReq);
|
||||
if (buildSql) {
|
||||
whereExprSql = whereExp.buildSql(whereReq);
|
||||
@@ -314,7 +314,7 @@ public class CQueryPredicates {
|
||||
OrmQueryProperties chunk = query.getDetail().getChunk(manyProperty.getName(), false);
|
||||
SpiExpressionList<?> filterMany = chunk.getFilterMany();
|
||||
if (filterMany != null) {
|
||||
DefaultExpressionRequest filterReq = new DefaultExpressionRequest(request, deployParser);
|
||||
DefaultExpressionRequest filterReq = new DefaultExpressionRequest(request, deployParser, binder);
|
||||
filterManyExprBindValues = filterMany.buildBindValues(filterReq);
|
||||
if (buildSql) {
|
||||
filterManyExprSql = filterMany.buildSql(filterReq);
|
||||
@@ -325,7 +325,7 @@ public class CQueryPredicates {
|
||||
// having expression
|
||||
SpiExpressionList<?> havingExpr = query.getHavingExpressions();
|
||||
if (havingExpr != null) {
|
||||
DefaultExpressionRequest havingReq = new DefaultExpressionRequest(request, deployParser);
|
||||
DefaultExpressionRequest havingReq = new DefaultExpressionRequest(request, deployParser, binder);
|
||||
havingExprBindValues = havingExpr.buildBindValues(havingReq);
|
||||
if (buildSql) {
|
||||
havingExprSql = havingExpr.buildSql(havingReq);
|
||||
|
||||
@@ -484,6 +484,7 @@ public class DefaultOrmQuery<T> implements SpiQuery<T> {
|
||||
copy.profilingListener = profilingListener;
|
||||
|
||||
copy.query = query;
|
||||
copy.rootTableAlias = rootTableAlias;
|
||||
copy.additionalWhere = additionalWhere;
|
||||
copy.additionalHaving = additionalHaving;
|
||||
copy.distinct = distinct;
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
package com.avaje.ebeaninternal.server.transaction;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.SQLException;
|
||||
|
||||
/**
|
||||
* This only works for Postgres and H2 (and doesn't work for Oracle).
|
||||
*
|
||||
* Uses explicit begin statement to start the transactions.
|
||||
*/
|
||||
public class ExplicitJdbcTransaction extends JdbcTransaction {
|
||||
|
||||
public ExplicitJdbcTransaction(String id, boolean explicit, Connection connection, TransactionManager manager) {
|
||||
super(id, explicit, connection, manager);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void checkAutoCommit(Connection connection) throws SQLException {
|
||||
// begin the transaction explicitly
|
||||
executeStatement("begin");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void performRollback() throws SQLException {
|
||||
// Postgres needs this explicit rollback statement when used with AutoCommit=true
|
||||
executeStatement("rollback");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void performCommit() throws SQLException {
|
||||
// Postgres needs this explicit commit statement when used with AutoCommit=true
|
||||
executeStatement("commit");
|
||||
}
|
||||
|
||||
private void executeStatement(String statement) throws SQLException {
|
||||
PreparedStatement stmt = connection.prepareStatement(statement);
|
||||
try {
|
||||
stmt.execute();
|
||||
} finally {
|
||||
stmt.close();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
package com.avaje.ebeaninternal.server.transaction;
|
||||
|
||||
import com.avaje.ebean.BackgroundExecutor;
|
||||
import com.avaje.ebean.config.ServerConfig;
|
||||
import com.avaje.ebeaninternal.api.SpiTransaction;
|
||||
import com.avaje.ebeaninternal.server.cluster.ClusterManager;
|
||||
import com.avaje.ebeaninternal.server.core.BootupClasses;
|
||||
import com.avaje.ebeaninternal.server.deploy.BeanDescriptorManager;
|
||||
|
||||
import java.sql.Connection;
|
||||
|
||||
/**
|
||||
* TransactionManager where the transactions start with explicit "begin" statement.
|
||||
*/
|
||||
public class ExplicitTransactionManager extends TransactionManager {
|
||||
|
||||
public ExplicitTransactionManager(ClusterManager clusterManager, BackgroundExecutor backgroundExecutor,
|
||||
ServerConfig config, BeanDescriptorManager descMgr, BootupClasses bootupClasses) {
|
||||
|
||||
super(clusterManager, backgroundExecutor, config, descMgr, bootupClasses);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a ExplicitJdbcTransaction.
|
||||
*/
|
||||
@Override
|
||||
protected SpiTransaction createTransaction(boolean explicit, Connection c, long id) {
|
||||
|
||||
return new ExplicitJdbcTransaction(prefix + id, explicit, c, this);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -849,23 +849,6 @@ public class JdbcTransaction implements SpiTransaction {
|
||||
connection.commit();
|
||||
}
|
||||
|
||||
/**
|
||||
* End the transaction on a query only request.
|
||||
*/
|
||||
@Override
|
||||
public void endQueryOnly() {
|
||||
if (!isActive()) {
|
||||
throw new IllegalStateException(illegalStateMessage);
|
||||
}
|
||||
try {
|
||||
connectionEndForQueryOnly();
|
||||
} finally {
|
||||
// these will not throw an exception
|
||||
deactivate();
|
||||
notifyQueryOnly();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Commit the transaction.
|
||||
*/
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package com.avaje.ebeaninternal.server.type;
|
||||
|
||||
import com.avaje.ebean.annotation.DbEnumType;
|
||||
import com.avaje.ebean.annotation.DbEnumValue;
|
||||
import com.avaje.ebean.annotation.EnumMapping;
|
||||
import com.avaje.ebean.annotation.EnumValue;
|
||||
import com.avaje.ebean.config.*;
|
||||
@@ -21,6 +23,7 @@ import org.slf4j.LoggerFactory;
|
||||
import java.io.File;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.math.BigDecimal;
|
||||
import java.math.BigInteger;
|
||||
import java.net.InetAddress;
|
||||
@@ -419,8 +422,10 @@ public final class DefaultTypeManager implements TypeManager, KnownImmutable {
|
||||
return value;
|
||||
}
|
||||
|
||||
@SuppressWarnings("ResultOfMethodCallIgnored")
|
||||
private boolean isIntegerType(String s) {
|
||||
protected boolean isIntegerType(String s) {
|
||||
if (isLeadingZeros(s)) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
Integer.parseInt(s);
|
||||
return true;
|
||||
@@ -429,6 +434,13 @@ public final class DefaultTypeManager implements TypeManager, KnownImmutable {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Treat leading zeros as a non-integer for enum values.
|
||||
*/
|
||||
private boolean isLeadingZeros(String s) {
|
||||
return s.length() > 1 && s.charAt(0) == '0';
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the Mapping of Enum fields to DB values using EnumValue annotations.
|
||||
* <p>
|
||||
@@ -469,7 +481,16 @@ public final class DefaultTypeManager implements TypeManager, KnownImmutable {
|
||||
* much shorter codes used in the DB.
|
||||
* </p>
|
||||
*/
|
||||
public ScalarType<?> createEnumScalarType(Class<?> enumType) {
|
||||
public ScalarType<?> createEnumScalarType(Class<? extends Enum<?>> enumType) {
|
||||
|
||||
Method[] methods = enumType.getMethods();
|
||||
for (int i = 0; i <methods.length; i++) {
|
||||
DbEnumValue dbValue = methods[i].getAnnotation(DbEnumValue.class);
|
||||
if (dbValue != null) {
|
||||
boolean integerValues = DbEnumType.INTEGER == dbValue.storage();
|
||||
return createEnumScalarTypeDbValue(enumType, methods[i], integerValues);
|
||||
}
|
||||
}
|
||||
|
||||
// get the mapping information from EnumMapping
|
||||
EnumMapping enumMapping = enumType.getAnnotation(EnumMapping.class);
|
||||
@@ -487,6 +508,33 @@ public final class DefaultTypeManager implements TypeManager, KnownImmutable {
|
||||
return createEnumScalarType(enumType, nameValueMap, integerType, dbColumnLength);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the Mapping of Enum fields to DB values using EnumValue annotations.
|
||||
* <p>
|
||||
* Return null if the EnumValue annotations are not present/used.
|
||||
* </p>
|
||||
*/
|
||||
private ScalarType<?> createEnumScalarTypeDbValue(Class<? extends Enum<?>> enumType, Method method, boolean integerType) {
|
||||
|
||||
Map<String, String> nameValueMap = new HashMap<String, String>();
|
||||
|
||||
Enum<?>[] enumConstants = enumType.getEnumConstants();
|
||||
for (int i = 0; i < enumConstants.length; i++) {
|
||||
try {
|
||||
Object value = method.invoke(enumConstants[i]);
|
||||
nameValueMap.put(enumConstants[i].name(), value.toString());
|
||||
} catch (Exception e) {
|
||||
throw new IllegalArgumentException("Error trying to invoke DbEnumValue method on "+enumConstants[i], e);
|
||||
}
|
||||
}
|
||||
if (nameValueMap.isEmpty()) {
|
||||
// Not using EnumValue here
|
||||
return null;
|
||||
}
|
||||
|
||||
return createEnumScalarType(enumType, nameValueMap, integerType, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Given the name value mapping and integer/string type and explicit DB column
|
||||
* length create the ScalarType for the Enum.
|
||||
@@ -768,7 +816,6 @@ public final class DefaultTypeManager implements TypeManager, KnownImmutable {
|
||||
*/
|
||||
protected void initialiseStandard(JsonConfig.DateTime mode, ServerConfig config) {
|
||||
|
||||
boolean binaryUUID = config.isUuidStoreAsBinary();
|
||||
DatabasePlatform databasePlatform = config.getDatabasePlatform();
|
||||
int platformClobType = databasePlatform.getClobDbType();
|
||||
int platformBlobType = databasePlatform.getBlobDbType();
|
||||
@@ -795,9 +842,17 @@ public final class DefaultTypeManager implements TypeManager, KnownImmutable {
|
||||
nativeMap.put(Types.BIT, booleanType);
|
||||
}
|
||||
|
||||
// Store UUID as binary(16) or varchar(40)
|
||||
ScalarType<?> uuidType = (binaryUUID) ? new ScalarTypeUUIDBinary() : new ScalarTypeUUIDVarchar();
|
||||
typeMap.put(UUID.class, uuidType);
|
||||
boolean nativeUuidType = databasePlatform.isNativeUuidType();
|
||||
ServerConfig.DbUuid dbUuid = config.getDbUuid();
|
||||
|
||||
if (nativeUuidType && dbUuid == ServerConfig.DbUuid.AUTO) {
|
||||
// DB has native support for UUID
|
||||
typeMap.put(UUID.class, new ScalarTypeUUIDNative());
|
||||
} else {
|
||||
// Store UUID as binary(16) or varchar(40)
|
||||
ScalarType<?> uuidType = (ServerConfig.DbUuid.BINARY == dbUuid) ? new ScalarTypeUUIDBinary() : new ScalarTypeUUIDVarchar();
|
||||
typeMap.put(UUID.class, uuidType);
|
||||
}
|
||||
|
||||
typeMap.put(File.class, fileType);
|
||||
typeMap.put(InetAddress.class, inetAddressType);
|
||||
|
||||
@@ -0,0 +1,120 @@
|
||||
package com.avaje.ebeaninternal.server.type;
|
||||
|
||||
import com.avaje.ebean.config.dbplatform.DbType;
|
||||
import com.avaje.ebean.text.json.JsonWriter;
|
||||
import com.fasterxml.jackson.core.JsonParser;
|
||||
import com.fasterxml.jackson.core.JsonToken;
|
||||
|
||||
import java.io.DataInput;
|
||||
import java.io.DataOutput;
|
||||
import java.io.IOException;
|
||||
import java.sql.SQLException;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Postgres Hstore type which maps Map<String,String> to a single 'HStore column' in the DB.
|
||||
*/
|
||||
@SuppressWarnings("rawtypes")
|
||||
public class ScalarTypeUUIDNative extends ScalarTypeBase<UUID> {
|
||||
|
||||
public ScalarTypeUUIDNative() {
|
||||
super(UUID.class, false, DbType.UUID);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isMutable() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDirty(Object value) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public UUID read(DataReader dataReader) throws SQLException {
|
||||
|
||||
Object value = dataReader.getObject();
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
return (UUID)value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bind(DataBind b, UUID value) throws SQLException {
|
||||
b.setObject(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object toJdbcType(Object value) {
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public UUID toBeanType(Object value) {
|
||||
return (UUID) value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String formatValue(UUID v) {
|
||||
return v.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public UUID parse(String value) {
|
||||
return UUID.fromString(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public UUID convertFromMillis(long dateTime) {
|
||||
throw new RuntimeException("Should never be called");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDateTimeCapable() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public UUID readData(DataInput dataInput) throws IOException {
|
||||
if (!dataInput.readBoolean()) {
|
||||
return null;
|
||||
} else {
|
||||
String json = dataInput.readUTF();
|
||||
return parse(json);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeData(DataOutput dataOutput, UUID v) throws IOException {
|
||||
if (v == null) {
|
||||
dataOutput.writeBoolean(false);
|
||||
} else {
|
||||
dataOutput.writeBoolean(true);
|
||||
String json = format(v);
|
||||
dataOutput.writeUTF(json);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void jsonWrite(JsonWriter writer, String name, UUID value) throws IOException {
|
||||
// write the field name followed by the Map/JSON Object
|
||||
if (value == null) {
|
||||
writer.writeNullField(name);
|
||||
} else {
|
||||
writer.writeStringField(name, formatValue(value));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public UUID jsonRead(JsonParser parser, JsonToken event) throws IOException {
|
||||
// at this point the BeanProperty has read the START_OBJECT token
|
||||
// to check for a null value. Pass the START_OBJECT token through to
|
||||
// the EJson parsing so that it knows the first token has been read
|
||||
String strValue = parser.getValueAsString();
|
||||
return strValue == null ? null : parse(strValue);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -55,7 +55,7 @@ public interface TypeManager {
|
||||
* Create a ScalarType for an Enum using a mapping (rather than JPA Ordinal
|
||||
* or String which has limitations).
|
||||
*/
|
||||
ScalarType<?> createEnumScalarType(Class<?> enumType);
|
||||
ScalarType<?> createEnumScalarType(Class<? extends Enum<?>> enumType);
|
||||
|
||||
/**
|
||||
* Return the ScalarType used to handle JSON content.
|
||||
|
||||
@@ -4,13 +4,24 @@ import com.avaje.ebeaninternal.api.ClassPathSearchService;
|
||||
import com.avaje.ebeaninternal.api.ClassUtil;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.boot.loader.jar.JarEntryData;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.*;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.URL;
|
||||
import java.net.URLDecoder;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Enumeration;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.jar.Attributes;
|
||||
import java.util.jar.JarFile;
|
||||
import java.util.jar.Manifest;
|
||||
@@ -157,7 +168,7 @@ public class ClassPathSearch implements ClassPathSearchService {
|
||||
|
||||
} else if (element.isJarOrWar()) {
|
||||
// search name including the ! offset if it is there
|
||||
if (classPathSize == 1 || filter.isSearchJar(element.getJarNameWithOffset())) {
|
||||
if (classPathSize == 1 || filter.isSearchJar(element.getJarNameWithOffset(), element.getJarOffset())) {
|
||||
scanJar(element);
|
||||
}
|
||||
|
||||
@@ -208,7 +219,7 @@ public class ClassPathSearch implements ClassPathSearchService {
|
||||
|
||||
private void scanDirectory(File directory) {
|
||||
List<String> directoryFiles = getDirectoryFiles(directory);
|
||||
searchFiles(Collections.enumeration(directoryFiles), null, null);
|
||||
searchFiles(Collections.enumeration(directoryFiles), null, null, null);
|
||||
}
|
||||
|
||||
private void scanUri(URI uri) throws IOException {
|
||||
@@ -234,12 +245,14 @@ public class ClassPathSearch implements ClassPathSearchService {
|
||||
File file = classPathEntry.classPath;
|
||||
module = new JarFile(file);
|
||||
|
||||
logger.trace("scanJar file:{}", file);
|
||||
|
||||
List<URI> classPathFromManifest = getClassPathFromManifest(file, module.getManifest());
|
||||
for (URI uri : classPathFromManifest) {
|
||||
scanUri(uri);
|
||||
}
|
||||
|
||||
searchFiles(module.entries(), classPathEntry.getJarName(), classPathEntry.jarOffset);
|
||||
searchFiles(module.entries(), classPathEntry.getJarName(), classPathEntry.jarOffset, file);
|
||||
|
||||
} catch (MalformedURLException ex) {
|
||||
throw new IOException("Bad classpath error: ", ex);
|
||||
@@ -284,26 +297,30 @@ public class ClassPathSearch implements ClassPathSearchService {
|
||||
/**
|
||||
* Searches through the Java Archive (jar or war file) looking for classes
|
||||
* that match our requirements.
|
||||
*
|
||||
* @param files - all of the files in the Java Archive, this is an enumeration
|
||||
* @param entries - all of the entries in the Java Archive, this is an enumeration
|
||||
* provided by the Jar file
|
||||
* @param jarFileName - the name of the java archive
|
||||
* @param jarOffset - an offset inside the archive to chop off the name of the class -
|
||||
* this is used when we have bang path offsets (e.g.
|
||||
* file:///myfile.war!/WEB-INF/classes)
|
||||
* @param module the containing jar/war file (used for spring boot embedded jar scanning)
|
||||
*/
|
||||
private void searchFiles(Enumeration<?> files, String jarFileName, String jarOffset) {
|
||||
private void searchFiles(Enumeration<?> entries, String jarFileName, String jarOffset, File module) {
|
||||
|
||||
if (files == null) {
|
||||
if (entries == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* Strips the first character off as all entries in a jar file have no /
|
||||
* prefix. We want to come out with a name like WEB-INF/classes/ to ensure
|
||||
* we filter the contents of the war/jar file by this.
|
||||
*/
|
||||
if (jarOffset != null) {
|
||||
logger.debug("searchFiles jarFileName:{} jarOffset:{}", jarFileName, jarOffset);
|
||||
|
||||
// Strips the first character off as all entries in a jar file have no /
|
||||
// prefix. We want to come out with a name like WEB-INF/classes/ to ensure
|
||||
// we filter the contents of the war/jar file by this.
|
||||
|
||||
if ("/".equals(jarOffset)) {
|
||||
// root level for runnable jar (spring boot etc)
|
||||
jarOffset = null;
|
||||
|
||||
} else if (jarOffset != null) {
|
||||
if (jarOffset.startsWith("/")) {
|
||||
jarOffset = jarOffset.substring(1);
|
||||
}
|
||||
@@ -313,48 +330,90 @@ public class ClassPathSearch implements ClassPathSearchService {
|
||||
}
|
||||
}
|
||||
|
||||
while (files.hasMoreElements()) {
|
||||
while (entries.hasMoreElements()) {
|
||||
|
||||
String fileName = files.nextElement().toString();
|
||||
Object element = entries.nextElement();
|
||||
String entryName = element.toString();
|
||||
|
||||
// we only want the class files
|
||||
if (fileName.endsWith(".class") && (jarOffset == null || fileName.startsWith(jarOffset))) {
|
||||
if (isEntryEmbeddedJar(module, entryName)) {
|
||||
scanSpringBootEmbeddedJar(jarFileName, module, entryName);
|
||||
}
|
||||
|
||||
if (jarOffset != null) {
|
||||
// we got through here only if there is an offset and we
|
||||
// matched it, so strip it off the file
|
||||
// as we are trying to find the className
|
||||
fileName = fileName.substring(jarOffset.length());
|
||||
if (isEntryClass(jarOffset, entryName)) {
|
||||
// check if it an 'interesting' class - entity etc
|
||||
registerScannedClass(jarFileName, jarOffset, entryName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if this is an embedded jar that should be scanned.
|
||||
*/
|
||||
private boolean isEntryEmbeddedJar(File module, String entryName) {
|
||||
return entryName.endsWith(".jar") && module != null && filter.isSearchJar(entryName, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if this is a class that should be checked (for entity, interesting interface etc).
|
||||
*/
|
||||
private boolean isEntryClass(String jarOffset, String entryName) {
|
||||
return entryName.endsWith(".class") && (jarOffset == null || entryName.startsWith(jarOffset));
|
||||
}
|
||||
|
||||
private void scanSpringBootEmbeddedJar(String jarFileName, File module, String fileName) {
|
||||
// spring boot embedded jar
|
||||
logger.debug("spring boot embedded:{} : module:{}", fileName, module.getAbsoluteFile());
|
||||
try {
|
||||
org.springframework.boot.loader.jar.JarFile jarFile = new org.springframework.boot.loader.jar.JarFile(module);
|
||||
org.springframework.boot.loader.jar.JarFile jarEntryFile = jarFile.getNestedJarFile(jarFile.getJarEntryData(fileName));
|
||||
Iterator<JarEntryData> iterator = jarEntryFile.iterator();
|
||||
while (iterator.hasNext()) {
|
||||
JarEntryData jarEntryData = iterator.next();
|
||||
if (jarEntryData.getName().toString().endsWith(".class")) {
|
||||
logger.debug("... spring boot class entry:{}", jarEntryData.getName().toString());
|
||||
registerScannedClass(jarFileName, null, jarEntryData.getName().toString());
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private void registerScannedClass(String jarFileName, String jarOffset, String fileName) {
|
||||
|
||||
if (jarOffset != null) {
|
||||
// we got through here only if there is an offset and we
|
||||
// matched it, so strip it off the file
|
||||
// as we are trying to find the className
|
||||
fileName = fileName.substring(jarOffset.length());
|
||||
}
|
||||
|
||||
String className = fileName.replace('/', '.').substring(0, fileName.length() - 6);
|
||||
int lastPeriod = className.lastIndexOf(".");
|
||||
|
||||
String pckgName;
|
||||
if (lastPeriod > 0) {
|
||||
pckgName = className.substring(0, lastPeriod);
|
||||
} else {
|
||||
pckgName = "";
|
||||
}
|
||||
|
||||
if (filter.isSearchPackage(pckgName)) {
|
||||
// get the class for our class name
|
||||
try {
|
||||
Class<?> theClass = Class.forName(className, false, classLoader);
|
||||
|
||||
if (matcher.isMatch(theClass)) {
|
||||
matchList.add(theClass);
|
||||
registerHit(jarFileName, theClass);
|
||||
}
|
||||
|
||||
String className = fileName.replace('/', '.').substring(0, fileName.length() - 6);
|
||||
int lastPeriod = className.lastIndexOf(".");
|
||||
|
||||
String pckgName;
|
||||
if (lastPeriod > 0) {
|
||||
pckgName = className.substring(0, lastPeriod);
|
||||
} else {
|
||||
pckgName = "";
|
||||
}
|
||||
|
||||
if (filter.isSearchPackage(pckgName)) {
|
||||
// get the class for our class name
|
||||
try {
|
||||
Class<?> theClass = Class.forName(className, false, classLoader);
|
||||
|
||||
if (matcher.isMatch(theClass)) {
|
||||
matchList.add(theClass);
|
||||
registerHit(jarFileName, theClass);
|
||||
}
|
||||
|
||||
} catch (ClassNotFoundException e) {
|
||||
// expected to get this hence trace
|
||||
logger.trace("Error searching classpath" + e.getMessage());
|
||||
} catch (NoClassDefFoundError e) {
|
||||
// expected to get this hence trace
|
||||
logger.trace("Error searching classpath" + e.getMessage());
|
||||
}
|
||||
}
|
||||
} catch (ClassNotFoundException e) {
|
||||
// expected to get this hence trace
|
||||
logger.trace("Error searching classpath" + e.getMessage());
|
||||
} catch (NoClassDefFoundError e) {
|
||||
// expected to get this hence trace
|
||||
logger.trace("Error searching classpath" + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -498,6 +557,10 @@ public class ClassPathSearch implements ClassPathSearchService {
|
||||
String getJarNameWithOffset() {
|
||||
return (jarOffset == null) ? classPath.getName() : classPath.getName() + "!" + jarOffset;
|
||||
}
|
||||
|
||||
String getJarOffset() {
|
||||
return jarOffset;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -122,17 +122,19 @@ public class ClassPathSearchFilter {
|
||||
/**
|
||||
* Return true if the jar should be included in the search.
|
||||
*/
|
||||
public boolean isSearchJar(String jarName) {
|
||||
public boolean isSearchJar(String jarName, String jarOffset) {
|
||||
|
||||
if (containedIn(includeJarSet, jarName)) {
|
||||
return true;
|
||||
}
|
||||
if (containedIn(includeJarSet, jarName)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (containedIn(excludeJarSet, jarName)) {
|
||||
return false;
|
||||
}
|
||||
return defaultJarMatch;
|
||||
}
|
||||
if (containedIn(excludeJarSet, jarName)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// by default scan the root level in runnable jar (spring boot etc)
|
||||
return "/".equals(jarOffset) || defaultJarMatch;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to determine is a match is contained in the set.
|
||||
|
||||
@@ -363,6 +363,87 @@ public class DefaultExpressionList<T> implements SpiExpressionList<T> {
|
||||
return hash;
|
||||
}
|
||||
|
||||
/**
|
||||
* Path exists - for the given path in a JSON document.
|
||||
*/
|
||||
@Override
|
||||
public ExpressionList<T> jsonExists(String propertyName, String path) {
|
||||
add(expr.jsonExists(propertyName, path));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Path does not exist - for the given path in a JSON document.
|
||||
*/
|
||||
@Override
|
||||
public ExpressionList<T> jsonNotExists(String propertyName, String path){
|
||||
add(expr.jsonNotExists(propertyName, path));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Equal to expression for the value at the given path in the JSON document.
|
||||
*/
|
||||
@Override
|
||||
public ExpressionList<T> jsonEqualTo(String propertyName, String path, Object value){
|
||||
add(expr.jsonEqualTo(propertyName, path, value));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Not Equal to - for the given path in a JSON document.
|
||||
*/
|
||||
@Override
|
||||
public ExpressionList<T> jsonNotEqualTo(String propertyName, String path, Object val){
|
||||
add(expr.jsonNotEqualTo(propertyName, path, val));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Greater than - for the given path in a JSON document.
|
||||
*/
|
||||
@Override
|
||||
public ExpressionList<T> jsonGreaterThan(String propertyName, String path, Object val){
|
||||
add(expr.jsonGreaterThan(propertyName, path, val));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Greater than or equal to - for the given path in a JSON document.
|
||||
*/
|
||||
@Override
|
||||
public ExpressionList<T> jsonGreaterOrEqual(String propertyName, String path, Object val){
|
||||
add(expr.jsonGreaterOrEqual(propertyName, path, val));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Less than - for the given path in a JSON document.
|
||||
*/
|
||||
@Override
|
||||
public ExpressionList<T> jsonLessThan(String propertyName, String path, Object val){
|
||||
add(expr.jsonLessThan(propertyName, path, val));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Less than or equal to - for the given path in a JSON document.
|
||||
*/
|
||||
@Override
|
||||
public ExpressionList<T> jsonLessOrEqualTo(String propertyName, String path, Object val){
|
||||
add(expr.jsonLessOrEqualTo(propertyName, path, val));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Between - for the given path in a JSON document.
|
||||
*/
|
||||
@Override
|
||||
public ExpressionList<T> jsonBetween(String propertyName, String path, Object lowerValue, Object upperValue){
|
||||
add(expr.jsonBetween(propertyName, path, lowerValue, upperValue));
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExpressionList<T> eq(String propertyName, Object value) {
|
||||
add(expr.eq(propertyName, value));
|
||||
|
||||
@@ -3,9 +3,11 @@ package com.avaje.ebeaninternal.util;
|
||||
import java.util.ArrayList;
|
||||
|
||||
import com.avaje.ebeaninternal.api.SpiExpressionRequest;
|
||||
import com.avaje.ebeaninternal.server.core.JsonExpressionHandler;
|
||||
import com.avaje.ebeaninternal.server.core.SpiOrmQueryRequest;
|
||||
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
|
||||
import com.avaje.ebeaninternal.server.deploy.DeployParser;
|
||||
import com.avaje.ebeaninternal.server.persist.Binder;
|
||||
|
||||
public class DefaultExpressionRequest implements SpiExpressionRequest {
|
||||
|
||||
@@ -19,18 +21,26 @@ public class DefaultExpressionRequest implements SpiExpressionRequest {
|
||||
|
||||
private final DeployParser deployParser;
|
||||
|
||||
private final Binder binder;
|
||||
|
||||
private int paramIndex;
|
||||
|
||||
public DefaultExpressionRequest(SpiOrmQueryRequest<?> queryRequest, DeployParser deployParser) {
|
||||
public DefaultExpressionRequest(SpiOrmQueryRequest<?> queryRequest, DeployParser deployParser, Binder binder) {
|
||||
this.queryRequest = queryRequest;
|
||||
this.beanDescriptor = queryRequest.getBeanDescriptor();
|
||||
this.deployParser = deployParser;
|
||||
this.binder = binder;
|
||||
}
|
||||
|
||||
public DefaultExpressionRequest(BeanDescriptor<?> beanDescriptor) {
|
||||
this.beanDescriptor = beanDescriptor;
|
||||
this.queryRequest = null;
|
||||
this.deployParser = null;
|
||||
this.binder = null;
|
||||
}
|
||||
|
||||
public JsonExpressionHandler getJsonHander() {
|
||||
return binder.getJsonExpressionHandler();
|
||||
}
|
||||
|
||||
public String parseDeploy(String logicalProp) {
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
package com.avaje.ebean;
|
||||
|
||||
import com.avaje.ebean.bean.EntityBean;
|
||||
import com.avaje.tests.model.converstation.User;
|
||||
import org.avaje.ebeantest.LoggedSqlCollector;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
public class TestUnsetLoadedProperties extends BaseTestCase {
|
||||
|
||||
@Test
|
||||
public void testUnsetPropertyLoadedState() {
|
||||
|
||||
// populate db with a user
|
||||
User seed = new User();
|
||||
seed.setName("someName");
|
||||
seed.setEmail("some@junk.com");
|
||||
seed.save();
|
||||
|
||||
|
||||
// our bean to perform stateless update
|
||||
User user = new User();
|
||||
user.setId(seed.getId());
|
||||
user.setName("name mod");
|
||||
user.setEmail("change@junk.com");
|
||||
|
||||
|
||||
BeanState beanState = Ebean.getBeanState(user);
|
||||
assertThat(beanState.getLoadedProps()).containsExactly("id", "name", "email");
|
||||
|
||||
user.markPropertyUnset("email");
|
||||
|
||||
assertThat(beanState.getLoadedProps()).containsExactly("id", "name");
|
||||
|
||||
LoggedSqlCollector.start();
|
||||
user.update();
|
||||
|
||||
List<String> loggedSql = LoggedSqlCollector.stop();
|
||||
assertThat(loggedSql).hasSize(1);
|
||||
assertThat(loggedSql.get(0)).doesNotContain("email");
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUnloadVia_EntityBeanIntercept_setPropertyLoaded() {
|
||||
|
||||
// our bean to perform stateless update
|
||||
User user = new User();
|
||||
user.setId(42L);
|
||||
user.setName("name mod");
|
||||
user.setEmail("change@junk.com");
|
||||
|
||||
BeanState beanState = Ebean.getBeanState(user);
|
||||
assertThat(beanState.getLoadedProps()).containsExactly("id", "name", "email");
|
||||
|
||||
// unset the loaded state for email
|
||||
((EntityBean)user)._ebean_getIntercept().setPropertyLoaded("email", false);
|
||||
|
||||
assertThat(beanState.getLoadedProps()).containsExactly("id", "name");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUnloadVia_Model_markPropertyUnset() {
|
||||
|
||||
// our bean to perform stateless update
|
||||
User user = new User();
|
||||
user.setId(42L);
|
||||
user.setName("name mod");
|
||||
user.setEmail("change@junk.com");
|
||||
|
||||
BeanState beanState = Ebean.getBeanState(user);
|
||||
assertThat(beanState.getLoadedProps()).containsExactly("id", "name", "email");
|
||||
|
||||
|
||||
user.markPropertyUnset("email");
|
||||
assertThat(beanState.getLoadedProps()).containsExactly("id", "name");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUnloadVia_BeanState_setPropertyLoaded() {
|
||||
|
||||
// our bean to perform stateless update
|
||||
User user = new User();
|
||||
user.setId(42L);
|
||||
user.setName("name mod");
|
||||
user.setEmail("change@junk.com");
|
||||
|
||||
BeanState beanState = Ebean.getBeanState(user);
|
||||
assertThat(beanState.getLoadedProps()).containsExactly("id", "name", "email");
|
||||
|
||||
Ebean.getBeanState(user).setPropertyLoaded("email", false);
|
||||
assertThat(beanState.getLoadedProps()).containsExactly("id", "name");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -34,4 +34,40 @@ public class DbConstraintNamingTest {
|
||||
assertThat(naming.normaliseTable("foo_bar]")).isEqualTo("foo_bar");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDefaultToLower() throws Exception {
|
||||
assertThat(naming.normaliseTable("SCH.FOO_BAR]")).isEqualTo("foo_bar");
|
||||
|
||||
assertThat(naming.lowerTableName("SCH.FOO_BAR]")).isEqualTo("SCH.FOO_BAR]");
|
||||
assertThat(naming.lowerTableName("SCH.FOO_BAR")).isEqualTo("sch.foo_bar");
|
||||
assertThat(naming.lowerColumnName("SCH.FOO_BAR]")).isEqualTo("SCH.FOO_BAR]");
|
||||
assertThat(naming.lowerColumnName("SCH.FOO_BAR")).isEqualTo("sch.foo_bar");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNoLowerCaseTable() throws Exception {
|
||||
|
||||
DbConstraintNaming naming = new DbConstraintNaming(false, true);
|
||||
assertThat(naming.normaliseTable("SCH.FOO_BAR]")).isEqualTo("FOO_BAR");
|
||||
assertThat(naming.normaliseColumn("SCH.FOO_BAR]")).isEqualTo("sch.foo_bar");
|
||||
assertThat(naming.lowerTableName("SCH.FOO_BAR]")).isEqualTo("SCH.FOO_BAR]");
|
||||
// table name not lowered
|
||||
assertThat(naming.lowerTableName("SCH.FOO_BAR")).isEqualTo("SCH.FOO_BAR");
|
||||
assertThat(naming.lowerColumnName("SCH.FOO_BAR]")).isEqualTo("SCH.FOO_BAR]");
|
||||
assertThat(naming.lowerColumnName("SCH.FOO_BAR")).isEqualTo("sch.foo_bar");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNoLowerCaseColumn() throws Exception {
|
||||
|
||||
DbConstraintNaming naming = new DbConstraintNaming(true, false);
|
||||
assertThat(naming.normaliseTable("SCH.FOO_BAR]")).isEqualTo("foo_bar");
|
||||
assertThat(naming.normaliseColumn("SCH.FOO_BAR]")).isEqualTo("SCH.FOO_BAR");
|
||||
assertThat(naming.lowerTableName("SCH.FOO_BAR]")).isEqualTo("SCH.FOO_BAR]");
|
||||
assertThat(naming.lowerTableName("SCH.FOO_BAR")).isEqualTo("sch.foo_bar");
|
||||
assertThat(naming.lowerColumnName("SCH.FOO_BAR]")).isEqualTo("SCH.FOO_BAR]");
|
||||
// column name not lowered
|
||||
assertThat(naming.lowerColumnName("SCH.FOO_BAR")).isEqualTo("SCH.FOO_BAR");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -31,11 +31,13 @@ public class ServerConfigTest {
|
||||
Properties props = new Properties();
|
||||
props.setProperty("persistBatch", "INSERT");
|
||||
props.setProperty("persistBatchOnCascade", "INSERT");
|
||||
props.setProperty("dbuuid","binary");
|
||||
|
||||
serverConfig.loadFromProperties(props);
|
||||
|
||||
assertEquals(PersistBatch.INSERT, serverConfig.getPersistBatch());
|
||||
assertEquals(PersistBatch.INSERT, serverConfig.getPersistBatchOnCascade());
|
||||
assertEquals(ServerConfig.DbUuid.BINARY, serverConfig.getDbUuid());
|
||||
|
||||
serverConfig.setPersistBatch(PersistBatch.NONE);
|
||||
serverConfig.setPersistBatchOnCascade(PersistBatch.NONE);
|
||||
@@ -45,6 +47,7 @@ public class ServerConfigTest {
|
||||
props1.setProperty("ebean.persistBatchOnCascade", "ALL");
|
||||
|
||||
serverConfig.loadFromProperties(props1);
|
||||
serverConfig.loadTestProperties();
|
||||
|
||||
assertEquals(PersistBatch.ALL, serverConfig.getPersistBatch());
|
||||
assertEquals(PersistBatch.ALL, serverConfig.getPersistBatchOnCascade());
|
||||
|
||||
@@ -35,7 +35,7 @@ public class ModelContainerApplyTest {
|
||||
assertThat(foo.getComment()).isEqualTo("comment");
|
||||
assertThat(foo.getTablespace()).isEqualTo("fooSpace");
|
||||
assertThat(foo.getIndexTablespace()).isEqualTo("fooIndexSpace");
|
||||
assertThat(foo.isWithHistory()).isEqualTo(true);
|
||||
assertThat(foo.isWithHistory()).isEqualTo(false);
|
||||
assertThat(foo.getColumns()).containsKeys("col1", "col3", "added_to_foo");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package com.avaje.ebeaninternal.server.lib;
|
||||
|
||||
import com.avaje.ebean.BaseTestCase;
|
||||
import com.avaje.ebean.Ebean;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
|
||||
public class ShutdownManagerTest extends BaseTestCase {
|
||||
|
||||
/**
|
||||
* Run this test manually.
|
||||
*/
|
||||
@Ignore
|
||||
@Test
|
||||
public void test_deregisterShutdownHook() {
|
||||
|
||||
Ebean.getDefaultServer();
|
||||
ShutdownManager.deregisterShutdownHook();
|
||||
}
|
||||
|
||||
/**
|
||||
* Run this test manually.
|
||||
*/
|
||||
@Ignore
|
||||
@Test
|
||||
public void test_noShutdownHook() {
|
||||
|
||||
System.setProperty("ebean.registerShutdownHook","false");
|
||||
Ebean.getDefaultServer();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package com.avaje.ebeaninternal.server.type;
|
||||
|
||||
import com.avaje.ebean.config.ServerConfig;
|
||||
import com.avaje.ebean.config.dbplatform.PostgresPlatform;
|
||||
import com.avaje.ebeaninternal.server.core.BootupClasses;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
public class DefaultTypeManagerTest {
|
||||
|
||||
DefaultTypeManager typeManager;
|
||||
|
||||
public DefaultTypeManagerTest() {
|
||||
ServerConfig serverConfig = new ServerConfig();
|
||||
serverConfig.setDatabasePlatform(new PostgresPlatform());
|
||||
BootupClasses bootupClasses = new BootupClasses();
|
||||
typeManager = new DefaultTypeManager(serverConfig, bootupClasses);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isIntegerType() {
|
||||
|
||||
assertTrue(typeManager.isIntegerType("1"));
|
||||
assertTrue(typeManager.isIntegerType("0"));
|
||||
|
||||
assertFalse(typeManager.isIntegerType("A"));
|
||||
assertFalse(typeManager.isIntegerType("01"));
|
||||
assertFalse(typeManager.isIntegerType(" 01"));
|
||||
assertFalse(typeManager.isIntegerType(" 0"));
|
||||
assertFalse(typeManager.isIntegerType(" 1"));
|
||||
assertFalse(typeManager.isIntegerType(" A"));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,22 +1,22 @@
|
||||
package com.avaje.ebeaninternal.server.type;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import com.avaje.ebean.BaseTestCase;
|
||||
import com.avaje.ebean.Ebean;
|
||||
import com.avaje.ebean.SqlRow;
|
||||
import com.avaje.tests.model.basic.UUOne;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
|
||||
public class TestBinaryUUID extends BaseTestCase {
|
||||
|
||||
@Test
|
||||
public void test() {
|
||||
|
||||
|
||||
|
||||
UUOne one0 = new UUOne();
|
||||
one0.setName("first one");
|
||||
|
||||
@@ -27,26 +27,31 @@ public class TestBinaryUUID extends BaseTestCase {
|
||||
|
||||
Ebean.save(one0);
|
||||
Ebean.save(one1);
|
||||
|
||||
|
||||
UUOne fetch0 = Ebean.find(UUOne.class, one0.getId());
|
||||
UUOne fetch1 = Ebean.find(UUOne.class, one1.getId());
|
||||
|
||||
Assert.assertEquals(one0.getId(), fetch0.getId());
|
||||
Assert.assertEquals(one0.getName(), fetch0.getName());
|
||||
|
||||
Assert.assertEquals(one1.getId(), fetch1.getId());
|
||||
Assert.assertEquals(one1.getName(), fetch1.getName());
|
||||
assertEquals(one0.getId(), fetch0.getId());
|
||||
assertEquals(one0.getName(), fetch0.getName());
|
||||
|
||||
assertEquals(one1.getId(), fetch1.getId());
|
||||
assertEquals(one1.getName(), fetch1.getName());
|
||||
|
||||
String sql = "select id, name from uuone";
|
||||
List<SqlRow> list = Ebean.createSqlQuery(sql).findList();
|
||||
for (SqlRow sqlRow : list) {
|
||||
Object sqlId = sqlRow.get("id");
|
||||
Assert.assertNotNull(sqlId);
|
||||
Assert.assertTrue(sqlId instanceof byte[]);
|
||||
assertNotNull(sqlId);
|
||||
UUID uuid = sqlRow.getUUID("id");
|
||||
Assert.assertNotNull(uuid);
|
||||
assertNotNull(uuid);
|
||||
}
|
||||
|
||||
|
||||
|
||||
String asJson = Ebean.json().toJson(fetch0);
|
||||
UUOne bean = Ebean.json().toBean(UUOne.class, asJson);
|
||||
|
||||
assertEquals(fetch0.getId(), bean.getId());
|
||||
assertEquals(fetch0.getName(), bean.getName());
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.avaje.tests.basic.type;
|
||||
|
||||
import com.avaje.tests.model.basic.EBasicEnumInt;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
@@ -11,6 +12,9 @@ import com.avaje.tests.model.basic.EBasic;
|
||||
import com.avaje.tests.model.basic.EBasic.Status;
|
||||
import com.avaje.tests.model.basic.EBasicEnumId;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
|
||||
public class TestEnumValueAnnotation extends BaseTestCase {
|
||||
|
||||
@Test
|
||||
@@ -28,7 +32,7 @@ public class TestEnumValueAnnotation extends BaseTestCase {
|
||||
SqlRow sqlRow = q.findUnique();
|
||||
String strStatus = sqlRow.getString("status");
|
||||
|
||||
Assert.assertEquals("N", strStatus);
|
||||
assertEquals("N", strStatus);
|
||||
|
||||
EBasic b2 = new EBasic();
|
||||
b2.setName("Apple");
|
||||
@@ -40,9 +44,9 @@ public class TestEnumValueAnnotation extends BaseTestCase {
|
||||
b3.setName("Orange");
|
||||
|
||||
Ebean.save(b3);
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAsId() {
|
||||
EBasicEnumId b = new EBasicEnumId();
|
||||
b.setName("Banana");
|
||||
@@ -56,7 +60,7 @@ public class TestEnumValueAnnotation extends BaseTestCase {
|
||||
SqlRow sqlRow = q.findUnique();
|
||||
String strStatus = sqlRow.getString("status");
|
||||
|
||||
Assert.assertEquals("N", strStatus);
|
||||
assertEquals("N", strStatus);
|
||||
|
||||
try {
|
||||
b = Ebean.find(EBasicEnumId.class, b.getStatus());
|
||||
@@ -64,7 +68,32 @@ public class TestEnumValueAnnotation extends BaseTestCase {
|
||||
Assert.fail("The use of an enum as id should work : " + iae.getLocalizedMessage());
|
||||
}
|
||||
|
||||
Assert.assertEquals(EBasicEnumId.Status.NEW, b.getStatus());
|
||||
assertEquals(EBasicEnumId.Status.NEW, b.getStatus());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDbEnumValueInt() {
|
||||
|
||||
EBasicEnumInt b = new EBasicEnumInt();
|
||||
b.setName("Banana");
|
||||
b.setStatus(EBasicEnumInt.Status.NEW);
|
||||
|
||||
Ebean.save(b);
|
||||
|
||||
SqlQuery q = Ebean.createSqlQuery("select * from e_basic_eni where id = :id");
|
||||
q.setParameter("id", b.getId());
|
||||
|
||||
SqlRow sqlRow = q.findUnique();
|
||||
Integer intStatus = sqlRow.getInteger("status");
|
||||
|
||||
assertEquals(Integer.valueOf(1), intStatus);
|
||||
|
||||
|
||||
EBasicEnumInt b2 = Ebean.find(EBasicEnumInt.class)
|
||||
.where().eq("id", b.getId())
|
||||
.eq("status", EBasicEnumInt.Status.NEW)
|
||||
.findUnique();
|
||||
|
||||
assertNotNull(b2);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +1,19 @@
|
||||
package com.avaje.tests.cascade;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import com.avaje.ebean.BaseTestCase;
|
||||
import com.avaje.ebean.Ebean;
|
||||
import com.avaje.ebean.bean.BeanCollection;
|
||||
import com.avaje.tests.model.basic.TSDetail;
|
||||
import com.avaje.tests.model.basic.TSMaster;
|
||||
import org.avaje.ebeantest.LoggedSqlCollector;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class TestPrivateOwned extends BaseTestCase {
|
||||
|
||||
@@ -33,9 +36,9 @@ public class TestPrivateOwned extends BaseTestCase {
|
||||
BeanCollection<?> bc = (BeanCollection<?>) details;
|
||||
Set<?> modifyRemovals = bc.getModifyRemovals();
|
||||
|
||||
Assert.assertNotNull(modifyRemovals);
|
||||
Assert.assertTrue(modifyRemovals.size() == 1);
|
||||
Assert.assertTrue(modifyRemovals.contains(removedDetail));
|
||||
assertNotNull(modifyRemovals);
|
||||
assertTrue(modifyRemovals.size() == 1);
|
||||
assertTrue(modifyRemovals.contains(removedDetail));
|
||||
|
||||
Ebean.save(master);
|
||||
|
||||
@@ -43,11 +46,42 @@ public class TestPrivateOwned extends BaseTestCase {
|
||||
List<TSDetail> detailsReload = masterReload.getDetails();
|
||||
|
||||
// the removed bean has really been removed
|
||||
Assert.assertTrue(detailsReload.size() == 1);
|
||||
assertTrue(detailsReload.size() == 1);
|
||||
|
||||
TSMaster master3 = Ebean.find(TSMaster.class, m0.getId());
|
||||
List<TSDetail> details3 = master3.getDetails();
|
||||
details3.clear();
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void insertThenUpdate() {
|
||||
|
||||
TSMaster m0 = new TSMaster();
|
||||
m0.setName("m2");
|
||||
|
||||
m0.addDetail(new TSDetail("m2 detail 1"));
|
||||
m0.addDetail(new TSDetail("m2 detail 2"));
|
||||
|
||||
Ebean.save(m0);
|
||||
assertThat(m0.getDetails()).hasSize(2);
|
||||
|
||||
LoggedSqlCollector.start();
|
||||
m0.getDetails().remove(0);
|
||||
Ebean.save(m0);
|
||||
|
||||
List<String> loggedSql = LoggedSqlCollector.stop();
|
||||
assertThat(loggedSql).hasSize(1);
|
||||
assertThat(loggedSql.get(0)).contains("delete from t_detail_with_other_namexxxyy where id=?");
|
||||
|
||||
TSMaster masterReload = Ebean.find(TSMaster.class, m0.getId());
|
||||
assertThat(masterReload.getDetails()).hasSize(1);
|
||||
|
||||
LoggedSqlCollector.start();
|
||||
Ebean.save(m0);
|
||||
loggedSql = LoggedSqlCollector.stop();
|
||||
assertThat(loggedSql).hasSize(0);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -5,6 +5,8 @@ import com.avaje.ebean.Ebean;
|
||||
import com.avaje.ebean.EbeanServer;
|
||||
import com.avaje.ebean.Query;
|
||||
import com.avaje.tests.model.basic.Contact;
|
||||
import com.avaje.tests.model.basic.Customer;
|
||||
import com.avaje.tests.model.basic.ResetBasicData;
|
||||
import org.avaje.ebeantest.LoggedSqlCollector;
|
||||
import org.junit.Test;
|
||||
|
||||
@@ -18,6 +20,10 @@ public class TestDeleteByQuery extends BaseTestCase {
|
||||
public void test() {
|
||||
|
||||
EbeanServer server = Ebean.getDefaultServer();
|
||||
if (server.getName().equals("mysql")) {
|
||||
// MySql does not the sub query selecting from the delete table
|
||||
return;
|
||||
}
|
||||
|
||||
Query<Contact> query = server.find(Contact.class).where().eq("group.name", "NahYeahMaybe").query();
|
||||
|
||||
@@ -45,4 +51,29 @@ public class TestDeleteByQuery extends BaseTestCase {
|
||||
assertThat(list).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCommit() {
|
||||
|
||||
EbeanServer server = Ebean.getDefaultServer();
|
||||
if (server.getName().equals("mysql")) {
|
||||
// MySql does not the sub query selecting from the delete table
|
||||
return;
|
||||
}
|
||||
|
||||
ResetBasicData.reset();
|
||||
|
||||
List<Customer> all = Customer.find.all();
|
||||
Contact contact = new Contact();
|
||||
contact.setFirstName("DelByQueryFirstName");
|
||||
contact.setLastName("deleteMe");
|
||||
contact.setCustomer(all.get(0));
|
||||
|
||||
Ebean.save(contact);
|
||||
|
||||
Ebean.find(Contact.class).select("id").where().eq("firstName", "DelByQueryFirstName").delete();
|
||||
|
||||
Contact contactFind = Ebean.find(Contact.class, contact.getId());
|
||||
assertThat(contactFind).isNull();
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
package com.avaje.tests.history;
|
||||
|
||||
import com.avaje.ebean.BaseTestCase;
|
||||
import com.avaje.ebean.Ebean;
|
||||
import com.avaje.ebean.SqlQuery;
|
||||
import com.avaje.ebean.SqlRow;
|
||||
import com.avaje.ebean.Version;
|
||||
import com.avaje.ebeaninternal.api.SpiEbeanServer;
|
||||
import com.avaje.tests.model.converstation.User;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.sql.Timestamp;
|
||||
import java.util.List;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
public class TestHistoryInsert extends BaseTestCase {
|
||||
|
||||
@Test
|
||||
public void test() {
|
||||
|
||||
SpiEbeanServer defaultServer = (SpiEbeanServer)Ebean.getDefaultServer();
|
||||
if (!"h2".equals(defaultServer.getDatabasePlatform().getName())) {
|
||||
// Oracle for example uses total recall so we can select the explicit
|
||||
// history tables as we do in this test
|
||||
return;
|
||||
}
|
||||
|
||||
User user = new User();
|
||||
user.setName("Jim");
|
||||
user.setEmail("one@email.com");
|
||||
|
||||
Ebean.save(user);
|
||||
|
||||
Timestamp afterInsert = new Timestamp(System.currentTimeMillis());
|
||||
|
||||
List<SqlRow> history = fetchHistory(user);
|
||||
assertThat(history).isEmpty();
|
||||
|
||||
List<Version<User>> versions = Ebean.find(User.class).setId(user.getId()).findVersions();
|
||||
assertThat(versions).hasSize(1);
|
||||
|
||||
user.setName("Jim v2");
|
||||
Ebean.save(user);
|
||||
|
||||
history = fetchHistory(user);
|
||||
assertThat(history).hasSize(1);
|
||||
assertThat(history.get(0).getString("name")).isEqualTo("Jim");
|
||||
|
||||
versions = Ebean.find(User.class).setId(user.getId()).findVersions();
|
||||
assertThat(versions).hasSize(2);
|
||||
assertThat(versions.get(0).getDiff()).containsKeys("name", "version", "whenModified");
|
||||
|
||||
user.setName("Jim v3");
|
||||
user.setEmail("three@email.com");
|
||||
Ebean.save(user);
|
||||
|
||||
history = fetchHistory(user);
|
||||
assertThat(history).hasSize(2);
|
||||
assertThat(history.get(1).getString("name")).isEqualTo("Jim v2");
|
||||
assertThat(history.get(1).getString("email")).isEqualTo("one@email.com");
|
||||
|
||||
versions = Ebean.find(User.class).setId(user.getId()).findVersions();
|
||||
assertThat(versions).hasSize(3);
|
||||
assertThat(versions.get(0).getDiff()).containsKeys("name", "email", "version", "whenModified");
|
||||
|
||||
Ebean.delete(user);
|
||||
|
||||
User earlyVersion = Ebean.find(User.class).setId(user.getId()).asOf(afterInsert).findUnique();
|
||||
assertThat(earlyVersion.getName()).isEqualTo("Jim");
|
||||
assertThat(earlyVersion.getEmail()).isEqualTo("one@email.com");
|
||||
|
||||
|
||||
history = fetchHistory(user);
|
||||
assertThat(history).hasSize(3);
|
||||
assertThat(history.get(2).getString("name")).isEqualTo("Jim v3");
|
||||
assertThat(history.get(2).getString("email")).isEqualTo("three@email.com");
|
||||
|
||||
versions = Ebean.find(User.class).setId(user.getId()).findVersions();
|
||||
assertThat(versions).hasSize(3);
|
||||
}
|
||||
|
||||
/**
|
||||
* Use SqlQuery to query the history table directly.
|
||||
*/
|
||||
private List<SqlRow> fetchHistory(User user) {
|
||||
SqlQuery sqlQuery = Ebean.createSqlQuery("select * from c_user_history where id = :id order by sys_period_start");
|
||||
sqlQuery.setParameter("id", user.getId());
|
||||
return sqlQuery.findList();
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,5 @@
|
||||
package com.avaje.tests.inheritance;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import com.avaje.ebean.BaseTestCase;
|
||||
import com.avaje.ebean.Ebean;
|
||||
import com.avaje.ebean.bean.BeanCollection.ModifyListenMode;
|
||||
@@ -13,6 +8,11 @@ import com.avaje.tests.model.basic.Animal;
|
||||
import com.avaje.tests.model.basic.AnimalShelter;
|
||||
import com.avaje.tests.model.basic.Cat;
|
||||
import com.avaje.tests.model.basic.Dog;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
|
||||
public class TestInheritanceOnMany extends BaseTestCase {
|
||||
|
||||
@@ -40,8 +40,9 @@ public class TestInheritanceOnMany extends BaseTestCase {
|
||||
BeanList<?> beanList = (BeanList<?>)animals;
|
||||
ModifyListenMode modifyListenMode = beanList.getModifyListenMode();
|
||||
|
||||
Assert.assertNotNull(modifyListenMode);
|
||||
|
||||
assertNotNull(modifyListenMode);
|
||||
|
||||
assertNotNull(Ebean.find(Animal.class).findList());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
package com.avaje.tests.iud;
|
||||
|
||||
import com.avaje.ebean.BaseTestCase;
|
||||
import com.avaje.ebean.Ebean;
|
||||
import com.avaje.tests.model.info.InfoCompany;
|
||||
import com.avaje.tests.model.info.InfoCustomer;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNull;
|
||||
|
||||
public class TestInfoOneToOne extends BaseTestCase {
|
||||
|
||||
@Test
|
||||
@@ -20,13 +24,17 @@ public class TestInfoOneToOne extends BaseTestCase {
|
||||
|
||||
customer.save();
|
||||
|
||||
// assert JsonIgnore working as expected
|
||||
String asJson = Ebean.json().toJson(company);
|
||||
assertThat(asJson).doesNotContain("contacts");
|
||||
|
||||
// assert both are inserted
|
||||
Assert.assertNotNull(customer.getId());
|
||||
Assert.assertNotNull(company.getId());
|
||||
assertNotNull(customer.getId());
|
||||
assertNotNull(company.getId());
|
||||
|
||||
// both can be fetched
|
||||
Assert.assertNotNull(InfoCustomer.find.byId(customer.getId()));
|
||||
Assert.assertNotNull(InfoCompany.find.byId(company.getId()));
|
||||
assertNotNull(InfoCustomer.find.byId(customer.getId()));
|
||||
assertNotNull(InfoCompany.find.byId(company.getId()));
|
||||
|
||||
|
||||
// just update the customer
|
||||
@@ -47,8 +55,8 @@ public class TestInfoOneToOne extends BaseTestCase {
|
||||
|
||||
// delete both customer and company
|
||||
fetchedCustomer.delete();
|
||||
Assert.assertNull(InfoCustomer.find.byId(customer.getId()));
|
||||
Assert.assertNull(InfoCompany.find.byId(company.getId()));
|
||||
assertNull(InfoCustomer.find.byId(customer.getId()));
|
||||
assertNull(InfoCompany.find.byId(company.getId()));
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
package com.avaje.tests.m2m;
|
||||
|
||||
import com.avaje.ebean.BaseTestCase;
|
||||
import com.avaje.ebean.Ebean;
|
||||
import com.avaje.ebean.SqlQuery;
|
||||
import com.avaje.ebean.SqlRow;
|
||||
import com.avaje.tests.model.basic.MnocRole;
|
||||
import com.avaje.tests.model.basic.MnocUser;
|
||||
import com.avaje.tests.model.m2m.MnyA;
|
||||
import com.avaje.tests.model.m2m.MnyB;
|
||||
import com.avaje.tests.model.m2m.MnyC;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import javax.persistence.PersistenceException;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
public class TestM2MDeleteCascadeFromParent extends BaseTestCase {
|
||||
|
||||
@Test
|
||||
public void test() {
|
||||
|
||||
MnyC c0 = new MnyC();
|
||||
c0.setName("c0");
|
||||
c0.save();
|
||||
|
||||
MnyA a0 = new MnyA();
|
||||
a0.setName("a0");
|
||||
a0.save();
|
||||
|
||||
MnyB b0 = new MnyB();
|
||||
b0.setName("b0");
|
||||
b0.setA(a0);
|
||||
b0.save();
|
||||
|
||||
c0.getBs().add(b0);
|
||||
Ebean.saveAssociation(c0, "bs");
|
||||
|
||||
SqlQuery sqlQuery = Ebean.createSqlQuery("select count(*) as count from mny_b_mny_c");
|
||||
SqlRow unique = sqlQuery.findUnique();
|
||||
assertEquals(Long.valueOf(1), unique.getLong("count"));
|
||||
|
||||
|
||||
//Ebean.deleteManyToManyAssociations(b0, "cs");
|
||||
a0.delete();
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package com.avaje.tests.m2m;
|
||||
|
||||
import com.avaje.ebean.BaseTestCase;
|
||||
import com.avaje.ebean.Ebean;
|
||||
import com.avaje.tests.model.m2m.MailBox;
|
||||
import com.avaje.tests.model.m2m.MailUser;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
public class TestM2MMultipleLists extends BaseTestCase {
|
||||
|
||||
@Test
|
||||
public void test() {
|
||||
|
||||
MailUser u = new MailUser();
|
||||
u.setName("mailUser1");
|
||||
|
||||
MailBox m1 = new MailBox();
|
||||
m1.setName("mailBox1");
|
||||
|
||||
u.getInbox().add(m1);
|
||||
Ebean.save(u);
|
||||
|
||||
u = Ebean.find(MailUser.class, u.getId());
|
||||
|
||||
assertEquals(1, u.getInbox().size());
|
||||
assertEquals(0, u.getOutbox().size());
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package com.avaje.tests.m2m;
|
||||
|
||||
import com.avaje.ebean.BaseTestCase;
|
||||
import com.avaje.ebean.Ebean;
|
||||
import com.avaje.tests.model.m2m.MnyTopic;
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* Added to test DDL generation for ManyToMany related back to itself.
|
||||
*/
|
||||
public class TestM2MSelfRelationship extends BaseTestCase {
|
||||
|
||||
@Test
|
||||
public void test() {
|
||||
|
||||
Ebean.getDefaultServer();
|
||||
|
||||
// Create 2 roles r0 and r1
|
||||
MnyTopic r0 = new MnyTopic("r0");
|
||||
MnyTopic r1 = new MnyTopic("r1");
|
||||
|
||||
// Save r1 and r2
|
||||
Ebean.save(r0);
|
||||
Ebean.save(r1);
|
||||
|
||||
r0.getSubTopics().add(r1);
|
||||
Ebean.save(r0);
|
||||
}
|
||||
}
|
||||
@@ -68,4 +68,28 @@ public class TestM2mDeleteObject extends BaseTestCase {
|
||||
Assert.assertEquals(2, permissionList2.size());
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void test_deleteCascading() {
|
||||
|
||||
Permission p1 = new Permission();
|
||||
Permission p2 = new Permission();
|
||||
p1.setName("p1");
|
||||
p2.setName("p2");
|
||||
Ebean.save(p1);
|
||||
Ebean.save(p2);
|
||||
|
||||
Role role1 = new Role();
|
||||
role1.setName("role");
|
||||
|
||||
Set<Permission> permissions = new HashSet<Permission>();
|
||||
permissions.add(p2);
|
||||
role1.setPermissions(permissions);
|
||||
Ebean.save(role1);
|
||||
|
||||
// only deletes as not associated
|
||||
Ebean.delete(p1);
|
||||
// delete cascades
|
||||
Ebean.delete(role1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.avaje.tests.model.basic;
|
||||
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.DiscriminatorColumn;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.Id;
|
||||
@@ -21,6 +22,9 @@ public abstract class Animal {
|
||||
@Version
|
||||
Long version;
|
||||
|
||||
@Column(name="species", insertable = false, updatable = false, nullable = false)
|
||||
String species;
|
||||
|
||||
@ManyToOne
|
||||
AnimalShelter shelter;
|
||||
|
||||
@@ -40,6 +44,14 @@ public abstract class Animal {
|
||||
this.version = version;
|
||||
}
|
||||
|
||||
public String getSpecies() {
|
||||
return species;
|
||||
}
|
||||
|
||||
public void setSpecies(String species) {
|
||||
this.species = species;
|
||||
}
|
||||
|
||||
public AnimalShelter getShelter() {
|
||||
return shelter;
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ package com.avaje.tests.model.basic;
|
||||
|
||||
import com.avaje.ebean.annotation.ChangeLog;
|
||||
import com.avaje.ebean.annotation.ChangeLogInsertMode;
|
||||
import com.avaje.ebean.annotation.EnumValue;
|
||||
import com.avaje.ebean.annotation.DbEnumValue;
|
||||
import com.avaje.ebean.annotation.JsonIgnore;
|
||||
import com.avaje.ebean.annotation.Where;
|
||||
import com.avaje.tests.model.basic.finder.CustomerFinder;
|
||||
@@ -36,14 +36,19 @@ public class Customer extends BasicDomain {
|
||||
* EnumValue is an Ebean specific mapping for enums.
|
||||
*/
|
||||
public enum Status {
|
||||
@EnumValue("N")
|
||||
NEW,
|
||||
NEW("N"),
|
||||
ACTIVE("A"),
|
||||
INACTIVE("I");
|
||||
|
||||
@EnumValue("A")
|
||||
ACTIVE,
|
||||
String dbValue;
|
||||
Status(String dbValue) {
|
||||
this.dbValue = dbValue;
|
||||
}
|
||||
|
||||
@EnumValue("I")
|
||||
INACTIVE
|
||||
@DbEnumValue
|
||||
public String getValue() {
|
||||
return dbValue;
|
||||
}
|
||||
}
|
||||
|
||||
@Transient
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
package com.avaje.tests.model.basic;
|
||||
|
||||
import com.avaje.ebean.annotation.DbEnumType;
|
||||
import com.avaje.ebean.annotation.DbEnumValue;
|
||||
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.Table;
|
||||
import java.sql.Timestamp;
|
||||
|
||||
@Entity
|
||||
@Table(name = "e_basic_eni")
|
||||
public class EBasicEnumInt {
|
||||
|
||||
public enum Status {
|
||||
NEW("1"),
|
||||
ACTIVE("2"),
|
||||
INACTIVE("3");
|
||||
|
||||
String value;
|
||||
Status(String value) {
|
||||
this.value = value;
|
||||
}
|
||||
@DbEnumValue(storage = DbEnumType.INTEGER)
|
||||
public String getValue() {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
@Id
|
||||
Integer id;
|
||||
|
||||
Status status;
|
||||
|
||||
String name;
|
||||
|
||||
String description;
|
||||
|
||||
Timestamp someDate;
|
||||
|
||||
public Integer getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Integer id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public Status getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
public void setStatus(Status status) {
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
public Timestamp getSomeDate() {
|
||||
return someDate;
|
||||
}
|
||||
|
||||
public void setSomeDate(Timestamp someDate) {
|
||||
this.someDate = someDate;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,14 +1,14 @@
|
||||
package com.avaje.tests.model.basic;
|
||||
|
||||
import java.sql.Date;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import com.avaje.ebean.Ebean;
|
||||
import com.avaje.ebean.EbeanServer;
|
||||
import com.avaje.ebean.TxRunnable;
|
||||
import com.avaje.tests.model.basic.Order.Status;
|
||||
|
||||
import java.sql.Date;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class ResetBasicData {
|
||||
|
||||
private static boolean runOnce;
|
||||
@@ -135,6 +135,7 @@ public class ResetBasicData {
|
||||
createOrder2(cust2);
|
||||
createOrder3(cust1);
|
||||
createOrder4(cust1);
|
||||
createOrder5(cust2);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -313,4 +314,13 @@ public class ResetBasicData {
|
||||
|
||||
Ebean.save(order);
|
||||
}
|
||||
|
||||
private void createOrder5(Customer customer) {
|
||||
|
||||
Order order = new Order();
|
||||
order.setCustomer(customer);
|
||||
order.addShipment(new OrderShipment());
|
||||
|
||||
Ebean.save(order);
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user