Revert "[15x] Remove "Future queries" findFutureList, findFutureIds, findFutureCount"

This commit is contained in:
Rob Bygrave
2024-02-26 22:02:28 +13:00
committed by GitHub
parent 24b99d065a
commit 71edd33fa3
20 changed files with 680 additions and 0 deletions
@@ -412,6 +412,42 @@ public interface ExpressionList<T> {
*/
Optional<T> findOneOrEmpty();
/**
* Execute find row count query in a background thread.
* <p>
* This returns a Future object which can be used to cancel, check the
* execution status (isDone etc) and get the value (with or without a
* timeout).
* </p>
*
* @return a Future object for the row count query
*/
FutureRowCount<T> findFutureCount();
/**
* Execute find Id's query in a background thread.
* <p>
* This returns a Future object which can be used to cancel, check the
* execution status (isDone etc) and get the value (with or without a
* timeout).
* </p>
*
* @return a Future object for the list of Id's
*/
FutureIds<T> findFutureIds();
/**
* Execute find list query in a background thread.
* <p>
* This returns a Future object which can be used to cancel, check the
* execution status (isDone etc) and get the value (with or without a
* timeout).
* </p>
*
* @return a Future object for the list result of the query
*/
FutureList<T> findFutureList();
/**
* Return a PagedList for this query using firstRow and maxRows.
* <p>
@@ -0,0 +1,20 @@
package io.ebean;
import java.util.List;
import java.util.concurrent.Future;
/**
* FutureIds represents the result of a background query execution for the Id's.
* <p>
* It extends the java.util.concurrent.Future with the ability to get the Id's
* while the query is still executing in the background.
* </p>
*/
public interface FutureIds<T> extends Future<List<Object>> {
/**
* Returns the original query used to fetch the Id's.
*/
Query<T> getQuery();
}
@@ -1087,6 +1087,41 @@ public interface Query<T> extends CancelableQuery {
*/
int findCount();
/**
* Execute find row count query in a background thread.
* <p>
* This returns a Future object which can be used to cancel, check the
* execution status (isDone etc) and get the value (with or without a
* timeout).
* </p>
*
* @return a Future object for the row count query
*/
FutureRowCount<T> findFutureCount();
/**
* Execute find Id's query in a background thread.
* <p>
* This returns a Future object which can be used to cancel, check the
* execution status (isDone etc) and get the value (with or without a
* timeout).
* </p>
*
* @return a Future object for the list of Id's
*/
FutureIds<T> findFutureIds();
/**
* Execute find list query in a background thread.
* <p>
* This query will execute in it's own PersistenceContext and using its own transaction.
* What that means is that it will not share any bean instances with other queries.
* </p>
*
* @return a Future object for the list result of the query
*/
FutureList<T> findFutureList();
/**
* Return a PagedList for this query using firstRow and maxRows.
* <p>
@@ -336,6 +336,10 @@ public interface SpiEbeanServer extends SpiServer, ExtendedServer, BeanCollectio
<T> FutureRowCount<T> findFutureCount(SpiQuery<T> query);
<T> FutureIds<T> findFutureIds(SpiQuery<T> query);
<T> FutureList<T> findFutureList(SpiQuery<T> query);
<T> PagedList<T> findPagedList(SpiQuery<T> query);
<T> Set<T> findSet(SpiQuery<T> query);
@@ -1200,6 +1200,45 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
return queryFuture;
}
@Override
public <T> FutureIds<T> findFutureIds(SpiQuery<T> query) {
SpiQuery<T> copy = query.copy();
boolean createdTransaction = false;
SpiTransaction transaction = query.transaction();
if (transaction == null) {
transaction = currentServerTransaction();
if (transaction == null) {
transaction = (SpiTransaction) createTransaction();
createdTransaction = true;
}
copy.usingTransaction(transaction);
}
QueryFutureIds<T> queryFuture = new QueryFutureIds<>(new CallableQueryIds<>(this, copy, createdTransaction));
backgroundExecutor.execute(queryFuture.futureTask());
return queryFuture;
}
@Override
public <T> FutureList<T> findFutureList(SpiQuery<T> query) {
SpiQuery<T> spiQuery = query.copy();
// FutureList query always run in it's own persistence content
spiQuery.setPersistenceContext(new DefaultPersistenceContext());
// Create a new transaction solely to execute the findList() at some future time
boolean createdTransaction = false;
SpiTransaction transaction = query.transaction();
if (transaction == null) {
transaction = currentServerTransaction();
if (transaction == null) {
transaction = (SpiTransaction) createTransaction();
createdTransaction = true;
}
spiQuery.usingTransaction(transaction);
}
QueryFutureList<T> queryFuture = new QueryFutureList<>(new CallableQueryList<>(this, spiQuery, createdTransaction));
backgroundExecutor.execute(queryFuture.futureTask());
return queryFuture;
}
@Override
public <T> PagedList<T> findPagedList(SpiQuery<T> query) {
int maxRows = query.getMaxRows();
@@ -218,6 +218,21 @@ public class DefaultExpressionList<T> implements SpiExpressionList<T> {
return query.update(transaction);
}
@Override
public FutureIds<T> findFutureIds() {
return query.findFutureIds();
}
@Override
public FutureRowCount<T> findFutureCount() {
return query.findFutureCount();
}
@Override
public FutureList<T> findFutureList() {
return query.findFutureList();
}
@Override
public PagedList<T> findPagedList() {
return query.findPagedList();
@@ -49,6 +49,21 @@ public final class FilterExpressionList<T> extends DefaultExpressionList<T> {
return rootQuery.filterMany(prop);
}
@Override
public FutureIds<T> findFutureIds() {
return rootQuery.findFutureIds();
}
@Override
public FutureList<T> findFutureList() {
return rootQuery.findFutureList();
}
@Override
public FutureRowCount<T> findFutureCount() {
return rootQuery.findFutureCount();
}
@Override
public List<T> findList() {
return rootQuery.findList();
@@ -328,6 +328,21 @@ final class JunctionExpression<T> implements SpiJunction<T>, SpiExpression, Expr
return exprList.exists();
}
@Override
public FutureIds<T> findFutureIds() {
return exprList.findFutureIds();
}
@Override
public FutureList<T> findFutureList() {
return exprList.findFutureList();
}
@Override
public FutureRowCount<T> findFutureCount() {
return exprList.findFutureCount();
}
@Override
public <A> List<A> findIds() {
return exprList.findIds();
@@ -12,6 +12,9 @@ import io.ebean.ExpressionList;
import io.ebean.FetchConfig;
import io.ebean.FetchGroup;
import io.ebean.FetchPath;
import io.ebean.FutureIds;
import io.ebean.FutureList;
import io.ebean.FutureRowCount;
import io.ebean.OrderBy;
import io.ebean.PagedList;
import io.ebean.PersistenceContextScope;
@@ -348,6 +351,21 @@ final class DefaultFetchGroupQuery<T> implements SpiFetchGroupQuery<T>, SpiQuery
throw new RuntimeException("EB102: Only select() and fetch() clause is allowed on FetchGroup");
}
@Override
public FutureRowCount<T> findFutureCount() {
throw new RuntimeException("EB102: Only select() and fetch() clause is allowed on FetchGroup");
}
@Override
public FutureIds<T> findFutureIds() {
throw new RuntimeException("EB102: Only select() and fetch() clause is allowed on FetchGroup");
}
@Override
public FutureList<T> findFutureList() {
throw new RuntimeException("EB102: Only select() and fetch() clause is allowed on FetchGroup");
}
@Override
public PagedList<T> findPagedList() {
throw new RuntimeException("EB102: Only select() and fetch() clause is allowed on FetchGroup");
@@ -0,0 +1,41 @@
package io.ebeaninternal.server.query;
import io.ebean.FutureIds;
import io.ebean.Query;
import io.ebean.Transaction;
import java.util.List;
import java.util.concurrent.FutureTask;
/**
* Default implementation of FutureIds.
*/
public final class QueryFutureIds<T> extends BaseFuture<List<Object>> implements FutureIds<T> {
private final CallableQueryIds<T> call;
public QueryFutureIds(CallableQueryIds<T> call) {
super(new FutureTask<>(call));
this.call = call;
}
public FutureTask<List<Object>> futureTask() {
return futureTask;
}
public Transaction transaction() {
return call.transaction;
}
@Override
public Query<T> getQuery() {
return call.query;
}
@Override
public boolean cancel(boolean mayInterruptIfRunning) {
call.query.cancel();
return super.cancel(mayInterruptIfRunning);
}
}
@@ -0,0 +1,75 @@
package io.ebeaninternal.server.query;
import io.ebean.FutureList;
import io.ebean.Query;
import io.ebean.Transaction;
import javax.persistence.PersistenceException;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
/**
* Default implementation for FutureList.
*/
public final class QueryFutureList<T> extends BaseFuture<List<T>> implements FutureList<T> {
private final CallableQueryList<T> call;
public QueryFutureList(CallableQueryList<T> call) {
super(new FutureTask<>(call));
this.call = call;
}
public FutureTask<List<T>> futureTask() {
return futureTask;
}
public Transaction transaction() {
return call.transaction;
}
@Override
public Query<T> getQuery() {
return call.query;
}
@Override
public boolean cancel(boolean mayInterruptIfRunning) {
call.query.cancel();
return super.cancel(mayInterruptIfRunning);
}
@Override
public List<T> getUnchecked() {
try {
return get();
} catch (InterruptedException e) {
// restore the interrupted status (so client can check for that)
Thread.currentThread().interrupt();
throw new PersistenceException(e);
} catch (ExecutionException e) {
throw new PersistenceException(e);
}
}
@Override
public List<T> getUnchecked(long timeout, TimeUnit unit) throws TimeoutException {
try {
return get(timeout, unit);
} catch (InterruptedException e) {
// restore the interrupted status (so client can check for that)
Thread.currentThread().interrupt();
throw new PersistenceException(e);
} catch (ExecutionException e) {
throw new PersistenceException(e);
}
}
}
@@ -1489,6 +1489,21 @@ public class DefaultOrmQuery<T> extends AbstractQuery implements SpiQuery<T> {
return server.findOneOrEmpty(this);
}
@Override
public final FutureIds<T> findFutureIds() {
return server.findFutureIds(this);
}
@Override
public final FutureList<T> findFutureList() {
return server.findFutureList(this);
}
@Override
public final FutureRowCount<T> findFutureCount() {
return server.findFutureCount(this);
}
@Override
public final PagedList<T> findPagedList() {
return server.findPagedList(this);
@@ -1782,6 +1782,47 @@ public abstract class TQRootBean<T, R> {
return query.findCount();
}
/**
* Execute find row count query in a background thread.
* <p>
* This returns a Future object which can be used to cancel, check the
* execution status (isDone etc) and get the value (with or without a
* timeout).
* </p>
*
* @return a Future object for the row count query
*/
public FutureRowCount<T> findFutureCount() {
return query.findFutureCount();
}
/**
* Execute find Id's query in a background thread.
* <p>
* This returns a Future object which can be used to cancel, check the
* execution status (isDone etc) and get the value (with or without a
* timeout).
* </p>
*
* @return a Future object for the list of Id's
*/
public FutureIds<T> findFutureIds() {
return query.findFutureIds();
}
/**
* Execute find list query in a background thread.
* <p>
* This query will execute in it's own PersistenceContext and using its own transaction.
* What that means is that it will not share any bean instances with other queries.
* </p>
*
* @return a Future object for the list result of the query
*/
public FutureList<T> findFutureList() {
return query.findFutureList();
}
/**
* Return a PagedList for this query using firstRow and maxRows.
* <p>
@@ -574,6 +574,16 @@ public class TDSpiEbeanServer extends TDSpiServer implements SpiEbeanServer {
return null;
}
@Override
public <T> FutureIds<T> findFutureIds(SpiQuery<T> query) {
return null;
}
@Override
public <T> FutureList<T> findFutureList(SpiQuery<T> query) {
return null;
}
@Override
public <T> PagedList<T> findPagedList(SpiQuery<T> query) {
return null;
@@ -0,0 +1,100 @@
package io.ebean.xtest.internal.server.query;
import io.ebean.*;
import io.ebean.xtest.BaseTestCase;
import io.ebeaninternal.server.query.QueryFutureIds;
import io.ebeaninternal.server.query.QueryFutureList;
import io.ebeaninternal.server.query.QueryFutureRowCount;
import org.junit.jupiter.api.Test;
import org.tests.model.basic.Customer;
import org.tests.model.basic.ResetBasicData;
import java.util.concurrent.ExecutionException;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.fail;
public class TestFutureRowCountErrorHandling extends BaseTestCase {
@Test
public void testFutureRowCount() throws InterruptedException {
ResetBasicData.reset();
Database server = DB.getDefault();
Query<Customer> query = server.createQuery(Customer.class)
.where().eq("doesNotExist", "this will fail")
.query();
FutureRowCount<Customer> futureRowCount = query.findFutureCount();
QueryFutureRowCount<Customer> internalRowCount = (QueryFutureRowCount<Customer>) futureRowCount;
Transaction t = internalRowCount.transaction();
try {
futureRowCount.get();
fail("never get here as the SQL is invalid");
} catch (ExecutionException e) {
// Confirm the Transaction has been rolled back
assertFalse(t.isActive());
}
}
@Test
public void testFutureIds() throws InterruptedException {
ResetBasicData.reset();
Database server = DB.getDefault();
Query<Customer> query = server.createQuery(Customer.class)
.where().eq("doesNotExist", "this will fail")
.query();
FutureIds<Customer> futureIds = query.findFutureIds();
QueryFutureIds<Customer> internalFuture = (QueryFutureIds<Customer>) futureIds;
Transaction t = internalFuture.transaction();
try {
internalFuture.get();
fail("never get here as the SQL is invalid");
} catch (ExecutionException e) {
// Confirm the Transaction has been rolled back
assertFalse(t.isActive());
}
}
@Test
public void testFutureList() throws InterruptedException {
ResetBasicData.reset();
Database server = DB.getDefault();
Query<Customer> query = server.createQuery(Customer.class)
.where().eq("doesNotExist", "this will fail")
.query();
FutureList<Customer> futureList = query.findFutureList();
QueryFutureList<Customer> internalFuture = (QueryFutureList<Customer>) futureList;
Transaction t = internalFuture.transaction();
try {
internalFuture.get();
fail("never get here as the SQL is invalid");
} catch (ExecutionException e) {
// Confirm the Transaction has been rolled back
assertFalse(t.isActive());
}
}
}
@@ -2,6 +2,7 @@ package org.tests.basic;
import io.ebean.xtest.BaseTestCase;
import io.ebean.DB;
import io.ebean.FutureIds;
import io.ebean.Query;
import org.junit.jupiter.api.Test;
import org.tests.model.basic.Order;
@@ -29,6 +30,12 @@ public class TestFetchId extends BaseTestCase {
List<Object> ids = query.findIds();
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
@@ -46,6 +53,11 @@ public class TestFetchId extends BaseTestCase {
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
@@ -29,7 +29,118 @@ class TestFindFutureRowCount extends BaseTestCase {
pagedList.loadCount();
assertThat(pagedList.getList()).hasSize(1);
assertThat(pagedList.getTotalCount()).isEqualTo(1);
FutureRowCount<EBasic> futureCount = DB.find(EBasic.class)
.where().eq("name", "count_when_inTransaction")
.findFutureCount();
assertThat(futureCount.get()).isEqualTo(1);
FutureRowCount<EBasic> futureCountUsingTxn = DB.find(EBasic.class)
.usingTransaction(transaction)
.where().eq("name", "count_when_inTransaction")
.findFutureCount();
assertThat(futureCountUsingTxn.get()).isEqualTo(1);
}
}
@Test
void findFutureIds_when_inTransaction() throws Exception {
try (Transaction transaction = DB.beginTransaction()) {
EBasic basic = new EBasic("findFutureIds_when_inTransaction");
DB.save(basic);
List<Object> ids = DB.find(EBasic.class)
.where().eq("name", "findFutureIds_when_inTransaction")
.findIds();
Object expectedIdValue = ids.get(0);
FutureIds<EBasic> futureIds = DB.find(EBasic.class)
.where().eq("name", "findFutureIds_when_inTransaction")
.findFutureIds();
List<Object> fids = futureIds.get();
assertThat(fids).hasSize(1);
assertThat(fids.get(0)).isEqualTo(expectedIdValue);
FutureIds<EBasic> futureIdsUsingTxn = DB.find(EBasic.class)
.usingTransaction(transaction)
.where().eq("name", "findFutureIds_when_inTransaction")
.findFutureIds();
List<Object> fids2 = futureIdsUsingTxn.get();
assertThat(fids2).hasSize(1);
assertThat(fids2.get(0)).isEqualTo(expectedIdValue);
}
}
@Test
void findFutureList_when_inTransaction() throws Exception {
try (Transaction transaction = DB.beginTransaction()) {
EBasic basic = new EBasic("findFutureList_when_inTransaction");
DB.save(basic);
List<EBasic> list = DB.find(EBasic.class)
.where().eq("name", "findFutureList_when_inTransaction")
.findList();
Object expectedIdValue = list.get(0).getId();
FutureList<EBasic> futureIds = DB.find(EBasic.class)
.where().eq("name", "findFutureList_when_inTransaction")
.findFutureList();
List<EBasic> fids = futureIds.get();
assertThat(fids).hasSize(1);
assertThat(fids.get(0).getId()).isEqualTo(expectedIdValue);
FutureList<EBasic> futureUsingTxn = DB.find(EBasic.class)
.usingTransaction(transaction)
.where().eq("name", "findFutureList_when_inTransaction")
.findFutureList();
List<EBasic> fids2 = futureUsingTxn.get();
assertThat(fids2).hasSize(1);
assertThat(fids2.get(0).getId()).isEqualTo(expectedIdValue);
}
}
@Test
void findFutures_when_newTransaction() throws Exception {
EBasic basic = new EBasic("findFutures_when_newTransaction");
DB.save(basic);
List<EBasic> list = DB.find(EBasic.class)
.where().eq("name", "findFutures_when_newTransaction")
.findList();
Object expectedIdValue = list.get(0).getId();
FutureList<EBasic> futureList = DB.find(EBasic.class)
.where().eq("name", "findFutures_when_newTransaction")
.findFutureList();
List<EBasic> flist = futureList.get();
assertThat(flist).hasSize(1);
assertThat(flist.get(0).getId()).isEqualTo(expectedIdValue);
FutureIds<EBasic> futureIds = DB.find(EBasic.class)
.where().eq("name", "findFutures_when_newTransaction")
.findFutureIds();
List<Object> fids = futureIds.get();
assertThat(fids).hasSize(1);
assertThat(fids.get(0)).isEqualTo(expectedIdValue);
FutureRowCount<EBasic> futureCount = DB.find(EBasic.class)
.where().eq("name", "findFutures_when_newTransaction")
.findFutureCount();
assertThat(futureCount.get()).isEqualTo(1);
DB.delete(basic);
}
}
@@ -0,0 +1,70 @@
package org.tests.query;
import io.ebean.xtest.BaseTestCase;
import io.ebean.DB;
import io.ebean.FutureList;
import io.ebean.Transaction;
import org.junit.jupiter.api.Test;
import org.tests.model.basic.Order;
import org.tests.model.basic.ResetBasicData;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class TestQueryFindFutureList extends BaseTestCase {
@Test
public void test_cancel() throws InterruptedException {
ResetBasicData.reset();
// warm the connection pool
Transaction t0 = DB.createTransaction();
Transaction t1 = DB.createTransaction();
Transaction t2 = DB.createTransaction();
t0.end();
t1.end();
t2.end();
FutureList<Order> futureList = DB.find(Order.class).findFutureList();
Thread.sleep(10);
futureList.cancel(true);
// calling again is ignored
futureList.cancel(true);
// don't shutdown immediately
Thread.sleep(50);
}
@Test
public void test_findFutureList() throws InterruptedException {
ResetBasicData.reset();
FutureList<Order> futureList = DB.find(Order.class).findFutureList();
// wait for it to complete
List<Order> orders = futureList.getUnchecked();
assertEquals(DB.find(Order.class).findCount(), orders.size());
}
@Test
public void test_findFutureListWithTimeout() throws InterruptedException, TimeoutException {
ResetBasicData.reset();
FutureList<Order> futureList = DB.find(Order.class).findFutureList();
// wait for it to complete
List<Order> orders = futureList.getUnchecked(1, TimeUnit.SECONDS);
assertEquals(DB.find(Order.class).findCount(), orders.size());
}
}
@@ -99,7 +99,10 @@ class SqlQueryCancelTest extends BaseTestCase {
@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);
@@ -123,6 +126,8 @@ class SqlQueryCancelTest extends BaseTestCase {
// 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);
@@ -138,7 +138,10 @@ class TestRawSqlOrmQuery extends BaseTestCase {
List<Customer> list = query.findList();
int rowCount = query.findCount();
FutureRowCount<Customer> futureRowCount = query.findFutureCount();
assertEquals(initialRowCount, rowCount);
assertEquals(initialRowCount, futureRowCount.get().intValue());
// check that lazy loading still executes
for (Customer customer : list) {