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
use std::any::type_name;
use std::collections::HashMap;
use std::marker::PhantomData;

use bonsaidb_core::arc_bytes::serde::Bytes;
use bonsaidb_core::document::DocumentId;
use bonsaidb_core::schema::CollectionName;
use bonsaidb_core::transaction::{ChangedDocument, ChangedKey, Changes, DocumentChanges};
use serde::{Deserialize, Serialize};
use transmog_versions::Versioned;

#[derive(thiserror::Error)]
pub struct UnknownVersion<T>(PhantomData<T>);

impl<T> Default for UnknownVersion<T> {
    fn default() -> Self {
        Self(PhantomData)
    }
}

impl<T> std::fmt::Debug for UnknownVersion<T> {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_tuple("UnknownVersion")
            .field(&type_name::<T>())
            .finish()
    }
}

impl<T> std::fmt::Display for UnknownVersion<T> {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "incompatilbe version of {}", type_name::<T>())
    }
}

#[derive(Clone, Copy, Debug)]
enum ChangesVersions {
    Legacy = 0,
    V1 = 1,
}

impl Versioned for ChangesVersions {
    fn version(&self) -> u64 {
        *self as u64
    }
}

impl TryFrom<u64> for ChangesVersions {
    type Error = UnknownVersion<Changes>;

    fn try_from(value: u64) -> Result<Self, Self::Error> {
        match value {
            0 => Ok(ChangesVersions::Legacy),
            1 => Ok(ChangesVersions::V1),
            _ => Err(UnknownVersion::default()),
        }
    }
}

pub fn deserialize_executed_transaction_changes(data: &[u8]) -> Result<Changes, crate::Error> {
    let (version, data) = transmog_versions::unwrap_version(data);
    match ChangesVersions::try_from(version)? {
        ChangesVersions::Legacy => {
            let legacy: ChangesV0 = match pot::from_slice(data) {
                Ok(changes) => changes,
                Err(pot::Error::NotAPot) => ChangesV0::Documents(bincode::deserialize(data)?),
                other => other?,
            };
            Changes::try_from(legacy).map_err(crate::Error::from)
        }
        ChangesVersions::V1 => pot::from_slice(data).map_err(crate::Error::from),
    }
}

pub fn serialize_executed_transaction_changes(changes: &Changes) -> Result<Vec<u8>, crate::Error> {
    let mut serialized = Vec::new();
    transmog_versions::write_header(&ChangesVersions::V1, &mut serialized)?;
    pot::to_writer(changes, &mut serialized)?;
    Ok(serialized)
}

/// A list of changes.
#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum ChangesV0 {
    /// A list of changed documents.
    Documents(Vec<ChangedDocumentV0>),
    /// A list of changed keys.
    Keys(Vec<ChangedKey>),
}

impl TryFrom<ChangesV0> for Changes {
    type Error = bonsaidb_core::Error;

    fn try_from(legacy: ChangesV0) -> Result<Self, Self::Error> {
        match legacy {
            ChangesV0::Documents(legacy_documents) => {
                let mut changed_documents = Vec::with_capacity(legacy_documents.len());
                let mut collections = Vec::new();
                let mut collection_indexes = HashMap::new();
                for changed in legacy_documents {
                    let collection = if let Some(id) = collection_indexes.get(&changed.collection) {
                        *id
                    } else {
                        let id = u16::try_from(collections.len()).unwrap();
                        collection_indexes.insert(changed.collection.clone(), id);
                        collections.push(changed.collection);
                        id
                    };
                    changed_documents.push(ChangedDocument {
                        collection,
                        id: changed.id.try_into()?,
                        deleted: changed.deleted,
                    });
                }
                Ok(Self::Documents(DocumentChanges {
                    collections,
                    documents: changed_documents,
                }))
            }
            ChangesV0::Keys(changes) => Ok(Self::Keys(changes)),
        }
    }
}

/// A record of a changed document.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ChangedDocumentV0 {
    /// The id of the `Collection` of the changed `Document`.
    pub collection: CollectionName,

    /// The id of the changed `Document`.
    pub id: LegacyDocumentId,

    /// If the `Document` has been deleted, this will be `true`.
    pub deleted: bool,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
pub enum LegacyDocumentId {
    U64(u64),
    Document(Bytes),
}

impl TryFrom<LegacyDocumentId> for DocumentId {
    type Error = bonsaidb_core::Error;

    fn try_from(id: LegacyDocumentId) -> Result<Self, Self::Error> {
        match id {
            LegacyDocumentId::Document(id) => DocumentId::try_from(&id[..]),
            LegacyDocumentId::U64(version) => Ok(DocumentId::from_u64(version)),
        }
    }
}