(
p: JoinPlanParams<'_>,
)
| 74 | } |
| 75 | |
| 76 | pub(in crate::control::planner::sql_plan_convert) fn convert_join( |
| 77 | p: JoinPlanParams<'_>, |
| 78 | ) -> crate::Result<Vec<PhysicalTask>> { |
| 79 | let JoinPlanParams { |
| 80 | left, |
| 81 | right, |
| 82 | on, |
| 83 | join_type, |
| 84 | condition, |
| 85 | limit, |
| 86 | projection, |
| 87 | filters, |
| 88 | tenant_id, |
| 89 | ctx, |
| 90 | } = p; |
| 91 | let mut left_collection = |
| 92 | super::super::convert::db_qualified(p.ctx.database_id, &extract_collection_name(left)); |
| 93 | let mut right_collection = |
| 94 | super::super::convert::db_qualified(p.ctx.database_id, &extract_collection_name(right)); |
| 95 | let mut left_alias = extract_scan_alias(left); |
| 96 | let mut right_alias = extract_scan_alias(right); |
| 97 | let join_projection = extract_join_projection_specs(projection); |
| 98 | let filter_bytes = serialize_join_filters(filters, condition)?; |
| 99 | |
| 100 | // Check if the left side is a nested join (multi-way join). |
| 101 | // If so, convert the inner join to a physical plan and pass it |
| 102 | // as `inline_left` so the executor runs it first. |
| 103 | let inline_left = if matches!(left, SqlPlan::Join { .. }) { |
| 104 | let inner_tasks = convert_one(left, tenant_id, ctx)?; |
| 105 | inner_tasks.into_iter().next().map(|t| Box::new(t.plan)) |
| 106 | } else { |
| 107 | None |
| 108 | }; |
| 109 | let inline_right = super::super::aggregate::inline_join_side(right, tenant_id, ctx)?; |
| 110 | |
| 111 | // RIGHT JOIN → swap sides and convert to LEFT JOIN. |
| 112 | let mut on_keys = on.to_vec(); |
| 113 | let mut inline_left = inline_left; |
| 114 | let mut inline_right = inline_right; |
| 115 | let effective_join_type = if join_type.as_str() == "right" { |
| 116 | std::mem::swap(&mut left_collection, &mut right_collection); |
| 117 | std::mem::swap(&mut left_alias, &mut right_alias); |
| 118 | std::mem::swap(&mut inline_left, &mut inline_right); |
| 119 | on_keys = on_keys.into_iter().map(|(l, r)| (r, l)).collect(); |
| 120 | "left".to_string() |
| 121 | } else { |
| 122 | join_type.as_str().to_string() |
| 123 | }; |
| 124 | |
| 125 | // Analyze join children for selective-predicate bitmap pushdown. |
| 126 | // The analysis runs on the *original* (pre-swap) children since it inspects |
| 127 | // SqlPlan shape. After the RIGHT→LEFT swap, we swap the resulting hints too. |
| 128 | let bitmap_hints = nodedb_sql::planner::bitmap_emit::hashjoin::analyze_join_sides(left, right); |
| 129 | let (mut raw_left_bm, mut raw_right_bm) = (bitmap_hints.left, bitmap_hints.right); |
| 130 | if join_type.as_str() == "right" { |
| 131 | std::mem::swap(&mut raw_left_bm, &mut raw_right_bm); |
| 132 | } |
| 133 | let db_id = p.ctx.database_id; |
nothing calls this directly
no test coverage detected