| /***************************************************************** |
| * 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]); |
| } |
| |
| } |