holochain_state/query/
entry_details.rs1use super::*;
2use holochain_sqlite::rusqlite::named_params;
3use holochain_types::prelude::DhtOpError;
4use holochain_types::prelude::Judged;
5use holochain_zome_types::op::ChainOpType;
6use std::fmt::Debug;
7
8#[derive(Debug, Clone)]
9pub struct GetEntryDetailsQuery(EntryHash, Option<Arc<AgentPubKey>>);
10
11impl GetEntryDetailsQuery {
12 pub fn new(hash: EntryHash) -> Self {
13 Self(hash, None)
14 }
15}
16
17pub struct State {
18 actions: HashSet<SignedActionHashed>,
19 rejected_actions: HashSet<SignedActionHashed>,
20 deletes: HashMap<ActionHash, SignedActionHashed>,
21 updates: HashSet<SignedActionHashed>,
22}
23
24impl Query for GetEntryDetailsQuery {
25 type Item = Judged<SignedActionHashed>;
26 type State = State;
27 type Output = Option<EntryDetails>;
28
29 fn query(&self) -> String {
30 "
31 SELECT Action.blob AS action_blob, DhtOp.validation_status AS status
32 FROM DhtOp
33 JOIN Action On DhtOp.action_hash = Action.hash
34 WHERE DhtOp.type IN (:create_type, :delete_type, :update_type)
35 AND DhtOp.basis_hash = :entry_hash
36 AND DhtOp.when_integrated IS NOT NULL
37 AND DhtOp.validation_status IS NOT NULL
38 AND (Action.private_entry = 0 OR Action.private_entry IS NULL OR Action.author = :author)
39 "
40 .into()
41 }
42 fn params(&self) -> Vec<Params<'_>> {
43 let params = named_params! {
44 ":create_type": ChainOpType::StoreEntry,
45 ":delete_type": ChainOpType::RegisterDeletedEntryAction,
46 ":update_type": ChainOpType::RegisterUpdatedContent,
47 ":entry_hash": self.0,
48 ":author": self.1,
49 };
50 params.to_vec()
51 }
52
53 fn as_map(&self) -> Arc<dyn Fn(&Row) -> StateQueryResult<Self::Item>> {
54 let f = |row: &Row| {
55 let action =
56 from_blob::<SignedAction>(row.get(row.as_ref().column_index("action_blob")?)?)?;
57 let (action, signature) = action.into();
58 let action = ActionHashed::from_content_sync(action);
59 let shh = SignedActionHashed::with_presigned(action, signature);
60 let status = row.get(row.as_ref().column_index("status")?)?;
61 let r = Judged::new(shh, status);
62 Ok(r)
63 };
64 Arc::new(f)
65 }
66
67 fn as_filter(&self) -> Box<dyn Fn(&QueryData<Self>) -> bool> {
68 let entry_filter = self.0.clone();
69 let f = move |action: &QueryData<Self>| {
70 let action = &action;
71 match action.action() {
72 Action::Create(Create { entry_hash, .. })
73 | Action::Update(Update { entry_hash, .. })
74 if *entry_hash == entry_filter =>
75 {
76 true
77 }
78 Action::Update(Update {
79 original_entry_address,
80 ..
81 }) => *original_entry_address == entry_filter,
82 Action::Delete(Delete {
83 deletes_entry_address,
84 ..
85 }) => *deletes_entry_address == entry_filter,
86 _ => false,
87 }
88 };
89 Box::new(f)
90 }
91
92 fn init_fold(&self) -> StateQueryResult<Self::State> {
93 Ok(State {
94 actions: Default::default(),
95 rejected_actions: Default::default(),
96 deletes: Default::default(),
97 updates: Default::default(),
98 })
99 }
100
101 fn fold(&self, mut state: Self::State, item: Self::Item) -> StateQueryResult<Self::State> {
102 let (shh, validation_status) = item.into();
103 let add_action = |state: &mut State, shh| match validation_status {
104 Some(ValidationStatus::Valid) => {
105 state.actions.insert(shh);
106 }
107 Some(ValidationStatus::Rejected) => {
108 state.rejected_actions.insert(shh);
109 }
110 _ => (),
111 };
112 match shh.action() {
113 Action::Create(_) => add_action(&mut state, shh),
114 Action::Update(update) => {
115 if update.original_entry_address == self.0 && update.entry_hash == self.0 {
116 state.updates.insert(shh.clone());
117 add_action(&mut state, shh);
118 } else if update.entry_hash == self.0 {
119 add_action(&mut state, shh);
120 } else if update.original_entry_address == self.0 {
121 state.updates.insert(shh.clone());
122 }
123 }
124 Action::Delete(delete) => {
125 let hash = delete.deletes_address.clone();
126 state.deletes.insert(hash, shh.clone());
127 }
128 _ => {
129 return Err(StateQueryError::UnexpectedAction(
130 shh.action().action_type(),
131 ))
132 }
133 }
134 Ok(state)
135 }
136
137 fn render<S>(&self, state: Self::State, stores: S) -> StateQueryResult<Self::Output>
138 where
139 S: Store,
140 {
141 let action = state
144 .actions
145 .iter()
146 .chain(state.rejected_actions.iter())
147 .next();
148 match action {
149 Some(action) => {
150 let entry_hash = action.action().entry_hash().ok_or_else(|| {
151 DhtOpError::ActionWithoutEntry(Box::new(action.action().clone()))
152 })?;
153 let author = self.1.as_ref().map(|a| a.as_ref());
154 let details = stores
155 .get_public_or_authored_entry(entry_hash, author)?
156 .map(|entry| {
157 let entry_dht_status = compute_entry_status(&state);
158 EntryDetails {
159 entry,
160 actions: state.actions.into_iter().collect(),
161 rejected_actions: state.rejected_actions.into_iter().collect(),
162 deletes: state.deletes.into_values().collect(),
163 updates: state.updates.into_iter().collect(),
164 entry_dht_status,
165 }
166 });
167 Ok(details)
168 }
169 None => Ok(None),
170 }
171 }
172}
173
174fn compute_entry_status(state: &State) -> EntryDhtStatus {
175 let live_actions = state
176 .actions
177 .iter()
178 .filter(|h| !state.deletes.contains_key(h.action_address()))
179 .count();
180 if live_actions > 0 {
181 EntryDhtStatus::Live
182 } else {
183 EntryDhtStatus::Dead
184 }
185}
186
187impl PrivateDataQuery for GetEntryDetailsQuery {
188 type Hash = EntryHash;
189
190 fn with_private_data_access(hash: Self::Hash, author: Arc<AgentPubKey>) -> Self {
191 Self(hash, Some(author))
192 }
193
194 fn without_private_data_access(hash: Self::Hash) -> Self {
195 Self::new(hash)
196 }
197}