Merge branch 'master' of github.com:ebean-orm/ebean

This commit is contained in:
rob bygrave
2021-03-09 20:13:47 +13:00
120 changed files with 2294 additions and 727 deletions
+1 -1
View File
@@ -293,6 +293,6 @@ ebean.tenant.schemaProvider
ebean.updateAllPropertiesInBatch
ebean.updateChangesOnly
ebean.updatesDeleteMissingChildren
ebean.useJavaxValidationNotNull
ebean.useValidationNotNull
ebean.useJtaTransactionManager
+3 -16
View File
@@ -4,18 +4,13 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>12.7.1-SNAPSHOT</version>
<version>12.7.3-SNAPSHOT</version>
</parent>
<name>ebean api</name>
<description>ebean api</description>
<artifactId>ebean-api</artifactId>
<properties>
<jackson-core.version>2.11.3</jackson-core.version>
<jackson-databind.version>2.11.3</jackson-databind.version>
</properties>
<dependencies>
<!--
@@ -74,7 +69,7 @@
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>${jackson-core.version}</version>
<version>${jackson.version}</version>
<optional>true</optional>
</dependency>
@@ -82,7 +77,7 @@
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>${jackson-databind.version}</version>
<version>${jackson.version}</version>
<optional>true</optional>
</dependency>
@@ -102,14 +97,6 @@
<optional>true</optional>
</dependency>
<!-- provided scope to read validation annotations Size etc -->
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<version>1.1.0.Final</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
@@ -14,7 +14,7 @@ import java.util.Optional;
*
* public class CustomerFinder extends BeanFinder<Long,Customer> {
*
* @Inject
* @Inject
* public CustomerFinder(Database database) {
* super(Customer.class, database);
* }
@@ -9,10 +9,10 @@ import java.util.Collection;
* <p>
* <pre>{@code
*
* @Repository
* @Repository
* public class CustomerRepository extends BeanRepository<Long,Customer> {
*
* @Inject
* @Inject
* public CustomerRepository(Database server) {
* super(Customer.class, server);
* }
@@ -897,7 +897,7 @@ public interface Database {
* <pre>{@code
* public class Order { ...
*
* @OneToMany(cascade=CascadeType.ALL, mappedBy="order")
* @OneToMany(cascade=CascadeType.ALL, mappedBy="order")
* List<OrderDetail> details;
* ...
* }
@@ -149,7 +149,7 @@ public interface DocumentStore {
* .setUseDocStore(true)
* .where()... // perhaps add predicates
* .findEachWhile(new Predicate<Order>() {
* @Override
* @Override
* public void accept(Order bean) {
* // process the bean
*
+1 -1
View File
@@ -346,7 +346,7 @@ public final class Ebean {
* <pre>{@code
* public class Order { ...
*
* @OneToMany(cascade=CascadeType.ALL, mappedBy="order")
* @OneToMany(cascade=CascadeType.ALL, mappedBy="order")
* List<OrderDetail> details;
* ...
* }
@@ -200,7 +200,7 @@ public interface ExpressionFactory {
Expression gtOrNull(String propertyName, Object value);
/**
* Greater than or Equal to OR Null <code> >= or null </code>
* Greater than or Equal to OR Null ({@code >= or null })
* <p>
* A convenient expression combining GE and Is Null. Most often useful for range
* expressions where the top range value is nullable.
@@ -227,7 +227,7 @@ public interface ExpressionFactory {
Expression ltOrNull(String propertyName, Object value);
/**
* Less Than or Equal to OR Null <code> <= or null </code>
* Less Than or Equal to OR Null ({@code <= or null })
* <p>
* A convenient expression combining LE and Is Null. Most often useful for range
* expressions where the bottom range value is nullable.
@@ -909,7 +909,7 @@ public interface ExpressionList<T> {
ExpressionList<T> gtOrNull(String propertyName, Object value);
/**
* Greater Than or Equal to OR Null - <code> >= or null </code>.
* Greater Than or Equal to OR Null - ({@code >= or null }).
*/
ExpressionList<T> geOrNull(String propertyName, Object value);
@@ -930,7 +930,7 @@ public interface ExpressionList<T> {
ExpressionList<T> ltOrNull(String propertyName, Object value);
/**
* Less Than or Equal to OR Null - <code> <= or null </code>.
* Less Than or Equal to OR Null - ({@code <= or null }).
*/
ExpressionList<T> leOrNull(String propertyName, Object value);
+2 -2
View File
@@ -37,7 +37,7 @@ import java.util.List;
* }
* }
*
* @Entity
* @Entity
* public class Customer extends BaseModel {
*
* public static final CustomerFinder find = new CustomerFinder();
@@ -80,7 +80,7 @@ public class Finder<I, T> {
* // ... add extra customer specific finder methods
* }
*
* @Entity
* @Entity
* public class Customer extends BaseModel {
*
* public static final CustomerFinder find = new CustomerFinder();
+6 -6
View File
@@ -32,16 +32,16 @@ import io.ebean.bean.EntityBean;
* // Typically there is a common base model that has some
* // common properties like the ones below
*
* @MappedSuperclass
* @MappedSuperclass
* public class BaseModel extends Model {
*
* @Id Long id;
* @Id Long id;
*
* @Version Long version;
* @Version Long version;
*
* @WhenCreated Timestamp whenCreated;
* @WhenCreated Timestamp whenCreated;
*
* @WhenUpdated Timestamp whenUpdated;
* @WhenUpdated Timestamp whenUpdated;
*
* ...
* }
@@ -52,7 +52,7 @@ import io.ebean.bean.EntityBean;
*
* // Extend the mappedSuperclass
*
* @Entity @Table(name="o_account")
* @Entity @Table(name="o_account")
* public class Customer extends BaseModel {
*
* String name;
+1 -1
View File
@@ -18,7 +18,7 @@ import java.util.List;
*
* // where a bean is annotated with a complex
* // natural key made of several properties
* @Cache(naturalKey = {"store","code","sku"})
* @Cache(naturalKey = {"store","code","sku"})
*
*
* Pairs pairs = new Pairs("sku", "code");
+4 -4
View File
@@ -41,14 +41,14 @@ package io.ebean;
* <h3>Example OrderAggregate</h3>
* <pre>{@code
* ...
* // @Sql indicates to that this bean
* // @Sql indicates to that this bean
* // is based on RawSql rather than a table
*
* @Entity
* @Sql
* @Entity
* @Sql
* public class OrderAggregate {
*
* @OneToOne
* @OneToOne
* Order order;
*
* Double totalAmount;
@@ -22,7 +22,7 @@ import java.sql.SQLException;
* //
* class CustomerMapper implements RowMapper<CustomerDto> {
*
* @Override
* @Override
* public CustomerDto map(ResultSet rset, int rowNum) throws SQLException {
*
* long id = rset.getLong(1);
+6 -6
View File
@@ -13,23 +13,23 @@ package io.ebean;
* </p>
* <pre>{@code
* ...
* @NamedUpdates(value = {
* @NamedUpdate(
* @NamedUpdates(value = {
* @NamedUpdate(
* name = "setTitle",
* notifyCache = false,
* update = "update topic set title = :title, postCount = :count where id = :id"),
* @NamedUpdate(
* @NamedUpdate(
* name = "setPostCount",
* notifyCache = false,
* update = "update f_topic set post_count = :postCount where id = :id"),
* @NamedUpdate(
* @NamedUpdate(
* name = "incrementPostCount",
* notifyCache = false,
* update = "update Topic set postCount = postCount + 1 where id = :id")
* //update = "update f_topic set post_count = post_count + 1 where id = :id")
* })
* @Entity
* @Table(name = "f_topic")
* @Entity
* @Table(name = "f_topic")
* public class Topic {
* ...
* }
@@ -87,7 +87,7 @@ public final class BeanList<E> extends AbstractBeanCollection<E> implements List
@Override
public void internalAddWithCheck(Object bean) {
if (list == null || !containsInstance(bean)) {
if (list == null || bean == null || !containsInstance(bean)) {
internalAdd(bean);
}
}
@@ -77,7 +77,7 @@ public final class BeanMap<K, E> extends AbstractBeanCollection<E> implements Ma
}
public void internalPutWithCheck(Object key, Object bean) {
if (map == null || !map.containsKey(key)) {
if (map == null || key == null || !map.containsKey(key)) {
internalPut(key, bean);
}
}
@@ -70,9 +70,8 @@ public final class BeanSet<E> extends AbstractBeanCollection<E> implements Set<E
@Override
public void internalAddWithCheck(Object bean) {
if (set == null || !set.contains(bean)) {
internalAdd(bean);
}
// set add() already de-dups so just add it
internalAdd(bean);
}
@Override
@@ -40,6 +40,13 @@ public class ClassLoadConfig {
return isPresent("javax.validation.constraints.NotNull");
}
/**
* Return true if jakarta validation annotations like Size and NotNull are present.
*/
public boolean isJakartaValidationAnnotationsPresent() {
return isPresent("jakarta.validation.constraints.NotNull");
}
/**
* Return true if javax PostConstruct annotation is present (maybe not in java9).
* If not we don't support PostConstruct lifecycle events.
@@ -489,7 +489,7 @@ public class DatabaseConfig {
* Should the javax.validation.constraints.NotNull enforce a notNull column in DB.
* If set to false, use io.ebean.annotation.NotNull or Column(nullable=true).
*/
private boolean useJavaxValidationNotNull = true;
private boolean useValidationNotNull = true;
/**
* Generally we want to perform L2 cache notification in the background and not impact
@@ -2809,7 +2809,7 @@ public class DatabaseConfig {
enabledL2Regions = p.get("enabledL2Regions", enabledL2Regions);
notifyL2CacheInForeground = p.getBoolean("notifyL2CacheInForeground", notifyL2CacheInForeground);
useJtaTransactionManager = p.getBoolean("useJtaTransactionManager", useJtaTransactionManager);
useJavaxValidationNotNull = p.getBoolean("useJavaxValidationNotNull", useJavaxValidationNotNull);
useValidationNotNull = p.getBoolean("useValidationNotNull", useValidationNotNull);
autoReadOnlyDataSource = p.getBoolean("autoReadOnlyDataSource", autoReadOnlyDataSource);
idGeneratorAutomatic = p.getBoolean("idGeneratorAutomatic", idGeneratorAutomatic);
@@ -3056,20 +3056,21 @@ public class DatabaseConfig {
/**
* Returns if we use javax.validation.constraints.NotNull
*/
public boolean isUseJavaxValidationNotNull() {
return useJavaxValidationNotNull;
public boolean isUseValidationNotNull() {
return useValidationNotNull;
}
/**
* Controls if Ebean should ignore <code>&x64;javax.validation.contstraints.NotNull</code>
* Controls if Ebean should ignore <code>&x64;javax.validation.contstraints.NotNull</code> or
* <code>&x64;jakarta.validation.contstraints.NotNull</code>
* with respect to generating a <code>NOT NULL</code> column.
* <p>
* Normally when Ebean sees javax NotNull annotation it means that column is defined as NOT NULL.
* Set this to <code>false</code> and the javax NotNull annotation is effectively ignored (and
* we instead use Ebean's own NotNull annotation or JPA Column(nullable=false) annotation.
*/
public void setUseJavaxValidationNotNull(boolean useJavaxValidationNotNull) {
this.useJavaxValidationNotNull = useJavaxValidationNotNull;
public void setUseValidationNotNull(boolean useValidationNotNull) {
this.useValidationNotNull = useValidationNotNull;
}
/**
@@ -15,7 +15,7 @@ package io.ebean.config;
*
* public class EbeanConfigProvider implements DatabaseConfigProvider {
*
* @Override
* @Override
* public void apply(DatabaseConfig config) {
*
* // register the entity bean classes explicitly
@@ -16,7 +16,7 @@ package io.ebean.config;
*
* public class EbeanConfigProvider implements ServerConfigProvider {
*
* @Override
* @Override
* public void apply(ServerConfig config) {
*
* // register the entity bean classes explicitly
@@ -79,7 +79,7 @@ public class DbDefaultValue {
}
/**
* This method checks & convert the {@link DbDefault#value()} to a valid SQL literal.
* This method checks and converts the {@link DbDefault#value()} to a valid SQL literal.
*
* This is mainly to quote string literals and verify integer/dates for correctness.
* <p>
@@ -13,6 +13,7 @@ public class Oracle11Platform extends OraclePlatform {
this.platform = Platform.ORACLE11;
this.columnAliasPrefix = "c";
this.sqlLimiter = new OracleRownumSqlLimiter();
this.basicSqlLimiter = new OracleRownumBasicLimiter();
dbIdentity.setIdType(IdType.SEQUENCE);
}
}
@@ -0,0 +1,35 @@
package io.ebean.config.dbplatform.oracle;
import io.ebean.config.dbplatform.BasicSqlLimiter;
/**
* Row limiter for Oracle 9,10,11 using rownum.
*/
public class OracleRownumBasicLimiter implements BasicSqlLimiter {
@Override
public String limit(String dbSql, int firstRow, int maxRows) {
if (firstRow < 1 && maxRows < 1) {
return dbSql;
}
StringBuilder sb = new StringBuilder(60 + dbSql.length());
int lastRow = maxRows;
if (lastRow > 0) {
lastRow += firstRow;
}
sb.append("select * from (select ");
if (maxRows > 0) {
sb.append("/*+ FIRST_ROWS(").append(maxRows).append(") */ ");
}
sb.append("a.*, rownum rn_ from (");
sb.append(dbSql).append(") a ");
if (lastRow > 0) {
sb.append(" where rownum <= ").append(lastRow);
}
sb.append(") ");
if (firstRow > 0) {
sb.append(" where rn_ > ").append(firstRow);
}
return sb.toString();
}
}
+2 -2
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>12.7.1-SNAPSHOT</version>
<version>12.7.3-SNAPSHOT</version>
</parent>
<!-- <parent>-->
<!-- <groupId>org.avaje</groupId>-->
@@ -26,7 +26,7 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>12.7.1-SNAPSHOT</version>
<version>12.7.3-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
@@ -11,12 +11,9 @@ import org.tests.model.basic.Order;
import static org.assertj.core.api.Assertions.assertThat;
//import org.tests.model.basic.ResetBasicData;
public class ProfileOriginTest extends BaseTestCase {
private BeanDescriptor<Order> desc = getBeanDescriptor(Order.class);
private final BeanDescriptor<Order> desc = getBeanDescriptor(Order.class);
@Test
public void buildDetail() {
+15 -15
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>12.7.1-SNAPSHOT</version>
<version>12.7.3-SNAPSHOT</version>
</parent>
<name>ebean bom</name>
@@ -81,88 +81,88 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean</artifactId>
<version>12.7.1-SNAPSHOT</version>
<version>12.7.3-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>12.7.1-SNAPSHOT</version>
<version>12.7.3-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>12.7.1-SNAPSHOT</version>
<version>12.7.3-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core-type</artifactId>
<version>12.7.1-SNAPSHOT</version>
<version>12.7.3-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-ddl-generator</artifactId>
<version>12.7.1-SNAPSHOT</version>
<version>12.7.3-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-externalmapping-api</artifactId>
<version>12.7.1-SNAPSHOT</version>
<version>12.7.3-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-externalmapping-xml</artifactId>
<version>12.7.1-SNAPSHOT</version>
<version>12.7.3-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-autotune</artifactId>
<version>12.7.1-SNAPSHOT</version>
<version>12.7.3-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>12.7.1-SNAPSHOT</version>
<version>12.7.3-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>querybean-generator</artifactId>
<version>12.7.1-SNAPSHOT</version>
<version>12.7.3-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>kotlin-querybean-generator</artifactId>
<version>12.7.1-SNAPSHOT</version>
<version>12.7.3-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-test</artifactId>
<version>12.7.1-SNAPSHOT</version>
<version>12.7.3-SNAPSHOT</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-postgis</artifactId>
<version>12.7.1-SNAPSHOT</version>
<version>12.7.3-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-redis</artifactId>
<version>12.7.1-SNAPSHOT</version>
<version>12.7.3-SNAPSHOT</version>
</dependency>
</dependencies>
+3 -8
View File
@@ -4,30 +4,25 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>12.7.1-SNAPSHOT</version>
<version>12.7.3-SNAPSHOT</version>
</parent>
<artifactId>ebean-core-type</artifactId>
<name>ebean core type</name>
<description>ebean scalar types api</description>
<properties>
<jackson-core.version>2.11.3</jackson-core.version>
<jackson-databind.version>2.11.3</jackson-databind.version>
</properties>
<dependencies>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>12.7.1-SNAPSHOT</version>
<version>12.7.3-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>${jackson-core.version}</version>
<version>${jackson.version}</version>
<optional>true</optional>
</dependency>
+12 -11
View File
@@ -3,7 +3,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>12.7.1-SNAPSHOT</version>
<version>12.7.3-SNAPSHOT</version>
</parent>
<artifactId>ebean-core</artifactId>
@@ -18,11 +18,6 @@
<tag>ebean-parent-12.6.5</tag>
</scm>
<properties>
<jackson-core.version>2.11.3</jackson-core.version>
<jackson-databind.version>2.11.3</jackson-databind.version>
</properties>
<profiles>
<profile>
<!-- Note: to use this profile, you need to download manually the db2jcc4 driver.
@@ -92,19 +87,19 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>12.7.1-SNAPSHOT</version>
<version>12.7.3-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core-type</artifactId>
<version>12.7.1-SNAPSHOT</version>
<version>12.7.3-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-externalmapping-api</artifactId>
<version>12.7.1-SNAPSHOT</version>
<version>12.7.3-SNAPSHOT</version>
</dependency>
<dependency>
@@ -138,6 +133,12 @@
<version>1.1.0.Final</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>jakarta.validation</groupId>
<artifactId>jakarta.validation-api</artifactId>
<version>3.0.0</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>joda-time</groupId>
@@ -157,14 +158,14 @@
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>${jackson-core.version}</version>
<version>${jackson.version}</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>${jackson-databind.version}</version>
<version>${jackson.version}</version>
<optional>true</optional>
</dependency>
@@ -20,6 +20,7 @@ import io.ebeaninternal.server.deploy.TableJoin;
import io.ebeaninternal.server.query.CancelableQuery;
import io.ebeaninternal.server.querydefn.NaturalKeyBindParam;
import io.ebeaninternal.server.querydefn.OrmQueryDetail;
import io.ebeaninternal.server.querydefn.OrmQueryProperties;
import io.ebeaninternal.server.querydefn.OrmUpdateProperties;
import io.ebeaninternal.server.rawsql.SpiRawSql;
@@ -30,7 +31,7 @@ import java.util.Set;
/**
* Object Relational query - Internal extension to Query object.
*/
public interface SpiQuery<T> extends Query<T>, TxnProfileEventCodes {
public interface SpiQuery<T> extends Query<T>, SpiQueryFetch, TxnProfileEventCodes {
enum Mode {
NORMAL(false), LAZYLOAD_MANY(false), LAZYLOAD_BEAN(true), REFRESH_BEAN(true);
@@ -289,6 +290,16 @@ public interface SpiQuery<T> extends Query<T>, TxnProfileEventCodes {
*/
boolean selectAllForLazyLoadProperty();
/**
* Set the select properties.
*/
void selectProperties(OrmQueryProperties other);
/**
* Set the fetch properties for the given path.
*/
void fetchProperties(String path, OrmQueryProperties other);
/**
* Set the on a secondary query given the label, relativePath and profile location of the parent query.
*/
@@ -0,0 +1,22 @@
package io.ebeaninternal.api;
import io.ebean.FetchConfig;
import java.util.Set;
/**
* Query select and fetch properties (that avoids parsing).
*/
public interface SpiQueryFetch {
/**
* Specify the select properties.
*/
void selectProperties(Set<String> properties);
/**
* Specify the fetch properties for the given path.
*/
void fetchProperties(String name, Set<String> properties, FetchConfig config);
}
@@ -727,7 +727,7 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
String[] cols = indexDef.getColumns();
BeanProperty[] props = new BeanProperty[cols.length];
for (int i = 0; i < cols.length; i++) {
String propName = findBeanPath("", cols[i]);
String propName = findBeanPath("", "", cols[i]);
if (propName == null) {
return;
}
@@ -2472,13 +2472,16 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
/**
* Return the property path given the db table and column.
*/
public String findBeanPath(String tableName, String columnName) {
if (tableName.isEmpty() || tableName.equalsIgnoreCase(baseTable)) {
public String findBeanPath(String schemaName, String tableName, String columnName) {
if (matchBaseTable(schemaName, tableName)) {
return columnPath.get(columnName);
}
BeanPropertyAssoc<?> assocProperty = tablePath.get(tableName);
if (assocProperty == null) {
assocProperty = tablePath.get(schemaName + "." + tableName);
}
if (assocProperty != null) {
String relativePath = assocProperty.getTargetDescriptor().findBeanPath(tableName, columnName);
String relativePath = assocProperty.getTargetDescriptor().findBeanPath(schemaName, tableName, columnName);
if (relativePath != null) {
return SplitName.add(assocProperty.getName(), relativePath);
}
@@ -2486,6 +2489,12 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
return null;
}
private boolean matchBaseTable(String schemaName, String tableName) {
return tableName.isEmpty()
|| baseTable.equalsIgnoreCase(tableName)
|| baseTable.equalsIgnoreCase(schemaName + "." + tableName);
}
/**
* Return a 'dynamic property' used to read a formula.
*/
@@ -609,7 +609,8 @@ public abstract class BeanPropertyAssoc<T> extends BeanProperty implements STree
String msg = "Error with the Join on [" + getFullBeanName()
+ "]. Could not find the matching foreign key for [" + matchColumn + "] in table[" + searchTable + "]?"
+ " Perhaps using a @JoinColumn with the name/referencedColumnName attributes swapped?";
+ " Perhaps using a @JoinColumn with the name/referencedColumnName attributes swapped? "
+ " or a @JoinColumn needs an explicit referencedColumnName specified?";
throw new PersistenceException(msg);
}
}
@@ -34,7 +34,6 @@ import javax.persistence.EmbeddedId;
import javax.persistence.FetchType;
import javax.persistence.Id;
import javax.persistence.Version;
import javax.validation.constraints.Size;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Type;
@@ -1140,15 +1139,6 @@ public class DeployBeanProperty {
return result;
}
public List<Size> getMetaAnnotationSize() {
final List<Size> size = getMetaAnnotations(Size.class);
final List<Size.List> lists = getMetaAnnotations(Size.List.class);
for (Size.List list : lists) {
Collections.addAll(size, list.value());
}
return size;
}
public Formula getMetaAnnotationFormula(Platform platform) {
Formula fallback = null;
for (Annotation ann : metaAnnotations) {
@@ -4,13 +4,11 @@ import io.ebean.annotation.DbForeignKey;
import io.ebean.annotation.FetchPreference;
import io.ebean.annotation.TenantId;
import io.ebean.annotation.Where;
import io.ebean.config.BeanNotRegisteredException;
import io.ebean.config.NamingConvention;
import io.ebeaninternal.server.deploy.BeanDescriptorManager;
import io.ebeaninternal.server.deploy.BeanTable;
import io.ebeaninternal.server.deploy.PropertyForeignKey;
import io.ebeaninternal.server.deploy.meta.DeployBeanProperty;
import io.ebeaninternal.server.deploy.meta.DeployBeanPropertyAssoc;
import io.ebeaninternal.server.deploy.meta.DeployBeanPropertyAssocOne;
import io.ebeaninternal.server.deploy.meta.DeployTableJoinColumn;
import io.ebeaninternal.server.query.SqlJoinType;
@@ -28,7 +26,6 @@ import javax.persistence.JoinTable;
import javax.persistence.ManyToOne;
import javax.persistence.OneToOne;
import javax.persistence.PrimaryKeyJoinColumn;
import javax.validation.constraints.NotNull;
/**
* Read the deployment annotations for Associated One beans.
@@ -117,13 +114,10 @@ public class AnnotationAssocOnes extends AnnotationAssoc {
if (nonNull != null) {
prop.setNullable(false);
}
if (validationAnnotations) {
NotNull notNull = get(prop, NotNull.class);
if (notNull != null && isEbeanValidationGroups(notNull.groups())) {
prop.setNullable(false);
// overrides optional attribute of ManyToOne etc
prop.getTableJoin().setType(SqlJoinType.INNER);
}
if (readConfig.isValidationNotNull(prop)) {
// overrides optional attribute of ManyToOne etc
prop.setNullable(false);
prop.getTableJoin().setType(SqlJoinType.INNER);
}
// check for manually defined joins
@@ -69,8 +69,6 @@ import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import javax.persistence.Transient;
import javax.persistence.Version;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import java.sql.Types;
import java.util.Set;
import java.util.UUID;
@@ -258,29 +256,19 @@ public class AnnotationFields extends AnnotationParser {
}
private void initValidation(DeployBeanProperty prop) {
NotNull notNull = get(prop, NotNull.class);
if (notNull != null && isEbeanValidationGroups(notNull.groups())) {
// Not null on all validation groups so enable
// DDL generation of Not Null Constraint
if (readConfig.isValidationNotNull(prop)) {
prop.setNullable(false);
}
if (!prop.isLob()) {
// take the max size of all @Size annotations
int maxSize = -1;
for (Size size : prop.getMetaAnnotationSize()) {
if (size.max() < Integer.MAX_VALUE) {
maxSize = Math.max(maxSize, size.max());
}
}
if (maxSize != -1) {
int maxSize = readConfig.maxValidationSize(prop);
if (maxSize > 0) {
prop.setDbLength(maxSize);
}
}
}
private void initTenantId(DeployBeanProperty prop) {
if (validationAnnotations) {
if (readConfig.checkValidationAnnotations()) {
initValidation(prop);
}
if (has(prop, TenantId.class)) {
@@ -9,7 +9,6 @@ import io.ebeaninternal.server.deploy.meta.DeployBeanPropertyAssocOne;
import javax.persistence.AttributeOverride;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.validation.groups.Default;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
@@ -26,14 +25,11 @@ public abstract class AnnotationParser extends AnnotationBase {
final Class<?> beanType;
final boolean validationAnnotations;
final ReadAnnotationConfig readConfig;
AnnotationParser(DeployBeanInfo<?> info, ReadAnnotationConfig readConfig) {
super(info.getUtil());
this.readConfig = readConfig;
this.validationAnnotations = readConfig.isJavaxValidationAnnotations();
this.info = info;
this.beanType = info.getDescriptor().getBeanType();
this.descriptor = info.getDescriptor();
@@ -127,17 +123,6 @@ public abstract class AnnotationParser extends AnnotationBase {
}
}
/**
* Return true if the validation groups are {@link Default} (respectively empty)
* can be applied to DDL generation.
*/
boolean isEbeanValidationGroups(Class<?>[] groups) {
if (!util.isUseJavaxValidationNotNull()) {
return false;
}
return groups.length == 0 || groups.length == 1 && Default.class.isAssignableFrom(groups[0]);
}
String[] convertColumnNames(String[] columnNames) {
for (int i = 0; i < columnNames.length; i++) {
columnNames[i] = databasePlatform.convertQuotedIdentifiers(columnNames[i]);
@@ -57,7 +57,7 @@ public class DeployUtil {
private final Encryptor bytesEncryptor;
private final boolean useJavaxValidationNotNull;
private final boolean useValidationNotNull;
public DeployUtil(TypeManager typeMgr, DatabaseConfig config) {
this.typeManager = typeMgr;
@@ -67,7 +67,7 @@ public class DeployUtil {
this.encryptKeyManager = config.getEncryptKeyManager();
Encryptor be = config.getEncryptor();
this.bytesEncryptor = be != null ? be : new SimpleAesEncryptor();
this.useJavaxValidationNotNull = config.isUseJavaxValidationNotNull();
this.useValidationNotNull = config.isUseValidationNotNull();
}
public TypeManager getTypeManager() {
@@ -286,8 +286,8 @@ public class DeployUtil {
return type.equals(String.class);
}
boolean isUseJavaxValidationNotNull() {
return useJavaxValidationNotNull;
boolean isUseValidationNotNull() {
return useValidationNotNull;
}
/**
@@ -1,11 +0,0 @@
package io.ebeaninternal.server.deploy.parse;
import javax.validation.constraints.Size;
class InitMetaValidationAnnotation {
static void init(ReadAnnotationConfig readConfig) {
readConfig.addMetaAnnotation(Size.class);
readConfig.addMetaAnnotation(Size.List.class);
}
}
@@ -3,8 +3,10 @@ package io.ebeaninternal.server.deploy.parse;
import io.ebean.annotation.Aggregation;
import io.ebean.annotation.Formula;
import io.ebean.annotation.Where;
import io.ebean.config.ClassLoadConfig;
import io.ebean.config.DatabaseConfig;
import io.ebeaninternal.server.deploy.generatedproperty.GeneratedPropertyFactory;
import io.ebeaninternal.server.deploy.meta.DeployBeanProperty;
import javax.persistence.Column;
import java.util.HashSet;
@@ -21,9 +23,12 @@ class ReadAnnotationConfig {
private final boolean disableL2Cache;
private final boolean eagerFetchLobs;
private final boolean javaxValidationAnnotations;
private final boolean jakartaValidationAnnotations;
private final boolean jacksonAnnotations;
private final boolean idGeneratorAutomatic;
private final boolean useValidationNotNull;
private final ReadValidationAnnotations javaxValidation;
private final ReadValidationAnnotations jakartaValidation;
private final Set<Class<?>> metaAnnotations = new HashSet<>();
ReadAnnotationConfig(GeneratedPropertyFactory generatedPropFactory, String asOfViewSuffix, String versionsBetweenSuffix, DatabaseConfig config) {
@@ -33,20 +38,32 @@ class ReadAnnotationConfig {
this.disableL2Cache = config.isDisableL2Cache();
this.eagerFetchLobs = config.isEagerFetchLobs();
this.idGeneratorAutomatic = config.isIdGeneratorAutomatic();
this.javaxValidationAnnotations = generatedPropFactory.getClassLoadConfig().isJavaxValidationAnnotationsPresent();
this.jacksonAnnotations = generatedPropFactory.getClassLoadConfig().isJacksonAnnotationsPresent();
this.useValidationNotNull = config.isUseValidationNotNull();
ClassLoadConfig classLoadConfig = generatedPropFactory.getClassLoadConfig();
this.javaxValidationAnnotations = classLoadConfig.isJavaxValidationAnnotationsPresent();
this.jakartaValidationAnnotations = classLoadConfig.isJakartaValidationAnnotationsPresent();
this.jacksonAnnotations = classLoadConfig.isJacksonAnnotationsPresent();
this.metaAnnotations.add(Column.class);
this.metaAnnotations.add(Formula.class);
this.metaAnnotations.add(Formula.List.class);
this.metaAnnotations.add(Where.class);
this.metaAnnotations.add(Where.List.class);
this.metaAnnotations.add(Aggregation.class);
this.javaxValidation = javaxValidationAnnotations ? new ReadValidationAnnotationsJavax(this) : null;
this.jakartaValidation = jakartaValidationAnnotations ? new ReadValidationAnnotationsJakarta(this) : null;
if (jacksonAnnotations) {
InitMetaJacksonAnnotation.init(this);
}
}
public void addMetaAnnotation(Class<?> annotation) {
void addMetaAnnotation(Class<?> annotation) {
metaAnnotations.add(annotation);
}
boolean checkValidationAnnotations() {
return javaxValidationAnnotations || jakartaValidationAnnotations;
}
GeneratedPropertyFactory getGeneratedPropFactory() {
return generatedPropFactory;
}
@@ -71,10 +88,6 @@ class ReadAnnotationConfig {
return idGeneratorAutomatic;
}
boolean isJavaxValidationAnnotations() {
return javaxValidationAnnotations;
}
boolean isJacksonAnnotations() {
return jacksonAnnotations;
}
@@ -82,4 +95,35 @@ class ReadAnnotationConfig {
public Set<Class<?>> getMetaAnnotations() {
return metaAnnotations;
}
/**
* Return true if a NotNull validation annotation is on the property.
*/
boolean isValidationNotNull(DeployBeanProperty property) {
if (!useValidationNotNull) {
return false;
}
if (javaxValidation != null && javaxValidation.isValidationNotNull(property)) {
return true;
}
if (jakartaValidation != null && jakartaValidation.isValidationNotNull(property)) {
return true;
}
return false;
}
/**
* Return the max size of all validation @Size annotations.
*/
int maxValidationSize(DeployBeanProperty prop) {
int maxSize = 0;
if (javaxValidation != null) {
maxSize = Math.max(maxSize, javaxValidation.maxSize(prop));
}
if (jakartaValidation != null) {
maxSize = Math.max(maxSize, jakartaValidation.maxSize(prop));
}
return maxSize;
}
}
@@ -14,12 +14,6 @@ public class ReadAnnotations {
public ReadAnnotations(GeneratedPropertyFactory generatedPropFactory, String asOfViewSuffix, String versionsBetweenSuffix, DatabaseConfig config) {
this.readConfig = new ReadAnnotationConfig(generatedPropFactory, asOfViewSuffix, versionsBetweenSuffix, config);
if (readConfig.isJavaxValidationAnnotations()) {
InitMetaValidationAnnotation.init(readConfig);
}
if (readConfig.isJacksonAnnotations()) {
InitMetaJacksonAnnotation.init(readConfig);
}
}
/**
@@ -0,0 +1,19 @@
package io.ebeaninternal.server.deploy.parse;
import io.ebeaninternal.server.deploy.meta.DeployBeanProperty;
/**
* Reads validation NotNull and Size annotations for mapping.
*/
interface ReadValidationAnnotations {
/**
* Return true if the property has a NotNull validation annotation.
*/
boolean isValidationNotNull(DeployBeanProperty property);
/**
* Return the max value of the Size validation annotations on the property.
*/
int maxSize(DeployBeanProperty property);
}
@@ -0,0 +1,51 @@
package io.ebeaninternal.server.deploy.parse;
import io.ebean.util.AnnotationUtil;
import io.ebeaninternal.server.deploy.meta.DeployBeanProperty;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import jakarta.validation.groups.Default;
import java.util.Collections;
import java.util.List;
/**
* Jakarta validation annotations reader.
*/
class ReadValidationAnnotationsJakarta implements ReadValidationAnnotations {
ReadValidationAnnotationsJakarta(ReadAnnotationConfig readConfig) {
readConfig.addMetaAnnotation(Size.class);
readConfig.addMetaAnnotation(Size.List.class);
}
@Override
public boolean isValidationNotNull(DeployBeanProperty property) {
NotNull notNull = AnnotationUtil.get(property.getField(), NotNull.class);
return (notNull != null && isEbeanValidationGroups(notNull.groups()));
}
private boolean isEbeanValidationGroups(Class<?>[] groups) {
return groups.length == 0 || groups.length == 1 && Default.class.isAssignableFrom(groups[0]);
}
@Override
public int maxSize(DeployBeanProperty property) {
int maxSize = 0;
for (Size size : getMetaAnnotationJavaxSize(property)) {
if (size.max() < Integer.MAX_VALUE) {
maxSize = Math.max(maxSize, size.max());
}
}
return maxSize;
}
private List<Size> getMetaAnnotationJavaxSize(DeployBeanProperty prop) {
final List<Size> size = prop.getMetaAnnotations(Size.class);
final List<Size.List> lists = prop.getMetaAnnotations(Size.List.class);
for (Size.List list : lists) {
Collections.addAll(size, list.value());
}
return size;
}
}
@@ -0,0 +1,52 @@
package io.ebeaninternal.server.deploy.parse;
import io.ebean.util.AnnotationUtil;
import io.ebeaninternal.server.deploy.meta.DeployBeanProperty;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import javax.validation.groups.Default;
import java.util.Collections;
import java.util.List;
/**
* Javax validation annotations reader.
*/
class ReadValidationAnnotationsJavax implements ReadValidationAnnotations {
ReadValidationAnnotationsJavax(ReadAnnotationConfig readConfig) {
readConfig.addMetaAnnotation(Size.class);
readConfig.addMetaAnnotation(Size.List.class);
}
@Override
public boolean isValidationNotNull(DeployBeanProperty property) {
NotNull notNull = AnnotationUtil.get(property.getField(), NotNull.class);
return (notNull != null && isEbeanValidationGroups(notNull.groups()));
}
private boolean isEbeanValidationGroups(Class<?>[] groups) {
return groups.length == 0 || groups.length == 1 && Default.class.isAssignableFrom(groups[0]);
}
@Override
public int maxSize(DeployBeanProperty prop) {
int maxSize = 0;
for (Size size : getMetaAnnotationJavaxSize(prop)) {
if (size.max() < Integer.MAX_VALUE) {
maxSize = Math.max(maxSize, size.max());
}
}
return maxSize;
}
private List<Size> getMetaAnnotationJavaxSize(DeployBeanProperty prop) {
final List<Size> size = prop.getMetaAnnotations(Size.class);
final List<Size.List> lists = prop.getMetaAnnotations(Size.List.class);
for (Size.List list : lists) {
Collections.addAll(size, list.value());
}
return size;
}
}
@@ -142,7 +142,7 @@ public class CQuery<T> implements DbReadContext, CancelableQuery, SpiProfileTran
* Tree that knows how to build the master and detail beans from the
* resultSet.
*/
private final SqlTreeNode rootNode;
private final SqlTreeRoot rootNode;
/**
* For master detail query.
@@ -444,13 +444,13 @@ public class CQuery<T> implements DbReadContext, CancelableQuery, SpiProfileTran
if (manyProperty == null) {
// only single resultSet row required to build object so we are done
// read a single resultSet row into single bean
nextBean = rootNode.load(this, null, null);
nextBean = rootNode.load(this);
return true;
}
if (nextBean == null) {
// very first read
nextBean = rootNode.load(this, null, null);
nextBean = rootNode.load(this);
} else {
// nextBean set to previously read currentBean
nextBean = currentBean;
@@ -478,7 +478,7 @@ public class CQuery<T> implements DbReadContext, CancelableQuery, SpiProfileTran
* is different to the nextBean (false if we need to read more rows).
*/
private boolean checkForDifferentBean() throws SQLException {
currentBean = rootNode.load(this, null, null);
currentBean = rootNode.load(this);
return currentBean != nextBean;
}
@@ -442,9 +442,10 @@ class CQueryBuilder {
int cols = 1 + metaData.getColumnCount();
List<String> propertyNames = new ArrayList<>(cols - 1);
for (int i = 1; i < cols; i++) {
String schemaName = metaData.getSchemaName(i).toLowerCase();
String tableName = metaData.getTableName(i).toLowerCase();
String columnName = metaData.getColumnName(i).toLowerCase();
String path = desc.findBeanPath(tableName, columnName);
String path = desc.findBeanPath(schemaName, tableName, columnName);
if (path != null) {
propertyNames.add(path);
} else {
@@ -11,6 +11,8 @@ import io.ebeaninternal.server.querydefn.SpiFetchGroup;
*/
class DFetchGroupBuilder<T> implements FetchGroupBuilder<T> {
private static final FetchConfig DEFAULT_FETCH = FetchConfig.ofDefault();
private static final FetchConfig FETCH_CACHE = FetchConfig.ofCache();
private static final FetchConfig FETCH_QUERY = FetchConfig.ofQuery();
@@ -31,13 +33,13 @@ class DFetchGroupBuilder<T> implements FetchGroupBuilder<T> {
@Override
public FetchGroupBuilder<T> fetch(String path) {
detail.fetch(path, null, null);
detail.fetchProperties(path, null, DEFAULT_FETCH);
return this;
}
@Override
public FetchGroupBuilder<T> fetch(String path, FetchGroup nestedGroup) {
return fetchNested(path, nestedGroup, null);
return fetchNested(path, nestedGroup, DEFAULT_FETCH);
}
@Override
@@ -51,7 +53,6 @@ class DFetchGroupBuilder<T> implements FetchGroupBuilder<T> {
}
private FetchGroupBuilder<T> fetchNested(String path, FetchGroup nestedGroup, FetchConfig fetchConfig) {
OrmQueryDetail nestedDetail = ((SpiFetchGroup) nestedGroup).underlying();
detail.addNested(path, nestedDetail, fetchConfig);
return this;
@@ -59,25 +60,25 @@ class DFetchGroupBuilder<T> implements FetchGroupBuilder<T> {
@Override
public FetchGroupBuilder<T> fetchQuery(String path) {
detail.fetch(path, null, FETCH_QUERY);
detail.fetchProperties(path, null, FETCH_QUERY);
return this;
}
@Override
public FetchGroupBuilder<T> fetchCache(String path) {
detail.fetch(path, null, FETCH_CACHE);
detail.fetchProperties(path, null, FETCH_CACHE);
return this;
}
@Override
public FetchGroupBuilder<T> fetchLazy(String path) {
detail.fetch(path, null, FETCH_LAZY);
detail.fetchProperties(path, null, FETCH_LAZY);
return this;
}
@Override
public FetchGroupBuilder<T> fetch(String path, String properties) {
detail.fetch(path, properties, null);
detail.fetch(path, properties, DEFAULT_FETCH);
return this;
}
@@ -25,6 +25,7 @@ import io.ebean.Transaction;
import io.ebean.UpdateQuery;
import io.ebean.Version;
import io.ebean.service.SpiFetchGroupQuery;
import io.ebeaninternal.api.SpiQueryFetch;
import io.ebeaninternal.server.querydefn.OrmQueryDetail;
import io.ebeaninternal.server.querydefn.SpiFetchGroup;
@@ -43,7 +44,7 @@ import java.util.stream.Stream;
/**
* Implementation of FetchGroup query for use to create FetchGroup via query beans.
*/
class DefaultFetchGroupQuery<T> implements SpiFetchGroupQuery<T> {
class DefaultFetchGroupQuery<T> implements SpiFetchGroupQuery<T>, SpiQueryFetch {
private static final FetchConfig FETCH_CACHE = FetchConfig.ofCache();
@@ -628,4 +629,14 @@ class DefaultFetchGroupQuery<T> implements SpiFetchGroupQuery<T> {
public Query<T> orderById(boolean orderById) {
throw new RuntimeException("EB102: Only select() and fetch() clause is allowed on FetchGroup");
}
@Override
public void selectProperties(Set<String> props) {
detail.selectProperties(props);
}
@Override
public void fetchProperties(String property, Set<String> columns, FetchConfig config) {
detail.fetchProperties(property, columns, config);
}
}
@@ -122,8 +122,8 @@ class SqlTree {
return inheritanceWhereSql;
}
SqlTreeNode getRootNode() {
return rootNode;
SqlTreeRoot getRootNode() {
return (SqlTreeRoot)rootNode;
}
/**
@@ -73,11 +73,6 @@ interface SqlTreeNode {
*/
EntityBean load(DbReadContext ctx, EntityBean localBean, EntityBean contextBean) throws SQLException;
/**
* Load a version of a @History bean with effective dates.
*/
<T> Version<T> loadVersion(DbReadContext ctx) throws SQLException;
/**
* Return true if the query has a many join.
*/
@@ -1,6 +1,5 @@
package io.ebeaninternal.server.query;
import io.ebean.Version;
import io.ebean.bean.BeanCollection;
import io.ebean.bean.EntityBean;
import io.ebean.bean.EntityBeanIntercept;
@@ -16,7 +15,6 @@ import io.ebeaninternal.server.deploy.TableJoin;
import io.ebeaninternal.server.deploy.id.IdBinder;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -56,6 +54,7 @@ class SqlTreeNodeBean implements SqlTreeNode {
* False if report bean and has no id property.
*/
final boolean readId;
private final boolean readIdNormal;
private final boolean disableLazyLoad;
@@ -88,7 +87,6 @@ class SqlTreeNodeBean implements SqlTreeNode {
*/
SqlTreeNodeBean(String prefix, STreePropertyAssoc beanProp, SqlTreeProperties props,
List<SqlTreeNode> myChildren, boolean withId, SpiQuery.TemporalMode temporalMode, boolean disableLazyLoad) {
this(prefix, beanProp, beanProp.target(), props, myChildren, withId, null, temporalMode, disableLazyLoad);
}
@@ -120,7 +118,8 @@ class SqlTreeNodeBean implements SqlTreeNode {
boolean aggregationRoot = props.isAggregationRoot();
// the bean has an Id property and we want to use it
this.readId = !aggregationRoot && withId && desc.hasId();
this.disableLazyLoad = disableLazyLoad || !readId || desc.isRawSqlBased() || temporalVersions;
this.readIdNormal = readId && !temporalVersions;
this.disableLazyLoad = disableLazyLoad || !readIdNormal || desc.isRawSqlBased();
this.partialObject = props.isPartialObject();
this.properties = props.getProps();
this.children = myChildren == null ? NO_CHILDREN : myChildren.toArray(new SqlTreeNode[0]);
@@ -192,26 +191,12 @@ class SqlTreeNodeBean implements SqlTreeNode {
}
}
/**
* Read the version bean.
*/
@Override
@SuppressWarnings("unchecked")
public <T> Version<T> loadVersion(DbReadContext ctx) throws SQLException {
// read the sys period lower and upper bounds
// these are always the first 2 columns in the resultSet
Timestamp start = ctx.getDataReader().getTimestamp();
Timestamp end = ctx.getDataReader().getTimestamp();
T bean = (T) load(ctx, null, null);
return new Version<>(bean, start, end);
}
/**
* Load that takes into account inheritance.
*/
private class LoadInherit extends Load {
LoadInherit(DbReadContext ctx, EntityBean parentBean) {
private LoadInherit(DbReadContext ctx, EntityBean parentBean) {
super(ctx, parentBean);
}
@@ -232,10 +217,8 @@ class SqlTreeNodeBean implements SqlTreeNode {
@Override
void loadProperties() {
// take account of inheritance and due to subclassing approach
// need to get a 'local' version of the property
// take account of inheritance
for (STreeProperty property : properties) {
// get a local version of the BeanProperty
localDesc.inheritanceLoad(sqlBeanLoad, property, ctx);
}
}
@@ -244,7 +227,7 @@ class SqlTreeNodeBean implements SqlTreeNode {
/**
* Load a bean instance.
*/
private class Load {
class Load {
final DbReadContext ctx;
final EntityBean parentBean;
@@ -262,12 +245,12 @@ class SqlTreeNodeBean implements SqlTreeNode {
SqlBeanLoad sqlBeanLoad;
boolean lazyLoadMany;
Load(DbReadContext ctx, EntityBean parentBean) {
private Load(DbReadContext ctx, EntityBean parentBean) {
this.ctx = ctx;
this.parentBean = parentBean;
}
void initLazyParent() throws SQLException {
private void initLazyParent() throws SQLException {
if (lazyLoadParentIdBinder != null) {
lazyLoadParentId = lazyLoadParentIdBinder.read(ctx);
}
@@ -279,12 +262,12 @@ class SqlTreeNodeBean implements SqlTreeNode {
localIdBinder = idBinder;
}
void initPersistenceContext() {
private void initPersistenceContext() {
queryMode = ctx.getQueryMode();
persistenceContext = (!readId || temporalVersions) ? null : ctx.getPersistenceContext();
persistenceContext = (!readIdNormal) ? null : ctx.getPersistenceContext();
}
void readId() throws SQLException {
private void readId() throws SQLException {
if (readId) {
id = localIdBinder.readSet(ctx, localBean);
if (id == null) {
@@ -325,7 +308,7 @@ class SqlTreeNodeBean implements SqlTreeNode {
}
}
void initSqlLoadBean() {
private void initSqlLoadBean() {
ctx.setCurrentPrefix(prefix, pathMap);
ctx.propagateState(localBean);
sqlBeanLoad = new SqlBeanLoad(ctx, localType, localBean, queryMode);
@@ -337,33 +320,29 @@ class SqlTreeNodeBean implements SqlTreeNode {
}
}
void loadChildren() throws SQLException {
//boolean lazyLoadMany = false;
private void loadChildren() throws SQLException {
if (localBean == null && queryMode == Mode.LAZYLOAD_MANY) {
// batch lazy load many into existing contextBean
localBean = contextBean;
lazyLoadMany = true;
}
// recursively continue reading...
for (SqlTreeNode aChildren : children) {
// read each child... and let them set their
// values back to this localBean
aChildren.load(ctx, localBean, contextBean);
for (SqlTreeNode child : children) {
child.load(ctx, localBean, contextBean);
}
}
boolean isLazyLoadManyRoot() {
private boolean isLazyLoadManyRoot() {
return queryMode == Mode.LAZYLOAD_MANY && isRoot();
}
EntityBean getContextBean() {
private EntityBean getContextBean() {
return contextBean;
}
void postLoad() {
private void postLoad() {
if (!lazyLoadMany && localBean != null) {
ctx.setCurrentPrefix(prefix, pathMap);
if (readId && !temporalVersions) {
if (readIdNormal) {
createListProxies();
}
if (temporalMode == SpiQuery.TemporalMode.DRAFT) {
@@ -384,7 +363,6 @@ class SqlTreeNodeBean implements SqlTreeNode {
if (disableLazyLoad) {
// bean does not have an Id or is SqlSelect based
ebi.setDisableLazyLoad(true);
} else if (partialObject) {
if (readId) {
// register for lazy loading
@@ -408,7 +386,6 @@ class SqlTreeNodeBean implements SqlTreeNode {
private void createListProxies() {
STreePropertyAssocMany fetchedMany = ctx.getManyProperty();
boolean forceNewReference = queryMode == Mode.REFRESH_BEAN;
// load the List/Set/Map proxy objects (deferred fetching of lists)
for (STreePropertyAssocMany many : localDesc.propsMany()) {
if (many != fetchedMany) {
// create a proxy for the many (deferred fetching)
@@ -425,21 +402,20 @@ class SqlTreeNodeBean implements SqlTreeNode {
}
}
void setBeanToParent() {
private void setBeanToParent() {
if (parentBean != null) {
// set this back to the parentBean
nodeBeanProp.setValue(parentBean, contextBean);
}
}
EntityBean complete() {
if (!readId || temporalVersions) {
private EntityBean complete() {
if (!readIdNormal) {
// a bean with no Id (never found in context)
if (lazyLoadParentId != null) {
ctx.setLazyLoadedChildBean(localBean, lazyLoadParentId);
}
return localBean;
} else {
if (lazyLoadParentId != null) {
ctx.setLazyLoadedChildBean(contextBean, lazyLoadParentId);
@@ -448,7 +424,7 @@ class SqlTreeNodeBean implements SqlTreeNode {
}
}
void initialise() throws SQLException {
private void initialise() throws SQLException {
initLazyParent();
initBeanType();
initPersistenceContext();
@@ -457,6 +433,27 @@ class SqlTreeNodeBean implements SqlTreeNode {
loadProperties();
loadChildren();
}
/**
* Perform the load returning the loaded bean.
*/
EntityBean perform() throws SQLException {
initialise();
if (isLazyLoadManyRoot()) {
return getContextBean();
}
postLoad();
setBeanToParent();
return complete();
}
/**
* Return true if this bean was already in the context. If already in the
* context we need to check if it is already contained in the collection.
*/
boolean isContextBean() {
return localBean == null;
}
}
/**
@@ -464,14 +461,14 @@ class SqlTreeNodeBean implements SqlTreeNode {
*/
@Override
public EntityBean load(DbReadContext ctx, EntityBean parentBean, EntityBean contextParent) throws SQLException {
Load load = (inheritInfo != null) ? new LoadInherit(ctx, parentBean) : new Load(ctx, parentBean);
load.initialise();
if (load.isLazyLoadManyRoot()) {
return load.getContextBean();
}
load.postLoad();
load.setBeanToParent();
return load.complete();
return createLoad(ctx, parentBean).perform();
}
/**
* Create the loader with or without inheritance.
*/
Load createLoad(DbReadContext ctx, EntityBean parentBean) {
return (inheritInfo != null) ? new LoadInherit(ctx, parentBean) : new Load(ctx, parentBean);
}
@Override
@@ -489,8 +486,8 @@ class SqlTreeNodeBean implements SqlTreeNode {
property.appendSelect(ctx, subQuery);
}
}
for (SqlTreeNode aChildren : children) {
aChildren.appendGroupBy(ctx, subQuery);
for (SqlTreeNode child : children) {
child.appendGroupBy(ctx, subQuery);
}
ctx.popTableAlias();
ctx.popJoin();
@@ -527,11 +524,8 @@ class SqlTreeNodeBean implements SqlTreeNode {
appendSelectId(ctx, idBinder.getBeanProperty());
}
appendSelect(ctx, subQuery, properties);
for (SqlTreeNode aChildren : children) {
// read each child... and let them set their
// values back to this localBean
aChildren.appendSelect(ctx, subQuery);
for (SqlTreeNode child : children) {
child.appendSelect(ctx, subQuery);
}
ctx.popTableAlias();
ctx.popJoin();
@@ -579,10 +573,8 @@ class SqlTreeNodeBean implements SqlTreeNode {
}
}
appendExtraWhere(ctx);
for (SqlTreeNode aChildren : children) {
// recursively add to the where clause any
// fixed predicates (extraWhere etc)
aChildren.appendWhere(ctx);
for (SqlTreeNode child : children) {
child.appendWhere(ctx);
}
}
@@ -618,10 +610,9 @@ class SqlTreeNodeBean implements SqlTreeNode {
property.appendFrom(ctx, joinType);
}
for (SqlTreeNode aChildren : children) {
aChildren.appendFrom(ctx, joinType);
for (SqlTreeNode child : children) {
child.appendFrom(ctx, joinType);
}
ctx.popTableAlias();
ctx.popJoin();
}
@@ -646,8 +637,8 @@ class SqlTreeNodeBean implements SqlTreeNode {
if (intersectionAsOfTableAlias) {
query.incrementAsOfTableCount();
}
for (SqlTreeNode aChildren : children) {
aChildren.addAsOfTableAlias(query);
for (SqlTreeNode child : children) {
child.addAsOfTableAlias(query);
}
}
@@ -178,14 +178,6 @@ class SqlTreeNodeExtraJoin implements SqlTreeNode {
return null;
}
/**
* Does nothing.
*/
@Override
public <T> Version<T> loadVersion(DbReadContext ctx) {
return null;
}
@Override
public boolean hasMany() {
return manyJoin;
@@ -99,12 +99,6 @@ class SqlTreeNodeFormulaWhereJoin implements SqlTreeNode {
return null;
}
@Override
public <T> Version<T> loadVersion(DbReadContext ctx) {
// nothing to do here
return null;
}
@Override
public boolean hasMany() {
return true;
@@ -18,6 +18,25 @@ final class SqlTreeNodeManyRoot extends SqlTreeNodeBean {
this.manyProp = prop;
}
@Override
public boolean hasMany() {
return true;
}
@Override
public EntityBean load(DbReadContext cquery, EntityBean parentBean, EntityBean contextParent) throws SQLException {
// pass in null for parentBean because added to a collection rather than set to the parentBean
SqlTreeNodeBean.Load load = createLoad(cquery, null);
EntityBean detailBean = load.perform();
if (contextParent != null) {
// Add to the collection and initialise collection if needed
// A null detailBean may initialise an empty collection
// Check for bean existing in collection based on load.isContextBean()
manyProp.addBeanToCollectionWithCreate(contextParent, detailBean, load.isContextBean());
}
return detailBean;
}
/**
* Append the property columns to the buffer.
*/
@@ -28,20 +47,6 @@ final class SqlTreeNodeManyRoot extends SqlTreeNodeBean {
ctx.popTableAlias();
}
@Override
public EntityBean load(DbReadContext cquery, EntityBean parentBean, EntityBean contextParent) throws SQLException {
// pass in null for parentBean because the localBean
// that is built is added to a collection rather than
// being set to the parentBean directly
EntityBean detailBean = super.load(cquery, null, null);
// initialise the collection and add detailBean if it is not null
if (contextParent != null) {
manyProp.addBeanToCollectionWithCreate(contextParent, detailBean, false);
}
return detailBean;
}
/**
* append extraWhere to the join.
*/
@@ -64,9 +69,4 @@ final class SqlTreeNodeManyRoot extends SqlTreeNodeBean {
public void appendFrom(DbSqlContext ctx, SqlJoinType joinType) {
super.appendFrom(ctx, joinType.autoToOuter());
}
@Override
public boolean hasMany() {
return true;
}
}
@@ -144,12 +144,6 @@ class SqlTreeNodeManyWhereJoin implements SqlTreeNode {
return null;
}
@Override
public <T> Version<T> loadVersion(DbReadContext ctx) {
// nothing to do here
return null;
}
@Override
public boolean hasMany() {
return true;
@@ -1,16 +1,21 @@
package io.ebeaninternal.server.query;
import io.ebean.Version;
import io.ebean.bean.EntityBean;
import io.ebeaninternal.api.SpiQuery;
import io.ebeaninternal.server.deploy.DbReadContext;
import io.ebeaninternal.server.deploy.DbSqlContext;
import io.ebeaninternal.server.deploy.TableJoin;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.util.List;
import java.util.Set;
/**
* Represents the root node of the Sql Tree.
*/
final class SqlTreeNodeRoot extends SqlTreeNodeBean {
final class SqlTreeNodeRoot extends SqlTreeNodeBean implements SqlTreeRoot {
private final TableJoin includeJoin;
@@ -35,6 +40,25 @@ final class SqlTreeNodeRoot extends SqlTreeNodeBean {
return true;
}
@Override
public EntityBean load(DbReadContext ctx) throws SQLException {
return load(ctx, null, null);
}
/**
* Read the version bean.
*/
@Override
@SuppressWarnings("unchecked")
public <T> Version<T> loadVersion(DbReadContext ctx) throws SQLException {
// read the sys period lower and upper bounds
// these are always the first 2 columns in the resultSet
Timestamp start = ctx.getDataReader().getTimestamp();
Timestamp end = ctx.getDataReader().getTimestamp();
T bean = (T) load(ctx, null, null);
return new Version<>(bean, start, end);
}
@Override
public boolean isSqlDistinct() {
return sqlDistinct;
@@ -0,0 +1,34 @@
package io.ebeaninternal.server.query;
import io.ebean.Version;
import io.ebean.bean.EntityBean;
import io.ebean.core.type.ScalarDataReader;
import io.ebeaninternal.server.deploy.DbReadContext;
import java.sql.SQLException;
/**
* The root level node of the SqlTree.
*/
interface SqlTreeRoot {
/**
* Load the bean from the DbReadContext.
* <p>
* At a high level this actually controls the reading of the data from the
* jdbc resultSet and putting it into the bean etc.
* </p>
*/
EntityBean load(DbReadContext ctx) throws SQLException;
/**
* Load a version of a @History bean with effective dates.
*/
<T> Version<T> loadVersion(DbReadContext ctx) throws SQLException;
/**
* Return a Scalar single attribute reader based on the first property.
*/
ScalarDataReader<?> getSingleAttributeReader();
}
@@ -391,7 +391,7 @@ public class DefaultOrmQuery<T> implements SpiQuery<T> {
}
@Override
public DefaultOrmQuery<T> setUseDocStore(boolean useDocStore) {
public Query<T> setUseDocStore(boolean useDocStore) {
this.useDocStore = useDocStore;
return this;
}
@@ -431,7 +431,7 @@ public class DefaultOrmQuery<T> implements SpiQuery<T> {
}
@Override
public DefaultOrmQuery<T> setAllowLoadErrors() {
public Query<T> setAllowLoadErrors() {
this.allowLoadErrors = true;
return this;
}
@@ -452,21 +452,21 @@ public class DefaultOrmQuery<T> implements SpiQuery<T> {
}
@Override
public DefaultOrmQuery<T> asOf(Timestamp asOfDateTime) {
public Query<T> asOf(Timestamp asOfDateTime) {
this.temporalMode = (asOfDateTime != null) ? TemporalMode.AS_OF : TemporalMode.CURRENT;
this.asOf = asOfDateTime;
return this;
}
@Override
public DefaultOrmQuery<T> asDraft() {
public Query<T> asDraft() {
this.temporalMode = TemporalMode.DRAFT;
this.useBeanCache = CacheMode.OFF;
return this;
}
@Override
public DefaultOrmQuery<T> setIncludeSoftDeletes() {
public Query<T> setIncludeSoftDeletes() {
this.temporalMode = TemporalMode.SOFT_DELETED;
return this;
}
@@ -489,7 +489,7 @@ public class DefaultOrmQuery<T> implements SpiQuery<T> {
}
@Override
public DefaultOrmQuery<T> setRawSql(RawSql rawSql) {
public Query<T> setRawSql(RawSql rawSql) {
this.rawSql = (SpiRawSql) rawSql;
return this;
}
@@ -802,12 +802,12 @@ public class DefaultOrmQuery<T> implements SpiQuery<T> {
}
@Override
public DefaultOrmQuery<T> copy() {
public SpiQuery<T> copy() {
return copy(server);
}
@Override
public DefaultOrmQuery<T> copy(SpiEbeanServer server) {
public SpiQuery<T> copy(SpiEbeanServer server) {
DefaultOrmQuery<T> copy = new DefaultOrmQuery<>(beanDescriptor, server, expressionFactory);
copy.transaction = transaction;
copy.m2mIncludeJoin = m2mIncludeJoin;
@@ -955,7 +955,7 @@ public class DefaultOrmQuery<T> implements SpiQuery<T> {
}
@Override
public DefaultOrmQuery<T> setAutoTune(boolean autoTune) {
public Query<T> setAutoTune(boolean autoTune) {
this.autoTune = autoTune;
return this;
}
@@ -971,21 +971,21 @@ public class DefaultOrmQuery<T> implements SpiQuery<T> {
}
@Override
public DefaultOrmQuery<T> forUpdate() {
public Query<T> forUpdate() {
return setForUpdateWithMode(LockWait.WAIT, LockType.DEFAULT);
}
@Override
public DefaultOrmQuery<T> forUpdateNoWait() {
public Query<T> forUpdateNoWait() {
return setForUpdateWithMode(LockWait.NOWAIT, LockType.DEFAULT);
}
@Override
public DefaultOrmQuery<T> forUpdateSkipLocked() {
public Query<T> forUpdateSkipLocked() {
return setForUpdateWithMode(LockWait.SKIPLOCKED, LockType.DEFAULT);
}
private DefaultOrmQuery<T> setForUpdateWithMode(LockWait mode, LockType lockType) {
private Query<T> setForUpdateWithMode(LockWait mode, LockType lockType) {
this.forUpdate = mode;
this.lockType = lockType;
this.useBeanCache = CacheMode.OFF;
@@ -1329,7 +1329,7 @@ public class DefaultOrmQuery<T> implements SpiQuery<T> {
}
@Override
public DefaultOrmQuery<T> setReadOnly(boolean readOnly) {
public Query<T> setReadOnly(boolean readOnly) {
this.readOnly = readOnly;
return this;
}
@@ -1375,82 +1375,110 @@ public class DefaultOrmQuery<T> implements SpiQuery<T> {
}
@Override
public DefaultOrmQuery<T> setUseQueryCache(CacheMode useQueryCache) {
public Query<T> setUseQueryCache(CacheMode useQueryCache) {
this.useQueryCache = useQueryCache;
return this;
}
@Override
public DefaultOrmQuery<T> setLoadBeanCache(boolean loadBeanCache) {
public Query<T> setLoadBeanCache(boolean loadBeanCache) {
this.useBeanCache = CacheMode.PUT;
return this;
}
@Override
public DefaultOrmQuery<T> setTimeout(int secs) {
public Query<T> setTimeout(int secs) {
this.timeout = secs;
return this;
}
@Override
public DefaultOrmQuery<T> select(String columns) {
public void selectProperties(Set<String> props) {
detail.selectProperties(props);
}
@Override
public void fetchProperties(String property, Set<String> columns, FetchConfig config) {
detail.fetchProperties(property, columns, config);
}
@Override
public void selectProperties(OrmQueryProperties properties) {
detail.selectProperties(properties);
}
@Override
public void fetchProperties(String path, OrmQueryProperties other) {
detail.fetchProperties(path, other);
}
@Override
public Query<T> select(String columns) {
detail.select(columns);
return this;
}
@Override
public DefaultOrmQuery<T> select(FetchGroup<T> fetchGroup) {
public Query<T> select(FetchGroup<T> fetchGroup) {
this.detail = ((SpiFetchGroup<T>) fetchGroup).detail();
return this;
}
@Override
public DefaultOrmQuery<T> fetch(String property) {
return fetch(property, null, null);
public Query<T> fetch(String path) {
return fetch(path, null, null);
}
@Override
public Query<T> fetchQuery(String property) {
return fetch(property, null, FETCH_QUERY);
}
public Query<T> fetchCache(String property) {
return fetch(property, null, FETCH_CACHE);
public Query<T> fetch(String path, FetchConfig joinConfig) {
return fetch(path, null, joinConfig);
}
@Override
public Query<T> fetchLazy(String property) {
return fetch(property, null, FETCH_LAZY);
public Query<T> fetch(String path, String properties) {
return fetch(path, properties, null);
}
@Override
public DefaultOrmQuery<T> fetch(String property, FetchConfig joinConfig) {
return fetch(property, null, joinConfig);
public Query<T> fetch(String path, String properties, FetchConfig config) {
if (nativeSql != null && (config == null || config.isJoin())) {
// can't use fetch join with nativeSql (as the root query)
config = FETCH_QUERY;
}
return fetchInternal(path, properties, config);
}
@Override
public DefaultOrmQuery<T> fetch(String property, String columns) {
return fetch(property, columns, null);
public Query<T> fetchQuery(String path) {
return fetchInternal(path, null, FETCH_QUERY);
}
public Query<T> fetchCache(String path) {
return fetchInternal(path, null, FETCH_CACHE);
}
@Override
public Query<T> fetchQuery(String property, String columns) {
return fetch(property, columns, FETCH_QUERY);
public Query<T> fetchLazy(String path) {
return fetchInternal(path, null, FETCH_LAZY);
}
@Override
public Query<T> fetchCache(String property, String columns) {
return fetch(property, columns, FETCH_CACHE);
public Query<T> fetchQuery(String path, String properties) {
return fetchInternal(path, properties, FETCH_QUERY);
}
@Override
public Query<T> fetchLazy(String property, String columns) {
return fetch(property, columns, FETCH_LAZY);
public Query<T> fetchCache(String path, String properties) {
return fetchInternal(path, properties, FETCH_CACHE);
}
@Override
public DefaultOrmQuery<T> fetch(String property, String columns, FetchConfig config) {
detail.fetch(property, columns, config);
public Query<T> fetchLazy(String path, String properties) {
return fetchInternal(path, properties, FETCH_LAZY);
}
private Query<T> fetchInternal(String path, String properties, FetchConfig config) {
detail.fetch(path, properties, config);
return this;
}
@@ -1613,7 +1641,7 @@ public class DefaultOrmQuery<T> implements SpiQuery<T> {
}
@Override
public DefaultOrmQuery<T> setParameter(Object value) {
public Query<T> setParameter(Object value) {
if (bindParams == null) {
bindParams = new BindParams();
}
@@ -1622,7 +1650,7 @@ public class DefaultOrmQuery<T> implements SpiQuery<T> {
}
@Override
public DefaultOrmQuery<T> setParameters(Object... values) {
public Query<T> setParameters(Object... values) {
if (bindParams == null) {
bindParams = new BindParams();
}
@@ -1636,7 +1664,7 @@ public class DefaultOrmQuery<T> implements SpiQuery<T> {
* have in the query.
*/
@Override
public DefaultOrmQuery<T> setParameter(int position, Object value) {
public Query<T> setParameter(int position, Object value) {
if (bindParams == null) {
bindParams = new BindParams();
}
@@ -1648,7 +1676,7 @@ public class DefaultOrmQuery<T> implements SpiQuery<T> {
* Set a named bind parameter. Named parameters have a colon to prefix the name.
*/
@Override
public DefaultOrmQuery<T> setParameter(String name, Object value) {
public Query<T> setParameter(String name, Object value) {
if (namedParams != null) {
ONamedParam param = namedParams.get(name);
if (param != null) {
@@ -1694,12 +1722,12 @@ public class DefaultOrmQuery<T> implements SpiQuery<T> {
@Override
@Deprecated
public DefaultOrmQuery<T> orderBy(String orderByClause) {
public Query<T> orderBy(String orderByClause) {
return order(orderByClause);
}
@Override
public DefaultOrmQuery<T> order(String orderByClause) {
public Query<T> order(String orderByClause) {
if (orderByClause == null || orderByClause.trim().isEmpty()) {
this.orderBy = null;
} else {
@@ -1710,12 +1738,12 @@ public class DefaultOrmQuery<T> implements SpiQuery<T> {
@Override
@Deprecated
public DefaultOrmQuery<T> setOrderBy(OrderBy<T> orderBy) {
public Query<T> setOrderBy(OrderBy<T> orderBy) {
return setOrder(orderBy);
}
@Override
public DefaultOrmQuery<T> setOrder(OrderBy<T> orderBy) {
public Query<T> setOrder(OrderBy<T> orderBy) {
this.orderBy = orderBy;
if (orderBy != null) {
orderBy.setQuery(this);
@@ -1747,13 +1775,13 @@ public class DefaultOrmQuery<T> implements SpiQuery<T> {
* Internally set to use SQL DISTINCT on the query but still have id property included.
*/
@Override
public DefaultOrmQuery<T> setDistinct(boolean distinct) {
public Query<T> setDistinct(boolean distinct) {
this.distinct = distinct;
return this;
}
@Override
public DefaultOrmQuery<T> setCountDistinct(CountDistinctOrder countDistinctOrder) {
public Query<T> setCountDistinct(CountDistinctOrder countDistinctOrder) {
this.countDistinctOrder = countDistinctOrder;
return this;
}
@@ -1804,7 +1832,7 @@ public class DefaultOrmQuery<T> implements SpiQuery<T> {
}
@Override
public DefaultOrmQuery<T> setDisableLazyLoading(boolean disableLazyLoading) {
public Query<T> setDisableLazyLoading(boolean disableLazyLoading) {
this.disableLazyLoading = disableLazyLoading;
return this;
}
@@ -1820,7 +1848,7 @@ public class DefaultOrmQuery<T> implements SpiQuery<T> {
}
@Override
public DefaultOrmQuery<T> setFirstRow(int firstRow) {
public Query<T> setFirstRow(int firstRow) {
this.firstRow = firstRow;
return this;
}
@@ -1831,7 +1859,7 @@ public class DefaultOrmQuery<T> implements SpiQuery<T> {
}
@Override
public DefaultOrmQuery<T> setMaxRows(int maxRows) {
public Query<T> setMaxRows(int maxRows) {
this.maxRows = maxRows;
return this;
}
@@ -1842,7 +1870,7 @@ public class DefaultOrmQuery<T> implements SpiQuery<T> {
}
@Override
public DefaultOrmQuery<T> setMapKey(String mapKey) {
public Query<T> setMapKey(String mapKey) {
this.mapKey = mapKey;
return this;
}
@@ -1853,7 +1881,7 @@ public class DefaultOrmQuery<T> implements SpiQuery<T> {
}
@Override
public DefaultOrmQuery<T> setId(Object id) {
public Query<T> setId(Object id) {
if (id == null) {
throw new NullPointerException("The id is null");
}
@@ -1867,7 +1895,7 @@ public class DefaultOrmQuery<T> implements SpiQuery<T> {
}
@Override
public DefaultOrmQuery<T> where(Expression expression) {
public Query<T> where(Expression expression) {
where().add(expression);
return this;
}
@@ -1897,7 +1925,7 @@ public class DefaultOrmQuery<T> implements SpiQuery<T> {
}
@Override
public DefaultOrmQuery<T> having(Expression expression) {
public Query<T> having(Expression expression) {
having().add(expression);
return this;
}
@@ -2023,7 +2051,7 @@ public class DefaultOrmQuery<T> implements SpiQuery<T> {
}
@Override
public DefaultOrmQuery<T> alias(String alias) {
public Query<T> alias(String alias) {
this.rootTableAlias = alias;
return this;
}
@@ -59,9 +59,9 @@ public class OrmQueryDetail implements Serializable {
* Add a nested OrmQueryDetail to this detail.
*/
public void addNested(String path, OrmQueryDetail other, FetchConfig config) {
fetch(path, other.baseProps.getProperties(), config);
fetchProperties(path, other.baseProps, config);
for (Map.Entry<String, OrmQueryProperties> entry : other.fetchPaths.entrySet()) {
fetch(path + "." + entry.getKey(), entry.getValue().getProperties(), entry.getValue().getFetchConfig());
fetchProperties(path + "." + entry.getKey(), entry.getValue(), entry.getValue().getFetchConfig());
}
}
@@ -111,7 +111,7 @@ public class OrmQueryDetail implements Serializable {
*/
public String asStringDebug() {
StringBuilder sb = new StringBuilder();
if (!baseProps.isEmpty()) {
if (baseProps.hasProperties()) {
baseProps.asStringDebug("select ", sb);
}
if (fetchPaths != null) {
@@ -133,8 +133,19 @@ public class OrmQueryDetail implements Serializable {
/**
* set the properties to include on the base / root entity.
*/
public void select(String columns) {
baseProps = new OrmQueryProperties(null, columns, null);
public void select(String properties) {
baseProps = new OrmQueryProperties(null, properties, null);
}
/**
* Set select properties that are already parsed.
*/
public void selectProperties(Set<String> properties) {
baseProps = new OrmQueryProperties(null, properties, OrmQueryProperties.DEFAULT_FETCH);
}
void selectProperties(OrmQueryProperties other) {
baseProps = new OrmQueryProperties(null, other, OrmQueryProperties.DEFAULT_FETCH);
}
boolean containsProperty(String property) {
@@ -262,10 +273,24 @@ public class OrmQueryDetail implements Serializable {
* @param partialProps the properties on the join property to include
*/
public void fetch(String path, String partialProps, FetchConfig fetchConfig) {
fetch(new OrmQueryProperties(path, partialProps, fetchConfig));
}
/**
* Set fetch properties that are already parsed.
*/
public void fetchProperties(String path, Set<String> properties, FetchConfig fetchConfig) {
fetch(new OrmQueryProperties(path, properties, fetchConfig));
}
void fetchProperties(String path, OrmQueryProperties other) {
fetchProperties(path, other, other.getFetchConfig());
}
void fetchProperties(String path, OrmQueryProperties other, FetchConfig fetchConfig) {
fetch(new OrmQueryProperties(path, other, fetchConfig));
}
/**
* Add for raw sql etc when the properties are already parsed into a set.
*/
@@ -30,7 +30,7 @@ public class OrmQueryProperties implements Serializable {
private final String parentPath;
private final String path;
private final String properties;
private final boolean allProperties;
private final Set<String> included;
private final FetchConfig fetchConfig;
private final boolean cache;
@@ -77,7 +77,7 @@ public class OrmQueryProperties implements Serializable {
public OrmQueryProperties(String path) {
this.path = path;
this.parentPath = SplitName.parent(path);
this.properties = null;
this.allProperties = false;
this.included = null;
this.cache = false;
this.fetchConfig = DEFAULT_FETCH;
@@ -91,7 +91,7 @@ public class OrmQueryProperties implements Serializable {
this.path = path;
this.parentPath = SplitName.parent(path);
OrmQueryPropertiesParser.Response response = OrmQueryPropertiesParser.parse(rawProperties);
this.properties = response.properties;
this.allProperties = response.allProperties;
this.included = response.included;
if (fetchConfig != null) {
this.fetchConfig = fetchConfig;
@@ -103,12 +103,25 @@ public class OrmQueryProperties implements Serializable {
}
public OrmQueryProperties(String path, Set<String> included) {
this(path, included, DEFAULT_FETCH);
}
OrmQueryProperties(String path, Set<String> included, FetchConfig fetchConfig) {
this.path = path;
this.parentPath = SplitName.parent(path);
this.included = included;
this.properties = String.join(",", included);
this.cache = false;
this.fetchConfig = DEFAULT_FETCH;
this.allProperties = false;
this.fetchConfig = fetchConfig;
this.cache = fetchConfig.isCache();
}
OrmQueryProperties(String path, OrmQueryProperties other, FetchConfig fetchConfig) {
this.path = path;
this.parentPath = SplitName.parent(path);
this.allProperties = other.allProperties;
this.included = other.included;
this.cache = other.cache;
this.fetchConfig = fetchConfig;
}
/**
@@ -118,7 +131,7 @@ public class OrmQueryProperties implements Serializable {
this.fetchConfig = sourceFetchConfig;
this.parentPath = source.parentPath;
this.path = source.path;
this.properties = source.properties;
this.allProperties = source.allProperties;
this.cache = source.cache;
this.filterMany = source.filterMany;
this.markForQueryJoin = source.markForQueryJoin;
@@ -158,7 +171,7 @@ public class OrmQueryProperties implements Serializable {
* Return the expressions used to filter on this path. This should be a many path to use this
* method.
*/
@SuppressWarnings({"rawtypes","unchecked"})
@SuppressWarnings({"rawtypes", "unchecked"})
public <T> SpiExpressionList<T> filterMany(Query<T> rootQuery) {
if (filterMany == null) {
FilterExprPath exprPath = new FilterExprPath(path);
@@ -201,9 +214,8 @@ public class OrmQueryProperties implements Serializable {
*/
@SuppressWarnings("unchecked")
public void configureBeanQuery(SpiQuery<?> query) {
if (properties != null && !properties.isEmpty()) {
query.select(properties);
if (hasProperties()) {
query.selectProperties(this);
}
if (filterMany != null) {
@@ -219,7 +231,7 @@ public class OrmQueryProperties implements Serializable {
for (OrmQueryProperties p : secondaryChildren) {
String path = p.getPath();
path = path.substring(trimPath);
query.fetch(path, p.getProperties(), p.getFetchConfig());
query.fetchProperties(path, p);
query.setFilterMany(path, p.getFilterManyTrimPath(trimPath));
}
}
@@ -230,19 +242,14 @@ public class OrmQueryProperties implements Serializable {
}
public boolean hasSelectClause() {
if ("*".equals(properties)) {
// explicitly selected all properties
return true;
}
// explicitly selected some properties
return included != null || filterMany != null;
return allProperties || included != null || filterMany != null;
}
/**
* Return true if the properties and configuration are empty.
* Return true if explicit properties have been specified.
*/
public boolean isEmpty() {
return properties == null || properties.isEmpty();
boolean hasProperties() {
return allProperties || included != null;
}
public void asStringDebug(String prefix, StringBuilder sb) {
@@ -250,8 +257,10 @@ public class OrmQueryProperties implements Serializable {
if (path != null) {
sb.append(path).append(" ");
}
if (!isEmpty()) {
sb.append("(").append(properties).append(")");
if (allProperties) {
sb.append("(*)");
} else if (included != null) {
sb.append("(").append(String.join(",", included)).append(")");
}
}
@@ -269,17 +278,11 @@ public class OrmQueryProperties implements Serializable {
secondaryChildren.add(child);
}
/**
* Return the raw properties.
*/
public String getProperties() {
return properties;
}
/**
* Return true if this includes all properties on the path.
*/
public boolean allProperties() {
// this is really "default" properties
return included == null;
}
@@ -326,7 +329,6 @@ public class OrmQueryProperties implements Serializable {
if (includedBeanJoin != null && includedBeanJoin.contains(propName)) {
return false;
}
// all properties included
return included == null || included.contains(propName);
}
@@ -403,7 +405,7 @@ public class OrmQueryProperties implements Serializable {
if (path != null) {
builder.append(path);
}
if (included != null){
if (included != null) {
builder.append("/i").append(included);
}
if (secondaryQueryJoins != null) {
@@ -7,72 +7,43 @@ import java.util.Set;
/**
* Parses the path properties string.
*/
class OrmQueryPropertiesParser {
final class OrmQueryPropertiesParser {
private static final Response EMPTY = new Response();
private static final Response EMPTY = new Response(false, null);
private static final Response ALL = new Response(true, null);
/**
* Immutable response of the parsed properties and options.
*/
static class Response {
final String properties;
final boolean allProperties;
final Set<String> included;
private Response(String properties, Set<String> included) {
this.properties = properties;
private Response(boolean allProperties, Set<String> included) {
this.allProperties = allProperties;
this.included = included;
}
private Response() {
this.properties = "";
this.included = null;
}
}
/**
* Parses the path properties string returning the parsed properties and options.
* In general it is comma delimited with some special strings like +lazy(20).
*/
public static Response parse(String rawProperties) {
return new OrmQueryPropertiesParser(rawProperties).parse();
}
private final String inputProperties;
private boolean allProperties;
private OrmQueryPropertiesParser(String inputProperties) {
this.inputProperties = inputProperties;
}
/**
* Parse the raw string properties input.
*/
private Response parse() {
if (inputProperties == null || inputProperties.isEmpty()) {
static Response parse(String rawProperties) {
if (rawProperties == null || rawProperties.isEmpty()) {
return EMPTY;
}
if (inputProperties.equals("*")) {
return new Response("*", null);
if (rawProperties.equals("*")) {
return ALL;
}
Set<String> fields = splitRawSelect(inputProperties);
for (String val : fields) {
if (val.equals("*")) {
allProperties = true;
break;
}
}
String properties = allProperties ? "*" : inputProperties;
if (fields.isEmpty()) {
fields = null;
}
return new Response(properties, fields);
return new Response(false, splitRawSelect(rawProperties));
}
/**
* Split allowing 'dynamic function based properties'.
*/
private Set<String> splitRawSelect(String inputProperties) {
private static Set<String> splitRawSelect(String inputProperties) {
return DSelectColumnsParser.parse(inputProperties);
}
@@ -10,9 +10,14 @@ import java.util.UUID;
interface ArrayElementConverter<T> {
/**
* Convert the array element to the logical type.
* Convert element it's json serialized form.
*/
T toElement(Object rawValue);
T fromSerialized(Object rawValue);
/**
* Convert the array element from it's DB array form.
*/
T fromDbArray(Object rawValue);
default Object[] toDbArray(Object[] objects) {
return objects;
@@ -46,7 +51,12 @@ interface ArrayElementConverter<T> {
class LongConverter implements ArrayElementConverter<Long> {
@Override
public Long toElement(Object rawValue) {
public Long fromSerialized(Object rawValue) {
return fromDbArray(rawValue);
}
@Override
public Long fromDbArray(Object rawValue) {
if (rawValue instanceof Long) {
return (Long) rawValue;
} else {
@@ -58,7 +68,12 @@ interface ArrayElementConverter<T> {
class IntegerConverter implements ArrayElementConverter<Integer> {
@Override
public Integer toElement(Object rawValue) {
public Integer fromSerialized(Object rawValue) {
return ((Number) rawValue).intValue();
}
@Override
public Integer fromDbArray(Object rawValue) {
if (rawValue instanceof Integer) {
return (Integer) rawValue;
} else {
@@ -70,7 +85,12 @@ interface ArrayElementConverter<T> {
class DoubleConverter implements ArrayElementConverter<Double> {
@Override
public Double toElement(Object rawValue) {
public Double fromSerialized(Object rawValue) {
return fromDbArray(rawValue);
}
@Override
public Double fromDbArray(Object rawValue) {
if (rawValue instanceof Double) {
return (Double) rawValue;
} else {
@@ -82,21 +102,34 @@ interface ArrayElementConverter<T> {
/**
* String converter (noop based).
*/
class StringConverter extends NoopConverter<String> {
class StringConverter implements ArrayElementConverter<String> {
public String fromDbArray(Object rawValue) {
return (String) rawValue;
}
@Override
public String fromSerialized(Object rawValue) {
return (String) rawValue;
}
}
/**
* UUID converter (noop based).
* UUID converter.
*/
class UuidConverter extends NoopConverter<UUID> {
}
class UuidConverter implements ArrayElementConverter<UUID> {
class NoopConverter<T> implements ArrayElementConverter<T> {
@SuppressWarnings("unchecked")
@Override
public T toElement(Object rawValue) {
return (T) rawValue;
public java.util.UUID fromSerialized(Object rawValue) {
return java.util.UUID.fromString((String)rawValue);
}
@Override
public java.util.UUID fromDbArray(Object rawValue) {
if (rawValue instanceof UUID) {
return (java.util.UUID) rawValue;
}
return java.util.UUID.fromString(rawValue.toString());
}
}
@@ -113,7 +146,12 @@ interface ArrayElementConverter<T> {
}
@Override
public Object toElement(Object rawValue) {
public Object fromSerialized(Object rawValue) {
return scalarType.parse((String) rawValue);
}
@Override
public Object fromDbArray(Object rawValue) {
return scalarType.toBeanType(rawValue);
}
@@ -1,25 +0,0 @@
package io.ebeaninternal.server.type;
import io.ebean.core.type.ScalarType;
public class ArrayElementConverterEnum implements ArrayElementConverter<String> {
final ScalarType<?> scalarType;
final Class<? extends Enum<?>> valueType1;
public ArrayElementConverterEnum(ScalarType<?> scalarType, Class<? extends Enum<?>> valueType1) {
this.scalarType = scalarType;
this.valueType1 = valueType1;
}
@Override
public String toElement(Object rawValue) {
// FIXME: Don't understand for what is this.
Enum<?>[] enumConstants = valueType1.getEnumConstants();
if (scalarType == null) {
return rawValue.toString();
}
return scalarType.format(rawValue);
}
}
@@ -105,7 +105,7 @@ public class ScalarTypeArrayList extends ScalarTypeArrayBase<List> implements Sc
if (element == null) {
list.add(null);
} else {
list.add(converter.toElement(element));
list.add(converter.fromDbArray(element));
}
}
return new ModifyAwareList(list);
@@ -145,15 +145,27 @@ public class ScalarTypeArrayList extends ScalarTypeArrayBase<List> implements Sc
@Override
public List parse(String value) {
try {
return EJson.parseList(value, false);
return convert(EJson.parseList(value, false));
} catch (IOException e) {
throw new TextException("Failed to parse JSON [{}] as List", value, e);
}
}
/**
* Convert from the json types to the proper scalar types (uuid, enum, double etc)
*/
@SuppressWarnings("rawtypes")
private List convert(List<Object> rawList) {
List list = new ArrayList(rawList.size());
for (Object rawVal : rawList) {
list.add(converter.fromSerialized(rawVal));
}
return new ModifyAwareList(list);
}
@Override
public List jsonRead(JsonParser parser) throws IOException {
return EJson.parseList(parser, parser.getCurrentToken());
return convert(EJson.parseList(parser, parser.getCurrentToken()));
}
@Override
@@ -17,6 +17,7 @@ import java.sql.SQLException;
import java.sql.Types;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
@@ -101,7 +102,7 @@ public class ScalarTypeArraySet extends ScalarTypeArrayBase<Set> implements Scal
protected Set fromArray(Object[] array1) {
Set set = new LinkedHashSet();
for (Object element : array1) {
set.add(converter.toElement(element));
set.add(converter.fromDbArray(element));
}
return new ModifyAwareSet(set);
}
@@ -140,15 +141,26 @@ public class ScalarTypeArraySet extends ScalarTypeArrayBase<Set> implements Scal
@Override
public Set parse(String value) {
try {
return EJson.parseSet(value, false);
return convert(EJson.parseList(value, false));
} catch (IOException e) {
throw new TextException("Failed to parse JSON [{}] as Set", value, e);
}
}
/**
* Convert from the json types to the proper scalar types (uuid, enum, double etc)
*/
private Set convert(List<Object> rawList) {
Set asSet = new LinkedHashSet();
for (Object rawVal : rawList) {
asSet.add(converter.fromSerialized(rawVal));
}
return new ModifyAwareSet(asSet);
}
@Override
public Set jsonRead(JsonParser parser) throws IOException {
return EJson.parseSet(parser, parser.getCurrentToken());
return convert(EJson.parseList(parser, parser.getCurrentToken()));
}
@Override
@@ -18,8 +18,7 @@ import static org.assertj.core.api.Assertions.assertThat;
public class OrmQueryPlanKeyTest extends BaseExpressionTest {
@SuppressWarnings("unchecked")
@SuppressWarnings({"unchecked", "rawtypes"})
private DefaultOrmQuery<Customer> query() {
return (DefaultOrmQuery) server().find(Customer.class);
}
@@ -85,41 +84,40 @@ public class OrmQueryPlanKeyTest extends BaseExpressionTest {
@Test
public void equals_when_firstRowsDifferent() {
CQueryPlanKey key1 = query().setFirstRow(10).createQueryPlanKey();
CQueryPlanKey key2 = query().createQueryPlanKey();
CQueryPlanKey key1 = planKey(query().setFirstRow(10));
CQueryPlanKey key2 = planKey(query());
assertDifferent(key1, key2);
key1 = query().setFirstRow(10).createQueryPlanKey();
key2 = query().setFirstRow(9).createQueryPlanKey();
key1 = planKey(query().setFirstRow(10));
key2 = planKey(query().setFirstRow(9));
assertDifferent(key1, key2);
key1 = query().createQueryPlanKey();
key2 = query().setFirstRow(9).createQueryPlanKey();
key1 = planKey(query());
key2 = planKey(query().setFirstRow(9));
assertDifferent(key1, key2);
}
@Test
public void equals_when_maxRowsDifferent() {
CQueryPlanKey key1 = query().setMaxRows(10).createQueryPlanKey();
CQueryPlanKey key2 = query().createQueryPlanKey();
CQueryPlanKey key1 = planKey(query().setMaxRows(10));
CQueryPlanKey key2 = planKey(query());
assertDifferent(key1, key2);
key1 = query().setMaxRows(10).createQueryPlanKey();
key2 = query().setMaxRows(9).createQueryPlanKey();
key1 = planKey(query().setMaxRows(10));
key2 = planKey(query().setMaxRows(9));
assertDifferent(key1, key2);
key1 = query().createQueryPlanKey();
key2 = query().setMaxRows(9).createQueryPlanKey();
key1 = planKey(query());
key2 = planKey(query().setMaxRows(9));
assertDifferent(key1, key2);
}
@Test
public void equals_when_firstRowsMaxRowsSame() {
CQueryPlanKey key1 = query().setMaxRows(10).setFirstRow(20).createQueryPlanKey();
CQueryPlanKey key2 = query().setFirstRow(20).setMaxRows(10).createQueryPlanKey();
CQueryPlanKey key1 = planKey(query().setMaxRows(10).setFirstRow(20));
CQueryPlanKey key2 = planKey(query().setFirstRow(20).setMaxRows(10));
assertSame(key1, key2);
}
@@ -132,43 +130,43 @@ public class OrmQueryPlanKeyTest extends BaseExpressionTest {
@Test
public void equals_when_diffOrderByNull() {
CQueryPlanKey key1 = query().order("id").createQueryPlanKey();
CQueryPlanKey key2 = query().createQueryPlanKey();
CQueryPlanKey key1 = planKey(query().order("id"));
CQueryPlanKey key2 = planKey(query());
assertDifferent(key1, key2);
key1 = ((DefaultOrmQuery) query().order().asc("id")).createQueryPlanKey();
key2 = query().createQueryPlanKey();
key1 = planKey(query().order().asc("id"));
key2 = planKey(query());
assertDifferent(key1, key2);
}
@Test
public void equals_when_orderBySame() {
CQueryPlanKey key1 = query().order("id, name").createQueryPlanKey();
CQueryPlanKey key2 = query().order("id, name").createQueryPlanKey();
CQueryPlanKey key1 = planKey(query().order("id, name"));
CQueryPlanKey key2 = planKey(query().order("id, name"));
assertSame(key1, key2);
}
@Test
public void equals_when_diffDistinct() {
CQueryPlanKey key1 = query().setDistinct(true).createQueryPlanKey();
CQueryPlanKey key2 = query().createQueryPlanKey();
CQueryPlanKey key1 = planKey(query().setDistinct(true));
CQueryPlanKey key2 = planKey(query());
assertDifferent(key1, key2);
}
@Test
public void equals_when_sameDistinct() {
CQueryPlanKey key1 = query().setDistinct(true).createQueryPlanKey();
CQueryPlanKey key2 = query().setDistinct(true).createQueryPlanKey();
CQueryPlanKey key1 = planKey(query().setDistinct(true));
CQueryPlanKey key2 = planKey(query().setDistinct(true));
assertSame(key1, key2);
}
@Test
public void equals_when_useDocStore() {
CQueryPlanKey key1 = query().setUseDocStore(true).createQueryPlanKey();
CQueryPlanKey key2 = query().createQueryPlanKey();
CQueryPlanKey key1 = planKey(query().setUseDocStore(true));
CQueryPlanKey key2 = planKey(query());
assertDifferent(key1, key2);
}
@@ -176,79 +174,78 @@ public class OrmQueryPlanKeyTest extends BaseExpressionTest {
@Test
public void equals_when_diffMapKey() {
CQueryPlanKey key1 = query().setMapKey("name").createQueryPlanKey();
CQueryPlanKey key2 = query().createQueryPlanKey();
CQueryPlanKey key1 = planKey(query().setMapKey("name"));
CQueryPlanKey key2 = planKey(query());
assertDifferent(key1, key2);
CQueryPlanKey key3 = query().setMapKey("email").createQueryPlanKey();
CQueryPlanKey key3 = planKey(query().setMapKey("email"));
assertDifferent(key1, key3);
CQueryPlanKey key4 = query().setMapKey("name").createQueryPlanKey();
CQueryPlanKey key4 = planKey(query().setMapKey("name"));
assertSame(key1, key4);
}
@Test
public void equals_when_diffIdNull() {
CQueryPlanKey key1 = query().setId(42).createQueryPlanKey();
CQueryPlanKey key2 = query().createQueryPlanKey();
CQueryPlanKey key1 = planKey(query().setId(42));
CQueryPlanKey key2 = planKey(query());
assertDifferent(key1, key2);
}
@Test
public void equals_when_idBothGiven() {
CQueryPlanKey key1 = query().setId(42).createQueryPlanKey();
CQueryPlanKey key2 = query().setId(23).createQueryPlanKey();
CQueryPlanKey key1 = planKey(query().setId(42));
CQueryPlanKey key2 = planKey(query().setId(23));
assertSame(key1, key2);
}
@Test
public void equals_when_diffTemporalMode() {
CQueryPlanKey key1 = query().createQueryPlanKey();
CQueryPlanKey key2 = query().asDraft().createQueryPlanKey();
CQueryPlanKey key1 = planKey(query());
CQueryPlanKey key2 = planKey(query().asDraft());
assertDifferent(key1, key2);
CQueryPlanKey key3 = query().asOf(new Timestamp(System.currentTimeMillis())).createQueryPlanKey();
CQueryPlanKey key3 = planKey(query().asOf(new Timestamp(System.currentTimeMillis())));
assertDifferent(key1, key3);
CQueryPlanKey key4 = query().setIncludeSoftDeletes().createQueryPlanKey();
CQueryPlanKey key4 = planKey(query().setIncludeSoftDeletes());
assertDifferent(key1, key4);
}
@Test
public void equals_when_diffForUpdate() {
CQueryPlanKey key1 = query().forUpdate().createQueryPlanKey();
CQueryPlanKey key2 = query().createQueryPlanKey();
CQueryPlanKey key1 = planKey(query().forUpdate());
CQueryPlanKey key2 = planKey(query());
assertDifferent(key1, key2);
CQueryPlanKey key3 = query().forUpdateNoWait().createQueryPlanKey();
CQueryPlanKey key3 = planKey(query().forUpdateNoWait());
assertDifferent(key1, key3);
CQueryPlanKey key4 = query().forUpdateSkipLocked().createQueryPlanKey();
CQueryPlanKey key4 = planKey(query().forUpdateSkipLocked());
assertDifferent(key1, key4);
CQueryPlanKey key5 = query().forUpdate().createQueryPlanKey();
CQueryPlanKey key5 = planKey(query().forUpdate());
assertSame(key1, key5);
}
@Test
public void equals_when_diffRootAliasNull() {
CQueryPlanKey key1 = query().alias("alias").createQueryPlanKey();
CQueryPlanKey key2 = query().createQueryPlanKey();
CQueryPlanKey key1 = planKey(query().alias("alias"));
CQueryPlanKey key2 = planKey(query());
assertDifferent(key1, key2);
CQueryPlanKey key3 = query().alias("diff").createQueryPlanKey();
CQueryPlanKey key3 = planKey(query().alias("diff"));
assertDifferent(key1, key3);
CQueryPlanKey key4 = query().alias("alias").createQueryPlanKey();
CQueryPlanKey key4 = planKey(query().alias("alias"));
assertSame(key1, key4);
}
private DefaultOrmQuery<Customer> list_id_eq_42() {
return (DefaultOrmQuery<Customer>) server().find(Customer.class)
.where().eq("id", 42).query();
@@ -298,7 +295,6 @@ public class OrmQueryPlanKeyTest extends BaseExpressionTest {
assertDifferent(key1, key4);
}
@Test
public void equals_when_sameHaving() {
@@ -309,7 +305,7 @@ public class OrmQueryPlanKeyTest extends BaseExpressionTest {
@Test
public void equals_when_manualId_andSelectClause() {
DefaultOrmQuery<Customer> q1 = query().select("name");
DefaultOrmQuery<Customer> q1 = (DefaultOrmQuery<Customer>)query().select("name");
q1.setManualId();
assertDifferent(q1, query().select("name"));
@@ -327,16 +323,19 @@ public class OrmQueryPlanKeyTest extends BaseExpressionTest {
return planKey(id.query());
}
@SuppressWarnings({"rawtypes"})
private CQueryPlanKey planKey(Query<Customer> query) {
return ((DefaultOrmQuery) query).createQueryPlanKey();
}
private void assertDifferent(DefaultOrmQuery q1, DefaultOrmQuery q2) {
assertDifferent(q1.createQueryPlanKey(), q2.createQueryPlanKey());
@SuppressWarnings({"unchecked", "rawtypes"})
private void assertDifferent(Query q1, Query q2) {
assertDifferent(planKey(q1), planKey(q2));
}
private void assertSame(DefaultOrmQuery q1, DefaultOrmQuery q2) {
assertSame(q1.createQueryPlanKey(), q2.createQueryPlanKey());
@SuppressWarnings({"unchecked", "rawtypes"})
private void assertSame(Query q1, Query q2) {
assertSame(planKey(q1), planKey(q2));
}
private void assertDifferent(CQueryPlanKey key1, CQueryPlanKey key2) {
@@ -11,7 +11,8 @@ public class OrmQueryPropertiesParserTest {
OrmQueryPropertiesParser.Response res = OrmQueryPropertiesParser.parse(null);
assertAllDefaults(res);
assertThat(res.properties).isEqualTo("");
assertThat(res.allProperties).isFalse();
assertThat(res.included).isNull();
}
@Test
@@ -19,7 +20,8 @@ public class OrmQueryPropertiesParserTest {
OrmQueryPropertiesParser.Response res = OrmQueryPropertiesParser.parse("");
assertAllDefaults(res);
assertThat(res.properties).isEqualTo("");
assertThat(res.allProperties).isFalse();
assertThat(res.included).isNull();
}
@Test
@@ -27,13 +29,15 @@ public class OrmQueryPropertiesParserTest {
OrmQueryPropertiesParser.Response res = OrmQueryPropertiesParser.parse("*");
assertAllDefaults(res);
assertThat(res.properties).isEqualTo("*");
assertThat(res.allProperties).isTrue();
assertThat(res.included).isNull();
}
@Test
public void when_no_spaces() {
OrmQueryPropertiesParser.Response res = OrmQueryPropertiesParser.parse("id,name");
assertThat(res.allProperties).isFalse();
assertThat(res.included).containsExactly("id", "name");
}
@@ -41,6 +45,7 @@ public class OrmQueryPropertiesParserTest {
public void when_spaced() {
OrmQueryPropertiesParser.Response res = OrmQueryPropertiesParser.parse("id, name");
assertThat(res.allProperties).isFalse();
assertThat(res.included).containsExactly("id", "name");
}
@@ -48,6 +53,7 @@ public class OrmQueryPropertiesParserTest {
public void when_formula() {
OrmQueryPropertiesParser.Response res = OrmQueryPropertiesParser.parse("a,MD5(id::text) as b,c");
assertThat(res.allProperties).isFalse();
assertThat(res.included).containsExactly("a", "MD5(id::text) as b", "c");
}
@@ -14,17 +14,12 @@ public class OrmQueryPropertiesTest {
return sb.toString();
}
@Test(expected = NullPointerException.class)
public void construct_with_propertySet_when_null() {
new OrmQueryProperties(null, (LinkedHashSet<String>) null);
}
@Test
public void construct_with_propertySet_when_empty() {
OrmQueryProperties p1 = new OrmQueryProperties(null, new LinkedHashSet<>());
assertThat(p1.getProperties()).isEqualTo("");
assertThat(p1.allProperties()).isFalse();
assertThat(p1.getIncluded()).isEmpty();
}
@Test
@@ -34,7 +29,7 @@ public class OrmQueryPropertiesTest {
set.add("name");
OrmQueryProperties p1 = new OrmQueryProperties(null, set);
assertThat(p1.getProperties()).isEqualTo("name");
assertThat(p1.getIncluded()).containsOnly("name");
assertThat(p1.allProperties()).isFalse();
}
@@ -47,7 +42,7 @@ public class OrmQueryPropertiesTest {
set.add("startDate");
OrmQueryProperties p1 = new OrmQueryProperties(null, set);
assertThat(p1.getProperties()).isEqualTo("id,name,startDate");
assertThat(p1.getIncluded()).containsOnly("id", "name", "startDate");
assertThat(p1.allProperties()).isFalse();
}
@@ -1,13 +1,18 @@
package io.ebeaninternal.server.type;
import com.fasterxml.jackson.core.JsonParser;
import io.ebean.DB;
import io.ebean.core.type.DataReader;
import io.ebean.core.type.ScalarType;
import io.ebean.text.json.EJson;
import org.junit.Test;
import org.mockito.Mockito;
import java.io.IOException;
import java.io.StringReader;
import java.sql.SQLException;
import java.util.UUID;
import java.util.*;
import static org.assertj.core.api.Assertions.assertThat;
@@ -63,4 +68,82 @@ public class ScalarTypeArrayListTest extends BasePlatformArrayTypeFactoryTest {
assertBindNullTo_Null(factory.typeFor(UUID.class, true));
}
@Test
@SuppressWarnings("unchecked")
public void parse_withUuidType_expect_uuidTypeReturned() {
ScalarType<?> scalarType = factory.typeFor(UUID.class, true);
List<UUID> input = new ArrayList<>();
input.add(UUID.randomUUID());
input.add(UUID.randomUUID());
String formatToJson = scalarType.format(input);
Object parsed = scalarType.parse(formatToJson);
assertThat(parsed).isEqualTo(input);
}
@Test
@SuppressWarnings("unchecked")
public void parse_withIntegerType_expect_longIntegerReturned() {
ScalarType<?> scalarType = factory.typeFor(Integer.class, true);
List<Integer> input = new ArrayList<>();
input.add(2);
input.add(4);
String formatToJson = scalarType.format(input);
Object parsed = scalarType.parse(formatToJson);
assertThat(parsed).isEqualTo(input);
}
@Test
@SuppressWarnings("unchecked")
public void parse_withLongType_expect_longTypeReturned() {
ScalarType<?> scalarType = factory.typeFor(Long.class, true);
List<Long> input = new ArrayList<>();
input.add(2L);
input.add(4L);
String formatToJson = scalarType.format(input);
Object parsed = scalarType.parse(formatToJson);
assertThat(parsed).isEqualTo(input);
}
@Test
public void parse_withDoubleType_expect_longTypeReturned() {
ScalarType<?> scalarType = factory.typeFor(Double.class, true);
List<Double> input = new ArrayList<>();
input.add(2D);
input.add(4D);
String formatToJson = scalarType.format(input);
Object parsed = scalarType.parse(formatToJson);
assertThat(parsed).isEqualTo(input);
}
@Test
public void jsonRead_withUuidType() throws IOException {
ScalarType<?> scalarType = factory.typeFor(UUID.class, true);
List<UUID> input = new ArrayList<>();
input.add(UUID.randomUUID());
input.add(UUID.randomUUID());
String asJson = EJson.write(input);
JsonParser parser = DB.json().createParser(new StringReader(asJson));
Object parsed = scalarType.jsonRead(parser);
assertThat(parsed).isEqualTo(input);
}
}
@@ -1,8 +1,16 @@
package io.ebeaninternal.server.type;
import com.fasterxml.jackson.core.JsonParser;
import io.ebean.DB;
import io.ebean.core.type.ScalarType;
import io.ebean.text.json.EJson;
import org.junit.Test;
import java.io.IOException;
import java.io.StringReader;
import java.sql.SQLException;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.UUID;
import static org.assertj.core.api.Assertions.assertThat;
@@ -49,4 +57,82 @@ public class ScalarTypeArraySetH2Test extends BasePlatformArrayTypeFactoryTest {
assertBindNullTo_Null(factory.typeFor(UUID.class, true));
}
@Test
@SuppressWarnings("unchecked")
public void parse_withUuidType_expect_uuidTypeReturned() {
ScalarType<?> scalarType = factory.typeFor(UUID.class, true);
Set<UUID> input = new LinkedHashSet<>();
input.add(UUID.randomUUID());
input.add(UUID.randomUUID());
String formatToJson = scalarType.format(input);
Set<UUID> parsed = (Set<UUID>)scalarType.parse(formatToJson);
assertThat(parsed).isEqualTo(input);
}
@Test
@SuppressWarnings("unchecked")
public void parse_withIntegerType_expect_longIntegerReturned() {
ScalarType<?> scalarType = factory.typeFor(Integer.class, true);
Set<Integer> input = new LinkedHashSet<>();
input.add(2);
input.add(4);
String formatToJson = scalarType.format(input);
Set<Integer> parsed = (Set<Integer>)scalarType.parse(formatToJson);
assertThat(parsed).isEqualTo(input);
}
@Test
@SuppressWarnings("unchecked")
public void parse_withLongType_expect_longTypeReturned() {
ScalarType<?> scalarType = factory.typeFor(Long.class, true);
Set<Long> input = new LinkedHashSet<>();
input.add(2L);
input.add(4L);
String formatToJson = scalarType.format(input);
Set<Long> parsed = (Set<Long>)scalarType.parse(formatToJson);
assertThat(parsed).isEqualTo(input);
}
@Test
public void parse_withDoubleType_expect_longTypeReturned() {
ScalarType<?> scalarType = factory.typeFor(Double.class, true);
Set<Double> input = new LinkedHashSet<>();
input.add(2D);
input.add(4D);
String formatToJson = scalarType.format(input);
Object parsed = scalarType.parse(formatToJson);
assertThat(parsed).isEqualTo(input);
}
@Test
public void jsonRead_withUuidType() throws IOException {
ScalarType<?> scalarType = factory.typeFor(UUID.class, true);
Set<UUID> input = new LinkedHashSet<>();
input.add(UUID.randomUUID());
input.add(UUID.randomUUID());
String asJson = EJson.write(input);
JsonParser parser = DB.json().createParser(new StringReader(asJson));
Object parsed = scalarType.jsonRead(parser);
assertThat(parsed).isEqualTo(input);
}
}
@@ -144,6 +144,69 @@ public class TestAnnotationBase extends BaseTestCase {
}
}
@Entity
public static class TestJakartaAnnotationBaseEntity extends MappedBaseEntity {
@Where(clause = "SELECT 'mysql' from 1", platforms = Platform.MYSQL)
@Where(clause = "SELECT 'h2' from 1", platforms = H2)
@Where(clause = "SELECT 'other' from 1")
private String direct;
@MetaTest
private String meta;
@MetaTest
@Where(clause = "SELECT 'oracle' from 1", platforms = Platform.ORACLE)
private String mixed;
@jakarta.validation.constraints.Size.List({
@jakarta.validation.constraints.Size(max = 10, message = "max length for you is 10"),
@jakarta.validation.constraints.Size(min = 1),
@jakarta.validation.constraints.Size(max = 40, message = "max value for you is 40", groups = ValidationGroupSomething.class)
})
private String constraintAnnotation;
@NotNull
private String null1;
@NotNull(groups = ValidationGroupSomething.class)
private String null2;
private String null3;
public String getConstraintAnnotation() {
return constraintAnnotation;
}
public void setConstraintAnnotation(String constraintAnnotation) {
this.constraintAnnotation = constraintAnnotation;
}
public String getNull1() {
return null1;
}
public void setNull1(String null1) {
this.null1 = null1;
}
public String getNull2() {
return null2;
}
public void setNull2(String null2) {
this.null2 = null2;
}
public String getNull3() {
return null3;
}
public void setNull3(String null3) {
this.null3 = null3;
}
}
@Test
public void testFindMaxSize() throws SecurityException {
@@ -152,6 +215,13 @@ public class TestAnnotationBase extends BaseTestCase {
assertEquals(40, bp.getDbLength());
}
@Test
public void testFindJakartaMaxSize() throws SecurityException {
BeanDescriptor<TestJakartaAnnotationBaseEntity> descriptor = spiEbeanServer().getBeanDescriptor(TestJakartaAnnotationBaseEntity.class);
BeanProperty bp = descriptor.findProperty("constraintAnnotation");
assertEquals(40, bp.getDbLength());
}
@Test
public void annotationClassIndexes() throws SecurityException {
BeanDescriptor<TestAnnotationBaseEntity> descriptor = spiEbeanServer().getBeanDescriptor(TestAnnotationBaseEntity.class);
@@ -173,6 +243,20 @@ public class TestAnnotationBase extends BaseTestCase {
assertTrue(bp.isNullable());
}
@Test
public void testJakartaNotNullWithGroup() throws SecurityException {
BeanDescriptor<TestJakartaAnnotationBaseEntity> descriptor = spiEbeanServer().getBeanDescriptor(TestJakartaAnnotationBaseEntity.class);
BeanProperty bp = descriptor.findProperty("null1");
assertFalse(bp.isNullable());
bp = descriptor.findProperty("null2");
assertTrue(bp.isNullable());
bp = descriptor.findProperty("null3");
assertTrue(bp.isNullable());
}
@Test
public void testFindAnnotation() throws NoSuchFieldException, SecurityException {
@@ -1,6 +1,7 @@
package org.tests.model.array;
import io.ebean.annotation.Cache;
import io.ebean.annotation.DbArray;
import io.ebean.annotation.NotNull;
@@ -13,6 +14,7 @@ import java.util.Set;
import java.util.UUID;
@Entity
@Cache
public class EArrayBean {
enum Status {
@@ -1,6 +1,7 @@
package org.tests.model.array;
import io.ebean.annotation.Cache;
import io.ebean.annotation.DbArray;
import javax.persistence.Entity;
@@ -11,6 +12,7 @@ import java.util.Set;
import java.util.UUID;
@Entity
@Cache
public class EArraySetBean {
@Id
@@ -30,6 +32,9 @@ public class EArraySetBean {
@DbArray
Set<Double> doubs;
@DbArray
Set<EArrayBean.Status> status;
@Version
Long version;
@@ -81,6 +86,14 @@ public class EArraySetBean {
this.doubs = doubs;
}
public Set<EArrayBean.Status> getStatus() {
return status;
}
public void setStatus(Set<EArrayBean.Status> status) {
this.status = status;
}
public Long getVersion() {
return version;
}
@@ -46,6 +46,11 @@ public class TestDbArray_asSet extends BaseTestCase {
bean.getOtherIds().add(97L);
bean.setDoubs(doubles);
Set<EArrayBean.Status> status = new LinkedHashSet<>();
status.add(EArrayBean.Status.ONE);
status.add(EArrayBean.Status.TWO);
bean.setStatus(status);
Ebean.save(bean);
found = Ebean.find(EArraySetBean.class, bean.getId());
@@ -154,4 +159,31 @@ public class TestDbArray_asSet extends BaseTestCase {
Ebean.saveAll(all);
Ebean.deleteAll(all);
}
@Test
@IgnorePlatform(Platform.HANA)
public void hitCache() {
Set<UUID> uids = new LinkedHashSet<>();
uids.add(UUID.randomUUID());
uids.add(UUID.randomUUID());
Set<EArrayBean.Status> statuses = new LinkedHashSet<>();
statuses.add(EArrayBean.Status.ONE);
statuses.add(EArrayBean.Status.THREE);
EArraySetBean bean = new EArraySetBean();
bean.setName("hitCache");
bean.setUids(uids);
bean.setStatus(statuses);
Ebean.save(bean);
// load cache
Ebean.find(EArraySetBean.class, bean.getId());
// hit cache
EArraySetBean found = Ebean.find(EArraySetBean.class, bean.getId());
assertThat(found.getUids()).isEqualTo(uids);
assertThat(found.getStatus()).isEqualTo(statuses);
}
}
@@ -249,4 +249,31 @@ public class TestDbArray_basic extends BaseTestCase {
assertThat(found.getIntEnums()).containsExactly(null, IntEnum.ZERO, null, IntEnum.TWO);
Ebean.delete(bean);
}
@Test
@IgnorePlatform(Platform.HANA)
public void hitCache() {
List<UUID> uids = new ArrayList<>();
uids.add(UUID.randomUUID());
uids.add(UUID.randomUUID());
List<EArrayBean.Status> statuses = new ArrayList<>();
statuses.add(EArrayBean.Status.ONE);
statuses.add(EArrayBean.Status.THREE);
EArrayBean bean = new EArrayBean();
bean.setName("hitCache");
bean.setUids(uids);
bean.setStatuses(statuses);
Ebean.save(bean);
// load cache
Ebean.find(EArrayBean.class, bean.getId());
// hit cache
EArrayBean found = Ebean.find(EArrayBean.class, bean.getId());
assertThat(found.getUids()).isEqualTo(uids);
assertThat(found.getStatuses()).isEqualTo(statuses);
}
}
@@ -2,6 +2,7 @@ package org.tests.query;
import io.ebean.BaseTestCase;
import io.ebean.DB;
import io.ebean.FetchConfig;
import io.ebean.PagedList;
import org.assertj.core.util.Lists;
import org.ebeantest.LoggedSqlCollector;
@@ -246,4 +247,102 @@ public class TestQueryFindNative extends BaseTestCase {
}
}
@Test
public void findNativeWithOneFetchQuery() {
ResetBasicData.reset();
String sql = "select * from o_customer";
LoggedSqlCollector.start();
List<Customer> result = DB.findNative(Customer.class, sql)
.fetchQuery("contacts")
.findList();
assertThat(result).isNotEmpty();
List<String> loggedSql = LoggedSqlCollector.stop();
if (isH2()) {
assertThat(loggedSql).hasSize(2);
assertThat(loggedSql.get(0)).contains("from o_customer");
assertThat(loggedSql.get(1)).contains("from contact");
}
}
@Test
public void findNativeWithOneFetch() {
ResetBasicData.reset();
String sql = "select * from o_customer";
LoggedSqlCollector.start();
List<Customer> result = DB.findNative(Customer.class, sql)
.fetch("contacts", "firstName, lastName")
.findList();
assertThat(result).isNotEmpty();
List<String> loggedSql = LoggedSqlCollector.stop();
if (isH2()) {
assertThat(loggedSql).hasSize(2);
assertThat(loggedSql.get(0)).contains("from o_customer");
assertThat(loggedSql.get(1)).contains("select t0.customer_id, t0.id, t0.first_name, t0.last_name from contact t0 where");
}
}
@Test
public void findNativeWithMultipleFetchQuery() {
ResetBasicData.reset();
String sql = "select * from o_customer";
LoggedSqlCollector.start();
List<Customer> result = DB.findNative(Customer.class, sql)
.fetchQuery("orders")
.fetchQuery("contacts")
.findList();
assertThat(result).isNotEmpty();
List<String> loggedSql = LoggedSqlCollector.stop();
if (isH2()) {
assertThat(loggedSql).hasSize(3);
assertThat(loggedSql.get(0)).contains("from o_customer");
assertThat(loggedSql).anyMatch(s -> s.contains("from o_order"));
assertThat(loggedSql).anyMatch(s -> s.contains("from contact"));
}
}
@Test
public void findNativeWithMultipleFetch() {
ResetBasicData.reset();
String sql = "select * from o_customer";
LoggedSqlCollector.start();
List<Customer> result = DB.findNative(Customer.class, sql)
// with nativeSql fetch (default) are converted to fetchQuery()
.fetch("orders")
.fetch("contacts", FetchConfig.ofDefault())
.findList();
assertThat(result).isNotEmpty();
List<String> loggedSql = LoggedSqlCollector.stop();
if (isH2()) {
assertThat(loggedSql).hasSize(3);
assertThat(loggedSql.get(0)).contains("from o_customer");
assertThat(loggedSql).anyMatch(s -> s.contains("from o_order"));
assertThat(loggedSql).anyMatch(s -> s.contains("from contact"));
}
}
}
@@ -11,6 +11,7 @@ import org.junit.Assert;
import org.junit.Test;
import java.util.List;
import java.util.Map;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
@@ -54,6 +55,17 @@ public class TestQueryJoinManyNonRoot extends BaseTestCase {
assertTrue(sql.contains("join o_customer t1 on t1.id "));
assertTrue(sql.contains("left join contact t2 on"));
Map<Integer, Order> ordersById = Ebean.find(Order.class).setMapKey("id").where().gt("id", 0).query().findMap();
for (Order o: list) {
int withoutFetch = ordersById.get(o.getId()).getCustomer().getContacts().size();
int withFetch = o.getCustomer().getContacts().size();
assertEquals(String.format("order.customer.contacts for order %d did not match. " +
"Items without fetch: %d, with fetch: %d", o.getId(), withoutFetch, withFetch),
withoutFetch,
withFetch);
}
// select t0.id c0, t0.status c1, t0.order_date c2, t0.ship_date c3, t1.name c4, t0.cretime c5, t0.updtime c6,
// t1.id c7, t1.status c8, t1.name c9, t1.smallnote c10, t1.anniversary c11, t1.cretime c12, t1.updtime c13, t1.billing_address_id c14, t1.shipping_address_id c15,
// t2.id c16, t2.first_name c17, t2.last_name c18, t2.phone c19, t2.mobile c20, t2.email c21, t2.cretime c22, t2.updtime c23, t2.customer_id c24, t2.group_id c25
+3 -3
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>12.7.1-SNAPSHOT</version>
<version>12.7.3-SNAPSHOT</version>
</parent>
<name>ebean ddl generation</name>
@@ -29,14 +29,14 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core-type</artifactId>
<version>12.7.1-SNAPSHOT</version>
<version>12.7.3-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>12.7.1-SNAPSHOT</version>
<version>12.7.3-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>12.7.1-SNAPSHOT</version>
<version>12.7.3-SNAPSHOT</version>
</parent>
<name>ebean external mapping api</name>
+4 -4
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>12.7.1-SNAPSHOT</version>
<version>12.7.3-SNAPSHOT</version>
</parent>
<!-- <parent>-->
<!-- <groupId>org.avaje</groupId>-->
@@ -33,7 +33,7 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-externalmapping-api</artifactId>
<version>12.7.1-SNAPSHOT</version>
<version>12.7.3-SNAPSHOT</version>
</dependency>
<dependency>
@@ -59,14 +59,14 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>12.7.1-SNAPSHOT</version>
<version>12.7.3-SNAPSHOT</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-ddl-generator</artifactId>
<version>12.7.1-SNAPSHOT</version>
<version>12.7.3-SNAPSHOT</version>
<scope>test</scope>
</dependency>
+4 -5
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>12.7.1-SNAPSHOT</version>
<version>12.7.3-SNAPSHOT</version>
</parent>
<name>ebean postgis</name>
@@ -15,7 +15,6 @@
<properties>
<postgis.jdbc.version>2.2.1</postgis.jdbc.version>
<jackson-core.version>2.11.3</jackson-core.version>
</properties>
<dependencies>
@@ -24,14 +23,14 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>12.7.1-SNAPSHOT</version>
<version>12.7.3-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>${jackson-core.version}</version>
<version>${jackson.version}</version>
<optional>true</optional>
</dependency>
@@ -75,7 +74,7 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-test</artifactId>
<version>12.7.1-SNAPSHOT</version>
<version>12.7.3-SNAPSHOT</version>
<scope>test</scope>
</dependency>
+11 -14
View File
@@ -4,18 +4,8 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>12.7.1-SNAPSHOT</version>
<version>12.7.3-SNAPSHOT</version>
</parent>
<!-- <parent>-->
<!-- <groupId>org.avaje</groupId>-->
<!-- <artifactId>java8-oss</artifactId>-->
<!-- <version>2.3</version>-->
<!-- </parent>-->
<scm>
<developerConnection>scm:git:git@github.com:ebean-orm/ebean.git</developerConnection>
<tag>ebean-parent-12.6.5</tag>
</scm>
<name>ebean querybean</name>
<description>Ebean querybean support</description>
@@ -27,7 +17,7 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>12.7.1-SNAPSHOT</version>
<version>12.7.3-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
@@ -67,14 +57,21 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-ddl-generator</artifactId>
<version>12.7.1-SNAPSHOT</version>
<version>12.7.3-SNAPSHOT</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>querybean-generator</artifactId>
<version>12.7.1-SNAPSHOT</version>
<version>12.7.3-SNAPSHOT</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-test</artifactId>
<version>12.7.3-SNAPSHOT</version>
<scope>test</scope>
</dependency>
@@ -121,12 +121,12 @@ public class PBaseCompareable<R, T> extends PBaseValueEqual<R, T> {
* Greater or equal to lower value and strictly less than upper value.
* <p>
* This is generally preferable over Between for date and datetime types
* as SQL Between is inclusive on the upper bound (<=) and generally we
* need the upper bound to be exclusive (<).
* as SQL Between is inclusive on the upper bound ({@code <= }) and generally
* we need the upper bound to be exclusive ({@code < }).
* </p>
*
* @param lower the lower bind value (>=)
* @param upper the upper bind value (<)
* @param lower the lower bind value ({@code >= })
* @param upper the upper bind value ({@code < })
* @return the root query bean instance
*/
public final R inRange(T lower, T upper) {
@@ -1,5 +1,7 @@
package io.ebean.typequery;
import io.ebean.Query;
import java.util.Collection;
/**
@@ -224,4 +226,25 @@ public abstract class PBaseValueEqual<R, T> extends TQPropertyBase<R> {
expr().in(_name, values);
return _root;
}
/**
* Is in the result of a subquery.
*
* @param subQuery values provided by a subQuery
* @return the root query bean instance
*/
public final R in(Query<?> subQuery) {
expr().in(_name, subQuery);
return _root;
}
/**
* Is in the result of a subquery. Synonym for in().
*
* @param subQuery values provided by a subQuery
* @return the root query bean instance
*/
public final R isIn(Query<?> subQuery) {
return in(subQuery);
}
}
@@ -1,6 +1,11 @@
package io.ebean.typequery;
import io.ebean.ExpressionList;
import io.ebean.FetchConfig;
import io.ebeaninternal.api.SpiQueryFetch;
import java.util.LinkedHashSet;
import java.util.Set;
/**
* Base type for associated beans.
@@ -11,6 +16,11 @@ import io.ebean.ExpressionList;
@SuppressWarnings("rawtypes")
public abstract class TQAssocBean<T, R> extends TQProperty<R> {
private static final FetchConfig FETCH_DEFAULT = FetchConfig.ofDefault();
private static final FetchConfig FETCH_QUERY = FetchConfig.ofQuery();
private static final FetchConfig FETCH_LAZY = FetchConfig.ofLazy();
private static final FetchConfig FETCH_CACHE = FetchConfig.ofCache();
/**
* Construct with a property name and root instance.
*
@@ -88,9 +98,8 @@ public abstract class TQAssocBean<T, R> extends TQProperty<R> {
/**
* Deprecated in favor of fetch().
*
* @deprecated
*/
@Deprecated
public R fetchAll() {
return fetch();
}
@@ -100,8 +109,7 @@ public abstract class TQAssocBean<T, R> extends TQProperty<R> {
*/
@SafeVarargs
protected final R fetchProperties(TQProperty<?>... props) {
((TQRootBean) _root).query().fetch(_name, properties(props));
return _root;
return fetchWithProperties(FETCH_DEFAULT, props);
}
/**
@@ -109,8 +117,7 @@ public abstract class TQAssocBean<T, R> extends TQProperty<R> {
*/
@SafeVarargs
protected final R fetchQueryProperties(TQProperty<?>... props) {
((TQRootBean) _root).query().fetchQuery(_name, properties(props));
return _root;
return fetchWithProperties(FETCH_QUERY, props);
}
/**
@@ -118,8 +125,7 @@ public abstract class TQAssocBean<T, R> extends TQProperty<R> {
*/
@SafeVarargs
protected final R fetchCacheProperties(TQProperty<?>... props) {
((TQRootBean) _root).query().fetchCache(_name, properties(props));
return _root;
return fetchWithProperties(FETCH_CACHE, props);
}
/**
@@ -127,23 +133,26 @@ public abstract class TQAssocBean<T, R> extends TQProperty<R> {
*/
@SafeVarargs
protected final R fetchLazyProperties(TQProperty<?>... props) {
((TQRootBean) _root).query().fetchLazy(_name, properties(props));
return fetchWithProperties(FETCH_LAZY, props);
}
@SafeVarargs
private final R fetchWithProperties(FetchConfig config, TQProperty<?>... props) {
spiQuery().fetchProperties(_name, properties(props), config);
return _root;
}
/**
* Append the properties as a comma delimited string.
*/
private final SpiQueryFetch spiQuery() {
return (SpiQueryFetch)((TQRootBean) _root).query();
}
@SafeVarargs
protected final String properties(TQProperty<?>... props) {
StringBuilder selectProps = new StringBuilder(50);
for (int i = 0; i < props.length; i++) {
if (i > 0) {
selectProps.append(",");
}
selectProps.append(props[i].propertyName());
private final Set<String> properties(TQProperty<?>... props) {
Set<String> set = new LinkedHashSet<>();
for (TQProperty<?> prop : props) {
set.add(prop.propertyName());
}
return selectProps.toString();
return set;
}
/**
@@ -25,6 +25,7 @@ import io.ebean.search.TextQueryString;
import io.ebean.search.TextSimple;
import io.ebean.service.SpiFetchGroupQuery;
import io.ebean.text.PathProperties;
import io.ebeaninternal.api.SpiQueryFetch;
import io.ebeaninternal.server.util.ArrayStack;
import javax.annotation.Nonnull;
@@ -32,6 +33,7 @@ import javax.annotation.Nullable;
import java.sql.Connection;
import java.sql.Timestamp;
import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
@@ -41,55 +43,39 @@ import java.util.function.Predicate;
import java.util.stream.Stream;
/**
* Base root query bean.
* Base root query bean providing common features for all root query beans.
* <p>
* With code generation for each entity bean type a query bean is created that extends this.
* For each entity bean querybean-generator generates a query bean that extends TQRootBean.
* <p>
* Provides common features for all root query beans
*
* </p>
* <p>
* <h2>Example - QCustomer extends TQRootBean</h2>
* <p>
* These 'query beans' like QCustomer are generated using the <code>avaje-ebeanorm-typequery-generator</code>.
* </p>
* <pre>{@code
*
* public class QCustomer extends TQRootBean<Customer,QCustomer> {
*
* // properties
* public PLong<QCustomer> id;
*
* public PString<QCustomer> name;
* ...
*
* }</pre>
* <p>
* <h2>Example - usage of QCustomer</h2>
* <pre>{@code
*
* Date fiveDaysAgo = ...
* Date fiveDaysAgo = ...
*
* List<Customer> customers =
* new QCustomer()
* .name.ilike("rob")
* .status.equalTo(Customer.Status.GOOD)
* .registered.after(fiveDaysAgo)
* .contacts.email.endsWith("@foo.com")
* .orderBy()
* .name.asc()
* .registered.desc()
* .findList();
* List<Customer> customers =
* new QCustomer()
* .name.ilike("rob")
* .status.equalTo(Customer.Status.GOOD)
* .registered.after(fiveDaysAgo)
* .contacts.email.endsWith("@foo.com")
* .orderBy()
* .name.asc()
* .registered.desc()
* .findList();
*
* }</pre>
* <p>
* <h2>Resulting SQL where</h2>
* <p>
* <pre>{@code sql
* <pre>{@code
*
* where lower(t0.name) like ? and t0.status = ? and t0.registered > ? and u1.email like ?
* order by t0.name, t0.registered desc;
* where lower(t0.name) like ? and t0.status = ? and t0.registered > ? and u1.email like ?
* order by t0.name, t0.registered desc;
*
* --bind(rob,GOOD,Mon Jul 27 12:05:37 NZST 2015,%@foo.com)
*
* --bind(rob,GOOD,Mon Jul 27 12:05:37 NZST 2015,%@foo.com)
* }</pre>
*
* @param <T> the entity bean type (normal entity bean type e.g. Customer)
@@ -278,22 +264,21 @@ public abstract class TQRootBean<T, R> {
*/
@SafeVarargs
public final R select(TQProperty<R>... properties) {
StringBuilder selectProps = new StringBuilder(50);
for (int i = 0; i < properties.length; i++) {
if (i > 0) {
selectProps.append(",");
}
selectProps.append(properties[i].propertyName());
}
query.select(selectProps.toString());
((SpiQueryFetch) query).selectProperties(properties(properties));
return root;
}
private Set<String> properties(TQProperty<R>[] properties) {
Set<String> props = new LinkedHashSet<>();
for (TQProperty<R> property : properties) {
props.add(property.propertyName());
}
return props;
}
/**
* Specify a path to load including all its properties.
* <p>
* The same as {@link #fetch(String, String)} with the fetchProperties as "*".
* </p>
*
* <pre>{@code
*
* List<Customer> customers =
@@ -8,7 +8,7 @@ import javax.validation.constraints.Size;
* Product entity bean.
*/
@Entity
@Table(name = "o_product")
@Table(name = "o_product", schema = "foo")
public class Product extends BaseModel {
@Size(max = 20)
@@ -0,0 +1,78 @@
package org.querytest;
import io.ebean.DB;
import org.example.domain.Customer;
import org.example.domain.Order;
import org.example.domain.OrderDetail;
import org.example.domain.Product;
import org.junit.Test;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
public class FindNative_withSchema {
@Test
public void test_rootLevelUsesSchema() {
Product p = new Product();
p.setName("prod1");
p.setSku("p1");
p.save();
String sql = "select * from foo.o_product p where p.sku = ?";
Product product = DB.findNative(Product.class, sql)
.setParameter("p1")
.findOne();
assertThat(product).isNotNull();
assertThat(product.getName()).isEqualTo("prod1");
}
@Test
public void test_joinToSchema() {
Order order = setupData();
String sql = "select l.*, p.* " +
" from o_order_detail l " +
" join foo.o_product p on p.id = l.product_id " +
" where l.order_id = ?";
List<OrderDetail> lines = DB.findNative(OrderDetail.class, sql)
.setParameter(order.getId())
.findList();
assertThat(lines).hasSize(1);
OrderDetail line = lines.get(0);
Product product = line.getProduct();
// check normal bean population
assertThat(line.getOrderQty()).isEqualTo(10);
assertThat(line.getOrder().getId()).isEqualTo(order.getId());
// check associated bean with schema is populated
assertThat(product).isNotNull();
assertThat(product.getName()).isEqualTo("prod2");
}
private Order setupData() {
Product p = new Product();
p.setName("prod2");
p.setSku("p2");
p.save();
Customer customer = new Customer();
customer.setName("junk");
customer.save();
Order order = new Order();
order.setCustomer(customer);
OrderDetail line = new OrderDetail(p, 10, 10.0);
order.getDetails().add(line);
order.save();
return order;
}
}
@@ -1,11 +1,17 @@
package org.querytest;
import io.ebean.DB;
import io.ebean.FetchGroup;
import io.ebean.test.LoggedSql;
import org.example.domain.Order;
import org.example.domain.query.QCustomer;
import org.example.domain.query.QOrder;
import org.junit.Test;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
public class QOrderTest {
private static final QCustomer cu = QCustomer.alias();
@@ -17,10 +23,14 @@ public class QOrderTest {
.customer.fetchCache(cu.name, cu.status, cu.registered, cu.comments)
.buildFetchGroup();
private static final FetchGroup<Order> fg2 = QOrder.forFetchGroup()
.select(or.status)
.customer.fetch(cu.name)
.buildFetchGroup();
@Test
public void fetchCache() {
new QOrder()
.status.eq(Order.Status.NEW)
.customer.fetchCache(cu.name, cu.registered)
@@ -35,10 +45,72 @@ public class QOrderTest {
@Test
public void viaFetchGraph() {
DB.getDefault();
LoggedSql.start();
new QOrder()
.status.eq(Order.Status.NEW)
.select(fg)
.findList();
final List<String> sql = LoggedSql.stop();
assertThat(sql).hasSize(1);
assertThat(sql.get(0)).contains("select t0.id, t0.status, t0.ship_date, t0.customer_id from o_order t0 where");
}
@Test
public void viaFetchGraph_withJoin() {
DB.getDefault();
LoggedSql.start();
new QOrder()
.status.eq(Order.Status.NEW)
.select(fg2)
.findList();
final List<String> sql = LoggedSql.stop();
assertThat(sql).hasSize(1);
assertThat(sql.get(0)).contains("select t0.id, t0.status, t1.id, t1.name from o_order t0 join be_customer t1 on t1.id = t0.customer_id where");
}
@Test
public void select_partial() {
DB.getDefault();
LoggedSql.start();
final QOrder o = QOrder.alias();
new QOrder()
.select(o.status, o.orderDate)
.findList();
final List<String> sql = LoggedSql.stop();
assertThat(sql).hasSize(1);
assertThat(sql.get(0)).contains("select t0.id, t0.status, t0.order_date from o_order t0");
}
@Test
public void fetch_partial() {
DB.getDefault();
LoggedSql.start();
final QOrder o = QOrder.alias();
final QCustomer c = QCustomer.alias();
new QOrder()
.select(o.status)
.customer.fetch(c.email, c.name)
.findList();
final List<String> sql = LoggedSql.stop();
assertThat(sql).hasSize(1);
assertThat(sql.get(0)).contains("select t0.id, t0.status, t1.id, t1.email, t1.name from o_order t0 join be_customer t1 on t1.id = t0.customer_id");
}
}
@@ -1,5 +1,6 @@
ebean.ddl.generate=true
ebean.ddl.run=true
ebean.ddl.initSql=init-db.sql
datasource.default=h2
@@ -0,0 +1 @@
create schema foo;
+6 -6
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>12.7.1-SNAPSHOT</version>
<version>12.7.3-SNAPSHOT</version>
</parent>
<artifactId>ebean-redis</artifactId>
@@ -22,35 +22,35 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>12.7.1-SNAPSHOT</version>
<version>12.7.3-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>12.7.1-SNAPSHOT</version>
<version>12.7.3-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>12.7.1-SNAPSHOT</version>
<version>12.7.3-SNAPSHOT</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>querybean-generator</artifactId>
<version>12.7.1-SNAPSHOT</version>
<version>12.7.3-SNAPSHOT</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-test</artifactId>
<version>12.7.1-SNAPSHOT</version>
<version>12.7.3-SNAPSHOT</version>
<scope>test</scope>
</dependency>
+12 -19
View File
@@ -4,27 +4,13 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>12.7.1-SNAPSHOT</version>
<version>12.7.3-SNAPSHOT</version>
</parent>
<!-- <parent>-->
<!-- <groupId>org.avaje</groupId>-->
<!-- <artifactId>java8-oss</artifactId>-->
<!-- <version>2.2</version>-->
<!-- </parent>-->
<scm>
<developerConnection>scm:git:git@github.com:ebean-orm/ebean.git</developerConnection>
<tag>ebean-parent-12.6.5</tag>
</scm>
<name>ebean test</name>
<description>Testing support for Ebean</description>
<artifactId>ebean-test</artifactId>
<properties>
<jackson-databind.version>2.11.3</jackson-databind.version>
</properties>
<dependencies>
<dependency>
@@ -43,27 +29,34 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>12.7.1-SNAPSHOT</version>
<version>12.7.3-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-ddl-generator</artifactId>
<version>12.7.1-SNAPSHOT</version>
<version>12.7.3-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>${jackson-databind.version}</version>
<version>${jackson.version}</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
<version>${jackson.version}</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-test-docker</artifactId>
<version>4.1</version>
<version>4.2</version>
</dependency>
<dependency>
@@ -2,34 +2,63 @@ package io.ebean.test;
import io.ebean.DB;
import java.io.IOException;
import java.io.InputStream;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Helper for testing to assert that the JSON form of an entity
* or list of entities match a String / typically test resource.
* or list of entities is as expected.
* <p>
* Using assertContains() we can match the JSON form of an entity
* against a subset of JSON content. Typically the subset of JSON
* excludes generated properties like id, when created and
* when modified properties.
* </p>
*
* <h3>Assert contains</h3>
* <pre>{@code
*
* DbJson.of(order)
* .assertContains("/order-partial.json");
*
* }</pre>
*
*
* <h3>Assert content matches</h3>
* <p>
* Using assertContentMatches() we are doing an exact content match.
* We typically need to replace generated property values. This assert
* will start to fail if the model changes like adding a property to
* the entity so we should use it less widely due to the maintenance
* burden we have with it.
* </p>
*
* <pre>{@code
*
* DbJson.of(timedEntries)
* .replace("id", "eventTime")
* .assertContentMatches("/assertJson/full-1-timed.json");
* DbJson.of(order)
* .replace("id", "whenCreated", "whenModified)
* .assertContentMatches("/order-full.json");
*
* }</pre>
*/
public class DbJson {
/**
* Create a PrettyJson object that has the JSON form of the
* entity bean or beans.
* Create a PrettyJson object that has the JSON form of the entity bean or beans.
*
* <h3>Assert contains</h3>
* <pre>{@code
*
* DbJson.of(timedEntries)
* .replace("id", "eventTime")
* .assertContentMatches("/assertJson/full-1-timed.json");
* DbJson.of(order)
* .assertContains("/order-partial.json");
*
* }</pre>
*
* <h3>Assert content matches</h3>
* <pre>{@code
*
* DbJson.of(order)
* .replace("id", "whenCreated", "whenModified)
* .assertContentMatches("/order-full.json");
*
* }</pre>
*/
@@ -41,12 +70,7 @@ public class DbJson {
* Read the content for the given resource path.
*/
public static String readResource(String resourcePath) {
InputStream is = DbJson.class.getResourceAsStream(resourcePath);
try {
return IOUtils.readUtf8(is).trim();
} catch (IOException e) {
throw new IllegalArgumentException(e);
}
return IOUtils.readResource(resourcePath);
}
/**
@@ -92,7 +116,7 @@ public class DbJson {
}
/**
* Assert the json matches the content at the given resource path.
* Assert the json exactly matches the content at the given resource path.
*
* <pre>{@code
*
@@ -103,7 +127,44 @@ public class DbJson {
* }</pre>
*/
public void assertContentMatches(String resourcePath) {
assertThat(rawJson).isEqualTo(readResource(resourcePath));
assertThat(lineEnd(rawJson)).isEqualTo(lineEnd(readResource(resourcePath)));
}
/**
* Normalise line ending characters to just use new line.
*/
private String lineEnd(String content) {
return content.replace("\r\n", "\n");
}
/**
* Assert the DB json contains the given json content.
* <p>
* With this "contains" check the DB Json can contain more content than what
* it is checked against. Typically the DB json can contain generated properties
* like id values, when created, when modified etc and we leave these out of the
* json content we are checking against.
* </p>
*
* @param json The subset json content that should be contained by the DB json.
*/
public void assertContains(String json) {
Json.assertContains(rawJson, json);
}
/**
* Assert the DB Json contains the Json at the given resource path.
* <p>
* With this "contains" check the DB Json can contain more content than what
* it is checked against. Typically the DB json can contain generated properties
* like id values, when created, when modified etc and we leave these out of the
* json content we are checking against.
* </p>
*
* @param resourcePath The resource path of the JSON content we are checking against.
*/
public void assertContainsResource(String resourcePath) {
assertContains(readResource(resourcePath));
}
}
}
@@ -11,6 +11,18 @@ import java.nio.charset.StandardCharsets;
*/
class IOUtils {
/**
* Read the content for the given resource path.
*/
static String readResource(String resourcePath) {
try {
InputStream is = IOUtils.class.getResourceAsStream(resourcePath);
return IOUtils.readUtf8(is).trim();
} catch (IOException e) {
throw new IllegalArgumentException("Error reading resource " + resourcePath, e);
}
}
/**
* Reads the entire contents of the specified input stream and return them as UTF-8 string.
*/
@@ -0,0 +1,168 @@
package io.ebean.test;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.type.CollectionType;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import java.io.IOException;
import java.util.List;
/**
* Helper to convert to and from json using Jackson object mapper and
* perform some useful asserts based on json content.
*/
public class Json {
/**
* For reading resource json content into a bean, list or jsonNode in a fluid style.
*/
public static class Resource {
private final String resourcePath;
private Resource(String resourcePath) {
this.resourcePath = resourcePath;
}
/**
* Return as a plain bean.
*/
<T> T asBean(Class<T> cls) {
return Json.read(cls, readResource(resourcePath));
}
/**
* Return as a list of beans.
*/
<T> List<T> asList(Class<T> cls) {
return Json.readList(cls, readResource(resourcePath));
}
/**
* Return as a JsonNode.
*/
JsonNode asNode() {
return Json.readNodeFromResource(resourcePath);
}
}
private static final ObjectMapper MAPPER = initMapper();
/**
* For fluid style reading resource json content and return as a
* bean, list of bean or JsonNode.
* <pre>{@code
*
* PlainBean bean =
* Json.resource("/example/plain-list.json").asBean(PlainBean.class);
*
* List<PlainBean> list =
* Json.resource("/example/plain-list.json").asList(PlainBean.class);
*
* JsonNode jsonNode =
* Json.resource("/example/plain-list.json").asJsonNode();
*
* }</pre>
*
* @param resourcePath The resource path where the json content is read from
* @return The resource to convert to a bean or jsonNode etc
*/
public static Resource resource(String resourcePath) {
return new Resource(resourcePath);
}
/**
* Assert all the fields in the expectedJson are present in actualJson and values match.
*/
public static void assertContains(String actualJson, String expectedJson) {
assertContains(readNode(actualJson), readNode(expectedJson));
}
/**
* Assert all the fields in the expectedJson are present in actualJson and values match.
*/
public static void assertContains(JsonNode actualJsonNode, JsonNode expectedJsonNode) {
JsonAssertContains.assertContains(actualJsonNode, expectedJsonNode);
}
/**
* Read the content for the given resource path.
*/
public static String readResource(String resourcePath) {
return IOUtils.readResource(resourcePath);
}
/**
* Return a bean from json content of a resource path.
*/
public static <T> T readFromResource(Class<T> type, String resourcePath) {
return read(type, readResource(resourcePath));
}
/**
* Return a typed object from json content.
*/
public static <T> T read(Class<T> type, String json) {
try {
return MAPPER.readValue(json, type);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
/**
* Return a list of a given type from json content.
*/
public static <T> List<T> readList(Class<T> type, String json) {
final CollectionType collectionType = MAPPER.getTypeFactory().constructCollectionType(List.class, type);
try {
return MAPPER.readValue(json, collectionType);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
/**
* Parse json into a generic JsonNode structure from resource.
*/
public static JsonNode readNodeFromResource(String resourcePath) {
return readNode(IOUtils.readResource(resourcePath));
}
/**
* Parse json into a generic JsonNode structure.
*/
public static JsonNode readNode(String json) {
try {
return MAPPER.readTree(json);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
/**
* Serialize object to string
*/
public static String toJsonString(Object bean) {
try {
return MAPPER.writeValueAsString(bean);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
private static ObjectMapper initMapper() {
return new ObjectMapper()
.registerModule(new JavaTimeModule())
.configure(SerializationFeature.INDENT_OUTPUT, true)
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false)
.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false)
.configure(SerializationFeature.INDENT_OUTPUT, true)
.setSerializationInclusion(JsonInclude.Include.NON_NULL);
}
}
@@ -0,0 +1,118 @@
package io.ebean.test;
import com.fasterxml.jackson.databind.JsonNode;
import org.assertj.core.api.Assertions;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Map;
import java.util.Stack;
/**
* Perform traversal of JsonNodes comparing against an expected JsonNode that
* typically contains a subset of the data (typically excludes any generated properties
* like when modified timestamps etc).
*/
class JsonAssertContains {
private final Stack<String> path = new Stack<>();
private final LinkedList<String> errors = new LinkedList<>();
static void assertContains(JsonNode actualJsonNode, JsonNode expectedJsonNode) {
new JsonAssertContains().contains(actualJsonNode, expectedJsonNode);
}
private void contains(JsonNode actualJsonNode, JsonNode expectedJsonNode) {
checkRecursive(null, actualJsonNode, expectedJsonNode);
if (!errors.isEmpty()) {
String errorsString = String.join("\n", errors);
errorsString += "\nExpected JSON fields: " + expectedJsonNode;
errorsString += "\nActual JSON: " + actualJsonNode;
Assertions.fail(errorsString);
}
}
private void checkRecursive(String name, JsonNode actualJsonNode, JsonNode expectedJsonNode) {
if (name != null) {
path.push(name);
}
if (checkNull(actualJsonNode, expectedJsonNode)) {
if (checkType(actualJsonNode, expectedJsonNode)) {
if (checkArray(actualJsonNode, expectedJsonNode)) {
if (checkObject(actualJsonNode, expectedJsonNode)) {
checkValue(actualJsonNode, expectedJsonNode);
}
}
}
}
if (name != null) {
path.pop();
}
}
private boolean checkNull(JsonNode actualJsonNode, JsonNode expectedJsonNode) {
if (actualJsonNode == null) {
errors.add(String.format("Expected field '%s' to be '%s' but was null", path(), expectedJsonNode));
return false;
}
return true;
}
private boolean checkType(JsonNode actualJsonNode, JsonNode expectedJsonNode) {
if (!expectedJsonNode.getNodeType().equals(actualJsonNode.getNodeType())) {
errors.add(String.format("Expected field '%s' to be of type '%s' but was '%s'", path(), expectedJsonNode.getNodeType(), actualJsonNode.getNodeType()));
return false;
}
return true;
}
private boolean checkArray(JsonNode actualJsonNode, JsonNode expectedJsonNode) {
if (!expectedJsonNode.isArray()) {
return true;
}
for (int i = 0; i < expectedJsonNode.size(); i++) {
checkRecursive("[" + i + "]", actualJsonNode.get(i), expectedJsonNode.get(i));
}
// do not continue (object or scalar type check)
return false;
}
private boolean checkObject(JsonNode actualJsonNode, JsonNode expectedJsonNode) {
if (!expectedJsonNode.isObject()) {
return true;
}
Iterator<Map.Entry<String, JsonNode>> expectedFields = expectedJsonNode.fields();
while (expectedFields.hasNext()) {
Map.Entry<String, JsonNode> expectedField = expectedFields.next();
String expectedKey = expectedField.getKey();
JsonNode actualNode = actualJsonNode.get(expectedKey);
if (actualNode == null) {
errors.add(String.format("Expected field '%s' to be present", path(expectedKey)));
} else {
checkRecursive(expectedKey, actualNode, expectedField.getValue());
}
}
// do not continue (scalar type check)
return false;
}
private void checkValue(JsonNode actualJsonNode, JsonNode expectedJsonNode) {
if (!expectedJsonNode.equals(actualJsonNode)) {
errors.add(String.format("Expected field '%s' to be equal to '%s' but was '%s'", path(), expectedJsonNode, actualJsonNode));
}
}
String path(String expectedKey) {
if (path.isEmpty()) {
return expectedKey;
}
return path() + "." + expectedKey;
}
String path() {
if (path.isEmpty()) {
return "";
}
return String.join(".", path).replace(".[", "[");
}
}
@@ -1,11 +1,13 @@
package io.ebean.test;
import com.fasterxml.jackson.databind.JsonNode;
import io.ebean.DB;
import org.junit.Test;
import org.test.BSimpleWithGen;
import java.util.List;
import static io.ebean.test.DbJson.readResource;
import static org.assertj.core.api.Assertions.assertThat;
@@ -36,6 +38,37 @@ public class DbJsonTest {
//.withPlaceholder("_")
.replace("id", "whenModified")
.assertContentMatches("/bean/example-list.json");
}
@Test
public void assertContains_pass() {
BSimpleWithGen bean = new BSimpleWithGen("something-contains-me", "YeahNah");
DB.save(bean);
BSimpleWithGen found = DB.find(BSimpleWithGen.class, bean.getId());
DbJson.of(found).assertContainsResource("/bean/contains-minimal.json");
DbJson.of(found).assertContains(readResource("/bean/contains-with-version.json"));
DB.delete(bean);
}
@Test
public void asJson() {
BSimpleWithGen bean = new BSimpleWithGen("other");
DB.save(bean);
String asJson = DbJson.of(bean)
.withPlaceholder("\"*Replaced*\"")
.replace("id")
.asJson();
JsonNode node = Json.readNode(asJson);
assertThat(node.get("id").asText()).isEqualTo("*Replaced*");
assertThat(node.get("name").asText()).isEqualTo("other");
DB.delete(bean);
}
}
@@ -0,0 +1,86 @@
package io.ebean.test;
import com.fasterxml.jackson.databind.JsonNode;
import org.junit.Test;
import java.util.stream.Stream;
import static io.ebean.test.Json.readNodeFromResource;
import static org.assertj.core.api.Assertions.assertThat;
public class JsonAssertContainsTest {
@Test
public void assertContains_itself() {
JsonNode original = readNodeFromResource("/contains/original.json");
JsonAssertContains.assertContains(original, original);
}
@Test
public void assertContains_subset() {
JsonNode original = readNodeFromResource("/contains/original.json");
JsonNode expected = readNodeFromResource("/contains/original-subset.json");
JsonAssertContains.assertContains(original, expected);
}
@Test
public void testContainsFails() {
JsonNode original = readNodeFromResource("/contains/original.json");
JsonNode expected = readNodeFromResource("/contains/original-subset-modified.json");
try {
JsonAssertContains.assertContains(original, expected);
} catch (AssertionError e) {
String exceptionMessage = e.getMessage();
Stream.of("Expected field 'someString1' to be equal to '\"aaaa\"' but was '\"string1\"",
"Expected field 'someValue1' to be equal to '99' but was '1'",
"Expected field 'someArray1[0]' to be of type 'STRING' but was 'NUMBER",
"Expected field 'someArray2[0].value1' to be of type 'ARRAY' but was 'NUMBER'",
"Expected field 'someArray2[0].value2' to be of type 'OBJECT' but was 'STRING'",
"Expected field 'someArray2[0].array1[0]' to be '\"1\"' but was null",
"Expected field 'someArray2[0].object1.val5' to be present",
"Expected field 'someArray2[0].object1.val6' to be present",
"Expected field 'someArray2[0].object2' to be of type 'NULL' but was 'OBJECT'",
"Expected field 'someArray2[0].objectNull' to be of type 'OBJECT' but was 'NULL'")
.forEach(assertionError -> assertThat(exceptionMessage).contains(assertionError));
}
}
@Test
public void assertContains_checkNull() {
JsonNode original = readNodeFromResource("/contains/check-null-actual.json");
JsonNode expected = readNodeFromResource("/contains/check-null-expected.json");
try {
JsonAssertContains.assertContains(original, expected);
} catch (AssertionError e) {
String exceptionMessage = e.getMessage();
Stream.of("Expected field 'someNull' to be of type 'NULL' but was 'STRING'",
"Expected field 'extra' to be present")
.forEach(assertionError -> assertThat(exceptionMessage).contains(assertionError));
}
}
@Test
public void assertContains_checkType() {
JsonNode original = readNodeFromResource("/contains/check-type-actual.json");
JsonNode expected = readNodeFromResource("/contains/check-type-expected.json");
try {
JsonAssertContains.assertContains(original, expected);
} catch (AssertionError e) {
String exceptionMessage = e.getMessage();
Stream.of("Expected field 'some' to be of type 'NUMBER' but was 'STRING'")
.forEach(assertionError -> assertThat(exceptionMessage).contains(assertionError));
}
}
@Test
public void path_when_empty() {
JsonAssertContains contains = new JsonAssertContains();
assertThat(contains.path()).isEqualTo("");
assertThat(contains.path("a")).isEqualTo("a");
assertThat(contains.path("b")).isEqualTo("b");
}
}

Some files were not shown because too many files have changed in this diff Show More