docs: @Nullable
    public TopNQuery toTopNQuery()
    {
      // Must have GROUP BY one column, ORDER BY zero or one column, limit less than maxTopNLimit, and no HAVING.
      final boolean topNOk = grouping != null
                             && grouping.getDimensions().size() == 1
                             && limitSpec != null
                             && (limitSpec.getColumns().size() <= 1
                                 && limitSpec.getLimit() <= plannerContext.getPlannerConfig().getMaxTopNLimit())
                             && grouping.getHavingFilter() == null;
  
      if (!topNOk) {
        return null;
      }
  
      final DimensionSpec dimensionSpec = Iterables.getOnlyElement(grouping.getDimensions()).toDimensionSpec();
      final OrderByColumnSpec limitColumn;
      if (limitSpec.getColumns().isEmpty()) {
        limitColumn = new OrderByColumnSpec(
            dimensionSpec.getOutputName(),
            OrderByColumnSpec.Direction.ASCENDING,
            Calcites.getStringComparatorForValueType(dimensionSpec.getOutputType())
        );
      } else {
        limitColumn = Iterables.getOnlyElement(limitSpec.getColumns());
      }
      final TopNMetricSpec topNMetricSpec;
  
      if (limitColumn.getDimension().equals(dimensionSpec.getOutputName())) {
        // DimensionTopNMetricSpec is exact; always return it even if allowApproximate is false.
        final DimensionTopNMetricSpec baseMetricSpec = new DimensionTopNMetricSpec(
            null,
            limitColumn.getDimensionComparator()
        );
        topNMetricSpec = limitColumn.getDirection() == OrderByColumnSpec.Direction.ASCENDING
                         ? baseMetricSpec
                         : new InvertedTopNMetricSpec(baseMetricSpec);
      } else if (plannerContext.getPlannerConfig().isUseApproximateTopN()) {
        // ORDER BY metric
        final NumericTopNMetricSpec baseMetricSpec = new NumericTopNMetricSpec(limitColumn.getDimension());
        topNMetricSpec = limitColumn.getDirection() == OrderByColumnSpec.Direction.ASCENDING
                         ? new InvertedTopNMetricSpec(baseMetricSpec)
                         : baseMetricSpec;
      } else {
        return null;
      }
  
      final Filtration filtration = Filtration.create(filter).optimize(sourceQuerySignature);
  
      final List<PostAggregator> postAggregators = new ArrayList<>(grouping.getPostAggregators());
      if (sortProject != null) {
        postAggregators.addAll(sortProject.getPostAggregators());
      }
  
      return new TopNQuery(
          dataSource,
          getVirtualColumns(true),
          dimensionSpec,
          topNMetricSpec,
          limitSpec.getLimit(),
          filtration.getQuerySegmentSpec(),
          filtration.getDimFilter(),
          Granularities.ALL,
          grouping.getAggregatorFactories(),
          postAggregators,
          ImmutableSortedMap.copyOf(plannerContext.getQueryContext())
      );
    }