blob: fc84de6bd6d2972b8209b785a1def2201ac06781 [file] [log] [blame]
/*****************************************************************
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
****************************************************************/
package org.apache.cayenne.access.jdbc;
import org.apache.cayenne.DataRow;
import org.apache.cayenne.ResultIterator;
import org.apache.cayenne.access.DataNode;
import org.apache.cayenne.access.OperationObserver;
import org.apache.cayenne.access.jdbc.reader.RowReader;
import org.apache.cayenne.access.translator.DbAttributeBinding;
import org.apache.cayenne.access.translator.select.SelectTranslator;
import org.apache.cayenne.dba.DbAdapter;
import org.apache.cayenne.log.JdbcEventLogger;
import org.apache.cayenne.query.PrefetchProcessor;
import org.apache.cayenne.query.PrefetchTreeNode;
import org.apache.cayenne.query.QueryMetadata;
import org.apache.cayenne.query.SelectQuery;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
/**
* A SQLAction that handles SelectQuery execution.
*
* @since 1.2
*/
public class SelectAction extends BaseSQLAction {
private static void bind(DbAdapter adapter, PreparedStatement statement, DbAttributeBinding[] bindings)
throws SQLException, Exception {
for (DbAttributeBinding b : bindings) {
if (b.isExcluded()) {
continue;
}
// null DbAttributes are a result of inferior qualifier
// processing (qualifier can't map parameters to DbAttributes
// and therefore only supports standard java types now) hence, a
// special moronic case here:
if (b.getAttribute() == null) {
statement.setObject(b.getStatementPosition(), b.getValue());
} else {
adapter.bindParameter(statement, b);
}
}
}
protected SelectQuery<?> query;
protected QueryMetadata queryMetadata;
/**
* @since 4.0
*/
public SelectAction(SelectQuery<?> query, DataNode dataNode) {
super(dataNode);
this.query = query;
this.queryMetadata = query.getMetaData(dataNode.getEntityResolver());
}
@SuppressWarnings({ "unchecked", "rawtypes", "resource" })
@Override
public void performAction(Connection connection, OperationObserver observer) throws SQLException, Exception {
final long t1 = System.currentTimeMillis();
JdbcEventLogger logger = dataNode.getJdbcEventLogger();
SelectTranslator translator = dataNode.selectTranslator(query);
final String sql = translator.getSql();
DbAttributeBinding[] bindings = translator.getBindings();
PreparedStatement statement = connection.prepareStatement(sql);
bind(dataNode.getAdapter(), statement, bindings);
int fetchSize = queryMetadata.getStatementFetchSize();
if (fetchSize != 0) {
statement.setFetchSize(fetchSize);
}
logger.logQuery(sql, bindings, System.currentTimeMillis() - t1);
ResultSet rs;
// need to run in try-catch block to close statement properly if
// exception happens
try {
rs = statement.executeQuery();
} catch (Exception ex) {
statement.close();
throw ex;
}
RowDescriptor descriptor = new RowDescriptorBuilder().setColumns(translator.getResultColumns()).getDescriptor(
dataNode.getAdapter().getExtendedTypes());
RowReader<?> rowReader = dataNode.rowReader(descriptor, queryMetadata, translator.getAttributeOverrides());
ResultIterator it = new JDBCResultIterator(statement, rs, rowReader);
it = forIteratedResult(it, observer, connection, t1, sql);
it = forSuppressedDistinct(it, translator);
it = forFetchLimit(it, translator);
// TODO: Should do something about closing ResultSet and
// PreparedStatement in this method, instead of relying on
// DefaultResultIterator to do that later
if (observer.isIteratedResult()) {
try {
observer.nextRows(query, it);
} catch (Exception ex) {
it.close();
throw ex;
}
} else {
List<DataRow> resultRows;
try {
resultRows = it.allRows();
} finally {
it.close();
}
dataNode.getJdbcEventLogger().logSelectCount(resultRows.size(), System.currentTimeMillis() - t1, sql);
observer.nextRows(query, resultRows);
}
}
private <T> ResultIterator<T> forIteratedResult(ResultIterator<T> iterator, OperationObserver observer,
Connection connection, final long queryStartedAt, final String sql) {
if (!observer.isIteratedResult()) {
return iterator;
}
return new ConnectionAwareResultIterator<T>(iterator, connection) {
@Override
protected void doClose() {
dataNode.getJdbcEventLogger().logSelectCount(rowCounter, System.currentTimeMillis() - queryStartedAt,
sql);
super.doClose();
}
};
}
private <T> ResultIterator<T> forFetchLimit(ResultIterator<T> iterator, SelectTranslator translator) {
// wrap iterator in a fetch limit checker ... there are a few cases when
// in-memory fetch limit is a noop, however in a general case this is
// needed, as the SQL result count does not directly correspond to the
// number of objects returned from Cayenne.
int fetchLimit = query.getFetchLimit();
int offset = translator.isSuppressingDistinct() ? query.getFetchOffset() : getInMemoryOffset(query
.getFetchOffset());
if (fetchLimit > 0 || offset > 0) {
return new LimitResultIterator<T>(iterator, offset, fetchLimit);
} else {
return iterator;
}
}
private <T> ResultIterator<T> forSuppressedDistinct(ResultIterator<T> iterator, SelectTranslator translator) {
if (!translator.isSuppressingDistinct()) {
return iterator;
}
// wrap result iterator if distinct has to be suppressed
// a joint prefetch warrants full row compare
final boolean[] compareFullRows = new boolean[1];
final PrefetchTreeNode rootPrefetch = queryMetadata.getPrefetchTree();
if (rootPrefetch != null) {
rootPrefetch.traverse(new PrefetchProcessor() {
@Override
public void finishPrefetch(PrefetchTreeNode node) {
}
@Override
public boolean startDisjointPrefetch(PrefetchTreeNode node) {
// continue to children only if we are at root
return rootPrefetch == node;
}
@Override
public boolean startDisjointByIdPrefetch(PrefetchTreeNode node) {
// continue to children only if we are at root
return rootPrefetch == node;
}
@Override
public boolean startUnknownPrefetch(PrefetchTreeNode node) {
// continue to children only if we are at root
return rootPrefetch == node;
}
@Override
public boolean startJointPrefetch(PrefetchTreeNode node) {
if (rootPrefetch != node) {
compareFullRows[0] = true;
return false;
}
return true;
}
@Override
public boolean startPhantomPrefetch(PrefetchTreeNode node) {
return true;
}
});
}
return new DistinctResultIterator<T>(iterator, queryMetadata.getDbEntity(), compareFullRows[0]);
}
}