package net.sourceforge.mayfly.evaluation.expression;
import net.sourceforge.mayfly.MayflyException;
import net.sourceforge.mayfly.MayflyInternalException;
import net.sourceforge.mayfly.datastore.Cell;
import net.sourceforge.mayfly.datastore.LongCell;
import net.sourceforge.mayfly.datastore.NullCell;
import net.sourceforge.mayfly.evaluation.Expression;
import net.sourceforge.mayfly.evaluation.ResultRow;
import net.sourceforge.mayfly.evaluation.ResultRows;
import net.sourceforge.mayfly.evaluation.select.Evaluator;
import net.sourceforge.mayfly.parser.Location;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
public abstract class AggregateExpression extends Expression {
protected final Expression column;
protected final String functionName;
protected final boolean distinct;
protected AggregateExpression(Expression column, String functionName,
boolean distinct,
Location location) {
super(location);
this.column = column;
this.functionName = functionName;
this.distinct = distinct;
}
/**
@internal
This is just for checking; aggregation happens in
{@link #aggregate(ResultRows)}.
It also is called in evaluating a HAVING condition,
although that part doesn't completely work yet. */
@Override
public Cell evaluate(ResultRow row, Evaluator evaluator) {
Cell cell = row.findValueOrNull(this);
return cell == null ? NullCell.INSTANCE : cell;
}
public Cell evaluateColumn(ResultRow row) {
return column.evaluate(row);
}
@Override
public String firstAggregate() {
return displayName();
}
@Override
public String displayName() {
return functionName + "(" + (distinct ? "distinct " : "") + column.displayName() + ")";
}
@Override
public Cell aggregate(ResultRows rows) {
Collection values = findValues(rows);
return aggregate(values);
}
abstract Cell aggregate(Collection values);
Cell aggregateSumAverage(Collection values, boolean isSum) {
if (values.isEmpty()) {
/* In this case of sum, this is lame (0 would be more convenient), but standard.
Is it possible/desirable for Mayfly to help?
(giving an error and pointing out a better way, or whatever). */
return NullCell.INSTANCE;
}
if (!(values.iterator().next() instanceof LongCell)) {
Cell first = (Cell) values.iterator().next();
throw new MayflyException("attempt to apply " + displayName() + " to " + first.displayName());
}
long count = 0;
long sum = 0;
for (Iterator iter = values.iterator(); iter.hasNext();) {
long value = ((LongCell) iter.next()).asLong();
count++;
sum += value;
}
return new LongCell(isSum ? sum : sum / count);
}
Cell aggregateMinMax(Collection values) {
Iterator iter = values.iterator();
if (!iter.hasNext()) {
return NullCell.INSTANCE;
}
Cell bestSoFar = (Cell) iter.next();
while (iter.hasNext()) {
Cell candidate = (Cell) iter.next();
if (isBetter(candidate, bestSoFar)) {
bestSoFar = candidate;
}
}
return bestSoFar;
}
boolean isBetter(Cell candidate, Cell bestSoFar) {
throw new MayflyInternalException("Override this for min/max");
}
private Collection findValues(ResultRows rows) {
Collection values;
if (distinct) {
values = new HashSet();
}
else {
values = new ArrayList();
}
for (Iterator iter = rows.iterator(); iter.hasNext();) {
ResultRow row = (ResultRow) iter.next();
Cell cell = evaluateColumn(row);
if (!(cell instanceof NullCell)) {
values.add(cell);
}
}
return values;
}
@Override
public boolean sameExpression(Expression other) {
if (getClass().equals(other.getClass())) {
AggregateExpression otherExpression = (AggregateExpression) other;
return column.sameExpression(otherExpression.column) &&
distinct == otherExpression.distinct;
}
else {
return false;
}
}
}