From bef8921af05f590a635005b4bebe41c607a11f3b Mon Sep 17 00:00:00 2001 From: joshua-spacetime Date: Fri, 26 Jun 2026 14:43:33 -0700 Subject: [PATCH] Supply empty arg hash for anonymous views at runtime Instead of baking it into the query plan. The query plan should parameterize anonymous views just like sender-scoped views. --- crates/execution/src/lib.rs | 23 +++++++++++++++++------ crates/physical-plan/src/plan.rs | 21 +++++++++++---------- 2 files changed, 28 insertions(+), 16 deletions(-) diff --git a/crates/execution/src/lib.rs b/crates/execution/src/lib.rs index 34d02317d1f..7eac07f7c29 100644 --- a/crates/execution/src/lib.rs +++ b/crates/execution/src/lib.rs @@ -1,9 +1,12 @@ use anyhow::Result; use core::hash::{Hash, Hasher}; use core::ops::RangeBounds; -use spacetimedb_lib::{hash_sender_view_args, identity::AuthCtx, query::Delta, AlgebraicType, Identity}; +use spacetimedb_lib::{ + hash_empty_view_args, hash_sender_view_args, identity::AuthCtx, query::Delta, AlgebraicType, Identity, +}; use spacetimedb_physical_plan::plan::{ - ParamResolver, ParamSlot, ProjectField, TupleField, PARAM_SENDER, PARAM_VIEW_ARG_HASH, + ParamResolver, ParamSlot, ProjectField, TupleField, PARAM_SENDER, PARAM_VIEW_ARG_HASH_EMPTY, + PARAM_VIEW_ARG_HASH_SENDER, }; use spacetimedb_primitives::{ColList, IndexId, TableId}; use spacetimedb_sats::bsatn::{BufReservedFill, EncodeError, ToBsatn}; @@ -18,14 +21,16 @@ pub mod pipelined; #[derive(Debug, Clone, Copy)] pub struct ExecutionParams { sender: Identity, - view_arg_hash: u256, + empty_view_arg_hash: u256, + sender_view_arg_hash: u256, } impl ExecutionParams { pub fn from_sender(sender: Identity) -> Self { Self { sender, - view_arg_hash: hash_sender_view_args(sender).to_u256(), + empty_view_arg_hash: hash_empty_view_args().to_u256(), + sender_view_arg_hash: hash_sender_view_args(sender).to_u256(), } } @@ -40,8 +45,14 @@ impl ParamResolver for ExecutionParams { PARAM_SENDER if ty.is_identity() => self.sender.into(), PARAM_SENDER if ty.is_bytes() => AlgebraicValue::Bytes(self.sender.to_be_byte_array().into()), PARAM_SENDER => panic!("unsupported type for :sender: {ty:?}"), - PARAM_VIEW_ARG_HASH if matches!(ty, AlgebraicType::U256) => AlgebraicValue::U256(self.view_arg_hash.into()), - PARAM_VIEW_ARG_HASH => panic!("unsupported type for view arg hash: {ty:?}"), + PARAM_VIEW_ARG_HASH_EMPTY if matches!(ty, AlgebraicType::U256) => { + AlgebraicValue::U256(self.empty_view_arg_hash.into()) + } + PARAM_VIEW_ARG_HASH_EMPTY => panic!("unsupported type for empty view arg hash: {ty:?}"), + PARAM_VIEW_ARG_HASH_SENDER if matches!(ty, AlgebraicType::U256) => { + AlgebraicValue::U256(self.sender_view_arg_hash.into()) + } + PARAM_VIEW_ARG_HASH_SENDER => panic!("unsupported type for sender view arg hash: {ty:?}"), ParamSlot(slot) => panic!("unknown physical plan parameter slot: {slot}"), } } diff --git a/crates/physical-plan/src/plan.rs b/crates/physical-plan/src/plan.rs index 3dfbd82d80c..aba742cce6b 100644 --- a/crates/physical-plan/src/plan.rs +++ b/crates/physical-plan/src/plan.rs @@ -6,9 +6,7 @@ use spacetimedb_expr::{ expr::{AggType, CollectViews}, StatementSource, }; -use spacetimedb_lib::{ - empty_view_arg_hash_value, query::Delta, sats::size_of::SizeOf, AlgebraicType, AlgebraicValue, ProductValue, -}; +use spacetimedb_lib::{query::Delta, sats::size_of::SizeOf, AlgebraicType, AlgebraicValue, ProductValue}; use spacetimedb_primitives::{ColId, ColOrCols, ColSet, IndexId, TableId, ViewId}; use spacetimedb_schema::schema::{IndexSchema, TableSchema, VIEW_ARG_HASH_COL}; use spacetimedb_sql_parser::ast::{BinOp, LogOp}; @@ -42,7 +40,8 @@ pub trait ParamResolver { pub struct ParamSlot(pub u16); pub const PARAM_SENDER: ParamSlot = ParamSlot(0); -pub const PARAM_VIEW_ARG_HASH: ParamSlot = ParamSlot(1); +pub const PARAM_VIEW_ARG_HASH_EMPTY: ParamSlot = ParamSlot(1); +pub const PARAM_VIEW_ARG_HASH_SENDER: ParamSlot = ParamSlot(2); /// Physical plans always terminate with a projection. /// This type of projection returns row ids. @@ -553,9 +552,10 @@ impl PhysicalPlan { /// If a view has private arguments, its backing table has an `arg_hash` column. /// This column tracks which rows belong to which argument tuple. /// - /// As a result, queries over such views cannot read the entire backing table. - /// They must only select the rows corresponding to the caller of the query. - /// Hence we must add an implicit selection over these types of views. + /// As a result, queries over views cannot read the entire backing table. + /// They must only select the rows corresponding to the view arguments used + /// by each view reference. Hence we must add an implicit selection over + /// these types of views. /// /// Ex. /// ```sql @@ -569,11 +569,12 @@ impl PhysicalPlan { fn expand_views(self) -> Self { match self { Self::TableScan(scan, label) if scan.schema.is_view() => { - let arg_hash = if scan.schema.is_anonymous_view() { - PhysicalExpr::Value(empty_view_arg_hash_value()) + let param = if scan.schema.is_anonymous_view() { + PARAM_VIEW_ARG_HASH_EMPTY } else { - PhysicalExpr::Param(PARAM_VIEW_ARG_HASH, AlgebraicType::U256) + PARAM_VIEW_ARG_HASH_SENDER }; + let arg_hash = PhysicalExpr::Param(param, AlgebraicType::U256); Self::Filter( Box::new(Self::TableScan(scan, label)), PhysicalExpr::BinOp(