PRQL/prql · error · Error

parameter `rows`: expected a range, found

Error message

parameter `rows`: expected a range, found {found}

What it means

The `rows` parameter of the `window` transform expects a range (a tuple of two bounds, e.g. `(2..4)`) describing the frame in rows. The resolver runs `try_restrict_range` on the argument and throws this error when the argument cannot be interpreted as a range.

Solutions

  1. Pass a range tuple with both bounds, e.g. `rows:(-2..4)`
  2. Use `..` range syntax rather than brackets or commas
  3. For a symmetric frame use `rolling:` instead, or unbounded sides like `rows:(..4)`

Example fix

// before
window rows:[2,4] (sum amount)
// after
window rows:(2..4) (sum amount)
Defensive patterns

Strategy: validation

Validate before calling

const rows = "(-2..4)"; // must be a PRQL range tuple
if (!/^\(-?[\w]+\.\.-?[\w]+\)$/.test(rows)) {
  throw new Error("rows must be a range like (start..end)");
}

Type guard

const isRangeArg = (v) => typeof v === "object" && v !== null && "start" in v && "end" in v;

Try / catch

try { prql_compile(query); } catch (e) { if (e.message.includes("parameter `rows`")) { /* convert arg to a range tuple */ } else { throw e; } }

Prevention

When it happens

Trigger: Calling `window rows:<x>` where `<x>` is not a range tuple — e.g. `rows:3` (single number), `rows:[2,4]` (list instead of range), or a non-literal expression.

Common situations: Porting SQL `ROWS BETWEEN 2 PRECEDING AND 4 FOLLOWING` without converting to PRQL range syntax; passing a single bound; using array/tuple braces incorrectly.

Understand the failure class

Background: "Invalid ... format", "must be in format X", "does not look like a ..." — invalid argument format errors across CLI tools and libraries — this error's family across 17 libraries.

Related errors


AI-assisted analysis of PRQL/prql@e164e249b9 (2026-09-09). Data as JSON: /api/errors/75a9c563b1120747. Report an issue: GitHub.

Appendix: source

Thrown at prqlc/prqlc/src/semantic/resolver/transforms.rs:191

                    })?
                };

                let rolling = {
                    let as_int = rolling.kind.as_literal().and_then(|x| x.as_integer());

                    *as_int.ok_or_else(|| {
                        Error::new(Reason::Expected {
                            who: Some("parameter `rolling`".to_string()),
                            expected: "a number".to_string(),
                            found: write_pl(rolling.clone()),
                        })
                        .with_span(rolling.span)
                    })?
                };

                let rows = {
                    let range_tuple = try_restrict_range(rows).map_err(|expr| {
                        Error::new(Reason::Expected {
                            who: Some("parameter `rows`".to_string()),
                            expected: "a range".to_string(),
                            found: write_pl(expr.clone()),
                        })
                        .with_span(expr.span)
                    })?;
                    into_literal_range(range_tuple)?
                };

                let range = {
                    let range_tuple = try_restrict_range(range).map_err(|expr| {
                        Error::new(Reason::Expected {
                            who: Some("parameter `range`".to_string()),
                            expected: "a range".to_string(),
                            found: write_pl(expr.clone()),
                        })
                        .with_span(expr.span)
                    })?;

View on GitHub (pinned to e164e249b9)