Skip to content

Commit 4d603d2

Browse files
authored
feat: add EVMProjectionExec and EVMSliceExec (#778)
Please be sure to look over the pull request guidelines here: https://github.com/spaceandtimelabs/sxt-proof-of-sql/blob/main/CONTRIBUTING.md#submit-pr. # Please go through the following checklist - [x] The PR title and commit messages adhere to guidelines here: https://github.com/spaceandtimelabs/sxt-proof-of-sql/blob/main/CONTRIBUTING.md. In particular `!` is used if and only if at least one breaking change has been introduced. - [x] I have run the ci check script with `source scripts/run_ci_checks.sh`. - [x] I have run the clean commit check script with `source scripts/check_commits.sh`, and the commit history is certified to follow clean commit guidelines as described here: https://github.com/spaceandtimelabs/sxt-proof-of-sql/blob/main/COMMIT_GUIDELINES.md - [x] The latest changes from `main` have been incorporated to this PR by simple rebase if possible, if not, then conflicts are resolved appropriately. # Rationale for this change Add more `ProofExec` to `evm_proof_plan` <!-- Why are you proposing this change? If this is already explained clearly in the linked issue then this section is not needed. Explaining clearly why changes are proposed helps reviewers understand your changes and offer better suggestions for fixes. Example: Add `NestedLoopJoinExec`. Closes #345. Since we added `HashJoinExec` in #323 it has been possible to do provable inner joins. However performance is not satisfactory in some cases. Hence we need to fix the problem by implement `NestedLoopJoinExec` and speed up the code for `HashJoinExec`. --> # What changes are included in this PR? See title <!-- There is no need to duplicate the description in the ticket here but it is sometimes worth providing a summary of the individual changes in this PR. Example: - Add `NestedLoopJoinExec`. - Speed up `HashJoinExec`. - Route joins to `NestedLoopJoinExec` if the outer input is sufficiently small. --> # Are these changes tested? <!-- We typically require tests for all PRs in order to: 1. Prevent the code from being accidentally broken by subsequent changes 2. Serve as another way to document the expected behavior of the code If tests are not included in your PR, please explain why (for example, are they covered by existing tests)? Example: Yes. --> Yes.
2 parents 11964f1 + f556bb5 commit 4d603d2

File tree

3 files changed

+264
-2
lines changed

3 files changed

+264
-2
lines changed

crates/proof-of-sql/src/sql/evm_proof_plan/plans.rs

+239-2
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,10 @@ use crate::{
66
},
77
sql::{
88
proof_exprs::{AliasedDynProofExpr, TableExpr},
9-
proof_plans::{DynProofPlan, EmptyExec, FilterExec, TableExec},
9+
proof_plans::{DynProofPlan, EmptyExec, FilterExec, ProjectionExec, SliceExec, TableExec},
1010
},
1111
};
12-
use alloc::{string::String, vec::Vec};
12+
use alloc::{boxed::Box, string::String, vec::Vec};
1313
use serde::{Deserialize, Serialize};
1414
use sqlparser::ast::Ident;
1515

@@ -19,6 +19,8 @@ pub(crate) enum EVMDynProofPlan {
1919
Filter(EVMFilterExec),
2020
Empty(EVMEmptyExec),
2121
Table(EVMTableExec),
22+
Projection(EVMProjectionExec),
23+
Slice(EVMSliceExec),
2224
}
2325

2426
impl EVMDynProofPlan {
@@ -39,6 +41,14 @@ impl EVMDynProofPlan {
3941
EVMFilterExec::try_from_proof_plan(filter_exec, table_refs, column_refs)
4042
.map(Self::Filter)
4143
}
44+
DynProofPlan::Projection(projection_exec) => {
45+
EVMProjectionExec::try_from_proof_plan(projection_exec, table_refs, column_refs)
46+
.map(Self::Projection)
47+
}
48+
DynProofPlan::Slice(slice_exec) => {
49+
EVMSliceExec::try_from_proof_plan(slice_exec, table_refs, column_refs)
50+
.map(Self::Slice)
51+
}
4252
_ => Err(EVMProofPlanError::NotSupported),
4353
}
4454
}
@@ -59,6 +69,16 @@ impl EVMDynProofPlan {
5969
EVMDynProofPlan::Filter(filter_exec) => Ok(DynProofPlan::Filter(
6070
filter_exec.try_into_proof_plan(table_refs, column_refs, output_column_names)?,
6171
)),
72+
EVMDynProofPlan::Projection(projection_exec) => Ok(DynProofPlan::Projection(
73+
projection_exec.try_into_proof_plan(
74+
table_refs,
75+
column_refs,
76+
output_column_names,
77+
)?,
78+
)),
79+
EVMDynProofPlan::Slice(slice_exec) => Ok(DynProofPlan::Slice(
80+
slice_exec.try_into_proof_plan(table_refs, column_refs)?,
81+
)),
6282
}
6383
}
6484
}
@@ -175,6 +195,103 @@ impl EVMFilterExec {
175195
}
176196
}
177197

198+
/// Represents a projection execution plan in EVM.
199+
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
200+
pub(crate) struct EVMProjectionExec {
201+
input_plan: Box<EVMDynProofPlan>,
202+
results: Vec<EVMDynProofExpr>,
203+
}
204+
205+
impl EVMProjectionExec {
206+
/// Try to create a `EVMProjectionExec` from a `ProjectionExec`.
207+
pub(crate) fn try_from_proof_plan(
208+
plan: &ProjectionExec,
209+
table_refs: &IndexSet<TableRef>,
210+
column_refs: &IndexSet<ColumnRef>,
211+
) -> EVMProofPlanResult<Self> {
212+
Ok(Self {
213+
input_plan: Box::new(EVMDynProofPlan::try_from_proof_plan(
214+
plan.input(),
215+
table_refs,
216+
column_refs,
217+
)?),
218+
results: plan
219+
.aliased_results()
220+
.iter()
221+
.map(|result| EVMDynProofExpr::try_from_proof_expr(&result.expr, column_refs))
222+
.collect::<Result<_, _>>()?,
223+
})
224+
}
225+
226+
pub(crate) fn try_into_proof_plan(
227+
&self,
228+
table_refs: &IndexSet<TableRef>,
229+
column_refs: &IndexSet<ColumnRef>,
230+
output_column_names: &IndexSet<String>,
231+
) -> EVMProofPlanResult<ProjectionExec> {
232+
Ok(ProjectionExec::new(
233+
self.results
234+
.iter()
235+
.zip(output_column_names.iter())
236+
.map(|(expr, name)| {
237+
Ok(AliasedDynProofExpr {
238+
expr: expr.try_into_proof_expr(column_refs)?,
239+
alias: Ident::new(name),
240+
})
241+
})
242+
.collect::<EVMProofPlanResult<Vec<_>>>()?,
243+
Box::new(self.input_plan.try_into_proof_plan(
244+
table_refs,
245+
column_refs,
246+
output_column_names,
247+
)?),
248+
))
249+
}
250+
}
251+
252+
/// Represents a slice execution plan in EVM.
253+
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
254+
pub(crate) struct EVMSliceExec {
255+
input_plan: Box<EVMDynProofPlan>,
256+
skip: usize,
257+
fetch: Option<usize>,
258+
}
259+
260+
impl EVMSliceExec {
261+
/// Try to create a `EVMSliceExec` from a `SliceExec`.
262+
pub(crate) fn try_from_proof_plan(
263+
plan: &SliceExec,
264+
table_refs: &IndexSet<TableRef>,
265+
column_refs: &IndexSet<ColumnRef>,
266+
) -> EVMProofPlanResult<Self> {
267+
Ok(Self {
268+
input_plan: Box::new(EVMDynProofPlan::try_from_proof_plan(
269+
plan.input(),
270+
table_refs,
271+
column_refs,
272+
)?),
273+
skip: plan.skip(),
274+
fetch: plan.fetch(),
275+
})
276+
}
277+
278+
pub(crate) fn try_into_proof_plan(
279+
&self,
280+
table_refs: &IndexSet<TableRef>,
281+
column_refs: &IndexSet<ColumnRef>,
282+
) -> EVMProofPlanResult<SliceExec> {
283+
Ok(SliceExec::new(
284+
Box::new(self.input_plan.try_into_proof_plan(
285+
table_refs,
286+
column_refs,
287+
&IndexSet::default(),
288+
)?),
289+
self.skip,
290+
self.fetch,
291+
))
292+
}
293+
}
294+
178295
#[cfg(test)]
179296
mod tests {
180297
use super::*;
@@ -190,6 +307,126 @@ mod tests {
190307
},
191308
};
192309

