Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Printer for the plan, ie: EXPLAIN #2075

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,7 @@ enum-as-inner = "0.6"
enum-map = "2.6.3"
env_logger = "0.10"
ethnum = { version = "1.5.0", features = ["serde"] }
expect-test = "1.5.0"
flate2 = "1.0.24"
foldhash = "0.1.4"
fs-err = "2.9.0"
Expand Down
1 change: 1 addition & 0 deletions crates/core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ derive_more.workspace = true
dirs.workspace = true
enum-as-inner.workspace = true
enum-map.workspace = true
expect-test.workspace = true
flate2.workspace = true
fs2.workspace = true
futures.workspace = true
Expand Down
2 changes: 1 addition & 1 deletion crates/core/src/sql/ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -977,7 +977,7 @@ pub(crate) fn compile_to_ast<T: TableSchemaView + StateView>(
) -> Result<Vec<SqlAst>, DBError> {
// NOTE: The following ensures compliance with the 1.0 sql api.
// Come 1.0, it will have replaced the current compilation stack.
compile_sql_stmt(sql_text, &SchemaViewer::new(tx, auth))?;
compile_sql_stmt(sql_text, &SchemaViewer::new(tx, auth), false)?;

let dialect = PostgreSqlDialect {};
let ast = Parser::parse_sql(&dialect, sql_text).map_err(|error| DBError::SqlParser {
Expand Down
1 change: 1 addition & 0 deletions crates/core/src/sql/execute.rs
Original file line number Diff line number Diff line change
Expand Up @@ -364,6 +364,7 @@ pub(crate) mod tests {
let result = run_for_testing(&db, sql)?;
assert_eq!(result, vec![product![5u64]], "Inventory");

// TODO: This should return product![2u64]
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// TODO: This should return product![2u64]

Aggregates are applied before LIMIT.

let sql = "SELECT count(*) as n FROM inventory limit 2";
let result = run_for_testing(&db, sql)?;
assert_eq!(result, vec![product![5u64]], "Inventory");
Expand Down
6 changes: 3 additions & 3 deletions crates/execution/src/pipelined.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ impl From<ProjectListPlan> for ProjectListExecutor {
ProjectListPlan::Name(plan) => Self::Name(plan.into()),
ProjectListPlan::List(plan, fields) => Self::List(plan.into(), fields),
ProjectListPlan::Limit(plan, n) => Self::Limit(Box::new((*plan).into()), n),
ProjectListPlan::Agg(plan, AggType::Count) => Self::Agg(plan.into(), AggType::Count),
ProjectListPlan::Agg(plan, agg) => Self::Agg(plan.into(), agg),
}
}
}
Expand Down Expand Up @@ -78,10 +78,10 @@ impl ProjectListExecutor {
// and if so, we retrieve the count from table metadata.
// It's a valid optimization but one that should be done by the optimizer.
// There should be no optimizations performed during execution.
Self::Agg(PipelinedExecutor::TableScan(table_scan), AggType::Count) => {
Self::Agg(PipelinedExecutor::TableScan(table_scan), AggType::Count { alias: _ }) => {
f(product![tx.table_or_err(table_scan.table)?.num_rows()])?;
}
Self::Agg(plan, AggType::Count) => {
Self::Agg(plan, AggType::Count { alias: _ }) => {
plan.execute(tx, metrics, &mut |_| {
n += 1;
Ok(())
Expand Down
8 changes: 7 additions & 1 deletion crates/expr/src/check.rs
Original file line number Diff line number Diff line change
Expand Up @@ -165,11 +165,17 @@ pub fn type_subscription(ast: SqlSelect, tx: &impl SchemaView) -> TypingResult<P
}

/// Parse and type check a *subscription* query into a `StatementCtx`
pub fn compile_sql_sub<'a>(sql: &'a str, tx: &impl SchemaView) -> TypingResult<StatementCtx<'a>> {
pub fn compile_sql_sub<'a>(sql: &'a str, tx: &impl SchemaView, with_timings: bool) -> TypingResult<StatementCtx<'a>> {
let planning_time = if with_timings {
Some(std::time::Instant::now())
} else {
None
};
Ok(StatementCtx {
statement: Statement::Select(ProjectList::Name(parse_and_type_sub(sql, tx)?)),
sql,
source: StatementSource::Subscription,
planning_time: planning_time.map(|t| t.elapsed()),
})
}

Expand Down
6 changes: 3 additions & 3 deletions crates/expr/src/expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,12 +75,12 @@ pub enum ProjectList {
Name(ProjectName),
List(RelExpr, Vec<(Box<str>, FieldProject)>),
Limit(Box<ProjectList>, u64),
Agg(RelExpr, AggType, Box<str>, AlgebraicType),
Agg(RelExpr, AggType, AlgebraicType),
}

#[derive(Debug)]
pub enum AggType {
Count,
Count { alias: Box<str> },
}

impl ProjectList {
Expand Down Expand Up @@ -120,7 +120,7 @@ impl ProjectList {
f(name, ty);
}
}
Self::Agg(_, _, name, ty) => f(name, ty),
Self::Agg(_, AggType::Count { alias }, ty) => f(alias, ty),
}
}
}
Expand Down
6 changes: 5 additions & 1 deletion crates/expr/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,9 @@ pub(crate) fn type_proj(input: RelExpr, proj: ast::Project, vars: &Relvars) -> T
Ok(ProjectList::Name(ProjectName::Some(input, var)))
}
ast::Project::Star(Some(SqlIdent(var))) => Err(Unresolved::var(&var).into()),
ast::Project::Count(SqlIdent(alias)) => Ok(ProjectList::Agg(input, AggType::Count, alias, AlgebraicType::U64)),
ast::Project::Count(SqlIdent(alias)) => {
Ok(ProjectList::Agg(input, AggType::Count { alias }, AlgebraicType::U64))
}
ast::Project::Exprs(elems) => {
let mut projections = vec![];
let mut names = HashSet::new();
Expand Down Expand Up @@ -326,6 +328,7 @@ pub(crate) fn parse(value: &str, ty: &AlgebraicType) -> anyhow::Result<Algebraic
}

