Skip to content

Commit e9a766c

Browse files
Add args column to view backing tables
1 parent 315afc9 commit e9a766c

12 files changed

Lines changed: 227 additions & 236 deletions

File tree

crates/core/src/db/relational_db.rs

Lines changed: 53 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ use spacetimedb_sats::{AlgebraicType, AlgebraicValue, ProductType, ProductValue}
5050
use spacetimedb_schema::def::{ModuleDef, TableDef, ViewDef};
5151
use spacetimedb_schema::reducer_name::ReducerName;
5252
use spacetimedb_schema::schema::{
53-
ColumnSchema, IndexSchema, RowLevelSecuritySchema, Schema, SequenceSchema, TableSchema,
53+
ColumnSchema, IndexSchema, RowLevelSecuritySchema, Schema, SequenceSchema, TableSchema, VIEW_ARG_HASH_COL,
5454
};
5555
use spacetimedb_schema::table_name::TableName;
5656
use spacetimedb_snapshot::{DynSnapshotRepo, ReconstructedSnapshot, SnapshotError, SnapshotRepository};
@@ -1559,10 +1559,10 @@ impl RelationalDB {
15591559
Ok(None)
15601560
}
15611561

1562-
/// Write `rows` into a (sender) view's backing table.
1562+
/// Write `rows` into a sender-scoped view's backing table.
15631563
///
15641564
/// # Process
1565-
/// 1. Delete all rows for `sender` from the view's backing table
1565+
/// 1. Delete all rows for `sender`'s implicit argument hash from the view's backing table
15661566
/// 2. Insert the new rows into the backing table
15671567
///
15681568
/// # Arguments
@@ -1578,39 +1578,24 @@ impl RelationalDB {
15781578
sender: Identity,
15791579
rows: Vec<ProductValue>,
15801580
) -> Result<(), DBError> {
1581-
// Delete rows for `sender` from the backing table
1582-
let rows_to_delete = self
1583-
.iter_by_col_eq_mut(tx, table_id, ColId(0), &sender.into())?
1584-
.map(|res| res.pointer())
1585-
.collect::<Vec<_>>();
1586-
self.delete(tx, table_id, rows_to_delete);
1587-
1588-
self.write_view_rows(tx, table_id, rows, Some(sender))?;
1589-
1590-
Ok(())
1581+
let arg_hash = MutTxId::view_arg_hash(sender);
1582+
self.materialize_view_arg_hash(tx, table_id, arg_hash, rows)
15911583
}
15921584

