class QueriedSelectRelation implements AnalyzedRelation {

    private final List<AnalyzedRelation> from;
    private final List<JoinPair> joinPairs;
    private final boolean isDistinct;
    private final List<Symbol> outputs;
    private final Symbol whereClause;
    private final List<Symbol> groupBy;
    @Nullable
    private final Symbol having;
    @Nullable
    private final OrderBy orderBy;
    @Nullable
    private final Symbol offset;
    @Nullable
    private final Symbol limit;

    public QueriedSelectRelation(boolean isDistinct,
                                 List<AnalyzedRelation> from,
                                 List<JoinPair> joinPairs,
                                 List<Symbol> outputs,
                                 Symbol whereClause,
                                 List<Symbol> groupBy,
                                 @Nullable Symbol having,
                                 @Nullable OrderBy orderBy,
                                 @Nullable Symbol limit,
                                 @Nullable Symbol offset) {
        this.outputs = outputs;
        this.whereClause = whereClause;
        this.groupBy = groupBy;
        this.having = having;
        this.orderBy = orderBy;
        this.offset = offset;
        this.limit = limit;
        assert from.size() >= 1 : "QueriedSelectRelation must have at least 1 relation in FROM";
        this.isDistinct = isDistinct;
        this.from = from;
        this.joinPairs = joinPairs;
    }

    public List<AnalyzedRelation> from() {
        return from;
    }

    public Symbol getField(ColumnIdent column, Operation operation, boolean errorOnUnknownObjectKey) throws AmbiguousColumnException, ColumnUnknownException, UnsupportedOperationException {
        Symbol match = null;
        for (Symbol output : outputs()) {
            ColumnIdent outputName = Symbols.pathFromSymbol(output);
            if (outputName.equals(column)) {
                if (match != null) {
                    throw new AmbiguousColumnException(column, output);
                }
                match = output;
            }
        }
        if (match != null || column.isRoot()) {
            return match;
        }
        ColumnIdent root = column.getRoot();

        // Try to optimize child-column access to use a Reference instead of a subscript function
        //
        // E.g.
        //
        //    SELECT obj['x'] FROM (select...)
        //
        // Should use Reference (obj.x) instead of Function subscript(obj, 'x')
        // Unless an alias shadows the sub-relation column:
        //
        //      SELECT obj['x'] FROM (SELECT unnest(obj) as obj FROM ...)
        //
        // -> Resolve both root field and child-field from source again.
        //    If the root field matches output -> it's not shadowed
        for (AnalyzedRelation source : from) {
            Symbol field = source.getField(column, operation, errorOnUnknownObjectKey);
            if (field != null) {
                if (match != null) {
                    throw new AmbiguousColumnException(column, match);
                }
                Symbol rootField = source.getField(root, operation, errorOnUnknownObjectKey);
                for (Symbol output : outputs()) {
                    Symbol symbol = output;
                    while (symbol instanceof AliasSymbol alias) {
                        symbol = alias.symbol();
                    }
                    if (symbol.equals(rootField) || output.equals(rootField)) {
                        match = field;
                        break;
                    }
                }
            }
        }
        return match;
    }

    public boolean isDistinct() {
        return isDistinct;
    }

    @Override
    public <C, R> R accept(AnalyzedRelationVisitor<C, R> visitor, C context) {
        return visitor.visitQueriedSelectRelation(this, context);
    }

    @Override
    public RelationName relationName() {
        throw new UnsupportedOperationException(
            "QueriedSelectRelation has no name. It must be beneath an aliased-relation to be addressable by name");
    }

    @NotNull
    @Override
    public List<Symbol> outputs() {
        return outputs;
    }

    public Symbol where() {
        return whereClause;
    }

    public List<Symbol> groupBy() {
        return groupBy;
    }

    @Nullable
    public Symbol having() {
        return having;
    }

    @Nullable
    public OrderBy orderBy() {
        return orderBy;
    }

    @Nullable
    public Symbol limit() {
        return limit;
    }

    @Nullable
    public Symbol offset() {
        return offset;
    }

    @Override
    public String toString() {
        return "SELECT "
               + Lists.joinOn(", ", outputs(), x -> Symbols.pathFromSymbol(x).sqlFqn())
               + " FROM ("
               + Lists.joinOn(", ", from, x -> x.relationName().toString())
               + ')';
    }

    @Override
    public void visitSymbols(Consumer<? super Symbol> consumer) {<FILL_FUNCTION_BODY>}

    public List<JoinPair> joinPairs() {
        return joinPairs;
    }
}

        for (Symbol output : outputs) {
            consumer.accept(output);
        }
        consumer.accept(whereClause);
        for (Symbol groupKey : groupBy) {
            consumer.accept(groupKey);
        }
        if (having != null) {
            consumer.accept(having);
        }
        if (orderBy != null) {
            orderBy.accept(consumer);
        }
        if (limit != null) {
            consumer.accept(limit);
        }
        if (offset != null) {
            consumer.accept(offset);
        }
        for (var joinPair : joinPairs) {
            if (joinPair.condition() != null) {
                consumer.accept(joinPair.condition());
            }
        }
    
<no_super_class>