Skip to content

Commit 4d60811

Browse files
committed
add memo trait interface and persistent memo implementation
This commit adds a first draft of a memo table trait and a persistent memo table implementation backed by SeaORM entities.
1 parent 06495a1 commit 4d60811

File tree

4 files changed

+387
-0
lines changed

4 files changed

+387
-0
lines changed

optd-mvp/src/lib.rs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,37 @@
11
use sea_orm::*;
22
use sea_orm_migration::prelude::*;
3+
use thiserror::Error;
34

45
mod migrator;
56
use migrator::Migrator;
67

78
mod entities;
89

10+
mod memo;
11+
use memo::MemoError;
12+
913
/// The filename of the SQLite database for migration.
1014
pub const DATABASE_FILENAME: &str = "sqlite.db";
1115
/// The URL of the SQLite database for migration.
1216
pub const DATABASE_URL: &str = "sqlite:./sqlite.db?mode=rwc";
1317

18+
/// An error type wrapping all the different kinds of error the optimizer might raise.
19+
///
20+
/// TODO more docs.
21+
#[derive(Error, Debug)]
22+
pub enum OptimizerError {
23+
#[error("SeaORM error")]
24+
Database(#[from] sea_orm::error::DbErr),
25+
#[error("Memo table logical error")]
26+
Memo(#[from] MemoError),
27+
#[error("unknown error")]
28+
Unknown,
29+
}
30+
31+
/// Shorthand for a [`Result`] with an error type [`OptimizerError`].
32+
pub type OptimizerResult<T> = Result<T, OptimizerError>;
33+
34+
/// Applies all migrations.
1435
pub async fn migrate(db: &DatabaseConnection) -> Result<(), DbErr> {
1536
Migrator::refresh(db).await
1637
}

optd-mvp/src/memo/interface.rs

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
use crate::OptimizerResult;
2+
use thiserror::Error;
3+
4+
#[derive(Error, Debug)]
5+
/// The different kinds of errors that might occur while running operations on a memo table.
6+
pub enum MemoError {
7+
#[error("unknown group ID {0}")]
8+
UnknownGroup(i32),
9+
#[error("unknown logical expression ID {0}")]
10+
UnknownLogicalExpression(i32),
11+
#[error("unknown physical expression ID {0}")]
12+
UnknownPhysicalExpression(i32),
13+
#[error("invalid expression encountered")]
14+
InvalidExpression,
15+
}
16+
17+
/// A trait representing an implementation of a memoization table.
18+
///
19+
/// Note that we use [`trait_variant`] here in order to add bounds on every method.
20+
/// See this [blog post](
21+
/// https://blog.rust-lang.org/2023/12/21/async-fn-rpit-in-traits.html#async-fn-in-public-traits)
22+
/// for more information.
23+
#[allow(dead_code)]
24+
#[trait_variant::make(Send)]
25+
pub trait Memo {
26+
/// A type representing a group in the Cascades framework.
27+
type Group;
28+
/// A type representing a unique identifier for a group.
29+
type GroupId;
30+
/// A type representing a logical expression.
31+
type LogicalExpression;
32+
/// A type representing a unique identifier for a logical expression.
33+
type LogicalExpressionId;
34+
/// A type representing a physical expression.
35+
type PhysicalExpression;
36+
/// A type representing a unique identifier for a physical expression.
37+
type PhysicalExpressionId;
38+
39+
/// Retrieves a [`Self::Group`] given a [`Self::GroupId`].
40+
///
41+
/// If the group does not exist, returns a [`MemoError::UnknownGroup`] error.
42+
async fn get_group(&self, group_id: Self::GroupId) -> OptimizerResult<Self::Group>;
43+
44+
/// Retrieves a [`Self::LogicalExpression`] given a [`Self::LogicalExpressionId`].
45+
///
46+
/// If the logical expression does not exist, returns a [`MemoError::UnknownLogicalExpression`]
47+
/// error.
48+
async fn get_logical_expression(
49+
&self,
50+
logical_expression_id: Self::LogicalExpressionId,
51+
) -> OptimizerResult<Self::LogicalExpression>;
52+
53+
/// Retrieves a [`Self::PhysicalExpression`] given a [`Self::PhysicalExpressionId`].
54+
///
55+
/// If the physical expression does not exist, returns a
56+
/// [`MemoError::UnknownPhysicalExpression`] error.
57+
async fn get_physical_expression(
58+
&self,
59+
physical_expression_id: Self::PhysicalExpressionId,
60+
) -> OptimizerResult<Self::PhysicalExpression>;
61+
62+
/// Retrieves all of the logical expression "children" IDs of a group.
63+
///
64+
/// If the group does not exist, returns a [`MemoError::UnknownGroup`] error.
65+
async fn get_logical_children(
66+
&self,
67+
group_id: Self::GroupId,
68+
) -> OptimizerResult<Vec<Self::LogicalExpressionId>>;
69+
70+
/// Retrieves all of the physical expression "children" IDs of a group.
71+
///
72+
/// If the group does not exist, returns a [`MemoError::UnknownGroup`] error.
73+
async fn get_physical_children(
74+
&self,
75+
group_id: Self::GroupId,
76+
) -> OptimizerResult<Vec<Self::PhysicalExpressionId>>;
77+
78+
/// Updates / replaces a group's best physical plan (winner). Optionally returns the previous
79+
/// winner's physical expression ID.
80+
///
81+
/// If the group does not exist, returns a [`MemoError::UnknownGroup`] error.
82+
async fn update_group_winner(
83+
&self,
84+
group_id: Self::GroupId,
85+
physical_expression_id: Self::PhysicalExpressionId,
86+
) -> OptimizerResult<Option<Self::PhysicalExpressionId>>;
87+
88+
/// Adds a logical expression to an existing group via its [`Self::GroupId`].
89+
///
90+
/// The caller is required to pass in a vector of `GroupId` that represent the child groups of
91+
/// the input expression.
92+
///
93+
/// The caller is also required to set the `group_id` field of the input `logical_expression`
94+
/// to be equal to `group_id`, otherwise this function will return a
95+
/// [`MemoError::InvalidExpression`] error.
96+
///
97+
/// If the group does not exist, returns a [`MemoError::UnknownGroup`] error.
98+
async fn add_logical_expression_to_group(
99+
&self,
100+
group_id: Self::GroupId,
101+
logical_expression: Self::LogicalExpression,
102+
children: Vec<Self::GroupId>,
103+
) -> OptimizerResult<()>;
104+
105+
/// Adds a physical expression to an existing group via its [`Self::GroupId`].
106+
///
107+
/// The caller is required to pass in a vector of `GroupId` that represent the child groups of
108+
/// the input expression.
109+
///
110+
/// The caller is also required to set the `group_id` field of the input `physical_expression`
111+
/// to be equal to `group_id`, otherwise this function will return a
112+
/// [`MemoError::InvalidExpression`] error.
113+
///
114+
/// If the group does not exist, returns a [`MemoError::UnknownGroup`] error.
115+
async fn add_physical_expression_to_group(
116+
&self,
117+
group_id: Self::GroupId,
118+
physical_expression: Self::PhysicalExpression,
119+
children: Vec<Self::GroupId>,
120+
) -> OptimizerResult<()>;
121+
122+
/// Adds a new logical expression into the memo table, creating a new group if the expression
123+
/// does not already exist.
124+
///
125+
/// The [`Self::LogicalExpression`] type should have some sort of mechanism for checking if
126+
/// the expression has been seen before, and if it has already been created, then the parent
127+
/// group ID should also be retrievable.
128+
///
129+
/// If the expression already exists, then this function will return the [`Self::GroupId`] of
130+
/// the parent group and the corresponding (already existing) [`Self::LogicalExpressionId`].
131+
///
132+
/// If the expression does not exist, this function will create a new group and a new
133+
/// expression, returning brand new IDs for both.
134+
async fn add_logical_expression(
135+
&self,
136+
expression: Self::LogicalExpression,
137+
children: Vec<Self::LogicalExpressionId>,
138+
) -> OptimizerResult<(Self::GroupId, Self::LogicalExpressionId)>;
139+
}

optd-mvp/src/memo/mod.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
//! This module contains items related to the memo table, which is key to the Cascades query
2+
//! optimization framework.
3+
//!
4+
//! TODO more docs.
5+
6+
mod persistent;
7+
8+
mod interface;
9+
pub use interface::{Memo, MemoError};

0 commit comments

Comments
 (0)