substreams_database_change/
change.rs

1use crate::pb::database::Field;
2use std::str;
3use substreams::pb::substreams::store_delta::Operation;
4use substreams::scalar::{BigDecimal, BigInt};
5use substreams::store::{
6    DeltaBigDecimal, DeltaBigInt, DeltaBool, DeltaBytes, DeltaInt32, DeltaInt64, DeltaString,
7};
8use substreams::Hex;
9
10pub trait ToField {
11    fn to_field<N: AsRef<str>>(self, name: N) -> Field;
12}
13
14/// We require to use a custom trait because we need to customize some of the string
15/// transformation and we need to control the String conversion ourself.
16pub trait AsString {
17    fn as_string(self) -> String;
18}
19
20macro_rules! impl_as_string_via_to_string {
21    ($name:ty) => {
22        impl AsString for $name {
23            fn as_string(self) -> String {
24                self.to_string()
25            }
26        }
27    };
28}
29
30impl_as_string_via_to_string!(i8);
31impl_as_string_via_to_string!(i16);
32impl_as_string_via_to_string!(i32);
33impl_as_string_via_to_string!(i64);
34impl_as_string_via_to_string!(u8);
35impl_as_string_via_to_string!(u16);
36impl_as_string_via_to_string!(u32);
37impl_as_string_via_to_string!(u64);
38impl_as_string_via_to_string!(String);
39impl_as_string_via_to_string!(&String);
40impl_as_string_via_to_string!(&str);
41impl_as_string_via_to_string!(bool);
42impl_as_string_via_to_string!(BigDecimal);
43impl_as_string_via_to_string!(&BigDecimal);
44impl_as_string_via_to_string!(BigInt);
45impl_as_string_via_to_string!(&BigInt);
46impl_as_string_via_to_string!(::prost_types::Timestamp);
47impl_as_string_via_to_string!(&::prost_types::Timestamp);
48
49impl<T: AsRef<[u8]>> AsString for Hex<T> {
50    fn as_string(self) -> String {
51        self.to_string()
52    }
53}
54
55impl<T: AsRef<[u8]>> AsString for &Hex<T> {
56    fn as_string(self) -> String {
57        self.to_string()
58    }
59}
60
61impl AsString for Vec<u8> {
62    fn as_string(self) -> String {
63        Hex::encode(self)
64    }
65}
66
67impl AsString for &Vec<u8> {
68    fn as_string(self) -> String {
69        Hex::encode(self)
70    }
71}
72
73impl<T: AsString> ToField for (T, T) {
74    fn to_field<N: AsRef<str>>(self, name: N) -> Field {
75        Field {
76            name: name.as_ref().to_string(),
77            old_value: self.0.as_string(),
78            new_value: self.1.as_string(),
79        }
80    }
81}
82
83impl<T: AsString> ToField for (Option<T>, T) {
84    fn to_field<N: AsRef<str>>(self, name: N) -> Field {
85        match self {
86            (Some(old), new) => ToField::to_field((old, new), name),
87            (None, new) => Field {
88                name: name.as_ref().to_string(),
89                old_value: "".to_string(),
90                new_value: new.as_string(),
91            },
92        }
93    }
94}
95
96impl<T: AsString> ToField for (T, Option<T>) {
97    fn to_field<N: AsRef<str>>(self, name: N) -> Field {
98        match self {
99            (old, Some(new)) => ToField::to_field((old, new), name),
100            (old, None) => Field {
101                name: name.as_ref().to_string(),
102                old_value: old.as_string(),
103                new_value: "".to_string(),
104            },
105        }
106    }
107}
108
109fn delta_to_field<T: AsString>(
110    name: &str,
111    operation: Operation,
112    old_value: T,
113    new_value: T,
114) -> Field {
115    match Operation::from(operation) {
116        Operation::Update => ToField::to_field((old_value, new_value), name),
117        Operation::Create => ToField::to_field((None, new_value), name),
118        Operation::Delete => ToField::to_field((None, new_value), name),
119        Operation::Unset => panic!("unsupported operation {:?}", Operation::from(operation)),
120    }
121}
122
123macro_rules! impl_to_field_from_delta_via_move {
124    ($type:ty) => {
125        impl ToField for $type {
126            fn to_field<N: AsRef<str>>(self, name: N) -> Field {
127                delta_to_field(
128                    name.as_ref(),
129                    self.operation,
130                    self.old_value,
131                    self.new_value,
132                )
133            }
134        }
135    };
136}
137
138macro_rules! impl_to_field_from_delta_via_ref {
139    ($type:ty) => {
140        impl ToField for $type {
141            fn to_field<N: AsRef<str>>(self, name: N) -> Field {
142                delta_to_field(
143                    name.as_ref(),
144                    self.operation,
145                    &self.old_value,
146                    &self.new_value,
147                )
148            }
149        }
150    };
151}
152
153impl_to_field_from_delta_via_move!(&DeltaInt32);
154impl_to_field_from_delta_via_move!(&DeltaInt64);
155impl_to_field_from_delta_via_ref!(&DeltaBigDecimal);
156impl_to_field_from_delta_via_ref!(&DeltaBigInt);
157impl_to_field_from_delta_via_move!(&DeltaBool);
158impl_to_field_from_delta_via_ref!(&DeltaBytes);
159impl_to_field_from_delta_via_ref!(&DeltaString);
160
161#[cfg(test)]
162mod test {
163    use crate::change::ToField;
164    use crate::pb::database::Field;
165    use substreams::pb::substreams::store_delta::Operation;
166    use substreams::scalar::{BigDecimal, BigInt};
167    use substreams::store::{DeltaBigDecimal, DeltaBigInt, DeltaBool, DeltaBytes, DeltaString};
168
169    const FIELD_NAME: &str = "field.name.1";
170
171    #[test]
172    fn i32_change() {
173        let i32_change = (None, 1i32);
174        assert_eq!(
175            create_expected_field(FIELD_NAME, None, Some("1".to_string())),
176            i32_change.to_field(FIELD_NAME)
177        );
178    }
179
180    #[test]
181    fn big_decimal_change() {
182        let bd_change = (None, BigDecimal::from(1 as i32));
183        assert_eq!(
184            create_expected_field(FIELD_NAME, None, Some("1".to_string())),
185            bd_change.to_field(FIELD_NAME)
186        );
187    }
188
189    #[test]
190    fn delta_big_decimal_change() {
191        let delta = DeltaBigDecimal {
192            operation: Operation::Update,
193            ordinal: 0,
194            key: "change".to_string(),
195            old_value: BigDecimal::from(10),
196            new_value: BigDecimal::from(20),
197        };
198
199        assert_eq!(
200            create_expected_field(FIELD_NAME, Some("10".to_string()), Some("20".to_string())),
201            delta.to_field(FIELD_NAME)
202        );
203    }
204
205    #[test]
206    fn big_int_change() {
207        let bi_change = (None, BigInt::from(1 as i32));
208        assert_eq!(
209            create_expected_field(FIELD_NAME, None, Some("1".to_string())),
210            bi_change.to_field(FIELD_NAME)
211        );
212    }
213
214    #[test]
215    fn delta_big_int_change() {
216        let delta = DeltaBigInt {
217            operation: Operation::Update,
218            ordinal: 0,
219            key: "change".to_string(),
220            old_value: BigInt::from(10),
221            new_value: BigInt::from(20),
222        };
223
224        assert_eq!(
225            create_expected_field(FIELD_NAME, Some("10".to_string()), Some("20".to_string())),
226            delta.to_field(FIELD_NAME)
227        );
228    }
229
230    #[test]
231    fn string_change() {
232        let string_change = (None, String::from("string"));
233        assert_eq!(
234            create_expected_field(FIELD_NAME, None, Some("string".to_string())),
235            string_change.to_field(FIELD_NAME)
236        );
237    }
238
239    #[test]
240    fn delta_string_change() {
241        let delta = DeltaString {
242            operation: Operation::Update,
243            ordinal: 0,
244            key: "change".to_string(),
245            old_value: String::from("string1"),
246            new_value: String::from("string2"),
247        };
248
249        assert_eq!(
250            create_expected_field(
251                FIELD_NAME,
252                Some("string1".to_string()),
253                Some("string2".to_string())
254            ),
255            delta.to_field(FIELD_NAME)
256        );
257    }
258
259    #[test]
260    fn bytes_change() {
261        let bytes_change: (Option<Vec<u8>>, Vec<u8>) = (None, Vec::from("bytes"));
262        assert_eq!(
263            create_expected_field(FIELD_NAME, None, Some("6279746573".to_string())),
264            bytes_change.to_field(FIELD_NAME)
265        );
266    }
267
268    #[test]
269    fn delta_bytes_change() {
270        let delta = DeltaBytes {
271            operation: Operation::Update,
272            ordinal: 0,
273            key: "change".to_string(),
274            old_value: Vec::from("bytes1"),
275            new_value: Vec::from("bytes2"),
276        };
277
278        assert_eq!(
279            create_expected_field(
280                FIELD_NAME,
281                Some("627974657331".to_string()),
282                Some("627974657332".to_string())
283            ),
284            delta.to_field(FIELD_NAME)
285        );
286    }
287
288    #[test]
289    fn bool_change() {
290        let bool_change = (None, true);
291        assert_eq!(
292            create_expected_field(FIELD_NAME, None, Some("true".to_string())),
293            bool_change.to_field(FIELD_NAME)
294        );
295    }
296
297    #[test]
298    fn delta_bool_change() {
299        let delta = DeltaBool {
300            operation: Operation::Update,
301            ordinal: 0,
302            key: "change".to_string(),
303            old_value: true,
304            new_value: false,
305        };
306
307        assert_eq!(
308            create_expected_field(FIELD_NAME, Some(true.to_string()), Some(false.to_string()),),
309            delta.to_field(FIELD_NAME)
310        );
311    }
312
313    fn create_expected_field<N: AsRef<str>>(
314        name: N,
315        old_value: Option<String>,
316        new_value: Option<String>,
317    ) -> Field {
318        let mut field = Field {
319            name: name.as_ref().to_string(),
320            ..Default::default()
321        };
322        if old_value.is_some() {
323            field.old_value = old_value.unwrap()
324        }
325        if new_value.is_some() {
326            field.new_value = new_value.unwrap()
327        }
328        field
329    }
330}