Skip to content

Commit

Permalink
wip sql: avoid catalog lookups during execution
Browse files Browse the repository at this point in the history
  • Loading branch information
erikgrinaker committed Jun 18, 2024
1 parent 5a1756a commit 4540a05
Show file tree
Hide file tree
Showing 115 changed files with 17,370 additions and 317 deletions.
4 changes: 2 additions & 2 deletions src/sql/engine/session.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ impl<'a, E: Engine<'a>> Session<'a, E> {
}
// TODO: this needs testing.
ast::Statement::Explain(statement) => self.with_txn_read_only(|txn| {
Ok(StatementResult::Explain(Plan::build(*statement, txn)?.optimize(txn)?))
Ok(StatementResult::Explain(Plan::build(*statement, txn)?.optimize()?))
}),
statement if self.txn.is_some() => {
Self::execute_with(statement, self.txn.as_mut().unwrap())
Expand Down Expand Up @@ -101,7 +101,7 @@ impl<'a, E: Engine<'a>> Session<'a, E> {
statement: ast::Statement,
txn: &mut E::Transaction,
) -> Result<StatementResult> {
Plan::build(statement, txn)?.optimize(txn)?.execute(txn)?.try_into()
Plan::build(statement, txn)?.optimize()?.execute(txn)?.try_into()
}

/// Runs a read-only closure in the session's transaction, or a new
Expand Down
2 changes: 1 addition & 1 deletion src/sql/execution/execute.rs
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ pub fn execute(node: Node, txn: &impl Transaction) -> Result<QueryIterator> {
Ok(transform::project(source, expressions))
}

Node::Scan { table, alias: _, filter } => source::scan(txn, &table, filter),
Node::Scan { table, alias: _, filter } => source::scan(txn, table, filter),
}
}

Expand Down
5 changes: 2 additions & 3 deletions src/sql/execution/source.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
use super::QueryIterator;
use crate::error::Result;
use crate::sql::engine::Transaction;
use crate::sql::types::schema::Table;
use crate::sql::types::{Column, Expression, Row, Value};

