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;
class SqlTreeNodeBean implements SqlTreeNode {
private static final SqlTreeNode[] NO_CHILDREN = new SqlTreeNode[0];
final STreeType desc;
final IdBinder idBinder;
final SqlTreeNode[] children;
private final boolean partialObject;
private final STreeProperty[] properties;
private final String ;
private final STreePropertyAssoc nodeBeanProp;
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;
private boolean intersectionAsOfTableAlias;
private final boolean aggregation;
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);
}
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);
}
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();
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 (children.length == 0) {
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) {
selectChain.add(getPath(prefix, inheritInfo.getDiscriminatorColumn()));
}
idBinder.buildRawSqlSelectChain(prefix, selectChain);
}
for (STreeProperty property : properties) {
property.buildRawSqlSelectChain(prefix, selectChain);
}
for (SqlTreeNode child : children) {
child.buildRawSqlSelectChain(selectChain);
}
}
@Override
@SuppressWarnings("unchecked")
public <T> Version<T> loadVersion(DbReadContext ctx) throws SQLException {
Timestamp start = ctx.getDataReader().getTimestamp();
Timestamp end = ctx.getDataReader().getTimestamp();
T bean = (T) load(ctx, null, null);
return new Version<>(bean, start, end);
}
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) {
localIdBinder = idBinder;
localDesc = desc;
} else {
localBean = localInfo.createEntityBean();
localType = localInfo.getType();
localIdBinder = localInfo.getIdBinder();
localDesc = localInfo.desc();
}
}
@Override
void loadProperties() {
for (STreeProperty property : properties) {
localDesc.inheritanceLoad(sqlBeanLoad, property, ctx);
}
}
}
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() {
contextBean = (EntityBean) localDesc.contextPutIfAbsent(persistenceContext, id, localBean);
if (contextBean == null) {
contextBean = localBean;
} else {
if (isLoadContextBeanNeeded(queryMode, contextBean)) {
localBean = contextBean;
} else {
localBean = null;
}
}
}
private void readIdNullBean() {
localBean = null;
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 {
if (localBean == null && queryMode == Mode.LAZYLOAD_MANY) {
localBean = contextBean;
lazyLoadMany = true;
}
for (SqlTreeNode aChildren : children) {
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) {
ebi.setLoadedLazy();
} else if (readId) {
ebi.setLoaded();
}
if (disableLazyLoad) {
ebi.setDisableLazyLoad(true);
} else if (partialObject) {
if (readId) {
ctx.register(null, ebi);
}
} else {
ebi.setFullyLoadedBean(true);
}
if (ctx.isAutoTuneProfiling() && !disableLazyLoad) {
ctx.profileBean(ebi, prefix);
}
}
}
private void createListProxies() {
STreePropertyAssocMany fetchedMany = ctx.getManyProperty();
boolean forceNewReference = queryMode == Mode.REFRESH_BEAN;
for (STreePropertyAssocMany many : localDesc.propsMany()) {
if (many != fetchedMany) {
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) {
nodeBeanProp.setValue(parentBean, contextBean);
}
}
EntityBean complete() {
if (!readId || temporalVersions) {
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();
}
}
@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();
}
@Override
public void appendDistinctOn(DbSqlContext ctx, boolean subQuery) {
for (SqlTreeNode child : children) {
child.appendDistinctOn(ctx, subQuery);
}
}
@Override
public void appendSelect(DbSqlContext ctx, boolean subQuery) {
ctx.pushJoin(prefix);
ctx.pushTableAlias(prefix);
if (temporalVersions) {
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) {
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;
}
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) {
if (inheritInfo != null && nodeBeanProp == null) {
if (!inheritInfo.isRoot()) {
if (ctx.length() > 0) {
ctx.append(" and");
}
ctx.append(" ").append(ctx.getTableAlias(prefix)).append(".");
ctx.append(inheritInfo.getWhere());
}
}
appendExtraWhere(ctx);
for (SqlTreeNode aChildren : children) {
aChildren.appendWhere(ctx);
}
}
void (DbSqlContext ctx) {
if (extraWhere != null) {
if (ctx.length() > 0) {
ctx.append(" and");
}
String ta = ctx.getTableAlias(prefix);
ctx.append(" ").append(extraWhere.replace("${ta}", ta));
}
}
@Override
public void appendFrom(DbSqlContext ctx, SqlJoinType joinType) {
if (nodeBeanProp != null && nodeBeanProp.isFormula()) {
nodeBeanProp.appendFrom(ctx, joinType);
}
ctx.pushJoin(prefix);
ctx.pushTableAlias(prefix);
baseTableAlias = ctx.getTableAlias(prefix);
joinType = appendFromBaseTable(ctx, joinType);
for (STreeProperty property : properties) {
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 (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);
}
}
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_";
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());
}
@Override
public String toString() {
return "SqlTreeNodeBean: " + desc;
}
private boolean isLoadContextBeanNeeded(Mode queryMode, EntityBean contextBean) {
if (queryMode.isLoadContextBean()) {
return true;
}
return !contextBean._ebean_getIntercept().isFullyLoadedBean();
}
@Override
public boolean hasMany() {
for (SqlTreeNode child : children) {
if (child.hasMany()) {
return true;
}
}
return false;
}
}