mirror of
https://github.com/apache/datafusion-sqlparser-rs.git
synced 2025-09-22 05:32:29 +00:00
Support arbitrary WITH options for CREATE [TABLE|VIEW]
Both Postgres and MSSQL accept this syntax, though the particular options they accept differ.
This commit is contained in:
parent
1931c76eb8
commit
69f0082db6
4 changed files with 140 additions and 4 deletions
|
@ -379,6 +379,7 @@ pub enum SQLStatement {
|
||||||
name: SQLObjectName,
|
name: SQLObjectName,
|
||||||
query: Box<SQLQuery>,
|
query: Box<SQLQuery>,
|
||||||
materialized: bool,
|
materialized: bool,
|
||||||
|
with_options: Vec<SQLOption>,
|
||||||
},
|
},
|
||||||
/// CREATE TABLE
|
/// CREATE TABLE
|
||||||
SQLCreateTable {
|
SQLCreateTable {
|
||||||
|
@ -387,6 +388,7 @@ pub enum SQLStatement {
|
||||||
/// Optional schema
|
/// Optional schema
|
||||||
columns: Vec<SQLColumnDef>,
|
columns: Vec<SQLColumnDef>,
|
||||||
constraints: Vec<TableConstraint>,
|
constraints: Vec<TableConstraint>,
|
||||||
|
with_options: Vec<SQLOption>,
|
||||||
external: bool,
|
external: bool,
|
||||||
file_format: Option<FileFormat>,
|
file_format: Option<FileFormat>,
|
||||||
location: Option<String>,
|
location: Option<String>,
|
||||||
|
@ -473,12 +475,19 @@ impl ToString for SQLStatement {
|
||||||
name,
|
name,
|
||||||
query,
|
query,
|
||||||
materialized,
|
materialized,
|
||||||
|
with_options,
|
||||||
} => {
|
} => {
|
||||||
let modifier = if *materialized { " MATERIALIZED" } else { "" };
|
let modifier = if *materialized { " MATERIALIZED" } else { "" };
|
||||||
|
let with_options = if !with_options.is_empty() {
|
||||||
|
format!(" WITH ({})", comma_separated_string(with_options))
|
||||||
|
} else {
|
||||||
|
"".into()
|
||||||
|
};
|
||||||
format!(
|
format!(
|
||||||
"CREATE{} VIEW {} AS {}",
|
"CREATE{} VIEW {}{} AS {}",
|
||||||
modifier,
|
modifier,
|
||||||
name.to_string(),
|
name.to_string(),
|
||||||
|
with_options,
|
||||||
query.to_string()
|
query.to_string()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -486,6 +495,7 @@ impl ToString for SQLStatement {
|
||||||
name,
|
name,
|
||||||
columns,
|
columns,
|
||||||
constraints,
|
constraints,
|
||||||
|
with_options,
|
||||||
external,
|
external,
|
||||||
file_format,
|
file_format,
|
||||||
location,
|
location,
|
||||||
|
@ -507,6 +517,9 @@ impl ToString for SQLStatement {
|
||||||
location.as_ref().unwrap()
|
location.as_ref().unwrap()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
if !with_options.is_empty() {
|
||||||
|
s += &format!(" WITH ({})", comma_separated_string(with_options));
|
||||||
|
}
|
||||||
s
|
s
|
||||||
}
|
}
|
||||||
SQLStatement::SQLAlterTable { name, operation } => {
|
SQLStatement::SQLAlterTable { name, operation } => {
|
||||||
|
@ -670,3 +683,15 @@ impl SQLObjectType {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Hash)]
|
||||||
|
pub struct SQLOption {
|
||||||
|
pub name: SQLIdent,
|
||||||
|
pub value: Value,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToString for SQLOption {
|
||||||
|
fn to_string(&self) -> String {
|
||||||
|
format!("{} = {}", self.name.to_string(), self.value.to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -761,6 +761,7 @@ impl Parser {
|
||||||
name: table_name,
|
name: table_name,
|
||||||
columns,
|
columns,
|
||||||
constraints,
|
constraints,
|
||||||
|
with_options: vec![],
|
||||||
external: true,
|
external: true,
|
||||||
file_format: Some(file_format),
|
file_format: Some(file_format),
|
||||||
location: Some(location),
|
location: Some(location),
|
||||||
|
@ -774,8 +775,11 @@ impl Parser {
|
||||||
// ANSI SQL and Postgres support RECURSIVE here, but we don't support it either.
|
// ANSI SQL and Postgres support RECURSIVE here, but we don't support it either.
|
||||||
let name = self.parse_object_name()?;
|
let name = self.parse_object_name()?;
|
||||||
// Parenthesized "output" columns list could be handled here.
|
// Parenthesized "output" columns list could be handled here.
|
||||||
// Some dialects allow WITH here, followed by some keywords (e.g. MS SQL)
|
let with_options = if self.parse_keyword("WITH") {
|
||||||
// or `(k1=v1, k2=v2, ...)` (Postgres)
|
self.parse_with_options()?
|
||||||
|
} else {
|
||||||
|
vec![]
|
||||||
|
};
|
||||||
self.expect_keyword("AS")?;
|
self.expect_keyword("AS")?;
|
||||||
let query = Box::new(self.parse_query()?);
|
let query = Box::new(self.parse_query()?);
|
||||||
// Optional `WITH [ CASCADED | LOCAL ] CHECK OPTION` is widely supported here.
|
// Optional `WITH [ CASCADED | LOCAL ] CHECK OPTION` is widely supported here.
|
||||||
|
@ -783,6 +787,7 @@ impl Parser {
|
||||||
name,
|
name,
|
||||||
query,
|
query,
|
||||||
materialized,
|
materialized,
|
||||||
|
with_options,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -828,10 +833,17 @@ impl Parser {
|
||||||
// parse optional column list (schema)
|
// parse optional column list (schema)
|
||||||
let (columns, constraints) = self.parse_columns()?;
|
let (columns, constraints) = self.parse_columns()?;
|
||||||
|
|
||||||
|
let with_options = if self.parse_keyword("WITH") {
|
||||||
|
self.parse_with_options()?
|
||||||
|
} else {
|
||||||
|
vec![]
|
||||||
|
};
|
||||||
|
|
||||||
Ok(SQLStatement::SQLCreateTable {
|
Ok(SQLStatement::SQLCreateTable {
|
||||||
name: table_name,
|
name: table_name,
|
||||||
columns,
|
columns,
|
||||||
constraints,
|
constraints,
|
||||||
|
with_options,
|
||||||
external: false,
|
external: false,
|
||||||
file_format: None,
|
file_format: None,
|
||||||
location: None,
|
location: None,
|
||||||
|
@ -941,6 +953,23 @@ impl Parser {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn parse_with_options(&mut self) -> Result<Vec<SQLOption>, ParserError> {
|
||||||
|
self.expect_token(&Token::LParen)?;
|
||||||
|
let mut options = vec![];
|
||||||
|
loop {
|
||||||
|
let name = self.parse_identifier()?;
|
||||||
|
self.expect_token(&Token::Eq)?;
|
||||||
|
let value = self.parse_value()?;
|
||||||
|
options.push(SQLOption { name, value });
|
||||||
|
match self.peek_token() {
|
||||||
|
Some(Token::Comma) => self.next_token(),
|
||||||
|
_ => break,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
self.expect_token(&Token::RParen)?;
|
||||||
|
Ok(options)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn parse_alter(&mut self) -> Result<SQLStatement, ParserError> {
|
pub fn parse_alter(&mut self) -> Result<SQLStatement, ParserError> {
|
||||||
self.expect_keyword("TABLE")?;
|
self.expect_keyword("TABLE")?;
|
||||||
let _ = self.parse_keyword("ONLY");
|
let _ = self.parse_keyword("ONLY");
|
||||||
|
|
|
@ -765,6 +765,7 @@ fn parse_create_table() {
|
||||||
name,
|
name,
|
||||||
columns,
|
columns,
|
||||||
constraints,
|
constraints,
|
||||||
|
with_options,
|
||||||
external: false,
|
external: false,
|
||||||
file_format: None,
|
file_format: None,
|
||||||
location: None,
|
location: None,
|
||||||
|
@ -787,6 +788,31 @@ fn parse_create_table() {
|
||||||
assert_eq!("lng", c_lng.name);
|
assert_eq!("lng", c_lng.name);
|
||||||
assert_eq!(SQLType::Double, c_lng.data_type);
|
assert_eq!(SQLType::Double, c_lng.data_type);
|
||||||
assert_eq!(true, c_lng.allow_null);
|
assert_eq!(true, c_lng.allow_null);
|
||||||
|
|
||||||
|
assert_eq!(with_options, vec![]);
|
||||||
|
}
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_create_table_with_options() {
|
||||||
|
let sql = "CREATE TABLE t (c int) WITH (foo = 'bar', a = 123)";
|
||||||
|
match verified_stmt(sql) {
|
||||||
|
SQLStatement::SQLCreateTable { with_options, .. } => {
|
||||||
|
assert_eq!(
|
||||||
|
vec![
|
||||||
|
SQLOption {
|
||||||
|
name: "foo".into(),
|
||||||
|
value: Value::SingleQuotedString("bar".into())
|
||||||
|
},
|
||||||
|
SQLOption {
|
||||||
|
name: "a".into(),
|
||||||
|
value: Value::Long(123)
|
||||||
|
},
|
||||||
|
],
|
||||||
|
with_options
|
||||||
|
);
|
||||||
}
|
}
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
}
|
}
|
||||||
|
@ -818,6 +844,7 @@ fn parse_create_external_table() {
|
||||||
name,
|
name,
|
||||||
columns,
|
columns,
|
||||||
constraints,
|
constraints,
|
||||||
|
with_options,
|
||||||
external,
|
external,
|
||||||
file_format,
|
file_format,
|
||||||
location,
|
location,
|
||||||
|
@ -844,6 +871,8 @@ fn parse_create_external_table() {
|
||||||
assert!(external);
|
assert!(external);
|
||||||
assert_eq!(FileFormat::TEXTFILE, file_format.unwrap());
|
assert_eq!(FileFormat::TEXTFILE, file_format.unwrap());
|
||||||
assert_eq!("/tmp/example.csv", location.unwrap());
|
assert_eq!("/tmp/example.csv", location.unwrap());
|
||||||
|
|
||||||
|
assert_eq!(with_options, vec![]);
|
||||||
}
|
}
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
}
|
}
|
||||||
|
@ -1512,10 +1541,35 @@ fn parse_create_view() {
|
||||||
name,
|
name,
|
||||||
query,
|
query,
|
||||||
materialized,
|
materialized,
|
||||||
|
with_options,
|
||||||
} => {
|
} => {
|
||||||
assert_eq!("myschema.myview", name.to_string());
|
assert_eq!("myschema.myview", name.to_string());
|
||||||
assert_eq!("SELECT foo FROM bar", query.to_string());
|
assert_eq!("SELECT foo FROM bar", query.to_string());
|
||||||
assert!(!materialized);
|
assert!(!materialized);
|
||||||
|
assert_eq!(with_options, vec![]);
|
||||||
|
}
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_create_view_with_options() {
|
||||||
|
let sql = "CREATE VIEW v WITH (foo = 'bar', a = 123) AS SELECT 1";
|
||||||
|
match verified_stmt(sql) {
|
||||||
|
SQLStatement::SQLCreateView { with_options, .. } => {
|
||||||
|
assert_eq!(
|
||||||
|
vec![
|
||||||
|
SQLOption {
|
||||||
|
name: "foo".into(),
|
||||||
|
value: Value::SingleQuotedString("bar".into())
|
||||||
|
},
|
||||||
|
SQLOption {
|
||||||
|
name: "a".into(),
|
||||||
|
value: Value::Long(123)
|
||||||
|
},
|
||||||
|
],
|
||||||
|
with_options
|
||||||
|
);
|
||||||
}
|
}
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
}
|
}
|
||||||
|
@ -1529,10 +1583,12 @@ fn parse_create_materialized_view() {
|
||||||
name,
|
name,
|
||||||
query,
|
query,
|
||||||
materialized,
|
materialized,
|
||||||
|
with_options,
|
||||||
} => {
|
} => {
|
||||||
assert_eq!("myschema.myview", name.to_string());
|
assert_eq!("myschema.myview", name.to_string());
|
||||||
assert_eq!("SELECT foo FROM bar", query.to_string());
|
assert_eq!("SELECT foo FROM bar", query.to_string());
|
||||||
assert!(materialized);
|
assert!(materialized);
|
||||||
|
assert_eq!(with_options, vec![]);
|
||||||
}
|
}
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,12 +18,14 @@ fn parse_create_table_with_defaults() {
|
||||||
activebool boolean DEFAULT true NOT NULL,
|
activebool boolean DEFAULT true NOT NULL,
|
||||||
create_date date DEFAULT now()::text NOT NULL,
|
create_date date DEFAULT now()::text NOT NULL,
|
||||||
last_update timestamp without time zone DEFAULT now() NOT NULL,
|
last_update timestamp without time zone DEFAULT now() NOT NULL,
|
||||||
active integer NOT NULL)";
|
active integer NOT NULL
|
||||||
|
) WITH (fillfactor = 20, user_catalog_table = true, autovacuum_vacuum_threshold = 100)";
|
||||||
match pg_and_generic().one_statement_parses_to(sql, "") {
|
match pg_and_generic().one_statement_parses_to(sql, "") {
|
||||||
SQLStatement::SQLCreateTable {
|
SQLStatement::SQLCreateTable {
|
||||||
name,
|
name,
|
||||||
columns,
|
columns,
|
||||||
constraints,
|
constraints,
|
||||||
|
with_options,
|
||||||
external: false,
|
external: false,
|
||||||
file_format: None,
|
file_format: None,
|
||||||
location: None,
|
location: None,
|
||||||
|
@ -46,6 +48,24 @@ fn parse_create_table_with_defaults() {
|
||||||
assert_eq!("first_name", c_lng.name);
|
assert_eq!("first_name", c_lng.name);
|
||||||
assert_eq!(SQLType::Varchar(Some(45)), c_lng.data_type);
|
assert_eq!(SQLType::Varchar(Some(45)), c_lng.data_type);
|
||||||
assert_eq!(false, c_lng.allow_null);
|
assert_eq!(false, c_lng.allow_null);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
with_options,
|
||||||
|
vec![
|
||||||
|
SQLOption {
|
||||||
|
name: "fillfactor".into(),
|
||||||
|
value: Value::Long(20)
|
||||||
|
},
|
||||||
|
SQLOption {
|
||||||
|
name: "user_catalog_table".into(),
|
||||||
|
value: Value::Boolean(true)
|
||||||
|
},
|
||||||
|
SQLOption {
|
||||||
|
name: "autovacuum_vacuum_threshold".into(),
|
||||||
|
value: Value::Long(100)
|
||||||
|
},
|
||||||
|
]
|
||||||
|
);
|
||||||
}
|
}
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
}
|
}
|
||||||
|
@ -72,6 +92,7 @@ fn parse_create_table_from_pg_dump() {
|
||||||
name,
|
name,
|
||||||
columns,
|
columns,
|
||||||
constraints,
|
constraints,
|
||||||
|
with_options,
|
||||||
external: false,
|
external: false,
|
||||||
file_format: None,
|
file_format: None,
|
||||||
location: None,
|
location: None,
|
||||||
|
@ -116,6 +137,8 @@ fn parse_create_table_from_pg_dump() {
|
||||||
])),
|
])),
|
||||||
c_release_year.data_type
|
c_release_year.data_type
|
||||||
);
|
);
|
||||||
|
|
||||||
|
assert_eq!(with_options, vec![]);
|
||||||
}
|
}
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
}
|
}
|
||||||
|
@ -135,6 +158,7 @@ fn parse_create_table_with_inherit() {
|
||||||
name,
|
name,
|
||||||
columns,
|
columns,
|
||||||
constraints,
|
constraints,
|
||||||
|
with_options,
|
||||||
external: false,
|
external: false,
|
||||||
file_format: None,
|
file_format: None,
|
||||||
location: None,
|
location: None,
|
||||||
|
@ -155,6 +179,8 @@ fn parse_create_table_with_inherit() {
|
||||||
assert_eq!(true, c_name.allow_null);
|
assert_eq!(true, c_name.allow_null);
|
||||||
assert_eq!(false, c_name.is_primary);
|
assert_eq!(false, c_name.is_primary);
|
||||||
assert_eq!(true, c_name.is_unique);
|
assert_eq!(true, c_name.is_unique);
|
||||||
|
|
||||||
|
assert_eq!(with_options, vec![]);
|
||||||
}
|
}
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue