This is an automated email from the ASF dual-hosted git repository.

github-bot pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/datafusion-sqlparser-rs.git


The following commit(s) were added to refs/heads/main by this push:
     new f69407b3 Add support for `INSERT INTO VALUE` (#2085)
f69407b3 is described below

commit f69407b344a6ca80266289c827bb5c23b202adfc
Author: etgarperets <[email protected]>
AuthorDate: Tue Nov 11 10:10:42 2025 +0200

    Add support for `INSERT INTO VALUE` (#2085)
---
 src/ast/query.rs              |  8 +++++++-
 src/ast/spans.rs              |  1 +
 src/parser/mod.rs             | 21 ++++++++++++++++-----
 tests/sqlparser_bigquery.rs   |  3 +++
 tests/sqlparser_common.rs     | 31 ++++++++++++++++++++++++-------
 tests/sqlparser_databricks.rs |  1 +
 tests/sqlparser_mysql.rs      |  9 +++++++++
 tests/sqlparser_postgres.rs   |  3 +++
 8 files changed, 64 insertions(+), 13 deletions(-)

diff --git a/src/ast/query.rs b/src/ast/query.rs
index 599b013a..33c92614 100644
--- a/src/ast/query.rs
+++ b/src/ast/query.rs
@@ -3135,12 +3135,18 @@ pub struct Values {
     /// Was there an explicit ROWs keyword (MySQL)?
     /// <https://dev.mysql.com/doc/refman/8.0/en/values.html>
     pub explicit_row: bool,
+    // MySql supports both VALUES and VALUE keywords.
+    // <https://dev.mysql.com/doc/refman/9.2/en/insert.html>
+    pub value_keyword: bool,
     pub rows: Vec<Vec<Expr>>,
 }
 
 impl fmt::Display for Values {
     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        f.write_str("VALUES")?;
+        match self.value_keyword {
+            true => f.write_str("VALUE")?,
+            false => f.write_str("VALUES")?,
+        };
         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 80244e69..719e261c 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,
+            value_keyword: _,
             rows,
         } = self;
 
diff --git a/src/parser/mod.rs b/src/parser/mod.rs
index 026f6249..9615343c 100644
--- a/src/parser/mod.rs
+++ b/src/parser/mod.rs
@@ -12533,7 +12533,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, false)?)
+        } else if self.parse_keyword(Keyword::VALUE) {
+            let is_mysql = dialect_of!(self is MySqlDialect);
+            SetExpr::Values(self.parse_values(is_mysql, true)?)
         } else if self.parse_keyword(Keyword::TABLE) {
             SetExpr::Table(Box::new(self.parse_as_table()?))
         } else {
@@ -13837,7 +13840,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, false)?);
             let alias = self.maybe_parse_table_alias()?;
             Ok(TableFactor::Derived {
                 lateral: false,
@@ -16504,7 +16507,11 @@ impl<'a> Parser<'a> {
         })
     }
 
-    pub fn parse_values(&mut self, allow_empty: bool) -> Result<Values, 
ParserError> {
+    pub fn parse_values(
+        &mut self,
+        allow_empty: bool,
+        value_keyword: bool,
+    ) -> Result<Values, ParserError> {
         let mut explicit_row = false;
 
         let rows = self.parse_comma_separated(|parser| {
@@ -16522,7 +16529,11 @@ impl<'a> Parser<'a> {
                 Ok(exprs)
             }
         })?;
-        Ok(Values { explicit_row, rows })
+        Ok(Values {
+            explicit_row,
+            rows,
+            value_keyword,
+        })
     }
 
     pub fn parse_start_transaction(&mut self) -> Result<Statement, 
ParserError> {
@@ -16937,7 +16948,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, false)?;
                         MergeInsertKind::Values(values)
                     };
                     MergeAction::Insert(MergeInsertExpr { columns, kind })
