Skip to content

Commit 989f6dd

Browse files
committed
impl join
1 parent e78e8d1 commit 989f6dd

3 files changed

Lines changed: 204 additions & 0 deletions

File tree

src/ast/query.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2751,6 +2751,12 @@ pub enum PipeOperator {
27512751
unpivot_columns: Vec<Ident>,
27522752
alias: Option<Ident>,
27532753
},
2754+
/// Joins the input table with another table.
2755+
///
2756+
/// Syntax: `|> [JOIN_TYPE] JOIN <table> [alias] ON <condition>` or `|> [JOIN_TYPE] JOIN <table> [alias] USING (<columns>)`
2757+
///
2758+
/// See more at <https://cloud.google.com/bigquery/docs/reference/standard-sql/pipe-syntax#join_pipe_operator>
2759+
Join(Join),
27542760
}
27552761

27562762
impl fmt::Display for PipeOperator {
@@ -2855,6 +2861,7 @@ impl fmt::Display for PipeOperator {
28552861
)?;
28562862
Self::fmt_optional_alias(f, alias)
28572863
}
2864+
PipeOperator::Join(join) => write!(f, "{}", join)
28582865
}
28592866
}
28602867
}

src/parser/mod.rs

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11128,6 +11128,12 @@ impl<'a> Parser<'a> {
1112811128
Keyword::CALL,
1112911129
Keyword::PIVOT,
1113011130
Keyword::UNPIVOT,
11131+
Keyword::JOIN,
11132+
Keyword::INNER,
11133+
Keyword::LEFT,
11134+
Keyword::RIGHT,
11135+
Keyword::FULL,
11136+
Keyword::CROSS,
1113111137
])?;
1113211138
match kw {
1113311139
Keyword::SELECT => {
@@ -11293,6 +11299,110 @@ impl<'a> Parser<'a> {
1129311299
alias,
1129411300
});
1129511301
}
11302+
Keyword::JOIN => {
11303+
let relation = self.parse_table_factor()?;
11304+
let constraint = self.parse_join_constraint(false)?;
11305+
if matches!(constraint, JoinConstraint::None) {
11306+
return Err(ParserError::ParserError(
11307+
"JOIN in pipe syntax requires ON or USING clause".to_string(),
11308+
));
11309+
}
11310+
let join_operator = JoinOperator::Join(constraint);
11311+
pipe_operators.push(PipeOperator::Join(Join {
11312+
relation,
11313+
global: false,
11314+
join_operator,
11315+
}))
11316+
}
11317+
Keyword::INNER => {
11318+
self.expect_keyword(Keyword::JOIN)?;
11319+
let relation = self.parse_table_factor()?;
11320+
let constraint = self.parse_join_constraint(false)?;
11321+
if matches!(constraint, JoinConstraint::None) {
11322+
return Err(ParserError::ParserError(
11323+
"INNER JOIN in pipe syntax requires ON or USING clause".to_string(),
11324+
));
11325+
}
11326+
let join_operator = JoinOperator::Inner(constraint);
11327+
pipe_operators.push(PipeOperator::Join(Join {
11328+
relation,
11329+
global: false,
11330+
join_operator,
11331+
}))
11332+
}
11333+
Keyword::LEFT => {
11334+
let outer = self.parse_keyword(Keyword::OUTER);
11335+
self.expect_keyword(Keyword::JOIN)?;
11336+
let relation = self.parse_table_factor()?;
11337+
let constraint = self.parse_join_constraint(false)?;
11338+
if matches!(constraint, JoinConstraint::None) {
11339+
let join_type = if outer { "LEFT OUTER JOIN" } else { "LEFT JOIN" };
11340+
return Err(ParserError::ParserError(format!(
11341+
"{} in pipe syntax requires ON or USING clause",
11342+
join_type
11343+
)));
11344+
}
11345+
let join_operator = if outer {
11346+
JoinOperator::LeftOuter(constraint)
11347+
} else {
11348+
JoinOperator::Left(constraint)
11349+
};
11350+
pipe_operators.push(PipeOperator::Join(Join {
11351+
relation,
11352+
global: false,
11353+
join_operator,
11354+
}))
11355+
}
11356+
Keyword::RIGHT => {
11357+
let outer = self.parse_keyword(Keyword::OUTER);
11358+
self.expect_keyword(Keyword::JOIN)?;
11359+
let relation = self.parse_table_factor()?;
11360+
let constraint = self.parse_join_constraint(false)?;
11361+
if matches!(constraint, JoinConstraint::None) {
11362+
let join_type = if outer { "RIGHT OUTER JOIN" } else { "RIGHT JOIN" };
11363+
return Err(ParserError::ParserError(format!(
11364+
"{} in pipe syntax requires ON or USING clause",
11365+
join_type
11366+
)));
11367+
}
11368+
let join_operator = if outer {
11369+
JoinOperator::RightOuter(constraint)
11370+
} else {
11371+
JoinOperator::Right(constraint)
11372+
};
11373+
pipe_operators.push(PipeOperator::Join(Join {
11374+
relation,
11375+
global: false,
11376+
join_operator,
11377+
}))
11378+
}
11379+
Keyword::FULL => {
11380+
let _outer = self.parse_keyword(Keyword::OUTER);
11381+
self.expect_keyword(Keyword::JOIN)?;
11382+
let relation = self.parse_table_factor()?;
11383+
let constraint = self.parse_join_constraint(false)?;
11384+
if matches!(constraint, JoinConstraint::None) {
11385+
return Err(ParserError::ParserError(
11386+
"FULL JOIN in pipe syntax requires ON or USING clause".to_string(),
11387+
));
11388+
}
11389+
let join_operator = JoinOperator::FullOuter(constraint);
11390+
pipe_operators.push(PipeOperator::Join(Join {
11391+
relation,
11392+
global: false,
11393+
join_operator,
11394+
}))
11395+
}
11396+
Keyword::CROSS => {
11397+
self.expect_keyword(Keyword::JOIN)?;
11398+
let relation = self.parse_table_factor()?;
11399+
let join_operator = JoinOperator::CrossJoin;
11400+
pipe_operators.push(PipeOperator::Join(Join {
11401+
relation,
11402+
global: false,
11403+
join_operator,
11404+
}))
11405+
}
1129611406
unhandled => {
1129711407
return Err(ParserError::ParserError(format!(
1129811408
"`expect_one_of_keywords` further up allowed unhandled keyword: {unhandled:?}"

tests/sqlparser_common.rs

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15360,6 +15360,58 @@ fn parse_pipeline_operator() {
1536015360
dialects.verified_stmt(
1536115361
"SELECT * FROM CustomerOrders |> AGGREGATE SUM(cost) AS total_cost GROUP BY customer_id, state, item_type |> EXTEND COUNT(*) OVER (PARTITION BY customer_id) AS num_orders |> WHERE num_orders > 1 |> AGGREGATE AVG(total_cost) AS average GROUP BY state DESC, item_type ASC",
1536215362
);
15363+
15364+
// join pipe operator - INNER JOIN
15365+
dialects.verified_stmt("SELECT * FROM users |> JOIN orders ON users.id = orders.user_id");
15366+
dialects.verified_stmt("SELECT * FROM users |> INNER JOIN orders ON users.id = orders.user_id");
15367+
15368+
// join pipe operator - LEFT JOIN
15369+
dialects.verified_stmt("SELECT * FROM users |> LEFT JOIN orders ON users.id = orders.user_id");
15370+
dialects.verified_stmt("SELECT * FROM users |> LEFT OUTER JOIN orders ON users.id = orders.user_id");
15371+
15372+
// join pipe operator - RIGHT JOIN
15373+
dialects.verified_stmt("SELECT * FROM users |> RIGHT JOIN orders ON users.id = orders.user_id");
15374+
dialects.verified_stmt("SELECT * FROM users |> RIGHT OUTER JOIN orders ON users.id = orders.user_id");
15375+
15376+
// join pipe operator - FULL JOIN
15377+
dialects.verified_stmt("SELECT * FROM users |> FULL JOIN orders ON users.id = orders.user_id");
15378+
dialects.verified_query_with_canonical(
15379+
"SELECT * FROM users |> FULL OUTER JOIN orders ON users.id = orders.user_id",
15380+
"SELECT * FROM users |> FULL JOIN orders ON users.id = orders.user_id",
15381+
);
15382+
15383+
// join pipe operator - CROSS JOIN
15384+
dialects.verified_stmt("SELECT * FROM users |> CROSS JOIN orders");
15385+
15386+
// join pipe operator with USING
15387+
dialects.verified_query_with_canonical(
15388+
"SELECT * FROM users |> JOIN orders USING (user_id)",
15389+
"SELECT * FROM users |> JOIN orders USING(user_id)",
15390+
);
15391+
dialects.verified_query_with_canonical(
15392+
"SELECT * FROM users |> LEFT JOIN orders USING (user_id, order_date)",
15393+
"SELECT * FROM users |> LEFT JOIN orders USING(user_id, order_date)",
15394+
);
15395+
15396+
// join pipe operator with alias
15397+
dialects.verified_query_with_canonical(
15398+
"SELECT * FROM users |> JOIN orders o ON users.id = o.user_id",
15399+
"SELECT * FROM users |> JOIN orders AS o ON users.id = o.user_id",
15400+
);
15401+
dialects.verified_stmt("SELECT * FROM users |> LEFT JOIN orders AS o ON users.id = o.user_id");
15402+
15403+
// join pipe operator with complex ON condition
15404+
dialects.verified_stmt("SELECT * FROM users |> JOIN orders ON users.id = orders.user_id AND orders.status = 'active'");
15405+
dialects.verified_stmt("SELECT * FROM users |> LEFT JOIN orders ON users.id = orders.user_id AND orders.amount > 100");
15406+
15407+
// multiple join pipe operators
15408+
dialects.verified_stmt("SELECT * FROM users |> JOIN orders ON users.id = orders.user_id |> JOIN products ON orders.product_id = products.id");
15409+
dialects.verified_stmt("SELECT * FROM users |> LEFT JOIN orders ON users.id = orders.user_id |> RIGHT JOIN products ON orders.product_id = products.id");
15410+
15411+
// join pipe operator with other pipe operators
15412+
dialects.verified_stmt("SELECT * FROM users |> JOIN orders ON users.id = orders.user_id |> WHERE orders.amount > 100");
15413+
dialects.verified_stmt("SELECT * FROM users |> WHERE users.active = true |> LEFT JOIN orders ON users.id = orders.user_id");
15414+
dialects.verified_stmt("SELECT * FROM users |> JOIN orders ON users.id = orders.user_id |> SELECT users.name, orders.amount");
1536315415
}
1536415416

1536515417
#[test]
@@ -15525,6 +15577,41 @@ fn parse_pipeline_operator_negative_tests() {
1552515577
assert!(dialects
1552615578
.parse_sql_statements("SELECT * FROM users |> UNPIVOT(value FOR name IN (col1, col2)")
1552715579
.is_err());
15580+
15581+
// Test that JOIN without table name fails
15582+
assert!(dialects
15583+
.parse_sql_statements("SELECT * FROM users |> JOIN ON users.id = orders.user_id")
15584+
.is_err());
15585+
15586+
// Test that JOIN without ON or USING condition fails (except CROSS JOIN)
15587+
assert!(dialects
15588+
.parse_sql_statements("SELECT * FROM users |> JOIN orders")
15589+
.is_err());
15590+
15591+
// Test that CROSS JOIN with ON condition fails
15592+
assert!(dialects
15593+
.parse_sql_statements("SELECT * FROM users |> CROSS JOIN orders ON users.id = orders.user_id")
15594+
.is_err());
15595+
15596+
// Test that CROSS JOIN with USING condition fails
15597+
assert!(dialects
15598+
.parse_sql_statements("SELECT * FROM users |> CROSS JOIN orders USING (user_id)")
15599+
.is_err());
15600+
15601+
// Test that JOIN with empty USING list fails
15602+
assert!(dialects
15603+
.parse_sql_statements("SELECT * FROM users |> JOIN orders USING ()")
15604+
.is_err());
15605+
15606+
// Test that JOIN with malformed ON condition fails
15607+
assert!(dialects
15608+
.parse_sql_statements("SELECT * FROM users |> JOIN orders ON")
15609+
.is_err());
15610+
15611+
// Test that JOIN with invalid USING syntax fails
15612+
assert!(dialects
15613+
.parse_sql_statements("SELECT * FROM users |> JOIN orders USING user_id")
15614+
.is_err());
1552815615
}
1552915616

1553015617
#[test]

0 commit comments

Comments
 (0)