From aa8fc0d45d8e7826625913f45d9c9dc64dcfd234 Mon Sep 17 00:00:00 2001 From: Egor Vorontsov Date: Sat, 8 Nov 2025 02:08:56 +0300 Subject: [PATCH] Implemented NULL literal in SQL for Units and Options. --- crates/expr/src/expr.rs | 17 +++++++++++++++++ crates/expr/src/lib.rs | 4 ++++ crates/expr/src/statement.rs | 19 +++++++++++++++++++ crates/sql-parser/src/ast/mod.rs | 2 ++ crates/sql-parser/src/parser/mod.rs | 1 + 5 files changed, 43 insertions(+) diff --git a/crates/expr/src/expr.rs b/crates/expr/src/expr.rs index 92883df0cc9..5c9c262ef3b 100644 --- a/crates/expr/src/expr.rs +++ b/crates/expr/src/expr.rs @@ -351,6 +351,23 @@ impl Expr { } } + /// A literal unit value + pub fn unit() -> Self { + Self::Value(AlgebraicValue::unit(), AlgebraicType::unit()) + } + + /// A literal option none value + #[allow(non_snake_case)] + pub fn OptionNone(ty: AlgebraicType) -> Self { + Self::Value(AlgebraicValue::OptionNone(), AlgebraicType::option(ty)) + } + + /// A literal option none value + #[allow(non_snake_case)] + pub fn OptionSome(ty: AlgebraicType, v: AlgebraicValue) -> Self { + Self::Value(AlgebraicValue::OptionSome(v), AlgebraicType::option(ty)) + } + /// A literal boolean value pub const fn bool(v: bool) -> Self { Self::Value(AlgebraicValue::Bool(v), AlgebraicType::Bool) diff --git a/crates/expr/src/lib.rs b/crates/expr/src/lib.rs index 2d9b3cdc5ab..94b28fb6b5c 100644 --- a/crates/expr/src/lib.rs +++ b/crates/expr/src/lib.rs @@ -88,6 +88,10 @@ fn _type_expr(vars: &Relvars, expr: SqlExpr, expected: Option<&AlgebraicType>, d recursion::guard(depth, recursion::MAX_RECURSION_TYP_EXPR, "expr::type_expr")?; match (expr, expected) { + (SqlExpr::Lit(SqlLiteral::Null), None) => Ok(Expr::unit()), + (SqlExpr::Lit(SqlLiteral::Null), Some(ty)) if ty.is_unit() => Ok(Expr::unit()), + (SqlExpr::Lit(SqlLiteral::Null), Some(ty)) if ty.is_option() => Ok(Expr::OptionNone(ty.clone())), + (SqlExpr::Lit(SqlLiteral::Null), Some(ty)) => Err(UnexpectedType::new(&AlgebraicType::unit(), ty).into()), (SqlExpr::Lit(SqlLiteral::Bool(v)), None | Some(AlgebraicType::Bool)) => Ok(Expr::bool(v)), (SqlExpr::Lit(SqlLiteral::Bool(_)), Some(ty)) => Err(UnexpectedType::new(&AlgebraicType::Bool, ty).into()), (SqlExpr::Lit(SqlLiteral::Str(_) | SqlLiteral::Num(_) | SqlLiteral::Hex(_)), None) => { diff --git a/crates/expr/src/statement.rs b/crates/expr/src/statement.rs index 8a3aa3a8d70..378ca3de968 100644 --- a/crates/expr/src/statement.rs +++ b/crates/expr/src/statement.rs @@ -137,6 +137,15 @@ pub fn type_insert(insert: SqlInsert, tx: &impl SchemaView) -> TypingResult { + values.push(AlgebraicValue::unit()) + } + (SqlLiteral::Null, _) if ty.is_option() => { + values.push(AlgebraicValue::OptionNone()) + } + (SqlLiteral::Null, _) => { + return Err(UnexpectedType::new(ty, &AlgebraicType::unit()).into()); + } (SqlLiteral::Bool(v), AlgebraicType::Bool) => { values.push(AlgebraicValue::Bool(v)); } @@ -212,6 +221,15 @@ pub fn type_update(update: SqlUpdate, tx: &impl SchemaView) -> TypingResult { + values.push((*col_id, AlgebraicValue::unit())); + } + (SqlLiteral::Null, _) if ty.is_option() => { + values.push((*col_id, AlgebraicValue::OptionNone())); + } + (SqlLiteral::Null, _) => { + return Err(UnexpectedType::new(ty, &AlgebraicType::unit()).into()); + } (SqlLiteral::Bool(v), AlgebraicType::Bool) => { values.push((*col_id, AlgebraicValue::Bool(v))); } @@ -286,6 +304,7 @@ pub fn type_and_rewrite_set(set: SqlSet, tx: &impl SchemaView) -> TypingResult Err(UnexpectedType::new(&AlgebraicType::U64, &AlgebraicType::unit()).into()), SqlLiteral::Bool(_) => Err(UnexpectedType::new(&AlgebraicType::U64, &AlgebraicType::Bool).into()), SqlLiteral::Str(_) => Err(UnexpectedType::new(&AlgebraicType::U64, &AlgebraicType::String).into()), SqlLiteral::Hex(_) => Err(UnexpectedType::new(&AlgebraicType::U64, &AlgebraicType::bytes()).into()), diff --git a/crates/sql-parser/src/ast/mod.rs b/crates/sql-parser/src/ast/mod.rs index 776d4fc5006..50390f312be 100644 --- a/crates/sql-parser/src/ast/mod.rs +++ b/crates/sql-parser/src/ast/mod.rs @@ -199,6 +199,8 @@ impl From for SqlIdent { /// A SQL constant expression #[derive(Debug)] pub enum SqlLiteral { + /// Null literal + Null, /// A boolean constant Bool(bool), /// A hex value like 0xFF or x'FF' diff --git a/crates/sql-parser/src/parser/mod.rs b/crates/sql-parser/src/parser/mod.rs index 9e6e5642bda..aca60c0d604 100644 --- a/crates/sql-parser/src/parser/mod.rs +++ b/crates/sql-parser/src/parser/mod.rs @@ -291,6 +291,7 @@ pub(crate) fn parse_binop(op: BinaryOperator) -> SqlParseResult { /// Parse a literal expression pub(crate) fn parse_literal(value: Value) -> SqlParseResult { match value { + Value::Null => Ok(SqlLiteral::Null), Value::Boolean(v) => Ok(SqlLiteral::Bool(v)), Value::Number(v, _) => Ok(SqlLiteral::Num(v.into_boxed_str())), Value::SingleQuotedString(s) => Ok(SqlLiteral::Str(s.into_boxed_str())),