mirror of
https://github.com/ebean-orm/ebean.git
synced 2024-04-21 10:51:47 +00:00
Compare commits
24
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
705e4f4098 | ||
|
|
1642833fdb | ||
|
|
492bb98e0e | ||
|
|
a8456f90db | ||
|
|
103b75172d | ||
|
|
0baa3a0eac | ||
|
|
f26e4b5381 | ||
|
|
3d4de4dee5 | ||
|
|
c621435a28 | ||
|
|
7408483a43 | ||
|
|
4d15535381 | ||
|
|
787c2f90f2 | ||
|
|
e590a2149d | ||
|
|
8c77740aa7 | ||
|
|
29582b57f7 | ||
|
|
2763f333d4 | ||
|
|
8840628d34 | ||
|
|
6fb28d7ded | ||
|
|
be810aa568 | ||
|
|
824cfa27be | ||
|
|
66cb6d5e7d | ||
|
|
4977aa2ad1 | ||
|
|
dd6a473008 | ||
|
|
ef1143bb70 |
+1
-1
@@ -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.
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
@@ -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>
|
||||
|
||||
@@ -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
@@ -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.
|
||||
|
||||
+39
-17
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
+1
@@ -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 @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();
|
||||
|
||||
+1
-1
@@ -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);
|
||||
|
||||
+1
-1
@@ -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 @Formula or @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();
|
||||
|
||||
+34
-14
@@ -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() {
|
||||
|
||||
+1
-1
@@ -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
|
||||
|
||||
+1
-1
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+44
-6
@@ -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);
|
||||
|
||||
+45
-6
@@ -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,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>
|
||||
|
||||
+1
-1
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
+18
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user