class BuiltInAggregate extends Aggregate {

    protected final int type;
    protected final boolean distinct;

    protected Expression on;
    protected int scale;
    protected long precision;
    protected int displaySize;

    /**
     * Create a new aggregate object.
     *
     * @param type the aggregate type
     * @param on the aggregated expression
     * @param select the select statement
     * @param distinct if distinct is used
     */
    public BuiltInAggregate(int type, Expression on, Select select, boolean distinct) {
        super(select);
        this.type = type;
        this.on = on;
        this.distinct = distinct;
    }

    public int getAType() {
        return type;
    }

    @Override
    public Expression getOn() {
        return on;
    }

    @Override
    public int getScale() {
        return scale;
    }

    @Override
    public long getPrecision() {
        return precision;
    }

    @Override
    public int getDisplaySize() {
        return displaySize;
    }

    @Override
    public int getCost() {
        return (on == null) ? 1 : on.getCost() + 1;
    }

    @Override
    public Expression optimize(ServerSession session) {
        if (on != null) {
            on = on.optimize(session);
            dataType = on.getType();
            scale = on.getScale();
            precision = on.getPrecision();
            displaySize = on.getDisplaySize();
        }
        return this;
    }

    protected abstract AggregateData createAggregateData();

    public AggregateData getAggregateData() {
        HashMap<Expression, Object> group = select.getCurrentGroup();
        if (group == null) {
            // this is a different level (the enclosing query)
            return null;
        }

        int groupRowId = select.getCurrentGroupRowId();
        if (lastGroupRowId == groupRowId) {
            // already visited
            return null;
        }
        lastGroupRowId = groupRowId;

        AggregateData data = (AggregateData) group.get(this);
        if (data == null) {
            data = createAggregateData();
            group.put(this, data);
        }
        return data;
    }

    @Override
    public void updateAggregate(ServerSession session) {
        AggregateData data = getAggregateData();
        if (data == null) {
            return;
        }
        Value v = on == null ? null : on.getValue(session);
        data.add(session, v);
    }

    @Override
    public Value getValue(ServerSession session) {
        if (select.isQuickAggregateQuery()) {
            switch (type) {
            case COUNT:
            case COUNT_ALL:
                Table table = select.getTopTableFilter().getTable();
                return ValueLong.get(table.getRowCount(session));
            case MIN:
            case MAX:
                boolean first = type == MIN;
                Index index = getColumnIndex();
                int sortType = index.getIndexColumns()[0].sortType;
                if ((sortType & SortOrder.DESCENDING) != 0) {
                    first = !first;
                }
                SearchRow row = index.findFirstOrLast(session, first);
                Value v;
                if (row == null) {
                    v = ValueNull.INSTANCE;
                } else {
                    v = row.getValue(index.getColumns()[0].getColumnId());
                }
                return v;
            default:
                DbException.throwInternalError("type=" + type);
            }
        }
        return getFinalAggregateData().getValue(session);
    }

    private AggregateData getFinalAggregateData() {
        HashMap<Expression, Object> group = select.getCurrentGroup();
        if (group == null) {
            throw DbException.get(ErrorCode.INVALID_USE_OF_AGGREGATE_FUNCTION_1, getSQL());
        }
        AggregateData data = (AggregateData) group.get(this);
        if (data == null) {
            data = createAggregateData();
        }
        return data;
    }

    private Index getColumnIndex() {
        if (on instanceof ExpressionColumn) {
            ExpressionColumn col = (ExpressionColumn) on;
            Column column = col.getColumn();
            TableFilter filter = col.getTableFilter();
            if (filter != null) {
                Table table = filter.getTable();
                Index index = table.getIndexForColumn(column);
                return index;
            }
        }
        return null;
    }

    public boolean isOptimizable(Table table) {
        switch (type) {
        case COUNT:
            if (!distinct && on.getNullable() == Column.NOT_NULLABLE) {
                return table.canGetRowCount();
            }
            return false;
        case COUNT_ALL:
            return table.canGetRowCount();
        case MIN:
        case MAX:
            Index index = getColumnIndex();
            return index != null;
        default:
            return false;
        }
    }

    static Value divide(Value a, long by) {
        if (by == 0) {
            return ValueNull.INSTANCE;
        }
        int type = Value.getHigherOrder(a.getType(), Value.LONG);
        Value b = ValueLong.get(by).convertTo(type);
        a = a.convertTo(type).divide(b);
        return a;
    }

    protected String getSQL(String text) {<FILL_FUNCTION_BODY>}

    @Override
    public <R> R accept(ExpressionVisitor<R> visitor) {
        return visitor.visitAggregate(this);
    }
}

        if (distinct) {
            return text + "(DISTINCT " + on.getSQL() + ")";
        }
        return text + StringUtils.enclose(on.getSQL());
    
<methods>public void <init>(com.lealone.sql.query.Select) ,public R accept(ExpressionVisitor<R>) ,public static com.lealone.sql.expression.aggregate.Aggregate create(int, com.lealone.sql.expression.Expression, com.lealone.sql.query.Select, boolean) ,public static int getAggregateType(java.lang.String) ,public com.lealone.sql.expression.Expression getOn() ,public int getType() <variables>private static final HashMap<java.lang.String,java.lang.Integer> AGGREGATES,public static final int AVG,public static final int BIT_AND,public static final int BIT_OR,public static final int BOOL_AND,public static final int BOOL_OR,public static final int COUNT,public static final int COUNT_ALL,public static final int GROUP_CONCAT,public static final int HISTOGRAM,public static final int MAX,public static final int MIN,public static final int SELECTIVITY,public static final int STDDEV_POP,public static final int STDDEV_SAMP,public static final int SUM,public static final int VAR_POP,public static final int VAR_SAMP,protected int dataType,protected int lastGroupRowId,protected final non-sealed com.lealone.sql.query.Select select