Skip to content

Commit 9e9da81

Browse files
committed
1 parent 2240dd0 commit 9e9da81

File tree

6 files changed

+509
-6
lines changed

6 files changed

+509
-6
lines changed

src/dialect/generic_sql.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@ impl Dialect for GenericSqlDialect {
1111
STORED, CSV, PARQUET, LOCATION, WITH, WITHOUT, HEADER, ROW, // SQL types
1212
CHAR, CHARACTER, VARYING, LARGE, OBJECT, VARCHAR, CLOB, BINARY, VARBINARY, BLOB, FLOAT,
1313
REAL, DOUBLE, PRECISION, INT, INTEGER, SMALLINT, BIGINT, NUMERIC, DECIMAL, DEC,
14-
BOOLEAN, DATE, TIME, TIMESTAMP, CASE, WHEN, THEN, ELSE, END, LIKE,
14+
BOOLEAN, DATE, TIME, TIMESTAMP, CASE, WHEN, THEN, ELSE, END, LIKE, JOIN, LEFT, RIGHT,
15+
FULL, CROSS, OUTER, INNER, NATURAL, ON, USING,
1516
];
1617
}
1718

src/dialect/postgresql.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ impl Dialect for PostgreSqlDialect {
1414
DOUBLE, PRECISION, INT, INTEGER, SMALLINT, BIGINT, NUMERIC, DECIMAL, DEC, BOOLEAN,
1515
DATE, TIME, TIMESTAMP, VALUES, DEFAULT, ZONE, REGCLASS, TEXT, BYTEA, TRUE, FALSE, COPY,
1616
STDIN, PRIMARY, KEY, UNIQUE, UUID, ADD, CONSTRAINT, FOREIGN, REFERENCES, CASE, WHEN,
17-
THEN, ELSE, END, LIKE,
17+
THEN, ELSE, END, LIKE, JOIN, LEFT, RIGHT, FULL, CROSS, OUTER, INNER, NATURAL, ON, USING,
1818
];
1919
}
2020

