Contain a caller-authored Query.where expression before it is
embedded in generated DuckDB SQL, returning the trimmed text on success.
Trust model
where is raw filter SQL — the GeoParquet/DuckDB lane has no value-escaping
equivalent for it, and the semantic (typed, parameterized) compiler in
query-planner/duckdb.ts is the path that removes the raw text entirely.
Until a caller migrates to that path, this validator draws the boundary:
Guaranteed by this function. The accepted text is a single expression
that cannot leave the WHERE ( … ) slot it is spliced into. It rejects, with
a typed GeoParquetWhereClauseError:
statement separators (;) and therefore chained/multi-statement input;
SQL comments — both line (--) and block comment markers — including the
trailing-comment trick that would otherwise swallow the compiler's own
AND (<spatial predicate>);
unterminated string / quoted-identifier literals, which is how x' OR 1=1
style probes escape a literal;
E'…' escape strings, where a backslash escapes a quote: that second
escaping grammar would let a literal end for this scanner but not for
DuckDB, so every boundary after it is mis-parsed. Plain literals with
doubled quotes carry the same data;
unbalanced parentheses, so the wrapping ( … ) cannot be closed early to
re-associate or append clauses;
SELECT / UNION and other statement or set-operation keywords, plus a
table-context FROM, so a filter cannot become a subquery or UNION probe
that reads other tables or files registered in the same DuckDB session.
FROM stays available in its expression forms — EXTRACT(YEAR FROM ts),
TRIM(BOTH ' ' FROM name), SUBSTRING(name FROM 2 FOR 3), OVERLAY(…);
parameter markers (?, $1, $name) — this lane binds no values;
control characters other than tab / newline / carriage return.
Still the caller's responsibility. This is containment, not a semantic
parser: accepted text is still executed by DuckDB. It can reference any
column in the scanned files, call any scalar function the session exposes,
and cost arbitrary CPU. Fabricating a syntactically invalid expression still
fails at DuckDB, not here. Applications that forward end-user input (a filter
box, a URL parameter) should build the expression from typed inputs rather
than concatenating strings, and treat this validator as a backstop.
The Source.protocol("geoparquet").sql(...) handle remains an explicit,
opt-in raw-SQL escape hatch and is deliberately not covered here.
Contain a caller-authored Query.where expression before it is embedded in generated DuckDB SQL, returning the trimmed text on success.
Trust model
whereis raw filter SQL — the GeoParquet/DuckDB lane has no value-escaping equivalent for it, and the semantic (typed, parameterized) compiler inquery-planner/duckdb.tsis the path that removes the raw text entirely. Until a caller migrates to that path, this validator draws the boundary:Guaranteed by this function. The accepted text is a single expression that cannot leave the
WHERE ( … )slot it is spliced into. It rejects, with a typed GeoParquetWhereClauseError:;) and therefore chained/multi-statement input;--) and block comment markers — including the trailing-comment trick that would otherwise swallow the compiler's ownAND (<spatial predicate>);x' OR 1=1style probes escape a literal;E'…'escape strings, where a backslash escapes a quote: that second escaping grammar would let a literal end for this scanner but not for DuckDB, so every boundary after it is mis-parsed. Plain literals with doubled quotes carry the same data;( … )cannot be closed early to re-associate or append clauses;SELECT/UNIONand other statement or set-operation keywords, plus a table-contextFROM, so a filter cannot become a subquery or UNION probe that reads other tables or files registered in the same DuckDB session.FROMstays available in its expression forms —EXTRACT(YEAR FROM ts),TRIM(BOTH ' ' FROM name),SUBSTRING(name FROM 2 FOR 3),OVERLAY(…);?,$1,$name) — this lane binds no values;Still the caller's responsibility. This is containment, not a semantic parser: accepted text is still executed by DuckDB. It can reference any column in the scanned files, call any scalar function the session exposes, and cost arbitrary CPU. Fabricating a syntactically invalid expression still fails at DuckDB, not here. Applications that forward end-user input (a filter box, a URL parameter) should build the expression from typed inputs rather than concatenating strings, and treat this validator as a backstop.
The
Source.protocol("geoparquet").sql(...)handle remains an explicit, opt-in raw-SQL escape hatch and is deliberately not covered here.