1593-
/// Write `rows` into an anonymous view's backing table.
1594-
///
1595-
/// # Process
1596-
/// 1. Clear the view's backing table
1597-
/// 2. Insert the new rows into the backing table
1598-
///
1599-
/// # Arguments
1600-
/// * `tx` - Mutable transaction context
1601-
/// * `table_id` - The id of the view's backing table
1602-
/// * `rows` - Product values to insert
1603-
#[allow(clippy::too_many_arguments)]
1604-
pub fn materialize_anonymous_view(
1585+
fn materialize_view_arg_hash(
16051586
&self,
16061587
tx: &mut MutTxId,
16071588
table_id: TableId,
1589+
arg_hash: AlgebraicValue,
16081590
rows: Vec<ProductValue>,
16091591
) -> Result<(), DBError> {
1610-
// Clear entire backing table
1611-
self.clear_table(tx, table_id)?;
1592+
let rows_to_delete = self
1593+
.iter_by_col_eq_mut(tx, table_id, VIEW_ARG_HASH_COL, &arg_hash)?
1594+
.map(|res| res.pointer())
1595+
.collect::<Vec<_>>();
1596+
self.delete(tx, table_id, rows_to_delete);
16121597

1613-
self.write_view_rows(tx, table_id, rows, None)?;
1598+
self.write_view_rows(tx, table_id, rows, &arg_hash)?;
16141599

16151600
Ok(())
16161601
}
@@ -1626,10 +1611,11 @@ impl RelationalDB {
16261611
view_call: ViewCallInfo,
16271612
rows: Vec<ProductValue>,
16281613
) -> Result<(), DBError> {
1629-
match view_call.sender {
1630-
Some(sender) => self.materialize_view(tx, table_id, sender, rows)?,
1631-
None => self.materialize_anonymous_view(tx, table_id, rows)?,
1632-
}
1614+
let arg_hash = match view_call.sender {
1615+
Some(sender) => MutTxId::view_arg_hash(sender),
1616+
None => MutTxId::anonymous_view_arg_hash(),
1617+
};
1618+
self.materialize_view_arg_hash(tx, table_id, arg_hash, rows)?;
16331619
tx.replace_view_read_set(view_call);
16341620

16351621
Ok(())
@@ -1640,34 +1626,18 @@ impl RelationalDB {
16401626
tx: &mut MutTxId,
16411627
table_id: TableId,
16421628
rows: Vec<ProductValue>,
1643-
sender: Option<Identity>,
1629+
arg_hash: &AlgebraicValue,
16441630
) -> Result<(), DBError> {
1645-
match sender {
1646-
Some(sender) => {
1647-
for product in rows {
1648-
let value = ProductValue::from_iter(std::iter::once(sender.into()).chain(product.elements));
1649-
self.insert(
1650-
tx,
1651-
table_id,
1652-
&value
1653-
.to_bsatn_vec()
1654-
.map_err(|_| ViewError::SerializeRow)
1655-
.map_err(DatastoreError::from)?,
1656-
)?;
1657-
}
1658-
}
1659-
None => {
1660-
for product in rows {
1661-
self.insert(
1662-
tx,
1663-
table_id,
1664-
&product
1665-
.to_bsatn_vec()
1666-
.map_err(|_| ViewError::SerializeRow)
1667-
.map_err(DatastoreError::from)?,
1668-
)?;
1669-
}
1670-
}
1631+
for product in rows {
1632+
let value = ProductValue::from_iter(std::iter::once(arg_hash.clone()).chain(product.elements));
1633+
self.insert(
1634+
tx,
1635+
table_id,
1636+
&value
1637+
.to_bsatn_vec()
1638+
.map_err(|_| ViewError::SerializeRow)
1639+
.map_err(DatastoreError::from)?,
1640+
)?;
16711641
}
16721642

16731643
Ok(())
@@ -2256,8 +2226,8 @@ pub mod tests_utils {
22562226
row: ProductValue,
22572227
) -> Result<RowRef<'a>, DBError> {
22582228
let meta_cols = match sender {
2259-
Some(identity) => vec![identity.into()],
2260-
None => vec![],
2229+
Some(identity) => vec![MutTxId::view_arg_hash(identity)],
2230+
None => vec![MutTxId::anonymous_view_arg_hash()],
22612231
};
22622232
let cols = meta_cols.into_iter().chain(row.elements);
22632233
let row = ProductValue::from_iter(cols);
@@ -2512,8 +2482,9 @@ mod tests {
25122482

25132483
fn project_views(stdb: &TestDB, table_id: TableId, sender: Identity) -> Vec<ProductValue> {
25142484
let tx = begin_tx(stdb);
2485+
let arg_hash = MutTxId::view_arg_hash(sender);
25152486

2516-
stdb.iter_by_col_eq(&tx, table_id, 0, &sender.into())
2487+
stdb.iter_by_col_eq(&tx, table_id, VIEW_ARG_HASH_COL, &arg_hash)
25172488
.unwrap()
25182489
.map(|row| {
25192490
let pv = row.to_product_value();
@@ -2526,10 +2497,16 @@ mod tests {
25262497

25272498
fn project_anonymous_views(stdb: &TestDB, table_id: TableId) -> Vec<ProductValue> {
25282499
let tx = begin_tx(stdb);
2500+
let arg_hash = MutTxId::anonymous_view_arg_hash();
25292501

2530-
stdb.iter(&tx, table_id)
2502+
stdb.iter_by_col_eq(&tx, table_id, VIEW_ARG_HASH_COL, &arg_hash)
25312503
.unwrap()
2532-
.map(|row| row.to_product_value())
2504+
.map(|row| {
2505+
let pv = row.to_product_value();
2506+
ProductValue {
2507+
elements: pv.elements.iter().skip(1).cloned().collect(),
2508+
}
2509+
})
25332510
.collect()
25342511
}
25352512

@@ -2737,7 +2714,12 @@ mod tests {
27372714
let mut tx = begin_mut_tx(&stdb);
27382715
tx.subscribe_view(view_id, ArgId::SENTINEL, stale_sender)?;
27392716
tx.subscribe_view(view_id, ArgId::SENTINEL, live_sender)?;
2740-
stdb.materialize_anonymous_view(&mut tx, table_id, vec![product![42u8]])?;
2717+
stdb.materialize_view_call(
2718+
&mut tx,
2719+
table_id,
2720+
ViewCallInfo { view_id, sender: None },
2721+
vec![product![42u8]],
2722+
)?;
27412723
stdb.commit_tx(tx)?;
27422724

27432725
let mut tx = begin_mut_tx(&stdb);
@@ -2786,7 +2768,12 @@ mod tests {
27862768

27872769
let mut tx = begin_mut_tx(&stdb);
27882770
tx.subscribe_view(view_id, ArgId::SENTINEL, sender)?;
2789-
stdb.materialize_anonymous_view(&mut tx, table_id, vec![product![42u8]])?;
2771+
stdb.materialize_view_call(
2772+
&mut tx,
2773+
table_id,
2774+
ViewCallInfo { view_id, sender: None },
2775+
vec![product![42u8]],
2776+
)?;
27902777
stdb.commit_tx(tx)?;
27912778

27922779
let mut tx = begin_mut_tx(&stdb);

crates/core/src/host/module_host.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3309,7 +3309,7 @@ impl ModuleHost {
33093309
let delta_tx = DeltaTx::from(tx);
33103310
let params = ExecutionParams::from_auth(auth);
33113311
let plan_fragments = optimized.iter();
3312-
let (rows, _, metrics) = if returns_view_table && num_private_cols > 0 {
3312+
let (rows, _, metrics) = if returns_view_table {
33133313
execute_plan_for_view::<F>(plan_fragments, num_cols, num_private_cols, &delta_tx, &params, rlb_pool)
33143314
} else {
33153315
execute_plan::<F>(optimized.iter(), &delta_tx, &params, rlb_pool)

crates/core/src/subscription/module_subscription_actor.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2040,6 +2040,7 @@ mod tests {
20402040
use spacetimedb_client_api_messages::energy::FunctionBudget;
20412041
use spacetimedb_client_api_messages::websocket::{common::RowListLen as _, v1 as ws_v1, v2 as ws_v2};
20422042
use spacetimedb_data_structures::map::{HashCollectionExt as _, HashMap};
2043+
use spacetimedb_datastore::locking_tx_datastore::MutTxId;
20432044
use spacetimedb_datastore::system_tables::{StRowLevelSecurityRow, ST_ROW_LEVEL_SECURITY_ID};
20442045
use spacetimedb_execution::dml::MutDatastore;
20452046
use spacetimedb_lib::bsatn::ToBsatn;
@@ -2536,7 +2537,8 @@ mod tests {
25362537
subs.remove_subscriber(client_id_for_b);
25372538

25382539
// Delete the backing row and verify the surviving subscriber still receives the view delta.
2539-
let _ = commit_tx(&db, &subs, [(view_table_id, product![id_for_a, 7_u8])], [])?;
2540+
let arg_hash = MutTxId::view_arg_hash(id_for_a);
2541+
let _ = commit_tx(&db, &subs, [(view_table_id, product![arg_hash, 7_u8])], [])?;
25402542

25412543
let schema = ProductType::from([AlgebraicType::U8]);
25422544
assert_v2_tx_update_for_table(

crates/datastore/src/error.rs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,6 @@ pub enum ViewError {
5757
TypeMismatchOption,
5858
#[error("expected ProductValue in view result")]
5959
ExpectedProduct,
60-
#[error("failed to serialize view arguments")]
61-
SerializeArgs,
6260
}
6361

6462
#[derive(Error, Debug)]

crates/datastore/src/locking_tx_datastore/mut_tx.rs

Lines changed: 29 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,9 @@ use spacetimedb_execution::{dml::MutDatastore, Datastore, DeltaStore, Row};
4343
use spacetimedb_lib::{
4444
db::raw_def::v9::RawSql,
4545
db::{auth::StAccess, raw_def::SEQUENCE_ALLOCATION_STEP},
46+
empty_view_arg_hash_value,
4647
metrics::ExecutionMetrics,
47-
ConnectionId, Identity, Timestamp,
48+
sender_view_arg_hash_value, ConnectionId, Identity, Timestamp,
4849
};
4950
use spacetimedb_primitives::{
5051
col_list, ArgId, ColId, ColList, ColSet, ConstraintId, IndexId, ScheduleId, SequenceId, TableId, ViewId,
@@ -57,7 +58,10 @@ use spacetimedb_schema::{
5758
def::{ModuleDef, ViewColumnDef, ViewDef, ViewParamDef},
5859
identifier::Identifier,
5960
reducer_name::ReducerName,
60-
schema::{ColumnSchema, ConstraintSchema, IndexSchema, RowLevelSecuritySchema, SequenceSchema, TableSchema},
61+
schema::{
62+
ColumnSchema, ConstraintSchema, IndexSchema, RowLevelSecuritySchema, SequenceSchema, TableSchema,
63+
VIEW_ARG_HASH_COL,
64+
},
6165
table_name::TableName,
6266
};
6367
use spacetimedb_table::{
@@ -2369,6 +2373,16 @@ impl<'a, I: Iterator<Item = RowRef<'a>>> Iterator for FilterDeleted<'a, I> {
23692373
}
23702374

23712375
impl MutTxId {
2376+
/// Returns the hash value for an anonymous view's empty arguments.
2377+
pub fn anonymous_view_arg_hash() -> AlgebraicValue {
2378+
empty_view_arg_hash_value()
2379+
}
2380+
2381+
/// Returns the hash value for a sender-scoped view's arguments.
2382+
pub fn view_arg_hash(sender: Identity) -> AlgebraicValue {
2383+
sender_view_arg_hash_value(sender)
2384+
}
2385+
23722386
/// Does this caller have an entry for `view_id` in `st_view_sub`?
23732387
pub fn is_view_materialized(&self, view_id: ViewId, arg_id: ArgId, sender: Identity) -> Result<bool> {
23742388
use StViewSubFields::*;
@@ -2542,22 +2556,27 @@ impl MutTxId {
25422556
} = self.lookup_st_view(view_id)?;
25432557
let table_id = table_id.expect("views have backing table");
25442558

2545-
if is_anonymous {
2546-
if !self.has_other_st_view_sub_entries(view_id, sub_row_ptr)? {
2547-
self.clear_table(table_id)?;
2548-
self.drop_view_from_committed_read_set(view_id);
2549-
}
2550-
} else {
2559+
let drop_materialization = !is_anonymous || !self.has_other_st_view_sub_entries(view_id, sub_row_ptr)?;
2560+
if drop_materialization {
2561+
let arg_hash = if is_anonymous {
2562+
Self::anonymous_view_arg_hash()
2563+
} else {
2564+
Self::view_arg_hash(sender)
2565+
};
25512566
let rows_to_delete = self
2552-
.iter_by_col_eq(table_id, 0, &sender.into())?
2567+
.iter_by_col_eq(table_id, VIEW_ARG_HASH_COL, &arg_hash)?
25532568
.map(|res| res.pointer())
25542569
.collect::<Vec<_>>();
25552570

25562571
for row_ptr in rows_to_delete {
25572572
self.delete(table_id, row_ptr)?;
25582573
}
25592574

2560-
self.drop_view_with_sender_from_committed_read_set(view_id, sender);
2575+
if is_anonymous {
2576+
self.drop_view_from_committed_read_set(view_id);
2577+
} else {
2578+
self.drop_view_with_sender_from_committed_read_set(view_id, sender);
2579+
}
25612580
}
25622581

25632582
// Finally, delete the subscription row

crates/datastore/src/locking_tx_datastore/state_view.rs

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,9 @@ use crate::system_tables::{
77
StConnectionCredentialsFields, StConnectionCredentialsRow, StConstraintFields, StConstraintRow, StEventTableFields,
88
StEventTableRow, StIndexAccessorFields, StIndexAccessorRow, StIndexFields, StIndexRow, StScheduledFields,
99
StScheduledRow, StSequenceFields, StSequenceRow, StTableAccessorFields, StTableAccessorRow, StTableFields,
10-
StTableRow, StViewFields, StViewParamFields, StViewRow, SystemTable, ST_COLUMN_ACCESSOR_ID, ST_COLUMN_ID,
10+
StTableRow, StViewFields, StViewRow, SystemTable, ST_COLUMN_ACCESSOR_ID, ST_COLUMN_ID,
1111
ST_CONNECTION_CREDENTIALS_ID, ST_CONSTRAINT_ID, ST_EVENT_TABLE_ID, ST_INDEX_ACCESSOR_ID, ST_INDEX_ID,
12-
ST_SCHEDULED_ID, ST_SEQUENCE_ID, ST_TABLE_ACCESSOR_ID, ST_TABLE_ID, ST_VIEW_ID, ST_VIEW_PARAM_ID,
12+
ST_SCHEDULED_ID, ST_SEQUENCE_ID, ST_TABLE_ACCESSOR_ID, ST_TABLE_ID, ST_VIEW_ID,
1313
};
1414
use anyhow::anyhow;
1515
use core::ops::RangeBounds;
@@ -240,14 +240,9 @@ pub trait StateView {
240240
.map(|mut iter| {
241241
iter.next().map(|row| -> Result<_> {
242242
let row = StViewRow::try_from(row)?;
243-
let has_args = self
244-
.iter_by_col_eq(ST_VIEW_PARAM_ID, StViewParamFields::ViewId, &row.view_id.into())?
245-
.next()
246-
.is_some();
247243

248244
Ok(ViewDefInfo {
249245
view_id: row.view_id,
250-
has_args,
251246
is_anonymous: row.is_anonymous,
252247
})
253248
})

crates/execution/src/lib.rs

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
use anyhow::Result;
22
use core::hash::{Hash, Hasher};
33
use core::ops::RangeBounds;
4-
use spacetimedb_lib::{identity::AuthCtx, query::Delta, AlgebraicType, Identity};
5-
use spacetimedb_physical_plan::plan::{ParamResolver, ProjectField, TupleField};
4+
use spacetimedb_lib::{hash_sender_view_args, identity::AuthCtx, query::Delta, AlgebraicType, Identity};
5+
use spacetimedb_physical_plan::plan::{
6+
ParamResolver, ParamSlot, ProjectField, TupleField, PARAM_SENDER, PARAM_VIEW_ARG_HASH,
7+
};
68
use spacetimedb_primitives::{ColList, IndexId, TableId};
79
use spacetimedb_sats::bsatn::{BufReservedFill, EncodeError, ToBsatn};
810
use spacetimedb_sats::buffer::BufWriter;
911
use spacetimedb_sats::product_value::InvalidFieldError;
10-
use spacetimedb_sats::{impl_serialize, AlgebraicValue, ProductValue};
11-
use spacetimedb_sql_parser::ast::Parameter;
12+
use spacetimedb_sats::{impl_serialize, u256, AlgebraicValue, ProductValue};
1213
use spacetimedb_table::{static_assert_size, table::RowRef};
1314

1415
pub mod dml;
@@ -17,11 +18,15 @@ pub mod pipelined;
1718
#[derive(Debug, Clone, Copy)]
1819
pub struct ExecutionParams {
1920
sender: Identity,
21+
view_arg_hash: u256,
2022
}
2123

2224
impl ExecutionParams {
2325
pub fn from_sender(sender: Identity) -> Self {
24-
Self { sender }
26+
Self {
27+
sender,
28+
view_arg_hash: hash_sender_view_args(sender).to_u256(),
29+
}
2530
}
2631

2732
pub fn from_auth(auth: &AuthCtx) -> Self {
@@ -30,11 +35,14 @@ impl ExecutionParams {
3035
}
3136

3237
impl ParamResolver for ExecutionParams {
33-
fn resolve_param(&self, param: Parameter, ty: &AlgebraicType) -> AlgebraicValue {
38+
fn resolve_param(&self, param: ParamSlot, ty: &AlgebraicType) -> AlgebraicValue {
3439
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:?}"),
40+
PARAM_SENDER if ty.is_identity() => self.sender.into(),
41+
PARAM_SENDER if ty.is_bytes() => AlgebraicValue::Bytes(self.sender.to_be_byte_array().into()),
42+
PARAM_SENDER => panic!("unsupported type for :sender: {ty:?}"),
43+
PARAM_VIEW_ARG_HASH if matches!(ty, AlgebraicType::U256) => AlgebraicValue::U256(self.view_arg_hash.into()),
44+
PARAM_VIEW_ARG_HASH => panic!("unsupported type for view arg hash: {ty:?}"),
45+
ParamSlot(slot) => panic!("unknown physical plan parameter slot: {slot}"),
3846
}
3947
}
4048
}

0 commit comments

Comments
 (0)