io.ebeaninternal.server.querydefn.DefaultOrmQuery Maven / Gradle / Ivy
package io.ebeaninternal.server.querydefn;
import io.avaje.lang.NonNullApi;
import io.avaje.lang.Nullable;
import io.ebean.*;
import io.ebean.bean.CallOrigin;
import io.ebean.bean.ObjectGraphNode;
import io.ebean.bean.ObjectGraphOrigin;
import io.ebean.bean.PersistenceContext;
import io.ebean.event.BeanQueryRequest;
import io.ebean.plugin.BeanType;
import io.ebeaninternal.api.*;
import io.ebeaninternal.server.autotune.ProfilingListener;
import io.ebeaninternal.server.core.SpiOrmQueryRequest;
import io.ebeaninternal.server.deploy.*;
import io.ebeaninternal.server.el.ElPropertyDeploy;
import io.ebeaninternal.server.expression.DefaultExpressionList;
import io.ebeaninternal.server.expression.IdInExpression;
import io.ebeaninternal.server.expression.InExpression;
import io.ebeaninternal.server.expression.SimpleExpression;
import io.ebeaninternal.server.query.NativeSqlQueryPlanKey;
import io.ebeaninternal.server.rawsql.SpiRawSql;
import io.ebeaninternal.server.transaction.ExternalJdbcTransaction;
import jakarta.persistence.PersistenceException;
import java.sql.Connection;
import java.sql.Timestamp;
import java.util.*;
import java.util.function.BooleanSupplier;
import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.stream.Stream;
/**
* Default implementation of an Object Relational query.
*/
@NonNullApi
public class DefaultOrmQuery extends AbstractQuery implements SpiQuery {
private static final String DEFAULT_QUERY_NAME = "default";
private static final FetchConfig FETCH_CACHE = FetchConfig.ofCache();
private static final FetchConfig FETCH_QUERY = FetchConfig.ofQuery();
private static final FetchConfig FETCH_LAZY = FetchConfig.ofLazy();
private final Class beanType;
private final ExpressionFactory expressionFactory;
private final BeanDescriptor rootBeanDescriptor;
private BeanDescriptor beanDescriptor;
private SpiEbeanServer server;
private SpiTransaction transaction;
/**
* For lazy loading of ManyToMany we need to add a join to the intersection table. This is that
* join to the intersection table.
*/
private TableJoin m2mIncludeJoin;
private ProfilingListener profilingListener;
private Type type;
private String label;
private String hint;
private Mode mode = Mode.NORMAL;
private boolean usingFuture;
private Object tenantId;
/**
* Holds query in structured form.
*/
private OrmQueryDetail detail;
private int maxRows;
private int firstRow;
private boolean disableLazyLoading;
/**
* Lazy loading batch size (can override server wide default).
*/
private int lazyLoadBatchSize;
private String distinctOn;
private OrderBy orderBy;
private String loadMode;
private String loadDescription;
private String generatedSql;
private String lazyLoadProperty;
private String lazyLoadManyPath;
private boolean allowLoadErrors;
/**
* Flag set for report/DTO beans when we may choose to explicitly include the Id property.
*/
private boolean manualId;
/**
* Set to true by a user wanting a DISTINCT query (id property must be excluded).
*/
private boolean distinct;
private int timeout;
/**
* The property used to get the key value for a Map.
*/
private String mapKey;
/**
* Used for find by id type query.
*/
private Object id;
private Map namedParams;
/**
* Bind parameters when using the query language.
*/
private BindParams bindParams;
private DefaultExpressionList whereExpressions;
private DefaultExpressionList havingExpressions;
private boolean asOfBaseTable;
private int asOfTableCount;
/**
* Set for flashback style 'as of' query.
*/
private Timestamp asOf;
private TemporalMode temporalMode = TemporalMode.CURRENT;
private Timestamp versionsStart;
private Timestamp versionsEnd;
private List softDeletePredicates;
private boolean disableReadAudit;
private int bufferFetchSizeHint;
private boolean usageProfiling = true;
private CacheMode useBeanCache = CacheMode.AUTO;
private CacheMode useQueryCache = CacheMode.OFF;
private Boolean readOnly;
private PersistenceContextScope persistenceContextScope;
/**
* Allow for explicit on off or null for default.
*/
private Boolean autoTune;
private LockWait forUpdate;
private LockType lockType;
private boolean singleAttribute;
private CountDistinctOrder countDistinctOrder;
private boolean autoTuned;
private String rootTableAlias;
private String baseTable;
/**
* The node of the bean or collection that fired lazy loading. Not null if profiling is on and
* this query is for lazy loading. Used to hook back a lazy loading query to the "original" query
* point.
*/
private ObjectGraphNode parentNode;
private BeanPropertyAssocMany> lazyLoadForParentsProperty;
private CQueryPlanKey queryPlanKey;
private PersistenceContext persistenceContext;
private ManyWhereJoins manyWhereJoins;
private SpiRawSql rawSql;
private OrmUpdateProperties updateProperties;
private String nativeSql;
private boolean orderById;
private ProfileLocation profileLocation;
public DefaultOrmQuery(BeanDescriptor desc, SpiEbeanServer server, ExpressionFactory expressionFactory) {
this.beanDescriptor = desc;
this.rootBeanDescriptor = desc;
this.beanType = desc.type();
this.server = server;
this.disableLazyLoading = server.config().isDisableLazyLoading();
this.expressionFactory = expressionFactory;
this.detail = new OrmQueryDetail();
}
public final void setNativeSql(String nativeSql) {
this.nativeSql = nativeSql;
}
@Override
public final DtoQuery asDto(Class dtoClass) {
return server.findDto(dtoClass, this);
}
@Override
public final UpdateQuery asUpdate() {
return new DefaultUpdateQuery<>(this);
}
@Override
public final BeanDescriptor descriptor() {
return beanDescriptor;
}
@Override
public final boolean isFindAll() {
return whereExpressions == null && nativeSql == null && rawSql == null;
}
@Override
public final boolean isFindById() {
if (id == null && whereExpressions != null) {
id = whereExpressions.idEqualTo(beanDescriptor.idName());
if (id != null) {
whereExpressions = null;
}
}
return id != null;
}
@Override
public final String profileEventId() {
switch (mode) {
case LAZYLOAD_BEAN:
return FIND_ONE_LAZY;
case LAZYLOAD_MANY:
return FIND_MANY_LAZY;
default:
return type.profileEventId();
}
}
@Override
public final String profileId() {
return planLabel();
}
@Override
public final Query setProfileLocation(ProfileLocation profileLocation) {
this.profileLocation = profileLocation;
return this;
}
@Override
public final String label() {
return label;
}
@Override
public final String hint() {
return hint;
}
@Override
public final String planLabel() {
if (label != null) {
return label;
}
if (profileLocation != null) {
return profileLocation.label();
}
return null;
}
@Override
public final void setProfilePath(String label, String relativePath, @Nullable ProfileLocation profileLocation) {
this.profileLocation = profileLocation;
this.label = (profileLocation == null ? label : profileLocation.label()) + '_' + relativePath;
}
@Override
public final Query setLabel(String label) {
this.label = label;
return this;
}
@Override
public final Query setHint(String hint) {
this.hint = hint;
return this;
}
@Override
public final boolean isAutoTunable() {
return nativeSql == null && beanDescriptor.isAutoTunable();
}
@Override
public final Query apply(FetchPath fetchPath) {
fetchPath.apply(this);
return this;
}
@Override
public Query also(Consumer> apply) {
apply.accept(this);
return this;
}
@Override
public Query alsoIf(BooleanSupplier predicate, Consumer> consumer) {
if (predicate.getAsBoolean()) {
consumer.accept(this);
}
return this;
}
@Override
public final void addSoftDeletePredicate(String softDeletePredicate) {
if (softDeletePredicates == null) {
softDeletePredicates = new ArrayList<>();
}
softDeletePredicates.add(softDeletePredicate);
}
@Override
public final List softDeletePredicates() {
return softDeletePredicates;
}
@Override
public final boolean isAsOfBaseTable() {
return asOfBaseTable;
}
@Override
public final void setAsOfBaseTable() {
this.asOfBaseTable = true;
}
@Override
public final Query setAllowLoadErrors() {
this.allowLoadErrors = true;
return this;
}
@Override
public final void incrementAsOfTableCount() {
asOfTableCount++;
}
@Override
public final void incrementAsOfTableCount(int increment) {
asOfTableCount += increment;
}
@Override
public final int getAsOfTableCount() {
return asOfTableCount;
}
@Override
public final Timestamp getAsOf() {
return asOf;
}
@Override
public final Query asOf(Timestamp asOfDateTime) {
this.temporalMode = (asOfDateTime != null) ? TemporalMode.AS_OF : TemporalMode.CURRENT;
this.asOf = asOfDateTime;
return this;
}
@Override
public final Query setIncludeSoftDeletes() {
this.temporalMode = TemporalMode.SOFT_DELETED;
return this;
}
@Override
public final SpiRawSql rawSql() {
return rawSql;
}
@Override
public final Query setRawSql(RawSql rawSql) {
this.rawSql = (SpiRawSql) rawSql;
return this;
}
@Override
public final String getOriginKey() {
if (parentNode == null || parentNode.origin() == null) {
return null;
} else {
return parentNode.origin().key();
}
}
@Override
public final int lazyLoadBatchSize() {
return lazyLoadBatchSize;
}
@Override
public final Query setLazyLoadBatchSize(int lazyLoadBatchSize) {
this.lazyLoadBatchSize = lazyLoadBatchSize;
return this;
}
@Override
public final String lazyLoadProperty() {
return lazyLoadProperty;
}
@Override
public final void setLazyLoadProperty(String lazyLoadProperty) {
this.lazyLoadProperty = lazyLoadProperty;
}
@Override
public final ExpressionFactory getExpressionFactory() {
return expressionFactory;
}
private void createExtraJoinsToSupportManyWhereClause() {
final var manyWhere = new ManyWhereJoins();
if (whereExpressions != null) {
whereExpressions.containsMany(beanDescriptor, manyWhere);
}
if (havingExpressions != null) {
havingExpressions.containsMany(beanDescriptor, manyWhere);
}
if (orderBy != null) {
for (OrderBy.Property orderProperty : orderBy.getProperties()) {
ElPropertyDeploy elProp = beanDescriptor.elPropertyDeploy(orderProperty.getProperty());
if (elProp != null && elProp.containsFormulaWithJoin()) {
manyWhere.addFormulaWithJoin(elProp.elPrefix(), elProp.name());
}
}
}
manyWhereJoins = manyWhere;
}
/**
* Return the extra joins required to support the where clause for 'Many' properties.
*/
@Override
public final ManyWhereJoins manyWhereJoins() {
return manyWhereJoins;
}
/**
* Return true if select all properties was used to ensure the property invoking a lazy load was
* included in the query.
*/
@Override
public final void selectAllForLazyLoadProperty() {
if (lazyLoadProperty != null) {
if (!detail.containsProperty(lazyLoadProperty)) {
detail.select("*");
}
}
}
private List removeQueryJoins() {
List queryJoins = detail.removeSecondaryQueries();
if (queryJoins != null) {
if (orderBy != null) {
// remove any orderBy properties that relate to
// paths of the secondary queries
for (OrmQueryProperties joinPath : queryJoins) {
// loop through the orderBy properties and
// move any ones related to the query join
List properties = orderBy.getProperties();
Iterator it = properties.iterator();
while (it.hasNext()) {
OrderBy.Property property = it.next();
if (property.getProperty().startsWith(joinPath.getPath())) {
// remove this orderBy segment and
// add it to the secondary join
it.remove();
joinPath.addSecJoinOrderProperty(property);
}
}
}
}
}
return queryJoins;
}
private List removeLazyJoins() {
return detail.removeSecondaryLazyQueries();
}
@Override
public final void setLazyLoadManyPath(String lazyLoadManyPath) {
this.lazyLoadManyPath = lazyLoadManyPath;
}
@Override
public final SpiQueryManyJoin convertJoins() {
createExtraJoinsToSupportManyWhereClause();
return markQueryJoins();
}
@Override
public SpiQuerySecondary secondaryQuery() {
return new OrmQuerySecondary(removeQueryJoins(), removeLazyJoins());
}
/**
* Limit the number of fetch joins to Many properties, mark as query joins as needed.
*
* @return The query join many property or null.
*/
private SpiQueryManyJoin markQueryJoins() {
// no automatic join to query join conversion when distinctOn is used
return distinctOn != null ? null : detail.markQueryJoins(beanDescriptor, lazyLoadManyPath, isAllowOneManyFetch(), type.defaultSelect());
}
private boolean isAllowOneManyFetch() {
if (Mode.LAZYLOAD_MANY == mode) {
return false;
} else {
return singleAttribute || !hasMaxRowsOrFirstRow() || isRawSql();
}
}
@Override
public final void setDefaultSelectClause() {
if (type.defaultSelect()) {
detail.setDefaultSelectClause(beanDescriptor);
} else if (!detail.hasSelectClause()) {
// explicit empty select when single attribute query on non-root fetch path
detail.setEmptyBase();
}
}
@Override
public final void setTenantId(Object tenantId) {
this.tenantId = tenantId;
}
@Override
public final Object tenantId() {
return tenantId;
}
@Override
public final void setDetail(OrmQueryDetail detail) {
this.detail = detail;
}
@Override
public final boolean tuneFetchProperties(OrmQueryDetail tunedDetail) {
return detail.tuneFetchProperties(tunedDetail);
}
@Override
public final OrmQueryDetail detail() {
return detail;
}
@Override
public final ExpressionList filterMany(String prop) {
OrmQueryProperties chunk = detail.getChunk(prop, true);
return chunk.filterMany(this);
}
@Override
public final void setFilterMany(String prop, ExpressionList> filterMany) {
if (filterMany != null) {
OrmQueryProperties chunk = detail.getChunk(prop, true);
chunk.setFilterMany((SpiExpressionList>) filterMany);
}
}
/**
* Setup to be a delete or update query.
*/
@Override
public final void setupForDeleteOrUpdate() {
forUpdate = null;
rootTableAlias = "${RTA}"; // alias we remove later
setSelectId();
}
@Override
public final CQueryPlanKey setDeleteByIdsPlan() {
// re-build plan for cascading via delete by ids
queryPlanKey = queryPlanKey.withDeleteByIds();
return queryPlanKey;
}
/**
* Set the select clause to select the Id property.
*/
@Override
public final void setSelectId() {
if (rawSql != null) {
String column = rawSql.mapToColumn(beanDescriptor.idSelect());
if (column != null) {
select(column);
}
} else {
// clear select and fetch joins
detail.clear();
select(beanDescriptor.idSelect());
singleAttribute = true;
}
}
@Override
public final void setSingleAttribute() {
this.singleAttribute = true;
}
/**
* Return true if this is a single attribute query.
*/
@Override
public final boolean isSingleAttribute() {
return singleAttribute;
}
@Override
public final CountDistinctOrder countDistinctOrder() {
return countDistinctOrder;
}
@Override
public final boolean isWithId() {
// distinctOn orm query will auto include the id property
// distinctOn dto query does NOT (via setting manualId to true)
return !manualId && !singleAttribute && (!distinct || distinctOn != null);
}
@Override
public final CacheIdLookup cacheIdLookup() {
if (whereExpressions == null) {
return null;
}
List underlyingList = whereExpressions.underlyingList();
if (underlyingList.isEmpty()) {
if (id != null) {
return new CacheIdLookupSingle<>(id);
}
} else if (underlyingList.size() == 1) {
SpiExpression singleExpression = underlyingList.get(0);
if (singleExpression instanceof IdInExpression) {
return new CacheIdLookupMany<>((IdInExpression) singleExpression);
} else if (singleExpression instanceof InExpression) {
InExpression in = (InExpression) singleExpression;
if (in.property().equals(beanDescriptor.idName())) {
return new CacheIdLookupMany<>(in);
}
}
}
return null;
}
@Override
public final NaturalKeyQueryData naturalKey() {
if (whereExpressions == null) {
return null;
}
BeanNaturalKey naturalKey = beanDescriptor.naturalKey();
if (naturalKey == null) {
return null;
}
NaturalKeyQueryData data = new NaturalKeyQueryData<>(naturalKey);
for (SpiExpression expression : whereExpressions.underlyingList()) {
// must be eq or in
if (!expression.naturalKey(data)) {
return null;
}
}
return data;
}
@Override
public final NaturalKeyBindParam naturalKeyBindParam() {
NaturalKeyBindParam namedBind = null;
if (bindParams != null) {
namedBind = bindParams.naturalKeyBindParam();
if (namedBind == null) {
return null;
}
}
if (whereExpressions != null) {
List exprList = whereExpressions.internalList();
if (exprList.size() > 1) {
return null;
} else if (exprList.isEmpty()) {
return namedBind;
} else {
if (namedBind != null) {
return null;
}
SpiExpression se = exprList.get(0);
if (se instanceof SimpleExpression) {
SimpleExpression e = (SimpleExpression) se;
if (e.isOpEquals()) {
return new NaturalKeyBindParam(e.getPropName(), e.getValue());
}
}
}
}
return null;
}
@Override
public SpiQuery copy() {
return copy(server);
}
@Override
public SpiQuery copy(SpiEbeanServer server) {
// forUpdate is NOT copied - see #2762
DefaultOrmQuery copy = new DefaultOrmQuery<>(beanDescriptor, server, expressionFactory);
copy.transaction = transaction;
copy.useMaster = useMaster;
copy.m2mIncludeJoin = m2mIncludeJoin;
copy.profilingListener = profilingListener;
copy.profileLocation = profileLocation;
copy.baseTable = baseTable;
copy.rootTableAlias = rootTableAlias;
copy.distinct = distinct;
copy.distinctOn = distinctOn;
copy.allowLoadErrors = allowLoadErrors;
copy.timeout = timeout;
copy.mapKey = mapKey;
copy.id = id;
copy.hint = hint;
copy.label = label;
copy.nativeSql = nativeSql;
copy.useBeanCache = useBeanCache;
copy.useQueryCache = useQueryCache;
copy.readOnly = readOnly;
if (detail != null) {
copy.detail = detail.copy();
}
copy.temporalMode = temporalMode;
copy.firstRow = firstRow;
copy.maxRows = maxRows;
if (orderBy != null) {
copy.orderBy = orderBy.copy();
}
copy.orderById = orderById;
if (bindParams != null) {
copy.bindParams = bindParams.copy();
}
if (whereExpressions != null) {
copy.whereExpressions = whereExpressions.copy(copy);
}
if (havingExpressions != null) {
copy.havingExpressions = havingExpressions.copy(copy);
}
copy.persistenceContextScope = persistenceContextScope;
copy.usageProfiling = usageProfiling;
copy.autoTune = autoTune;
copy.parentNode = parentNode;
copy.rawSql = rawSql;
setCancelableQuery(copy); // required to cancel findId query
return copy;
}
@Override
public final Query setPersistenceContextScope(PersistenceContextScope scope) {
this.persistenceContextScope = scope;
return this;
}
@Override
public final PersistenceContextScope persistenceContextScope() {
return persistenceContextScope;
}
@Override
public final Type type() {
return type;
}
@Override
public final void setType(Type type) {
this.type = type;
}
@Override
public String distinctOn() {
return distinctOn;
}
@Override
public final String loadDescription() {
return loadDescription;
}
@Override
public final String loadMode() {
return loadMode;
}
@Override
public final void setLoadDescription(String loadMode, String loadDescription) {
this.loadMode = loadMode;
this.loadDescription = loadDescription;
}
/**
* Return the TransactionContext.
*
* If no TransactionContext is present on the query then the TransactionContext from the
* Transaction is used (transaction scoped persistence context).
*
*/
@Override
public final PersistenceContext persistenceContext() {
return persistenceContext;
}
/**
* Set an explicit TransactionContext (typically for a refresh query).
*
* If no TransactionContext is present on the query then the TransactionContext from the
* Transaction is used (transaction scoped persistence context).
*
*/
@Override
public final void setPersistenceContext(PersistenceContext persistenceContext) {
this.persistenceContext = persistenceContext;
}
@Override
public final void setLazyLoadForParents(BeanPropertyAssocMany> many) {
this.lazyLoadForParentsProperty = many;
}
@Override
public final BeanPropertyAssocMany> lazyLoadMany() {
return lazyLoadForParentsProperty;
}
/**
* Return true if the query detail has neither select or joins specified.
*/
@Override
public final boolean isDetailEmpty() {
return detail.isEmpty();
}
@Override
public final boolean isAutoTuned() {
return autoTuned;
}
@Override
public final void setAutoTuned(boolean autoTuned) {
this.autoTuned = autoTuned;
}
@Override
public final Boolean isAutoTune() {
return autoTune;
}
@Override
public final Query setAutoTune(boolean autoTune) {
this.autoTune = autoTune;
return this;
}
@Override
public final Query withLock(LockType lockType) {
return setForUpdateWithMode(LockWait.WAIT, lockType);
}
@Override
public final Query withLock(LockType lockType, LockWait lockWait) {
return setForUpdateWithMode(lockWait, lockType);
}
@Override
public final Query forUpdate() {
return setForUpdateWithMode(LockWait.WAIT, LockType.DEFAULT);
}
@Override
public final Query forUpdateNoWait() {
return setForUpdateWithMode(LockWait.NOWAIT, LockType.DEFAULT);
}
@Override
public final Query forUpdateSkipLocked() {
return setForUpdateWithMode(LockWait.SKIPLOCKED, LockType.DEFAULT);
}
private Query setForUpdateWithMode(LockWait mode, LockType lockType) {
this.forUpdate = mode;
this.lockType = lockType;
this.useBeanCache = CacheMode.OFF;
return this;
}
@Override
public final boolean isForUpdate() {
return forUpdate != null;
}
@Override
public final LockWait getForUpdateLockWait() {
return forUpdate;
}
@Override
public final LockType getForUpdateLockType() {
return lockType;
}
@Override
public final ProfilingListener profilingListener() {
return profilingListener;
}
@Override
public final void setProfilingListener(ProfilingListener profilingListener) {
this.profilingListener = profilingListener;
}
@Override
public final QueryType getQueryType() {
if (type != null) {
switch (type) {
case DELETE:
return QueryType.DELETE;
case UPDATE:
return QueryType.UPDATE;
}
}
return QueryType.FIND;
}
@Override
public final Mode mode() {
return mode;
}
@Override
public final TemporalMode temporalMode() {
return temporalMode;
}
@Override
public final boolean isAsOfQuery() {
return asOf != null;
}
@Override
public final boolean isIncludeSoftDeletes() {
return TemporalMode.SOFT_DELETED == temporalMode;
}
@Override
public final void setMode(Mode mode) {
this.mode = mode;
}
@Override
public final void usingFuture() {
this.usingFuture = true;
}
@Override
public final boolean isUsingFuture() {
return usingFuture;
}
@Override
public final boolean isUsageProfiling() {
return usageProfiling;
}
@Override
public final void setUsageProfiling(boolean usageProfiling) {
this.usageProfiling = usageProfiling;
}
@Override
public final void setParentNode(ObjectGraphNode parentNode) {
this.parentNode = parentNode;
}
@Override
public final ObjectGraphNode parentNode() {
return parentNode;
}
@Override
public final ObjectGraphNode setOrigin(CallOrigin callOrigin) {
// create a 'origin' which links this query to the profiling information
ObjectGraphOrigin o = new ObjectGraphOrigin(calculateOriginQueryHash(), callOrigin, beanType.getName());
parentNode = new ObjectGraphNode(o, null);
return parentNode;
}
/**
* Calculate a hash for use in determining the ObjectGraphOrigin.
*
* This should be quite a stable hash as most uniqueness is determined by the CallStack, so we
* only use the bean type and overall query type.
*
*
* This stable hash allows the query to be changed (joins added etc) without losing the already
* collected usage profiling.
*
*/
private int calculateOriginQueryHash() {
int hc = beanType.getName().hashCode();
hc = hc * 92821 + (type == null ? 0 : type.ordinal());
return hc;
}
/**
* Calculate the query hash for either AutoTune query tuning or Query Plan caching.
*/
final CQueryPlanKey createQueryPlanKey() {
if (isNativeSql()) {
String bindHash = (bindParams == null) ? "" : bindParams.calcQueryPlanHash();
queryPlanKey = new NativeSqlQueryPlanKey(type.ordinal() + nativeSql + "-" + firstRow + "-" + maxRows + "-" + bindHash);
} else {
queryPlanKey = new OrmQueryPlanKey(planDescription(), maxRows, firstRow, rawSql);
}
return queryPlanKey;
}
private String planDescription() {
StringBuilder sb = new StringBuilder(300);
if (type != null) {
sb.append(type.ordinal());
}
if (temporalMode != SpiQuery.TemporalMode.CURRENT) {
sb.append("/tm").append(temporalMode.ordinal());
if (versionsStart != null) {
sb.append('v');
}
}
if (forUpdate != null) {
sb.append("/fu").append(forUpdate.ordinal());
if (lockType != null) {
sb.append('t').append(lockType.ordinal());
}
}
if (id != null) {
sb.append("/id");
}
if (manualId) {
sb.append("/md");
}
if (hint != null) {
sb.append("/h:").append(hint);
}
if (distinct) {
sb.append("/dt");
if (distinctOn != null) {
sb.append("/o:").append(distinctOn);
}
}
if (allowLoadErrors) {
sb.append("/ae");
}
if (disableLazyLoading) {
sb.append("/dl");
}
if (baseTable != null) {
sb.append("/bt").append(baseTable);
}
if (rootTableAlias != null) {
sb.append("/ra").append(rootTableAlias);
}
if (orderBy != null) {
sb.append("/ob").append(orderBy.toStringFormat());
}
if (m2mIncludeJoin != null) {
sb.append("/m2").append(m2mIncludeJoin.getTable());
}
if (mapKey != null) {
sb.append("/mk").append(mapKey);
}
if (countDistinctOrder != null) {
sb.append("/cd").append(countDistinctOrder.name());
}
if (detail != null) {
sb.append("/d[");
detail.queryPlanHash(sb);
sb.append(']');
}
if (bindParams != null) {
sb.append("/b[");
bindParams.buildQueryPlanHash(sb);
sb.append(']');
}
if (whereExpressions != null) {
sb.append("/w[");
whereExpressions.queryPlanHash(sb);
sb.append(']');
}
if (havingExpressions != null) {
sb.append("/h[");
havingExpressions.queryPlanHash(sb);
sb.append(']');
}
if (updateProperties != null) {
sb.append("/u[");
updateProperties.buildQueryPlanHash(sb);
sb.append(']');
}
return sb.toString();
}
@Override
public final boolean isNativeSql() {
return nativeSql != null;
}
@Override
public final String nativeSql() {
return nativeSql;
}
@Override
public final Object queryPlanKey() {
return queryPlanKey;
}
/**
* Prepare the query which prepares any expressions (sub-query expressions etc) and calculates the query plan key.
*/
@Override
public final CQueryPlanKey prepare(SpiOrmQueryRequest request) {
prepareExpressions(request);
prepareForPaging();
queryPlanKey = createQueryPlanKey();
return queryPlanKey;
}
/**
* Prepare the expressions (compile sub-queries etc).
*/
private void prepareExpressions(BeanQueryRequest> request) {
detail.prepareExpressions(request);
if (whereExpressions != null) {
whereExpressions.prepareExpression(request);
}
if (havingExpressions != null) {
havingExpressions.prepareExpression(request);
}
}
/**
* deemed to be a be a paging query - check that the order by contains the id
* property to ensure unique row ordering for predicable paging but only in
* case, this is not a distinct query
*/
private void prepareForPaging() {
// add the rawSql statement - if any
if (orderByIsEmpty()) {
if (rawSql != null && rawSql.getSql() != null) {
orderBy(rawSql.getSql().getOrderBy());
}
}
if (checkPagingOrderBy()) {
beanDescriptor.appendOrderById(this);
}
}
@Override
public final void queryBindKey(BindValuesKey key) {
key.add(id);
if (whereExpressions != null) whereExpressions.queryBindKey(key);
if (havingExpressions != null) havingExpressions.queryBindKey(key);
if (bindParams != null) bindParams.queryBindHash(key);
key.add(asOf).add(versionsStart).add(versionsEnd);
}
/**
* Return a hash that includes the query plan and bind values.
*
* This hash can be used to identify if we have executed the exact same query (including bind
* values) before.
*
*/
@Override
public final HashQuery queryHash() {
// calculateQueryPlanHash is called just after potential AutoTune tuning
// so queryPlanHash is calculated well before this method is called
BindValuesKey bindKey = new BindValuesKey();
queryBindKey(bindKey);
return new HashQuery(queryPlanKey, bindKey);
}
@Override
public final boolean isRawSql() {
return rawSql != null;
}
/**
* Return the timeout.
*/
@Override
public final int timeout() {
return timeout;
}
@Override
public final boolean hasMaxRowsOrFirstRow() {
return maxRows > 0 || firstRow > 0;
}
@Override
public final boolean isVersionsBetween() {
return versionsStart != null;
}
@Override
public final Timestamp versionStart() {
return versionsStart;
}
@Override
public final Timestamp versionEnd() {
return versionsEnd;
}
@Override
public final Boolean isReadOnly() {
return readOnly;
}
@Override
public final Query setReadOnly(boolean readOnly) {
this.readOnly = readOnly;
return this;
}
@Override
public final boolean isBeanCachePut() {
return useBeanCache.isPut() && beanDescriptor.isBeanCaching();
}
@Override
public final boolean isBeanCacheGet() {
return useBeanCache.isGet() && beanDescriptor.isBeanCaching();
}
@Override
public final boolean isForceHitDatabase() {
return forUpdate != null || CacheMode.PUT == useBeanCache;
}
@Override
public final void resetBeanCacheAutoMode(boolean findOne) {
if (useBeanCache == CacheMode.AUTO && useQueryCache != CacheMode.OFF) {
useBeanCache = CacheMode.OFF;
}
}
@Override
public final CacheMode beanCacheMode() {
return useBeanCache;
}
@Override
public final CacheMode queryCacheMode() {
return useQueryCache;
}
@Override
public final Query setBeanCacheMode(CacheMode beanCacheMode) {
this.useBeanCache = beanCacheMode;
return this;
}
@Override
public final Query setUseQueryCache(CacheMode useQueryCache) {
this.useQueryCache = useQueryCache;
return this;
}
@Override
public final Query setTimeout(int secs) {
this.timeout = secs;
return this;
}
@Override
public final void selectProperties(Set props) {
detail.selectProperties(props);
}
@Override
public final void fetchProperties(String property, Set columns, FetchConfig config) {
detail.fetchProperties(property, columns, config);
}
@Override
public final void selectProperties(OrmQueryProperties properties) {
detail.selectProperties(properties);
}
@Override
public final void fetchProperties(String path, OrmQueryProperties other) {
detail.fetchProperties(path, other);
}
@Override
public final void addNested(String name, OrmQueryDetail nestedDetail, FetchConfig config) {
detail.addNested(name, nestedDetail, config);
}
@Override
public final Query distinctOn(String distinctOn) {
this.distinctOn = distinctOn;
this.distinct = true;
return this;
}
@Override
public final Query select(String columns) {
detail.select(columns);
return this;
}
@Override
public final Query select(FetchGroup fetchGroup) {
if (fetchGroup != null) {
this.detail = ((SpiFetchGroup) fetchGroup).detail();
}
return this;
}
@Override
public final Query fetch(String path) {
return fetch(path, null, null);
}
@Override
public final Query fetch(String path, FetchConfig joinConfig) {
return fetch(path, null, joinConfig);
}
@Override
public final Query fetch(String path, String properties) {
return fetch(path, properties, null);
}
@Override
public final Query fetch(String path, String properties, FetchConfig config) {
if (nativeSql != null && (config == null || config.isJoin())) {
// can't use fetch join with nativeSql (as the root query)
config = FETCH_QUERY;
}
return fetchInternal(path, properties, config);
}
@Override
public final Query fetchQuery(String path) {
return fetchInternal(path, null, FETCH_QUERY);
}
@Override
public final Query fetchCache(String path) {
return fetchInternal(path, null, FETCH_CACHE);
}
@Override
public final Query fetchLazy(String path) {
return fetchInternal(path, null, FETCH_LAZY);
}
@Override
public final Query fetchQuery(String path, String properties) {
return fetchInternal(path, properties, FETCH_QUERY);
}
@Override
public final Query fetchCache(String path, String properties) {
return fetchInternal(path, properties, FETCH_CACHE);
}
@Override
public final Query fetchLazy(String path, String properties) {
return fetchInternal(path, properties, FETCH_LAZY);
}
private Query fetchInternal(String path, String properties, FetchConfig config) {
detail.fetch(path, properties, config);
return this;
}
@Override
public SpiTransaction transaction() {
return transaction;
}
@Override
public final Query usingTransaction(Transaction transaction) {
this.transaction = (SpiTransaction) transaction;
return this;
}
@Override
public final Query usingConnection(Connection connection) {
this.transaction = new ExternalJdbcTransaction(connection);
return this;
}
@Override
public final Query usingDatabase(Database database) {
this.server = (SpiEbeanServer) database;
return this;
}
@Override
public Query usingMaster() {
this.useMaster = true;
return this;
}
@Override
public boolean isUseMaster() {
return useMaster;
}
@Override
public final int delete() {
return server.delete(this);
}
@Override
public final int delete(Transaction transaction) {
return server.delete(this);
}
@Override
public final int update() {
return server.update(this);
}
@Override
public final int update(Transaction transaction) {
return server.update(this);
}
@Override
public final List findIds() {
// a copy of this query is made in the server
// as the query needs to modified (so we modify
// the copy rather than this query instance)
return server.findIds(this);
}
@Override
public final boolean exists() {
return server.exists(this);
}
@Override
public final int findCount() {
// a copy of this query is made in the server
// as the query needs to modified (so we modify
// the copy rather than this query instance)
return server.findCount(this);
}
@Override
public final void findEachWhile(Predicate consumer) {
server.findEachWhile(this, consumer);
}
@Override
public final void findEach(Consumer consumer) {
server.findEach(this, consumer);
}
@Override
public final void findEach(int batch, Consumer> consumer) {
server.findEach(this, batch, consumer);
}
@Override
public final QueryIterator findIterate() {
return server.findIterate(this);
}
@Override
public final Stream findStream() {
return server.findStream(this);
}
@Override
public final List> findVersions() {
this.temporalMode = TemporalMode.VERSIONS;
return server.findVersions(this);
}
@Override
public final List> findVersionsBetween(Timestamp start, Timestamp end) {
if (start == null || end == null) {
throw new IllegalArgumentException("start and end must not be null");
}
this.temporalMode = TemporalMode.VERSIONS;
this.versionsStart = start;
this.versionsEnd = end;
return server.findVersions(this);
}
@Override
public final List findList() {
return server.findList(this);
}
@Override
public final Set findSet() {
return server.findSet(this);
}
@Override
public final Map findMap() {
return server.findMap(this);
}
@Override
public final List findSingleAttributeList() {
return server.findSingleAttributeList(this);
}
@Override
public final Set findSingleAttributeSet() {
return server.findSingleAttributeSet(this);
}
@Override
public final A findSingleAttribute() {
List list = findSingleAttributeList();
return !list.isEmpty() ? list.get(0) : null;
}
@Override
public final Optional findSingleAttributeOrEmpty() {
return Optional.ofNullable(findSingleAttribute());
}
@Override
public final T findOne() {
return server.findOne(this);
}
@Override
public final Optional findOneOrEmpty() {
return server.findOneOrEmpty(this);
}
@Override
public final FutureIds findFutureIds() {
return server.findFutureIds(this);
}
@Override
public final FutureList findFutureList() {
return server.findFutureList(this);
}
@Override
public final FutureRowCount findFutureCount() {
return server.findFutureCount(this);
}
@Override
public final PagedList findPagedList() {
return server.findPagedList(this);
}
@Override
public final Query setParameter(Object value) {
initBindParams().setNextParameter(value);
return this;
}
@Override
public final Query setParameters(Object... values) {
initBindParams().setNextParameters(values);
return this;
}
/**
* Set an ordered bind parameter according to its position. Note that the position starts at 1 to
* be consistent with JDBC PreparedStatement. You need to set a parameter value for each ? you
* have in the query.
*/
@Override
public final Query setParameter(int position, Object value) {
initBindParams().setParameter(position, value);
return this;
}
/**
* Set a named bind parameter. Named parameters have a colon to prefix the name.
*/
@Override
public final Query setParameter(String name, Object value) {
if (namedParams != null) {
ONamedParam param = namedParams.get(name);
if (param != null) {
param.setValue(value);
return this;
}
}
initBindParams().setParameter(name, value);
return this;
}
@Override
public final void setArrayParameter(String name, Collection> values) {
if (namedParams != null) {
throw new IllegalStateException("setArrayParameter() not supported when EQL parsed query");
}
initBindParams().setArrayParameter(name, values);
}
@Override
public final boolean checkPagingOrderBy() {
return orderById;
}
@Override
public final boolean orderByIsEmpty() {
return orderBy == null || orderBy.isEmpty();
}
@Override
public final OrderBy getOrderBy() {
return orderBy;
}
@Override
public final OrderBy orderBy() {
if (orderBy == null) {
orderBy = new OrderBy<>(this, null);
}
return orderBy;
}
@Override
public final Query orderBy(String orderByClause) {
if (orderByClause == null || orderByClause.trim().isEmpty()) {
this.orderBy = null;
} else {
this.orderBy = new OrderBy<>(this, orderByClause);
}
return this;
}
@Override
public final Query setOrderBy(OrderBy orderBy) {
this.orderBy = orderBy;
if (orderBy != null) {
orderBy.setQuery(this);
}
return this;
}
@Override
public final boolean isManualId() {
return manualId;
}
@Override
public final void setManualId() {
if (detail != null && detail.hasSelectClause()) {
this.manualId = true;
}
}
/**
* return true if user specified to use SQL DISTINCT (effectively excludes id property).
*/
@Override
public final boolean isDistinct() {
return distinct;
}
/**
* Internally set to use SQL DISTINCT on the query but still have id property included.
*/
@Override
public final Query setDistinct(boolean distinct) {
this.distinct = distinct;
return this;
}
@Override
public final Query setCountDistinct(CountDistinctOrder countDistinctOrder) {
this.countDistinctOrder = countDistinctOrder;
return this;
}
@Override
public final boolean isCountDistinct() {
return countDistinctOrder != null;
}
@Override
public final Class getBeanType() {
return beanType;
}
@Override
public final String toString() {
return "Query " + whereExpressions;
}
@Override
public final TableJoin m2mIncludeJoin() {
return m2mIncludeJoin;
}
@Override
public final void setM2MIncludeJoin(TableJoin m2mIncludeJoin) {
this.m2mIncludeJoin = m2mIncludeJoin;
}
@Override
public final Query setDisableLazyLoading(boolean disableLazyLoading) {
this.disableLazyLoading = disableLazyLoading;
return this;
}
@Override
public final boolean isDisableLazyLoading() {
return disableLazyLoading;
}
@Override
public final int getFirstRow() {
return firstRow;
}
@Override
public final Query setFirstRow(int firstRow) {
this.firstRow = firstRow;
return this;
}
@Override
public final int getMaxRows() {
return maxRows;
}
@Override
public final Query setMaxRows(int maxRows) {
this.maxRows = maxRows;
return this;
}
@Override
@SuppressWarnings("unchecked")
public Query setPaging(@Nullable Paging paging) {
if (paging != null && paging.pageSize() > 0) {
firstRow = paging.pageIndex() * paging.pageSize();
maxRows = paging.pageSize();
orderBy = (OrderBy) paging.orderBy();
if (orderBy == null || orderBy.isEmpty()) {
// should not be paging without any order by clause so set
// orderById such that the Id property is used
orderById = true;
}
}
return this;
}
@Override
public final String mapKey() {
return mapKey;
}
@Override
public final Query setMapKey(String mapKey) {
this.mapKey = mapKey;
return this;
}
@Override
public final Object getId() {
return id;
}
@Override
public final Query setId(Object id) {
if (id == null) {
throw new NullPointerException("The id is null");
}
this.id = id;
return this;
}
@Override
public final BindParams bindParams() {
return bindParams;
}
@Override
public BindParams initBindParams() {
if (bindParams == null) {
bindParams = new BindParams();
}
return bindParams;
}
@Override
public final Query where(Expression expression) {
where().add(expression);
return this;
}
@Override
public final ExpressionList where() {
if (whereExpressions == null) {
whereExpressions = new DefaultExpressionList<>(this, null);
}
return whereExpressions;
}
@Override
public final void simplifyExpressions() {
if (whereExpressions != null) {
whereExpressions.simplify();
}
}
@Override
public final Query having(Expression expression) {
having().add(expression);
return this;
}
@Override
public final ExpressionList having() {
if (havingExpressions == null) {
havingExpressions = new DefaultExpressionList<>(this, null);
}
return havingExpressions;
}
@Override
public final SpiExpressionList havingExpressions() {
return havingExpressions;
}
@Override
public final SpiExpressionList whereExpressions() {
return whereExpressions;
}
@Override
public final String getGeneratedSql() {
return generatedSql;
}
@Override
public final void setGeneratedSql(String generatedSql) {
this.generatedSql = generatedSql;
}
@Override
public final void checkNamedParameters() {
if (namedParams != null) {
for (ONamedParam value : namedParams.values()) {
value.checkValueSet();
}
}
}
@Override
public final SpiNamedParam createNamedParameter(String name) {
if (namedParams == null) {
namedParams = new HashMap<>();
}
return namedParams.computeIfAbsent(name, ONamedParam::new);
}
@Override
public final void setDefaultFetchBuffer(int fetchSize) {
if (bufferFetchSizeHint == 0) {
bufferFetchSizeHint = fetchSize;
}
}
@Override
public final Query setBufferFetchSizeHint(int bufferFetchSizeHint) {
this.bufferFetchSizeHint = bufferFetchSizeHint;
return this;
}
@Override
public final int bufferFetchSizeHint() {
return bufferFetchSizeHint;
}
@Override
public final Query setBaseTable(String baseTable) {
this.baseTable = baseTable;
return this;
}
@Override
public final String baseTable() {
return baseTable;
}
@Override
public final Query alias(String alias) {
this.rootTableAlias = alias;
return this;
}
@Override
public final String alias() {
return rootTableAlias;
}
@Override
public final String getAlias(String defaultAlias) {
return rootTableAlias != null ? rootTableAlias : defaultAlias;
}
@Override
public final Set validate() {
return server.validateQuery(this);
}
/**
* Validate all the expression properties/paths given the bean descriptor.
*/
@Override
public final Set validate(BeanType desc) {
SpiExpressionValidation validation = new SpiExpressionValidation(desc);
if (whereExpressions != null) {
whereExpressions.validate(validation);
}
if (havingExpressions != null) {
havingExpressions.validate(validation);
}
if (orderBy != null) {
for (OrderBy.Property property : orderBy.getProperties()) {
validation.validate(property.getProperty());
}
}
return validation.unknownProperties();
}
final void setUpdateProperties(OrmUpdateProperties updateProperties) {
this.updateProperties = updateProperties;
}
@Override
public final OrmUpdateProperties updateProperties() {
return updateProperties;
}
@Override
public final ProfileLocation profileLocation() {
return profileLocation;
}
@Override
public final void handleLoadError(String fullName, Exception e) {
if (!allowLoadErrors) {
throw new PersistenceException("Error loading on " + fullName, e);
}
}
@Override
public final Query orderById(boolean orderById) {
this.orderById = orderById;
return this;
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy