class DetailedQuery {

        private final Symbol query;
        private final DocKeys docKeys;
        private final List<List<Symbol>> partitions;
        private final Set<Symbol> clusteredByValues;
        private final boolean queryHasPkSymbolsOnly;

        DetailedQuery(Symbol query,
                      DocKeys docKeys,
                      List<List<Symbol>> partitionValues,
                      Set<Symbol> clusteredByValues,
                      boolean queryHasPkSymbolsOnly) {
            this.query = query;
            this.docKeys = docKeys;
            this.partitions = Objects.requireNonNullElse(partitionValues, Collections.emptyList());
            this.clusteredByValues = clusteredByValues;
            this.queryHasPkSymbolsOnly = queryHasPkSymbolsOnly;
        }

        public Optional<DocKeys> docKeys() {
            return Optional.ofNullable(docKeys);
        }

        /**
         * @return Symbols "pointing" to the values of any `partition_col = S` expressions:
         *         The outer list contains 1 entry per "equals pair" (e.g. `pcol = ? or pcol = ?` -> 2 entries
         *
         *         The inner list contains 1 entry per partitioned by column.
         *         The order matches the order of the partitioned by column definition.
         */
        public List<List<Symbol>> partitions() {
            return partitions;
        }

        public Symbol query() {
            return query;
        }

        public Set<Symbol> clusteredBy() {
            return clusteredByValues;
        }

        public WhereClause toBoundWhereClause(DocTableInfo table,
                                              Row params,
                                              SubQueryResults subQueryResults,
                                              CoordinatorTxnCtx txnCtx,
                                              NodeContext nodeCtx) {<FILL_FUNCTION_BODY>}

        public boolean queryHasPkSymbolsOnly() {
            return queryHasPkSymbolsOnly;
        }
    }

            SubQueryAndParamBinder binder = new SubQueryAndParamBinder(params, subQueryResults);
            Symbol boundQuery = binder.apply(query);
            HashSet<Symbol> clusteredBy = HashSet.newHashSet(clusteredByValues.size());
            for (Symbol clusteredByValue : clusteredByValues) {
                clusteredBy.add(binder.apply(clusteredByValue));
            }
            if (table.isPartitioned()) {
                if (table.partitions().isEmpty()) {
                    return WhereClause.NO_MATCH;
                }
                WhereClauseAnalyzer.PartitionResult partitionResult =
                    WhereClauseAnalyzer.resolvePartitions(boundQuery, table, txnCtx, nodeCtx);
                return new WhereClause(
                    partitionResult.query,
                    partitionResult.partitions,
                    clusteredBy
                );
            } else {
                return new WhereClause(
                    boundQuery,
                    Collections.emptyList(),
                    clusteredBy
                );
            }
        
<no_super_class>