Merge pull request #91 from benesch/from-values

Improve VALUES-related syntax parsing
This commit is contained in:
Nikhil Benesch 2019-06-03 11:44:52 -04:00 committed by GitHub
commit b8ba188191
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 95 additions and 45 deletions

View file

@ -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 { "" },
),
}

View file

@ -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))
}
}

View file

@ -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 {

View file

@ -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(";;");