From 31113f8d6b3013fd01075546b98ae6d955d5f79f Mon Sep 17 00:00:00 2001 From: Mosha Pasumansky Date: Thu, 29 Oct 2026 14:04:33 -0700 Subject: [PATCH 1/2] Spark, Databricks: parse the `exists` higher-order function Spark, like Databricks, has a higher-order function `exists`, but SparkSqlDialect read `exists(` as the EXISTS predicate and failed on the array. A new dialect method, supports_exists_function, replaces the Databricks-only check; Spark and Databricks enable it. A query after the parenthesis keeps the predicate. That check knew only SELECT and WITH, so Databricks read `EXISTS (VALUES (1))` as a function call; TABLE, VALUES and FROM now start a query too. Co-authored-by: Claude Opus 5.5 --- src/dialect/databricks.rs | 5 +++++ src/dialect/mod.rs | 7 +++++++ src/dialect/spark.rs | 5 +++++ src/parser/mod.rs | 10 +++++++--- tests/sqlparser_common.rs | 38 ++++++++++++++++++++++++++++++++++++-- 5 files changed, 60 insertions(+), 5 deletions(-) diff --git a/src/dialect/databricks.rs b/src/dialect/databricks.rs index d660cec9eb..6f3b258781 100644 --- a/src/dialect/databricks.rs +++ b/src/dialect/databricks.rs @@ -90,6 +90,11 @@ impl Dialect for DatabricksDialect { true } + /// See + fn supports_exists_function(&self) -> bool { + true + } + // https://docs.databricks.com/en/sql/language-manual/sql-ref-syntax-qry-select.html#syntax fn supports_select_wildcard_except(&self) -> bool { true diff --git a/src/dialect/mod.rs b/src/dialect/mod.rs index 20fe597d54..a065a075a2 100644 --- a/src/dialect/mod.rs +++ b/src/dialect/mod.rs @@ -656,6 +656,13 @@ pub trait Dialect: Debug + Any { false } + /// Returns true if `exists(...)` is a call to a function named `exists`, + /// as in `exists(array(1, 2), x -> x > 1)`, unless a query follows the + /// parenthesis, as in `EXISTS (SELECT 1)`. + fn supports_exists_function(&self) -> bool { + false + } + /// Returns true if the dialect supports the `LAMBDA` keyword spelling of /// lambda functions, for example: /// diff --git a/src/dialect/spark.rs b/src/dialect/spark.rs index c3f369cfbf..70491abdb5 100644 --- a/src/dialect/spark.rs +++ b/src/dialect/spark.rs @@ -81,6 +81,11 @@ impl Dialect for SparkSqlDialect { true } + /// See + fn supports_exists_function(&self) -> bool { + true + } + /// See fn supports_select_wildcard_except(&self) -> bool { true diff --git a/src/parser/mod.rs b/src/parser/mod.rs index c1c489c70b..f4b78bed0b 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -1620,12 +1620,16 @@ impl<'a> Parser<'a> { Keyword::TRY_CAST => Ok(Some(self.parse_cast_expr(CastKind::TryCast)?)), Keyword::SAFE_CAST => Ok(Some(self.parse_cast_expr(CastKind::SafeCast)?)), Keyword::EXISTS - // Support parsing Databricks has a function named `exists`. - if !dialect_of!(self is DatabricksDialect) + // A query after the parenthesis is still the EXISTS predicate. + if !self.dialect.supports_exists_function() || matches!( self.peek_nth_token_ref(1).token, Token::Word(Word { - keyword: Keyword::SELECT | Keyword::WITH, + keyword: Keyword::SELECT + | Keyword::WITH + | Keyword::TABLE + | Keyword::VALUES + | Keyword::FROM, .. }) ) => diff --git a/tests/sqlparser_common.rs b/tests/sqlparser_common.rs index 379e37a660..6f64516bc3 100644 --- a/tests/sqlparser_common.rs +++ b/tests/sqlparser_common.rs @@ -9058,7 +9058,7 @@ fn parse_exists_subquery() { verified_stmt("SELECT * FROM t WHERE EXISTS (WITH u AS (SELECT 1) SELECT * FROM u)"); verified_stmt("SELECT EXISTS (SELECT 1)"); - let res = all_dialects_except(|d| d.is::()) + let res = all_dialects_except(|d| d.supports_exists_function()) .parse_sql_statements("SELECT EXISTS ("); assert_eq!( ParserError::ParserError( @@ -9067,7 +9067,7 @@ fn parse_exists_subquery() { res.unwrap_err(), ); - let res = all_dialects_except(|d| d.is::()) + let res = all_dialects_except(|d| d.supports_exists_function()) .parse_sql_statements("SELECT EXISTS (NULL)"); assert_eq!( ParserError::ParserError( @@ -9077,6 +9077,40 @@ fn parse_exists_subquery() { ); } +#[test] +fn parse_exists_function() { + let dialects = all_dialects_where(|d| d.supports_exists_function()); + match dialects.verified_expr("exists(array(1, 2), x -> x > 1)") { + Expr::Function(f) => assert_eq!(f.name.to_string(), "exists"), + other => panic!("expected a function, got {other:?}"), + } + for sql in [ + "EXISTS (SELECT 1)", + "EXISTS (WITH t AS (SELECT 1) SELECT * FROM t)", + "EXISTS (VALUES (1))", + "EXISTS (TABLE t)", + ] { + assert!( + matches!(dialects.verified_expr(sql), Expr::Exists { .. }), + "{sql}" + ); + } + let dialects = + all_dialects_where(|d| d.supports_exists_function() && d.supports_from_first_select()); + assert!(matches!( + dialects.verified_expr("EXISTS (FROM t SELECT a)"), + Expr::Exists { .. } + )); + + assert_eq!( + all_dialects_except(|d| d.supports_exists_function()) + .parse_sql_statements("SELECT exists(array(1, 2), x -> x > 1)"), + Err(ParserError::ParserError( + "Expected: SELECT, VALUES, or a subquery in the query body, found: array".to_string() + )) + ); +} + #[test] fn parse_create_database() { let sql = "CREATE DATABASE mydb"; From 263f4510f78be932ad2229e44ca4455c3a9a7ec6 Mon Sep 17 00:00:00 2001 From: Mosha Pasumansky Date: Thu, 29 Oct 2026 14:32:57 -0700 Subject: [PATCH 2/2] Add a fuzz seed for the exists function Co-authored-by: Claude Opus 5.5 --- fuzz/fuzz_seeds/spark_2772_exists_function.sql | 1 + 1 file changed, 1 insertion(+) create mode 100644 fuzz/fuzz_seeds/spark_2772_exists_function.sql diff --git a/fuzz/fuzz_seeds/spark_2772_exists_function.sql b/fuzz/fuzz_seeds/spark_2772_exists_function.sql new file mode 100644 index 0000000000..833e2f9c7b --- /dev/null +++ b/fuzz/fuzz_seeds/spark_2772_exists_function.sql @@ -0,0 +1 @@ +SELECT exists(array(1, 2), x -> x > 1), EXISTS (VALUES (1))