src/sqlast/mod.rs

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,8 @@ pub enum ASTNode {
7575
projection: Vec<ASTNode>,
7676
/// FROM
7777
relation: Option<Box<ASTNode>>,
78+
// JOIN
79+
joins: Vec<Join>,
7880
/// WHERE
7981
selection: Option<Box<ASTNode>>,
8082
/// ORDER BY
@@ -189,6 +191,7 @@ impl ToString for ASTNode {
189191
ASTNode::SQLSelect {
190192
projection,
191193
relation,
194+
joins,
192195
selection,
193196
order_by,
194197
group_by,
@@ -206,6 +209,9 @@ impl ToString for ASTNode {
206209
if let Some(relation) = relation {
207210
s += &format!(" FROM {}", relation.as_ref().to_string());
208211
}
212+
for join in joins {
213+
s += &join.to_string();
214+
}
209215
if let Some(selection) = selection {
210216
s += &format!(" WHERE {}", selection.as_ref().to_string());
211217
}
@@ -408,3 +414,72 @@ impl ToString for SQLColumnDef {
408414
s
409415
}
410416
}
417+
418+
#[derive(Debug, Clone, PartialEq)]
419+
pub struct Join {
420+
pub relation: ASTNode,
421+
pub join_operator: JoinOperator,
422+
}
423+
424+
impl ToString for Join {
425+
fn to_string(&self) -> String {
426+
fn prefix(constraint: &JoinConstraint) -> String {
427+
match constraint {
428+
JoinConstraint::Natural => "NATURAL ".to_string(),
429+
_ => "".to_string(),
430+
}
431+
}
432+
fn suffix(constraint: &JoinConstraint) -> String {
433+
match constraint {
434+
JoinConstraint::On(expr) => format!("ON {}", expr.to_string()),
435+
JoinConstraint::Using(attrs) => format!("USING({})", attrs.join(", ")),
436+
_ => "".to_string(),
437+
}
438+
}
439+
match &self.join_operator {
440+
JoinOperator::Inner(constraint) => format!(
441+
" {}JOIN {} {}",
442+
prefix(constraint),
443+
self.relation.to_string(),
444+
suffix(constraint)
445+
),
446+
JoinOperator::Cross => format!(" CROSS JOIN {}", self.relation.to_string()),
447+
JoinOperator::Implicit => format!(", {}", self.relation.to_string()),
448+
JoinOperator::LeftOuter(constraint) => format!(
449+
" {}LEFT JOIN {} {}",
450+
prefix(constraint),
451+
self.relation.to_string(),
452+
suffix(constraint)
453+
),
454+
JoinOperator::RightOuter(constraint) => format!(
455+
" {}RIGHT JOIN {} {}",
456+
prefix(constraint),
457+
self.relation.to_string(),
458+
suffix(constraint)
459+
),
460+
JoinOperator::FullOuter(constraint) => format!(
461+
" {}FULL JOIN {} {}",
462+
prefix(constraint),
463+
self.relation.to_string(),
464+
suffix(constraint)
465+
),
466+
}
467+
}
468+
}
469+
470+
#[derive(Debug, Clone, PartialEq)]
471+
pub enum JoinOperator {
472+
Inner(JoinConstraint),
473+
LeftOuter(JoinConstraint),
474+
RightOuter(JoinConstraint),
475+
FullOuter(JoinConstraint),
476+
Implicit,
477+
Cross,
478+
}
479+
480+
#[derive(Debug, Clone, PartialEq)]
481+
pub enum JoinConstraint {
482+
On(ASTNode),
483+
Using(Vec<String>),
484+
Natural,
485+
}

src/sqlparser.rs

Lines changed: 143 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -486,6 +486,18 @@ impl Parser {
486486
true
487487
}
488488

489+
pub fn expect_keyword(&mut self, expected: &'static str) -> Result<(), ParserError> {
490+
if self.parse_keyword(expected) {
491+
Ok(())
492+
} else {
493+
parser_err!(format!(
494+
"Expected keyword {}, found {:?}",
495+
expected,
496+
self.peek_token()
497+
))
498+
}
499+
}
500+
489501
//TODO: this function is inconsistent and sometimes returns bool and sometimes fails
490502

491503
/// Consume the next token if it matches the expected token, otherwise return an error
@@ -1105,11 +1117,12 @@ impl Parser {
11051117
pub fn parse_select(&mut self) -> Result<ASTNode, ParserError> {
11061118
let projection = self.parse_expr_list()?;
11071119

1108-
let relation: Option<Box<ASTNode>> = if self.parse_keyword("FROM") {
1109-
//TODO: add support for JOIN
1110-
Some(Box::new(self.parse_expr(0)?))
1120+
let (relation, joins): (Option<Box<ASTNode>>, Vec<Join>) = if self.parse_keyword("FROM") {
1121+
let relation = Some(Box::new(self.parse_expr(0)?));
1122+
let joins = self.parse_joins()?;
1123+
(relation, joins)
11111124
} else {
1112-
None
1125+
(None, vec![])
11131126
};
11141127

11151128
let selection = if self.parse_keyword("WHERE") {
@@ -1155,6 +1168,7 @@ impl Parser {
11551168
projection,
11561169
selection,
11571170
relation,
1171+
joins,
11581172
limit,
11591173
order_by,
11601174
group_by,
@@ -1163,6 +1177,131 @@ impl Parser {
11631177
}
11641178
}
11651179

1180+
fn parse_join_constraint(&mut self, natural: bool) -> Result<JoinConstraint, ParserError> {
1181+
if natural {
1182+
Ok(JoinConstraint::Natural)
1183+
} else if self.parse_keyword("ON") {
1184+
let constraint = self.parse_expr(0)?;
1185+
Ok(JoinConstraint::On(constraint))
1186+
} else if self.parse_keyword("USING") {
1187+
if self.consume_token(&Token::LParen)? {
1188+
let attributes = self
1189+
.parse_expr_list()?
1190+
.into_iter()
1191+
.map(|ast_node| match ast_node {
1192+
ASTNode::SQLIdentifier(ident) => Ok(ident),
1193+
unexpected => {
1194+
parser_err!(format!("Expected identifier, found {:?}", unexpected))
1195+
}
1196+
})
1197+
.collect::<Result<Vec<String>, ParserError>>()?;
1198+
1199+
if self.consume_token(&Token::RParen)? {
1200+
Ok(JoinConstraint::Using(attributes))
1201+
} else {
1202+
parser_err!(format!("Expected token ')', found {:?}", self.peek_token()))
1203+
}
1204+
} else {
1205+
parser_err!(format!("Expected token '(', found {:?}", self.peek_token()))
1206+
}
1207+
} else {
1208+
parser_err!(format!(
1209+
"Unexpected token after JOIN: {:?}",
1210+
self.peek_token()
1211+
))
1212+
}
1213+
}
1214+
1215+
fn parse_joins(&mut self) -> Result<Vec<Join>, ParserError> {
1216+
let mut joins = vec![];
1217+
loop {
1218+
let natural = match &self.peek_token() {
1219+
Some(Token::Comma) => {
1220+
self.next_token();
1221+
let relation = self.parse_expr(0)?;
1222+
let join = Join {
1223+
relation,
1224+
join_operator: JoinOperator::Implicit,
1225+
};
1226+
joins.push(join);
1227+
continue;
1228+
}
1229+
Some(Token::Keyword(kw)) if kw == "CROSS" => {
1230+
self.next_token();
1231+
self.expect_keyword("JOIN")?;
1232+
let relation = self.parse_expr(0)?;
1233+
let join = Join {
1234+
relation,
1235+
join_operator: JoinOperator::Cross,
1236+
};
1237+
joins.push(join);
1238+
continue;
1239+
}
1240+
Some(Token::Keyword(kw)) if kw == "NATURAL" => {
1241+
self.next_token();
1242+
true
1243+
}
1244+
Some(_) => false,
1245+
None => return Ok(joins),
1246+
};
1247+
1248+
let join = match &self.peek_token() {
1249+
Some(Token::Keyword(kw)) if kw == "INNER" => {
1250+
self.next_token();
1251+
self.expect_keyword("JOIN")?;
1252+
Join {
1253+
relation: self.parse_expr(0)?,
1254+
join_operator: JoinOperator::Inner(self.parse_join_constraint(natural)?),
1255+
}
1256+
}
1257+
Some(Token::Keyword(kw)) if kw == "JOIN" => {
1258+
self.next_token();
1259+
Join {
1260+
relation: self.parse_expr(0)?,
1261+
join_operator: JoinOperator::Inner(self.parse_join_constraint(natural)?),
1262+
}
1263+
}
1264+
Some(Token::Keyword(kw)) if kw == "LEFT" => {
1265+
self.next_token();
1266+
self.parse_keyword("OUTER");
1267+
self.expect_keyword("JOIN")?;
1268+
Join {
1269+
relation: self.parse_expr(0)?,
1270+
join_operator: JoinOperator::LeftOuter(
1271+
self.parse_join_constraint(natural)?,
1272+
),
1273+
}
1274+
}
1275+
Some(Token::Keyword(kw)) if kw == "RIGHT" => {
1276+
self.next_token();
1277+
self.parse_keyword("OUTER");
1278+
self.expect_keyword("JOIN")?;
1279+
Join {
1280+
relation: self.parse_expr(0)?,
1281+
join_operator: JoinOperator::RightOuter(
1282+
self.parse_join_constraint(natural)?,
1283+
),
1284+
}
1285+
}
1286+
Some(Token::Keyword(kw)) if kw == "FULL" => {
1287+
self.next_token();
1288+
self.parse_keyword("OUTER");
1289+
self.expect_keyword("JOIN")?;
1290+
Join {
1291+
relation: self.parse_expr(0)?,
1292+
join_operator: JoinOperator::FullOuter(
1293+
self.parse_join_constraint(natural)?,
1294+
),
1295+
}
1296+
}
1297+
_ => break,
1298+
};
1299+
joins.push(join);
1300+
}
1301+
1302+
Ok(joins)
1303+
}
1304+
11661305
/// Parse an INSERT statement
11671306
pub fn parse_insert(&mut self) -> Result<ASTNode, ParserError> {
11681307
self.parse_keyword("INTO");

0 commit comments

Comments
 (0)