package io.ebeaninternal.server.querydefn;
import io.ebean.CacheMode;
import io.ebean.CountDistinctOrder;
import io.ebean.DtoQuery;
import io.ebean.Expression;
import io.ebean.ExpressionFactory;
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.OrderBy.Property;
import io.ebean.PagedList;
import io.ebean.PersistenceContextScope;
import io.ebean.ProfileLocation;
import io.ebean.Query;
import io.ebean.QueryIterator;
import io.ebean.QueryType;
import io.ebean.RawSql;
import io.ebean.Transaction;
import io.ebean.UpdateQuery;
import io.ebean.Version;
import io.ebean.bean.CallStack;
import io.ebean.bean.ObjectGraphNode;
import io.ebean.bean.ObjectGraphOrigin;
import io.ebean.bean.PersistenceContext;
import io.ebean.event.BeanQueryRequest;
import io.ebean.event.readaudit.ReadEvent;
import io.ebean.plugin.BeanType;
import io.ebeaninternal.api.BindParams;
import io.ebeaninternal.api.CQueryPlanKey;
import io.ebeaninternal.api.HashQuery;
import io.ebeaninternal.api.ManyWhereJoins;
import io.ebeaninternal.api.NaturalKeyQueryData;
import io.ebeaninternal.api.SpiEbeanServer;
import io.ebeaninternal.api.SpiExpression;
import io.ebeaninternal.api.SpiExpressionList;
import io.ebeaninternal.api.SpiExpressionValidation;
import io.ebeaninternal.api.SpiNamedParam;
import io.ebeaninternal.api.SpiQuery;
import io.ebeaninternal.api.SpiQuerySecondary;
import io.ebeaninternal.server.autotune.ProfilingListener;
import io.ebeaninternal.server.core.SpiOrmQueryRequest;
import io.ebeaninternal.server.deploy.BeanDescriptor;
import io.ebeaninternal.server.deploy.BeanPropertyAssocMany;
import io.ebeaninternal.server.deploy.InheritInfo;
import io.ebeaninternal.server.deploy.TableJoin;
import io.ebeaninternal.server.expression.DefaultExpressionList;
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 javax.persistence.PersistenceException;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.function.Consumer;
import java.util.function.Predicate;
Default implementation of an Object Relational query.
/**
* Default implementation of an Object Relational query.
*/
public class DefaultOrmQuery<T> implements SpiQuery<T> {
private static final String DEFAULT_QUERY_NAME = "default";
private static final FetchConfig FETCH_QUERY = new FetchConfig().query();
private static final FetchConfig FETCH_LAZY = new FetchConfig().lazy();
private final Class<T> beanType;
private final BeanDescriptor<T> rootBeanDescriptor;
private BeanDescriptor<T> beanDescriptor;
private final SpiEbeanServer server;
private final ExpressionFactory expressionFactory;
For lazy loading of ManyToMany we need to add a join to the intersection table. This is that
join to the intersection table.
/**
* 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 boolean cancelled;
private CancelableQuery cancelableQuery;
private Type type;
private String label;
private Mode mode = Mode.NORMAL;
private Object tenantId;
Holds query in structured form.
/**
* Holds query in structured form.
*/
private OrmQueryDetail detail;
private int maxRows;
private int firstRow;
Set to true to disable lazy loading on the object graph returned.
/**
* Set to true to disable lazy loading on the object graph returned.
*/
private boolean disableLazyLoading;
Lazy loading batch size (can override server wide default).
/**
* Lazy loading batch size (can override server wide default).
*/
private int lazyLoadBatchSize;
private OrderBy<T> 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.
/**
* 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).
/**
* Set to true by a user wanting a DISTINCT query (id property must be excluded).
*/
private boolean distinct;
Set to true if this is a future fetch using background threads.
/**
* Set to true if this is a future fetch using background threads.
*/
private boolean futureFetch;
Only used for read auditing with findFutureList() query.
/**
* Only used for read auditing with findFutureList() query.
*/
private ReadEvent futureFetchAudit;
private int timeout;
The property used to get the key value for a Map.
/**
* The property used to get the key value for a Map.
*/
private String mapKey;
Used for find by id type query.
/**
* Used for find by id type query.
*/
private Object id;
private Map<String, ONamedParam> namedParams;
Bind parameters when using the query language.
/**
* Bind parameters when using the query language.
*/
private BindParams bindParams;
private DefaultExpressionList<T> textExpressions;
private DefaultExpressionList<T> whereExpressions;
private DefaultExpressionList<T> havingExpressions;
private boolean asOfBaseTable;
private int asOfTableCount;
Set for flashback style 'as of' query.
/**
* Set for flashback style 'as of' query.
*/
private Timestamp asOf;
private TemporalMode temporalMode = TemporalMode.CURRENT;
private Timestamp versionsStart;
private Timestamp versionsEnd;
private List<String> 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.
/**
* Allow for explicit on off or null for default.
*/
private Boolean autoTune;
For update mode.
/**
* For update mode.
*/
private ForUpdate forUpdate;
private boolean singleAttribute;
private CountDistinctOrder countDistinctOrder;
Set to true if this query has been tuned by autoTune.
/**
* Set to true if this query has been tuned by autoTune.
*/
private boolean autoTuned;
Root table alias. For Query.alias(String)
command. /**
* Root table alias. For {@link Query#alias(String)} command.
*/
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.
/**
* 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;
Hash of final query after AutoTune tuning.
/**
* Hash of final query after AutoTune tuning.
*/
private CQueryPlanKey queryPlanKey;
private PersistenceContext persistenceContext;
private ManyWhereJoins manyWhereJoins;
private SpiRawSql rawSql;
private boolean useDocStore;
private String docIndexName;
private OrmUpdateProperties updateProperties;
private String nativeSql;
private boolean orderById;
Identity the query for profiling purposes (expected to be unique for a bean type).
/**
* Identity the query for profiling purposes (expected to be unique for a bean type).
*/
private short profileId;
private ProfileLocation profileLocation;
public DefaultOrmQuery(BeanDescriptor<T> desc, SpiEbeanServer server, ExpressionFactory expressionFactory) {
this.beanDescriptor = desc;
this.rootBeanDescriptor = desc;
this.beanType = desc.getBeanType();
this.server = server;
this.orderById = server.getServerConfig().isDefaultOrderById();
this.disableLazyLoading = server.getServerConfig().isDisableLazyLoading();
this.expressionFactory = expressionFactory;
this.detail = new OrmQueryDetail();
}
public void setNativeSql(String nativeSql) {
this.nativeSql = nativeSql;
}
@Override
public <D> DtoQuery<D> asDto(Class<D> dtoClass) {
return server.findDto(dtoClass, this);
}
@Override
public UpdateQuery<T> asUpdate() {
return new DefaultUpdateQuery<>(this);
}
@Override
public BeanDescriptor<T> getBeanDescriptor() {
return beanDescriptor;
}
@Override
public boolean isFindAll() {
return whereExpressions == null && nativeSql == null && rawSql == null;
}
@Override
public boolean isFindById() {
if (id == null && whereExpressions != null) {
id = whereExpressions.idEqualTo(beanDescriptor.getIdName());
if (id != null) {
whereExpressions = null;
}
}
return id != null;
}
@Override
public String profileEventId() {
switch (mode) {
case LAZYLOAD_BEAN:
return FIND_ONE_LAZY;
case LAZYLOAD_MANY:
return FIND_MANY_LAZY;
default:
return type.profileEventId();
}
}
@Override
public short getProfileId() {
return profileId;
}
@Override
public Query<T> setProfileId(int profileId) {
this.profileId = (short) profileId;
return this;
}
@Override
public Query<T> setProfileLocation(ProfileLocation profileLocation) {
this.profileLocation = profileLocation;
return this;
}
@Override
public String getLabel() {
return label;
}
@Override
public String getPlanLabel() {
if (label != null) {
return label;
}
if (profileLocation != null) {
return profileLocation.label();
}
return null;
}
@Override
public Query<T> setLabel(String label) {
this.label = label;
return this;
}
@Override
public boolean isAutoTunable() {
return nativeSql == null && beanDescriptor.isAutoTunable();
}
@Override
public DefaultOrmQuery<T> setUseDocStore(boolean useDocStore) {
this.useDocStore = useDocStore;
return this;
}
@Override
public boolean isUseDocStore() {
return useDocStore;
}
@Override
public Query<T> apply(FetchPath fetchPath) {
fetchPath.apply(this);
return this;
}
@Override
public void addSoftDeletePredicate(String softDeletePredicate) {
if (softDeletePredicates == null) {
softDeletePredicates = new ArrayList<>();
}
softDeletePredicates.add(softDeletePredicate);
}
@Override
public List<String> getSoftDeletePredicates() {
return softDeletePredicates;
}
@Override
public boolean isAsOfBaseTable() {
return asOfBaseTable;
}
@Override
public void setAsOfBaseTable() {
this.asOfBaseTable = true;
}
@Override
public DefaultOrmQuery<T> setAllowLoadErrors() {
this.allowLoadErrors = true;
return this;
}
@Override
public void incrementAsOfTableCount() {
asOfTableCount++;
}
@Override
public int getAsOfTableCount() {
return asOfTableCount;
}
@Override
public Timestamp getAsOf() {
return asOf;
}
@Override
public DefaultOrmQuery<T> asOf(Timestamp asOfDateTime) {
this.temporalMode = (asOfDateTime != null) ? TemporalMode.AS_OF : TemporalMode.CURRENT;
this.asOf = asOfDateTime;
return this;
}
@Override
public DefaultOrmQuery<T> asDraft() {
this.temporalMode = TemporalMode.DRAFT;
this.useBeanCache = CacheMode.OFF;
return this;
}
@Override
public DefaultOrmQuery<T> setIncludeSoftDeletes() {
this.temporalMode = TemporalMode.SOFT_DELETED;
return this;
}
@Override
public Query<T> setDocIndexName(String indexName) {
this.docIndexName = indexName;
this.useDocStore = true;
return this;
}
@Override
public String getDocIndexName() {
return docIndexName;
}
@Override
public SpiRawSql getRawSql() {
return rawSql;
}
@Override
public DefaultOrmQuery<T> setRawSql(RawSql rawSql) {
this.rawSql = (SpiRawSql) rawSql;
return this;
}
@Override
public String getOriginKey() {
if (parentNode == null || parentNode.getOriginQueryPoint() == null) {
return null;
} else {
return parentNode.getOriginQueryPoint().getKey();
}
}
@Override
public int getLazyLoadBatchSize() {
return lazyLoadBatchSize;
}
@Override
public Query<T> setLazyLoadBatchSize(int lazyLoadBatchSize) {
this.lazyLoadBatchSize = lazyLoadBatchSize;
return this;
}
@Override
public String getLazyLoadProperty() {
return lazyLoadProperty;
}
@Override
public void setLazyLoadProperty(String lazyLoadProperty) {
this.lazyLoadProperty = lazyLoadProperty;
}
@Override
public ExpressionFactory getExpressionFactory() {
return expressionFactory;
}
private void createExtraJoinsToSupportManyWhereClause() {
manyWhereJoins = new ManyWhereJoins();
if (whereExpressions != null) {
whereExpressions.containsMany(beanDescriptor, manyWhereJoins);
}
if (havingExpressions != null) {
havingExpressions.containsMany(beanDescriptor, manyWhereJoins);
}
}
Return the extra joins required to support the where clause for 'Many' properties.
/**
* Return the extra joins required to support the where clause for 'Many' properties.
*/
@Override
public ManyWhereJoins getManyWhereJoins() {
return manyWhereJoins;
}
Return true if select all properties was used to ensure the property invoking a lazy load was
included in the query.
/**
* Return true if select all properties was used to ensure the property invoking a lazy load was
* included in the query.
*/
@Override
public boolean selectAllForLazyLoadProperty() {
if (lazyLoadProperty != null) {
if (!detail.containsProperty(lazyLoadProperty)) {
detail.select("*");
return true;
}
}
return false;
}
private List<OrmQueryProperties> removeQueryJoins() {
List<OrmQueryProperties> 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<Property> properties = orderBy.getProperties();
Iterator<Property> it = properties.iterator();
while (it.hasNext()) {
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<OrmQueryProperties> removeLazyJoins() {
return detail.removeSecondaryLazyQueries();
}
@Override
public void setLazyLoadManyPath(String lazyLoadManyPath) {
this.lazyLoadManyPath = lazyLoadManyPath;
}
@Override
public SpiQuerySecondary convertJoins() {
if (!useDocStore) {
createExtraJoinsToSupportManyWhereClause();
}
markQueryJoins();
return new OrmQuerySecondary(removeQueryJoins(), removeLazyJoins());
}
Limit the number of fetch joins to Many properties, mark as query joins as needed.
/**
* Limit the number of fetch joins to Many properties, mark as query joins as needed.
*/
private void markQueryJoins() {
detail.markQueryJoins(beanDescriptor, lazyLoadManyPath, isAllowOneManyFetch(), type != Type.ATTRIBUTE);
}
private boolean isAllowOneManyFetch() {
if (Mode.LAZYLOAD_MANY == getMode()) {
return false;
} else if (hasMaxRowsOrFirstRow() && !isRawSql()) {
return false;
}
return true;
}
@Override
public void setDefaultSelectClause() {
if (type != Type.ATTRIBUTE) {
detail.setDefaultSelectClause(beanDescriptor);
} else if (!detail.hasSelectClause()) {
// explicit empty select when single attribute query on non-root fetch path
detail.setEmptyBase();
}
}
@Override
public void setTenantId(Object tenantId) {
this.tenantId = tenantId;
}
@Override
public Object getTenantId() {
return tenantId;
}
@Override
public void setDetail(OrmQueryDetail detail) {
this.detail = detail;
}
@Override
public boolean tuneFetchProperties(OrmQueryDetail tunedDetail) {
return detail.tuneFetchProperties(tunedDetail);
}
@Override
public OrmQueryDetail getDetail() {
return detail;
}
@Override
public ExpressionList<T> filterMany(String prop) {
OrmQueryProperties chunk = detail.getChunk(prop, true);
return chunk.filterMany(this);
}
@Override
public void setFilterMany(String prop, ExpressionList<?> filterMany) {
if (filterMany != null) {
OrmQueryProperties chunk = detail.getChunk(prop, true);
chunk.setFilterMany((SpiExpressionList<?>) filterMany);
}
}
@Override
public void prepareDocNested() {
if (textExpressions != null) {
textExpressions.prepareDocNested(beanDescriptor);
}
if (whereExpressions != null) {
whereExpressions.prepareDocNested(beanDescriptor);
}
}
Setup to be a delete query.
/**
* Setup to be a delete query.
*/
@Override
public void setDelete() {
// unset any paging and select on the id in the case where the query
// includes joins and we use - delete ... where id in (...)
maxRows = 0;
firstRow = 0;
forUpdate = null;
rootTableAlias = "${RTA}"; // alias we remove later
setSelectId();
}
@Override
public 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.
/**
* Set the select clause to select the Id property.
*/
@Override
public void setSelectId() {
// clear select and fetch joins..
detail.clear();
select(beanDescriptor.getIdBinder().getIdProperty());
}
@Override
public void setSingleAttribute() {
this.singleAttribute = true;
}
Return true if this is a single attribute query.
/**
* Return true if this is a single attribute query.
*/
@Override
public boolean isSingleAttribute() {
return singleAttribute;
}
@Override
public CountDistinctOrder getCountDistinctOrder() {
return countDistinctOrder;
}
Return true if the Id should be included in the query.
/**
* Return true if the Id should be included in the query.
*/
@Override
public boolean isWithId() {
return !manualId && !distinct && !singleAttribute;
}
@Override
public NaturalKeyQueryData<T> naturalKey() {
if (whereExpressions == null) {
return null;
}
String[] naturalKey = beanDescriptor.getNaturalKey();
if (naturalKey == null || naturalKey.length == 0) {
return null;
}
NaturalKeyQueryData<T> data = new NaturalKeyQueryData<>(naturalKey);
for (SpiExpression expression : whereExpressions.getUnderlyingList()) {
// must be eq or in
if (!expression.naturalKey(data)) {
return null;
}
}
return data;
}
@Override
public NaturalKeyBindParam getNaturalKeyBindParam() {
NaturalKeyBindParam namedBind = null;
if (bindParams != null) {
namedBind = bindParams.getNaturalKeyBindParam();
if (namedBind == null) {
return null;
}
}
if (whereExpressions != null) {
List<SpiExpression> 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 DefaultOrmQuery<T> copy() {
return copy(server);
}
@Override
public DefaultOrmQuery<T> copy(SpiEbeanServer server) {
DefaultOrmQuery<T> copy = new DefaultOrmQuery<>(beanDescriptor, server, expressionFactory);
copy.m2mIncludeJoin = m2mIncludeJoin;
copy.profilingListener = profilingListener;
copy.profileLocation = profileLocation;
copy.baseTable = baseTable;
copy.rootTableAlias = rootTableAlias;
copy.distinct = distinct;
copy.allowLoadErrors = allowLoadErrors;
copy.timeout = timeout;
copy.mapKey = mapKey;
copy.id = id;
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.forUpdate = forUpdate;
copy.rawSql = rawSql;
return copy;
}
@Override
public Query<T> setPersistenceContextScope(PersistenceContextScope scope) {
this.persistenceContextScope = scope;
return this;
}
@Override
public PersistenceContextScope getPersistenceContextScope() {
return persistenceContextScope;
}
@Override
public Type getType() {
return type;
}
@Override
public void setType(Type type) {
this.type = type;
}
@Override
public String getLoadDescription() {
return loadDescription;
}
@Override
public String getLoadMode() {
return loadMode;
}
@Override
public 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).
/**
* Return the TransactionContext.
* <p>
* If no TransactionContext is present on the query then the TransactionContext from the
* Transaction is used (transaction scoped persistence context).
* </p>
*/
@Override
public PersistenceContext getPersistenceContext() {
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).
/**
* Set an explicit TransactionContext (typically for a refresh query).
* <p>
* If no TransactionContext is present on the query then the TransactionContext from the
* Transaction is used (transaction scoped persistence context).
* </p>
*/
@Override
public void setPersistenceContext(PersistenceContext persistenceContext) {
this.persistenceContext = persistenceContext;
}
@Override
public void setLazyLoadForParents(BeanPropertyAssocMany<?> many) {
this.lazyLoadForParentsProperty = many;
}
@Override
public BeanPropertyAssocMany<?> getLazyLoadMany() {
return lazyLoadForParentsProperty;
}
Return true if the query detail has neither select or joins specified.
/**
* Return true if the query detail has neither select or joins specified.
*/
@Override
public boolean isDetailEmpty() {
return detail.isEmpty();
}
@Override
public boolean isAutoTuned() {
return autoTuned;
}
@Override
public void setAutoTuned(boolean autoTuned) {
this.autoTuned = autoTuned;
}
@Override
public Boolean isAutoTune() {
return autoTune;
}
@Override
public void setDefaultRawSqlIfRequired() {
if (beanDescriptor.isRawSqlBased() && rawSql == null) {
rawSql = beanDescriptor.getNamedRawSql(DEFAULT_QUERY_NAME);
}
}
@Override
public DefaultOrmQuery<T> setAutoTune(boolean autoTune) {
this.autoTune = autoTune;
return this;
}
@Override
public DefaultOrmQuery<T> forUpdate() {
return setForUpdateWithMode(ForUpdate.BASE);
}
@Override
public DefaultOrmQuery<T> forUpdateNoWait() {
return setForUpdateWithMode(ForUpdate.NOWAIT);
}
@Override
public DefaultOrmQuery<T> forUpdateSkipLocked() {
return setForUpdateWithMode(ForUpdate.SKIPLOCKED);
}
private DefaultOrmQuery<T> setForUpdateWithMode(ForUpdate mode) {
this.forUpdate = mode;
this.useBeanCache = CacheMode.OFF;
return this;
}
@Override
public boolean isForUpdate() {
return forUpdate != null;
}
@Override
public ForUpdate getForUpdateMode() {
return forUpdate;
}
@Override
public ProfilingListener getProfilingListener() {
return profilingListener;
}
@Override
public void setProfilingListener(ProfilingListener profilingListener) {
this.profilingListener = profilingListener;
}
@Override
public QueryType getQueryType() {
if (type != null) {
switch (type) {
case DELETE:
return QueryType.DELETE;
case UPDATE:
return QueryType.UPDATE;
}
}
return QueryType.FIND;
}
@Override
public Mode getMode() {
return mode;
}
@Override
public TemporalMode getTemporalMode() {
return temporalMode;
}
@Override
public boolean isAsOfQuery() {
return asOf != null;
}
@Override
public boolean isAsDraft() {
return TemporalMode.DRAFT == temporalMode;
}
@Override
public boolean isIncludeSoftDeletes() {
return TemporalMode.SOFT_DELETED == temporalMode;
}
@Override
public void setMode(Mode mode) {
this.mode = mode;
}
@Override
public boolean isUsageProfiling() {
return usageProfiling;
}
@Override
public void setUsageProfiling(boolean usageProfiling) {
this.usageProfiling = usageProfiling;
}
@Override
public void setParentNode(ObjectGraphNode parentNode) {
this.parentNode = parentNode;
}
@Override
public ObjectGraphNode getParentNode() {
return parentNode;
}
@Override
public ObjectGraphNode setOrigin(CallStack callStack) {
// create a 'origin' which links this query to the profiling information
ObjectGraphOrigin o = new ObjectGraphOrigin(calculateOriginQueryHash(), callStack, 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.
/**
* Calculate a hash for use in determining the ObjectGraphOrigin.
* <p>
* 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.
* </p>
* <p>
* This stable hash allows the query to be changed (joins added etc) without losing the already
* collected usage profiling.
* </p>
*/
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.
/**
* Calculate the query hash for either AutoTune query tuning or Query Plan caching.
*/
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("t:").append(type.ordinal());
}
if (useDocStore) {
sb.append(",ds:");
}
if (beanDescriptor.getDiscValue() != null) {
sb.append(",disc:").append(beanDescriptor.getDiscValue());
}
if (temporalMode != SpiQuery.TemporalMode.CURRENT) {
sb.append(",temp:").append(temporalMode.ordinal());
}
if (forUpdate != null) {
sb.append(",forUpd:").append(forUpdate.ordinal());
}
if (id != null) {
sb.append(",id:");
}
if (manualId) {
sb.append(",manId:");
}
if (distinct) {
sb.append(",dist:");
}
if (allowLoadErrors) {
sb.append(",allowLoadErrors:");
}
if (disableLazyLoading) {
sb.append(",disLazy:");
}
if (baseTable != null) {
sb.append(",baseTable:").append(baseTable);
}
if (rootTableAlias != null) {
sb.append(",root:").append(rootTableAlias);
}
if (orderBy != null) {
sb.append(",orderBy:").append(orderBy.toStringFormat());
}
if (m2mIncludeJoin != null) {
sb.append(",m2m:").append(m2mIncludeJoin.getTable());
}
if (mapKey != null) {
sb.append(",mapKey:").append(mapKey);
}
if (countDistinctOrder != null) {
sb.append(",countDistOrd:").append(countDistinctOrder.name());
}
if (detail != null) {
sb.append(" detail[");
detail.queryPlanHash(sb);
sb.append("]");
}
if (bindParams != null) {
sb.append(" bindParams[");
bindParams.buildQueryPlanHash(sb);
sb.append("]");
}
if (whereExpressions != null) {
sb.append(" where[");
whereExpressions.queryPlanHash(sb);
sb.append("]");
}
if (havingExpressions != null) {
sb.append(" having[");
havingExpressions.queryPlanHash(sb);
sb.append("]");
}
if (updateProperties != null) {
sb.append(" update[");
updateProperties.buildQueryPlanHash(sb);
sb.append("]");
}
return sb.toString();
}
@Override
public boolean isNativeSql() {
return nativeSql != null;
}
@Override
public String getNativeSql() {
return nativeSql;
}
@Override
public Object getQueryPlanKey() {
return queryPlanKey;
}
Prepare the query which prepares any expressions (sub-query expressions etc) and calculates the query plan key.
/**
* Prepare the query which prepares any expressions (sub-query expressions etc) and calculates the query plan key.
*/
@Override
public CQueryPlanKey prepare(SpiOrmQueryRequest<T> request) {
prepareExpressions(request);
prepareForPaging();
queryPlanKey = createQueryPlanKey();
return queryPlanKey;
}
Prepare the expressions (compile sub-queries etc).
/**
* Prepare the expressions (compile sub-queries etc).
*/
private void prepareExpressions(BeanQueryRequest<?> 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
/**
* 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) {
order(rawSql.getSql().getOrderBy());
}
}
if (checkPagingOrderBy()) {
beanDescriptor.appendOrderById(this);
}
}
Calculate a hash based on the bind values used in the query.
Used with queryPlanHash() to get a unique hash for a query.
/**
* Calculate a hash based on the bind values used in the query.
* <p>
* Used with queryPlanHash() to get a unique hash for a query.
* </p>
*/
@Override
public int queryBindHash() {
int hc = (id == null ? 0 : id.hashCode());
hc = hc * 92821 + (whereExpressions == null ? 0 : whereExpressions.queryBindHash());
hc = hc * 92821 + (havingExpressions == null ? 0 : havingExpressions.queryBindHash());
hc = hc * 92821 + (bindParams == null ? 0 : bindParams.queryBindHash());
hc = hc * 92821 + (asOf == null ? 0 : asOf.hashCode());
hc = hc * 92821 + (versionsStart == null ? 0 : versionsStart.hashCode());
hc = hc * 92821 + (versionsEnd == null ? 0 : versionsEnd.hashCode());
return hc;
}
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.
/**
* Return a hash that includes the query plan and bind values.
* <p>
* This hash can be used to identify if we have executed the exact same query (including bind
* values) before.
* </p>
*/
@Override
public HashQuery queryHash() {
// calculateQueryPlanHash is called just after potential AutoTune tuning
// so queryPlanHash is calculated well before this method is called
int hc = queryBindHash();
return new HashQuery(queryPlanKey, hc);
}
@Override
public boolean isRawSql() {
return rawSql != null;
}
Return the timeout.
/**
* Return the timeout.
*/
@Override
public int getTimeout() {
return timeout;
}
@Override
public boolean hasMaxRowsOrFirstRow() {
return maxRows > 0 || firstRow > 0;
}
@Override
public boolean isVersionsBetween() {
return versionsStart != null;
}
@Override
public Timestamp getVersionStart() {
return versionsStart;
}
@Override
public Timestamp getVersionEnd() {
return versionsEnd;
}
@Override
public Boolean isReadOnly() {
return readOnly;
}
@Override
public DefaultOrmQuery<T> setReadOnly(boolean readOnly) {
this.readOnly = readOnly;
return this;
}
@Override
public boolean isBeanCachePut() {
return useBeanCache.isPut() && beanDescriptor.isBeanCaching();
}
@Override
public boolean isBeanCacheGet() {
return useBeanCache.isGet() && beanDescriptor.isBeanCaching();
}
@Override
public boolean isForceHitDatabase() {
return forUpdate != null || CacheMode.PUT == useBeanCache;
}
@Override
public void resetBeanCacheAutoMode(boolean findOne) {
if (useBeanCache == CacheMode.AUTO) {
if (!findOne || useQueryCache != CacheMode.OFF) {
useBeanCache = CacheMode.OFF;
}
}
}
@Override
public CacheMode getUseBeanCache() {
return useBeanCache;
}
@Override
public CacheMode getUseQueryCache() {
return useQueryCache;
}
@Override
public Query<T> setBeanCacheMode(CacheMode beanCacheMode) {
this.useBeanCache = beanCacheMode;
return this;
}
@Override
public DefaultOrmQuery<T> setUseQueryCache(CacheMode useQueryCache) {
this.useQueryCache = useQueryCache;
return this;
}
@Override
public DefaultOrmQuery<T> setLoadBeanCache(boolean loadBeanCache) {
this.useBeanCache = CacheMode.PUT;
return this;
}
@Override
public DefaultOrmQuery<T> setTimeout(int secs) {
this.timeout = secs;
return this;
}
@Override
public DefaultOrmQuery<T> select(String columns) {
detail.select(columns);
return this;
}
@Override
public DefaultOrmQuery<T> select(FetchGroup fetchGroup) {
this.detail = ((SpiFetchGroup) fetchGroup).detail();
return this;
}
@Override
public DefaultOrmQuery<T> fetch(String property) {
return fetch(property, null, null);
}
@Override
public Query<T> fetchQuery(String property) {
return fetch(property, null, FETCH_QUERY);
}
@Override
public Query<T> fetchLazy(String property) {
return fetch(property, null, FETCH_LAZY);
}
@Override
public DefaultOrmQuery<T> fetch(String property, FetchConfig joinConfig) {
return fetch(property, null, joinConfig);
}
@Override
public DefaultOrmQuery<T> fetch(String property, String columns) {
return fetch(property, columns, null);
}
@Override
public Query<T> fetchQuery(String property, String columns) {
return fetch(property, columns, FETCH_QUERY);
}
@Override
public Query<T> fetchLazy(String property, String columns) {
return fetch(property, columns, FETCH_LAZY);
}
@Override
public DefaultOrmQuery<T> fetch(String property, String columns, FetchConfig config) {
detail.fetch(property, columns, config);
return this;
}
@Override
public int delete() {
return server.delete(this, null);
}
@Override
public int delete(Transaction transaction) {
return server.delete(this, transaction);
}
@Override
public int update() {
return server.update(this, null);
}
@Override
public int update(Transaction transaction) {
return server.update(this, transaction);
}
@Override
public <A> List<A> 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, null);
}
@Override
public boolean exists() {
return server.exists(this, null);
}
@Override
public 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, null);
}
@Override
public void findEachWhile(Predicate<T> consumer) {
server.findEachWhile(this, consumer, null);
}
@Override
public void findEach(Consumer<T> consumer) {
server.findEach(this, consumer, null);
}
@Override
public QueryIterator<T> findIterate() {
return server.findIterate(this, null);
}
@Override
public List<Version<T>> findVersions() {
this.temporalMode = TemporalMode.VERSIONS;
return server.findVersions(this, null);
}
@Override
public List<Version<T>> 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, null);
}
@Override
public List<T> findList() {
return server.findList(this, null);
}
@Override
public Set<T> findSet() {
return server.findSet(this, null);
}
@Override
public <K> Map<K, T> findMap() {
return server.findMap(this, null);
}
@Override
@SuppressWarnings("unchecked")
public <A> List<A> findSingleAttributeList() {
return (List<A>) server.findSingleAttributeList(this, null);
}
@Override
public <A> A findSingleAttribute() {
List<A> list = findSingleAttributeList();
return !list.isEmpty() ? list.get(0) : null;
}
@Override
public T findOne() {
return server.findOne(this, null);
}
@Override
public Optional<T> findOneOrEmpty() {
return server.findOneOrEmpty(this, null);
}
@Override
public FutureIds<T> findFutureIds() {
return server.findFutureIds(this, null);
}
@Override
public FutureList<T> findFutureList() {
return server.findFutureList(this, null);
}
@Override
public FutureRowCount<T> findFutureCount() {
return server.findFutureCount(this, null);
}
@Override
public PagedList<T> findPagedList() {
return server.findPagedList(this, null);
}
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.
/**
* 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 DefaultOrmQuery<T> setParameter(int position, Object value) {
if (bindParams == null) {
bindParams = new BindParams();
}
bindParams.setParameter(position, value);
return this;
}
Set a named bind parameter. Named parameters have a colon to prefix the name.
/**
* Set a named bind parameter. Named parameters have a colon to prefix the name.
*/
@Override
public DefaultOrmQuery<T> setParameter(String name, Object value) {
if (namedParams != null) {
ONamedParam param = namedParams.get(name);
if (param != null) {
param.setValue(value);
return this;
}
}
if (bindParams == null) {
bindParams = new BindParams();
}
bindParams.setParameter(name, value);
return this;
}
@Override
public boolean checkPagingOrderBy() {
return !useDocStore && (maxRows > 1 || firstRow > 0) && !distinct && (orderByIsEmpty() || isOrderById());
}
@Override
public boolean orderByIsEmpty() {
return orderBy == null || orderBy.isEmpty();
}
@Override
public OrderBy<T> getOrderBy() {
return orderBy;
}
@Override
public OrderBy<T> orderBy() {
return order();
}
@Override
public OrderBy<T> order() {
if (orderBy == null) {
orderBy = new OrderBy<>(this, null);
}
return orderBy;
}
@Override
public DefaultOrmQuery<T> orderBy(String orderByClause) {
return order(orderByClause);
}
@Override
public DefaultOrmQuery<T> order(String orderByClause) {
if (orderByClause == null || orderByClause.trim().isEmpty()) {
this.orderBy = null;
} else {
this.orderBy = new OrderBy<>(this, orderByClause);
}
return this;
}
@Override
public DefaultOrmQuery<T> setOrderBy(OrderBy<T> orderBy) {
return setOrder(orderBy);
}
@Override
public DefaultOrmQuery<T> setOrder(OrderBy<T> orderBy) {
this.orderBy = orderBy;
if (orderBy != null) {
orderBy.setQuery(this);
}
return this;
}
@Override
public boolean isManualId() {
return manualId;
}
@Override
public void setManualId(boolean manualId) {
this.manualId = manualId;
}
return true if user specified to use SQL DISTINCT (effectively excludes id property).
/**
* return true if user specified to use SQL DISTINCT (effectively excludes id property).
*/
@Override
public boolean isDistinct() {
return distinct;
}
Internally set to use SQL DISTINCT on the query but still have id property included.
/**
* Internally set to use SQL DISTINCT on the query but still have id property included.
*/
@Override
public DefaultOrmQuery<T> setDistinct(boolean distinct) {
this.distinct = distinct;
return this;
}
@Override
public DefaultOrmQuery<T> setCountDistinct(CountDistinctOrder countDistinctOrder) {
this.countDistinctOrder = countDistinctOrder;
return this;
}
@Override
public boolean isCountDistinct() {
return countDistinctOrder != null;
}
@Override
public Class<T> getBeanType() {
return beanType;
}
@Override
public Class<? extends T> getInheritType() {
return beanDescriptor.getBeanType();
}
@Override
public Query<T> setInheritType(Class<? extends T> type) {
if (type == beanType) {
return this;
}
InheritInfo inheritInfo = rootBeanDescriptor.getInheritInfo();
inheritInfo = inheritInfo == null ? null : inheritInfo.readType(type);
if (inheritInfo == null) {
throw new IllegalArgumentException("Given type " + type + " is not a subtype of " + beanType);
}
beanDescriptor = (BeanDescriptor<T>) rootBeanDescriptor.getBeanDescriptor(type);
return this;
}
@Override
public String toString() {
return "Query [" + whereExpressions + "]";
}
@Override
public TableJoin getM2mIncludeJoin() {
return m2mIncludeJoin;
}
@Override
public void setM2MIncludeJoin(TableJoin m2mIncludeJoin) {
this.m2mIncludeJoin = m2mIncludeJoin;
}
@Override
public DefaultOrmQuery<T> setDisableLazyLoading(boolean disableLazyLoading) {
this.disableLazyLoading = disableLazyLoading;
return this;
}
@Override
public boolean isDisableLazyLoading() {
return disableLazyLoading;
}
@Override
public int getFirstRow() {
return firstRow;
}
@Override
public DefaultOrmQuery<T> setFirstRow(int firstRow) {
this.firstRow = firstRow;
return this;
}
@Override
public int getMaxRows() {
return maxRows;
}
@Override
public DefaultOrmQuery<T> setMaxRows(int maxRows) {
this.maxRows = maxRows;
return this;
}
@Override
public String getMapKey() {
return mapKey;
}
@Override
public DefaultOrmQuery<T> setMapKey(String mapKey) {
this.mapKey = mapKey;
return this;
}
@Override
public Object getId() {
return id;
}
@Override
public DefaultOrmQuery<T> setId(Object id) {
if (id == null) {
throw new NullPointerException("The id is null");
}
this.id = id;
return this;
}
@Override
public BindParams getBindParams() {
return bindParams;
}
@Override
public DefaultOrmQuery<T> where(Expression expression) {
where().add(expression);
return this;
}
@Override
public ExpressionList<T> text() {
if (textExpressions == null) {
useDocStore = true;
textExpressions = new DefaultExpressionList<>(this);
}
return textExpressions;
}
@Override
public ExpressionList<T> where() {
if (whereExpressions == null) {
whereExpressions = new DefaultExpressionList<>(this, null);
}
return whereExpressions;
}
@Override
public void simplifyExpressions() {
if (whereExpressions != null) {
whereExpressions.simplify();
}
}
@Override
public DefaultOrmQuery<T> having(Expression expression) {
having().add(expression);
return this;
}
@Override
public ExpressionList<T> having() {
if (havingExpressions == null) {
havingExpressions = new DefaultExpressionList<>(this, null);
}
return havingExpressions;
}
@Override
public SpiExpressionList<T> getHavingExpressions() {
return havingExpressions;
}
@Override
public SpiExpressionList<T> getWhereExpressions() {
return whereExpressions;
}
@Override
public SpiExpressionList<T> getTextExpression() {
return textExpressions;
}
@Override
public String getGeneratedSql() {
return generatedSql;
}
@Override
public void setGeneratedSql(String generatedSql) {
this.generatedSql = generatedSql;
}
@Override
public void checkNamedParameters() {
if (namedParams != null) {
Collection<ONamedParam> values = namedParams.values();
for (ONamedParam value : values) {
value.checkValueSet();
}
}
}
@Override
public SpiNamedParam createNamedParameter(String name) {
if (namedParams == null) {
namedParams = new HashMap<>();
}
ONamedParam param = namedParams.computeIfAbsent(name, ONamedParam::new);
return param;
}
@Override
public void setDefaultFetchBuffer(int fetchSize) {
if (bufferFetchSizeHint == 0) {
bufferFetchSizeHint = fetchSize;
}
}
@Override
public Query<T> setBufferFetchSizeHint(int bufferFetchSizeHint) {
this.bufferFetchSizeHint = bufferFetchSizeHint;
return this;
}
@Override
public int getBufferFetchSizeHint() {
return bufferFetchSizeHint;
}
@Override
public Query<T> setDisableReadAuditing() {
this.disableReadAudit = true;
return this;
}
@Override
public boolean isDisableReadAudit() {
return disableReadAudit;
}
@Override
public boolean isFutureFetch() {
return futureFetch;
}
@Override
public void setFutureFetch(boolean backgroundFetch) {
this.futureFetch = backgroundFetch;
}
@Override
public void setFutureFetchAudit(ReadEvent event) {
this.futureFetchAudit = event;
}
@Override
public ReadEvent getFutureFetchAudit() {
return futureFetchAudit;
}
@Override
public void setCancelableQuery(CancelableQuery cancelableQuery) {
synchronized (this) {
this.cancelableQuery = cancelableQuery;
}
}
@Override
public Query<T> setBaseTable(String baseTable) {
this.baseTable = baseTable;
return this;
}
@Override
public String getBaseTable() {
return baseTable;
}
@Override
public DefaultOrmQuery<T> alias(String alias) {
this.rootTableAlias = alias;
return this;
}
@Override
public String getAlias() {
return rootTableAlias;
}
@Override
public void cancel() {
synchronized (this) {
cancelled = true;
if (cancelableQuery != null) {
cancelableQuery.cancel();
}
}
}
@Override
public boolean isCancelled() {
synchronized (this) {
return cancelled;
}
}
@Override
public Set<String> validate() {
return server.validateQuery(this);
}
Validate all the expression properties/paths given the bean descriptor.
/**
* Validate all the expression properties/paths given the bean descriptor.
*/
@Override
public Set<String> validate(BeanType<T> desc) {
SpiExpressionValidation validation = new SpiExpressionValidation(desc);
if (whereExpressions != null) {
whereExpressions.validate(validation);
}
if (havingExpressions != null) {
havingExpressions.validate(validation);
}
if (orderBy != null) {
for (Property property : orderBy.getProperties()) {
validation.validate(property.getProperty());
}
}
return validation.getUnknownProperties();
}
void setUpdateProperties(OrmUpdateProperties updateProperties) {
this.updateProperties = updateProperties;
}
@Override
public OrmUpdateProperties getUpdateProperties() {
return updateProperties;
}
@Override
public ProfileLocation getProfileLocation() {
return profileLocation;
}
@Override
public void handleLoadError(String fullName, Exception e) {
if (!allowLoadErrors) {
throw new PersistenceException("Error loading on " + fullName, e);
}
}
@Override
public Query<T> orderById(boolean orderById) {
this.orderById = orderById;
return this;
}
public boolean isOrderById() {
return orderById;
}
}