Skip to content

Commit 6fceba8

Browse files
authored
Merge pull request #74 from benesch/with-options
Support arbitrary WITH options for CREATE [TABLE|VIEW]
2 parents 1931c76 + 69f0082 commit 6fceba8

File tree

4 files changed

+140
-4
lines changed

4 files changed

+140
-4
lines changed

src/sqlast/mod.rs

+26-1
Original file line numberDiff line numberDiff line change
@@ -379,6 +379,7 @@ pub enum SQLStatement {
379379
name: SQLObjectName,
380380
query: Box<SQLQuery>,
381381
materialized: bool,
382+
with_options: Vec<SQLOption>,
382383
},
383384
/// CREATE TABLE
384385
SQLCreateTable {
@@ -387,6 +388,7 @@ pub enum SQLStatement {
387388
/// Optional schema
388389
columns: Vec<SQLColumnDef>,
389390
constraints: Vec<TableConstraint>,
391+
with_options: Vec<SQLOption>,
390392
external: bool,
391393
file_format: Option<FileFormat>,
392394
location: Option<String>,
@@ -473,19 +475,27 @@ impl ToString for SQLStatement {
473475
name,
474476
query,
475477
materialized,
478+
with_options,
476479
} => {
477480
let modifier = if *materialized { " MATERIALIZED" } else { "" };
481+
let with_options = if !with_options.is_empty() {
482+
format!(" WITH ({})", comma_separated_string(with_options))
483+
} else {
484+
"".into()
485+
};
478486
format!(
479-
"CREATE{} VIEW {} AS {}",
487+
"CREATE{} VIEW {}{} AS {}",
480488
modifier,
481489
name.to_string(),
490+
with_options,
482491
query.to_string()
483492
)
484493
}
485494
SQLStatement::SQLCreateTable {
486495
name,
487496
columns,
488497
constraints,
498+
with_options,
489499
external,
490500
file_format,
491501
location,
@@ -507,6 +517,9 @@ impl ToString for SQLStatement {
507517
location.as_ref().unwrap()
508518
);
509519
}
520+
if !with_options.is_empty() {
521+
s += &format!(" WITH ({})", comma_separated_string(with_options));
522+
}
510523
s
511524
}
512525
SQLStatement::SQLAlterTable { name, operation } => {
@@ -670,3 +683,15 @@ impl SQLObjectType {
670683
}
671684
}
672685
}
686+
687+
#[derive(Debug, Clone, PartialEq, Hash)]
688+
pub struct SQLOption {
689+
pub name: SQLIdent,
690+
pub value: Value,
691+
}
692+
693+
impl ToString for SQLOption {
694+
fn to_string(&self) -> String {
695+
format!("{} = {}", self.name.to_string(), self.value.to_string())
696+
}
697+
}

src/sqlparser.rs

