/**
* Copyright (C) 2009-2013 FoundationDB, LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.foundationdb.sql.optimizer.plan;
import com.foundationdb.server.types.TInstance;
import com.foundationdb.server.types.texpressions.TValidatedAggregator;
import java.util.List;
import java.util.ArrayList;
/** This node has three phases:<ul>
* <li>After parsing, it only knows the group by fields.</li>
* <li>From analysis of downstream references, aggregate expressions are filled in.</li>
* <li>After index select, but before maps are folded, a {@link Project}
* is split off with input expressions, which are then forgotten.</li></ul>
*/
public class AggregateSource extends BasePlanWithInput implements ColumnSource
{
public static enum Implementation {
PRESORTED, PREAGGREGATE_RESORT, SORT, HASH, TREE, UNGROUPED,
COUNT_STAR, COUNT_TABLE_STATUS, FIRST_FROM_INDEX
}
private boolean projectSplitOff;
private List<ExpressionNode> groupBy;
private List<AggregateFunctionExpression> aggregates;
private List<Object> options;
private int nGroupBy;
private List<String> aggregateFunctions;
private List<ResolvableExpression<TValidatedAggregator>> resolvedFunctions;
private TableSource table;
private Implementation implementation;
public AggregateSource(PlanNode input,
List<ExpressionNode> groupBy) {
super(input);
this.groupBy = groupBy;
nGroupBy = groupBy.size();
if (!hasGroupBy())
implementation = Implementation.UNGROUPED;
aggregates = new ArrayList<>();
options = new ArrayList<>();
}
public boolean isProjectSplitOff() {
return projectSplitOff;
}
public boolean hasGroupBy() {
return (nGroupBy > 0);
}
public List<ExpressionNode> getGroupBy() {
assert !projectSplitOff;
return groupBy;
}
public List<AggregateFunctionExpression> getAggregates() {
assert !projectSplitOff;
return aggregates;
}
/** Add a new grouping field and return its position. */
public int addGroupBy(ExpressionNode expr) {
assert !projectSplitOff;
int position = nGroupBy++;
assert (position == groupBy.size());
groupBy.add(expr);
return position;
}
/** Add a new aggregate and return its position. */
public int addAggregate(AggregateFunctionExpression aggregate) {
int position = groupBy.size() + aggregates.size();
aggregates.add(aggregate);
options.add(aggregate.getOption());
return position;
}
public boolean hasAggregate(AggregateFunctionExpression aggregate) {
if (aggregates.contains(aggregate))
return true;
return false;
}
public int getPosition(AggregateFunctionExpression aggregate) {
return groupBy.size() + aggregates.indexOf(aggregate);
}
public ExpressionNode getField(int position) {
assert !projectSplitOff;
if (position < nGroupBy)
return groupBy.get(position);
else
return aggregates.get(position - nGroupBy);
}
public int getNGroupBy() {
return nGroupBy;
}
public int getNAggregates() {
if (projectSplitOff)
return aggregateFunctions.size();
else
return aggregates.size();
}
public int getNFields() {
return getNGroupBy() + getNAggregates();
}
public List<String> getAggregateFunctions() {
assert projectSplitOff;
return aggregateFunctions;
}
public List<ResolvableExpression<TValidatedAggregator>> getResolved() {
assert projectSplitOff;
return resolvedFunctions;
}
public List<Object> getOptions()
{
assert projectSplitOff;
return options;
}
public TableSource getTable() {
assert (implementation == Implementation.COUNT_TABLE_STATUS);
return table;
}
public void setTable(TableSource table) {
assert (implementation == Implementation.COUNT_TABLE_STATUS);
this.table = table;
}
public Implementation getImplementation() {
return implementation;
}
public void setImplementation(Implementation implementation) {
this.implementation = implementation;
}
public String getName() {
return "GROUP"; // TODO: Something unique needed?
}
public List<ExpressionNode> splitOffProject() {
List<ExpressionNode> result = new ArrayList<>(groupBy);
nGroupBy = groupBy.size();
groupBy = null;
aggregateFunctions = new ArrayList<>(aggregates.size());
resolvedFunctions = new ArrayList<>(aggregates.size());
for (AggregateFunctionExpression aggregate : aggregates) {
String function = aggregate.getFunction();
ExpressionNode operand = aggregate.getOperand();
aggregate.setOperand(null);
if (operand == null) {
if ("COUNT".equals(function))
function = "COUNT(*)";
TInstance type = aggregate.getType(); // Some kind of BIGINT.
operand = new ConstantExpression(1L, type);
}
aggregateFunctions.add(function);
resolvedFunctions.add(aggregate);
result.add(operand);
}
aggregates = null;
projectSplitOff = true;
return result;
}
@Override
public boolean accept(PlanVisitor v) {
if (v.visitEnter(this)) {
if (getInput().accept(v) && !projectSplitOff) {
if (v instanceof ExpressionRewriteVisitor) {
for (int i = 0; i < groupBy.size(); i++) {
groupBy.set(i, groupBy.get(i).accept((ExpressionRewriteVisitor)v));
}
for (int i = 0; i < aggregates.size(); i++) {
aggregates.set(i, (AggregateFunctionExpression)aggregates.get(i).accept((ExpressionRewriteVisitor)v));
}
}
else if (v instanceof ExpressionVisitor) {
children:
{
for (ExpressionNode child : groupBy) {
if (!child.accept((ExpressionVisitor)v))
break children;
}
for (AggregateFunctionExpression child : aggregates) {
if (!child.accept((ExpressionVisitor)v))
break children;
}
}
}
}
}
return v.visitLeave(this);
}
@Override
public String summaryString(SummaryConfiguration configuration) {
StringBuilder str = new StringBuilder(super.summaryString(configuration));
str.append("(");
if (implementation != null) {
str.append(implementation);
str.append(",");
}
if (projectSplitOff) {
if (hasGroupBy()) {
str.append(nGroupBy);
str.append(",");
}
str.append(aggregateFunctions);
}
else {
if (hasGroupBy()) {
str.append(groupBy);
str.append(",");
}
str.append(aggregates);
}
str.append(")");
return str.toString();
}
@Override
protected boolean maintainInDuplicateMap() {
return true;
}
@Override
protected void deepCopy(DuplicateMap map) {
super.deepCopy(map);
if (!projectSplitOff) {
groupBy = duplicateList(groupBy, map);
aggregates = duplicateList(aggregates, map);
}
}
}