Compare commits

..
Author SHA1 Message Date
rbygrave 705e4f4098 [maven-release-plugin] prepare release ebean-parent-12.10.0 2021-07-22 19:03:43 +12:00
rbygrave 1642833fdb Improve javadoc on TQRootBean and generated javadoc on query bean forFetchGroup() method 2021-07-22 18:56:02 +12:00
rbygrave 492bb98e0e #2269 - findSingleAttributeList does not filter soft-deleted record 2021-07-22 17:12:06 +12:00
rbygrave a8456f90db Bump ebean-agent version to 2.10.0 2021-07-22 16:46:57 +12:00
Rob BygraveandGitHub 103b75172d Merge pull request #2267 from FOCONIS/pr/bugfix/connection_closed_on_clob
Retrieve (C|B)LOB on SqlRow construction
2021-07-22 16:44:05 +12:00
rbygrave 0baa3a0eac #2270 - ebean-agent enhancement NPE with MappedSuperclass with no properties that uses named database 2021-07-22 16:38:28 +12:00
rbygrave f26e4b5381 Bump jedis to 3.6.3 (from 3.6.1) 2021-07-22 12:37:42 +12:00
Jonas Pöhler 3d4de4dee5 FIX TestRawSqlService and TestRawSqlBuilder 2021-07-16 15:17:04 +02:00
Jonas Pöhler c621435a28 Merge remote-tracking branch 'ebean/master' into pr/bugfix/connection_closed_on_clob 2021-07-16 13:32:24 +02:00
rbygrave 7408483a43 #2264 - Use of default timezone for OffsetDateTime offsets problematic for unit tests and presentation layer 2021-07-15 17:14:22 +12:00
rbygrave 4d15535381 No functional change - tidy DefaultDbSqlContext 2021-07-13 16:48:45 +12:00
Rob BygraveandGitHub 787c2f90f2 Merge pull request #2188 from FOCONIS/fix-order-by
FIX: orderBy does not work when used on formula property
2021-07-13 16:41:33 +12:00
Rob BygraveandGitHub e590a2149d Merge pull request #2257 from FOCONIS/queries-are-cancelable
Queries are cancelable
2021-07-13 16:11:36 +12:00
rbygrave 8c77740aa7 Bump version tp 12.10.0-SNAPSHOT (with 12.9.4-RC1 ddl generator) 2021-07-13 15:48:10 +12:00
Rob BygraveandGitHub 29582b57f7 Merge pull request #2256 from FOCONIS/many-where-support
Improved @Where support, especially for Many-to-many tables
2021-07-13 15:01:49 +12:00
rbygrave 2763f333d4 #2262 - Tidy tests for Transaction postCommit Callback 2021-07-13 14:48:40 +12:00
rbygrave 8840628d34 #2262 - Transaction postCommit Callback not invoked when the active transaction has no database writes 2021-07-13 14:45:48 +12:00
rbygrave 6fb28d7ded #2255 Specific test for setParameter cannot assigning the value when parameter in selecting section of query 2021-07-13 12:54:21 +12:00
rbygrave be810aa568 [maven-release-plugin] prepare for next development iteration 2021-07-13 12:16:12 +12:00
Roland Praml 824cfa27be Queries are cancelable 2021-06-23 17:11:21 +02:00
Roland Praml 66cb6d5e7d Imporved @Where support, especially for Many-to-many tables 2021-06-21 14:50:15 +02:00
Jonas Pöhler (JPo) 4977aa2ad1 Merge remote-tracking branch 'ebean/master' into pr/bugfix/connection_closed_on_clob 2021-04-26 14:54:32 +02:00
Jonas Pöhler (JPo) dd6a473008 ADD: Test and possible fix for (C)LOBs being handed out of connnection-context
Signed-off-by: Jonas Pöhler (JPo) <jonas.poehler@foconis.de>
2021-03-22 15:59:59 +01:00
Roland Praml ef1143bb70 FIX: orderBy does not work when used on formula property or in conjunction with "exists" query 2021-03-02 15:26:29 +01:00
87 changed files with 1723 additions and 323 deletions
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>12.9.3</version>
<version>12.10.0</version>
</parent>
<name>ebean api</name>
@@ -0,0 +1,19 @@
package io.ebean;
/**
* Defines a cancelable query.
* <p>
* Typically holds a representation of the PreparedStatement to perform the
* actual cancel.
* </p>
*/
public interface CancelableQuery {
/**
* Cancel the query.
* <p>
* For JDBC this translates to calling cancel on the PreparedStatement.
* </p>
*/
void cancel();
}
@@ -38,7 +38,7 @@ import java.util.stream.Stream;
*
* }</pre>
*/
public interface DtoQuery<T> {
public interface DtoQuery<T> extends CancelableQuery {
/**
* Execute the query returning a list.
+1 -10
View File
@@ -177,7 +177,7 @@ import java.util.stream.Stream;
*
* @param <T> the type of Entity bean this query will fetch.
*/
public interface Query<T> {
public interface Query<T> extends CancelableQuery {
/**
* The lock type (strength) to use with query FOR UPDATE row locking.
@@ -291,15 +291,6 @@ public interface Query<T> {
*/
UpdateQuery<T> asUpdate();
/**
* Cancel the query execution if supported by the underlying database and
* driver.
* <p>
* This must be called from a different thread to the query executor.
* </p>
*/
void cancel();
/**
* Return a copy of the query.
* <p>
@@ -37,7 +37,7 @@ import java.util.function.Predicate;
*
* }</pre>
*/
public interface SqlQuery extends Serializable {
public interface SqlQuery extends Serializable, CancelableQuery {
/**
* Execute the query returning a list.
@@ -66,4 +66,17 @@ public class JdbcClose {
logger.warn("Error on connection rollback", e);
}
}
/**
* Cancels the statement
*/
public static void cancel(Statement stmt) {
try {
if (stmt != null) {
stmt.cancel();
}
} catch (SQLException e) {
logger.warn("Error on cancelling statement", e);
}
}
}
+3 -3
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>12.9.3</version>
<version>12.10.0</version>
</parent>
<!-- <parent>-->
<!-- <groupId>org.avaje</groupId>-->
@@ -14,7 +14,7 @@
<scm>
<developerConnection>scm:git:git@github.com:ebean-orm/ebean.git</developerConnection>
<tag>ebean-parent-12.9.3</tag>
<tag>ebean-parent-12.10.0</tag>
</scm>
<name>ebean autotune</name>
@@ -26,7 +26,7 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>12.9.3</version>
<version>12.10.0</version>
<scope>provided</scope>
</dependency>
+17 -17
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>12.9.3</version>
<version>12.10.0</version>
</parent>
<name>ebean bom</name>
@@ -18,8 +18,8 @@
<ebean-migration.version>12.4.0</ebean-migration.version>
<ebean-test-docker.version>4.1</ebean-test-docker.version>
<ebean-datasource.version>7.0</ebean-datasource.version>
<ebean-agent.version>12.9.0</ebean-agent.version>
<ebean-maven-plugin.version>12.9.1</ebean-maven-plugin.version>
<ebean-agent.version>12.10.0</ebean-agent.version>
<ebean-maven-plugin.version>12.10.0</ebean-maven-plugin.version>
</properties>
<dependencyManagement>
@@ -81,88 +81,88 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean</artifactId>
<version>12.9.3</version>
<version>12.10.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>12.9.3</version>
<version>12.10.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>12.9.3</version>
<version>12.10.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core-type</artifactId>
<version>12.9.3</version>
<version>12.10.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-ddl-generator</artifactId>
<version>12.9.3</version>
<version>12.10.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-externalmapping-api</artifactId>
<version>12.9.3</version>
<version>12.10.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-externalmapping-xml</artifactId>
<version>12.9.3</version>
<version>12.10.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-autotune</artifactId>
<version>12.9.3</version>
<version>12.10.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>12.9.3</version>
<version>12.10.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>querybean-generator</artifactId>
<version>12.9.3</version>
<version>12.10.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>kotlin-querybean-generator</artifactId>
<version>12.9.3</version>
<version>12.10.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-test</artifactId>
<version>12.9.3</version>
<version>12.10.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-postgis</artifactId>
<version>12.9.3</version>
<version>12.10.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-redis</artifactId>
<version>12.9.3</version>
<version>12.10.0</version>
</dependency>
</dependencies>
+2 -2
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>12.9.3</version>
<version>12.10.0</version>
</parent>
<artifactId>ebean-core-type</artifactId>
@@ -16,7 +16,7 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>12.9.3</version>
<version>12.10.0</version>
</dependency>
<dependency>
+7 -7
View File
@@ -3,7 +3,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>12.9.3</version>
<version>12.10.0</version>
</parent>
<artifactId>ebean-core</artifactId>
@@ -15,7 +15,7 @@
<scm>
<developerConnection>scm:git:git@github.com:ebean-orm/ebean.git</developerConnection>
<tag>ebean-parent-12.9.3</tag>
<tag>ebean-parent-12.10.0</tag>
</scm>
<profiles>
@@ -72,7 +72,7 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-ddl-generator</artifactId>
<version>12.9.0</version>
<version>12.9.4-RC1</version>
<scope>test</scope>
</dependency>
@@ -87,19 +87,19 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>12.9.3</version>
<version>12.10.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core-type</artifactId>
<version>12.9.3</version>
<version>12.10.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-externalmapping-api</artifactId>
<version>12.9.3</version>
<version>12.10.0</version>
</dependency>
<dependency>
@@ -302,7 +302,7 @@
<plugin>
<groupId>io.ebean</groupId>
<artifactId>ebean-maven-plugin</artifactId>
<version>12.9.1</version>
<version>12.10.0</version>
<executions>
<execution>
<id>test</id>
@@ -119,7 +119,7 @@ public class LoadManyRequest extends LoadRequest {
if (extraWhere != null) {
// replace special ${ta} placeholder with the base table alias
// which is always t0 and add the extra where clause
query.where().raw(extraWhere.replace("${ta}", "t0"));
query.where().raw(extraWhere.replace("${ta}", "t0").replace("${mta}", "int_"));
}
query.setLazyLoadForParents(many);
@@ -0,0 +1,26 @@
package io.ebeaninternal.api;
import javax.persistence.PersistenceException;
import io.ebean.CancelableQuery;
/**
* Cancellable query, that has a delegate.
*
* @author Roland Praml, FOCONIS AG
*
*/
public interface SpiCancelableQuery extends CancelableQuery {
/**
* Checks if the query was cancelled.
* @throws PersistenceException if query was cancelled.
*/
void checkCancelled();
/**
* Set the underlying cancelable query (with the PreparedStatement).
*/
void setCancelableQuery(CancelableQuery cancelableQuery);
}
@@ -17,7 +17,6 @@ import io.ebeaninternal.server.core.SpiOrmQueryRequest;
import io.ebeaninternal.server.deploy.BeanDescriptor;
import io.ebeaninternal.server.deploy.BeanPropertyAssocMany;
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;
@@ -31,7 +30,7 @@ import java.util.Set;
/**
* Object Relational query - Internal extension to Query object.
*/
public interface SpiQuery<T> extends Query<T>, SpiQueryFetch, TxnProfileEventCodes {
public interface SpiQuery<T> extends Query<T>, SpiQueryFetch, TxnProfileEventCodes, SpiCancelableQuery {
enum Mode {
NORMAL(false), LAZYLOAD_MANY(false), LAZYLOAD_BEAN(true), REFRESH_BEAN(true);
@@ -847,16 +846,6 @@ public interface SpiQuery<T> extends Query<T>, SpiQueryFetch, TxnProfileEventCod
*/
ReadEvent getFutureFetchAudit();
/**
* Set the underlying cancelable query (with the PreparedStatement).
*/
void setCancelableQuery(CancelableQuery cancelableQuery);
/**
* Return true if this query has been cancelled.
*/
boolean isCancelled();
/**
* Return the base table to use if user defined on the query.
*/
@@ -3,7 +3,7 @@ package io.ebeaninternal.api;
/**
* SQL query binding (for SqlQuery and DtoQuery).
*/
public interface SpiSqlBinding {
public interface SpiSqlBinding extends SpiCancelableQuery {
/**
* Return the named or positioned parameters.
@@ -1,5 +1,6 @@
package io.ebeaninternal.server.core;
import io.ebean.CancelableQuery;
import io.ebean.Transaction;
import io.ebean.util.JdbcClose;
import io.ebeaninternal.api.*;
@@ -12,11 +13,14 @@ import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.concurrent.locks.ReentrantLock;
import javax.persistence.PersistenceException;
/**
* Wraps the objects involved in executing a SQL / Relational Query.
*/
public abstract class AbstractSqlQueryRequest {
public abstract class AbstractSqlQueryRequest implements CancelableQuery {
protected final SpiSqlBinding query;
@@ -36,6 +40,8 @@ public abstract class AbstractSqlQueryRequest {
protected long startNano;
private final ReentrantLock lock = new ReentrantLock();
/**
* Create the BeanFindRequest.
*/
@@ -43,6 +49,7 @@ public abstract class AbstractSqlQueryRequest {
this.server = server;
this.query = query;
this.transaction = (SpiTransaction) t;
this.query.setCancelableQuery(this);
}
/**
@@ -137,24 +144,30 @@ public abstract class AbstractSqlQueryRequest {
}
protected void executeAsSql(Binder binder) throws SQLException {
prepareSql();
Connection conn = transaction.getInternalConnection();
pstmt = conn.prepareStatement(sql);
if (query.getTimeout() > 0) {
pstmt.setQueryTimeout(query.getTimeout());
lock.lock();
try {
query.checkCancelled();
prepareSql();
Connection conn = transaction.getInternalConnection();
pstmt = conn.prepareStatement(sql);
if (query.getTimeout() > 0) {
pstmt.setQueryTimeout(query.getTimeout());
}
if (query.getBufferFetchSizeHint() > 0) {
pstmt.setFetchSize(query.getBufferFetchSizeHint());
}
BindParams bindParams = query.getBindParams();
if (!bindParams.isEmpty()) {
this.bindLog = binder.bind(bindParams, pstmt, conn);
}
if (isLogSql()) {
transaction.logSql(Str.add(TrimLogSql.trim(sql), "; --bind(", bindLog, ")"));
}
} finally {
lock.unlock();
}
if (query.getBufferFetchSizeHint() > 0) {
pstmt.setFetchSize(query.getBufferFetchSizeHint());
}
BindParams bindParams = query.getBindParams();
if (!bindParams.isEmpty()) {
this.bindLog = binder.bind(bindParams, pstmt, conn);
}
if (isLogSql()) {
transaction.logSql(Str.add(TrimLogSql.trim(sql), "; --bind(", bindLog, ")"));
}
setResultSet(pstmt.executeQuery(), null);
query.checkCancelled();
}
/**
@@ -164,4 +177,13 @@ public abstract class AbstractSqlQueryRequest {
return sql;
}
@Override
public void cancel() {
lock.lock();
try {
JdbcClose.cancel(pstmt);
} finally {
lock.unlock();
}
}
}
@@ -1382,7 +1382,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
@Nonnull
@Override
public <T> FutureList<T> findFutureList(Query<T> query, Transaction t) {
SpiQuery<T> spiQuery = (SpiQuery<T>) query;
SpiQuery<T> spiQuery = (SpiQuery<T>) query.copy();
spiQuery.setFutureFetch(true);
// FutureList query always run in it's own persistence content
spiQuery.setPersistenceContext(new DefaultPersistenceContext());
@@ -53,6 +53,7 @@ public final class DtoQueryRequest<T> extends AbstractSqlQueryRequest {
ormQuery.setType(type);
ormQuery.setManualId();
query.setCancelableQuery(ormQuery);
// execute the underlying ORM query returning the ResultSet
SpiResultSet result = server.findResultSet(ormQuery, transaction);
this.pstmt = result.getStatement();
@@ -117,6 +118,7 @@ public final class DtoQueryRequest<T> extends AbstractSqlQueryRequest {
}
public boolean next() throws SQLException {
query.checkCancelled();
return dataReader.next();
}
@@ -1,6 +1,7 @@
package io.ebeaninternal.server.core;
import io.ebean.CacheMode;
import io.ebean.CancelableQuery;
import io.ebean.OrderBy;
import io.ebean.PersistenceContextScope;
import io.ebean.QueryIterator;
@@ -35,7 +36,6 @@ import io.ebeaninternal.server.deploy.DeployPropertyParserMap;
import io.ebeaninternal.server.el.ElPropertyValue;
import io.ebeaninternal.server.loadcontext.DLoadContext;
import io.ebeaninternal.server.query.CQueryPlan;
import io.ebeaninternal.server.query.CancelableQuery;
import io.ebeaninternal.server.transaction.DefaultPersistenceContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -139,6 +139,7 @@ public final class RelationalQueryRequest extends AbstractSqlQueryRequest {
@Override
public boolean next() throws SQLException {
query.checkCancelled();
if (!resultSet.next()) {
return false;
} else {
@@ -2016,6 +2016,13 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType, SpiBeanType {
return owner.getBeanDescriptor(otherType);
}
/**
* Returns true, if the table is managed (i.e. an existing m2m relation).
*/
public boolean isTableManaged(String tableName) {
return owner.isTableManaged(tableName);
}
/**
* Return the order column property.
*/
@@ -445,6 +445,12 @@ public class BeanDescriptorManager implements BeanDescriptorMap, SpiBeanTypeMana
return tableToDescMap.get(tableName.toLowerCase());
}
@Override
public boolean isTableManaged(String tableName) {
return tableToDescMap.get(tableName.toLowerCase()) != null
|| tableToViewDescMap.get(tableName.toLowerCase()) != null;
}
/**
* Invalidate entity beans based on views via their dependent tables.
*/
@@ -76,4 +76,10 @@ public interface BeanDescriptorMap {
* Return true if Jackson core is present on the classpath.
*/
boolean isJacksonCorePresent();
/**
* Returns true, if the given table (or view) is managed by ebean
* (= an entity exists)
*/
boolean isTableManaged(String tableName);
}
@@ -439,6 +439,7 @@ public class BeanPropertyAssocMany<T> extends BeanPropertyAssoc<T> implements ST
@Override
public String getAssocIsEmpty(SpiExpressionRequest request, String path) {
boolean softDelete = targetDescriptor.isSoftDelete();
boolean needsX2Table = softDelete || getExtraWhere() != null;
StringBuilder sb = new StringBuilder(50);
SpiQuery<?> query = request.getQueryRequest().getQuery();
if (hasJoinTable()) {
@@ -446,7 +447,7 @@ public class BeanPropertyAssocMany<T> extends BeanPropertyAssoc<T> implements ST
} else {
sb.append(targetDescriptor.getBaseTable(query.getTemporalMode()));
}
if (softDelete && hasJoinTable()) {
if (needsX2Table && hasJoinTable()) {
sb.append(" x join ");
sb.append(targetDescriptor.getBaseTable(query.getTemporalMode()));
sb.append(" x2 on ");
@@ -461,6 +462,16 @@ public class BeanPropertyAssocMany<T> extends BeanPropertyAssoc<T> implements ST
}
exportedProperties[i].appendWhere(sb, "x.", path);
}
if (getExtraWhere() != null) {
sb.append(" and ");
if (hasJoinTable()) {
sb.append(getExtraWhere().replace("${ta}", "x2").replace("${mta}", "x"));
} else {
sb.append(getExtraWhere().replace("${ta}", "x"));
}
}
if (softDelete) {
String alias = hasJoinTable() ? "x2" : "x";
sb.append(" and ").append(targetDescriptor.getSoftDeletePredicate(alias));
@@ -1061,4 +1072,16 @@ public class BeanPropertyAssocMany<T> extends BeanPropertyAssoc<T> implements ST
public void bindElementValue(SqlUpdate insert, Object value) {
targetDescriptor.bindElementValue(insert, value);
}
/**
* Returns true, if we must create a m2m join table.
*/
public boolean createJoinTable() {
if (hasJoinTable() && getMappedBy() == null) {
// only create on other 'owning' side
return !descriptor.isTableManaged(intersectionJoin.getTable());
} else {
return false;
}
}
}
@@ -10,7 +10,7 @@ public interface DbSqlContext {
/**
* Add a join to the sql query.
*/
void addJoin(String type, String table, TableJoinColumn[] cols, String a1, String a2);
void addJoin(String type, String table, TableJoinColumn[] cols, String a1, String a2, String extraWhere);
/**
* Push the current table alias onto the stack.
@@ -34,6 +34,8 @@ public final class TableJoin {
private final int queryHash;
private final PropertyForeignKey foreignKey;
private final String extraWhere;
public TableJoin(DeployTableJoin deploy) {
this(deploy, null);
@@ -44,6 +46,7 @@ public final class TableJoin {
*/
public TableJoin(DeployTableJoin deploy, PropertyForeignKey foreignKey) {
this.foreignKey = foreignKey;
this.extraWhere = deploy.getExtraWhere();
this.table = InternString.intern(deploy.getTable());
this.type = deploy.getType();
this.inheritInfo = deploy.getInheritInfo();
@@ -57,6 +60,7 @@ public final class TableJoin {
private TableJoin(TableJoin source, String overrideColumn) {
this.foreignKey = null;
this.extraWhere = source.extraWhere;
this.table = source.table;
this.type = source.type;
this.inheritInfo = source.inheritInfo;
@@ -146,7 +150,7 @@ public final class TableJoin {
public SqlJoinType addJoin(SqlJoinType joinType, String a1, String a2, DbSqlContext ctx) {
String joinLiteral = joinType.getLiteral(type);
ctx.addJoin(joinLiteral, table, columns(), a1, a2);
ctx.addJoin(joinLiteral, table, columns(), a1, a2, extraWhere);
return joinType.autoToOuter(type);
}
@@ -86,6 +86,7 @@ public abstract class DeployBeanPropertyAssoc<T> extends DeployBeanProperty {
* collection.
*/
public void setExtraWhere(String extraWhere) {
this.tableJoin.setExtraWhere(extraWhere);
this.extraWhere = extraWhere;
}
@@ -33,6 +33,8 @@ public class DeployTableJoin {
private ArrayList<DeployTableJoinColumn> columns = new ArrayList<>(4);
private InheritInfo inheritInfo;
private String extraWhere;
/**
* Create a DeployTableJoin.
@@ -137,6 +139,18 @@ public class DeployTableJoin {
this.type = type;
}
/**
* Returns the clause of an extra &#64;Where annotation.
* @return
*/
public String getExtraWhere() {
return extraWhere;
}
public void setExtraWhere(String extraWhere) {
this.extraWhere = extraWhere;
}
public DeployTableJoin createInverse(String tableName) {
DeployTableJoin inverse = new DeployTableJoin();
@@ -118,7 +118,7 @@ class AnnotationAssocManys extends AnnotationAssoc {
Where where = prop.getMetaAnnotationWhere(platform);
if (where != null) {
prop.setExtraWhere(where.clause());
prop.setExtraWhere(processFormula(where.clause()));
}
FetchPreference fetchPreference = get(prop, FetchPreference.class);
@@ -96,7 +96,7 @@ public class AnnotationAssocOnes extends AnnotationAssoc {
Where where = prop.getMetaAnnotationWhere(platform);
if (where != null) {
// not expecting this to be used on assoc one properties
prop.setExtraWhere(where.clause());
prop.setExtraWhere(processFormula(where.clause()));
}
PrimaryKeyJoinColumn primaryKeyJoin = get(prop, PrimaryKeyJoinColumn.class);
@@ -153,7 +153,7 @@ public class AnnotationFields extends AnnotationParser {
Formula formula = prop.getMetaAnnotationFormula(platform);
if (formula != null) {
prop.setSqlFormula(formula.select(), formula.join());
prop.setSqlFormula(processFormula(formula.select()), processFormula(formula.join()));
}
initWhoProperties(prop);
@@ -334,7 +334,7 @@ public class AnnotationFields extends AnnotationParser {
}
Formula formula = prop.getMetaAnnotationFormula(platform);
if (formula != null) {
prop.setSqlFormula(formula.select(), formula.join());
prop.setSqlFormula(processFormula(formula.select()), processFormula(formula.join()));
}
final Aggregation aggregation = prop.getMetaAnnotation(Aggregation.class);
@@ -129,4 +129,11 @@ public abstract class AnnotationParser extends AnnotationBase {
}
return columnNames;
}
/**
* Process any formula from &#64;Formula or &#64;Where.
*/
protected String processFormula(String source) {
return source == null ? null : source.replace("${dbTableName}", descriptor.getBaseTable());
}
}
@@ -1,5 +1,6 @@
package io.ebeaninternal.server.query;
import io.ebean.CancelableQuery;
import io.ebean.QueryIterator;
import io.ebean.Version;
import io.ebean.bean.*;
@@ -139,8 +140,6 @@ public class CQuery<T> implements DbReadContext, CancelableQuery, SpiProfileTran
*/
private PreparedStatement pstmt;
private boolean cancelled;
private String bindLog;
private final CQueryPlan queryPlan;
@@ -272,15 +271,7 @@ public class CQuery<T> implements DbReadContext, CancelableQuery, SpiProfileTran
public void cancel() {
lock.lock();
try {
this.cancelled = true;
if (pstmt != null) {
try {
logger.debug("Cancelling query");
pstmt.cancel();
} catch (SQLException e) {
throw new PersistenceException("Error cancelling query", e);
}
}
JdbcClose.cancel(pstmt);
} finally {
lock.unlock();
}
@@ -312,9 +303,8 @@ public class CQuery<T> implements DbReadContext, CancelableQuery, SpiProfileTran
ResultSet prepareResultSet(boolean forwardOnlyHint) throws SQLException {
lock.lock();
try {
if (cancelled) {
throw new SQLException("Query cancelled");
}
// cancelled before we started
query.checkCancelled();
startNano = System.nanoTime();
SpiTransaction t = request.getTransaction();
profileOffset = t.profileOffset();
@@ -342,10 +332,12 @@ public class CQuery<T> implements DbReadContext, CancelableQuery, SpiProfileTran
pstmt.setFetchSize(query.getBufferFetchSizeHint());
}
bindLog = predicates.bind(queryPlan.bindEncryptedProperties(pstmt, conn));
return pstmt.executeQuery();
} finally {
lock.unlock();
}
ResultSet ret = pstmt.executeQuery();
query.checkCancelled();
return ret;
}
/**
@@ -491,7 +483,8 @@ public class CQuery<T> implements DbReadContext, CancelableQuery, SpiProfileTran
boolean hasNext() throws SQLException {
lock.lock();
try {
if (noMoreRows || cancelled) {
query.checkCancelled();
if (noMoreRows) {
return false;
}
if (hasNextCache) {
@@ -189,6 +189,12 @@ class CQueryBuilder {
SpiQuery<?> query = request.getQuery();
query.setSingleAttribute();
if (!query.isIncludeSoftDeletes()) {
BeanDescriptor<?> desc = request.getBeanDescriptor();
if (desc.isSoftDelete()) {
query.addSoftDeletePredicate(desc.getSoftDeletePredicate(alias(query.getAlias())));
}
}
CQueryPredicates predicates = new CQueryPredicates(binder, request);
CQueryPlan queryPlan = request.getQueryPlan();
@@ -616,11 +622,19 @@ class CQueryBuilder {
if (request.isInlineCountDistinct()) {
sb.append(")");
}
if (distinct && dbOrderBy != null && !query.isSingleAttribute()) {
if (distinct && dbOrderBy != null) {
// add the orderBy columns to the select clause (due to distinct)
final OrderBy<?> orderBy = query.getOrderBy();
if (orderBy != null && orderBy.supportsSelect()) {
sb.append(", ").append(DbOrderByTrim.trim(dbOrderBy));
String trimmed = DbOrderByTrim.trim(dbOrderBy);
if (query.isSingleAttribute() && trimmed.equals(select.getSelectSql())) {
// NOP, already in SQL
// TODO: what to do if we select("id").orderBy("prop,id")?
// Can we live with a query like "select t0.id, t0.prop, t0.id from"
// or should we elliminate the second "t0.id" from select
} else {
sb.append(", ").append(trimmed);
}
}
}
}
@@ -66,11 +66,13 @@ public class CQueryEngine {
public <T> int delete(OrmQueryRequest<T> request) {
CQueryUpdate query = queryBuilder.buildUpdateQuery(true, request);
request.setCancelableQuery(query);
return executeUpdate(request, query);
}
public <T> int update(OrmQueryRequest<T> request) {
CQueryUpdate query = queryBuilder.buildUpdateQuery(false, request);
request.setCancelableQuery(query);
return executeUpdate(request, query);
}
@@ -97,6 +99,7 @@ public class CQueryEngine {
public <A> List<A> findSingleAttributeList(OrmQueryRequest<?> request) {
CQueryFetchSingleAttribute rcQuery = queryBuilder.buildFetchAttributeQuery(request);
request.setCancelableQuery(rcQuery);
return findAttributeList(request, rcQuery);
}
@@ -151,6 +154,7 @@ public class CQueryEngine {
public <A> List<A> findIds(OrmQueryRequest<?> request) {
CQueryFetchSingleAttribute rcQuery = queryBuilder.buildFetchIdsQuery(request);
request.setCancelableQuery(rcQuery);
return findAttributeList(request, rcQuery);
}
@@ -164,6 +168,7 @@ public class CQueryEngine {
public <T> int findCount(OrmQueryRequest<T> request) {
CQueryRowCount rcQuery = queryBuilder.buildRowCountQuery(request);
request.setCancelableQuery(rcQuery);
try {
int count = rcQuery.findCount();
@@ -235,8 +240,10 @@ public class CQueryEngine {
} catch (SQLException e) {
try {
PersistenceException pex = cquery.createPersistenceException(e);
// create exception before closing connection
cquery.close();
throw cquery.createPersistenceException(e);
throw pex;
} finally {
request.rollbackTransIfRequired();
}
@@ -259,6 +266,7 @@ public class CQueryEngine {
// order by lower sys period desc
query.order().desc(sysPeriodLower);
CQuery<T> cquery = queryBuilder.buildQuery(request);
request.setCancelableQuery(cquery);
try {
cquery.prepareBindExecuteQuery();
if (request.isLogSql()) {
@@ -327,6 +335,7 @@ public class CQueryEngine {
*/
public <T> SpiResultSet findResultSet(OrmQueryRequest<T> request) {
CQuery<T> cquery = queryBuilder.buildQuery(request);
request.setCancelableQuery(cquery);
try {
boolean fwdOnly;
if (request.isFindIterate()) {
@@ -411,6 +420,7 @@ public class CQueryEngine {
EntityBean bean = null;
CQuery<T> cquery = queryBuilder.buildQuery(request);
request.setCancelableQuery(cquery);
try {
cquery.prepareBindExecuteQuery();
@@ -1,5 +1,6 @@
package io.ebeaninternal.server.query;
import io.ebean.CancelableQuery;
import io.ebean.CountedValue;
import io.ebean.core.type.ScalarDataReader;
import io.ebean.util.JdbcClose;
@@ -18,11 +19,12 @@ import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.concurrent.locks.ReentrantLock;
/**
* Base compiled query request for single attribute queries.
*/
class CQueryFetchSingleAttribute implements SpiProfileTransactionEvent {
class CQueryFetchSingleAttribute implements SpiProfileTransactionEvent, CancelableQuery {
private static final Logger logger = LoggerFactory.getLogger(CQueryFetchSingleAttribute.class);
@@ -65,6 +67,8 @@ class CQueryFetchSingleAttribute implements SpiProfileTransactionEvent {
private final boolean containsCounts;
private long profileOffset;
private final ReentrantLock lock = new ReentrantLock();
/**
* Create the Sql select based on the request.
@@ -147,21 +151,27 @@ class CQueryFetchSingleAttribute implements SpiProfileTransactionEvent {
}
private void prepareExecute() throws SQLException {
SpiTransaction t = getTransaction();
profileOffset = t.profileOffset();
Connection conn = t.getInternalConnection();
pstmt = conn.prepareStatement(sql);
if (query.getBufferFetchSizeHint() > 0) {
pstmt.setFetchSize(query.getBufferFetchSizeHint());
lock.lock();
try {
query.checkCancelled();
SpiTransaction t = getTransaction();
profileOffset = t.profileOffset();
Connection conn = t.getInternalConnection();
pstmt = conn.prepareStatement(sql);
if (query.getBufferFetchSizeHint() > 0) {
pstmt.setFetchSize(query.getBufferFetchSizeHint());
}
if (query.getTimeout() > 0) {
pstmt.setQueryTimeout(query.getTimeout());
}
bindLog = predicates.bind(pstmt, conn);
} finally {
lock.unlock();
}
if (query.getTimeout() > 0) {
pstmt.setQueryTimeout(query.getTimeout());
}
bindLog = predicates.bind(pstmt, conn);
dataReader = new RsetDataReader(request.getDataTimeZone(), pstmt.executeQuery());
query.checkCancelled();
}
/**
@@ -194,4 +204,14 @@ class CQueryFetchSingleAttribute implements SpiProfileTransactionEvent {
Set<String> getDependentTables() {
return queryPlan.getDependentTables();
}
@Override
public void cancel() {
lock.lock();
try {
JdbcClose.cancel(pstmt);
} finally {
lock.unlock();
}
}
}
@@ -1,5 +1,6 @@
package io.ebeaninternal.server.query;
import io.ebean.CancelableQuery;
import io.ebean.util.JdbcClose;
import io.ebeaninternal.api.SpiProfileTransactionEvent;
import io.ebeaninternal.api.SpiQuery;
@@ -13,11 +14,12 @@ import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Set;
import java.util.concurrent.locks.ReentrantLock;
/**
* Executes the select row count query.
*/
class CQueryRowCount implements SpiProfileTransactionEvent {
class CQueryRowCount implements SpiProfileTransactionEvent, CancelableQuery {
private final CQueryPlan queryPlan;
@@ -57,6 +59,8 @@ class CQueryRowCount implements SpiProfileTransactionEvent {
private int rowCount;
private long profileOffset;
private final ReentrantLock lock = new ReentrantLock();
/**
* Create the Sql select based on the request.
@@ -110,14 +114,22 @@ class CQueryRowCount implements SpiProfileTransactionEvent {
SpiTransaction t = getTransaction();
profileOffset = t.profileOffset();
Connection conn = t.getInternalConnection();
pstmt = conn.prepareStatement(sql);
lock.lock();
try {
query.checkCancelled();
pstmt = conn.prepareStatement(sql);
if (query.getTimeout() > 0) {
pstmt.setQueryTimeout(query.getTimeout());
if (query.getTimeout() > 0) {
pstmt.setQueryTimeout(query.getTimeout());
}
bindLog = predicates.bind(pstmt, conn);
} finally {
lock.unlock();
}
bindLog = predicates.bind(pstmt, conn);
rset = pstmt.executeQuery();
query.checkCancelled();
if (!rset.next()) {
throw new PersistenceException("Expecting 1 row but got none?");
}
@@ -161,4 +173,14 @@ class CQueryRowCount implements SpiProfileTransactionEvent {
Set<String> getDependentTables() {
return queryPlan.getDependentTables();
}
@Override
public void cancel() {
lock.lock();
try {
JdbcClose.cancel(pstmt);
} finally {
lock.unlock();
}
}
}
@@ -1,5 +1,6 @@
package io.ebeaninternal.server.query;
import io.ebean.CancelableQuery;
import io.ebean.util.JdbcClose;
import io.ebeaninternal.api.SpiProfileTransactionEvent;
import io.ebeaninternal.api.SpiQuery;
@@ -10,11 +11,12 @@ import io.ebeaninternal.server.deploy.BeanDescriptor;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.concurrent.locks.ReentrantLock;
/**
* Executes the delete query.
* Executes the update query.
*/
class CQueryUpdate implements SpiProfileTransactionEvent {
class CQueryUpdate implements SpiProfileTransactionEvent, CancelableQuery {
private final CQueryPlan queryPlan;
@@ -45,6 +47,8 @@ class CQueryUpdate implements SpiProfileTransactionEvent {
private long profileOffset;
private final ReentrantLock lock = new ReentrantLock();
/**
* Create the Sql select based on the request.
*/
@@ -82,15 +86,22 @@ class CQueryUpdate implements SpiProfileTransactionEvent {
SpiTransaction t = getTransaction();
profileOffset = t.profileOffset();
Connection conn = t.getInternalConnection();
pstmt = conn.prepareStatement(sql);
lock.lock();
try {
query.checkCancelled();
pstmt = conn.prepareStatement(sql);
if (query.getTimeout() > 0) {
pstmt.setQueryTimeout(query.getTimeout());
if (query.getTimeout() > 0) {
pstmt.setQueryTimeout(query.getTimeout());
}
bindLog = predicates.bind(pstmt, conn);
} finally {
lock.unlock();
}
bindLog = predicates.bind(pstmt, conn);
rowCount = pstmt.executeUpdate();
query.checkCancelled();
long executionTimeMicros = (System.nanoTime() - startNano) / 1000L;
request.slowQueryCheck(executionTimeMicros, rowCount);
if (queryPlan.executionTime(executionTimeMicros)) {
@@ -122,4 +133,14 @@ class CQueryUpdate implements SpiProfileTransactionEvent {
.profileStream()
.addQueryEvent(query.profileEventId(), profileOffset, desc.getName(), rowCount, query.getProfileId());
}
@Override
public void cancel() {
lock.lock();
try {
JdbcClose.cancel(pstmt);
} finally {
lock.unlock();
}
}
}
@@ -11,11 +11,10 @@ import java.util.HashSet;
class DefaultDbSqlContext implements DbSqlContext {
private static final String COMMA = ", ";
private static final String PERIOD = ".";
private static final int STRING_BUILDER_INITIAL_CAPACITY = 140;
private static final String tableAliasPlaceHolder = "${ta}";
private static final String tableAliasManyPlaceHolder = "${mta}";
private final String columnAliasPrefix;
@@ -111,27 +110,22 @@ class DefaultDbSqlContext implements DbSqlContext {
}
@Override
public void addJoin(String type, String table, TableJoinColumn[] cols, String a1, String a2) {
public void addJoin(String type, String table, TableJoinColumn[] cols, String a1, String a2, String extraWhere) {
if (tableJoins == null) {
tableJoins = new HashSet<>();
}
String joinKey = table + "-" + a1 + "-" + a2;
if (tableJoins.contains(joinKey)) {
return;
}
tableJoins.add(joinKey);
sb.append(" ").append(type);
boolean addAsOfOnClause = false;
if (draftSupport != null) {
appendTable(table, draftSupport.getDraftTable(table));
} else if (!historyQuery) {
sb.append(" ").append(table).append(" ");
} else {
// check if there is an associated history table and if so
// use the unionAll view - we expect an additional predicate to match
@@ -164,13 +158,17 @@ class DefaultDbSqlContext implements DbSqlContext {
if (addAsOfOnClause) {
sb.append(" and ").append(historySupport.getAsOfPredicate(a2));
}
if (extraWhere != null && !extraWhere.isEmpty()) {
sb.append(" and ");
// we will also need a many-table alias here
sb.append(extraWhere.replace(tableAliasPlaceHolder, a2).replace(tableAliasManyPlaceHolder, a1));
}
}
private void appendTable(String table, String draftTable) {
if (draftTable != null) {
// there is an associated history table and view so use that
sb.append(" ").append(draftTable).append(" ");
} else {
sb.append(" ").append(table).append(" ");
}
@@ -193,7 +191,6 @@ class DefaultDbSqlContext implements DbSqlContext {
@Override
public String getRelativePrefix(String propName) {
return currentPrefix == null ? propName : currentPrefix + "." + propName;
}
@@ -228,7 +225,6 @@ class DefaultDbSqlContext implements DbSqlContext {
// the same join has already been added.
return;
}
// we only want to add this join once
formulaJoins.add(converted);
sb.append(" ");
@@ -263,13 +259,10 @@ class DefaultDbSqlContext implements DbSqlContext {
@Override
public void appendHistorySysPeriod() {
String tableAlias = tableAliasStack.peek();
sb.append(COMMA);
sb.append(historySupport.getSysPeriodLower(tableAlias));
appendColumnAlias();
sb.append(COMMA);
sb.append(historySupport.getSysPeriodUpper(tableAlias));
appendColumnAlias();
@@ -29,7 +29,7 @@ public class DtoQueryEngine {
}
return rows;
} catch (Throwable e) {
} catch (SQLException e) {
throw new PersistenceException(errMsg(e.getMessage(), request.getSql()), e);
} finally {
request.close();
@@ -51,7 +51,7 @@ public class DtoQueryEngine {
while (request.next()) {
consumer.accept(request.readNextBean());
}
} catch (Exception e) {
} catch (SQLException e) {
throw new PersistenceException(errMsg(e.getMessage(), request.getSql()), e);
} finally {
request.close();
@@ -88,9 +88,8 @@ public class DtoQueryEngine {
break;
}
}
} catch (Exception e) {
} catch (SQLException e) {
throw new PersistenceException(errMsg(e.getMessage(), request.getSql()), e);
} finally {
request.close();
}
@@ -47,16 +47,6 @@ final class SqlTreeNodeManyRoot extends SqlTreeNodeBean {
ctx.popTableAlias();
}
/**
* append extraWhere to the join.
*/
@Override
protected SqlJoinType appendFromAsJoin(DbSqlContext ctx, SqlJoinType joinType) {
SqlJoinType join = super.appendFromAsJoin(ctx, joinType);
super.appendExtraWhere(ctx);
return join;
}
@Override
protected void appendExtraWhere(DbSqlContext ctx) {
// extraWhere is already appended to the tableJoin
@@ -0,0 +1,56 @@
package io.ebeaninternal.server.querydefn;
import java.util.concurrent.locks.ReentrantLock;
import javax.persistence.PersistenceException;
import io.ebean.CancelableQuery;
import io.ebeaninternal.api.SpiCancelableQuery;
/**
* Common code for Dto/Orm/RelationalQuery
*
* @author Roland Praml, FOCONIS AG
*
*/
public class AbstractQuery implements SpiCancelableQuery {
private boolean cancelled;
private CancelableQuery cancelableQuery;
private final ReentrantLock lock = new ReentrantLock();
@Override
public void cancel() {
lock.lock();
try {
if (!cancelled) {
cancelled = true;
if (cancelableQuery != null) {
cancelableQuery.cancel();
}
}
} finally {
lock.unlock();
}
}
@Override
public void checkCancelled() {
if (cancelled) {
throw new PersistenceException("Query was cancelled");
}
}
@Override
public void setCancelableQuery(CancelableQuery cancelableQuery) {
lock.lock();
try {
checkCancelled();
this.cancelableQuery = cancelableQuery;
} finally {
lock.unlock();
}
}
}
@@ -21,7 +21,7 @@ import java.util.stream.Stream;
/**
* Default implementation of DtoQuery.
*/
public class DefaultDtoQuery<T> implements SpiDtoQuery<T> {
public class DefaultDtoQuery<T> extends AbstractQuery implements SpiDtoQuery<T> {
private final SpiEbeanServer server;
@@ -55,10 +55,10 @@ import io.ebeaninternal.server.deploy.BeanNaturalKey;
import io.ebeaninternal.server.deploy.BeanPropertyAssocMany;
import io.ebeaninternal.server.deploy.InheritInfo;
import io.ebeaninternal.server.deploy.TableJoin;
import io.ebeaninternal.server.el.ElPropertyDeploy;
import io.ebeaninternal.server.expression.DefaultExpressionList;
import io.ebeaninternal.server.expression.IdInExpression;
import io.ebeaninternal.server.expression.SimpleExpression;
import io.ebeaninternal.server.query.CancelableQuery;
import io.ebeaninternal.server.query.NativeSqlQueryPlanKey;
import io.ebeaninternal.server.rawsql.SpiRawSql;
import io.ebeaninternal.server.transaction.ExternalJdbcTransaction;
@@ -81,7 +81,7 @@ import java.util.stream.Stream;
/**
* Default implementation of an Object Relational query.
*/
public class DefaultOrmQuery<T> implements SpiQuery<T> {
public class DefaultOrmQuery<T> extends AbstractQuery implements SpiQuery<T> {
private static final String DEFAULT_QUERY_NAME = "default";
@@ -113,10 +113,6 @@ public class DefaultOrmQuery<T> implements SpiQuery<T> {
private ProfilingListener profilingListener;
private boolean cancelled;
private CancelableQuery cancelableQuery;
private Type type;
private String label;
@@ -539,6 +535,14 @@ public class DefaultOrmQuery<T> implements SpiQuery<T> {
if (havingExpressions != null) {
havingExpressions.containsMany(beanDescriptor, manyWhereJoins);
}
if (orderBy != null) {
for (Property orderProperty : orderBy.getProperties()) {
ElPropertyDeploy elProp = beanDescriptor.getElPropertyDeploy(orderProperty.getProperty());
if (elProp != null && elProp.containsFormulaWithJoin()) {
manyWhereJoins.addFormulaWithJoin(orderProperty.getProperty());
}
}
}
}
/**
@@ -856,6 +860,7 @@ public class DefaultOrmQuery<T> implements SpiQuery<T> {
copy.parentNode = parentNode;
copy.forUpdate = forUpdate;
copy.rawSql = rawSql;
setCancelableQuery(copy); // required to cancel findId query
return copy;
}
@@ -2046,16 +2051,6 @@ public class DefaultOrmQuery<T> implements SpiQuery<T> {
return futureFetchAudit;
}
@Override
public void setCancelableQuery(CancelableQuery cancelableQuery) {
lock.lock();
try {
this.cancelableQuery = cancelableQuery;
} finally {
lock.unlock();
}
}
@Override
public Query<T> setBaseTable(String baseTable) {
this.baseTable = baseTable;
@@ -2083,28 +2078,6 @@ public class DefaultOrmQuery<T> implements SpiQuery<T> {
return rootTableAlias != null ? rootTableAlias : defaultAlias;
}
@Override
public void cancel() {
lock.lock();
try {
if (!cancelled && cancelableQuery != null) {
cancelled = true;
cancelableQuery.cancel();
}
} finally {
lock.unlock();
}
}
@Override
public boolean isCancelled() {
lock.lock();
try {
return cancelled;
} finally {
lock.unlock();
}
}
@Override
public Set<String> validate() {
@@ -17,7 +17,7 @@ import java.util.function.Predicate;
/**
* Default implementation of SQuery - SQL Query.
*/
public class DefaultRelationalQuery implements SpiSqlQuery {
public class DefaultRelationalQuery extends AbstractQuery implements SpiSqlQuery {
private static final long serialVersionUID = -1098305779779591068L;
@@ -9,6 +9,7 @@ import io.ebeaninternal.server.query.DefaultSqlRow;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Types;
public class DRawSqlService implements SpiRawSqlService {
@@ -48,7 +49,26 @@ public class DRawSqlService implements SpiRawSqlService {
if (ret.containsKey(name)) {
name = combine(meta.getSchemaName(i), meta.getTableName(i), name);
}
ret.put(name, resultSet.getObject(i));
// convert (C/B)LOBs to java objects.
// A java.sql.Clob depends on an open connection, so storing this object in a map
// that is accessed later, when the connection is closed, will result in a "connection is closed" exception.
// From the java.sql.Clob documentation: "... which means that a Clob object contains a logical pointer to the SQL CLOB
// data rather than the data itself."
switch (meta.getColumnType(i)) {
case Types.CLOB:
case Types.NCLOB:
ret.put(name, resultSet.getString(i));
break;
case Types.BLOB:
ret.put(name, resultSet.getBytes(i));
break;
default:
ret.put(name, resultSet.getObject(i));
break;
}
}
return ret;
}
@@ -941,11 +941,13 @@ class JdbcTransaction implements SpiTransaction, TxnProfileEventCodes {
*/
private void connectionEndForQueryOnly() {
try {
withEachCallback(TransactionCallback::preCommit);
if (onQueryOnly == OnQueryOnly.COMMIT) {
performCommit();
} else {
performRollback();
}
withEachCallback(TransactionCallback::postCommit);
} catch (SQLException e) {
logger.error("Error when ending a query only transaction via " + onQueryOnly, e);
}
@@ -750,12 +750,15 @@ public final class DefaultTypeManager implements TypeManager {
}
private void initialiseJavaTimeTypes(DatabaseConfig config) {
ZoneId zoneId = getZoneId(config);
typeMap.put(java.nio.file.Path.class, new ScalarTypePath());
addType(java.time.Period.class, new ScalarTypePeriod());
addType(java.time.LocalDate.class, new ScalarTypeLocalDate(jsonDate));
addType(java.time.LocalDateTime.class, new ScalarTypeLocalDateTime(jsonDateTime));
addType(OffsetDateTime.class, new ScalarTypeOffsetDateTime(jsonDateTime));
addType(ZonedDateTime.class, new ScalarTypeZonedDateTime(jsonDateTime));
addType(OffsetDateTime.class, new ScalarTypeOffsetDateTime(jsonDateTime, zoneId));
addType(ZonedDateTime.class, new ScalarTypeZonedDateTime(jsonDateTime, zoneId));
addType(Instant.class, new ScalarTypeInstant(jsonDateTime));
addType(DayOfWeek.class, new ScalarTypeDayOfWeek());
addType(Month.class, new ScalarTypeMonth());
@@ -771,6 +774,11 @@ public final class DefaultTypeManager implements TypeManager {
addType(Duration.class, (durationNanos) ? new ScalarTypeDurationWithNanos() : new ScalarTypeDuration());
}
private ZoneId getZoneId(DatabaseConfig config) {
final String dataTimeZone = config.getDataTimeZone();
return (dataTimeZone == null) ? ZoneOffset.systemDefault() : TimeZone.getTimeZone(dataTimeZone).toZoneId();
}
private void addType(Class<?> clazz, ScalarType<?> scalarType) {
typeMap.put(clazz, scalarType);
logicalMap.putIfAbsent(clazz.getSimpleName(), scalarType);
@@ -15,8 +15,11 @@ import static io.ebeaninternal.server.type.IsoJsonDateTimeParser.formatIso;
*/
public class ScalarTypeOffsetDateTime extends ScalarTypeBaseDateTime<OffsetDateTime> {
public ScalarTypeOffsetDateTime(JsonConfig.DateTime mode) {
private final ZoneId zoneId;
public ScalarTypeOffsetDateTime(JsonConfig.DateTime mode, ZoneId zoneId) {
super(mode, OffsetDateTime.class, false, Types.TIMESTAMP);
this.zoneId = zoneId;
}
@Override
@@ -46,7 +49,7 @@ public class ScalarTypeOffsetDateTime extends ScalarTypeBaseDateTime<OffsetDateT
@Override
public OffsetDateTime convertFromInstant(Instant ts) {
return OffsetDateTime.ofInstant(ts, ZoneId.systemDefault());
return OffsetDateTime.ofInstant(ts, zoneId);
}
@Override
@@ -13,8 +13,11 @@ import java.time.ZonedDateTime;
*/
public class ScalarTypeZonedDateTime extends ScalarTypeBaseDateTime<ZonedDateTime> {
public ScalarTypeZonedDateTime(JsonConfig.DateTime mode) {
private final ZoneId zoneId;
public ScalarTypeZonedDateTime(JsonConfig.DateTime mode, ZoneId zoneId) {
super(mode, ZonedDateTime.class, false, Types.TIMESTAMP);
this.zoneId = zoneId;
}
@Override
@@ -44,7 +47,7 @@ public class ScalarTypeZonedDateTime extends ScalarTypeBaseDateTime<ZonedDateTim
@Override
public ZonedDateTime convertFromInstant(Instant ts) {
return ZonedDateTime.ofInstant(ts, ZoneId.systemDefault());
return ZonedDateTime.ofInstant(ts, zoneId);
}
@Override
@@ -33,7 +33,7 @@ public class TestNotEnhancedMappedSuper extends BaseTestCase {
NotEnhancedMappedSuper mappedSuper = new NotEnhancedMappedSuper();
boolean enhanced = (mappedSuper instanceof EntityBean);
Assert.assertFalse(enhanced);
Assert.assertTrue(enhanced);
}
@@ -9,17 +9,25 @@ import io.ebean.RawSqlBuilder;
import io.ebean.SqlRow;
import io.ebean.annotation.ForPlatform;
import io.ebean.annotation.Platform;
import io.ebean.datasource.DataSourceConfig;
import io.ebeaninternal.server.core.DefaultServer;
import io.ebeaninternal.server.rawsql.SpiRawSql.Sql;
import org.junit.Test;
import org.tests.model.basic.Customer;
import org.tests.model.basic.EBasicClob;
import org.tests.model.basic.PersistentFileContent;
import org.tests.model.basic.ResetBasicData;
import org.tests.model.rawsql.ERawSqlAggBean;
import javax.sql.DataSource;
import java.nio.charset.StandardCharsets;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.Assert.assertEquals;
@@ -283,4 +291,58 @@ public class TestRawSqlBuilder extends BaseTestCase {
}
}
@Test
public void testCLobClosedConnection() throws Exception {
final EBasicClob eBasicClob = new EBasicClob();
eBasicClob.setName("eBasicClob");
final String description = "This is the CLob description";
eBasicClob.setDescription(description);
DB.save(eBasicClob);
final String sql = "select description from ebasic_clob where id = ?";
List<SqlRow> rows = new ArrayList<>();
final DataSourceConfig config = ((DefaultServer) DB.getDefault()).getServerConfig().getDataSourceConfig();
try (Connection connection = DriverManager.getConnection(config.getUrl(), config.getUsername(), config.getPassword());
PreparedStatement stmt = connection.prepareStatement(sql)) {
stmt.setLong(1, eBasicClob.getId());
try (ResultSet resultSet = stmt.executeQuery()) {
while (resultSet.next()) {
rows.add(RawSqlBuilder.sqlRow(resultSet, "true", false));
}
}
}
assertThat(rows).hasSize(1);
assertThat(rows.get(0).getString("description")).isEqualTo(description);
}
@Test
public void testBLobClosedConnection() throws Exception {
final PersistentFileContent pfc = new PersistentFileContent();
final byte[] bytes = "This is the blob as String".getBytes(StandardCharsets.UTF_8);
pfc.setContent(bytes);
DB.save(pfc);
List<SqlRow> rows = new ArrayList<>();
final DataSourceConfig config = ((DefaultServer) DB.getDefault()).getServerConfig().getDataSourceConfig();
final String sql = "select content from persistent_file_content where id = ?";
try (Connection connection = DriverManager.getConnection(config.getUrl(), config.getUsername(), config.getPassword());
PreparedStatement stmt = connection.prepareStatement(sql)) {
stmt.setLong(1, pfc.getId());
try (ResultSet resultSet = stmt.executeQuery()) {
while (resultSet.next()) {
rows.add(RawSqlBuilder.sqlRow(resultSet, "true", false));
}
}
}
assertThat(rows).hasSize(1);
assertThat(rows.get(0).get("content")).isEqualTo(bytes);
}
}
@@ -5,6 +5,8 @@ import org.junit.Test;
import java.sql.Timestamp;
import java.time.OffsetDateTime;
import java.time.ZoneOffset;
import java.util.TimeZone;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.Assert.assertEquals;
@@ -14,12 +16,12 @@ import static org.junit.Assert.assertTrue;
public class ScalarTypeOffsetDateTimeTest {
ScalarTypeOffsetDateTime type = new ScalarTypeOffsetDateTime(JsonConfig.DateTime.MILLIS);
ScalarTypeOffsetDateTime type = new ScalarTypeOffsetDateTime(JsonConfig.DateTime.MILLIS, ZoneOffset.systemDefault());
OffsetDateTime warmUp = OffsetDateTime.now();
@Test
public void testConvertToMillis() throws Exception {
public void testConvertToMillis() {
warmUp.hashCode();
@@ -30,7 +32,43 @@ public class ScalarTypeOffsetDateTimeTest {
}
@Test
public void testConvertFromTimestamp() throws Exception {
public void convertFromInstant_with_UTC_expect_matchingZoneOffset() {
final TimeZone timeZoneToUse = TimeZone.getTimeZone("UTC");
final ZoneOffset expectedZoneOffset = ZoneOffset.UTC;
convertFromInstantWithConfiguredTimeZone(timeZoneToUse, expectedZoneOffset);
}
@Test
public void convertFromInstant_with_EST_expect_matchingZoneOffset() {
final TimeZone timeZoneToUse = TimeZone.getTimeZone("EST");
final ZoneOffset expectedOffset = OffsetDateTime.now(timeZoneToUse.toZoneId()).getOffset();
convertFromInstantWithConfiguredTimeZone(timeZoneToUse, expectedOffset);
}
private void convertFromInstantWithConfiguredTimeZone(TimeZone timeZoneToUse, ZoneOffset expectedZoneOffset) {
TimeZone previous = TimeZone.getDefault();
try {
OffsetDateTime dateTime = OffsetDateTime.parse("2021-01-01T00:00:00+11:00");
// test ScalarTypeOffsetDateTime with the configured timeZone to use
ScalarTypeOffsetDateTime type = new ScalarTypeOffsetDateTime(JsonConfig.DateTime.MILLIS, timeZoneToUse.toZoneId());
// effectively we desire to ignore the system timezone and use the configured one
TimeZone.setDefault(timeZoneToUse);
final OffsetDateTime offsetDateTime = type.convertFromInstant(dateTime.toInstant());
assertEquals(expectedZoneOffset, offsetDateTime.getOffset());
} finally {
TimeZone.setDefault(previous);
}
}
@Test
public void testConvertFromTimestamp() {
Timestamp now = new Timestamp(System.currentTimeMillis());
@@ -69,11 +107,11 @@ public class ScalarTypeOffsetDateTimeTest {
JsonTester<OffsetDateTime> jsonTester = new JsonTester<>(type);
jsonTester.test(now);
ScalarTypeOffsetDateTime typeNanos = new ScalarTypeOffsetDateTime(JsonConfig.DateTime.NANOS);
ScalarTypeOffsetDateTime typeNanos = new ScalarTypeOffsetDateTime(JsonConfig.DateTime.NANOS, ZoneOffset.systemDefault());
jsonTester = new JsonTester<>(typeNanos);
jsonTester.test(now);
ScalarTypeOffsetDateTime typeIso = new ScalarTypeOffsetDateTime(JsonConfig.DateTime.ISO8601);
ScalarTypeOffsetDateTime typeIso = new ScalarTypeOffsetDateTime(JsonConfig.DateTime.ISO8601, ZoneOffset.systemDefault());
jsonTester = new JsonTester<>(typeIso);
jsonTester.test(now);
}
@@ -81,7 +119,7 @@ public class ScalarTypeOffsetDateTimeTest {
@Test
public void isoJsonFormatParse() {
ScalarTypeOffsetDateTime typeIso = new ScalarTypeOffsetDateTime(JsonConfig.DateTime.ISO8601);
ScalarTypeOffsetDateTime typeIso = new ScalarTypeOffsetDateTime(JsonConfig.DateTime.ISO8601, ZoneOffset.systemDefault());
OffsetDateTime now = OffsetDateTime.now();
String asJson = typeIso.toJsonISO8601(now);
@@ -4,7 +4,11 @@ import io.ebean.config.JsonConfig;
import org.junit.Test;
import java.sql.Timestamp;
import java.time.OffsetDateTime;
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.util.TimeZone;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.Assert.*;
@@ -12,12 +16,12 @@ import static org.junit.Assert.*;
public class ScalarTypeZonedDateTimeTest {
ScalarTypeZonedDateTime type = new ScalarTypeZonedDateTime(JsonConfig.DateTime.MILLIS);
ScalarTypeZonedDateTime type = new ScalarTypeZonedDateTime(JsonConfig.DateTime.MILLIS, ZoneId.systemDefault());
ZonedDateTime warmUp = ZonedDateTime.now();
@Test
public void testConvertToMillis() throws Exception {
public void testConvertToMillis() {
warmUp.hashCode();
@@ -29,7 +33,7 @@ public class ScalarTypeZonedDateTimeTest {
}
@Test
public void testConvertFromTimestamp() throws Exception {
public void testConvertFromTimestamp() {
Timestamp now = new Timestamp(System.currentTimeMillis());
@@ -39,6 +43,41 @@ public class ScalarTypeZonedDateTimeTest {
assertEquals(now, timestamp);
}
@Test
public void convertFromInstant_with_UTC_expect_matchingZoneOffset() {
final TimeZone timeZoneToUse = TimeZone.getTimeZone("UTC");
final ZoneOffset expectedZoneOffset = ZoneOffset.UTC;
convertFromInstantWithConfiguredTimeZone(timeZoneToUse, expectedZoneOffset);
}
@Test
public void convertFromInstant_with_EST_expect_matchingZoneOffset() {
final TimeZone timeZoneToUse = TimeZone.getTimeZone("EST");
final ZoneOffset expectedOffset = OffsetDateTime.now(timeZoneToUse.toZoneId()).getOffset();
convertFromInstantWithConfiguredTimeZone(timeZoneToUse, expectedOffset);
}
private void convertFromInstantWithConfiguredTimeZone(TimeZone timeZoneToUse, ZoneOffset expectedZoneOffset) {
TimeZone previous = TimeZone.getDefault();
try {
OffsetDateTime dateTime = OffsetDateTime.parse("2021-01-01T00:00:00+11:00");
// test ScalarTypeOffsetDateTime with the configured timeZone to use
ScalarTypeZonedDateTime type = new ScalarTypeZonedDateTime(JsonConfig.DateTime.MILLIS, timeZoneToUse.toZoneId());
// effectively we desire to ignore the system timezone and use the configured one
TimeZone.setDefault(timeZoneToUse);
final ZonedDateTime zonedDateTime = type.convertFromInstant(dateTime.toInstant());
assertEquals(expectedZoneOffset, zonedDateTime.getOffset());
} finally {
TimeZone.setDefault(previous);
}
}
@Test
public void testToJdbcType() throws Exception {
@@ -68,11 +107,11 @@ public class ScalarTypeZonedDateTimeTest {
JsonTester<ZonedDateTime> jsonTester = new JsonTester<>(type);
jsonTester.test(now);
ScalarTypeZonedDateTime typeNanos = new ScalarTypeZonedDateTime(JsonConfig.DateTime.NANOS);
ScalarTypeZonedDateTime typeNanos = new ScalarTypeZonedDateTime(JsonConfig.DateTime.NANOS, ZoneId.systemDefault());
jsonTester = new JsonTester<>(typeNanos);
jsonTester.test(now);
ScalarTypeZonedDateTime typeIso = new ScalarTypeZonedDateTime(JsonConfig.DateTime.ISO8601);
ScalarTypeZonedDateTime typeIso = new ScalarTypeZonedDateTime(JsonConfig.DateTime.ISO8601, ZoneId.systemDefault());
jsonTester = new JsonTester<>(typeIso);
jsonTester.test(now);
}
@@ -80,7 +119,7 @@ public class ScalarTypeZonedDateTimeTest {
@Test
public void toJsonISO8601() {
ScalarTypeZonedDateTime typeIso = new ScalarTypeZonedDateTime(JsonConfig.DateTime.ISO8601);
ScalarTypeZonedDateTime typeIso = new ScalarTypeZonedDateTime(JsonConfig.DateTime.ISO8601, ZoneId.systemDefault());
ZonedDateTime now = ZonedDateTime.now();
String asJson = typeIso.toJsonISO8601(now);
@@ -1,10 +1,14 @@
package org.tests.basic;
import io.ebean.BaseTestCase;
import io.ebean.DB;
import io.ebean.Ebean;
import io.ebean.FutureIds;
import io.ebean.Query;
import io.ebeantest.LoggedSql;
import org.tests.model.basic.Order;
import org.tests.model.basic.OrderDetail;
import org.tests.model.basic.ResetBasicData;
import org.junit.Test;
@@ -36,4 +40,36 @@ public class TestFetchId extends BaseTestCase {
List<Object> idList = futureIds.get();
assertThat(idList).isNotEmpty();
}
@Test
public void testFetchIdWithExists() throws InterruptedException, ExecutionException {
ResetBasicData.reset();
Query<OrderDetail> subQuery = Ebean.find(OrderDetail.class)
.alias("sq")
.where().raw("details.id = sq.id").query();
Query<Order> query = Ebean.find(Order.class)
.where().exists(subQuery)
.orderBy("orderDate").query();
List<Object> ids = query.findIds();
// TODO: assert(query.getGeneratedSql())
assertThat(ids).isNotEmpty();
FutureIds<Order> futureIds = query.findFutureIds();
// wait for all the id's to be fetched
List<Object> idList = futureIds.get();
assertThat(idList).isNotEmpty();
}
@Test
public void testFetchIdWithOrderFormula() throws InterruptedException, ExecutionException {
ResetBasicData.reset();
Query<Order> query = DB.find(Order.class).orderBy("totalItems");
query.findIds();
// TODO: assert(query.getGeneratedSql())
}
}
@@ -59,19 +59,19 @@ public class MyEBasicConfigStartup implements ServerConfigStartup {
@Override
public void inserted(Object bean) {
insertCount.incrementAndGet();
System.out.println("-- EBasic inserted " + ((EBasic) bean).getId());
// System.out.println("-- EBasic inserted " + ((EBasic) bean).getId());
}
@Override
public void updated(Object bean, Set<String> updatedProperties) {
updateCount.incrementAndGet();
System.out.println("-- EBasic updated " + ((EBasic) bean).getId() + " updatedProperties: " + updatedProperties);
// System.out.println("-- EBasic updated " + ((EBasic) bean).getId() + " updatedProperties: " + updatedProperties);
}
@Override
public void deleted(Object bean) {
deleteCount.incrementAndGet();
System.out.println("-- EBasic deleted " + ((EBasic) bean).getId());
// System.out.println("-- EBasic deleted " + ((EBasic) bean).getId());
}
}
@@ -0,0 +1,57 @@
package org.tests.model.m2m;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.ManyToOne;
import io.ebean.annotation.Index;
@Entity
@Index(unique = true, columnNames = { "from_id", "to_id" })
@Index(unique = true, columnNames = { "to_id", "from_id" })
public class MnyEdge {
@Id
private Integer id;
@ManyToOne
private MnyNode from;
@ManyToOne
private MnyNode to;
private int flags;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public MnyNode getFrom() {
return from;
}
public void setFrom(MnyNode from) {
this.from = from;
}
public MnyNode getTo() {
return to;
}
public void setTo(MnyNode to) {
this.to = to;
}
public int getFlags() {
return flags;
}
public void setFlags(int flags) {
this.flags = flags;
}
}
@@ -0,0 +1,129 @@
package org.tests.model.m2m;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;
import io.ebean.annotation.Platform;
import io.ebean.annotation.Where;
import java.util.List;
@Entity
public class MnyNode {
@Id
Integer id;
String name;
@ManyToMany
@JoinTable(name = "mny_edge",
joinColumns = @JoinColumn(name = "from_id", referencedColumnName = "id"),
inverseJoinColumns = @JoinColumn(name = "to_id", referencedColumnName = "id"))
List<MnyNode> allRelations;
@ManyToMany
@JoinTable(name = "mny_edge",
joinColumns = @JoinColumn(name = "to_id", referencedColumnName = "id"),
inverseJoinColumns = @JoinColumn(name = "from_id", referencedColumnName = "id"))
List<MnyNode> allReverseRelations;
@ManyToMany
@JoinTable(name = "mny_edge",
joinColumns = @JoinColumn(name = "from_id", referencedColumnName = "id"),
inverseJoinColumns = @JoinColumn(name = "to_id", referencedColumnName = "id"))
@Where(clause = "${mta}.flags & 1 != 0")
@Where(clause = "BITAND(${mta}.flags, 1) != 0", platforms = Platform.H2)
List<MnyNode> bit1Relations;
@ManyToMany
@JoinTable(name = "mny_edge",
joinColumns = @JoinColumn(name = "to_id", referencedColumnName = "id"),
inverseJoinColumns = @JoinColumn(name = "from_id", referencedColumnName = "id"))
@Where(clause = "${mta}.flags & 1 != 0")
@Where(clause = "BITAND(${mta}.flags, 1) != 0", platforms = Platform.H2)
List<MnyNode> bit1ReverseRelations;
@ManyToMany
@JoinTable(name = "mny_edge",
joinColumns = @JoinColumn(name = "from_id", referencedColumnName = "id"),
inverseJoinColumns = @JoinColumn(name = "to_id", referencedColumnName = "id"))
@Where(clause = "${mta}.flags & 2 != 0")
@Where(clause = "BITAND(${mta}.flags, 2) != 0", platforms = Platform.H2)
List<MnyNode> bit2Relations;
@ManyToMany
@JoinTable(name = "mny_edge",
joinColumns = @JoinColumn(name = "to_id", referencedColumnName = "id"),
inverseJoinColumns = @JoinColumn(name = "from_id", referencedColumnName = "id"))
@Where(clause = "${mta}.flags & 2 != 0")
@Where(clause = "BITAND(${mta}.flags, 2) != 0", platforms = Platform.H2)
List<MnyNode> bit2ReverseRelations;
@ManyToMany
@JoinTable(name = "mny_edge",
joinColumns = @JoinColumn(name = "to_id", referencedColumnName = "id"),
inverseJoinColumns = @JoinColumn(name = "from_id", referencedColumnName = "id"))
@Where(clause = "'${dbTableName}' = ${ta}.name")
List<MnyNode> withDbTableName;
public MnyNode() {
}
public MnyNode(String name) {
this.name = name;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public List<MnyNode> getAllRelations() {
return allRelations;
}
public List<MnyNode> getAllReverseRelations() {
return allReverseRelations;
}
public List<MnyNode> getBit1Relations() {
return bit1Relations;
}
public List<MnyNode> getBit1ReverseRelations() {
return bit1ReverseRelations;
}
public List<MnyNode> getBit2Relations() {
return bit2Relations;
}
public List<MnyNode> getBit2ReverseRelations() {
return bit2ReverseRelations;
}
public List<MnyNode> getWithDbTableName() {
return withDbTableName;
}
public void setWithDbTableName(List<MnyNode> withDbTableName) {
this.withDbTableName = withDbTableName;
}
}
@@ -0,0 +1,159 @@
package org.tests.model.m2m;
import static org.assertj.core.api.Assertions.assertThat;
import java.util.List;
import org.junit.Test;
import org.tests.model.m2m.MnyEdge;
import org.tests.model.m2m.MnyNode;
import io.ebean.BaseTestCase;
import io.ebean.DB;
import io.ebeantest.LoggedSql;
/**
* Tests M2M with complex where queries.
*
* @author Roland Praml, FOCONIS AG
*
*/
public class TestM2MWithWhere extends BaseTestCase {
@Test
public void testQuery() throws Exception {
createTestData();
MnyNode node = DB.find(MnyNode.class, 1);
List<MnyNode> result = DB.find(MnyNode.class).where().eq("allRelations", node).findList();
assertThat(result).extracting(MnyNode::getId).containsExactly(1, 2, 3, 4, 5);
result = DB.find(MnyNode.class).where().eq("allReverseRelations", node).findList();
assertThat(result).extracting(MnyNode::getId).containsExactly(1, 2, 3, 4, 5);
result = DB.find(MnyNode.class).where().eq("bit1Relations", node).findList();
assertThat(result).isEmpty(); // -> to = 1 column: 2 0 2 0 2
result = DB.find(MnyNode.class).where().eq("bit2Relations", node).findList();
assertThat(result).extracting(MnyNode::getId).containsExactly(1, 3, 5);
result = DB.find(MnyNode.class).where().eq("bit1ReverseRelations", node).findList();
// -> from = 1 column: 2 1 3 1 3
assertThat(result).extracting(MnyNode::getId).containsExactly(2, 3, 4, 5);
result = DB.find(MnyNode.class).where().eq("bit2ReverseRelations", node).findList();
assertThat(result).hasSize(3).extracting(MnyNode::getId).containsExactly(1, 3, 5);
result = DB.find(MnyNode.class).where().eq("bit2ReverseRelations", node).findList();
assertThat(result).hasSize(3).extracting(MnyNode::getId).containsExactly(1, 3, 5);
}
@Test
public void testGetter() throws Exception {
createTestData();
MnyNode node = DB.find(MnyNode.class, 3);
assertThat(node.getAllRelations()).extracting(MnyNode::getId).containsExactly(1, 2, 3, 4, 5);
assertThat(node.getAllReverseRelations()).extracting(MnyNode::getId).containsExactly(1, 2, 3, 4, 5);
assertThat(node.getBit1Relations()).extracting(MnyNode::getId).containsExactly(4, 5);
assertThat(node.getBit1ReverseRelations()).extracting(MnyNode::getId).containsExactly(1, 2);
assertThat(node.getBit2Relations()).extracting(MnyNode::getId).containsExactly(1, 3, 5);
LoggedSql.start();
assertThat(node.getBit2ReverseRelations()).extracting(MnyNode::getId).containsExactly(1, 3, 5);
List<String> sqls = LoggedSql.stop();
assertThat(sqls).hasSize(1); // lazy load
// prefetch everything
LoggedSql.start();
node = DB.find(MnyNode.class)
.fetch("bit1Relations","*")
.fetch("bit1ReverseRelations","*")
.where().idEq(3).findOne();
sqls = LoggedSql.stop();
assertThat(sqls).hasSize(2);
// no lazyLoad expected
LoggedSql.start();
assertThat(node.getBit1Relations()).extracting(MnyNode::getId).containsExactly(4, 5);
assertThat(node.getBit1ReverseRelations()).extracting(MnyNode::getId).containsExactly(1, 2);
sqls = LoggedSql.stop();
assertThat(sqls).hasSize(0);
}
// to = | 1 2 3 4 5
// ---------+---------------
// from = 1 | 2 1 3 1 3
// from = 2 | 0 2 1 3 1
// from = 3 | 2 0 2 1 3
// from = 4 | 0 2 0 2 1
// from = 5 | 2 0 2 0 2
private void createTestData() {
DB.find(MnyEdge.class).delete();
DB.find(MnyNode.class).delete();
for (int i = 1; i <= 5; i++) {
MnyNode node = new MnyNode();
node.setId(i);
node.setName("Node #" + i);
DB.save(node);
}
StringBuilder sb = new StringBuilder();
for (int from = 1; from <= 5; from++) {
sb.append("from = ").append(from).append(" |");
for (int to = 1; to <= 5; to++) {
MnyEdge edge = new MnyEdge();
edge.setFrom(DB.getReference(MnyNode.class, from));
edge.setTo(DB.getReference(MnyNode.class, to));
int flags = 0;
if (from < to) {
flags |= 1;
}
if ((from + to) % 2 == 0) {
flags |= 2;
}
edge.setFlags(flags);
DB.save(edge);
sb.append(" ").append(flags);
}
sb.append('\n');
}
// System.out.println(sb); dump the table
}
@Test
public void testWithDbTableName() {
LoggedSql.start();
DB.find(MnyNode.class).where().isNotNull("withDbTableName.name").findList();
List<String> sql = LoggedSql.stop();
assertThat(sql).hasSize(1);
assertThat(sql.get(0)).contains("'mny_node' = u1.name");
LoggedSql.start();
DB.find(MnyNode.class).where().isNotEmpty("withDbTableName").findList();
sql = LoggedSql.stop();
assertThat(sql).hasSize(1);
assertThat(sql.get(0)).contains("'mny_node' = x2.name");
}
@Test
public void testLazyLoad() throws Exception {
MnyNode el = new MnyNode("testLazyLoad");
DB.save(el);
LoggedSql.start();
el = DB.find(MnyNode.class).select("name").where().eq("name", "testLazyLoad").findOne();
el.getWithDbTableName().size(); // trigger Lazy load
List<String> sql = LoggedSql.stop();
assertThat(sql).hasSize(2);
assertThat(sql.get(0)).contains("select t0.id, t0.name from mny_node");
assertThat(sql.get(1)).contains("where 'mny_node' = t0.name");
DB.delete(el);
}
}
@@ -4,6 +4,9 @@ import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.OneToMany;
import javax.persistence.Version;
import io.ebean.annotation.Where;
import java.util.List;
import static javax.persistence.CascadeType.ALL;
@@ -26,6 +29,10 @@ public class OmBasicParent {
@OneToMany(cascade = ALL, mappedBy = "parent")
private List<? extends OmBasicChild> children;
@OneToMany(cascade = ALL, mappedBy = "parent")
@Where(clause = "'${dbTableName}' = ${ta}.name")
private List<? extends OmBasicChild> childrenWithWhere;
public OmBasicParent(String name) {
this.name = name;
}
@@ -57,4 +64,13 @@ public class OmBasicParent {
public void setChildren(List<? extends OmBasicChild> children) {
this.children = children;
}
public List<? extends OmBasicChild> getChildrenWithWhere() {
return childrenWithWhere;
}
public void setChildrenWithWhere(List<? extends OmBasicChild> childrenWithWhere) {
this.childrenWithWhere = childrenWithWhere;
}
}
@@ -0,0 +1,44 @@
package org.tests.o2m;
import io.ebean.BaseTestCase;
import io.ebean.DB;
import io.ebeantest.LoggedSql;
import org.junit.Test;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
public class TestOneToManyWhere extends BaseTestCase {
@Test
public void testWithDbTableName() {
LoggedSql.start();
DB.find(OmBasicParent.class).where().isNotNull("childrenWithWhere.name").findList();
List<String> sql = LoggedSql.stop();
assertThat(sql).hasSize(1);
assertThat(sql.get(0)).contains("'om_basic_parent' = u1.name");
LoggedSql.start();
DB.find(OmBasicParent.class).where().isNotEmpty("childrenWithWhere").findList();
sql = LoggedSql.stop();
assertThat(sql).hasSize(1);
assertThat(sql.get(0)).contains("'om_basic_parent' = x.name");
}
@Test
public void testLazyLoad() throws Exception {
OmBasicParent el = new OmBasicParent("testLazyLoad");
DB.save(el);
LoggedSql.start();
el = DB.find(OmBasicParent.class).select("name").where().eq("name", "testLazyLoad").findOne();
el.getChildrenWithWhere().size(); // trigger Lazy load
List<String> sql = LoggedSql.stop();
assertThat(sql).hasSize(2);
assertThat(sql.get(0)).contains("select t0.id, t0.name from om_basic_parent");
assertThat(sql.get(1)).contains("where 'om_basic_parent' = t0.name");
DB.delete(el);
}
}
@@ -24,11 +24,11 @@ public class TestImplicitJoinOnParentRelationship extends BaseTestCase {
query.findList();
if (isPostgres()) {
String expectedSql = "select distinct on (t0.id) t0.id, t0.name from o_customer t0 join o_order u1 on u1.kcustomer_id = t0.id join o_order_detail u2 on u2.order_id = u1.id join o_product u3 on u3.id = u2.product_id where u3.name = ?";
String expectedSql = "select distinct on (t0.id) t0.id, t0.name from o_customer t0 join o_order u1 on u1.kcustomer_id = t0.id and u1.order_date is not null join o_order_detail u2 on u2.order_id = u1.id and u2.id > 0 join o_product u3 on u3.id = u2.product_id where u3.name = ?";
assertThat(sqlOf(query, 1)).contains(expectedSql);
} else {
String expectedSql = "select distinct t0.id, t0.name from o_customer t0 join o_order u1 on u1.kcustomer_id = t0.id join o_order_detail u2 on u2.order_id = u1.id join o_product u3 on u3.id = u2.product_id where u3.name = ?";
String expectedSql = "select distinct t0.id, t0.name from o_customer t0 join o_order u1 on u1.kcustomer_id = t0.id and u1.order_date is not null join o_order_detail u2 on u2.order_id = u1.id and u2.id > 0 join o_product u3 on u3.id = u2.product_id where u3.name = ?";
assertThat(sqlOf(query, 1)).contains(expectedSql);
}
@@ -55,10 +55,10 @@ public class TestImplicitJoinOnParentRelationship extends BaseTestCase {
query.findList();
if (isPostgres()) {
String expectedSql = "select distinct on (t0.id) t0.id, t0.name from o_customer t0 left join o_order u1 on u1.kcustomer_id = t0.id left join o_order_detail u2 on u2.order_id = u1.id left join o_product u3 on u3.id = u2.product_id where (u3.name = ? or t0.id = ?)";
String expectedSql = "select distinct on (t0.id) t0.id, t0.name from o_customer t0 left join o_order u1 on u1.kcustomer_id = t0.id and u1.order_date is not null left join o_order_detail u2 on u2.order_id = u1.id and u2.id > 0 left join o_product u3 on u3.id = u2.product_id where (u3.name = ? or t0.id = ?)";
assertThat(sqlOf(query, 1)).contains(expectedSql);
} else {
String expectedSql = "select distinct t0.id, t0.name from o_customer t0 left join o_order u1 on u1.kcustomer_id = t0.id left join o_order_detail u2 on u2.order_id = u1.id left join o_product u3 on u3.id = u2.product_id where (u3.name = ? or t0.id = ?)";
String expectedSql = "select distinct t0.id, t0.name from o_customer t0 left join o_order u1 on u1.kcustomer_id = t0.id and u1.order_date is not null left join o_order_detail u2 on u2.order_id = u1.id and u2.id > 0 left join o_product u3 on u3.id = u2.product_id where (u3.name = ? or t0.id = ?)";
assertThat(sqlOf(query, 1)).contains(expectedSql);
}
}
@@ -76,11 +76,11 @@ public class TestImplicitJoinOnParentRelationship extends BaseTestCase {
query.findList();
if (isPostgres()) {
String expectedSql = "select distinct on (t0.id) t0.id, t0.name from o_customer t0 left join o_order u1 on u1.kcustomer_id = t0.id left join o_order_detail u2 on u2.order_id = u1.id left join o_product u3 on u3.id = u2.product_id where (u3.name = ? or t0.id = ?)";
String expectedSql = "select distinct on (t0.id) t0.id, t0.name from o_customer t0 left join o_order u1 on u1.kcustomer_id = t0.id and u1.order_date is not null left join o_order_detail u2 on u2.order_id = u1.id and u2.id > 0 left join o_product u3 on u3.id = u2.product_id where (u3.name = ? or t0.id = ?)";
assertThat(sqlOf(query, 1)).contains(expectedSql);
} else {
String expectedSql = "select distinct t0.id, t0.name from o_customer t0 left join o_order u1 on u1.kcustomer_id = t0.id left join o_order_detail u2 on u2.order_id = u1.id left join o_product u3 on u3.id = u2.product_id where (u3.name = ? or t0.id = ?)";
String expectedSql = "select distinct t0.id, t0.name from o_customer t0 left join o_order u1 on u1.kcustomer_id = t0.id and u1.order_date is not null left join o_order_detail u2 on u2.order_id = u1.id and u2.id > 0 left join o_product u3 on u3.id = u2.product_id where (u3.name = ? or t0.id = ?)";
assertThat(sqlOf(query, 1)).contains(expectedSql);
}
}
@@ -41,7 +41,7 @@ public class TestManyWhereJoin extends BaseTestCase {
}
assertThat(sql).contains("join o_order ");
assertThat(sql).contains(".status = ?");
assertThat(sql).contains("t0.id, t0.status from o_customer t0 join o_order u1 on u1.kcustomer_id = t0.id where u1.status = ?");
assertThat(sql).contains("t0.id, t0.status from o_customer t0 join o_order u1 on u1.kcustomer_id = t0.id and u1.order_date is not null where u1.status = ?");
}
@Test
@@ -183,7 +183,7 @@ public class TestQueryFilterMany extends BaseTestCase {
List<String> sqlList = LoggedSqlCollector.stop();
assertEquals(1, sqlList.size());
assertThat(sqlList.get(0)).contains("from o_customer t0 left join o_order t1 on t1.kcustomer_id = t0.id and t1.order_date is not null left join o_customer t2 on t2.id = t1.kcustomer_id where exists (select 1 from o_order x where x.kcustomer_id = t0.id) and 1=0 order by t0.id");
assertThat(sqlList.get(0)).contains("from o_customer t0 left join o_order t1 on t1.kcustomer_id = t0.id and t1.order_date is not null left join o_customer t2 on t2.id = t1.kcustomer_id where exists (select 1 from o_order x where x.kcustomer_id = t0.id and x.order_date is not null) and 1=0 order by t0.id");
}
@Test
@@ -1,14 +1,15 @@
package org.tests.query;
import io.ebean.BaseTestCase;
import io.ebean.Ebean;
import io.ebean.DB;
import io.ebean.Query;
import org.tests.model.basic.Order;
import org.tests.model.basic.ResetBasicData;
import org.tests.model.m2m.Role;
import org.junit.Test;
import static org.junit.Assert.assertTrue;
import static org.assertj.core.api.Assertions.assertThat;
public class TestQueryIsNull extends BaseTestCase {
@@ -17,90 +18,90 @@ public class TestQueryIsNull extends BaseTestCase {
public void queryShouldContainIsNullOnColumn() {
ResetBasicData.reset();
Query<Order> query = Ebean.find(Order.class).where().isNull("customerName").query();
Query<Order> query = DB.find(Order.class).where().isNull("customerName").query();
query.findList();
assertTrue(query.getGeneratedSql().contains("name is null"));
assertThat(query.getGeneratedSql()).contains("name is null");
}
@Test
public void isNotNull_when_OneToMany_expect_existsSubquery() {
ResetBasicData.reset();
Query<Order> query = Ebean.find(Order.class).where().isNotNull("details").query();
Query<Order> query = DB.find(Order.class).where().isNotNull("details").query();
query.findList();
assertTrue(query.getGeneratedSql().contains(" where exists (select 1 from o_order_detail x where x.order_id = t0.id)"));
assertThat(query.getGeneratedSql()).contains(" where exists (select 1 from o_order_detail x where x.order_id = t0.id and x.id > 0)");
}
@Test
public void isNotEmpty_when_OneToMany_expect_existsSubquery() {
ResetBasicData.reset();
Query<Order> query = Ebean.find(Order.class).where().isNotEmpty("details").query();
Query<Order> query = DB.find(Order.class).where().isNotEmpty("details").query();
query.findList();
assertTrue(query.getGeneratedSql().contains(" where exists (select 1 from o_order_detail x where x.order_id = t0.id)"));
assertThat(query.getGeneratedSql()).contains(" where exists (select 1 from o_order_detail x where x.order_id = t0.id and x.id > 0)");
}
@Test
public void isNull_when_OneToMany_expect_notExistsSubquery() {
ResetBasicData.reset();
Query<Order> query = Ebean.find(Order.class).where().isNull("details").query();
Query<Order> query = DB.find(Order.class).where().isNull("details").query();
query.findList();
assertTrue(query.getGeneratedSql().contains(" where not exists (select 1 from o_order_detail x where x.order_id = t0.id)"));
assertThat(query.getGeneratedSql()).contains(" where not exists (select 1 from o_order_detail x where x.order_id = t0.id and x.id > 0)");
}
@Test
public void isEmpty_when_OneToMany_expect_notExistsSubquery() {
ResetBasicData.reset();
Query<Order> query = Ebean.find(Order.class).where().isEmpty("details").query();
Query<Order> query = DB.find(Order.class).where().isEmpty("details").query();
query.findList();
assertTrue(query.getGeneratedSql().contains(" where not exists (select 1 from o_order_detail x where x.order_id = t0.id)"));
assertThat(query.getGeneratedSql()).contains(" where not exists (select 1 from o_order_detail x where x.order_id = t0.id and x.id > 0)");
}
@Test
public void isEmpty_when_ManyToMany_expect_notExistsSubqueryAndNoJoin() {
ResetBasicData.reset();
Query<Role> query = Ebean.find(Role.class).where().isEmpty("permissions").query();
Query<Role> query = DB.find(Role.class).where().isEmpty("permissions").query();
query.findList();
assertTrue(query.getGeneratedSql().contains("from mt_role t0 where not exists (select 1 from mt_role_permission x where x.mt_role_id = t0.id)"));
assertThat(query.getGeneratedSql()).contains("from mt_role t0 where not exists (select 1 from mt_role_permission x where x.mt_role_id = t0.id)");
}
@Test
public void isNull_when_ManyToMany_expect_notExistsSubqueryAndNoJoin() {
ResetBasicData.reset();
Query<Role> query = Ebean.find(Role.class).where().isNull("permissions").query();
Query<Role> query = DB.find(Role.class).where().isNull("permissions").query();
query.findList();
assertTrue(query.getGeneratedSql().contains("from mt_role t0 where not exists (select 1 from mt_role_permission x where x.mt_role_id = t0.id)"));
assertThat(query.getGeneratedSql()).contains("from mt_role t0 where not exists (select 1 from mt_role_permission x where x.mt_role_id = t0.id)");
}
@Test
public void isNotEmpty_when_ManyToMany_expect_existsSubqueryAndNoJoin() {
ResetBasicData.reset();
Query<Role> query = Ebean.find(Role.class).where().isNotEmpty("permissions").query();
Query<Role> query = DB.find(Role.class).where().isNotEmpty("permissions").query();
query.findList();
assertTrue(query.getGeneratedSql().contains("from mt_role t0 where exists (select 1 from mt_role_permission x where x.mt_role_id = t0.id)"));
assertThat(query.getGeneratedSql()).contains("from mt_role t0 where exists (select 1 from mt_role_permission x where x.mt_role_id = t0.id)");
}
@Test
public void isNotNull_when_ManyToMany_expect_existsSubqueryAndNoJoin() {
ResetBasicData.reset();
Query<Role> query = Ebean.find(Role.class).where().isNotNull("permissions").query();
Query<Role> query = DB.find(Role.class).where().isNotNull("permissions").query();
query.findList();
assertTrue(query.getGeneratedSql().contains("from mt_role t0 where exists (select 1 from mt_role_permission x where x.mt_role_id = t0.id)"));
assertThat(query.getGeneratedSql()).contains("from mt_role t0 where exists (select 1 from mt_role_permission x where x.mt_role_id = t0.id)");
}
}
@@ -0,0 +1,28 @@
package org.tests.query.cancel;
import org.tests.model.basic.EBasic.Status;
/**
* DTO for Ebasic Queries.
*/
public class EBasicDto {
private Integer id;
private Status status;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public Status getStatus() {
return status;
}
public void setStatus(Status status) {
this.status = status;
}
}
@@ -0,0 +1,57 @@
package org.tests.query.cancel;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import org.h2.api.Trigger;
import io.ebean.DB;
import io.ebean.Transaction;
/**
* Class to artificially slow down selects on 'e_basic' table
*/
public class SlowDownEBasic implements Trigger {
private static int wait;
private static boolean triggerInstalled;
@Override
public void init(final Connection conn, final String schemaName, final String triggerName, final String tableName,
final boolean before, final int type) {
}
@Override
public void fire(final Connection conn, final Object[] oldRow, final Object[] newRow) {
try {
Thread.sleep(wait);
} catch (InterruptedException e) {
// nop
}
}
@Override
public void close() {
}
@Override
public void remove() {
}
public static void setSelectWaitMillis(final int wait) throws SQLException {
SlowDownEBasic.wait = wait;
if (triggerInstalled) {
return;
}
triggerInstalled = true;
try (Transaction txn = DB.beginTransaction(); Statement stmt = txn.getConnection().createStatement()) {
stmt.execute("CREATE TRIGGER SLOW_DOWN_E_BASIC BEFORE SELECT ON e_basic " + "CALL \""
+ SlowDownEBasic.class.getName() + "\"");
txn.commit();
}
}
}
@@ -0,0 +1,362 @@
package org.tests.query.cancel;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatCode;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import java.sql.SQLException;
import java.util.UUID;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.function.Consumer;
import java.util.function.Function;
import javax.persistence.PersistenceException;
import org.junit.BeforeClass;
import org.junit.Test;
import org.tests.model.basic.EBasic;
import io.ebean.BaseTestCase;
import io.ebean.DB;
import io.ebean.DtoQuery;
import io.ebean.Query;
import io.ebean.QueryIterator;
import io.ebean.SqlQuery;
import io.ebean.annotation.ForPlatform;
import io.ebean.annotation.Platform;
/**
* Tests, if all kind of queries are cancelable. There are two ways how to
* cancel a query: <br/>
* <b>At begin:</b>
*
* <pre>
* query = DB.find(...)
* query.cancel();
* query.findList();
* </pre>
*
* The query was caneled before executing. In this case we do hit the DB driver
* <br/>
* <br/>
* <b>During run:</b>
*
* <pre>
* // Thread 1: Thread 2
* query = DB.find(...)
* query.findList();
* ...finding
* ...finding query.cancel();
* ...JDBC-Exception
* </pre>
*
* The test tries to simulate a slow query by installing the
* {@link SlowDownEBasic} 'SELECT' trigger. The trigger can be configured to
* wait 3 * <code>timing</code> ms and a second thread will cancel the query in
* <code>timing</code> ms.
*
* in this case, we expect a JDBC exception from the driver. <br/>
* <br/>
* NOTE:<br/>
* H2 checks the cancel flag in org.h2.command.Prepared::setCurrentRowNumber
* only every 128th row. So we need at least 128 models and we cannot check
* queries like findCount or findOne, because they only return one row.
*
* @author Roland Praml, FOCONIS AG
*
*/
public class SqlQueryCancelTest extends BaseTestCase {
private int timing = 10;
@BeforeClass
public static void setupTestData() throws SQLException {
for (int i = 0; i < 128; i++) {
EBasic model = new EBasic("Basic " + i);
DB.save(model);
}
SlowDownEBasic.setSelectWaitMillis(0);
}
@Test
public void cancelSqlQueryAtBegin() throws SQLException {
doCancelSqlAtBegin(SqlQuery::findList);
doCancelSqlAtBegin(SqlQuery::findOne);
doCancelSqlAtBegin(q -> q.findEach(e -> {}));
doCancelSqlAtBegin(q -> q.findEachWhile(e -> true));
}
@ForPlatform(Platform.H2)
@Test
public void cancelSqlDuringRun() throws SQLException {
doCancelSqlDuringRun(SqlQuery::findList);
// doCancelSqlDuringRun(q -> q.setMaxRows(1).findOne());
// findOne cannot be tested, as H2 does the cancel check every 128 rows only
doCancelSqlDuringRun(q -> q.findEach(e -> {}));
doCancelSqlDuringRun(q -> q.findEachWhile(e -> true));
}
@Test
public void cancelOrmQueryAtBegin() throws SQLException {
doCancelOrmAtBegin(Query::findCount);
doCancelOrmAtBegin(Query::findFutureCount);
// We cannot test 'findCount' due H2 restrictions
doCancelOrmAtBegin(Query::findFutureIds);
doCancelOrmAtBegin(Query::findFutureList);
doCancelOrmAtBegin(Query::findIds);
doCancelOrmAtBegin(Query::findIterate);
doCancelOrmAtBegin(Query::findList);
doCancelOrmAtBegin(Query::findMap);
doCancelOrmAtBegin(Query::findOne);
doCancelOrmAtBegin(q -> q.setMaxRows(1000).findPagedList().getList()); // untested
doCancelOrmAtBegin(Query::findSet);
doCancelOrmAtBegin(Query::findSingleAttribute);
doCancelOrmAtBegin(Query::findSingleAttributeList);
doCancelOrmAtBegin(Query::findStream);
// testDuringRun(Query::findVersions);
// EBasic has no history support, but it should work if @History is added
doCancelOrmAtBegin(q -> q.findEach(e -> {}));
doCancelOrmAtBegin(q -> q.findEachWhile(e -> true));
}
@ForPlatform(Platform.H2)
@Test
public void cancelOrmDuringRun() throws Throwable {
// doCancelOrmDuringRun(Query::findCount);
// testDuringRunFuture(Query::findFutureCount);
// We cannot test 'findCount' due H2 restrictions
doCancelOrmFutureDuringRun(Query::findFutureIds);
doCancelOrmFutureDuringRun(Query::findFutureList);
doCancelOrmDuringRun(Query::findIds);
doCancelOrmDuringRun(Query::findIterate);
doCancelOrmDuringRun(Query::findList);
doCancelOrmDuringRun(Query::findMap);
// doCancelOrmDuringRun(q -> q.setMaxRows(1).findOne());
// findOne cannot be tested, as H2 does the cancel check every 128 rows only
doCancelOrmDuringRun(q -> q.setMaxRows(1000).findPagedList().getList()); // untested
doCancelOrmDuringRun(Query::findSet);
doCancelOrmDuringRun(Query::findSingleAttribute);
doCancelOrmDuringRun(Query::findSingleAttributeList);
doCancelOrmDuringRun(Query::findStream);
// testDuringRun(Query::findVersions);
// EBasic has no history support, but it should work if @History is added
doCancelOrmDuringRun(q -> q.findEach(e -> {}));
doCancelOrmDuringRun(q -> q.findEachWhile(e -> true));
}
@Test
public void cancelOrmDuringIterate() throws SQLException {
Query<EBasic> query = DB.find(EBasic.class);
QueryIterator<EBasic> iter = query.findIterate();
assertThat(iter.hasNext()).isTrue();
query.cancel();
assertThat(iter.next()).isNotNull();
// We might have 100 entities in a buffer. So we must iterate through all.
assertThatThrownBy(() -> {
while(iter.hasNext()) iter.next();
})
.isInstanceOf(PersistenceException.class)
.hasMessageContaining("Query was cancelled");
}
@Test
public void cancelOrmDtoQueryAtBegin() throws SQLException {
doCancelOrmDtoAtBegin(DtoQuery::findIterate);
doCancelOrmDtoAtBegin(DtoQuery::findList);
doCancelOrmDtoAtBegin(DtoQuery::findOne);
doCancelOrmDtoAtBegin(DtoQuery::findStream);
doCancelOrmDtoAtBegin(q -> q.findEach(e -> {}));
doCancelOrmDtoAtBegin(q -> q.findEachWhile(e -> true));
}
@ForPlatform(Platform.H2)
@Test
public void cancelOrmDtoDuringRun() throws SQLException {
doCancelOrmDtoDuringRun(DtoQuery::findIterate);
doCancelOrmDtoDuringRun(DtoQuery::findList);
// doCancelOrmDtoDuringRun(q -> q.setMaxRows(1).findOne());
// findOne cannot be tested, as H2 does the cancel check every 128 rows only
doCancelOrmDtoDuringRun(DtoQuery::findStream);
doCancelOrmDtoDuringRun(q -> q.findEach(e -> {}));
doCancelOrmDtoDuringRun(q -> q.findEachWhile(e -> true));
}
@Test
public void cancelOrmDtoDuringIterate() throws SQLException {
DtoQuery<EBasicDto> query = DB.find(EBasic.class).select("id,status").asDto(EBasicDto.class);
QueryIterator<EBasicDto> iter = query.findIterate();
assertThat(iter.hasNext()).isTrue();
query.cancel();
assertThat(iter.next()).isNotNull();
// We might have 100 entities in a buffer. So we must iterate through all.
assertThatThrownBy(() -> {
while(iter.hasNext()) iter.next();
})
.isInstanceOf(PersistenceException.class)
.hasMessageContaining("Query was cancelled");
}
@Test
public void cancelSqlDtoQueryAtBegin() throws SQLException {
doCancelSqlDtoAtBegin(DtoQuery::findIterate);
doCancelSqlDtoAtBegin(DtoQuery::findList);
doCancelSqlDtoAtBegin(DtoQuery::findOne);
doCancelSqlDtoAtBegin(DtoQuery::findStream);
doCancelSqlDtoAtBegin(q -> q.findEach(e -> {}));
doCancelSqlDtoAtBegin(q -> q.findEachWhile(e -> true));
}
@ForPlatform(Platform.H2)
@Test
public void cancelSqlDtoDuringRun() throws SQLException {
//doCancelSqlDtoDuringRun(DtoQuery::findIterate);
doCancelSqlDtoDuringRun(DtoQuery::findList);
// doCancelSqlDtoDuringRun(q -> q.setMaxRows(1).findOne());
// findOne cannot be tested, as H2 does the cancel check every 128 rows only
doCancelSqlDtoDuringRun(DtoQuery::findStream);
doCancelSqlDtoDuringRun(q -> q.findEach(e -> {}));
doCancelSqlDtoDuringRun(q -> q.findEachWhile(e -> true));
}
@Test
public void cancelSqlDtoDuringIterate() throws SQLException {
DtoQuery<EBasicDto> query = DB.findDto(EBasicDto.class, "select id, status from e_basic");
QueryIterator<EBasicDto> iter = query.findIterate();
assertThat(iter.hasNext()).isTrue();
query.cancel();
assertThat(iter.next()).isNotNull();
// We might have 100 entities in a buffer. So we must iterate through all.
assertThatThrownBy(() -> {
while(iter.hasNext()) iter.next();
})
.isInstanceOf(PersistenceException.class)
.hasMessageContaining("Query was cancelled");
}
private void doCancelSqlAtBegin(Consumer<SqlQuery> test) throws SQLException {
SqlQuery query = DB.sqlQuery("select * from e_basic");
query.cancel();
assertThatThrownBy(() -> test.accept(query))
.isInstanceOf(PersistenceException.class)
.hasMessageContaining("Query was cancelled");
}
private void doCancelSqlDuringRun(Consumer<SqlQuery> test) throws SQLException {
SqlQuery warmup = DB.sqlQuery("select * from e_basic");
test.accept(warmup);
SqlQuery query = DB.sqlQuery("select * from e_basic");
executeDelayed(query::cancel);
assertThatThrownBy(() -> test.accept(query))
.isInstanceOf(PersistenceException.class)
.hasCauseInstanceOf(org.h2.jdbc.JdbcSQLTimeoutException.class);
}
private void doCancelOrmAtBegin(Consumer<Query<EBasic>> test) throws SQLException {
Query<EBasic> query = DB.find(EBasic.class);
query.cancel();
assertThatThrownBy(() -> test.accept(query))
.isInstanceOf(PersistenceException.class)
.hasMessageContaining("Query was cancelled");
}
private void doCancelOrmDuringRun(Consumer<Query<EBasic>> test) throws SQLException {
Query<EBasic> warmup = DB.find(EBasic.class);
test.accept(warmup);
Query<EBasic> warmup2 = DB.find(EBasic.class);
test.accept(warmup2);
Query<EBasic> query = DB.find(EBasic.class);
executeDelayed(query::cancel);
assertThatThrownBy(() -> test.accept(query))
.isInstanceOf(PersistenceException.class)
.hasCauseInstanceOf(org.h2.jdbc.JdbcSQLTimeoutException.class);
}
private void doCancelOrmFutureDuringRun(Function<Query<EBasic>, Future<?>> test) throws SQLException, InterruptedException, ExecutionException {
Query<EBasic> warmup = DB.find(EBasic.class);
test.apply(warmup).get();
Query<EBasic> query = DB.find(EBasic.class);
executeDelayed(query::cancel);
assertThatThrownBy(() -> {
try {
test.apply(query).get();
} catch (ExecutionException ee) {
throw ee.getCause();
}
})
.isInstanceOf(PersistenceException.class)
.hasCauseInstanceOf(org.h2.jdbc.JdbcSQLTimeoutException.class);
}
private void doCancelOrmDtoAtBegin(Consumer<DtoQuery<EBasicDto>> test) throws SQLException {
DtoQuery<EBasicDto> query = DB.find(EBasic.class).select("id,status").asDto(EBasicDto.class);
query.cancel();
assertThatThrownBy(() -> test.accept(query))
.isInstanceOf(PersistenceException.class)
.hasMessageContaining("Query was cancelled");
}
private void doCancelOrmDtoDuringRun(Consumer<DtoQuery<EBasicDto>> test) throws SQLException {
DtoQuery<EBasicDto> warmup = DB.find(EBasic.class).select("id,status").asDto(EBasicDto.class);
test.accept(warmup);
DtoQuery<EBasicDto> query = DB.find(EBasic.class).select("id,status").asDto(EBasicDto.class);
executeDelayed(query::cancel);
assertThatThrownBy(() -> test.accept(query))
.isInstanceOf(PersistenceException.class)
.hasCauseInstanceOf(org.h2.jdbc.JdbcSQLTimeoutException.class);
}
private void doCancelSqlDtoAtBegin(Consumer<DtoQuery<EBasicDto>> test) throws SQLException {
DtoQuery<EBasicDto> query = DB.findDto(EBasicDto.class, "select id, status from e_basic");
query.cancel();
assertThatThrownBy(() -> test.accept(query))
.isInstanceOf(PersistenceException.class)
.hasMessageContaining("Query was cancelled");
}
private void doCancelSqlDtoDuringRun(Consumer<DtoQuery<EBasicDto>> test) throws SQLException {
DtoQuery<EBasicDto> warmup = DB.findDto(EBasicDto.class, "select id, status from e_basic");
test.accept(warmup);
DtoQuery<EBasicDto> query = DB.findDto(EBasicDto.class, "select id, status from e_basic");
executeDelayed(query::cancel);
assertThatThrownBy(() -> test.accept(query))
.isInstanceOf(PersistenceException.class)
.hasCauseInstanceOf(org.h2.jdbc.JdbcSQLTimeoutException.class);
}
private void executeDelayed(Runnable r) throws SQLException {
// We modify the DB here. Otherwise we may hit an internal H2 cache, if the
// same query is performed. Queries from the cache cannot be canceled.
EBasic makeDbDirty = new EBasic("Basic " + UUID.randomUUID());
DB.save(makeDbDirty);
SlowDownEBasic.setSelectWaitMillis(timing * 3);
new Thread(() -> {
try {
Thread.sleep(timing);
r.run();
SlowDownEBasic.setSelectWaitMillis(0);
} catch (Exception e) {
e.printStackTrace();
}
}).start();
}
}
@@ -30,6 +30,6 @@ public class TestQueryRawExpressionMany extends BaseTestCase {
query.findCount();
List<String> sql = LoggedSqlCollector.stop();
assertThat(trimSql(sql.get(0), 1)).contains("select count(*) from ( select distinct t0.id from o_order t0 left join o_order_detail t1 on t1.order_id = t0.id where t1.order_qty = ?)");
assertThat(trimSql(sql.get(0), 1)).contains("select count(*) from ( select distinct t0.id from o_order t0 left join o_order_detail t1 on t1.order_id = t0.id and t1.id > 0 where t1.order_qty = ?)");
}
}
@@ -69,7 +69,7 @@ public class TestQueryRowCountWithMany extends BaseTestCase {
assertEquals(list.size(), rowCount);
assertEquals(2, sqlLogged.size());
assertThat(trimSql(sqlLogged.get(1), 1)).contains(
"select count(*) from ( select distinct t0.id from o_order t0 join o_order_detail u1 on u1.order_id = t0.id where u1.product_id = ?)");
"select count(*) from ( select distinct t0.id from o_order t0 join o_order_detail u1 on u1.order_id = t0.id and u1.id > 0 where u1.product_id = ?)");
}
@@ -95,7 +95,7 @@ public class TestQueryRowCountWithMany extends BaseTestCase {
List<String> sqlLogged = LoggedSqlCollector.stop();
assertEquals(1, sqlLogged.size());
assertThat(trimSql(sqlLogged.get(0), 1)).contains("select count(*) from ( select distinct t0.id from o_order t0 join o_order_detail u1 on u1.order_id = t0.id where u1.product_id = ?)");
assertThat(trimSql(sqlLogged.get(0), 1)).contains("select count(*) from ( select distinct t0.id from o_order t0 join o_order_detail u1 on u1.order_id = t0.id and u1.id > 0 where u1.product_id = ?)");
query.findList();
}
@@ -64,7 +64,7 @@ public class TestQuerySingleAttribute extends BaseTestCase {
assertThat(sqlOf(query)).contains("select r1.attribute_, count(*) " +
"from (select distinct t0.id, t0.name as attribute_ " +
"from o_customer t0 left join contact u1 on u1.customer_id = t0.id left join o_order u2 on u2.kcustomer_id = t0.id " +
"from o_customer t0 left join contact u1 on u1.customer_id = t0.id left join o_order u2 on u2.kcustomer_id = t0.id and u2.order_date is not null " +
"where t0.name = ? and (u2.status = ? or u1.first_name = ?)) r1 " +
"group by r1.attribute_ " +
"order by count(*) desc, r1.attribute_");
@@ -5,6 +5,8 @@ import io.ebean.DB;
import io.ebean.RowMapper;
import io.ebean.SqlQuery;
import io.ebean.SqlRow;
import io.ebean.annotation.ForPlatform;
import io.ebean.annotation.Platform;
import io.ebean.meta.MetaTimedMetric;
import org.ebeantest.LoggedSqlCollector;
import org.junit.Test;
@@ -24,6 +26,19 @@ import static org.junit.Assert.assertEquals;
public class SqlQueryTests extends BaseTestCase {
@ForPlatform(Platform.H2)
@Test
public void selectFunction() {
String sql = "select length(?)";
final Long val = DB.sqlQuery(sql).setParameter("NotVeryLong").mapToScalar(Long.class).findOne();
assertThat(val).isEqualTo(11);
String sql2 = "select length(:val)";
final Long val2 = DB.sqlQuery(sql2).setParameter("val", "NotVeryLong").mapToScalar(Long.class).findOne();
assertThat(val2).isEqualTo(11);
}
@Test
public void findSingleAttributeList_decimal() {
@@ -58,6 +58,52 @@ public class TestSoftDeleteBasic extends BaseTestCase {
}
@Test
public void findSingleAttribute() {
EBasicSoftDelete bean = new EBasicSoftDelete();
bean.setName("findSingleAttribute");
DB.save(bean);
LoggedSqlCollector.start();
final String name0 = DB.find(EBasicSoftDelete.class)
.select("name")
.where().eq("name", "findSingleAttribute")
.findSingleAttribute();
List<String> sql0 = LoggedSqlCollector.current();
assertThat(sql0.get(0)).contains("where t0.name = ? and t0.deleted =");
assertThat(name0).isEqualTo("findSingleAttribute");
// now soft delete the bean
DB.delete(bean);
List<String> sqlUpdate = LoggedSqlCollector.current();
assertThat(sqlUpdate.get(0)).contains("update ebasic_sdchild set");
// use setIncludeSoftDeletes
final String name1 = DB.find(EBasicSoftDelete.class)
.select("name")
.where().eq("name", "findSingleAttribute")
.setIncludeSoftDeletes()
.findSingleAttribute();
List<String> sql1 = LoggedSqlCollector.current();
assertThat(sql1.get(0)).doesNotContain(" and t0.deleted =");
assertThat(name1).isEqualTo("findSingleAttribute");
// not using setIncludeSoftDeletes, so don't find it
final String name2 = DB.find(EBasicSoftDelete.class)
.select("name")
.where().eq("name", "findSingleAttribute")
.findSingleAttribute();
List<String> sql2 = LoggedSqlCollector.stop();
assertThat(sql2.get(0)).contains(" and t0.deleted =");
assertThat(name2).isNull();
}
@Test
public void testFindIdsWhenIncludeSoftDeletedChlld() {
@@ -1,8 +1,7 @@
package org.tests.transaction;
import io.ebean.BaseTestCase;
import io.ebean.Ebean;
import io.ebean.EbeanServer;
import io.ebean.DB;
import io.ebean.Transaction;
import io.ebean.TransactionCallbackAdapter;
import org.junit.Test;
@@ -18,20 +17,12 @@ public class TestTransactionCallback extends BaseTestCase {
int countPreRollback;
int countPostRollback;
@Test(expected = PersistenceException.class)
public void test_noActiveTransaction() {
Ebean.register(new MyCallback());
}
@Test
public void test_commitAndRollback() {
try (Transaction txn = Ebean.beginTransaction()) {
Ebean.register(new MyCallback());
txn.getConnection();
Ebean.commitTransaction();
try (Transaction txn = DB.beginTransaction()) {
DB.register(new MyCallback());
txn.getConnection(); // Ebean assumes writes have occurred
txn.commit();
}
assertEquals(1, countPreCommit);
@@ -39,28 +30,54 @@ public class TestTransactionCallback extends BaseTestCase {
assertEquals(0, countPreRollback);
assertEquals(0, countPostRollback);
Ebean.beginTransaction();
DB.beginTransaction();
try {
Ebean.register(new MyCallback());
DB.register(new MyCallback());
} finally {
Ebean.rollbackTransaction();
DB.rollbackTransaction();
}
assertEquals(1, countPreCommit);
assertEquals(1, countPostCommit);
assertEquals(1, countPreRollback);
assertEquals(1, countPostRollback);
}
@Test
public void test_commit_whenNoDbWrite() {
try (Transaction txn = DB.beginTransaction()) {
DB.register(new MyCallback());
txn.commit();
}
assertEquals(1, countPreCommit);
assertEquals(1, countPostCommit);
assertEquals(0, countPreRollback);
assertEquals(0, countPostRollback);
}
@Test
public void test_rollback_whenNoDbWrite() {
try (Transaction txn = DB.beginTransaction()) {
DB.register(new MyCallback());
txn.rollback();
}
assertEquals(0, countPreCommit);
assertEquals(0, countPostCommit);
assertEquals(1, countPreRollback);
assertEquals(1, countPostRollback);
}
@Test(expected = PersistenceException.class)
public void test_withEbeanserver() {
EbeanServer server = Ebean.getServer(null);
server.register(new MyCallback());
public void test_noActiveTransaction() {
DB.register(new MyCallback());
}
@Test(expected = PersistenceException.class)
public void test_noActiveTransaction_withDatabase() {
DB.getDefault().register(new MyCallback());
}
class MyCallback extends TransactionCallbackAdapter {
+4 -4
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>12.9.3</version>
<version>12.10.0</version>
</parent>
<name>ebean ddl generation</name>
@@ -28,14 +28,14 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core-type</artifactId>
<version>12.9.3</version>
<version>12.10.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>12.9.3</version>
<version>12.10.0</version>
<scope>provided</scope>
</dependency>
@@ -76,7 +76,7 @@
<plugin>
<groupId>io.ebean</groupId>
<artifactId>ebean-maven-plugin</artifactId>
<version>12.9.1</version>
<version>12.10.0</version>
<executions>
<execution>
<id>test</id>
@@ -149,7 +149,7 @@ public class ModelBuildPropertyVisitor extends BaseTablePropertyVisitor {
@Override
public void visitMany(BeanPropertyAssocMany<?> p) {
if (p.hasJoinTable() && p.getMappedBy() == null) {
if (p.createJoinTable()) {
// only create on other 'owning' side
// build the create table and fkey constraints
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>12.9.3</version>
<version>12.10.0</version>
</parent>
<name>ebean external mapping api</name>
+5 -5
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>12.9.3</version>
<version>12.10.0</version>
</parent>
<!-- <parent>-->
<!-- <groupId>org.avaje</groupId>-->
@@ -14,7 +14,7 @@
<scm>
<developerConnection>scm:git:git@github.com:ebean-orm/ebean.git</developerConnection>
<tag>ebean-parent-12.9.3</tag>
<tag>ebean-parent-12.10.0</tag>
</scm>
<name>ebean external mapping xml</name>
@@ -33,7 +33,7 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-externalmapping-api</artifactId>
<version>12.9.3</version>
<version>12.10.0</version>
</dependency>
<dependency>
@@ -59,14 +59,14 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>12.9.3</version>
<version>12.10.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-ddl-generator</artifactId>
<version>12.9.3</version>
<version>12.10.0</version>
<scope>test</scope>
</dependency>
+3 -3
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>12.9.3</version>
<version>12.10.0</version>
</parent>
<name>ebean postgis</name>
@@ -23,7 +23,7 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>12.9.3</version>
<version>12.10.0</version>
<scope>provided</scope>
</dependency>
@@ -74,7 +74,7 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-test</artifactId>
<version>12.9.3</version>
<version>12.10.0</version>
<scope>test</scope>
</dependency>
+5 -5
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>12.9.3</version>
<version>12.10.0</version>
</parent>
<name>ebean querybean</name>
@@ -17,7 +17,7 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>12.9.3</version>
<version>12.10.0</version>
<scope>provided</scope>
</dependency>
@@ -57,21 +57,21 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-ddl-generator</artifactId>
<version>12.9.3</version>
<version>12.10.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>querybean-generator</artifactId>
<version>12.9.3</version>
<version>12.10.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-test</artifactId>
<version>12.9.3</version>
<version>12.10.0</version>
<scope>test</scope>
</dependency>
@@ -213,20 +213,38 @@ public abstract class TQRootBean<T, R> {
/**
* Set a FetchGroup to control what part of the object graph is loaded.
* <p>
* This is an alternative to using select() and fetch() providing a nice clean separation
* between what a query should load and the query predicates.
* </p>
* FetchGroup is immutable and threadsafe. We expect to create and store
* FetchGroup to a static final field and reuse the instance.
* <p>
* FetchGroup is an alternative to using select() and fetch() providing a nice
* clean separation between what a query should load and the query predicates.
*
* <pre>{@code
*
* FetchGroup<Customer> fetchGroup = FetchGroup.of(Customer.class)
* .select("name, status")
* .fetch("contacts", "firstName, lastName, email")
* .build();
* // immutable threadsafe
*
* List<Customer> customers =
* static final FetchGroup<Customer> fetchGroup =
* QCustomer.forFetchGroup()
* .shippingAddress.fetch()
* .contacts.fetch()
* .buildFetchGroup();
*
* new QCustomer()
* List<Customer> customers = new QCustomer()
* .select(fetchGroup)
* .findList();
*
* }</pre>
*
*
* <pre>{@code
*
* static final FetchGroup<Customer> fetchGroup =
* FetchGroup.of(Customer.class)
* .select("name, status")
* .fetch("contacts", "firstName, lastName, email")
* .build();
*
* List<Customer> customers = new QCustomer()
* .select(fetchGroup)
* .findList();
*
+7 -7
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>12.9.3</version>
<version>12.10.0</version>
</parent>
<artifactId>ebean-redis</artifactId>
@@ -16,41 +16,41 @@
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.6.1</version>
<version>3.6.3</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>12.9.3</version>
<version>12.10.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>12.9.3</version>
<version>12.10.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>12.9.3</version>
<version>12.10.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>querybean-generator</artifactId>
<version>12.9.3</version>
<version>12.10.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-test</artifactId>
<version>12.9.3</version>
<version>12.10.0</version>
<scope>test</scope>
</dependency>
+3 -3
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>12.9.3</version>
<version>12.10.0</version>
</parent>
<name>ebean test</name>
@@ -29,14 +29,14 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>12.9.3</version>
<version>12.10.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-ddl-generator</artifactId>
<version>12.9.3</version>
<version>12.10.0</version>
</dependency>
<dependency>
+4 -4
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>12.9.3</version>
<version>12.10.0</version>
</parent>
<name>ebean composite</name>
@@ -22,20 +22,20 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>12.9.3</version>
<version>12.10.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>12.9.3</version>
<version>12.10.0</version>
</dependency>
<!-- Technically optional but most expected to use query beans -->
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>12.9.3</version>
<version>12.10.0</version>
</dependency>
</dependencies>
+5 -5
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>12.9.3</version>
<version>12.10.0</version>
</parent>
<name>kotlin querybean generator</name>
@@ -29,7 +29,7 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>12.9.3</version>
<version>12.10.0</version>
<scope>test</scope>
</dependency>
@@ -43,7 +43,7 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>12.9.3</version>
<version>12.10.0</version>
<scope>test</scope>
</dependency>
@@ -64,7 +64,7 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-ddl-generator</artifactId>
<version>12.9.3</version>
<version>12.10.0</version>
<scope>test</scope>
</dependency>
@@ -145,7 +145,7 @@
<plugin>
<groupId>io.ebean</groupId>
<artifactId>ebean-maven-plugin</artifactId>
<version>12.9.1</version>
<version>12.10.0</version>
<executions>
<execution>
<id>test</id>
+2 -2
View File
@@ -9,7 +9,7 @@
<groupId>io.ebean</groupId>
<artifactId>ebean-parent</artifactId>
<version>12.9.3</version>
<version>12.10.0</version>
<packaging>pom</packaging>
<name>ebean parent</name>
@@ -18,7 +18,7 @@
<scm>
<developerConnection>scm:git:git@github.com:ebean-orm/ebean.git</developerConnection>
<tag>ebean-parent-12.9.3</tag>
<tag>ebean-parent-12.10.0</tag>
</scm>
<licenses>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>12.9.3</version>
<version>12.10.0</version>
</parent>
<name>querybean generator</name>
@@ -188,6 +188,24 @@ class SimpleQueryBeanWriter {
writer.eol();
writer.append(" /**").eol();
writer.append(" * Return a query bean used to build a FetchGroup.").eol();
writer.append(" * <p>").eol();
writer.append(" * FetchGroups are immutable and threadsafe and can be used by many").eol();
writer.append(" * concurrent queries. We typically stored FetchGroup as a static final field.").eol();
writer.append(" * <p>").eol();
writer.append(" * Example creating and using a FetchGroup.").eol();
writer.append(" * <pre>{@code").eol();
writer.append(" * ").eol();
writer.append(" * static final FetchGroup<Customer> fetchGroup = ").eol();
writer.append(" * QCustomer.forFetchGroup()").eol();
writer.append(" * .shippingAddress.fetch()").eol();
writer.append(" * .contacts.fetch()").eol();
writer.append(" * .buildFetchGroup();").eol();
writer.append(" * ").eol();
writer.append(" * List<Customer> customers = new QCustomer()").eol();
writer.append(" * .select(fetchGroup)").eol();
writer.append(" * .findList();").eol();
writer.append(" * ").eol();
writer.append(" * }</pre>").eol();
writer.append(" */").eol();
writer.append(" public static Q%s forFetchGroup() {", shortName).eol();
writer.append(" return new Q%s(FetchGroup.queryFor(%s.class));", shortName, shortName).eol();