add support for findSingleAttributeSet

This commit is contained in:
Roland Praml
2022-05-13 14:45:08 +02:00
parent 61f529d15a
commit ca20c9ef1f
19 changed files with 148 additions and 48 deletions
@@ -383,6 +383,15 @@ public interface ExpressionList<T> {
*/
<A> List<A> findSingleAttributeList();
/**
* Executes the query returning a set of values for a single property.
*
* This can be used to cache sets.
*
* @return a HashSet of values for the selegted property
*/
<A> Set<A> findSingleAttributeSet();
/**
* Execute a query returning a single value of a single property/column.
* <pre>{@code
@@ -368,6 +368,11 @@ public interface ExtendedServer {
*/
<A, T> List<A> findSingleAttributeList(Query<T> query, Transaction transaction);
/**
* Execute the query returning a hashset of values for a single property.
*/
<A, T> Set<A> findSingleAttributeSet(Query<T> query, Transaction transaction);
/**
* Execute the query returning at most one entity bean or null (if no matching
* bean is found).
@@ -925,6 +925,11 @@ public interface Query<T> extends CancelableQuery {
*/
<A> List<A> findSingleAttributeList();
/**
* Execute the query returning a hashset of values for a single property.
*/
<A> Set<A> findSingleAttributeSet();
/**
* Execute a query returning a single value of a single property/column.
* <p>
@@ -143,6 +143,7 @@ public class BaseQueryTuner {
switch (type) {
case COUNT:
case ATTRIBUTE:
case ATTRIBUTE_SET:
case ID_LIST:
case UPDATE:
case DELETE:
@@ -92,6 +92,11 @@ public interface SpiQuery<T> extends Query<T>, SpiQueryFetch, TxnProfileEventCod
*/
ATTRIBUTE(FIND_ATTRIBUTE, "findAttribute", false, false),
/**
* Find single attribute set.
*/
ATTRIBUTE_SET(FIND_ATTRIBUTE_SET, "findAttributeSet", false, false),
/**
* Find rowCount.
*/
@@ -28,6 +28,7 @@ public interface TxnProfileEventCodes {
String FIND_ID_LIST = "fi";
String FIND_EXISTS = "ex";
String FIND_ATTRIBUTE = "fa";
String FIND_ATTRIBUTE_SET = "fas";
String FIND_COUNT = "fc";
String FIND_SUBQUERY = "fs";
@@ -364,7 +364,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
startQueryPlanCapture();
}
@Override
public void start() {
for (Plugin plugin : serverPlugins) {
@@ -1189,7 +1189,23 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
}
try {
request.initTransIfRequired();
return request.findSingleAttributeList();
return request.findSingleAttributeCollection(new ArrayList<>());
} finally {
request.endTransIfRequired();
}
}
@Override
@SuppressWarnings("unchecked")
public <A, T> Set<A> findSingleAttributeSet(Query<T> query, Transaction transaction) {
SpiOrmQueryRequest<T> request = createQueryRequest(Type.ATTRIBUTE_SET, query, transaction);
Object result = request.getFromQueryCache();
if (result != null) {
return (Set<A>) result;
}
try {
request.initTransIfRequired();
return request.findSingleAttributeCollection(new LinkedHashSet<>());
} finally {
request.endTransIfRequired();
}
@@ -6,6 +6,7 @@ import io.ebean.bean.BeanCollection;
import javax.persistence.PersistenceException;
import java.sql.SQLException;
import java.util.Collection;
import java.util.List;
/**
@@ -29,11 +30,11 @@ public interface OrmQueryEngine {
<T> BeanCollection<T> findMany(OrmQueryRequest<T> request);
/**
* Execute the findSingleAttributeList query.
* Execute the findSingleAttributeCollection query.
*/
<A> List<A> findSingleAttributeList(OrmQueryRequest<?> request);
<A extends Collection<?>> A findSingleAttributeCollection(OrmQueryRequest<?> request, A collection);
/**
/**
* Execute the findVersions query.
*/
<T> List<Version<T>> findVersions(OrmQueryRequest<T> request);
@@ -431,8 +431,8 @@ public final class OrmQueryRequest<T> extends BeanRequest implements SpiOrmQuery
}
@Override
public <A> List<A> findSingleAttributeList() {
return queryEngine.findSingleAttributeList(this);
public <A extends Collection<?>> A findSingleAttributeCollection(A collection) {
return queryEngine.findSingleAttributeCollection(this, collection);
}
/**
@@ -7,6 +7,7 @@ import io.ebeaninternal.api.SpiQuery;
import io.ebeaninternal.server.deploy.BeanDescriptor;
import io.ebeanservice.docstore.api.DocQueryRequest;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -120,9 +121,9 @@ public interface SpiOrmQueryRequest<T> extends BeanQueryRequest<T>, DocQueryRequ
<K> Map<K, T> findMap();
/**
* Execute the findSingleAttributeList query.
* Execute the findSingleAttributeCollection query.
*/
<A> List<A> findSingleAttributeList();
<A extends Collection<?>> A findSingleAttributeCollection(A collection);
/**
* Execute returning the ResultSet.
@@ -467,6 +467,11 @@ public class DefaultExpressionList<T> implements SpiExpressionList<T> {
return query.findSingleAttributeList();
}
@Override
public <A> Set<A> findSingleAttributeSet() {
return query.findSingleAttributeSet();
}
@Override
public boolean exists() {
return query.exists();
@@ -464,6 +464,11 @@ final class JunctionExpression<T> implements SpiJunction<T>, SpiExpression, Expr
return exprList.findSingleAttributeList();
}
@Override
public <A> Set<A> findSingleAttributeSet() {
return exprList.findSingleAttributeSet();
}
@Override
public PagedList<T> findPagedList() {
return exprList.findPagedList();
@@ -24,11 +24,7 @@ import org.slf4j.Logger;
import javax.persistence.PersistenceException;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.*;
/**
* Handles the Object Relational fetching.
@@ -91,16 +87,16 @@ public final class CQueryEngine {
/**
* Build and execute the findSingleAttributeList query.
*/
public <A> List<A> findSingleAttributeList(OrmQueryRequest<?> request) {
public <A extends Collection<?>> A findSingleAttributeList(OrmQueryRequest<?> request, A collection) {
CQueryFetchSingleAttribute rcQuery = queryBuilder.buildFetchAttributeQuery(request);
request.setCancelableQuery(rcQuery);
return findAttributeList(request, rcQuery);
return findAttributeCollection(request, rcQuery, collection);
}
@SuppressWarnings("unchecked")
private <A> List<A> findAttributeList(OrmQueryRequest<?> request, CQueryFetchSingleAttribute rcQuery) {
private <A extends Collection<?>> A findAttributeCollection(OrmQueryRequest<?> request, CQueryFetchSingleAttribute rcQuery, A collection) {
try {
List<A> list = (List<A>) rcQuery.findList();
rcQuery.findCollection(collection);
if (request.logSql()) {
logGeneratedSql(request, rcQuery.getGeneratedSql(), rcQuery.getBindLog(), rcQuery.micros());
}
@@ -109,13 +105,22 @@ public final class CQueryEngine {
}
if (request.isQueryCachePut()) {
request.addDependentTables(rcQuery.getDependentTables());
list = Collections.unmodifiableList(list);
request.putToQueryCache(list);
if (Boolean.FALSE.equals(request.query().isReadOnly())) {
list = new ArrayList<>(list);
if (collection instanceof List) {
collection = (A) Collections.unmodifiableList((List<?>) collection);
request.putToQueryCache(collection);
if (Boolean.FALSE.equals(request.query().isReadOnly())) {
collection = (A) new ArrayList<>(collection);
}
} else if (collection instanceof Set) {
collection = (A) Collections.unmodifiableSet((Set<?>) collection);
request.putToQueryCache(collection);
if (Boolean.FALSE.equals(request.query().isReadOnly())) {
collection = (A) new LinkedHashSet<>(collection);
}
}
}
return list;
return collection;
} catch (SQLException e) {
throw translate(request, rcQuery.getBindLog(), rcQuery.getGeneratedSql(), e);
}
@@ -144,7 +149,7 @@ public final class CQueryEngine {
public <A> List<A> findIds(OrmQueryRequest<?> request) {
CQueryFetchSingleAttribute rcQuery = queryBuilder.buildFetchIdsQuery(request);
request.setCancelableQuery(rcQuery);
return findAttributeList(request, rcQuery);
return findAttributeCollection(request, rcQuery, new ArrayList<>());
}
private <T> void logGeneratedSql(OrmQueryRequest<T> request, String sql, String bindLog, long micros) {
@@ -16,6 +16,7 @@ import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Set;
import java.util.concurrent.locks.ReentrantLock;
@@ -76,11 +77,10 @@ final class CQueryFetchSingleAttribute implements SpiProfileTransactionEvent, Ca
/**
* Execute the query returning the row count.
*/
List<Object> findList() throws SQLException {
void findCollection(Collection result) throws SQLException {
long startNano = System.nanoTime();
try {
prepareExecute();
List<Object> result = new ArrayList<>();
while (dataReader.next()) {
Object value = reader.read(dataReader);
if (containsCounts) {
@@ -95,7 +95,6 @@ final class CQueryFetchSingleAttribute implements SpiProfileTransactionEvent, Ca
queryPlan.captureBindForQueryPlan(predicates, executionTimeMicros);
}
getTransaction().profileEvent(this);
return result;
} finally {
close();
}
@@ -286,6 +286,11 @@ final class DefaultFetchGroupQuery<T> implements SpiFetchGroupQuery<T>, SpiQuery
throw new RuntimeException("EB102: Only select() and fetch() clause is allowed on FetchGroup");
}
@Override
public <A> Set<A> findSingleAttributeSet() {
throw new RuntimeException("EB102: Only select() and fetch() clause is allowed on FetchGroup");
}
@Override
public <A> A findSingleAttribute() {
throw new RuntimeException("EB102: Only select() and fetch() clause is allowed on FetchGroup");
@@ -17,6 +17,7 @@ import javax.persistence.PersistenceException;
import java.sql.SQLException;
import java.util.Collection;
import java.util.List;
import java.util.Set;
/**
* Main Finder implementation.
@@ -97,9 +98,9 @@ public final class DefaultOrmQueryEngine implements OrmQueryEngine {
}
@Override
public <A> List<A> findSingleAttributeList(OrmQueryRequest<?> request) {
public <A extends Collection<?>> A findSingleAttributeCollection(OrmQueryRequest<?> request, A collection) {
flushJdbcBatchOnQuery(request);
return queryEngine.findSingleAttributeList(request);
return queryEngine.findSingleAttributeList(request, collection);
}
@Override
@@ -1498,6 +1498,10 @@ public class DefaultOrmQuery<T> extends AbstractQuery implements SpiQuery<T> {
return (List<A>) server.findSingleAttributeList(this, transaction);
}
@Override
public final <A> Set<A> findSingleAttributeSet() {
return (Set<A>) server.findSingleAttributeSet(this, transaction);
}
@Override
public final <A> A findSingleAttribute() {
List<A> list = findSingleAttributeList();
@@ -695,6 +695,11 @@ public class TDSpiEbeanServer extends TDSpiServer implements SpiEbeanServer {
return null;
}
@Override
public <A, T> Set<A> findSingleAttributeSet(Query<T> query, Transaction transaction) {
return null;
}
@Override
public <T> T findOne(Query<T> query, Transaction transaction) {
return null;
+46 -19
View File
@@ -1,21 +1,23 @@
package org.tests.cache;
import io.ebean.xtest.BaseTestCase;
import io.ebean.CacheMode;
import io.ebean.DB;
import io.ebean.ExpressionList;
import io.ebean.bean.BeanCollection;
import io.ebean.cache.ServerCache;
import io.ebean.test.LoggedSql;
import io.ebean.xtest.BaseTestCase;
import org.junit.jupiter.api.Test;
import org.tests.model.basic.Customer;
import org.tests.model.basic.ResetBasicData;
import org.tests.model.cache.EColAB;
import java.util.List;
import java.util.Set;
import java.util.function.Consumer;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.junit.jupiter.api.Assertions.*;
public class TestQueryCache extends BaseTestCase {
@@ -50,7 +52,7 @@ public class TestQueryCache extends BaseTestCase {
}
@Test
public void findSingleAttribute() {
public void findSingleAttributeList() {
DB.find(EColAB.class).delete();
new EColAB("03", "SingleAttribute").save();
@@ -76,6 +78,7 @@ public class TestQueryCache extends BaseTestCase {
assertThat(colA_Second).isSameAs(colA_first);
List<String> colA_NotDistinct = DB
.find(EColAB.class)
.setUseQueryCache(true)
@@ -89,15 +92,39 @@ public class TestQueryCache extends BaseTestCase {
// ensure that findCount & findSingleAttribute use different
// slots in cache. If not a "Cannot cast List to int" should happen.
int count = DB
.find(EColAB.class)
.setUseQueryCache(true)
.select("columnA")
.where()
.eq("columnB", "SingleAttribute")
.findCount();
.find(EColAB.class)
.setUseQueryCache(true)
.select("columnA")
.where()
.eq("columnB", "SingleAttribute")
.findCount();
assertThat(count).isEqualTo(2);
}
@Test
public void findSingleAttributeSet() {
DB.find(EColAB.class).delete();
new EColAB("03", "SingleAttribute").save();
new EColAB("03", "SingleAttribute").save();
ExpressionList<EColAB> query = DB
.find(EColAB.class)
.setUseQueryCache(true)
.select("columnA")
.where()
.eq("columnB", "SingleAttribute");
Set<String> colA_first = query.findSingleAttributeSet();
Set<String> colA_Second = query.findSingleAttributeSet();
assertThat(colA_Second).isSameAs(colA_first).hasSize(1);
assertThatThrownBy(colA_first::clear).isInstanceOf(UnsupportedOperationException.class);
// ensure, that we do not have cache collisions on same query
query.findSingleAttributeList();
query.findSingleAttribute();
query.findCount();
}
@Test
public void findCount() {
@@ -279,7 +306,7 @@ public class TestQueryCache extends BaseTestCase {
assertSame(list, list2B);
List<Customer> list3 = DB.find(Customer.class).setUseQueryCache(true).setReadOnly(false).where()
.ilike("name", "Rob").findList();
.ilike("name", "Rob").findList();
assertNotSame(list, list3);
BeanCollection<Customer> bc3 = (BeanCollection<Customer>) list3;
@@ -323,10 +350,10 @@ public class TestQueryCache extends BaseTestCase {
// and now, ensure that we hit the database
LoggedSql.start();
colA_second = DB.find(EColAB.class)
.setUseQueryCache(CacheMode.PUT)
.where()
.eq("columnB", "someId")
.findIds();
.setUseQueryCache(CacheMode.PUT)
.where()
.eq("columnB", "someId")
.findIds();
sql = LoggedSql.stop();
assertThat(sql).hasSize(1);
@@ -335,15 +362,15 @@ public class TestQueryCache extends BaseTestCase {
@Test
public void findCountDifferentQueriesBit() {
DB.getDefault().pluginApi().cacheManager().clearAll();
differentFindCount(q->q.bitwiseAny("id",1), q->q.bitwiseAny("id",0));
differentFindCount(q->q.bitwiseAll("id",1), q->q.bitwiseAll("id",0));
differentFindCount(q -> q.bitwiseAny("id", 1), q -> q.bitwiseAny("id", 0));
differentFindCount(q -> q.bitwiseAll("id", 1), q -> q.bitwiseAll("id", 0));
// differentFindCount(q->q.bitwiseNot("id",1), q->q.bitwiseNot("id",0)); NOT 1 == AND 1 = 0
differentFindCount(q->q.bitwiseAnd("id",1, 0), q->q.bitwiseAnd("id",1, 1));
differentFindCount(q -> q.bitwiseAnd("id", 1, 0), q -> q.bitwiseAnd("id", 1, 1));
differentFindCount(q->q.bitwiseAnd("id",2, 0), q->q.bitwiseAnd("id",4, 0));
differentFindCount(q->q.bitwiseAnd("id",2, 1), q->q.bitwiseAnd("id",4, 1));
differentFindCount(q -> q.bitwiseAnd("id", 2, 0), q -> q.bitwiseAnd("id", 4, 0));
differentFindCount(q -> q.bitwiseAnd("id", 2, 1), q -> q.bitwiseAnd("id", 4, 1));
// Will produce hash collision
differentFindCount(q->q.bitwiseAnd("id",10, 0), q->q.bitwiseAnd("id",0, 928210));
differentFindCount(q -> q.bitwiseAnd("id", 10, 0), q -> q.bitwiseAnd("id", 0, 928210));
}