package io.ebeaninternal.server.query;
import io.ebean.QueryIterator;
import io.ebean.ValuePair;
import io.ebean.Version;
import io.ebean.bean.BeanCollection;
import io.ebean.bean.EntityBean;
import io.ebean.bean.ObjectGraphNode;
import io.ebean.config.DatabaseConfig;
import io.ebean.config.dbplatform.DatabasePlatform;
import io.ebean.util.JdbcClose;
import io.ebean.util.StringHelper;
import io.ebeaninternal.api.SpiQuery;
import io.ebeaninternal.api.SpiTransaction;
import io.ebeaninternal.server.core.DiffHelp;
import io.ebeaninternal.server.core.OrmQueryRequest;
import io.ebeaninternal.server.core.SpiResultSet;
import io.ebeaninternal.server.deploy.BeanDescriptor;
import io.ebeaninternal.server.lib.Str;
import io.ebeaninternal.server.persist.Binder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.persistence.PersistenceException;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
public class CQueryEngine {
private static final Logger logger = LoggerFactory.getLogger(CQueryEngine.class);
private static final int defaultSecondaryQueryBatchSize = 100;
private static final String T0 = "t0";
private final int defaultFetchSizeFindList;
private final int defaultFetchSizeFindEach;
private final boolean forwardOnlyHintOnFindIterate;
private final CQueryBuilder queryBuilder;
private final CQueryHistorySupport historySupport;
private final DatabasePlatform dbPlatform;
public CQueryEngine(DatabaseConfig config, DatabasePlatform dbPlatform, Binder binder, Map<String, String> asOfTableMapping, Map<String, String> draftTableMap) {
this.dbPlatform = dbPlatform;
this.defaultFetchSizeFindEach = config.getJdbcFetchSizeFindEach();
this.defaultFetchSizeFindList = config.getJdbcFetchSizeFindList();
this.forwardOnlyHintOnFindIterate = dbPlatform.isForwardOnlyHintOnFindIterate();
this.historySupport = new CQueryHistorySupport(dbPlatform.getHistorySupport(), asOfTableMapping, config.getAsOfSysPeriod());
this.queryBuilder = new CQueryBuilder(dbPlatform, binder, historySupport, new CQueryDraftSupport(draftTableMap));
}
public <T> CQuery<T> buildQuery(OrmQueryRequest<T> request) {
return queryBuilder.buildQuery(request);
}
public <T> int delete(OrmQueryRequest<T> request) {
CQueryUpdate query = queryBuilder.buildUpdateQuery(true, request);
return executeUpdate(request, query);
}
public <T> int update(OrmQueryRequest<T> request) {
CQueryUpdate query = queryBuilder.buildUpdateQuery(false, request);
return executeUpdate(request, query);
}
private <T> int executeUpdate(OrmQueryRequest<T> request, CQueryUpdate query) {
try {
int rows = query.execute();
if (request.isLogSql()) {
String logSql = query.getGeneratedSql();
logSql = Str.add(logSql, "; --bind(", query.getBindLog(), ") rows:", String.valueOf(rows));
request.logSql(logSql);
}
return rows;
} catch (SQLException e) {
throw translate(request, query.getBindLog(), query.getGeneratedSql(), e);
}
}
public <A> List<A> findSingleAttributeList(OrmQueryRequest<?> request) {
CQueryFetchSingleAttribute rcQuery = queryBuilder.buildFetchAttributeQuery(request);
return findAttributeList(request, rcQuery);
}
@SuppressWarnings("unchecked")
private <A> List<A> findAttributeList(OrmQueryRequest<?> request, CQueryFetchSingleAttribute rcQuery) {
try {
List<A> list = (List<A>) rcQuery.findList();
if (request.isLogSql()) {
logGeneratedSql(request, rcQuery.getGeneratedSql(), rcQuery.getBindLog());
}
if (request.isLogSummary()) {
request.getTransaction().logSummary(rcQuery.getSummary());
}
if (request.isQueryCachePut()) {
request.addDependentTables(rcQuery.getDependentTables());
list = Collections.unmodifiableList(list);
request.putToQueryCache(list);
if (Boolean.FALSE.equals(request.getQuery().isReadOnly())) {
list = new ArrayList<>(list);
}
}
return list;
} catch (SQLException e) {
throw translate(request, rcQuery.getBindLog(), rcQuery.getGeneratedSql(), e);
}
}
<T> PersistenceException translate(OrmQueryRequest<T> request, String bindLog, String sql, SQLException e) {
SpiTransaction t = request.getTransaction();
if (t.isLogSummary()) {
String msg = "ERROR executing query, bindLog[" + bindLog + "] error[" + StringHelper.removeNewLines(e.getMessage()) + "]";
t.logSummary(msg);
}
t.getConnection();
String m = "Query threw SQLException:" + e.getMessage() + " Bind values:[" + bindLog + "] Query was:" + sql;
return dbPlatform.translate(m, e);
}
public <A> List<A> findIds(OrmQueryRequest<?> request) {
CQueryFetchSingleAttribute rcQuery = queryBuilder.buildFetchIdsQuery(request);
return findAttributeList(request, rcQuery);
}
private <T> void logGeneratedSql(OrmQueryRequest<T> request, String sql, String bindLog) {
request.logSql(Str.add(sql, "; --bind(", bindLog, ")"));
}
public <T> int findCount(OrmQueryRequest<T> request) {
CQueryRowCount rcQuery = queryBuilder.buildRowCountQuery(request);
try {
int count = rcQuery.findCount();
if (request.isLogSql()) {
logGeneratedSql(request, rcQuery.getGeneratedSql(), rcQuery.getBindLog());
}
if (request.isLogSummary()) {
request.getTransaction().logSummary(rcQuery.getSummary());
}
if (request.getQuery().isFutureFetch()) {
request.getTransaction().end();
}
if (request.isQueryCachePut()) {
request.addDependentTables(rcQuery.getDependentTables());
request.putToQueryCache(count);
}
return count;
} catch (SQLException e) {
throw translate(request, rcQuery.getBindLog(), rcQuery.getGeneratedSql(), e);
}
}
public <T> QueryIterator<T> findIterate(OrmQueryRequest<T> request) {
CQuery<T> cquery = queryBuilder.buildQuery(request);
request.setCancelableQuery(cquery);
try {
if (defaultFetchSizeFindEach > 0) {
request.setDefaultFetchBuffer(defaultFetchSizeFindEach);
}
if (!cquery.prepareBindExecuteQueryForwardOnly(forwardOnlyHintOnFindIterate)) {
logger.trace("Future fetch already cancelled");
return null;
}
if (request.isLogSql()) {
logSql(cquery);
}
int iterateBufferSize = request.getSecondaryQueriesMinBatchSize(defaultSecondaryQueryBatchSize);
if (iterateBufferSize < 1) {
int queryBatch = request.getQuery().getLazyLoadBatchSize();
if (queryBatch > 0) {
iterateBufferSize = queryBatch;
} else {
iterateBufferSize = 100;
}
}
QueryIterator<T> readIterate = cquery.readIterate(iterateBufferSize, request);
if (request.isLogSummary()) {
logFindManySummary(cquery);
}
if (request.isAuditReads()) {
cquery.auditFindIterate();
}
return readIterate;
} catch (SQLException e) {
try {
cquery.close();
throw cquery.createPersistenceException(e);
} finally {
request.rollbackTransIfRequired();
}
}
}
public <T> List<Version<T>> findVersions(OrmQueryRequest<T> request) {
SpiQuery<T> query = request.getQuery();
String sysPeriodLower = getSysPeriodLower(query);
if (query.isVersionsBetween() && !historySupport.isStandardsBased()) {
query.where().lt(sysPeriodLower, query.getVersionEnd());
query.where().geOrNull(getSysPeriodUpper(query), query.getVersionStart());
}
query.order().desc(sysPeriodLower);
CQuery<T> cquery = queryBuilder.buildQuery(request);
try {
cquery.prepareBindExecuteQuery();
if (request.isLogSql()) {
logSql(cquery);
}
List<Version<T>> versions = cquery.readVersions();
versions.sort(OrderVersionDesc.INSTANCE);
deriveVersionDiffs(versions, request);
if (request.isLogSummary()) {
logFindManySummary(cquery);
}
if (request.isAuditReads()) {
cquery.auditFindMany();
}
return versions;
} catch (SQLException e) {
throw cquery.createPersistenceException(e);
} finally {
if (cquery != null) {
cquery.close();
}
}
}
private <T> void deriveVersionDiffs(List<Version<T>> versions, OrmQueryRequest<T> request) {
BeanDescriptor<T> descriptor = request.getBeanDescriptor();
if (!versions.isEmpty()) {
Version<T> current = versions.get(0);
if (versions.size() > 1) {
for (int i = 1; i < versions.size(); i++) {
Version<T> next = versions.get(i);
deriveVersionDiff(current, next, descriptor);
current = next;
}
}
current.setDiff(new LinkedHashMap<>());
}
}
private <T> void deriveVersionDiff(Version<T> current, Version<T> prior, BeanDescriptor<T> descriptor) {
Map<String, ValuePair> diff = DiffHelp.diff(current.getBean(), prior.getBean(), descriptor);
current.setDiff(diff);
}
private <T> String getSysPeriodLower(SpiQuery<T> query) {
return historySupport.getSysPeriodLower(query.getAlias(T0));
}
private <T> String getSysPeriodUpper(SpiQuery<T> query) {
return historySupport.getSysPeriodUpper(query.getAlias(T0));
}
public <T> SpiResultSet findResultSet(OrmQueryRequest<T> request) {
CQuery<T> cquery = queryBuilder.buildQuery(request);
try {
boolean fwdOnly;
if (request.isFindIterate()) {
fwdOnly = forwardOnlyHintOnFindIterate;
if (defaultFetchSizeFindEach > 0) {
request.setDefaultFetchBuffer(defaultFetchSizeFindEach);
}
} else {
fwdOnly = false;
if (defaultFetchSizeFindList > 0) {
request.setDefaultFetchBuffer(defaultFetchSizeFindList);
}
}
ResultSet resultSet = cquery.prepareResultSet(fwdOnly);
if (request.isLogSql()) {
logSql(cquery);
}
return new SpiResultSet(cquery.getPstmt(), resultSet);
} catch (SQLException e) {
JdbcClose.close(cquery.getPstmt());
throw cquery.createPersistenceException(e);
}
}
<T> BeanCollection<T> findMany(OrmQueryRequest<T> request) {
CQuery<T> cquery = queryBuilder.buildQuery(request);
request.setCancelableQuery(cquery);
try {
if (defaultFetchSizeFindList > 0) {
request.setDefaultFetchBuffer(defaultFetchSizeFindList);
}
if (!cquery.prepareBindExecuteQuery()) {
logger.trace("Future fetch already cancelled");
return null;
}
if (request.isLogSql()) {
logSql(cquery);
}
BeanCollection<T> beanCollection = cquery.readCollection();
if (request.isLogSummary()) {
logFindManySummary(cquery);
}
if (request.isAuditReads()) {
cquery.auditFindMany();
}
request.executeSecondaryQueries(false);
if (request.isQueryCachePut()) {
request.addDependentTables(cquery.getDependentTables());
}
return beanCollection;
} catch (SQLException e) {
throw cquery.createPersistenceException(e);
} finally {
if (cquery != null) {
cquery.close();
}
if (request.getQuery().isFutureFetch()) {
logger.debug("Future fetch completed!");
request.getTransaction().end();
}
}
}
@SuppressWarnings("unchecked")
public <T> T find(OrmQueryRequest<T> request) {
EntityBean bean = null;
CQuery<T> cquery = queryBuilder.buildQuery(request);
try {
cquery.prepareBindExecuteQuery();
if (request.isLogSql()) {
logSql(cquery);
}
if (cquery.readBean()) {
bean = cquery.next();
}
if (request.isLogSummary()) {
logFindBeanSummary(cquery);
}
if (request.isAuditReads()) {
cquery.auditFind(bean);
}
request.executeSecondaryQueries(false);
return (T) bean;
} catch (SQLException e) {
throw cquery.createPersistenceException(e);
} finally {
cquery.close();
}
}
private void logSql(CQuery<?> query) {
String sql = query.getGeneratedSql();
sql = Str.add(sql, "; --bind(", query.getBindLog(), ")");
query.getTransaction().logSql(sql);
}
private void logFindBeanSummary(CQuery<?> q) {
SpiQuery<?> query = q.getQueryRequest().getQuery();
String loadMode = query.getLoadMode();
String loadDesc = query.getLoadDescription();
String lazyLoadProp = query.getLazyLoadProperty();
ObjectGraphNode node = query.getParentNode();
String originKey;
if (node == null || node.getOriginQueryPoint() == null) {
originKey = null;
} else {
originKey = node.getOriginQueryPoint().getKey();
}
StringBuilder msg = new StringBuilder(200);
msg.append("FindBean ");
if (loadMode != null) {
msg.append("mode[").append(loadMode).append("] ");
}
msg.append("type[").append(q.getBeanName()).append("] ");
if (query.isAutoTuned()) {
msg.append("tuned[true] ");
}
if (query.isAsDraft()) {
msg.append(" draft[true] ");
}
if (originKey != null) {
msg.append("origin[").append(originKey).append("] ");
}
if (lazyLoadProp != null) {
msg.append("lazyLoadProp[").append(lazyLoadProp).append("] ");
}
if (loadDesc != null) {
msg.append("load[").append(loadDesc).append("] ");
}
msg.append("exeMicros[").append(q.getQueryExecutionTimeMicros());
msg.append("] rows[").append(q.getLoadedRowDetail());
msg.append("] bind[").append(q.getBindLog()).append("]");
q.getTransaction().logSummary(msg.toString());
}
private void logFindManySummary(CQuery<?> q) {
SpiQuery<?> query = q.getQueryRequest().getQuery();
String loadMode = query.getLoadMode();
String loadDesc = query.getLoadDescription();
String lazyLoadProp = query.getLazyLoadProperty();
ObjectGraphNode node = query.getParentNode();
String originKey;
if (node == null || node.getOriginQueryPoint() == null) {
originKey = null;
} else {
originKey = node.getOriginQueryPoint().getKey();
}
StringBuilder msg = new StringBuilder(200);
msg.append("FindMany ");
if (loadMode != null) {
msg.append("mode[").append(loadMode).append("] ");
}
msg.append("type[").append(q.getBeanName()).append("] ");
if (query.isAutoTuned()) {
msg.append("tuned[true] ");
}
if (query.isAsDraft()) {
msg.append(" draft[true] ");
}
if (originKey != null) {
msg.append("origin[").append(originKey).append("] ");
}
if (lazyLoadProp != null) {
msg.append("lazyLoadProp[").append(lazyLoadProp).append("] ");
}
if (loadDesc != null) {
msg.append("load[").append(loadDesc).append("] ");
}
msg.append("exeMicros[").append(q.getQueryExecutionTimeMicros());
msg.append("] rows[").append(q.getLoadedRowDetail());
msg.append("] predicates[").append(q.getLogWhereSql());
msg.append("] bind[").append(q.getBindLog()).append("]");
q.getTransaction().logSummary(msg.toString());
}
}