diff --git a/tests/sqlparser_bigquery.rs b/tests/sqlparser_bigquery.rs
index 03a0ac81..0ef1c4f0 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 {
+            value_keyword: false,
             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 {
+                                value_keyword: false,
                                 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 {
+                                value_keyword: false,
                                 explicit_row: false,
                                 rows: vec![vec![
                                     Expr::value(number("1")),
diff --git a/tests/sqlparser_common.rs b/tests/sqlparser_common.rs
index 9ea91c64..a235c392 100644
--- a/tests/sqlparser_common.rs
+++ b/tests/sqlparser_common.rs
@@ -106,19 +106,19 @@ fn parse_insert_values() {
     let rows2 = vec![row.clone(), row];
 
     let sql = "INSERT customer VALUES (1, 2, 3)";
-    check_one(sql, "customer", &[], &rows1);
+    check_one(sql, "customer", &[], &rows1, false);
 
     let sql = "INSERT INTO customer VALUES (1, 2, 3)";
-    check_one(sql, "customer", &[], &rows1);
+    check_one(sql, "customer", &[], &rows1, false);
 
     let sql = "INSERT INTO customer VALUES (1, 2, 3), (1, 2, 3)";
-    check_one(sql, "customer", &[], &rows2);
+    check_one(sql, "customer", &[], &rows2, false);
 
     let sql = "INSERT INTO public.customer VALUES (1, 2, 3)";
-    check_one(sql, "public.customer", &[], &rows1);
+    check_one(sql, "public.customer", &[], &rows1, false);
 
     let sql = "INSERT INTO db.public.customer VALUES (1, 2, 3)";
-    check_one(sql, "db.public.customer", &[], &rows1);
+    check_one(sql, "db.public.customer", &[], &rows1, false);
 
     let sql = "INSERT INTO public.customer (id, name, active) VALUES (1, 2, 
3)";
     check_one(
@@ -126,6 +126,16 @@ fn parse_insert_values() {
         "public.customer",
         &["id".to_string(), "name".to_string(), "active".to_string()],
         &rows1,
+        false,
+    );
+
+    let sql = r"INSERT INTO t (id, name, active) VALUE (1, 2, 3)";
+    check_one(
+        sql,
+        "t",
+        &["id".to_string(), "name".to_string(), "active".to_string()],
+        &rows1,
+        true,
     );
 
     fn check_one(
@@ -133,6 +143,7 @@ fn parse_insert_values() {
         expected_table_name: &str,
         expected_columns: &[String],
         expected_rows: &[Vec<Expr>],
+        expected_value_keyword: bool,
     ) {
         match verified_stmt(sql) {
             Statement::Insert(Insert {
@@ -147,8 +158,13 @@ fn parse_insert_values() {
                     assert_eq!(column, 
&Ident::new(expected_columns[index].clone()));
                 }
                 match *source.body {
-                    SetExpr::Values(Values { rows, .. }) => {
-                        assert_eq!(rows.as_slice(), expected_rows)
+                    SetExpr::Values(Values {
+                        rows,
+                        value_keyword,
+                        ..
+                    }) => {
+                        assert_eq!(rows.as_slice(), expected_rows);
+                        assert!(value_keyword == expected_value_keyword);
                     }
                     _ => unreachable!(),
                 }
@@ -9908,6 +9924,7 @@ fn parse_merge() {
                         action: MergeAction::Insert(MergeInsertExpr {
                             columns: vec![Ident::new("A"), Ident::new("B"), 
Ident::new("C")],
                             kind: MergeInsertKind::Values(Values {
+                                value_keyword: false,
                                 explicit_row: false,
                                 rows: vec![vec![
                                     Expr::CompoundIdentifier(vec![
diff --git a/tests/sqlparser_databricks.rs b/tests/sqlparser_databricks.rs
index 92b63533..065e8f9e 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 {
+        value_keyword: false,
         explicit_row: false,
         rows: vec![
             vec![
diff --git a/tests/sqlparser_mysql.rs b/tests/sqlparser_mysql.rs
index 86c1013c..b31a5b7c 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 {
+                        value_keyword: false,
                         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 {
+                        value_keyword: false,
                         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 {
+                        value_keyword: false,
                         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 {
+                        value_keyword: false,
                         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 {
+                        value_keyword: false,
                         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 {
+                        value_keyword: false,
                         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 {
+                        value_keyword: false,
                         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 {
+                        value_keyword: false,
                         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 {
+                        value_keyword: false,
                         explicit_row: false,
                         rows: vec![vec![
                             Expr::Value(
diff --git a/tests/sqlparser_postgres.rs b/tests/sqlparser_postgres.rs
index 9ba0fb97..87cb43ed 100644
--- a/tests/sqlparser_postgres.rs
+++ b/tests/sqlparser_postgres.rs
@@ -5169,6 +5169,7 @@ fn test_simple_postgres_insert_with_alias() {
             source: Some(Box::new(Query {
                 with: None,
                 body: Box::new(SetExpr::Values(Values {
+                    value_keyword: false,
                     explicit_row: false,
                     rows: vec![vec![
                         Expr::Identifier(Ident::new("DEFAULT")),
@@ -5238,6 +5239,7 @@ fn test_simple_postgres_insert_with_alias() {
             source: Some(Box::new(Query {
                 with: None,
                 body: Box::new(SetExpr::Values(Values {
+                    value_keyword: false,
                     explicit_row: false,
                     rows: vec![vec![
                         Expr::Identifier(Ident::new("DEFAULT")),
@@ -5309,6 +5311,7 @@ fn test_simple_insert_with_quoted_alias() {
             source: Some(Box::new(Query {
                 with: None,
                 body: Box::new(SetExpr::Values(Values {
+                    value_keyword: false,
                     explicit_row: false,
                     rows: vec![vec![
                         Expr::Identifier(Ident::new("DEFAULT")),


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to