package io.ebeaninternal.server.query;

import io.ebean.Version;
import io.ebean.bean.BeanCollection;
import io.ebean.bean.EntityBean;
import io.ebean.bean.EntityBeanIntercept;
import io.ebean.bean.PersistenceContext;
import io.ebean.core.type.ScalarDataReader;
import io.ebean.util.SplitName;
import io.ebeaninternal.api.SpiQuery;
import io.ebeaninternal.api.SpiQuery.Mode;
import io.ebeaninternal.server.deploy.DbReadContext;
import io.ebeaninternal.server.deploy.DbSqlContext;
import io.ebeaninternal.server.deploy.InheritInfo;
import io.ebeaninternal.server.deploy.TableJoin;
import io.ebeaninternal.server.deploy.id.IdBinder;

import java.sql.SQLException;
import java.sql.Timestamp;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

Normal bean included in the query.
/** * Normal bean included in the query. */
class SqlTreeNodeBean implements SqlTreeNode { private static final SqlTreeNode[] NO_CHILDREN = new SqlTreeNode[0]; final STreeType desc; final IdBinder idBinder;
The children which will be other SelectBean or SelectProxyBean.
/** * The children which will be other SelectBean or SelectProxyBean. */
final SqlTreeNode[] children;
Set to true if this is a partial object fetch.
/** * Set to true if this is a partial object fetch. */
private final boolean partialObject; private final STreeProperty[] properties;
Extra where clause added by Where annotation on associated many.
/** * Extra where clause added by Where annotation on associated many. */
private final String extraWhere; private final STreePropertyAssoc nodeBeanProp;
False if report bean and has no id property.
/** * False if report bean and has no id property. */
final boolean readId; private final boolean disableLazyLoad; private final InheritInfo inheritInfo; final String prefix; private final Map<String, String> pathMap; final STreePropertyAssocMany lazyLoadParent; private final SpiQuery.TemporalMode temporalMode; private final boolean temporalVersions; private final IdBinder lazyLoadParentIdBinder; String baseTableAlias;
Table alias set if this bean node includes a join to a intersection table and that table has history support.
/** * Table alias set if this bean node includes a join to a intersection * table and that table has history support. */
private boolean intersectionAsOfTableAlias; private final boolean aggregation;
Construct for leaf node.
/** * Construct for leaf node. */
SqlTreeNodeBean(String prefix, STreePropertyAssoc beanProp, SqlTreeProperties props, List<SqlTreeNode> myChildren, boolean withId, SpiQuery.TemporalMode temporalMode, boolean disableLazyLoad) { this(prefix, beanProp, beanProp.target(), props, myChildren, withId, null, temporalMode, disableLazyLoad); }
Construct for root node.
/** * Construct for root node. */
SqlTreeNodeBean(STreeType desc, SqlTreeProperties props, List<SqlTreeNode> myList, boolean withId, STreePropertyAssocMany many, SpiQuery.TemporalMode temporalMode, boolean disableLazyLoad) { this(null, null, desc, props, myList, withId, many, temporalMode, disableLazyLoad); }
Create with the appropriate node.
/** * Create with the appropriate node. */
private SqlTreeNodeBean(String prefix, STreePropertyAssoc beanProp, STreeType desc, SqlTreeProperties props, List<SqlTreeNode> myChildren, boolean withId, STreePropertyAssocMany lazyLoadParent, SpiQuery.TemporalMode temporalMode, boolean disableLazyLoad) { this.lazyLoadParent = lazyLoadParent; this.lazyLoadParentIdBinder = (lazyLoadParent == null) ? null : lazyLoadParent.getIdBinder(); this.prefix = prefix; this.desc = desc; this.inheritInfo = desc.getInheritInfo(); this.idBinder = desc.getIdBinder(); this.temporalMode = temporalMode; this.temporalVersions = temporalMode == SpiQuery.TemporalMode.VERSIONS; this.nodeBeanProp = beanProp; this.extraWhere = (beanProp == null) ? null : beanProp.getExtraWhere(); this.aggregation = props.isAggregation(); boolean aggregationRoot = props.isAggregationRoot(); // the bean has an Id property and we want to use it this.readId = !aggregationRoot && withId && desc.hasId(); this.disableLazyLoad = disableLazyLoad || !readId || desc.isRawSqlBased() || temporalVersions; this.partialObject = props.isPartialObject(); this.properties = props.getProps(); this.children = myChildren == null ? NO_CHILDREN : myChildren.toArray(new SqlTreeNode[0]); pathMap = createPathMap(prefix, desc); } boolean isRoot() { return false; } @Override public boolean isSingleProperty() { return properties != null && properties.length == 1 && children.length == 0; } @Override public ScalarDataReader<?> getSingleAttributeReader() { if (properties == null || properties.length == 0) { // if we have no property ask first children (in a distinct select with join) if (children.length == 0) { // expected to be a findIds query return desc.getIdBinder().getBeanProperty(); } return children[0].getSingleAttributeReader(); } if (properties[0] instanceof STreePropertyAssocOne) { STreePropertyAssocOne assocOne = (STreePropertyAssocOne)properties[0]; if (assocOne.isAssocId()) { return assocOne.getIdReader(); } } return properties[0]; } private Map<String, String> createPathMap(String prefix, STreeType desc) { HashMap<String, String> m = new HashMap<>(); for (STreePropertyAssocMany many : desc.propsMany()) { String name = many.getName(); m.put(name, getPath(prefix, name)); } return m; } private String getPath(String prefix, String propertyName) { if (prefix == null) { return propertyName; } else { return prefix + "." + propertyName; } } @Override public void buildRawSqlSelectChain(List<String> selectChain) { if (readId) { if (inheritInfo != null) { // discriminator column always proceeds id column selectChain.add(getPath(prefix, inheritInfo.getDiscriminatorColumn())); } idBinder.buildRawSqlSelectChain(prefix, selectChain); } for (STreeProperty property : properties) { property.buildRawSqlSelectChain(prefix, selectChain); } // recursively continue reading... for (SqlTreeNode child : children) { // read each child... and let them set their // values back to this localBean child.buildRawSqlSelectChain(selectChain); } }
Read the version bean.
/** * Read the version bean. */
@Override @SuppressWarnings("unchecked") public <T> Version<T> loadVersion(DbReadContext ctx) throws SQLException { // read the sys period lower and upper bounds // these are always the first 2 columns in the resultSet Timestamp start = ctx.getDataReader().getTimestamp(); Timestamp end = ctx.getDataReader().getTimestamp(); T bean = (T) load(ctx, null, null); return new Version<>(bean, start, end); }
Load that takes into account inheritance.
/** * Load that takes into account inheritance. */
private class LoadInherit extends Load { LoadInherit(DbReadContext ctx, EntityBean parentBean) { super(ctx, parentBean); } @Override void initBeanType() throws SQLException { InheritInfo localInfo = readId ? inheritInfo.readType(ctx) : desc.getInheritInfo(); if (localInfo == null) { // the bean must be null localIdBinder = idBinder; localDesc = desc; } else { localBean = localInfo.createEntityBean(); localType = localInfo.getType(); localIdBinder = localInfo.getIdBinder(); localDesc = localInfo.desc(); } } @Override void loadProperties() { // take account of inheritance and due to subclassing approach // need to get a 'local' version of the property for (STreeProperty property : properties) { // get a local version of the BeanProperty localDesc.inheritanceLoad(sqlBeanLoad, property, ctx); } } }
Load a bean instance.
/** * Load a bean instance. */
private class Load { final DbReadContext ctx; final EntityBean parentBean; Object lazyLoadParentId; Class<?> localType; STreeType localDesc; IdBinder localIdBinder; EntityBean localBean; Mode queryMode; PersistenceContext persistenceContext; Object id; EntityBean contextBean; SqlBeanLoad sqlBeanLoad; boolean lazyLoadMany; Load(DbReadContext ctx, EntityBean parentBean) { this.ctx = ctx; this.parentBean = parentBean; } void initLazyParent() throws SQLException { if (lazyLoadParentIdBinder != null) { lazyLoadParentId = lazyLoadParentIdBinder.read(ctx); } } void initBeanType() throws SQLException { localDesc = desc; localBean = desc.createEntityBean(); localIdBinder = idBinder; } void initPersistenceContext() { queryMode = ctx.getQueryMode(); persistenceContext = (!readId || temporalVersions) ? null : ctx.getPersistenceContext(); } void readId() throws SQLException { if (readId) { id = localIdBinder.readSet(ctx, localBean); if (id == null) { readIdNullBean(); } else if (!temporalVersions) { readIdBean(); } } } private void readIdBean() { // check the PersistenceContext to see if the bean already exists contextBean = (EntityBean) localDesc.contextPutIfAbsent(persistenceContext, id, localBean); if (contextBean == null) { // bean just added to the persistenceContext contextBean = localBean; } else { // bean already exists in persistenceContext if (isLoadContextBeanNeeded(queryMode, contextBean)) { // refresh it anyway (lazy loading for example) localBean = contextBean; } else { // ignore the DB data... localBean = null; } } } private void readIdNullBean() { // bean must be null... localBean = null; // ... but there may exist as reference bean in parent which has to be marked as deleted. if (parentBean != null && nodeBeanProp instanceof STreePropertyAssocOne) { contextBean = ((STreePropertyAssocOne)nodeBeanProp).getValueAsEntityBean(parentBean); if (contextBean != null) { desc.markAsDeleted(contextBean); } } } void initSqlLoadBean() { ctx.setCurrentPrefix(prefix, pathMap); ctx.propagateState(localBean); sqlBeanLoad = new SqlBeanLoad(ctx, localType, localBean, queryMode); } void loadProperties() { for (STreeProperty property : properties) { property.load(sqlBeanLoad); } } void loadChildren() throws SQLException { //boolean lazyLoadMany = false; if (localBean == null && queryMode == Mode.LAZYLOAD_MANY) { // batch lazy load many into existing contextBean localBean = contextBean; lazyLoadMany = true; } // recursively continue reading... for (SqlTreeNode aChildren : children) { // read each child... and let them set their // values back to this localBean aChildren.load(ctx, localBean, contextBean); } } boolean isLazyLoadManyRoot() { return queryMode == Mode.LAZYLOAD_MANY && isRoot(); } EntityBean getContextBean() { return contextBean; } void postLoad() { if (!lazyLoadMany && localBean != null) { ctx.setCurrentPrefix(prefix, pathMap); if (readId && !temporalVersions) { createListProxies(); } if (temporalMode == SpiQuery.TemporalMode.DRAFT) { localDesc.setDraft(localBean); } localDesc.postLoad(localBean); EntityBeanIntercept ebi = localBean._ebean_getIntercept(); ebi.setPersistenceContext(persistenceContext); if (Mode.LAZYLOAD_BEAN == queryMode) { // Lazy Load does not reset the dirty state ebi.setLoadedLazy(); } else if (readId) { // normal bean loading ebi.setLoaded(); } if (disableLazyLoad) { // bean does not have an Id or is SqlSelect based ebi.setDisableLazyLoad(true); } else if (partialObject) { if (readId) { // register for lazy loading ctx.register(null, ebi); } } else { ebi.setFullyLoadedBean(true); } if (ctx.isAutoTuneProfiling() && !disableLazyLoad) { // collect autoTune profiling for this bean... ctx.profileBean(ebi, prefix); } } }
Create lazy loading proxies for the Many's except for the one that is included in the actual query.
/** * Create lazy loading proxies for the Many's except for the one that is * included in the actual query. */
private void createListProxies() { STreePropertyAssocMany fetchedMany = ctx.getManyProperty(); boolean forceNewReference = queryMode == Mode.REFRESH_BEAN; // load the List/Set/Map proxy objects (deferred fetching of lists) for (STreePropertyAssocMany many : localDesc.propsMany()) { if (many != fetchedMany) { // create a proxy for the many (deferred fetching) BeanCollection<?> ref = many.createReference(localBean, forceNewReference); if (ref != null) { if (disableLazyLoad) { ref.setDisableLazyLoad(true); } if (!ref.isRegisteredWithLoadContext()) { ctx.register(many.asMany(), ref); } } } } } void setBeanToParent() { if (parentBean != null) { // set this back to the parentBean nodeBeanProp.setValue(parentBean, contextBean); } } EntityBean complete() { if (!readId || temporalVersions) { // a bean with no Id (never found in context) if (lazyLoadParentId != null) { ctx.setLazyLoadedChildBean(localBean, lazyLoadParentId); } return localBean; } else { if (lazyLoadParentId != null) { ctx.setLazyLoadedChildBean(contextBean, lazyLoadParentId); } return contextBean; } } void initialise() throws SQLException { initLazyParent(); initBeanType(); initPersistenceContext(); readId(); initSqlLoadBean(); loadProperties(); loadChildren(); } }
read the properties from the resultSet.
/** * read the properties from the resultSet. */
@Override public EntityBean load(DbReadContext ctx, EntityBean parentBean, EntityBean contextParent) throws SQLException { Load load = (inheritInfo != null) ? new LoadInherit(ctx, parentBean) : new Load(ctx, parentBean); load.initialise(); if (load.isLazyLoadManyRoot()) { return load.getContextBean(); } load.postLoad(); load.setBeanToParent(); return load.complete(); } @Override public void appendGroupBy(DbSqlContext ctx, boolean subQuery) { ctx.pushJoin(prefix); ctx.pushTableAlias(prefix); if (lazyLoadParent != null) { lazyLoadParent.addSelectExported(ctx, prefix); } if (readId) { appendSelectId(ctx, idBinder.getBeanProperty()); } for (STreeProperty property : properties) { if (!property.isAggregation()) { property.appendSelect(ctx, subQuery); } } for (SqlTreeNode aChildren : children) { aChildren.appendGroupBy(ctx, subQuery); } ctx.popTableAlias(); ctx.popJoin(); }
Append the property columns to the buffer.
/** * Append the property columns to the buffer. */
@Override public void appendDistinctOn(DbSqlContext ctx, boolean subQuery) { for (SqlTreeNode child : children) { child.appendDistinctOn(ctx, subQuery); } }
Append the property columns to the buffer.
/** * Append the property columns to the buffer. */
@Override public void appendSelect(DbSqlContext ctx, boolean subQuery) { ctx.pushJoin(prefix); ctx.pushTableAlias(prefix); if (temporalVersions) { // select sys_period lower and upper columns ctx.appendHistorySysPeriod(); } if (lazyLoadParent != null) { lazyLoadParent.addSelectExported(ctx, prefix); } if (readId) { if (!subQuery && inheritInfo != null) { ctx.appendColumn(inheritInfo.getDiscriminatorColumn()); } appendSelectId(ctx, idBinder.getBeanProperty()); } appendSelect(ctx, subQuery, properties); for (SqlTreeNode aChildren : children) { // read each child... and let them set their // values back to this localBean aChildren.appendSelect(ctx, subQuery); } ctx.popTableAlias(); ctx.popJoin(); } @Override public boolean isAggregation() { if (aggregation) { return true; } for (SqlTreeNode child : children) { if (child.isAggregation()) { return true; } } return false; }
Append the properties to the buffer.
/** * Append the properties to the buffer. */
private void appendSelect(DbSqlContext ctx, boolean subQuery, STreeProperty[] props) { for (STreeProperty prop : props) { prop.appendSelect(ctx, subQuery); } } void appendSelectId(DbSqlContext ctx, STreeProperty prop) { if (prop != null) { prop.appendSelect(ctx, false); } } @Override public void appendWhere(DbSqlContext ctx) { // Only apply inheritance to root node as any join will already have the inheritance join include - see TableJoin if (inheritInfo != null && nodeBeanProp == null) { if (!inheritInfo.isRoot()) { // restrict to this type and sub types of this type. if (ctx.length() > 0) { ctx.append(" and"); } ctx.append(" ").append(ctx.getTableAlias(prefix)).append("."); ctx.append(inheritInfo.getWhere()); } } appendExtraWhere(ctx); for (SqlTreeNode aChildren : children) { // recursively add to the where clause any // fixed predicates (extraWhere etc) aChildren.appendWhere(ctx); } } void appendExtraWhere(DbSqlContext ctx) { if (extraWhere != null) { if (ctx.length() > 0) { ctx.append(" and"); } String ta = ctx.getTableAlias(prefix); ctx.append(" ").append(extraWhere.replace("${ta}", ta)); } }
Append to the FROM clause for this node.
/** * Append to the FROM clause for this node. */
@Override public void appendFrom(DbSqlContext ctx, SqlJoinType joinType) { if (nodeBeanProp != null && nodeBeanProp.isFormula()) { // add joins for formula beans nodeBeanProp.appendFrom(ctx, joinType); } ctx.pushJoin(prefix); ctx.pushTableAlias(prefix); baseTableAlias = ctx.getTableAlias(prefix); // join and return SqlJoinType to use for child joins joinType = appendFromBaseTable(ctx, joinType); for (STreeProperty property : properties) { // usually nothing... except for 1-1 Exported property.appendFrom(ctx, joinType); } for (SqlTreeNode aChildren : children) { aChildren.appendFrom(ctx, joinType); } ctx.popTableAlias(); ctx.popJoin(); } @Override public void addSoftDeletePredicate(SpiQuery<?> query) { if (desc.isSoftDelete()) { query.addSoftDeletePredicate(desc.getSoftDeletePredicate(baseTableAlias)); } } @Override public void addAsOfTableAlias(SpiQuery<?> query) { // if history on this bean type add it's alias // for each alias we add an effect date predicate if (desc.isHistorySupport()) { query.incrementAsOfTableCount(); } if (lazyLoadParent != null && lazyLoadParent.isManyToManyWithHistory()) { query.incrementAsOfTableCount(); } if (intersectionAsOfTableAlias) { query.incrementAsOfTableCount(); } for (SqlTreeNode aChildren : children) { aChildren.addAsOfTableAlias(query); } } @Override public void dependentTables(Set<String> tables) { tables.add(nodeBeanProp.target().getBaseTable(temporalMode)); for (SqlTreeNode child : children) { child.dependentTables(tables); } }
Join to base table for this node. This includes a join to the intersection table if this is a ManyToMany node.
/** * Join to base table for this node. This includes a join to the intersection * table if this is a ManyToMany node. */
SqlJoinType appendFromBaseTable(DbSqlContext ctx, SqlJoinType joinType) { SqlJoinType sqlJoinType = appendFromAsJoin(ctx, joinType); if (inheritInfo != null) { appendJoinDiscriminator(ctx); } if (desc.isSoftDelete() && temporalMode != SpiQuery.TemporalMode.SOFT_DELETED) { ctx.append(" and ").append(desc.getSoftDeletePredicate(ctx.getTableAlias(prefix))); } return sqlJoinType; } SqlJoinType appendFromAsJoin(DbSqlContext ctx, SqlJoinType joinType) { if (nodeBeanProp instanceof STreePropertyAssocMany) { STreePropertyAssocMany manyProp = (STreePropertyAssocMany) nodeBeanProp; if (manyProp.hasJoinTable()) { String alias = ctx.getTableAlias(prefix); String[] split = SplitName.split(prefix); String parentAlias = ctx.getTableAlias(split[0]); String alias2 = alias + "z_"; // adding the additional join to the intersection table TableJoin manyToManyJoin = manyProp.getIntersectionTableJoin(); manyToManyJoin.addJoin(joinType, parentAlias, alias2, ctx); if (!manyProp.isExcludedFromHistory()) { intersectionAsOfTableAlias = true; } return nodeBeanProp.addJoin(joinType, alias2, alias, ctx); } } return nodeBeanProp.addJoin(joinType, prefix, ctx); } void appendJoinDiscriminator(DbSqlContext ctx) { if (inheritInfo.getWhere() == null) return; String alias = ctx.getTableAlias(prefix); ctx.append(" and ").append(alias).append(".").append(inheritInfo.getWhere()); }
Summary description.
/** * Summary description. */
@Override public String toString() { return "SqlTreeNodeBean: " + desc; } private boolean isLoadContextBeanNeeded(Mode queryMode, EntityBean contextBean) { // if explicitly set loadContextBean to true, then reload if (queryMode.isLoadContextBean()) { return true; } // reload if contextBean is partial object return !contextBean._ebean_getIntercept().isFullyLoadedBean(); } @Override public boolean hasMany() { for (SqlTreeNode child : children) { if (child.hasMany()) { return true; } } return false; } }