mirror of
https://github.com/apache/datafusion-sqlparser-rs.git
synced 2025-10-09 21:42:05 +00:00
Merge pull request #91 from benesch/from-values
Improve VALUES-related syntax parsing
This commit is contained in:
commit
b8ba188191
4 changed files with 95 additions and 45 deletions
|
@ -20,10 +20,12 @@ mod sql_operator;
|
|||
mod sqltype;
|
||||
mod value;
|
||||
|
||||
use std::ops::Deref;
|
||||
|
||||
pub use self::ddl::{AlterTableOperation, TableConstraint};
|
||||
pub use self::query::{
|
||||
Cte, Fetch, Join, JoinConstraint, JoinOperator, SQLOrderByExpr, SQLQuery, SQLSelect,
|
||||
SQLSelectItem, SQLSetExpr, SQLSetOperator, TableFactor,
|
||||
SQLSelectItem, SQLSetExpr, SQLSetOperator, SQLValues, TableFactor,
|
||||
};
|
||||
pub use self::sqltype::SQLType;
|
||||
pub use self::value::Value;
|
||||
|
@ -31,9 +33,14 @@ pub use self::value::Value;
|
|||
pub use self::sql_operator::SQLOperator;
|
||||
|
||||
/// Like `vec.join(", ")`, but for any types implementing ToString.
|
||||
fn comma_separated_string<T: ToString>(vec: &[T]) -> String {
|
||||
vec.iter()
|
||||
.map(T::to_string)
|
||||
fn comma_separated_string<I>(iter: I) -> String
|
||||
where
|
||||
I: IntoIterator,
|
||||
I::Item: Deref,
|
||||
<I::Item as Deref>::Target: ToString,
|
||||
{
|
||||
iter.into_iter()
|
||||
.map(|t| t.deref().to_string())
|
||||
.collect::<Vec<String>>()
|
||||
.join(", ")
|
||||
}
|
||||
|
@ -339,8 +346,8 @@ pub enum SQLStatement {
|
|||
table_name: SQLObjectName,
|
||||
/// COLUMNS
|
||||
columns: Vec<SQLIdent>,
|
||||
/// VALUES (vector of rows to insert)
|
||||
values: Vec<Vec<ASTNode>>,
|
||||
/// A SQL query that specifies what to insert
|
||||
source: Box<SQLQuery>,
|
||||
},
|
||||
SQLCopy {
|
||||
/// TABLE
|
||||
|
@ -406,22 +413,13 @@ impl ToString for SQLStatement {
|
|||
SQLStatement::SQLInsert {
|
||||
table_name,
|
||||
columns,
|
||||
values,
|
||||
source,
|
||||
} => {
|
||||
let mut s = format!("INSERT INTO {}", table_name.to_string());
|
||||
let mut s = format!("INSERT INTO {} ", table_name.to_string());
|
||||
if !columns.is_empty() {
|
||||
s += &format!(" ({})", columns.join(", "));
|
||||
}
|
||||
if !values.is_empty() {
|
||||
s += &format!(
|
||||
" VALUES({})",
|
||||
values
|
||||
.iter()
|
||||
.map(|row| comma_separated_string(row))
|
||||
.collect::<Vec<String>>()
|
||||
.join(", ")
|
||||
);
|
||||
s += &format!("({}) ", columns.join(", "));
|
||||
}
|
||||
s += &source.to_string();
|
||||
s
|
||||
}
|
||||
SQLStatement::SQLCopy {
|
||||
|
@ -523,7 +521,7 @@ impl ToString for SQLStatement {
|
|||
"DROP {}{} {}{}",
|
||||
object_type.to_string(),
|
||||
if *if_exists { " IF EXISTS" } else { "" },
|
||||
comma_separated_string(&names),
|
||||
comma_separated_string(names),
|
||||
if *cascade { " CASCADE" } else { "" },
|
||||
),
|
||||
}
|
||||
|
|
|
@ -58,7 +58,8 @@ pub enum SQLSetExpr {
|
|||
left: Box<SQLSetExpr>,
|
||||
right: Box<SQLSetExpr>,
|
||||
},
|
||||
// TODO: ANSI SQL supports `TABLE` and `VALUES` here.
|
||||
Values(SQLValues),
|
||||
// TODO: ANSI SQL supports `TABLE` here.
|
||||
}
|
||||
|
||||
impl ToString for SQLSetExpr {
|
||||
|
@ -66,6 +67,7 @@ impl ToString for SQLSetExpr {
|
|||
match self {
|
||||
SQLSetExpr::Select(s) => s.to_string(),
|
||||
SQLSetExpr::Query(q) => format!("({})", q.to_string()),
|
||||
SQLSetExpr::Values(v) => v.to_string(),
|
||||
SQLSetExpr::SetOperation {
|
||||
left,
|
||||
right,
|
||||
|
@ -364,3 +366,16 @@ impl ToString for Fetch {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct SQLValues(pub Vec<Vec<ASTNode>>);
|
||||
|
||||
impl ToString for SQLValues {
|
||||
fn to_string(&self) -> String {
|
||||
let rows = self
|
||||
.0
|
||||
.iter()
|
||||
.map(|row| format!("({})", comma_separated_string(row)));
|
||||
format!("VALUES {}", comma_separated_string(rows))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1361,6 +1361,8 @@ impl Parser {
|
|||
let subquery = self.parse_query()?;
|
||||
self.expect_token(&Token::RParen)?;
|
||||
SQLSetExpr::Query(Box::new(subquery))
|
||||
} else if self.parse_keyword("VALUES") {
|
||||
SQLSetExpr::Values(self.parse_values()?)
|
||||
} else {
|
||||
return self.expected("SELECT or a subquery in the query body", self.peek_token());
|
||||
};
|
||||
|
@ -1572,14 +1574,11 @@ impl Parser {
|
|||
self.expect_keyword("INTO")?;
|
||||
let table_name = self.parse_object_name()?;
|
||||
let columns = self.parse_parenthesized_column_list(Optional)?;
|
||||
self.expect_keyword("VALUES")?;
|
||||
self.expect_token(&Token::LParen)?;
|
||||
let values = self.parse_expr_list()?;
|
||||
self.expect_token(&Token::RParen)?;
|
||||
let source = Box::new(self.parse_query()?);
|
||||
Ok(SQLStatement::SQLInsert {
|
||||
table_name,
|
||||
columns,
|
||||
values: vec![values],
|
||||
source,
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -1697,6 +1696,20 @@ impl Parser {
|
|||
quantity,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn parse_values(&mut self) -> Result<SQLValues, ParserError> {
|
||||
let mut values = vec![];
|
||||
loop {
|
||||
self.expect_token(&Token::LParen)?;
|
||||
values.push(self.parse_expr_list()?);
|
||||
self.expect_token(&Token::RParen)?;
|
||||
match self.peek_token() {
|
||||
Some(Token::Comma) => self.next_token(),
|
||||
_ => break,
|
||||
};
|
||||
}
|
||||
Ok(SQLValues(values))
|
||||
}
|
||||
}
|
||||
|
||||
impl SQLWord {
|
||||
|
|
|
@ -14,44 +14,61 @@ use sqlparser::test_utils::{all_dialects, expr_from_projection, only};
|
|||
|
||||
#[test]
|
||||
fn parse_insert_values() {
|
||||
let sql = "INSERT INTO customer VALUES(1, 2, 3)";
|
||||
check_one(sql, "customer", vec![]);
|
||||
let row = vec![
|
||||
ASTNode::SQLValue(Value::Long(1)),
|
||||
ASTNode::SQLValue(Value::Long(2)),
|
||||
ASTNode::SQLValue(Value::Long(3)),
|
||||
];
|
||||
let rows1 = vec![row.clone()];
|
||||
let rows2 = vec![row.clone(), row];
|
||||
|
||||
let sql = "INSERT INTO public.customer VALUES(1, 2, 3)";
|
||||
check_one(sql, "public.customer", vec![]);
|
||||
let sql = "INSERT INTO customer VALUES (1, 2, 3)";
|
||||
check_one(sql, "customer", &[], &rows1);
|
||||
|
||||
let sql = "INSERT INTO db.public.customer VALUES(1, 2, 3)";
|
||||
check_one(sql, "db.public.customer", vec![]);
|
||||
let sql = "INSERT INTO customer VALUES (1, 2, 3), (1, 2, 3)";
|
||||
check_one(sql, "customer", &[], &rows2);
|
||||
|
||||
let sql = "INSERT INTO public.customer (id, name, active) VALUES(1, 2, 3)";
|
||||
let sql = "INSERT INTO public.customer VALUES (1, 2, 3)";
|
||||
check_one(sql, "public.customer", &[], &rows1);
|
||||
|
||||
let sql = "INSERT INTO db.public.customer VALUES (1, 2, 3)";
|
||||
check_one(sql, "db.public.customer", &[], &rows1);
|
||||
|
||||
let sql = "INSERT INTO public.customer (id, name, active) VALUES (1, 2, 3)";
|
||||
check_one(
|
||||
sql,
|
||||
"public.customer",
|
||||
vec!["id".to_string(), "name".to_string(), "active".to_string()],
|
||||
&["id".to_string(), "name".to_string(), "active".to_string()],
|
||||
&rows1,
|
||||
);
|
||||
|
||||
fn check_one(sql: &str, expected_table_name: &str, expected_columns: Vec<String>) {
|
||||
fn check_one(
|
||||
sql: &str,
|
||||
expected_table_name: &str,
|
||||
expected_columns: &[String],
|
||||
expected_rows: &[Vec<ASTNode>],
|
||||
) {
|
||||
match verified_stmt(sql) {
|
||||
SQLStatement::SQLInsert {
|
||||
table_name,
|
||||
columns,
|
||||
values,
|
||||
source,
|
||||
..
|
||||
} => {
|
||||
assert_eq!(table_name.to_string(), expected_table_name);
|
||||
assert_eq!(columns, expected_columns);
|
||||
assert_eq!(
|
||||
vec![vec![
|
||||
ASTNode::SQLValue(Value::Long(1)),
|
||||
ASTNode::SQLValue(Value::Long(2)),
|
||||
ASTNode::SQLValue(Value::Long(3))
|
||||
]],
|
||||
values
|
||||
);
|
||||
match &source.body {
|
||||
SQLSetExpr::Values(SQLValues(values)) => {
|
||||
assert_eq!(values.as_slice(), expected_rows)
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
verified_stmt("INSERT INTO customer WITH foo AS (SELECT 1) SELECT * FROM foo UNION VALUES (1)");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -1383,6 +1400,13 @@ fn parse_union() {
|
|||
verified_stmt("SELECT foo FROM tab UNION SELECT bar FROM TAB");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_values() {
|
||||
verified_stmt("SELECT * FROM (VALUES (1), (2), (3))");
|
||||
verified_stmt("SELECT * FROM (VALUES (1), (2), (3)), (VALUES (1, 2, 3))");
|
||||
verified_stmt("SELECT * FROM (VALUES (1)) UNION VALUES (1)");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_multiple_statements() {
|
||||
fn test_with(sql1: &str, sql2_kw: &str, sql2_rest: &str) {
|
||||
|
@ -1416,7 +1440,7 @@ fn parse_multiple_statements() {
|
|||
" cte AS (SELECT 1 AS s) SELECT bar",
|
||||
);
|
||||
test_with("DELETE FROM foo", "SELECT", " bar");
|
||||
test_with("INSERT INTO foo VALUES(1)", "SELECT", " bar");
|
||||
test_with("INSERT INTO foo VALUES (1)", "SELECT", " bar");
|
||||
test_with("CREATE TABLE foo (baz int)", "SELECT", " bar");
|
||||
// Make sure that empty statements do not cause an error:
|
||||
let res = parse_sql_statements(";;");
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue