class QGroup extends QOperator {

    private final ValueHashMap<HashMap<Expression, Object>> groups;

    QGroup(Select select) {
        super(select);
        select.currentGroup = null;
        groups = ValueHashMap.newInstance();
    }

    public ValueHashMap<HashMap<Expression, Object>> getGroups() {
        return groups;
    }

    @Override
    public void run() {
        while (next()) {
            boolean yield = yieldIfNeeded(++loopCount);
            if (conditionEvaluator.getBooleanValue()) {
                if (select.isForUpdate && !tryLockRow()) {
                    return; // 锁记录失败
                }
                rowCount++;
                Value key = getKey(select);
                select.currentGroup = getOrCreateGroup(groups, key);
                select.currentGroupRowId++;
                updateAggregate(select, columnCount);
                if (sampleSize > 0 && rowCount >= sampleSize) {
                    break;
                }
            }
            if (yield)
                return;
        }
        // 把分组后的记录放到result中
        addGroupRows(groups, select, columnCount, result);
        loopEnd = true;
    }

    public static Value getKey(Select select) {
        // 避免在ExpressionColumn.getValue中取到旧值
        // 例如SELECT id/3 AS A, COUNT(*) FROM mytable GROUP BY A HAVING A>=0
        select.currentGroup = null;
        return ValueArray.get(getKeyValues(select));
    }

    // 分组key，包括一到多个字段
    public static Value[] getKeyValues(Select select) {
        Value[] keyValues = new Value[select.groupIndex.length];
        for (int i = 0; i < select.groupIndex.length; i++) {
            int idx = select.groupIndex[i];
            Expression expr = select.expressions.get(idx);
            keyValues[i] = expr.getValue(select.getSession());
        }
        return keyValues;
    }

    public static HashMap<Expression, Object> getOrCreateGroup(
            ValueHashMap<HashMap<Expression, Object>> groups, Value key) {
        HashMap<Expression, Object> values = groups.get(key);
        if (values == null) {
            values = new HashMap<>();
            groups.put(key, values);
        }
        return values;
    }

    public static void addGroupRows(ValueHashMap<HashMap<Expression, Object>> groups, Select select,
            int columnCount, ResultTarget result) {
        for (Value v : groups.keys()) {
            ValueArray key = (ValueArray) v;
            select.currentGroup = groups.get(key);
            Value[] keyValues = key.getList();
            addGroupRow(select, keyValues, columnCount, result);
        }
    }

    public static void addGroupRow(Select select, Value[] keyValues, int columnCount,
            ResultTarget result) {<FILL_FUNCTION_BODY>}

    private static boolean isHavingNullOrFalse(Value[] row, int havingIndex) {
        if (havingIndex >= 0) {
            Value v = row[havingIndex];
            if (v == ValueNull.INSTANCE)
                return true;
            return !v.getBoolean();
        }
        return false;
    }

    // 不包含having和group by中加入的列
    public static Value[] toResultRow(Value[] row, int columnCount, int resultColumnCount) {
        if (columnCount == resultColumnCount) {
            return row;
        }
        return Arrays.copyOf(row, resultColumnCount);
    }

    static void updateAggregate(Select select, int columnCount) {
        for (int i = 0; i < columnCount; i++) {
            if (select.groupByExpression == null || !select.groupByExpression[i]) {
                Expression expr = select.expressions.get(i);
                expr.updateAggregate(select.getSession());
            }
        }
    }
}

        Value[] row = new Value[columnCount];
        for (int i = 0; select.groupIndex != null && i < select.groupIndex.length; i++) {
            row[select.groupIndex[i]] = keyValues[i];
        }
        for (int i = 0; i < columnCount; i++) {
            if (select.groupByExpression != null && select.groupByExpression[i]) {
                continue;
            }
            Expression expr = select.expressions.get(i);
            row[i] = expr.getValue(select.getSession());
        }
        if (isHavingNullOrFalse(row, select.havingIndex)) {
            return;
        }
        row = toResultRow(row, columnCount, select.resultColumnCount);
        result.addRow(row);
    
<methods>public void <init>(com.lealone.sql.query.Select) ,public boolean canBreakLoop() ,public void copyStatusTo(com.lealone.sql.query.QOperator) ,public com.lealone.sql.expression.evaluator.ExpressionEvaluator createConditionEvaluator(com.lealone.sql.expression.Expression) ,public com.lealone.db.value.Value[] createRow() ,public com.lealone.db.result.LocalResult getLocalResult() ,public boolean isStopped() ,public void onLockedException() ,public void run() ,public void start() ,public void stop() ,public boolean yieldIfNeeded(int) <variables>protected int columnCount,protected final non-sealed com.lealone.sql.expression.evaluator.ExpressionEvaluator conditionEvaluator,protected long limitRows,protected com.lealone.db.result.LocalResult localResult,protected int loopCount,protected boolean loopEnd,protected int maxRows,protected com.lealone.db.result.ResultTarget result,protected int rowCount,protected int sampleSize,protected final non-sealed com.lealone.sql.query.Select select,protected final non-sealed com.lealone.db.session.ServerSession session,protected final non-sealed com.lealone.sql.optimizer.TableIterator tableIterator,protected com.lealone.db.result.ResultTarget target,protected com.lealone.sql.query.YieldableSelect yieldableSelect