holochain_state/query/
entry_details.rs

1use 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        // Choose an arbitrary action.
142        // TODO: Is it sound to us a rejected action here?
143        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}