+31-2
Original file line numberDiff line numberDiff line change
@@ -761,6 +761,7 @@ impl Parser {
761761
name: table_name,
762762
columns,
763763
constraints,
764+
with_options: vec![],
764765
external: true,
765766
file_format: Some(file_format),
766767
location: Some(location),
@@ -774,15 +775,19 @@ impl Parser {
774775
// ANSI SQL and Postgres support RECURSIVE here, but we don't support it either.
775776
let name = self.parse_object_name()?;
776777
// Parenthesized "output" columns list could be handled here.
777-
// Some dialects allow WITH here, followed by some keywords (e.g. MS SQL)
778-
// or `(k1=v1, k2=v2, ...)` (Postgres)
778+
let with_options = if self.parse_keyword("WITH") {
779+
self.parse_with_options()?
780+
} else {
781+
vec![]
782+
};
779783
self.expect_keyword("AS")?;
780784
let query = Box::new(self.parse_query()?);
781785
// Optional `WITH [ CASCADED | LOCAL ] CHECK OPTION` is widely supported here.
782786
Ok(SQLStatement::SQLCreateView {
783787
name,
784788
query,
785789
materialized,
790+
with_options,
786791
})
787792
}
788793

@@ -828,10 +833,17 @@ impl Parser {
828833
// parse optional column list (schema)
829834
let (columns, constraints) = self.parse_columns()?;
830835

836+
let with_options = if self.parse_keyword("WITH") {
837+
self.parse_with_options()?
838+
} else {
839+
vec![]
840+
};
841+
831842
Ok(SQLStatement::SQLCreateTable {
832843
name: table_name,
833844
columns,
834845
constraints,
846+
with_options,
835847
external: false,
836848
file_format: None,
837849
location: None,
@@ -941,6 +953,23 @@ impl Parser {
941953
}
942954
}
943955

956+
pub fn parse_with_options(&mut self) -> Result<Vec<SQLOption>, ParserError> {
957+
self.expect_token(&Token::LParen)?;
958+
let mut options = vec![];
959+
loop {
960+
let name = self.parse_identifier()?;
961+
self.expect_token(&Token::Eq)?;
962+
let value = self.parse_value()?;
963+
options.push(SQLOption { name, value });
964+
match self.peek_token() {
965+
Some(Token::Comma) => self.next_token(),
966+
_ => break,
967+
};
968+
}
969+
self.expect_token(&Token::RParen)?;
970+
Ok(options)
971+
}
972+
944973
pub fn parse_alter(&mut self) -> Result<SQLStatement, ParserError> {
945974
self.expect_keyword("TABLE")?;
946975
let _ = self.parse_keyword("ONLY");

tests/sqlparser_common.rs

+56
Original file line numberDiff line numberDiff line change
@@ -765,6 +765,7 @@ fn parse_create_table() {
765765
name,
766766
columns,
767767
constraints,
768+
with_options,
768769
external: false,
769770
file_format: None,
770771
location: None,
@@ -787,6 +788,31 @@ fn parse_create_table() {
787788
assert_eq!("lng", c_lng.name);
788789
assert_eq!(SQLType::Double, c_lng.data_type);
789790
assert_eq!(true, c_lng.allow_null);
791+
792+
assert_eq!(with_options, vec![]);
793+
}
794+
_ => unreachable!(),
795+
}
796+
}
797+
798+
#[test]
799+
fn parse_create_table_with_options() {
800+
let sql = "CREATE TABLE t (c int) WITH (foo = 'bar', a = 123)";
801+
match verified_stmt(sql) {
802+
SQLStatement::SQLCreateTable { with_options, .. } => {
803+
assert_eq!(
804+
vec![
805+
SQLOption {
806+
name: "foo".into(),
807+
value: Value::SingleQuotedString("bar".into())
808+
},
809+
SQLOption {
810+
name: "a".into(),
811+
value: Value::Long(123)
812+
},
813+
],
814+
with_options
815+
);
790816
}
791817
_ => unreachable!(),
792818
}
@@ -818,6 +844,7 @@ fn parse_create_external_table() {
818844
name,
819845
columns,
820846
constraints,
847+
with_options,
821848
external,
822849
file_format,
823850
location,
@@ -844,6 +871,8 @@ fn parse_create_external_table() {
844871
assert!(external);
845872
assert_eq!(FileFormat::TEXTFILE, file_format.unwrap());
846873
assert_eq!("/tmp/example.csv", location.unwrap());
874+
875+
assert_eq!(with_options, vec![]);
847876
}
848877
_ => unreachable!(),
849878
}
@@ -1512,10 +1541,35 @@ fn parse_create_view() {
15121541
name,
15131542
query,
15141543
materialized,
1544+
with_options,
15151545
} => {
15161546
assert_eq!("myschema.myview", name.to_string());
15171547
assert_eq!("SELECT foo FROM bar", query.to_string());
15181548
assert!(!materialized);
1549+
assert_eq!(with_options, vec![]);
1550+
}
1551+
_ => unreachable!(),
1552+
}
1553+
}
1554+
1555+
#[test]
1556+
fn parse_create_view_with_options() {
1557+
let sql = "CREATE VIEW v WITH (foo = 'bar', a = 123) AS SELECT 1";
1558+
match verified_stmt(sql) {
1559+
SQLStatement::SQLCreateView { with_options, .. } => {
1560+
assert_eq!(
1561+
vec![
1562+
SQLOption {
1563+
name: "foo".into(),
1564+
value: Value::SingleQuotedString("bar".into())
1565+
},
1566+
SQLOption {
1567+
name: "a".into(),
1568+
value: Value::Long(123)
1569+
},
1570+
],
1571+
with_options
1572+
);
15191573
}
15201574
_ => unreachable!(),
15211575
}
@@ -1529,10 +1583,12 @@ fn parse_create_materialized_view() {
15291583
name,
15301584
query,
15311585
materialized,
1586+
with_options,
15321587
} => {
15331588
assert_eq!("myschema.myview", name.to_string());
15341589
assert_eq!("SELECT foo FROM bar", query.to_string());
15351590
assert!(materialized);
1591+
assert_eq!(with_options, vec![]);
15361592
}
15371593
_ => unreachable!(),
15381594
}

tests/sqlparser_postgres.rs

+27-1
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,14 @@ fn parse_create_table_with_defaults() {
1818
activebool boolean DEFAULT true NOT NULL,
1919
create_date date DEFAULT now()::text NOT NULL,
2020
last_update timestamp without time zone DEFAULT now() NOT NULL,
21-
active integer NOT NULL)";
21+
active integer NOT NULL
22+
) WITH (fillfactor = 20, user_catalog_table = true, autovacuum_vacuum_threshold = 100)";
2223
match pg_and_generic().one_statement_parses_to(sql, "") {
2324
SQLStatement::SQLCreateTable {
2425
name,
2526
columns,
2627
constraints,
28+
with_options,
2729
external: false,
2830
file_format: None,
2931
location: None,
@@ -46,6 +48,24 @@ fn parse_create_table_with_defaults() {
4648
assert_eq!("first_name", c_lng.name);
4749
assert_eq!(SQLType::Varchar(Some(45)), c_lng.data_type);
4850
assert_eq!(false, c_lng.allow_null);
51+
52+
assert_eq!(
53+
with_options,
54+
vec![
55+
SQLOption {
56+
name: "fillfactor".into(),
57+
value: Value::Long(20)
58+
},
59+
SQLOption {
60+
name: "user_catalog_table".into(),
61+
value: Value::Boolean(true)
62+
},
63+
SQLOption {
64+
name: "autovacuum_vacuum_threshold".into(),
65+
value: Value::Long(100)
66+
},
67+
]
68+
);
4969
}
5070
_ => unreachable!(),
5171
}
@@ -72,6 +92,7 @@ fn parse_create_table_from_pg_dump() {
7292
name,
7393
columns,
7494
constraints,
95+
with_options,
7596
external: false,
7697
file_format: None,
7798
location: None,
@@ -116,6 +137,8 @@ fn parse_create_table_from_pg_dump() {
116137
])),
117138
c_release_year.data_type
118139
);
140+
141+
assert_eq!(with_options, vec![]);
119142
}
120143
_ => unreachable!(),
121144
}
@@ -135,6 +158,7 @@ fn parse_create_table_with_inherit() {
135158
name,
136159
columns,
137160
constraints,
161+
with_options,
138162
external: false,
139163
file_format: None,
140164
location: None,
@@ -155,6 +179,8 @@ fn parse_create_table_with_inherit() {
155179
assert_eq!(true, c_name.allow_null);
156180
assert_eq!(false, c_name.is_primary);
157181
assert_eq!(true, c_name.is_unique);
182+
183+
assert_eq!(with_options, vec![]);
158184
}
159185
_ => unreachable!(),
160186
}

0 commit comments

Comments
 (0)