Skip to content

Commit

Permalink
sql: fix scope tracking when planning joins
Browse files Browse the repository at this point in the history
  • Loading branch information
erikgrinaker committed Jul 9, 2024
1 parent 36c6e98 commit 48dde9a
Show file tree
Hide file tree
Showing 2 changed files with 36 additions and 19 deletions.
35 changes: 19 additions & 16 deletions src/sql/planner/planner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -277,29 +277,29 @@ impl<'a, C: Catalog> Planner<'a, C> {

// Build and implicitly join additional items.
for from in items {
// Each item is evaluated in its own, initially empty scope, and
// can't reference previous items. Consider e.g. "SELECT * FROM a,
// b JOIN c ON c.id = a.id" which is illegal (the join predicate can
// reference b or c, but not a).
let mut right_scope = Scope::new();
let right = self.build_from(from, &mut right_scope)?;
let left_size = scope.len();
let right = self.build_from(from, scope)?;
let right_size = scope.len() - left_size;
node = Node::NestedLoopJoin {
left: Box::new(node),
left_size: scope.len(),
left_size,
right: Box::new(right),
right_size: right_scope.len(),
right_size,
predicate: None,
outer: false,
};
scope.merge(right_scope)?;
}
Ok(node)
}

/// Builds FROM items, which can either be a single table or a chained join
/// of multiple tables, e.g. "SELECT * FROM a LEFT JOIN b ON b.a_id = a.id".
fn build_from(&self, from: ast::From, scope: &mut Scope) -> Result<Node> {
Ok(match from {
fn build_from(&self, from: ast::From, parent_scope: &mut Scope) -> Result<Node> {
// Each from item is built in its own scope, such that a join node only
// sees the columns of its children. It's then merged into the parent.
let mut scope = Scope::new();

let node = match from {
// A full table scan.
ast::From::Table { name, alias } => {
let table = self.catalog.must_get_table(&name)?;
Expand All @@ -316,13 +316,13 @@ impl<'a, C: Catalog> Planner<'a, C> {
}

// Build the left and right nodes.
let left = Box::new(self.build_from(*left, scope)?);
let left = Box::new(self.build_from(*left, &mut scope)?);
let left_size = scope.len();
let right = Box::new(self.build_from(*right, scope)?);
let right = Box::new(self.build_from(*right, &mut scope)?);
let right_size = scope.len() - left_size;

// Build the join node.
let predicate = predicate.map(|e| Self::build_expression(e, scope)).transpose()?;
// Build the join node.
let predicate = predicate.map(|e| Self::build_expression(e, &scope)).transpose()?;
let outer = r#type.is_outer();
let mut node =
Node::NestedLoopJoin { left, left_size, right, right_size, predicate, outer };
Expand All @@ -339,7 +339,10 @@ impl<'a, C: Catalog> Planner<'a, C> {
}
node
}
})
};

parent_scope.merge(scope)?;
Ok(node)
}

/// Builds an aggregation node. All aggregate parameters and GROUP BY expressions are evaluated
Expand Down
20 changes: 17 additions & 3 deletions src/sql/testscripts/queries/join_outer
Original file line number Diff line number Diff line change
Expand Up @@ -160,12 +160,26 @@ NestedLoopJoin: outer on genres.id > movies.id OR genres.id = movies.id
10, Inception, 4, 1, 2010, 8.8, TRUE, NULL, NULL

# Three-way join.
# TODO: this shouldn't panic.
[plan]!> SELECT * FROM studios \
[plan]> SELECT * FROM studios \
LEFT JOIN genres ON studios.id = genres.id \
RIGHT JOIN movies ON movies.id = studios.id
---
Panic: index out of bounds: the len is 3 but the index is 7
Projection: studios.id, studios.name, studios.country_id, genres.id, genres.name, movies.id, movies.title, movies.studio_id, movies.genre_id, movies.released, movies.rating, movies.ultrahd
└─ HashJoin: outer on movies.id = studios.id
├─ Scan: movies
└─ HashJoin: outer on studios.id = genres.id
├─ Scan: studios
└─ Scan: genres
1, Mosfilm, ru, 1, Science Fiction, 1, Stalker, 1, 1, 1979, 8.2, NULL
2, Lionsgate, us, 2, Action, 2, Sicario, 2, 2, 2015, 7.6, TRUE
3, StudioCanal, fr, 3, Comedy, 3, Primer, 3, 1, 2004, 6.9, NULL
4, Warner Bros, us, NULL, NULL, 4, Heat, 4, 2, 1995, 8.2, TRUE
NULL, NULL, NULL, NULL, NULL, 5, The Fountain, 4, 1, 2006, 7.2, FALSE
NULL, NULL, NULL, NULL, NULL, 6, Solaris, 1, 1, 1972, 8.1, NULL
NULL, NULL, NULL, NULL, NULL, 7, Gravity, 4, 1, 2013, 7.7, TRUE
NULL, NULL, NULL, NULL, NULL, 8, Blindspotting, 2, 3, 2018, 7.4, TRUE
NULL, NULL, NULL, NULL, NULL, 9, Birdman, 4, 3, 2014, 7.7, TRUE
NULL, NULL, NULL, NULL, NULL, 10, Inception, 4, 1, 2010, 8.8, TRUE

# Aliased tables.
[plan]> SELECT * FROM movies m LEFT JOIN genres AS g on m.id = g.id
Expand Down

0 comments on commit 48dde9a

Please sign in to comment.