Skip to content

Commit 315afc9

Browse files
Parameterized query plans (#5287)
# Description of Changes Makes runtime parameters explicit in query plans (prerequisite for parameterized views). As part of this change, `sender` is no longer baked directly into query plans as a literal value. Instead it is represented as a parameter in the query plan. Values are supplied at runtime via a variable environment called `ExecutionParams`. Note, parameterized plans are still not shared across subscriptions yet. That will be done in a follow up. This is mostly a mechanical change. The majority of the diff is just threading runtime params/variables through various call sites. # API and ABI breaking changes N/A # Expected complexity level and risk ... # Testing Existing coverage
1 parent eb5b287 commit 315afc9

21 files changed

Lines changed: 358 additions & 247 deletions

File tree

crates/bench/benches/subscription.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ use spacetimedb_bench::database::BenchDatabase as _;
1111
use spacetimedb_bench::spacetime_raw::SpacetimeRaw;
1212
use spacetimedb_client_api_messages::websocket::v1::BsatnFormat;
1313
use spacetimedb_datastore::execution_context::Workload;
14-
use spacetimedb_execution::pipelined::PipelinedProject;
14+
use spacetimedb_execution::{pipelined::PipelinedProject, ExecutionParams};
1515
use spacetimedb_primitives::{col_list, TableId};
1616
use spacetimedb_query::compile_subscription;
1717
use spacetimedb_sats::{bsatn, product, AlgebraicType, AlgebraicValue};
@@ -113,9 +113,10 @@ fn eval(c: &mut Criterion) {
113113
let auth = AuthCtx::for_testing();
114114
let schema_viewer = &SchemaViewer::new(&tx, &auth);
115115
let (plans, table_id, table_name, _) = compile_subscription(sql, schema_viewer, &auth).unwrap();
116+
let params = ExecutionParams::from_auth(&auth);
116117
let plans = plans
117118
.into_iter()
118-
.map(|plan| plan.optimize(&auth).unwrap())
119+
.map(|plan| plan.optimize().unwrap())
119120
.map(PipelinedProject::from)
120121
.collect::<Vec<_>>();
121122
let tx = DeltaTx::from(&tx);
@@ -126,6 +127,7 @@ fn eval(c: &mut Criterion) {
126127
table_id,
127128
table_name.clone(),
128129
&tx,
130+
&params,
129131
TableUpdateType::Subscribe,
130132
&bsatn_rlb_pool,
131133
));

crates/core/src/estimation.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,7 @@ mod tests {
152152
.map(|(plans, ..)| plans)
153153
.expect("failed to compile sql query")
154154
.into_iter()
155-
.map(|plan| plan.optimize(&auth).expect("failed to optimize sql query"))
155+
.map(|plan| plan.optimize().expect("failed to optimize sql query"))
156156
.map(|plan| row_estimate(&tx, &plan))
157157
.sum()
158158
}
@@ -166,7 +166,7 @@ mod tests {
166166
.map(|(plans, ..)| plans)
167167
.expect("failed to compile sql query")
168168
.into_iter()
169-
.map(|plan| plan.optimize(&auth).expect("failed to optimize sql query"))
169+
.map(|plan| plan.optimize().expect("failed to optimize sql query"))
170170
.map(|plan| estimate_rows_scanned(&tx, plan.physical_plan()))
171171
.sum()
172172
}

crates/core/src/host/module_host.rs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ use spacetimedb_datastore::locking_tx_datastore::{MutTxId, ViewCallInfo};
5252
use spacetimedb_datastore::traits::{IsolationLevel, Program, TxData};
5353
pub use spacetimedb_durability::{DurabilityExited, DurableOffset};
5454
use spacetimedb_execution::pipelined::PipelinedProject;
55+
use spacetimedb_execution::ExecutionParams;
5556
use spacetimedb_execution::RelValue;
5657
use spacetimedb_expr::expr::CollectViews;
5758
use spacetimedb_lib::db::raw_def::v9::Lifecycle;
@@ -3278,7 +3279,7 @@ impl ModuleHost {
32783279
// Optimize each fragment.
32793280
let optimized = plans
32803281
.into_iter()
3281-
.map(|plan| plan.optimize(auth))
3282+
.map(|plan| plan.optimize())
32823283
.collect::<Result<Vec<_>, _>>()?;
32833284

32843285
check_row_limit(
@@ -3306,10 +3307,12 @@ impl ModuleHost {
33063307

33073308
let table_name = table_name.into();
33083309
let delta_tx = DeltaTx::from(tx);
3310+
let params = ExecutionParams::from_auth(auth);
3311+
let plan_fragments = optimized.iter();
33093312
let (rows, _, metrics) = if returns_view_table && num_private_cols > 0 {
3310-
execute_plan_for_view::<F>(optimized.iter(), num_cols, num_private_cols, &delta_tx, rlb_pool)
3313+
execute_plan_for_view::<F>(plan_fragments, num_cols, num_private_cols, &delta_tx, &params, rlb_pool)
33113314
} else {
3312-
execute_plan::<F>(optimized.iter(), &delta_tx, rlb_pool)
3315+
execute_plan::<F>(optimized.iter(), &delta_tx, &params, rlb_pool)
33133316
}
33143317
.context("One-off queries are not allowed to modify the database")?;
33153318

crates/core/src/host/wasm_common/module_host_actor.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ use spacetimedb_datastore::error::{DatastoreError, ViewError};
3939
use spacetimedb_datastore::execution_context::{self, ReducerContext, Workload};
4040
use spacetimedb_datastore::locking_tx_datastore::{FuncCallType, MutTxId, ViewCallInfo};
4141
use spacetimedb_datastore::traits::{IsolationLevel, Program};
42+
use spacetimedb_execution::ExecutionParams;
4243
use spacetimedb_lib::buffer::DecodeError;
4344
use spacetimedb_lib::db::raw_def::v9::{Lifecycle, ViewResultHeader};
4445
use spacetimedb_lib::de::DeserializeSeed;
@@ -206,14 +207,16 @@ pub(crate) fn run_query_for_view(
206207
let mut metrics = ExecutionMetrics::default();
207208
let mut rows = Vec::new();
208209

210+
let params = ExecutionParams::from_auth(&auth);
211+
209212
for plan in plans {
210213
// Track read sets for all tables involved in this plan.
211214
// TODO(jsdt): This means we will rerun the view and query for any change to these tables, so we should optimize this asap.
212215
for table_id in plan.table_ids() {
213216
tx.record_table_scan(&op, table_id);
214217
}
215218

216-
plan.base_plan().execute(&*tx, &mut metrics, &mut |row| {
219+
plan.base_plan().execute(&*tx, &params, &mut metrics, &mut |row| {
217220
rows.push(row.to_product_value());
218221
Ok(())
219222
})?;

crates/core/src/subscription/mod.rs

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ use spacetimedb_datastore::{
1212
};
1313
use spacetimedb_execution::{pipelined::PipelinedProject, Datastore, DeltaStore, Row};
1414
use spacetimedb_lib::{metrics::ExecutionMetrics, Identity};
15+
use spacetimedb_physical_plan::plan::ParamResolver;
1516
use spacetimedb_primitives::{ColList, TableId};
1617
use spacetimedb_sats::bsatn::ToBsatn;
1718
use spacetimedb_sats::Serialize;
@@ -102,6 +103,7 @@ pub fn execute_plan_for_view<'p, F>(
102103
num_cols: usize,
103104
num_private_cols: usize,
104105
tx: &(impl Datastore + DeltaStore),
106+
params: &impl ParamResolver,
105107
rlb_pool: &impl RowListBuilderSource<F>,
106108
) -> Result<(F::List, u64, ExecutionMetrics)>
107109
where
@@ -110,7 +112,7 @@ where
110112
build_list_with_executor(rlb_pool, |metrics, add| {
111113
let col_list = ColList::from_iter(num_private_cols..num_cols);
112114
for fragment in plan_fragments {
113-
fragment.execute(tx, metrics, &mut |row| match row {
115+
fragment.execute(tx, params, metrics, &mut |row| match row {
114116
Row::Ptr(ptr) => add(ptr.project_product(&col_list)?),
115117
Row::Ref(val) => add(val.project_product(&col_list)?),
116118
})?;
@@ -123,14 +125,15 @@ where
123125
pub fn execute_plan<'p, F>(
124126
plan_fragments: impl IntoIterator<Item = &'p PipelinedProject>,
125127
tx: &(impl Datastore + DeltaStore),
128+
params: &impl ParamResolver,
126129
rlb_pool: &impl RowListBuilderSource<F>,
127130
) -> Result<(F::List, u64, ExecutionMetrics)>
128131
where
129132
F: BuildableWebsocketFormat,
130133
{
131134
build_list_with_executor(rlb_pool, |metrics, add| {
132135
for fragment in plan_fragments {
133-
fragment.execute(tx, metrics, add)?;
136+
fragment.execute(tx, params, metrics, add)?;
134137
}
135138
Ok(())
136139
})
@@ -208,14 +211,15 @@ pub fn collect_table_update_for_view<'p, Tx, F>(
208211
table_id: TableId,
209212
table_name: TableName,
210213
tx: &Tx,
214+
params: &impl ParamResolver,
211215
update_type: TableUpdateType,
212216
rlb_pool: &impl RowListBuilderSource<F>,
213217
) -> Result<(ws_v1::TableUpdate<F>, ExecutionMetrics)>
214218
where
215219
Tx: Datastore + DeltaStore,
216220
F: BuildableWebsocketFormat,
217221
{
218-
execute_plan_for_view::<F>(plan_fragments, num_cols, num_private_cols, tx, rlb_pool).map(
222+
execute_plan_for_view::<F>(plan_fragments, num_cols, num_private_cols, tx, params, rlb_pool).map(
219223
|(rows, num_rows, metrics)| table_update_from_rows(rows, num_rows, metrics, table_id, table_name, update_type),
220224
)
221225
}
@@ -226,13 +230,14 @@ pub fn collect_table_update<'p, F>(
226230
table_id: TableId,
227231
table_name: TableName,
228232
tx: &(impl Datastore + DeltaStore),
233+
params: &impl ParamResolver,
229234
update_type: TableUpdateType,
230235
rlb_pool: &impl RowListBuilderSource<F>,
231236
) -> Result<(ws_v1::TableUpdate<F>, ExecutionMetrics)>
232237
where
233238
F: BuildableWebsocketFormat,
234239
{
235-
execute_plan::<F>(plan_fragments, tx, rlb_pool).map(|(rows, num_rows, metrics)| {
240+
execute_plan::<F>(plan_fragments, tx, params, rlb_pool).map(|(rows, num_rows, metrics)| {
236241
table_update_from_rows(rows, num_rows, metrics, table_id, table_name, update_type)
237242
})
238243
}
@@ -265,6 +270,7 @@ pub fn execute_plans<F: BuildableWebsocketFormat>(
265270
table_id,
266271
table_name.clone(),
267272
tx,
273+
plan.params(),
268274
update_type,
269275
rlb_pool,
270276
)?
@@ -274,6 +280,7 @@ pub fn execute_plans<F: BuildableWebsocketFormat>(
274280
table_id,
275281
table_name.clone(),
276282
tx,
283+
plan.params(),
277284
update_type,
278285
rlb_pool,
279286
)?

crates/core/src/subscription/module_subscription_actor.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ use spacetimedb_datastore::locking_tx_datastore::datastore::TxMetrics;
3535
use spacetimedb_datastore::locking_tx_datastore::{MutTxId, TxId};
3636
use spacetimedb_datastore::traits::{IsolationLevel, TxData};
3737
use spacetimedb_durability::TxOffset;
38+
use spacetimedb_execution::ExecutionParams;
3839
use spacetimedb_expr::expr::CollectViews;
3940
use spacetimedb_lib::identity::RequestId;
4041
use spacetimedb_lib::metrics::ExecutionMetrics;
@@ -467,6 +468,7 @@ impl ModuleSubscriptions {
467468
.unwrap_or_default();
468469

469470
let tx = DeltaTx::from(tx);
471+
let params = ExecutionParams::from_sender(sender.id.identity);
470472

471473
// TODO: See the comment on `collect_table_update_for_view`.
472474
// The following view and non-view branches should be merged together,
@@ -479,6 +481,7 @@ impl ModuleSubscriptions {
479481
table_id,
480482
table_name.clone(),
481483
&tx,
484+
&params,
482485
update_type,
483486
&self.bsatn_rlb_pool,
484487
)
@@ -488,6 +491,7 @@ impl ModuleSubscriptions {
488491
table_id,
489492
table_name.clone(),
490493
&tx,
494+
&params,
491495
update_type,
492496
&self.bsatn_rlb_pool,
493497
)
@@ -499,6 +503,7 @@ impl ModuleSubscriptions {
499503
table_id,
500504
table_name,
501505
&tx,
506+
&params,
502507
update_type,
503508
&JsonRowListBuilderFakePool,
504509
)
@@ -508,6 +513,7 @@ impl ModuleSubscriptions {
508513
table_id,
509514
table_name,
510515
&tx,
516+
&params,
511517
update_type,
512518
&JsonRowListBuilderFakePool,
513519
)

crates/execution/src/dml.rs

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use anyhow::Result;
22
use spacetimedb_lib::{metrics::ExecutionMetrics, AlgebraicValue, ProductValue};
33
use spacetimedb_physical_plan::dml::{DeletePlan, InsertPlan, MutationPlan, UpdatePlan};
4+
use spacetimedb_physical_plan::plan::ParamResolver;
45
use spacetimedb_primitives::{ColId, TableId};
56
use spacetimedb_sats::size_of::SizeOf;
67

@@ -30,11 +31,16 @@ impl From<MutationPlan> for MutExecutor {
3031
}
3132

3233
impl MutExecutor {
33-
pub fn execute<Tx: MutDatastore>(&self, tx: &mut Tx, metrics: &mut ExecutionMetrics) -> Result<()> {
34+
pub fn execute<Tx: MutDatastore>(
35+
&self,
36+
tx: &mut Tx,
37+
params: &impl ParamResolver,
38+
metrics: &mut ExecutionMetrics,
39+
) -> Result<()> {
3440
match self {
3541
Self::Insert(exec) => exec.execute(tx, metrics),
36-
Self::Delete(exec) => exec.execute(tx, metrics),
37-
Self::Update(exec) => exec.execute(tx, metrics),
42+
Self::Delete(exec) => exec.execute(tx, params, metrics),
43+
Self::Update(exec) => exec.execute(tx, params, metrics),
3844
}
3945
}
4046
}
@@ -84,10 +90,15 @@ impl From<DeletePlan> for DeleteExecutor {
8490
}
8591

8692
impl DeleteExecutor {
87-
fn execute<Tx: MutDatastore>(&self, tx: &mut Tx, metrics: &mut ExecutionMetrics) -> Result<()> {
93+
fn execute<Tx: MutDatastore>(
94+
&self,
95+
tx: &mut Tx,
96+
params: &impl ParamResolver,
97+
metrics: &mut ExecutionMetrics,
98+
) -> Result<()> {
8899
// TODO: Delete by row id instead of product value
89100
let mut deletes = vec![];
90-
self.filter.execute(tx, metrics, &mut |row| {
101+
self.filter.execute(tx, params, metrics, &mut |row| {
91102
deletes.push(row.to_product_value());
92103
Ok(())
93104
})?;
@@ -122,9 +133,14 @@ impl From<UpdatePlan> for UpdateExecutor {
122133
}
123134

124135
impl UpdateExecutor {
125-
fn execute<Tx: MutDatastore>(&self, tx: &mut Tx, metrics: &mut ExecutionMetrics) -> Result<()> {
136+
fn execute<Tx: MutDatastore>(
137+
&self,
138+
tx: &mut Tx,
139+
params: &impl ParamResolver,
140+
metrics: &mut ExecutionMetrics,
141+
) -> Result<()> {
126142
let mut deletes = vec![];
127-
self.filter.execute(tx, metrics, &mut |row| {
143+
self.filter.execute(tx, params, metrics, &mut |row| {
128144
deletes.push(row.to_product_value());
129145
Ok(())
130146
})?;

crates/execution/src/lib.rs

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,44 @@
11
use anyhow::Result;
22
use core::hash::{Hash, Hasher};
33
use core::ops::RangeBounds;
4-
use spacetimedb_lib::query::Delta;
5-
use spacetimedb_physical_plan::plan::{ProjectField, TupleField};
4+
use spacetimedb_lib::{identity::AuthCtx, query::Delta, AlgebraicType, Identity};
5+
use spacetimedb_physical_plan::plan::{ParamResolver, ProjectField, TupleField};
66
use spacetimedb_primitives::{ColList, IndexId, TableId};
77
use spacetimedb_sats::bsatn::{BufReservedFill, EncodeError, ToBsatn};
88
use spacetimedb_sats::buffer::BufWriter;
99
use spacetimedb_sats::product_value::InvalidFieldError;
1010
use spacetimedb_sats::{impl_serialize, AlgebraicValue, ProductValue};
11+
use spacetimedb_sql_parser::ast::Parameter;
1112
use spacetimedb_table::{static_assert_size, table::RowRef};
1213

1314
pub mod dml;
1415
pub mod pipelined;
1516

17+
#[derive(Debug, Clone, Copy)]
18+
pub struct ExecutionParams {
19+
sender: Identity,
20+
}
21+
22+
impl ExecutionParams {
23+
pub fn from_sender(sender: Identity) -> Self {
24+
Self { sender }
25+
}
26+
27+
pub fn from_auth(auth: &AuthCtx) -> Self {
28+
Self::from_sender(auth.caller())
29+
}
30+
}
31+
32+
impl ParamResolver for ExecutionParams {
33+
fn resolve_param(&self, param: Parameter, ty: &AlgebraicType) -> AlgebraicValue {
34+
match param {
35+
Parameter::Sender if ty.is_identity() => self.sender.into(),
36+
Parameter::Sender if ty.is_bytes() => AlgebraicValue::Bytes(self.sender.to_be_byte_array().into()),
37+
Parameter::Sender => panic!("unsupported type for :sender: {ty:?}"),
38+
}
39+
}
40+
}
41+
1642
pub trait Datastore {
1743
/// Iterator type for table scans
1844
type TableIter<'a>: Iterator<Item = RowRef<'a>> + 'a

0 commit comments

Comments
 (0)