public static Estimate calculateFilterFactor(StatisticRange range, ConnectorHistogram histogram, Estimate totalDistinctValues, boolean useHeuristics)
    {
        boolean openHigh = range.getOpenHigh();
        boolean openLow = range.getOpenLow();
        Estimate min = histogram.inverseCumulativeProbability(0.0);
        Estimate max = histogram.inverseCumulativeProbability(1.0);

        // range is either above or below histogram
        if ((!max.isUnknown() && (openHigh ? max.getValue() <= range.getLow() : max.getValue() < range.getLow()))
                || (!min.isUnknown() && (openLow ? min.getValue() >= range.getHigh() : min.getValue() > range.getHigh()))) {
            return Estimate.of(0.0);
        }

        // one of the max/min bounds can't be determined
        if ((max.isUnknown() && !min.isUnknown()) || (!max.isUnknown() && min.isUnknown())) {
            // when the range length is 0, the filter factor should be 1/distinct value count
            if (!useHeuristics) {
                return Estimate.unknown();
            }

            if (range.length() == 0.0) {
                return totalDistinctValues.map(distinct -> 1.0 / distinct);
            }

            if (isFinite(range.length())) {
                return Estimate.of(StatisticRange.INFINITE_TO_FINITE_RANGE_INTERSECT_OVERLAP_HEURISTIC_FACTOR);
            }
            return Estimate.of(StatisticRange.INFINITE_TO_INFINITE_RANGE_INTERSECT_OVERLAP_HEURISTIC_FACTOR);
        }

        // we know the bounds are both known, so calculate the percentile for each bound
        // The inclusivity arguments can be derived from the open-ness of the interval we're
        // calculating the filter factor for
        // e.g. given a variable with values in [0, 10] to calculate the filter of
        // [1, 9) (openness: false, true) we need the percentile from
        // [0.0 to 1.0) (openness: false, true) and from [0.0, 9.0) (openness: false, true)
        // thus for the "lowPercentile" calculation we should pass "false" to be non-inclusive
        // (same as openness) however, on the high-end we want the inclusivity to be the opposite
        // of the openness since if it's open, we _don't_ want to include the bound.
        Estimate lowPercentile = histogram.cumulativeProbability(range.getLow(), openLow);
        Estimate highPercentile = histogram.cumulativeProbability(range.getHigh(), !openHigh);

        // both bounds are probably infinity, use the infinite-infinite heuristic
        if (lowPercentile.isUnknown() || highPercentile.isUnknown()) {
            if (!useHeuristics) {
                return Estimate.unknown();
            }
            // in the case the histogram has no values
            if (totalDistinctValues.equals(Estimate.zero()) || range.getDistinctValuesCount() == 0.0) {
                return Estimate.of(0.0);
            }

            // in the case only one is unknown
            if (((lowPercentile.isUnknown() && !highPercentile.isUnknown()) ||
                    (!lowPercentile.isUnknown() && highPercentile.isUnknown())) &&
                    isFinite(range.length())) {
                return Estimate.of(StatisticRange.INFINITE_TO_FINITE_RANGE_INTERSECT_OVERLAP_HEURISTIC_FACTOR);
            }

            if (range.length() == 0.0) {
                return totalDistinctValues.map(distinct -> 1.0 / distinct);
            }

            if (!isNaN(range.getDistinctValuesCount())) {
                return totalDistinctValues.map(distinct -> min(1.0, range.getDistinctValuesCount() / distinct));
            }

            return Estimate.of(StatisticRange.INFINITE_TO_INFINITE_RANGE_INTERSECT_OVERLAP_HEURISTIC_FACTOR);
        }

        // in the case the range is a single value, this can occur if the input
        // filter range is a single value (low == high) OR in the case that the
        // bounds of the filter or this histogram are infinite.
        // in the case of infinite bounds, we should return an estimate that
        // correlates to the overlapping distinct values.
        if (lowPercentile.equals(highPercentile)) {
            if (!useHeuristics) {
                return Estimate.zero();
            }

            return totalDistinctValues.map(distinct -> 1.0 / distinct);
        }

        // in the case that we return the entire range, the returned factor percent should be
        // proportional to the number of distinct values in the range
        if (lowPercentile.equals(Estimate.zero()) && highPercentile.equals(Estimate.of(1.0)) && min.isUnknown() && max.isUnknown()) {
            if (!useHeuristics) {
                return Estimate.unknown();
            }

            return totalDistinctValues.flatMap(totalDistinct -> {
                if (DoubleMath.fuzzyEquals(totalDistinct, 0.0, 1E-6)) {
                    return Estimate.of(1.0);
                }
                return Estimate.of(min(1.0, range.getDistinctValuesCount() / totalDistinct));
            })
            // in the case totalDistinct is NaN or 0
            .or(() -> Estimate.of(StatisticRange.INFINITE_TO_INFINITE_RANGE_INTERSECT_OVERLAP_HEURISTIC_FACTOR));
        }

        return lowPercentile.flatMap(lowPercent -> highPercentile.map(highPercent -> highPercent - lowPercent));
    }
@Test
    public void testCalculateFilterFactor()
    {
        StatisticRange zeroToTen = range(0, 10, 10);
        StatisticRange empty = StatisticRange.empty();

        // Equal ranges
        assertFilterFactor(Estimate.of(1.0), zeroToTen, uniformHist(0, 10), 5);
        assertFilterFactor(Estimate.of(1.0), zeroToTen, uniformHist(0, 10), 20);

        // Some overlap
        assertFilterFactor(Estimate.of(0.5), range(5, 3000, 5), uniformHist(zeroToTen), zeroToTen.getDistinctValuesCount());

        // Single value overlap
        assertFilterFactor(Estimate.of(1.0 / zeroToTen.getDistinctValuesCount()), range(3, 3, 1), uniformHist(zeroToTen), zeroToTen.getDistinctValuesCount());
        assertFilterFactor(Estimate.of(1.0 / zeroToTen.getDistinctValuesCount()), range(10, 100, 357), uniformHist(zeroToTen), zeroToTen.getDistinctValuesCount());

        // No overlap
        assertFilterFactor(Estimate.zero(), range(20, 30, 10), uniformHist(zeroToTen), zeroToTen.getDistinctValuesCount());

        // Empty ranges
        assertFilterFactor(Estimate.zero(), zeroToTen, uniformHist(empty), empty.getDistinctValuesCount());
        assertFilterFactor(Estimate.zero(), empty, uniformHist(zeroToTen), zeroToTen.getDistinctValuesCount());

        // no test for (empty, empty) since any return value is correct
        assertFilterFactor(Estimate.zero(), unboundedRange(10), uniformHist(empty), empty.getDistinctValuesCount());
        assertFilterFactor(Estimate.zero(), empty, uniformHist(unboundedRange(10)), 10);

        // Unbounded (infinite), NDV-based
        assertFilterFactor(Estimate.of(0.5), unboundedRange(10), uniformHist(unboundedRange(20)), 20);
        assertFilterFactor(Estimate.of(1.0), unboundedRange(20), uniformHist(unboundedRange(10)), 10);

        // NEW TESTS (TPC-H Q2)
        // unbounded ranges
        assertFilterFactor(Estimate.of(.5), unboundedRange(0.5), uniformHist(unboundedRange(NaN)), NaN);
        // unbounded ranges with limited distinct values
        assertFilterFactor(Estimate.of(0.2), unboundedRange(1.0),
                domainConstrained(unboundedRange(5.0), uniformHist(unboundedRange(7.0))), 5.0);
    }