prestodb/presto · error · PrestoException

INVALID_FUNCTION_ARGUMENT

INVALID_FUNCTION_ARGUMENT

Error message

Percentile must be between 0 and 1

What it means

getValidPercentile parses the percentile fraction string and validates it lies in [0, 1] before scaling to a percentage for Pinot's PERCENTILEEST aggregation. A fraction outside that range cannot be converted to a valid percentile, so the connector throws PrestoException with INVALID_FUNCTION_ARGUMENT.

Source

Thrown at presto-pinot-toolkit/src/main/java/com/facebook/presto/pinot/query/PinotQueryGenerator.java:439

            //     rsd = 1.106 / sqrt(2^(log2m))
            // So:
            //     log2m = 2 * log(1.106 / rsd) / log(2)
            int log2m = (int) (2 * Math.log(1.106 / standardError) / Math.log(2));
            if (log2m < 1) {
                throw new PinotException(
                        PINOT_UNSUPPORTED_EXPRESSION,
                        Optional.empty(),
                        format("Cannot handle approx_distinct, the log2m generated from error is %d from input %s (function %s)", log2m, standardErrorString, aggregation));
            }
            return format("DISTINCTCOUNTHLL(%s, %d)", selection, log2m);
        }

        private int getValidPercentile(String fraction)
        {
            try {
                double percent = Double.parseDouble(fraction);
                if (percent < 0 || percent > 1) {
                    throw new PrestoException(INVALID_FUNCTION_ARGUMENT, "Percentile must be between 0 and 1");
                }
                percent = percent * 100.0;
                if (percent == Math.floor(percent)) {
                    return (int) percent;
                }
            }
            catch (NumberFormatException ne) {
                // Skip
            }
            return -1;
        }

        @Override
        public PinotQueryGeneratorContext visitAggregation(AggregationNode node, PinotQueryGeneratorContext contextIn)
        {
            List<AggregationColumnNode> aggregationColumnNodes = computeAggregationNodes(node);

            // Make two passes over the aggregationColumnNodes: In the first pass identify all the variables that will be used

View on GitHub (pinned to 55bb57d202)

Solutions

  1. Pass the percentile as a fraction between 0 and 1, e.g. percentile(latency, 0.95) instead of percentile(latency, 95).
  2. Normalize programmatic inputs before building the query: if the value > 1, divide by 100.
  3. If the value should legitimately exceed 1, it is not a percentile; use a different aggregation.

Example fix

// before
SELECT percentile(latency, 95) FROM metrics;
// after
SELECT percentile(latency, 0.95) FROM metrics;
Defensive patterns

Strategy: validation

Validate before calling

double p = Double.parseDouble(percentileInput);
if (p < 0 || p > 1) {
    throw new IllegalArgumentException("Percentile must be between 0 and 1, got: " + p);
}

Prevention

When it happens

Trigger: Pushing a percentile aggregation (e.g. percentile(x, 1.5) or percentile(x, -0.1)) down to Pinot where the fraction string parses to a double outside 0..1.

Common situations: Hand-written SQL passing 0-100 style percentile values (e.g. 95 instead of 0.95); programmatic query builders formatting the percentile with the wrong scale; dashboards configured with 'p95' mapped to 95.

Related errors


AI-assisted analysis of prestodb/presto@55bb57d202 (2026-09-04). Data as JSON: /api/errors/cd530dd5b34d5057. Report an issue: GitHub.