use crate::{db::ReadSetRevisionProof, value::OutputValue};
use candid::CandidType;
use serde::Deserialize;
#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq)]
pub struct ScalarPageWork {
pub envelope_identity: u64,
pub entries_visited: u64,
pub result_rows: u32,
}
#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq)]
pub struct LiveQueryPageOutput {
pub entity: String,
pub columns: Vec<String>,
pub rows: Vec<Vec<OutputValue>>,
pub row_count: u32,
pub continuation: Option<String>,
pub work: ScalarPageWork,
}
#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq)]
pub struct ExhaustiveQueryPageOutput {
pub entity: String,
pub columns: Vec<String>,
pub rows: Vec<Vec<OutputValue>>,
pub row_count: u32,
pub continuation: Option<String>,
pub work: ScalarPageWork,
pub proof: ReadSetRevisionProof,
}
impl ExhaustiveQueryPageOutput {
pub(in crate::db) fn from_live_page(
page: LiveQueryPageOutput,
proof: ReadSetRevisionProof,
) -> Self {
Self {
entity: page.entity,
columns: page.columns,
rows: page.rows,
row_count: page.row_count,
continuation: page.continuation,
work: page.work,
proof,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[derive(CandidType)]
struct FrozenScalarPageWorkWire {
envelope_identity: u64,
entries_visited: u64,
result_rows: u32,
}
#[derive(CandidType)]
struct FrozenLiveQueryPageOutputWire {
entity: String,
columns: Vec<String>,
rows: Vec<Vec<OutputValue>>,
row_count: u32,
continuation: Option<String>,
work: FrozenScalarPageWorkWire,
}
#[derive(CandidType)]
struct FrozenReadSetStoreIdentityWire([u8; 32]);
#[derive(CandidType)]
struct FrozenReadSetStoreRevisionWire {
store: FrozenReadSetStoreIdentityWire,
data_revision: u64,
access_state_revision: u64,
}
#[derive(CandidType)]
struct FrozenReadSetRevisionProofWire {
database_incarnation: [u8; 16],
accepted_root_revision: u64,
accepted_root_fingerprint_method: u8,
accepted_root_fingerprint: [u8; 32],
stores: Vec<FrozenReadSetStoreRevisionWire>,
}
#[derive(CandidType)]
struct FrozenExhaustiveQueryPageOutputWire {
entity: String,
columns: Vec<String>,
rows: Vec<Vec<OutputValue>>,
row_count: u32,
continuation: Option<String>,
work: FrozenScalarPageWorkWire,
proof: FrozenReadSetRevisionProofWire,
}
#[test]
fn live_query_page_output_preserves_its_initial_candid_record_shape() {
let current = LiveQueryPageOutput {
entity: "example".to_string(),
columns: vec!["id".to_string()],
rows: vec![vec![OutputValue::Nat64(7)]],
row_count: 1,
continuation: Some("opaque".to_string()),
work: ScalarPageWork {
envelope_identity: 11,
entries_visited: 2,
result_rows: 1,
},
};
let frozen = FrozenLiveQueryPageOutputWire {
entity: current.entity.clone(),
columns: current.columns.clone(),
rows: current.rows.clone(),
row_count: current.row_count,
continuation: current.continuation.clone(),
work: FrozenScalarPageWorkWire {
envelope_identity: current.work.envelope_identity,
entries_visited: current.work.entries_visited,
result_rows: current.work.result_rows,
},
};
assert_eq!(
candid::encode_one(¤t).expect("current live page should encode"),
candid::encode_one(&frozen).expect("frozen live page should encode"),
);
}
#[test]
fn exhaustive_query_page_output_freezes_its_initial_candid_record_shape() {
let proof = ReadSetRevisionProof::from_parts(
[1; 16],
7,
1,
[2; 32],
vec![crate::db::ReadSetStoreRevision::new(
crate::db::ReadSetStoreIdentity::from_bytes([3; 32]),
11,
13,
)],
)
.expect("bounded canonical proof should admit");
let current = ExhaustiveQueryPageOutput {
entity: "example".to_string(),
columns: vec!["id".to_string()],
rows: vec![vec![OutputValue::Nat64(7)]],
row_count: 1,
continuation: Some("opaque".to_string()),
work: ScalarPageWork {
envelope_identity: 11,
entries_visited: 2,
result_rows: 1,
},
proof,
};
let frozen = FrozenExhaustiveQueryPageOutputWire {
entity: current.entity.clone(),
columns: current.columns.clone(),
rows: current.rows.clone(),
row_count: current.row_count,
continuation: current.continuation.clone(),
work: FrozenScalarPageWorkWire {
envelope_identity: current.work.envelope_identity,
entries_visited: current.work.entries_visited,
result_rows: current.work.result_rows,
},
proof: FrozenReadSetRevisionProofWire {
database_incarnation: current.proof.database_incarnation(),
accepted_root_revision: current.proof.accepted_root_revision(),
accepted_root_fingerprint_method: current.proof.accepted_root_fingerprint_method(),
accepted_root_fingerprint: current.proof.accepted_root_fingerprint(),
stores: current
.proof
.stores()
.iter()
.map(|store| FrozenReadSetStoreRevisionWire {
store: FrozenReadSetStoreIdentityWire(store.store().to_bytes()),
data_revision: store.data_revision(),
access_state_revision: store.access_state_revision(),
})
.collect(),
},
};
assert_eq!(
candid::encode_one(¤t).expect("current exhaustive page should encode"),
candid::encode_one(&frozen).expect("frozen exhaustive page should encode"),
);
}
}