1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
use holo_hash::*;
use holochain_sqlite::rusqlite::named_params;
use holochain_types::dht_op::DhtOpType;
use holochain_types::prelude::DhtOpError;
use holochain_zome_types::*;
use std::fmt::Debug;

use super::*;

#[cfg(test)]
mod test;

#[derive(Debug, Clone)]
pub struct GetLiveEntryQuery(EntryHash, Option<Arc<AgentPubKey>>);

impl GetLiveEntryQuery {
    pub fn new(hash: EntryHash) -> Self {
        Self(hash, None)
    }
}

impl Query for GetLiveEntryQuery {
    type Item = Judged<SignedActionHashed>;
    type State = Maps<SignedActionHashed>;
    type Output = Option<Record>;

    fn query(&self) -> String {
        "
        SELECT Action.blob AS action_blob
        FROM DhtOp
        JOIN Action On DhtOp.action_hash = Action.hash
        WHERE DhtOp.type IN (:create_type, :delete_type, :update_type)
        AND DhtOp.basis_hash = :entry_hash
        AND DhtOp.validation_status = :status
        AND DhtOp.when_integrated IS NOT NULL
        AND (Action.private_entry = 0 OR Action.private_entry IS NULL OR Action.author = :author)
        "
        .into()
    }
    fn params(&self) -> Vec<Params> {
        let params = named_params! {
            ":create_type": DhtOpType::StoreEntry,
            ":delete_type": DhtOpType::RegisterDeletedEntryAction,
            ":update_type": DhtOpType::RegisterUpdatedContent,
            ":status": ValidationStatus::Valid,
            ":entry_hash": self.0,
            ":author": self.1,
        };
        params.to_vec()
    }

    fn as_map(&self) -> Arc<dyn Fn(&Row) -> StateQueryResult<Self::Item>> {
        let f = row_blob_to_action("action_blob");
        // Data is valid because it is filtered in the sql query.
        Arc::new(move |row| Ok(Judged::valid(f(row)?)))
    }

    fn as_filter(&self) -> Box<dyn Fn(&QueryData<Self>) -> bool> {
        let entry_filter = self.0.clone();
        let f = move |action: &QueryData<Self>| match action.action() {
            Action::Create(Create { entry_hash, .. })
            | Action::Update(Update { entry_hash, .. }) => *entry_hash == entry_filter,
            Action::Delete(Delete {
                deletes_entry_address,
                ..
            }) => *deletes_entry_address == entry_filter,
            _ => false,
        };
        Box::new(f)
    }

    fn init_fold(&self) -> StateQueryResult<Self::State> {
        Ok(Maps::new())
    }

    fn fold(&self, mut state: Self::State, data: Self::Item) -> StateQueryResult<Self::State> {
        let shh = data.data;
        let hash = shh.as_hash().clone();
        match shh.action() {
            Action::Create(_) => {
                if !state.deletes.contains(&hash) {
                    state.creates.insert(hash, shh);
                }
            }
            Action::Update(update) => {
                if update.original_entry_address == self.0 && update.entry_hash == self.0 {
                    follow_update_chain(&state, &shh);
                    if !state.deletes.contains(&hash) {
                        state.creates.insert(hash, shh);
                    }
                } else if update.entry_hash == self.0 {
                    if !state.deletes.contains(&hash) {
                        state.creates.insert(hash, shh);
                    }
                } else if update.original_entry_address == self.0 {
                    follow_update_chain(&state, &shh);
                }
            }
            Action::Delete(delete) => {
                state.creates.remove(&delete.deletes_address);
                state.deletes.insert(delete.deletes_address.clone());
            }
            _ => {
                return Err(StateQueryError::UnexpectedAction(
                    shh.action().action_type(),
                ))
            }
        }
        Ok(state)
    }

    fn render<S>(&self, state: Self::State, stores: S) -> StateQueryResult<Self::Output>
    where
        S: Store,
    {
        // If we have author authority then find an action from this author.
        let authored_action = self.1.as_ref().map(|a| a.as_ref()).and_then(|a| {
            state
                .creates
                .values()
                .find(|h| *h.action().author() == *a)
                .cloned()
        });
        let is_authored = authored_action.is_some();
        // If there is no authored action, choose an arbitrary action.
        let action = authored_action.or_else(|| {
            // The line below was added when migrating to rust edition 2021, per
            // https://doc.rust-lang.org/edition-guide/rust-2021/disjoint-capture-in-closures.html#migration
            let _ = &state;
            state.creates.into_values().next()
        });
        match action {
            Some(action) => {
                let entry_hash = action
                    .action()
                    .entry_hash()
                    .ok_or_else(|| DhtOpError::ActionWithoutEntry(action.action().clone()))?;
                // If this action is authored then we can get an authored entry.
                let author = is_authored.then(|| action.action().author());
                let record = stores
                    .get_public_or_authored_entry(entry_hash, author)?
                    .map(|entry| Record::new(action, Some(entry)));
                Ok(record)
            }
            None => Ok(None),
        }
    }
}

fn follow_update_chain(_state: &Maps<SignedActionHashed>, _shh: &SignedActionHashed) {
    // TODO: This is where update chains will be followed
    // when we add that functionality.
}

impl PrivateDataQuery for GetLiveEntryQuery {
    type Hash = EntryHash;

    fn with_private_data_access(hash: Self::Hash, author: Arc<AgentPubKey>) -> Self {
        Self(hash, Some(author))
    }

    fn without_private_data_access(hash: Self::Hash) -> Self {
        Self::new(hash)
    }
}