/// The source of a statement
#[derive(Debug, Clone, Copy)]
pub enum StatementSource {
Subscription,
Query,
Expand All @@ -338,4 +341,5 @@ pub struct StatementCtx<'a> {
pub statement: Statement,
pub sql: &'a str,
pub source: StatementSource,
pub planning_time: Option<std::time::Duration>,
}
8 changes: 7 additions & 1 deletion crates/expr/src/statement.rs
Original file line number Diff line number Diff line change
Expand Up @@ -439,12 +439,18 @@ pub fn parse_and_type_sql(sql: &str, tx: &impl SchemaView) -> TypingResult<State
}

/// Parse and type check a *general* query into a [StatementCtx].
pub fn compile_sql_stmt<'a>(sql: &'a str, tx: &impl SchemaView) -> TypingResult<StatementCtx<'a>> {
pub fn compile_sql_stmt<'a>(sql: &'a str, tx: &impl SchemaView, with_timings: bool) -> TypingResult<StatementCtx<'a>> {
let planning_time = if with_timings {
Some(std::time::Instant::now())
} else {
None
};
let statement = parse_and_type_sql(sql, tx)?;
Ok(StatementCtx {
statement,
sql,
source: StatementSource::Query,
planning_time: planning_time.map(|t| t.elapsed()),
})
}

Expand Down
2 changes: 2 additions & 0 deletions crates/physical-plan/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ description = "The physical query plan for the SpacetimeDB query engine"
[dependencies]
anyhow.workspace = true
derive_more.workspace = true
expect-test.workspace = true
itertools.workspace = true
spacetimedb-lib.workspace = true
spacetimedb-primitives.workspace = true
spacetimedb-schema.workspace = true
Expand Down
32 changes: 30 additions & 2 deletions crates/physical-plan/src/compile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,10 @@ use crate::plan::{
HashJoin, Label, PhysicalExpr, PhysicalPlan, ProjectListPlan, ProjectPlan, Semi, TableScan, TupleField,
};

use crate::{PhysicalCtx, PlanCtx};
use spacetimedb_expr::expr::{Expr, FieldProject, LeftDeepJoin, ProjectList, ProjectName, RelExpr, Relvar};
use spacetimedb_expr::statement::DML;
use spacetimedb_expr::statement::{Statement, DML};
use spacetimedb_expr::StatementCtx;

