From 0c2e93950907846aeded00cfcaa0f8675f133f10 Mon Sep 17 00:00:00 2001 From: Kevin Longe <273747162+kvlonge@users.noreply.github.com> Date: Sun, 15 Nov 2026 01:15:09 +0000 Subject: [PATCH] fix(syntax): accept function calls nested up to 256 deep The complexity guard refused function calls nested more than 64 deep, while Snowflake parses ABS, COALESCE and arithmetic nested 100 deep, so valid statements were refused. The default limit is now 256, the limit the shared parse layer already uses, and deeper nesting is still refused with the guard's function nesting error. The depth probe parses nested calls on a 2 MiB thread in every native parser and profile, and a new analysed construct holds parsing and analysis of nested calls to the probe's time bound. The parser grows its stack on demand, so the caller's thread size, including a Windows main thread or the Python wheel's, does not bound the new depth; on wasm the parser depth guard still caps recursion. The ABS and COALESCE receipts this resolves leave the disagreement list. --- coverage/syntax/receipt-disagreements.toml | 2 -- .../src/parser/tests/test_duckdb_syntax.rs | 11 +++++++++-- crates/sqlquack-syntax/src/guard/constants.rs | 2 +- .../src/depth/_helpers/analysed.rs | 10 ++++++++++ 4 files changed, 20 insertions(+), 5 deletions(-) diff --git a/coverage/syntax/receipt-disagreements.toml b/coverage/syntax/receipt-disagreements.toml index 3bef4c070..284a30efd 100644 --- a/coverage/syntax/receipt-disagreements.toml +++ b/coverage/syntax/receipt-disagreements.toml @@ -136,7 +136,6 @@ other_receipts_contradicted = [ "sf.ddl.create-iceberg-table.table.kdeb38bc667b1", "sf.expressions.snowflake-db-classes.k9eccc2186fa2", "sf.functions.abs.k6169455e23b9", - "sf.functions.abs.k93f76764032f", "sf.functions.acos.k9277749f1514", "sf.functions.acosh.k2bfcf40e4874", "sf.functions.ai-summarize-agg.k96152ee79950", @@ -173,7 +172,6 @@ other_receipts_contradicted = [ "sf.functions.ceil.kadb89c6ce35e", "sf.functions.check-xml.check-xml.kebab6f35e2a9", "sf.functions.chr.ke91db843fd30", - "sf.functions.coalesce.k25882bf706ba", "sf.functions.collate.collate.syntax.kd9832a5c1f19", "sf.functions.cos.k5ffbc006170e", "sf.functions.cosh.k26b88d9dac0b", diff --git a/crates/sqlquack-syntax-snowflake/src/parser/tests/test_duckdb_syntax.rs b/crates/sqlquack-syntax-snowflake/src/parser/tests/test_duckdb_syntax.rs index 398c6e197..54e26898a 100644 --- a/crates/sqlquack-syntax-snowflake/src/parser/tests/test_duckdb_syntax.rs +++ b/crates/sqlquack-syntax-snowflake/src/parser/tests/test_duckdb_syntax.rs @@ -57,12 +57,19 @@ fn given_parser_depth_limits_when_parsing_then_deep_input_is_refused_by_its_guar expected_outcome_part: "Parse error", }, GuardedParseTestCase { - description: "the function nesting guard is independent", - sql: nested_calls(100, "abs"), + description: "the function nesting guard is independent: 257 nested calls are refused", + sql: nested_calls(257, "abs"), max_parser_depth: None, outcome: configured_outcome, expected_outcome_part: "E_GUARD_FUNCTION_NESTING_DEPTH_EXCEEDED", }, + GuardedParseTestCase { + description: "256 nested calls, deeper than the 100 Snowflake parses, are read", + sql: nested_calls(256, "abs"), + max_parser_depth: None, + outcome: configured_outcome, + expected_outcome_part: "parsed", + }, GuardedParseTestCase { description: "16 lambda wrappers consume parser depth", sql: wrapped_lambda(16), diff --git a/crates/sqlquack-syntax/src/guard/constants.rs b/crates/sqlquack-syntax/src/guard/constants.rs index a29a631db..8baf588d5 100644 --- a/crates/sqlquack-syntax/src/guard/constants.rs +++ b/crates/sqlquack-syntax/src/guard/constants.rs @@ -10,7 +10,7 @@ pub(crate) const DEFAULT_MAX_AST_DEPTH: usize = 512; pub(crate) const DEFAULT_MAX_PARENTHESES_DEPTH: usize = 512; -pub(crate) const DEFAULT_MAX_FUNCTION_CALL_DEPTH: usize = 64; +pub(crate) const DEFAULT_MAX_FUNCTION_CALL_DEPTH: usize = 256; /// Deepest type nesting, every `[]`, `ARRAY<`, `ARRAY(`, `OBJECT(` or `MAP(` level counted as one. pub const MAX_DATA_TYPE_DEPTH: usize = 512; diff --git a/tools/sqlquack-coverage-check/src/depth/_helpers/analysed.rs b/tools/sqlquack-coverage-check/src/depth/_helpers/analysed.rs index d03293da5..ae5d7cb2d 100644 --- a/tools/sqlquack-coverage-check/src/depth/_helpers/analysed.rs +++ b/tools/sqlquack-coverage-check/src/depth/_helpers/analysed.rs @@ -198,6 +198,12 @@ fn nested_arithmetic(depth: usize) -> String { format!("SELECT {value} AS x FROM orders") } +fn nested_calls(depth: usize) -> String { + let value: String = + (0..depth).fold("id".to_owned(), |value, _| format!("COALESCE({value}, 0)")); + format!("SELECT {value} AS x FROM orders") +} + fn scalar_subqueries(depth: usize) -> String { let value: String = (0..depth).fold("id".to_owned(), |value, _| { format!("(SELECT {value} FROM orders AS o LIMIT 1)") @@ -243,6 +249,10 @@ pub(crate) fn analysed_constructs() -> Vec { name: "analysed_arithmetic", text: |parser, depth| analysed(parser, depth, nested_arithmetic), }, + Construct { + name: "analysed_function_calls", + text: |parser, depth| analysed(parser, depth, nested_calls), + }, Construct { name: "analysed_scalar_subqueries", text: |parser, depth| analysed(parser, depth, scalar_subqueries),