Skip to content

Commit 4c27222

Browse files
authored
Merge pull request #98 from benesch/col-names
Support column names in more places
2 parents fdbf64c + 9abcac3 commit 4c27222

File tree

4 files changed

+97
-17
lines changed

4 files changed

+97
-17
lines changed

src/sqlast/mod.rs

+11-3
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ use std::ops::Deref;
2525
pub use self::ddl::{AlterTableOperation, TableConstraint};
2626
pub use self::query::{
2727
Cte, Fetch, Join, JoinConstraint, JoinOperator, SQLOrderByExpr, SQLQuery, SQLSelect,
28-
SQLSelectItem, SQLSetExpr, SQLSetOperator, SQLValues, TableFactor,
28+
SQLSelectItem, SQLSetExpr, SQLSetOperator, SQLValues, TableAlias, TableFactor,
2929
};
3030
pub use self::sqltype::SQLType;
3131
pub use self::value::Value;
@@ -377,6 +377,7 @@ pub enum SQLStatement {
377377
SQLCreateView {
378378
/// View name
379379
name: SQLObjectName,
380+
columns: Vec<SQLIdent>,
380381
query: Box<SQLQuery>,
381382
materialized: bool,
382383
with_options: Vec<SQLOption>,
@@ -474,6 +475,7 @@ impl ToString for SQLStatement {
474475
}
475476
SQLStatement::SQLCreateView {
476477
name,
478+
columns,
477479
query,
478480
materialized,
479481
with_options,
@@ -484,12 +486,18 @@ impl ToString for SQLStatement {
484486
} else {
485487
"".into()
486488
};
489+
let columns = if !columns.is_empty() {
490+
format!(" ({})", comma_separated_string(columns))
491+
} else {
492+
"".into()
493+
};
487494
format!(
488-
"CREATE{} VIEW {}{} AS {}",
495+
"CREATE{} VIEW {}{}{} AS {}",
489496
modifier,
490497
name.to_string(),
491498
with_options,
492-
query.to_string()
499+
columns,
500+
query.to_string(),
493501
)
494502
}
495503
SQLStatement::SQLCreateTable {

src/sqlast/query.rs

+20-4
Original file line numberDiff line numberDiff line change
@@ -202,7 +202,7 @@ impl ToString for SQLSelectItem {
202202
pub enum TableFactor {
203203
Table {
204204
name: SQLObjectName,
205-
alias: Option<SQLIdent>,
205+
alias: Option<TableAlias>,
206206
/// Arguments of a table-valued function, as supported by Postgres
207207
/// and MSSQL. Note that deprecated MSSQL `FROM foo (NOLOCK)` syntax
208208
/// will also be parsed as `args`.
@@ -213,7 +213,7 @@ pub enum TableFactor {
213213
Derived {
214214
lateral: bool,
215215
subquery: Box<SQLQuery>,
216-
alias: Option<SQLIdent>,
216+
alias: Option<TableAlias>,
217217
},
218218
}
219219

@@ -231,7 +231,7 @@ impl ToString for TableFactor {
231231
s += &format!("({})", comma_separated_string(args))
232232
};
233233
if let Some(alias) = alias {
234-
s += &format!(" AS {}", alias);
234+
s += &format!(" AS {}", alias.to_string());
235235
}
236236
if !with_hints.is_empty() {
237237
s += &format!(" WITH ({})", comma_separated_string(with_hints));
@@ -249,14 +249,30 @@ impl ToString for TableFactor {
249249
}
250250
s += &format!("({})", subquery.to_string());
251251
if let Some(alias) = alias {
252-
s += &format!(" AS {}", alias);
252+
s += &format!(" AS {}", alias.to_string());
253253
}
254254
s
255255
}
256256
}
257257
}
258258
}
259259

260+
#[derive(Debug, Clone, PartialEq, Hash)]
261+
pub struct TableAlias {
262+
pub name: SQLIdent,
263+
pub columns: Vec<SQLIdent>,
264+
}
265+
266+
impl ToString for TableAlias {
267+
fn to_string(&self) -> String {
268+
let mut s = self.name.clone();
269+
if !self.columns.is_empty() {
270+
s += &format!(" ({})", comma_separated_string(&self.columns));
271+
}
272+
s
273+
}
274+
}
275+
260276
#[derive(Debug, Clone, PartialEq, Hash)]
261277
pub struct Join {
262278
pub relation: TableFactor,

src/sqlparser.rs

+21-3
Original file line numberDiff line numberDiff line change
@@ -775,7 +775,7 @@ impl Parser {
775775
// Many dialects support `OR REPLACE` | `OR ALTER` right after `CREATE`, but we don't (yet).
776776
// ANSI SQL and Postgres support RECURSIVE here, but we don't support it either.
777777
let name = self.parse_object_name()?;
778-
// Parenthesized "output" columns list could be handled here.
778+
let columns = self.parse_parenthesized_column_list(Optional)?;
779779
let with_options = if self.parse_keyword("WITH") {
780780
self.parse_with_options()?
781781
} else {
@@ -786,6 +786,7 @@ impl Parser {
786786
// Optional `WITH [ CASCADED | LOCAL ] CHECK OPTION` is widely supported here.
787787
Ok(SQLStatement::SQLCreateView {
788788
name,
789+
columns,
789790
query,
790791
materialized,
791792
with_options,
@@ -1207,6 +1208,23 @@ impl Parser {
12071208
}
12081209
}
12091210

1211+
/// Parse `AS identifier` when the AS is describing a table-valued object,
1212+
/// like in `... FROM generate_series(1, 10) AS t (col)`. In this case
1213+
/// the alias is allowed to optionally name the columns in the table, in
1214+
/// addition to the table itself.
1215+
pub fn parse_optional_table_alias(
1216+
&mut self,
1217+
reserved_kwds: &[&str],
1218+
) -> Result<Option<TableAlias>, ParserError> {
1219+
match self.parse_optional_alias(reserved_kwds)? {
1220+
Some(name) => {
1221+
let columns = self.parse_parenthesized_column_list(Optional)?;
1222+
Ok(Some(TableAlias { name, columns }))
1223+
}
1224+
None => Ok(None),
1225+
}
1226+
}
1227+
12101228
/// Parse one or more identifiers with the specified separator between them
12111229
pub fn parse_list_of_ids(&mut self, separator: &Token) -> Result<Vec<SQLIdent>, ParserError> {
12121230
let mut idents = vec![];
@@ -1490,7 +1508,7 @@ impl Parser {
14901508
if self.consume_token(&Token::LParen) {
14911509
let subquery = Box::new(self.parse_query()?);
14921510
self.expect_token(&Token::RParen)?;
1493-
let alias = self.parse_optional_alias(keywords::RESERVED_FOR_TABLE_ALIAS)?;
1511+
let alias = self.parse_optional_table_alias(keywords::RESERVED_FOR_TABLE_ALIAS)?;
14941512
Ok(TableFactor::Derived {
14951513
lateral,
14961514
subquery,
@@ -1506,7 +1524,7 @@ impl Parser {
15061524
} else {
15071525
vec![]
15081526
};
1509-
let alias = self.parse_optional_alias(keywords::RESERVED_FOR_TABLE_ALIAS)?;
1527+
let alias = self.parse_optional_table_alias(keywords::RESERVED_FOR_TABLE_ALIAS)?;
15101528
// MSSQL-specific table hints:
15111529
let mut with_hints = vec![];
15121530
if self.parse_keyword("WITH") {

tests/sqlparser_common.rs

+45-7
Original file line numberDiff line numberDiff line change
@@ -1083,7 +1083,7 @@ fn parse_delimited_identifiers() {
10831083
with_hints,
10841084
} => {
10851085
assert_eq!(vec![r#""a table""#.to_string()], name.0);
1086-
assert_eq!(r#""alias""#, alias.unwrap());
1086+
assert_eq!(r#""alias""#, alias.unwrap().name);
10871087
assert!(args.is_empty());
10881088
assert!(with_hints.is_empty());
10891089
}
@@ -1236,11 +1236,18 @@ fn parse_cross_join() {
12361236
);
12371237
}
12381238

1239+
fn table_alias(name: impl Into<String>) -> Option<TableAlias> {
1240+
Some(TableAlias {
1241+
name: name.into(),
1242+
columns: vec![],
1243+
})
1244+
}
1245+
12391246
#[test]
12401247
fn parse_joins_on() {
12411248
fn join_with_constraint(
12421249
relation: impl Into<String>,
1243-
alias: Option<SQLIdent>,
1250+
alias: Option<TableAlias>,
12441251
f: impl Fn(JoinConstraint) -> JoinOperator,
12451252
) -> Join {
12461253
Join {
@@ -1262,7 +1269,7 @@ fn parse_joins_on() {
12621269
verified_only_select("SELECT * FROM t1 JOIN t2 AS foo ON c1 = c2").joins,
12631270
vec![join_with_constraint(
12641271
"t2",
1265-
Some("foo".to_string()),
1272+
table_alias("foo"),
12661273
JoinOperator::Inner
12671274
)]
12681275
);
@@ -1293,7 +1300,7 @@ fn parse_joins_on() {
12931300
fn parse_joins_using() {
12941301
fn join_with_constraint(
12951302
relation: impl Into<String>,
1296-
alias: Option<SQLIdent>,
1303+
alias: Option<TableAlias>,
12971304
f: impl Fn(JoinConstraint) -> JoinOperator,
12981305
) -> Join {
12991306
Join {
@@ -1311,7 +1318,7 @@ fn parse_joins_using() {
13111318
verified_only_select("SELECT * FROM t1 JOIN t2 AS foo USING(c1)").joins,
13121319
vec![join_with_constraint(
13131320
"t2",
1314-
Some("foo".to_string()),
1321+
table_alias("foo"),
13151322
JoinOperator::Inner
13161323
)]
13171324
);
@@ -1471,6 +1478,12 @@ fn parse_derived_tables() {
14711478
let sql = "SELECT a.x, b.y FROM (SELECT x FROM foo) AS a CROSS JOIN (SELECT y FROM bar) AS b";
14721479
let _ = verified_only_select(sql);
14731480
//TODO: add assertions
1481+
1482+
let sql = "SELECT a.x, b.y \
1483+
FROM (SELECT x FROM foo) AS a (x) \
1484+
CROSS JOIN (SELECT y FROM bar) AS b (y)";
1485+
let _ = verified_only_select(sql);
1486+
//TODO: add assertions
14741487
}
14751488

14761489
#[test]
@@ -1595,11 +1608,13 @@ fn parse_create_view() {
15951608
match verified_stmt(sql) {
15961609
SQLStatement::SQLCreateView {
15971610
name,
1611+
columns,
15981612
query,
15991613
materialized,
16001614
with_options,
16011615
} => {
16021616
assert_eq!("myschema.myview", name.to_string());
1617+
assert_eq!(Vec::<SQLIdent>::new(), columns);
16031618
assert_eq!("SELECT foo FROM bar", query.to_string());
16041619
assert!(!materialized);
16051620
assert_eq!(with_options, vec![]);
@@ -1631,17 +1646,40 @@ fn parse_create_view_with_options() {
16311646
}
16321647
}
16331648

1649+
#[test]
1650+
fn parse_create_view_with_columns() {
1651+
let sql = "CREATE VIEW v (has, cols) AS SELECT 1, 2";
1652+
match verified_stmt(sql) {
1653+
SQLStatement::SQLCreateView {
1654+
name,
1655+
columns,
1656+
with_options,
1657+
query,
1658+
materialized,
1659+
} => {
1660+
assert_eq!("v", name.to_string());
1661+
assert_eq!(columns, vec!["has".to_string(), "cols".to_string()]);
1662+
assert_eq!(with_options, vec![]);
1663+
assert_eq!("SELECT 1, 2", query.to_string());
1664+
assert!(!materialized);
1665+
}
1666+
_ => unreachable!(),
1667+
}
1668+
}
1669+
16341670
#[test]
16351671
fn parse_create_materialized_view() {
16361672
let sql = "CREATE MATERIALIZED VIEW myschema.myview AS SELECT foo FROM bar";
16371673
match verified_stmt(sql) {
16381674
SQLStatement::SQLCreateView {
16391675
name,
1676+
columns,
16401677
query,
16411678
materialized,
16421679
with_options,
16431680
} => {
16441681
assert_eq!("myschema.myview", name.to_string());
1682+
assert_eq!(Vec::<SQLIdent>::new(), columns);
16451683
assert_eq!("SELECT foo FROM bar", query.to_string());
16461684
assert!(materialized);
16471685
assert_eq!(with_options, vec![]);
@@ -1928,11 +1966,11 @@ fn lateral_derived() {
19281966
if let TableFactor::Derived {
19291967
lateral,
19301968
ref subquery,
1931-
ref alias,
1969+
alias: Some(ref alias),
19321970
} = select.joins[0].relation
19331971
{
19341972
assert_eq!(lateral_in, lateral);
1935-
assert_eq!(Some("order".to_string()), *alias);
1973+
assert_eq!("order".to_string(), alias.name);
19361974
assert_eq!(
19371975
subquery.to_string(),
19381976
"SELECT * FROM order WHERE order.customer = customer.id LIMIT 3"

0 commit comments

Comments
 (0)