310+
#[test]
311+
fn we_can_put_projection_exec_in_evm() {
312+
let table_ref: TableRef = "namespace.table".parse().unwrap();
313+
let ident_a: Ident = "a".into();
314+
let ident_b: Ident = "b".into();
315+
let alias = "alias".to_string();
316+
317+
let column_ref_a = ColumnRef::new(table_ref.clone(), ident_a.clone(), ColumnType::BigInt);
318+
let column_ref_b = ColumnRef::new(table_ref.clone(), ident_b.clone(), ColumnType::BigInt);
319+
320+
// Create a table exec to use as the input
321+
let column_fields = vec![
322+
ColumnField::new(ident_a.clone(), ColumnType::BigInt),
323+
ColumnField::new(ident_b.clone(), ColumnType::BigInt),
324+
];
325+
let table_exec = TableExec::new(table_ref.clone(), column_fields);
326+
327+
// Create a projection exec
328+
let projection_exec = ProjectionExec::new(
329+
vec![AliasedDynProofExpr {
330+
expr: DynProofExpr::Column(ColumnExpr::new(column_ref_b.clone())),
331+
alias: Ident::new(alias.clone()),
332+
}],
333+
Box::new(DynProofPlan::Table(table_exec)),
334+
);
335+
336+
// Convert to EVM plan
337+
let evm_projection_exec = EVMProjectionExec::try_from_proof_plan(
338+
&projection_exec,
339+
&indexset![table_ref.clone()],
340+
&indexset![column_ref_a.clone(), column_ref_b.clone()],
341+
)
342+
.unwrap();
343+
344+
// Verify the structure
345+
assert_eq!(evm_projection_exec.results.len(), 1);
346+
assert!(matches!(
347+
evm_projection_exec.results[0],
348+
EVMDynProofExpr::Column(_)
349+
));
350+
assert!(matches!(
351+
*evm_projection_exec.input_plan,
352+
EVMDynProofPlan::Table(_)
353+
));
354+
355+
// Roundtrip
356+
let roundtripped_projection_exec = EVMProjectionExec::try_into_proof_plan(
357+
&evm_projection_exec,
358+
&indexset![table_ref.clone()],
359+
&indexset![column_ref_a.clone(), column_ref_b.clone()],
360+
&indexset![alias],
361+
)
362+
.unwrap();
363+
364+
// Verify the roundtripped plan has the expected structure
365+
assert_eq!(roundtripped_projection_exec.aliased_results().len(), 1);
366+
assert!(matches!(
367+
roundtripped_projection_exec.aliased_results()[0].expr,
368+
DynProofExpr::Column(_)
369+
));
370+
assert!(matches!(
371+
*roundtripped_projection_exec.input(),
372+
DynProofPlan::Table(_)
373+
));
374+
}
375+
376+
#[test]
377+
fn we_can_put_slice_exec_in_evm() {
378+
let table_ref: TableRef = "namespace.table".parse().unwrap();
379+
let ident_a: Ident = "a".into();
380+
let ident_b: Ident = "b".into();
381+
382+
let column_ref_a = ColumnRef::new(table_ref.clone(), ident_a.clone(), ColumnType::BigInt);
383+
let column_ref_b = ColumnRef::new(table_ref.clone(), ident_b.clone(), ColumnType::BigInt);
384+
385+
// Create a table exec to use as the input
386+
let column_fields = vec![
387+
ColumnField::new(ident_a.clone(), ColumnType::BigInt),
388+
ColumnField::new(ident_b.clone(), ColumnType::BigInt),
389+
];
390+
let table_exec = TableExec::new(table_ref.clone(), column_fields);
391+
392+
// Create a slice exec
393+
let skip = 10;
394+
let fetch = Some(5);
395+
let slice_exec = SliceExec::new(Box::new(DynProofPlan::Table(table_exec)), skip, fetch);
396+
397+
// Convert to EVM plan
398+
let evm_slice_exec = EVMSliceExec::try_from_proof_plan(
399+
&slice_exec,
400+
&indexset![table_ref.clone()],
401+
&indexset![column_ref_a.clone(), column_ref_b.clone()],
402+
)
403+
.unwrap();
404+
405+
// Verify the structure
406+
assert_eq!(evm_slice_exec.skip, skip);
407+
assert_eq!(evm_slice_exec.fetch, fetch);
408+
assert!(matches!(
409+
*evm_slice_exec.input_plan,
410+
EVMDynProofPlan::Table(_)
411+
));
412+
413+
// Roundtrip
414+
let roundtripped_slice_exec = EVMSliceExec::try_into_proof_plan(
415+
&evm_slice_exec,
416+
&indexset![table_ref.clone()],
417+
&indexset![column_ref_a.clone(), column_ref_b.clone()],
418+
)
419+
.unwrap();
420+
421+
// Verify the roundtripped plan has the expected structure
422+
assert_eq!(roundtripped_slice_exec.skip(), skip);
423+
assert_eq!(roundtripped_slice_exec.fetch(), fetch);
424+
assert!(matches!(
425+
*roundtripped_slice_exec.input(),
426+
DynProofPlan::Table(_)
427+
));
428+
}
429+
193430
#[test]
194431
fn we_can_put_empty_exec_in_evm() {
195432
let empty_exec = EmptyExec::new();

crates/proof-of-sql/src/sql/proof_plans/projection_exec.rs

+10
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,16 @@ impl ProjectionExec {
4040
input,
4141
}
4242
}
43+
44+
/// Get a reference to the input plan
45+
pub fn input(&self) -> &DynProofPlan {
46+
&self.input
47+
}
48+
49+
/// Get a reference to the aliased results
50+
pub fn aliased_results(&self) -> &[AliasedDynProofExpr] {
51+
&self.aliased_results
52+
}
4353
}
4454

4555
impl ProofPlan for ProjectionExec {

crates/proof-of-sql/src/sql/proof_plans/slice_exec.rs

+15
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,21 @@ impl SliceExec {
4848
pub fn new(input: Box<DynProofPlan>, skip: usize, fetch: Option<usize>) -> Self {
4949
Self { input, skip, fetch }
5050
}
51+
52+
/// Get a reference to the input plan
53+
pub fn input(&self) -> &DynProofPlan {
54+
&self.input
55+
}
56+
57+
/// Get the skip value
58+
pub fn skip(&self) -> usize {
59+
self.skip
60+
}
61+
62+
/// Get the fetch value
63+
pub fn fetch(&self) -> Option<usize> {
64+
self.fetch
65+
}
5166
}
5267

5368
impl ProofPlan for SliceExec

0 commit comments

Comments
 (0)