Skip to content

Commit 2cbdae0

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 b15b4a7 commit 2cbdae0

File tree

4 files changed

+402
-0
lines changed

4 files changed

+402
-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: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
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+
/// Retrieves the best physical query plan (winner) for a given group.
79+
///
80+
/// If the group does not exist, returns a [`MemoError::UnknownGroup`] error.
81+
async fn get_winner(
82+
&self,
83+
group_id: Self::GroupId,
84+
) -> OptimizerResult<Option<Self::PhysicalExpressionId>>;
85+
86+
/// Updates / replaces a group's best physical plan (winner). Optionally returns the previous
87+
/// winner's physical expression ID.
88+
///
89+
/// If the group does not exist, returns a [`MemoError::UnknownGroup`] error.
90+
async fn update_group_winner(
91+
&self,
92+
group_id: Self::GroupId,
93+
physical_expression_id: Self::PhysicalExpressionId,
94+
) -> OptimizerResult<Option<Self::PhysicalExpressionId>>;
95+
96+
/// Adds a logical expression to an existing group via its [`Self::GroupId`].
97+
///
98+
/// The caller is required to pass in a vector of `GroupId` that represent the child groups of
99+
/// the input expression.
100+
///
101+
/// The caller is also required to set the `group_id` field of the input `logical_expression`
102+
/// to be equal to `group_id`, otherwise this function will return a
103+
/// [`MemoError::InvalidExpression`] error.
104+
///
105+
/// If the group does not exist, returns a [`MemoError::UnknownGroup`] error.
106+
async fn add_logical_expression_to_group(
107+
&self,
108+
group_id: Self::GroupId,
109+
logical_expression: Self::LogicalExpression,
110+
children: Vec<Self::GroupId>,
111+
) -> OptimizerResult<()>;
112+
113+
/// Adds a physical expression to an existing group via its [`Self::GroupId`].
114+
///
115+
/// The caller is required to pass in a vector of `GroupId` that represent the child groups of
116+
/// the input expression.
117+
///
118+
/// The caller is also required to set the `group_id` field of the input `physical_expression`
119+
/// to be equal to `group_id`, otherwise this function will return a
120+
/// [`MemoError::InvalidExpression`] error.
121+
///
122+
/// If the group does not exist, returns a [`MemoError::UnknownGroup`] error.
123+
async fn add_physical_expression_to_group(
124+
&self,
125+
group_id: Self::GroupId,
126+
physical_expression: Self::PhysicalExpression,
127+
children: Vec<Self::GroupId>,
128+
) -> OptimizerResult<()>;
129+
130+
/// Adds a new logical expression into the memo table, creating a new group if the expression
131+
/// does not already exist.
132+
///
133+
/// The [`Self::LogicalExpression`] type should have some sort of mechanism for checking if
134+
/// the expression has been seen before, and if it has already been created, then the parent
135+
/// group ID should also be retrievable.
136+
///
137+
/// If the expression already exists, then this function will return the [`Self::GroupId`] of
138+
/// the parent group and the corresponding (already existing) [`Self::LogicalExpressionId`].
139+
///
140+
/// If the expression does not exist, this function will create a new group and a new
141+
/// expression, returning brand new IDs for both.
142+
async fn add_logical_expression(
143+
&self,
144+
expression: Self::LogicalExpression,
145+
children: Vec<Self::LogicalExpressionId>,
146+
) -> OptimizerResult<(Self::GroupId, Self::LogicalExpressionId)>;
147+
}

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)