Skip to content

Commit 8610184

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 8610184

File tree

4 files changed

+419
-0
lines changed

4 files changed

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

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)