blob: d139335a31f8d881f08494c92db69fd1bf4aad02 [file] [log] [blame]
/*
Derby - Class org.apache.derby.impl.sql.compile.GroupByList
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.derby.impl.sql.compile;
import java.util.List;
import org.apache.derby.iapi.error.StandardException;
import org.apache.derby.iapi.reference.Limits;
import org.apache.derby.iapi.reference.SQLState;
import org.apache.derby.iapi.services.context.ContextManager;
import org.apache.derby.shared.common.sanity.SanityManager;
/**
* A GroupByList represents the list of expressions in a GROUP BY clause in
* a SELECT statement.
*
*/
class GroupByList extends OrderedColumnList<GroupByColumn>
{
int numGroupingColsAdded = 0;
boolean rollup = false;
public GroupByList(ContextManager cm) {
super(GroupByColumn.class, cm);
}
/**
Add a column to the list
@param column The column to add to the list
*/
void addGroupByColumn(GroupByColumn column)
{
addElement(column);
}
/**
Get a column from the list
@param position The column to get from the list
*/
GroupByColumn getGroupByColumn(int position)
{
if (SanityManager.DEBUG)
{
SanityManager.ASSERT(position >=0 && position < size(),
"position (" + position +
") expected to be between 0 and " + size());
}
return elementAt(position);
}
void setRollup()
{
rollup = true;
}
boolean isRollup()
{
return rollup;
}
/**
* Get the number of grouping columns that need to be added to the SELECT list.
*
* @return int The number of grouping columns that need to be added to
* the SELECT list.
*/
int getNumNeedToAddGroupingCols()
{
return numGroupingColsAdded;
}
/**
* Bind the group by list. Verify:
* o Number of grouping columns matches number of non-aggregates in
* SELECT's RCL.
* o Names in the group by list are unique
* o Names of grouping columns match names of non-aggregate
* expressions in SELECT's RCL.
*
* @param select The SelectNode
* @param aggregates The aggregate list being built as we find AggregateNodes
*
* @exception StandardException Thrown on error
*/
void bindGroupByColumns(SelectNode select, List<AggregateNode> aggregates)
throws StandardException
{
FromList fromList = select.getFromList();
ResultColumnList selectRCL = select.getResultColumns();
SubqueryList dummySubqueryList = new SubqueryList(getContextManager());
int numColsAddedHere = 0;
/* Only 32677 columns allowed in GROUP BY clause */
if (size() > Limits.DB2_MAX_ELEMENTS_IN_GROUP_BY)
{
throw StandardException.newException(SQLState.LANG_TOO_MANY_ELEMENTS);
}
/* Bind the grouping column */
for (GroupByColumn groupByCol : this)
{
groupByCol.bindExpression(fromList,
dummySubqueryList, aggregates);
}
int rclSize = selectRCL.size();
for (GroupByColumn groupingCol : this)
{
boolean matchFound = false;
/* Verify that this entry in the GROUP BY list matches a
* grouping column in the select list.
*/
for (int inner = 0; inner < rclSize; inner++)
{
ResultColumn selectListRC = selectRCL.elementAt(inner);
if (!(selectListRC.getExpression() instanceof ColumnReference)) {
continue;
}
ColumnReference selectListCR = (ColumnReference) selectListRC.getExpression();
if (selectListCR.isEquivalent(groupingCol.getColumnExpression())) {
/* Column positions for grouping columns are 0-based */
groupingCol.setColumnPosition(inner + 1);
/* Mark the RC in the SELECT list as a grouping column */
selectListRC.markAsGroupingColumn();
matchFound = true;
break;
}
}
/* If no match found in the SELECT list, then add a matching
* ResultColumn/ColumnReference pair to the SelectNode's RCL.
* However, don't add additional result columns if the query
* specified DISTINCT, because distinct processing considers
* the entire RCL and including extra columns could change the
* results: e.g. select distinct a,b from t group by a,b,c
* should not consider column c in distinct processing (DERBY-3613)
*/
if (! matchFound && !select.hasDistinct() &&
groupingCol.getColumnExpression() instanceof ColumnReference)
{
// only add matching columns for column references not
// expressions yet. See DERBY-883 for details.
ResultColumn newRC;
/* Get a new ResultColumn */
newRC = new ResultColumn(
groupingCol.getColumnName(),
groupingCol.getColumnExpression().getClone(),
getContextManager());
newRC.setVirtualColumnId(selectRCL.size() + 1);
newRC.markGenerated();
newRC.markAsGroupingColumn();
/* Add the new RC/CR to the RCL */
selectRCL.addElement(newRC);
/* Set the columnPosition in the GroupByColumn, now that it
* has a matching entry in the SELECT list.
*/
groupingCol.setColumnPosition(selectRCL.size());
// a new hidden or generated column is added to this RCL
// i.e. that the size() of the RCL != visibleSize().
// Error checking done later should be aware of this
// special case.
selectRCL.setCountMismatchAllowed(true);
/*
** Track the number of columns that we have added
** in this routine. We track this separately
** than the total number of columns added by this
** object (numGroupingColsAdded) because we
** might be bound (though not gagged) more than
** once (in which case numGroupingColsAdded will
** already be set).
*/
numColsAddedHere++;
}
if (groupingCol.getColumnExpression() instanceof JavaToSQLValueNode)
{
// disallow any expression which involves native java computation.
// Not possible to consider java expressions for equivalence.
throw StandardException.newException(
SQLState.LANG_INVALID_GROUPED_SELECT_LIST);
}
}
/* Verify that no subqueries got added to the dummy list */
if (dummySubqueryList.size() != 0) {
throw StandardException.newException(
SQLState.LANG_SUBQUERY_IN_GROUPBY_LIST);
}
numGroupingColsAdded+= numColsAddedHere;
}
/**
* Find the matching grouping column if any for the given expression
*
* @param node an expression for which we are trying to find a match
* in the group by list.
*
* @return the matching GroupByColumn if one exists, null otherwise.
*
* @throws StandardException
*/
GroupByColumn findGroupingColumn(ValueNode node)
throws StandardException
{
for (GroupByColumn gbc : this)
{
if (gbc.getColumnExpression().isEquivalent(node))
{
return gbc;
}
}
return null;
}
/**
* Remap all ColumnReferences in this tree to be clones of the
* underlying expression.
*
* @exception StandardException Thrown on error
*/
void remapColumnReferencesToExpressions() throws StandardException
{
/* This method is called when flattening a FromTable. */
for (GroupByColumn gbc : this)
{
gbc.setColumnExpression(
gbc.getColumnExpression().remapColumnReferencesToExpressions());
}
}
/**
* Convert this object to a String. See comments in QueryTreeNode.java
* for how this should be done for tree printing.
*
* @return This object as a String
*/
@Override
public String toString()
{
if (SanityManager.DEBUG) {
return "numGroupingColsAdded: " + numGroupingColsAdded + "\n" +
super.toString();
} else {
return "";
}
}
void preprocess(int numTables,
FromList fromList,
SubqueryList whereSubquerys,
PredicateList wherePredicates) throws StandardException
{
for (GroupByColumn gbc : this)
{
gbc.setColumnExpression(
gbc.getColumnExpression().preprocess(
numTables, fromList, whereSubquerys, wherePredicates));
}
}
}