Skip to content

Commit

Permalink
add memo trait interface and persistent memo implementation
Browse files Browse the repository at this point in the history
This commit adds a first draft of a memo table trait and a persistent
memo table implementation backed by SeaORM entities.
  • Loading branch information
connortsui20 committed Nov 28, 2024
1 parent 06495a1 commit 8610184
Show file tree
Hide file tree
Showing 4 changed files with 419 additions and 0 deletions.
21 changes: 21 additions & 0 deletions optd-mvp/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,37 @@
use sea_orm::*;
use sea_orm_migration::prelude::*;
use thiserror::Error;

mod migrator;
use migrator::Migrator;

mod entities;

mod memo;
use memo::MemoError;

/// The filename of the SQLite database for migration.
pub const DATABASE_FILENAME: &str = "sqlite.db";
/// The URL of the SQLite database for migration.
pub const DATABASE_URL: &str = "sqlite:./sqlite.db?mode=rwc";

/// An error type wrapping all the different kinds of error the optimizer might raise.
///
/// TODO more docs.
#[derive(Error, Debug)]
pub enum OptimizerError {
#[error("SeaORM error")]
Database(#[from] sea_orm::error::DbErr),
#[error("Memo table logical error")]
Memo(#[from] MemoError),
#[error("unknown error")]
Unknown,
}

/// Shorthand for a [`Result`] with an error type [`OptimizerError`].
pub type OptimizerResult<T> = Result<T, OptimizerError>;

/// Applies all migrations.
pub async fn migrate(db: &DatabaseConnection) -> Result<(), DbErr> {
Migrator::refresh(db).await
}
146 changes: 146 additions & 0 deletions optd-mvp/src/memo/interface.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
use crate::OptimizerResult;
use thiserror::Error;

#[derive(Error, Debug)]
/// The different kinds of errors that might occur while running operations on a memo table.
pub enum MemoError {
#[error("unknown group ID {0}")]
UnknownGroup(i32),
#[error("unknown logical expression ID {0}")]
UnknownLogicalExpression(i32),
#[error("unknown physical expression ID {0}")]
UnknownPhysicalExpression(i32),
#[error("invalid expression encountered")]
InvalidExpression,
}

/// A trait representing an implementation of a memoization table.
///
/// Note that we use [`trait_variant`] here in order to add bounds on every method.
/// See this [blog post](
/// https://blog.rust-lang.org/2023/12/21/async-fn-rpit-in-traits.html#async-fn-in-public-traits)
/// for more information.
#[allow(dead_code)]
#[trait_variant::make(Send)]
pub trait Memo {
/// A type representing a group in the Cascades framework.
type Group;
/// A type representing a unique identifier for a group.
type GroupId;
/// A type representing a logical expression.
type LogicalExpression;
/// A type representing a unique identifier for a logical expression.
type LogicalExpressionId;
/// A type representing a physical expression.
type PhysicalExpression;
/// A type representing a unique identifier for a physical expression.
type PhysicalExpressionId;

/// Retrieves a [`Self::Group`] given a [`Self::GroupId`].
///
/// If the group does not exist, returns a [`MemoError::UnknownGroup`] error.
async fn get_group(&self, group_id: Self::GroupId) -> OptimizerResult<Self::Group>;

/// Retrieves a [`Self::LogicalExpression`] given a [`Self::LogicalExpressionId`].
///
/// If the logical expression does not exist, returns a [`MemoError::UnknownLogicalExpression`]
/// error.
async fn get_logical_expression(
&self,
logical_expression_id: Self::LogicalExpressionId,
) -> OptimizerResult<Self::LogicalExpression>;

/// Retrieves a [`Self::PhysicalExpression`] given a [`Self::PhysicalExpressionId`].
///
/// If the physical expression does not exist, returns a
/// [`MemoError::UnknownPhysicalExpression`] error.
async fn get_physical_expression(
&self,
physical_expression_id: Self::PhysicalExpressionId,
) -> OptimizerResult<Self::PhysicalExpression>;

/// Retrieves all of the logical expression "children" IDs of a group.
///
/// If the group does not exist, returns a [`MemoError::UnknownGroup`] error.
async fn get_logical_children(
&self,
group_id: Self::GroupId,
) -> OptimizerResult<Vec<Self::LogicalExpressionId>>;

/// Retrieves all of the physical expression "children" IDs of a group.
///
/// If the group does not exist, returns a [`MemoError::UnknownGroup`] error.
async fn get_physical_children(
&self,
group_id: Self::GroupId,
) -> OptimizerResult<Vec<Self::PhysicalExpressionId>>;

/// Updates / replaces a group's best physical plan (winner). Optionally returns the previous
/// winner's physical expression ID.
///
/// If the group does not exist, returns a [`MemoError::UnknownGroup`] error.
async fn update_group_winner(
&self,
group_id: Self::GroupId,
physical_expression_id: Self::PhysicalExpressionId,
) -> OptimizerResult<Option<Self::PhysicalExpressionId>>;

/// Adds a logical expression to an existing group via its [`Self::GroupId`]. This function
/// assumes that insertion of this expression would not create any duplicates.
///
/// The caller is required to pass in a slice of `GroupId` that represent the child groups of
/// the input expression.
///
/// The caller is also required to set the `group_id` field of the input `logical_expression`
/// to be equal to `group_id`, otherwise this function will return a
/// [`MemoError::InvalidExpression`] error.
///
/// If the group does not exist, returns a [`MemoError::UnknownGroup`] error.
async fn add_logical_expression_to_group(
&self,
group_id: Self::GroupId,
logical_expression: Self::LogicalExpression,
children: &[Self::GroupId],
) -> OptimizerResult<()>;

/// Adds a physical expression to an existing group via its [`Self::GroupId`]. This function
/// assumes that insertion of this expression would not create any duplicates.
///
/// The caller is required to pass in a slice of `GroupId` that represent the child groups of
/// the input expression.
///
/// The caller is also required to set the `group_id` field of the input `physical_expression`
/// to be equal to `group_id`, otherwise this function will return a
/// [`MemoError::InvalidExpression`] error.
///
/// If the group does not exist, returns a [`MemoError::UnknownGroup`] error.
async fn add_physical_expression_to_group(
&self,
group_id: Self::GroupId,
physical_expression: Self::PhysicalExpression,
children: &[Self::GroupId],
) -> OptimizerResult<()>;

/// Adds a new logical expression into the memo table, creating a new group if the expression
/// does not already exist.
///
/// The caller is required to pass in a slice of `GroupId` that represent the child groups of
/// the input expression.
///
/// The [`Self::LogicalExpression`] type should have some sort of mechanism for checking if
/// the expression has been seen before, and if it has already been created, then the parent
/// group ID should also be retrievable.
///
/// If the expression already exists, then this function will return the [`Self::GroupId`] of
/// the parent group and the corresponding (already existing) [`Self::LogicalExpressionId`]. It
/// will also completely ignore the group ID field of the input expression as well as ignore the
/// input slice of child groups.
///
/// If the expression does not exist, this function will create a new group and a new
/// expression, returning brand new IDs for both.
async fn add_logical_expression(
&self,
expression: Self::LogicalExpression,
children: &[Self::LogicalExpressionId],
) -> OptimizerResult<(Self::GroupId, Self::LogicalExpressionId)>;
}
9 changes: 9 additions & 0 deletions optd-mvp/src/memo/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
//! This module contains items related to the memo table, which is key to the Cascades query
//! optimization framework.
//!
//! TODO more docs.
mod persistent;

mod interface;
pub use interface::{Memo, MemoError};
Loading

0 comments on commit 8610184

Please sign in to comment.