/// A table scan source.
pub(super) fn scan(
txn: &impl Transaction,
table: &str,
table: Table,
filter: Option<Expression>,
) -> Result<QueryIterator> {
// TODO: this should not be fallible. Pass the schema in the plan node.
let table = txn.must_get_table(table)?;
Ok(QueryIterator {
columns: table.columns.into_iter().map(|c| Column { name: Some(c.name) }).collect(),
rows: Box::new(txn.scan(&table.name, filter)?),
Expand Down
24 changes: 9 additions & 15 deletions src/sql/plan/optimizer.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
use super::super::types::{Expression, Value};
use super::plan::Node;
use crate::error::Result;
use crate::sql::engine::Catalog;

use std::mem::replace;

Expand Down Expand Up @@ -158,15 +157,9 @@ impl FilterPushdown {
}

/// An index lookup optimizer, which converts table scans to index lookups.
pub struct IndexLookup<'a, C: Catalog> {
catalog: &'a mut C,
}

impl<'a, C: Catalog> IndexLookup<'a, C> {
pub fn new(catalog: &'a mut C) -> Self {
Self { catalog }
}
pub struct IndexLookup;

impl IndexLookup {
// Wraps a node in a filter for the given CNF vector, if any, otherwise returns the bare node.
fn wrap_cnf(&self, node: Node, cnf: Vec<Expression>) -> Node {
if let Some(predicate) = Expression::from_cnf_vec(cnf) {
Expand All @@ -177,12 +170,11 @@ impl<'a, C: Catalog> IndexLookup<'a, C> {
}
}

impl<'a, C: Catalog> Optimizer for IndexLookup<'a, C> {
impl Optimizer for IndexLookup {
fn optimize(&self, node: Node) -> Result<Node> {
node.transform(&Ok, &|n| match n {
Node::Scan { table, alias, filter: Some(filter) } => {
let columns = self.catalog.must_get_table(&table)?.columns;
let pk = columns.iter().position(|c| c.primary_key).unwrap();
let pk = table.columns.iter().position(|c| c.primary_key).unwrap();

// Convert the filter into conjunctive normal form, and try to convert each
// sub-expression into a lookup. If a lookup is found, return a lookup node and then
Expand All @@ -191,14 +183,16 @@ impl<'a, C: Catalog> Optimizer for IndexLookup<'a, C> {
for i in 0..cnf.len() {
if let Some(keys) = cnf[i].as_lookup(pk) {
cnf.remove(i);
return Ok(self.wrap_cnf(Node::KeyLookup { table, alias, keys }, cnf));
return Ok(
self.wrap_cnf(Node::KeyLookup { table: table.name, alias, keys }, cnf)
);
}
for (ci, column) in columns.iter().enumerate().filter(|(_, c)| c.index) {
for (ci, column) in table.columns.iter().enumerate().filter(|(_, c)| c.index) {
if let Some(values) = cnf[i].as_lookup(ci) {
cnf.remove(i);
return Ok(self.wrap_cnf(
Node::IndexLookup {
table,
table: table.name,
alias,
column: column.name.clone(),
values,
Expand Down
10 changes: 5 additions & 5 deletions src/sql/plan/plan.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,11 @@ impl Plan {
}

/// Optimizes the plan, consuming it.
pub fn optimize<C: Catalog>(self, catalog: &mut C) -> Result<Self> {
let mut optimize = |mut node| -> Result<Node> {
pub fn optimize(self) -> Result<Self> {
let optimize = |mut node| -> Result<Node> {
node = optimizer::ConstantFolder.optimize(node)?;
node = optimizer::FilterPushdown.optimize(node)?;
node = optimizer::IndexLookup::new(catalog).optimize(node)?;
node = optimizer::IndexLookup.optimize(node)?;
node = optimizer::NoopCleaner.optimize(node)?;
node = optimizer::JoinType.optimize(node)?;
Ok(node)
Expand Down Expand Up @@ -154,7 +154,7 @@ pub enum Node {
expressions: Vec<(Expression, Option<String>)>,
},
Scan {
table: String,
table: Table,
alias: Option<String>,
filter: Option<Expression>,
},
Expand Down Expand Up @@ -378,7 +378,7 @@ impl Node {
s += &source.format(indent, false, true);
}
Self::Scan { table, alias, filter } => {
s += &format!("Scan: {}", table);
s += &format!("Scan: {}", table.name);
if let Some(alias) = alias {
s += &format!(" as {}", alias);
}
Expand Down
18 changes: 9 additions & 9 deletions src/sql/plan/planner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,9 +69,10 @@ impl<'a, C: Catalog> Planner<'a, C> {

// DML statements (mutations).
ast::Statement::Delete { table, r#where } => {
let scope = &mut Scope::from_table(self.catalog.must_get_table(&table)?)?;
let table = self.catalog.must_get_table(&table)?;
let scope = &mut Scope::from_table(table.clone())?;
Plan::Delete {
table: table.clone(),
table: table.name.clone(),
source: Node::Scan {
table,
alias: None,
Expand All @@ -95,9 +96,10 @@ impl<'a, C: Catalog> Planner<'a, C> {
},

ast::Statement::Update { table, set, r#where } => {
let scope = &mut Scope::from_table(self.catalog.must_get_table(&table)?)?;
let table = self.catalog.must_get_table(&table)?;
let scope = &mut Scope::from_table(table.clone())?;
Plan::Update {
table: table.clone(),
table: table.name.clone(),
source: Node::Scan {
table,
alias: None,
Expand Down Expand Up @@ -285,11 +287,9 @@ impl<'a, C: Catalog> Planner<'a, C> {
fn build_from_item(&self, scope: &mut Scope, item: ast::FromItem) -> Result<Node> {
Ok(match item {
ast::FromItem::Table { name, alias } => {
scope.add_table(
alias.clone().unwrap_or_else(|| name.clone()),
self.catalog.must_get_table(&name)?,
)?;
Node::Scan { table: name, alias, filter: None }
let table = self.catalog.must_get_table(&name)?;
scope.add_table(alias.clone().unwrap_or_else(|| name.clone()), table.clone())?;
Node::Scan { table, alias, filter: None }
}

ast::FromItem::Join { left, right, r#type, predicate } => {
Expand Down
4 changes: 2 additions & 2 deletions tests/sql/query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ macro_rules! test_query {
// First, just try to generate a plan and execute it
let result = Parser::new($query).parse()
.and_then(|ast| Plan::build(ast, &mut txn))
.and_then(|plan| plan.optimize(&mut txn))
.and_then(|plan| plan.optimize())
.and_then(|plan| {
write!(f, "Explain:\n{}\n\n", plan)?;
plan.execute(&mut txn).and_then(|r| r.try_into())
Expand Down Expand Up @@ -128,7 +128,7 @@ macro_rules! test_query {
write!(f, "{:#?}\n\n", plan)?;

write!(f, "Optimized plan: ")?;
let plan = match plan.optimize(&mut txn) {
let plan = match plan.optimize() {
Ok(plan) => plan,
Err(err) => {
write!(f, "{:?}", err)?;
Expand Down
56 changes: 54 additions & 2 deletions tests/sql/query/agg_boolean
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,33 @@ Plan: Select(
source: Projection {
source: Filter {
source: Scan {
table: "booleans",
table: Table {
name: "booleans",
columns: [
Column {
name: "id",
datatype: Integer,
primary_key: true,
nullable: false,
default: None,
unique: true,
references: None,
index: false,
},
Column {
name: "b",
datatype: Boolean,
primary_key: false,
nullable: true,
default: Some(
Null,
),
unique: false,
references: None,
index: false,
},
],
},
alias: None,
filter: None,
},
Expand Down Expand Up @@ -239,7 +265,33 @@ Optimized plan: Select(
source: Aggregation {
source: Projection {
source: Scan {
table: "booleans",
table: Table {
name: "booleans",
columns: [
Column {
name: "id",
datatype: Integer,
primary_key: true,
nullable: false,
default: None,
unique: true,
references: None,
index: false,
},
Column {
name: "b",
datatype: Boolean,
primary_key: false,
nullable: true,
default: Some(
Null,
),
unique: false,
references: None,
index: false,
},
],
},
alias: None,
filter: Some(
Not(
Expand Down
56 changes: 54 additions & 2 deletions tests/sql/query/agg_boolean_null
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,33 @@ Plan: Select(
source: Aggregation {
source: Projection {
source: Scan {
table: "booleans",
table: Table {
name: "booleans",
columns: [
Column {
name: "id",
datatype: Integer,
primary_key: true,
nullable: false,
default: None,
unique: true,
references: None,
index: false,
},
Column {
name: "b",
datatype: Boolean,
primary_key: false,
nullable: true,
default: Some(
Null,
),
unique: false,
references: None,
index: false,
},
],
},
alias: None,
filter: None,
},
Expand Down Expand Up @@ -211,7 +237,33 @@ Optimized plan: Select(
source: Aggregation {
source: Projection {
source: Scan {
table: "booleans",
table: Table {
name: "booleans",
columns: [
Column {
name: "id",
datatype: Integer,
primary_key: true,
nullable: false,
default: None,
unique: true,
references: None,
index: false,
},
Column {
name: "b",
datatype: Boolean,
primary_key: false,
nullable: true,
default: Some(
Null,
),
unique: false,
references: None,
index: false,
},
],
},
alias: None,
filter: None,
},
Expand Down
Loading

0 comments on commit 4540a05

Please sign in to comment.