|
15 | 15 | // specific language governing permissions and limitations |
16 | 16 | // under the License. |
17 | 17 |
|
18 | | -use datafusion_common::{internal_err, not_impl_err, plan_err, DataFusionError, Result}; |
| 18 | +use datafusion_common::{ |
| 19 | + internal_err, not_impl_err, plan_err, Column, DataFusionError, Result, |
| 20 | +}; |
19 | 21 | use datafusion_expr::{ |
20 | 22 | expr::Alias, Distinct, Expr, JoinConstraint, JoinType, LogicalPlan, Projection, |
21 | 23 | }; |
@@ -368,37 +370,11 @@ impl Unparser<'_> { |
368 | 370 | self.select_to_sql_recursively(input, query, select, relation) |
369 | 371 | } |
370 | 372 | LogicalPlan::Join(join) => { |
371 | | - match join.join_constraint { |
372 | | - JoinConstraint::On => {} |
373 | | - JoinConstraint::Using => { |
374 | | - return not_impl_err!( |
375 | | - "Unsupported join constraint: {:?}", |
376 | | - join.join_constraint |
377 | | - ) |
378 | | - } |
379 | | - } |
380 | | - |
381 | | - // parse filter if exists |
382 | | - let join_filter = match &join.filter { |
383 | | - Some(filter) => Some(self.expr_to_sql(filter)?), |
384 | | - None => None, |
385 | | - }; |
386 | | - |
387 | | - // map join.on to `l.a = r.a AND l.b = r.b AND ...` |
388 | | - let eq_op = ast::BinaryOperator::Eq; |
389 | | - let join_on = self.join_conditions_to_sql(&join.on, eq_op)?; |
390 | | - |
391 | | - // Merge `join_on` and `join_filter` |
392 | | - let join_expr = match (join_filter, join_on) { |
393 | | - (Some(filter), Some(on)) => Some(self.and_op_to_sql(filter, on)), |
394 | | - (Some(filter), None) => Some(filter), |
395 | | - (None, Some(on)) => Some(on), |
396 | | - (None, None) => None, |
397 | | - }; |
398 | | - let join_constraint = match join_expr { |
399 | | - Some(expr) => ast::JoinConstraint::On(expr), |
400 | | - None => ast::JoinConstraint::None, |
401 | | - }; |
| 373 | + let join_constraint = self.join_constraint_to_sql( |
| 374 | + join.join_constraint, |
| 375 | + &join.on, |
| 376 | + join.filter.as_ref(), |
| 377 | + )?; |
402 | 378 |
|
403 | 379 | let mut right_relation = RelationBuilder::default(); |
404 | 380 |
|
@@ -583,24 +559,108 @@ impl Unparser<'_> { |
583 | 559 | } |
584 | 560 | } |
585 | 561 |
|
586 | | - fn join_conditions_to_sql( |
| 562 | + /// Convert the components of a USING clause to the USING AST. Returns |
| 563 | + /// 'None' if the conditions are not compatible with a USING expression, |
| 564 | + /// e.g. non-column expressions or non-matching names. |
| 565 | + fn join_using_to_sql( |
587 | 566 | &self, |
588 | | - join_conditions: &Vec<(Expr, Expr)>, |
589 | | - eq_op: ast::BinaryOperator, |
590 | | - ) -> Result<Option<ast::Expr>> { |
591 | | - // Only support AND conjunction for each binary expression in join conditions |
592 | | - let mut exprs: Vec<ast::Expr> = vec![]; |
| 567 | + join_conditions: &[(Expr, Expr)], |
| 568 | + ) -> Option<ast::JoinConstraint> { |
| 569 | + let mut idents = Vec::with_capacity(join_conditions.len()); |
593 | 570 | for (left, right) in join_conditions { |
594 | | - // Parse left |
| 571 | + match (left, right) { |
| 572 | + ( |
| 573 | + Expr::Column(Column { |
| 574 | + relation: _, |
| 575 | + name: left_name, |
| 576 | + }), |
| 577 | + Expr::Column(Column { |
| 578 | + relation: _, |
| 579 | + name: right_name, |
| 580 | + }), |
| 581 | + ) if left_name == right_name => { |
| 582 | + idents.push(self.new_ident_quoted_if_needs(left_name.to_string())); |
| 583 | + } |
| 584 | + // USING is only valid with matching column names; arbitrary expressions |
| 585 | + // are not allowed |
| 586 | + _ => return None, |
| 587 | + } |
| 588 | + } |
| 589 | + Some(ast::JoinConstraint::Using(idents)) |
| 590 | + } |
| 591 | + |
| 592 | + /// Convert a join constraint and associated conditions and filter to a SQL AST node |
| 593 | + fn join_constraint_to_sql( |
| 594 | + &self, |
| 595 | + constraint: JoinConstraint, |
| 596 | + conditions: &[(Expr, Expr)], |
| 597 | + filter: Option<&Expr>, |
| 598 | + ) -> Result<ast::JoinConstraint> { |
| 599 | + match (constraint, conditions, filter) { |
| 600 | + // No constraints |
| 601 | + (JoinConstraint::On | JoinConstraint::Using, [], None) => { |
| 602 | + Ok(ast::JoinConstraint::None) |
| 603 | + } |
| 604 | + |
| 605 | + (JoinConstraint::Using, conditions, None) => { |
| 606 | + match self.join_using_to_sql(conditions) { |
| 607 | + Some(using) => Ok(using), |
| 608 | + // As above, this should not be reachable from parsed SQL, |
| 609 | + // but a user could create this; we "downgrade" to ON. |
| 610 | + None => self.join_conditions_to_sql_on(conditions, None), |
| 611 | + } |
| 612 | + } |
| 613 | + |
| 614 | + // Two cases here: |
| 615 | + // 1. Straightforward ON case, with possible equi-join conditions |
| 616 | + // and additional filters |
| 617 | + // 2. USING with additional filters; we "downgrade" to ON, because |
| 618 | + // you can't use USING with arbitrary filters. (This should not |
| 619 | + // be accessible from parsed SQL, but may have been a |
| 620 | + // custom-built JOIN by a user.) |
| 621 | + (JoinConstraint::On | JoinConstraint::Using, conditions, filter) => { |
| 622 | + self.join_conditions_to_sql_on(conditions, filter) |
| 623 | + } |
| 624 | + } |
| 625 | + } |
| 626 | + |
| 627 | + // Convert a list of equi0join conditions and an optional filter to a SQL ON |
| 628 | + // AST node, with the equi-join conditions and the filter merged into a |
| 629 | + // single conditional expression |
| 630 | + fn join_conditions_to_sql_on( |
| 631 | + &self, |
| 632 | + join_conditions: &[(Expr, Expr)], |
| 633 | + filter: Option<&Expr>, |
| 634 | + ) -> Result<ast::JoinConstraint> { |
| 635 | + let mut condition = None; |
| 636 | + // AND the join conditions together to create the overall condition |
| 637 | + for (left, right) in join_conditions { |
| 638 | + // Parse left and right |
595 | 639 | let l = self.expr_to_sql(left)?; |
596 | | - // Parse right |
597 | 640 | let r = self.expr_to_sql(right)?; |
598 | | - // AND with existing expression |
599 | | - exprs.push(self.binary_op_to_sql(l, r, eq_op.clone())); |
| 641 | + let e = self.binary_op_to_sql(l, r, ast::BinaryOperator::Eq); |
| 642 | + condition = match condition { |
| 643 | + Some(expr) => Some(self.and_op_to_sql(expr, e)), |
| 644 | + None => Some(e), |
| 645 | + }; |
600 | 646 | } |
601 | | - let join_expr: Option<ast::Expr> = |
602 | | - exprs.into_iter().reduce(|r, l| self.and_op_to_sql(r, l)); |
603 | | - Ok(join_expr) |
| 647 | + |
| 648 | + // Then AND the non-equijoin filter condition as well |
| 649 | + condition = match (condition, filter) { |
| 650 | + (Some(expr), Some(filter)) => { |
| 651 | + Some(self.and_op_to_sql(expr, self.expr_to_sql(filter)?)) |
| 652 | + } |
| 653 | + (Some(expr), None) => Some(expr), |
| 654 | + (None, Some(filter)) => Some(self.expr_to_sql(filter)?), |
| 655 | + (None, None) => None, |
| 656 | + }; |
| 657 | + |
| 658 | + let constraint = match condition { |
| 659 | + Some(filter) => ast::JoinConstraint::On(filter), |
| 660 | + None => ast::JoinConstraint::None, |
| 661 | + }; |
| 662 | + |
| 663 | + Ok(constraint) |
604 | 664 | } |
605 | 665 |
|
606 | 666 | fn and_op_to_sql(&self, lhs: ast::Expr, rhs: ast::Expr) -> ast::Expr { |
|
0 commit comments