pub trait VarLabel {
fn label(&mut self, name: &str) -> Label;
Expand Down Expand Up @@ -146,6 +148,12 @@ struct NamesToIds {
map: HashMap<String, usize>,
}

impl NamesToIds {
fn into_map(self) -> HashMap<String, usize> {
self.map
}
}

impl VarLabel for NamesToIds {
fn label(&mut self, name: &str) -> Label {
if let Some(id) = self.map.get(name) {
Expand All @@ -168,7 +176,11 @@ pub fn compile_select(project: ProjectName) -> ProjectPlan {
/// Note, this utility is applicable to a generic selections.
/// In particular, it supports explicit column projections.
pub fn compile_select_list(project: ProjectList) -> ProjectListPlan {
compile_project_list(&mut NamesToIds::default(), project)
compile_select_list_raw(&mut NamesToIds::default(), project)
}

pub fn compile_select_list_raw(var: &mut impl VarLabel, project: ProjectList) -> ProjectListPlan {
compile_project_list(var, project)
}

/// Converts a logical DML statement into a physical plan,
Expand All @@ -180,3 +192,19 @@ pub fn compile_dml_plan(stmt: DML) -> MutationPlan {
DML::Update(update) => MutationPlan::Update(UpdatePlan::compile(update)),
}
}

pub fn compile(ast: StatementCtx<'_>) -> PhysicalCtx<'_> {
let mut vars = NamesToIds::default();
let plan = match ast.statement {
Statement::Select(project) => PlanCtx::ProjectList(compile_select_list_raw(&mut vars, project)),
Statement::DML(stmt) => PlanCtx::DML(compile_dml_plan(stmt)),
};

PhysicalCtx {
plan,
sql: ast.sql,
vars: vars.into_map(),
source: ast.source,
planning_time: None,
}
}
4 changes: 4 additions & 0 deletions crates/physical-plan/src/dml.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ use spacetimedb_schema::schema::TableSchema;
use crate::{compile::compile_select, plan::ProjectPlan};

/// A plan for mutating a table in the database
#[derive(Debug)]
pub enum MutationPlan {
Insert(InsertPlan),
Delete(DeletePlan),
Expand All @@ -30,6 +31,7 @@ impl MutationPlan {
}

/// A plan for inserting rows into a table
#[derive(Debug)]
pub struct InsertPlan {
pub table: Arc<TableSchema>,
pub rows: Vec<ProductValue>,
Expand All @@ -44,6 +46,7 @@ impl From<TableInsert> for InsertPlan {
}

/// A plan for deleting rows from a table
#[derive(Debug)]
pub struct DeletePlan {
pub table: Arc<TableSchema>,
pub filter: ProjectPlan,
Expand Down Expand Up @@ -77,6 +80,7 @@ impl DeletePlan {
}

/// A plan for updating rows in a table
#[derive(Debug)]
pub struct UpdatePlan {
pub table: Arc<TableSchema>,
pub columns: Vec<(ColId, AlgebraicValue)>,
Expand Down
42 changes: 42 additions & 0 deletions crates/physical-plan/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,46 @@
use crate::dml::MutationPlan;
use crate::plan::ProjectListPlan;
use anyhow::Result;
use spacetimedb_expr::StatementSource;
use std::collections::HashMap;

pub mod compile;
pub mod dml;
pub mod plan;
pub mod printer;
pub mod rules;

#[derive(Debug)]
pub enum PlanCtx {
ProjectList(ProjectListPlan),
DML(MutationPlan),
}

impl PlanCtx {
pub(crate) fn optimize(self) -> Result<PlanCtx> {
Ok(match self {
Self::ProjectList(plan) => Self::ProjectList(plan.optimize()?),
Self::DML(plan) => Self::DML(plan.optimize()?),
})
}
}

/// A physical context for the result of a query compilation.
#[derive(Debug)]
pub struct PhysicalCtx<'a> {
pub plan: PlanCtx,
pub sql: &'a str,
// A map from table names to their labels
pub vars: HashMap<String, usize>,
pub source: StatementSource,
pub planning_time: Option<std::time::Duration>,
}

impl PhysicalCtx<'_> {
pub fn optimize(self) -> Result<Self> {
Ok(Self {
plan: self.plan.optimize()?,
..self
})
}
}
Loading
Loading