From efd47435128b5696b890372839c711842a0f1494 Mon Sep 17 00:00:00 2001 From: Etgar Perets Date: Mon, 3 Nov 2025 11:14:16 +0200 Subject: [PATCH] SGA-12672 added support for insert into value, previously only values was allowed --- src/ast/mod.rs | 5 ++-- src/ast/query.rs | 20 ++++++++++++++- src/ast/spans.rs | 1 + src/parser/mod.rs | 22 ++++++++++++---- tests/sqlparser_bigquery.rs | 3 +++ tests/sqlparser_common.rs | 1 + tests/sqlparser_databricks.rs | 1 + tests/sqlparser_mysql.rs | 47 +++++++++++++++++++++++++++++++++++ tests/sqlparser_postgres.rs | 3 +++ 9 files changed, 95 insertions(+), 8 deletions(-) diff --git a/src/ast/mod.rs b/src/ast/mod.rs index 176d36545..fc86af90d 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -92,8 +92,9 @@ pub use self::query::{ TableIndexType, TableSample, TableSampleBucket, TableSampleKind, TableSampleMethod, TableSampleModifier, TableSampleQuantity, TableSampleSeed, TableSampleSeedModifier, TableSampleUnit, TableVersion, TableWithJoins, Top, TopQuantity, UpdateTableFromKind, - ValueTableMode, Values, WildcardAdditionalOptions, With, WithFill, XmlNamespaceDefinition, - XmlPassingArgument, XmlPassingClause, XmlTableColumn, XmlTableColumnOption, + ValueTableMode, Values, ValuesKeyword, WildcardAdditionalOptions, With, WithFill, + XmlNamespaceDefinition, XmlPassingArgument, XmlPassingClause, XmlTableColumn, + XmlTableColumnOption, }; pub use self::trigger::{ diff --git a/src/ast/query.rs b/src/ast/query.rs index 599b013ab..adc0489fa 100644 --- a/src/ast/query.rs +++ b/src/ast/query.rs @@ -3135,12 +3135,30 @@ pub struct Values { /// Was there an explicit ROWs keyword (MySQL)? /// pub explicit_row: bool, + // MySql supports both VALUES and VALUE keywords. + // + pub keyword: ValuesKeyword, pub rows: Vec>, } +#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] +pub enum ValuesKeyword { + Values, + Value, +} + +impl fmt::Display for ValuesKeyword { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + ValuesKeyword::Values => write!(f, "VALUES"), + ValuesKeyword::Value => write!(f, "VALUE"), + } + } +} + impl fmt::Display for Values { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.write_str("VALUES")?; + write!(f, "{}", self.keyword)?; let prefix = if self.explicit_row { "ROW" } else { "" }; let mut delim = ""; for row in &self.rows { diff --git a/src/ast/spans.rs b/src/ast/spans.rs index 7d2a00095..f96442cdb 100644 --- a/src/ast/spans.rs +++ b/src/ast/spans.rs @@ -223,6 +223,7 @@ impl Spanned for Values { fn span(&self) -> Span { let Values { explicit_row: _, // bool, + keyword: _, rows, } = self; diff --git a/src/parser/mod.rs b/src/parser/mod.rs index b44171c7d..7f9578e60 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -39,6 +39,7 @@ use crate::ast::helpers::{ stmt_create_table::{CreateTableBuilder, CreateTableConfiguration}, }; use crate::ast::Statement::CreatePolicy; +use crate::ast::ValuesKeyword; use crate::ast::*; use crate::dialect::*; use crate::keywords::{Keyword, ALL_KEYWORDS}; @@ -12524,7 +12525,10 @@ impl<'a> Parser<'a> { SetExpr::Query(subquery) } else if self.parse_keyword(Keyword::VALUES) { let is_mysql = dialect_of!(self is MySqlDialect); - SetExpr::Values(self.parse_values(is_mysql)?) + SetExpr::Values(self.parse_values(is_mysql, ValuesKeyword::Values)?) + } else if self.parse_keyword(Keyword::VALUE) { + let is_mysql = dialect_of!(self is MySqlDialect); + SetExpr::Values(self.parse_values(is_mysql, ValuesKeyword::Value)?) } else if self.parse_keyword(Keyword::TABLE) { SetExpr::Table(Box::new(self.parse_as_table()?)) } else { @@ -13828,7 +13832,7 @@ impl<'a> Parser<'a> { // Snowflake and Databricks allow syntax like below: // SELECT * FROM VALUES (1, 'a'), (2, 'b') AS t (col1, col2) // where there are no parentheses around the VALUES clause. - let values = SetExpr::Values(self.parse_values(false)?); + let values = SetExpr::Values(self.parse_values(false, ValuesKeyword::Values)?); let alias = self.maybe_parse_table_alias()?; Ok(TableFactor::Derived { lateral: false, @@ -16495,7 +16499,11 @@ impl<'a> Parser<'a> { }) } - pub fn parse_values(&mut self, allow_empty: bool) -> Result { + pub fn parse_values( + &mut self, + allow_empty: bool, + keyword: ValuesKeyword, + ) -> Result { let mut explicit_row = false; let rows = self.parse_comma_separated(|parser| { @@ -16513,7 +16521,11 @@ impl<'a> Parser<'a> { Ok(exprs) } })?; - Ok(Values { explicit_row, rows }) + Ok(Values { + explicit_row, + rows, + keyword, + }) } pub fn parse_start_transaction(&mut self) -> Result { @@ -16928,7 +16940,7 @@ impl<'a> Parser<'a> { MergeInsertKind::Row } else { self.expect_keyword_is(Keyword::VALUES)?; - let values = self.parse_values(is_mysql)?; + let values = self.parse_values(is_mysql, ValuesKeyword::Values)?; MergeInsertKind::Values(values) }; MergeAction::Insert(MergeInsertExpr { columns, kind }) diff --git a/tests/sqlparser_bigquery.rs b/tests/sqlparser_bigquery.rs index 03a0ac813..84e0c17c8 100644 --- a/tests/sqlparser_bigquery.rs +++ b/tests/sqlparser_bigquery.rs @@ -1807,6 +1807,7 @@ fn parse_merge() { let insert_action = MergeAction::Insert(MergeInsertExpr { columns: vec![Ident::new("product"), Ident::new("quantity")], kind: MergeInsertKind::Values(Values { + keyword: ValuesKeyword::Values, explicit_row: false, rows: vec![vec![Expr::value(number("1")), Expr::value(number("2"))]], }), @@ -1951,6 +1952,7 @@ fn parse_merge() { action: MergeAction::Insert(MergeInsertExpr { columns: vec![Ident::new("a"), Ident::new("b"),], kind: MergeInsertKind::Values(Values { + keyword: ValuesKeyword::Values, explicit_row: false, rows: vec![vec![ Expr::value(number("1")), @@ -1965,6 +1967,7 @@ fn parse_merge() { action: MergeAction::Insert(MergeInsertExpr { columns: vec![], kind: MergeInsertKind::Values(Values { + keyword: ValuesKeyword::Values, explicit_row: false, rows: vec![vec![ Expr::value(number("1")), diff --git a/tests/sqlparser_common.rs b/tests/sqlparser_common.rs index 99b7ac3fa..8f2f9a12d 100644 --- a/tests/sqlparser_common.rs +++ b/tests/sqlparser_common.rs @@ -9908,6 +9908,7 @@ fn parse_merge() { action: MergeAction::Insert(MergeInsertExpr { columns: vec![Ident::new("A"), Ident::new("B"), Ident::new("C")], kind: MergeInsertKind::Values(Values { + keyword: ValuesKeyword::Values, explicit_row: false, rows: vec![vec![ Expr::CompoundIdentifier(vec![ diff --git a/tests/sqlparser_databricks.rs b/tests/sqlparser_databricks.rs index e01611b6f..5bd3cdf27 100644 --- a/tests/sqlparser_databricks.rs +++ b/tests/sqlparser_databricks.rs @@ -157,6 +157,7 @@ fn test_databricks_lambdas() { #[test] fn test_values_clause() { let values = Values { + keyword: ValuesKeyword::Values, explicit_row: false, rows: vec![ vec![ diff --git a/tests/sqlparser_mysql.rs b/tests/sqlparser_mysql.rs index e43df87ab..7145d95e6 100644 --- a/tests/sqlparser_mysql.rs +++ b/tests/sqlparser_mysql.rs @@ -1885,6 +1885,7 @@ fn parse_simple_insert() { Some(Box::new(Query { with: None, body: Box::new(SetExpr::Values(Values { + keyword: ValuesKeyword::Values, explicit_row: false, rows: vec![ vec![ @@ -1950,6 +1951,7 @@ fn parse_ignore_insert() { Some(Box::new(Query { with: None, body: Box::new(SetExpr::Values(Values { + keyword: ValuesKeyword::Values, explicit_row: false, rows: vec![vec![ Expr::Value( @@ -1999,6 +2001,7 @@ fn parse_priority_insert() { Some(Box::new(Query { with: None, body: Box::new(SetExpr::Values(Values { + keyword: ValuesKeyword::Values, explicit_row: false, rows: vec![vec![ Expr::Value( @@ -2045,6 +2048,7 @@ fn parse_priority_insert() { Some(Box::new(Query { with: None, body: Box::new(SetExpr::Values(Values { + keyword: ValuesKeyword::Values, explicit_row: false, rows: vec![vec![ Expr::Value( @@ -2097,6 +2101,7 @@ fn parse_insert_as() { Some(Box::new(Query { with: None, body: Box::new(SetExpr::Values(Values { + keyword: ValuesKeyword::Values, explicit_row: false, rows: vec![vec![Expr::Value( (Value::SingleQuotedString("2024-01-01".to_string())).with_empty_span() @@ -2156,6 +2161,7 @@ fn parse_insert_as() { Some(Box::new(Query { with: None, body: Box::new(SetExpr::Values(Values { + keyword: ValuesKeyword::Values, explicit_row: false, rows: vec![vec![ Expr::value(number("1")), @@ -2206,6 +2212,7 @@ fn parse_replace_insert() { Some(Box::new(Query { with: None, body: Box::new(SetExpr::Values(Values { + keyword: ValuesKeyword::Values, explicit_row: false, rows: vec![vec![ Expr::Value( @@ -2253,6 +2260,7 @@ fn parse_empty_row_insert() { Some(Box::new(Query { with: None, body: Box::new(SetExpr::Values(Values { + keyword: ValuesKeyword::Values, explicit_row: false, rows: vec![vec![], vec![]] })), @@ -2303,6 +2311,7 @@ fn parse_insert_with_on_duplicate_update() { Some(Box::new(Query { with: None, body: Box::new(SetExpr::Values(Values { + keyword: ValuesKeyword::Values, explicit_row: false, rows: vec![vec![ Expr::Value( @@ -4353,3 +4362,41 @@ fn test_create_index_options() { "CREATE INDEX idx_name ON t(c1, c2) USING BTREE LOCK = EXCLUSIVE ALGORITHM = DEFAULT", ); } + +#[test] +fn test_insert_into_value() { + let sql = r"INSERT INTO t (id, name) VALUE ('AAA', 'BBB')"; + match mysql_and_generic().verified_stmt(sql) { + Statement::Insert(Insert { source, .. }) => { + assert_eq!( + Some(Box::new(Query { + with: None, + body: Box::new(SetExpr::Values(Values { + keyword: ValuesKeyword::Value, + explicit_row: false, + rows: vec![vec![ + Expr::Value(ValueWithSpan { + value: Value::SingleQuotedString("AAA".to_string()), + span: Span::empty(), + }), + Expr::Value(ValueWithSpan { + value: Value::SingleQuotedString("BBB".to_string()), + span: Span::empty(), + }), + ]], + })), + order_by: None, + limit_clause: None, + fetch: None, + locks: vec![], + for_clause: None, + settings: None, + format_clause: None, + pipe_operators: vec![], + })), + source + ); + } + _ => unreachable!(), + } +} diff --git a/tests/sqlparser_postgres.rs b/tests/sqlparser_postgres.rs index bcc154287..2d0924d89 100644 --- a/tests/sqlparser_postgres.rs +++ b/tests/sqlparser_postgres.rs @@ -5171,6 +5171,7 @@ fn test_simple_postgres_insert_with_alias() { source: Some(Box::new(Query { with: None, body: Box::new(SetExpr::Values(Values { + keyword: ValuesKeyword::Values, explicit_row: false, rows: vec![vec![ Expr::Identifier(Ident::new("DEFAULT")), @@ -5240,6 +5241,7 @@ fn test_simple_postgres_insert_with_alias() { source: Some(Box::new(Query { with: None, body: Box::new(SetExpr::Values(Values { + keyword: ValuesKeyword::Values, explicit_row: false, rows: vec![vec![ Expr::Identifier(Ident::new("DEFAULT")), @@ -5311,6 +5313,7 @@ fn test_simple_insert_with_quoted_alias() { source: Some(Box::new(Query { with: None, body: Box::new(SetExpr::Values(Values { + keyword: ValuesKeyword::Values, explicit_row: false, rows: vec![vec![ Expr::Identifier(Ident::new("DEFAULT")),