Skip to main content

hidane_core/export/
datastore.rs

1//! Datastore mode's entities as App Engine `EntityProto`s, the records of an export the official
2//! emulator writes in `--database-mode datastore-mode` (`tests/fixtures/datastore_export.json`
3//! in the `hidane` crate).
4//!
5//! The mapping differs from [`super::entity`]'s for documents:
6//!
7//! - the key's app is the project itself (no `dev~`), a namespace is the key's `name_space`;
8//! - indexed values are properties, values excluded from indexes raw properties: first every
9//!   indexed property in the entity's order, then every raw one; an array is one property per
10//!   element, marked `multiple`, each in its own group; an empty array is an indexed property of
11//!   its own meaning, where it stands;
12//! - a value's meaning is written as the entity holds it, or else its type's: a timestamp's (7, in
13//!   microseconds), a geo point's (9), an entity value's (19, a nested entity, keyless or not), a
14//!   blob's (16 indexed, 14 not); meaning 22 is 14 with the URI `ZLIB`;
15//! - a double zero is `0.0`, whatever its sign.
16//!
17//! Reading back, properties come in the order the record holds them and array elements in the
18//! order of their properties; the meanings a type gives are dropped.
19
20use super::wire::{Field, Malformed, Reader, Writer};
21use hidane_proto::google::{
22    datastore::v1::{
23        ArrayValue, Entity, Key, PartitionId, Value,
24        key::{PathElement, path_element::IdType},
25        value::ValueType,
26    },
27    r#type::LatLng,
28};
29use hidane_proto::ordered::Properties;
30use prost_types::Timestamp;
31
32// EntityProto
33const KEY: u32 = 13;
34const PROPERTY: u32 = 14;
35const RAW_PROPERTY: u32 = 15;
36const ENTITY_GROUP: u32 = 16;
37// Reference (the key)
38const APP: u32 = 13;
39const PATH: u32 = 14;
40const NAME_SPACE: u32 = 20;
41const DATABASE_ID: u32 = 23;
42// Path
43const ELEMENT: u32 = 1;
44const ELEMENT_TYPE: u32 = 2;
45const ELEMENT_ID: u32 = 3;
46const ELEMENT_NAME: u32 = 4;
47// Property
48const MEANING: u32 = 1;
49const MEANING_URI: u32 = 2;
50const NAME: u32 = 3;
51const MULTIPLE: u32 = 4;
52const VALUE: u32 = 5;
53// PropertyValue
54const INT64: u32 = 1;
55const BOOLEAN: u32 = 2;
56const STRING: u32 = 3;
57const DOUBLE: u32 = 4;
58const POINT: u32 = 5;
59const POINT_X: u32 = 6;
60const POINT_Y: u32 = 7;
61const REFERENCE: u32 = 12;
62const REFERENCE_APP: u32 = 13;
63const REFERENCE_ELEMENT: u32 = 14;
64const REFERENCE_TYPE: u32 = 15;
65const REFERENCE_ID: u32 = 16;
66const REFERENCE_NAME: u32 = 17;
67const REFERENCE_NAME_SPACE: u32 = 20;
68const REFERENCE_DATABASE_ID: u32 = 23;
69// Meanings
70const GD_WHEN: i32 = 7;
71const GEORSS_POINT: i32 = 9;
72const BLOB: i32 = 14;
73const BYTESTRING: i32 = 16;
74const ENTITY_PROTO: i32 = 19;
75const ZLIB: i32 = 22;
76const EMPTY_LIST: i32 = 24;
77const ZLIB_URI: &[u8] = b"ZLIB";
78
79/// Encodes the entity at `key` with `properties`.
80pub fn encode(key: &Key, properties: &Properties) -> Vec<u8> {
81    let mut w = Writer::default();
82    entity(&mut w, Some(key), properties);
83    w.into_bytes()
84}
85
86fn entity(w: &mut Writer, key: Option<&Key>, properties: &Properties) {
87    w.message(KEY, |k| match key {
88        Some(key) => {
89            let partition = key.partition_id.clone().unwrap_or_default();
90            k.bytes(APP, partition.project_id.as_bytes());
91            k.message(PATH, |p| {
92                for element in &key.path {
93                    p.group(ELEMENT, |e| {
94                        path_element(e, element, ELEMENT_TYPE, ELEMENT_ID, ELEMENT_NAME);
95                    });
96                }
97            });
98            if !partition.namespace_id.is_empty() {
99                k.bytes(NAME_SPACE, partition.namespace_id.as_bytes());
100            }
101            if !partition.database_id.is_empty() {
102                k.bytes(DATABASE_ID, partition.database_id.as_bytes());
103            }
104        }
105        None => {
106            k.bytes(APP, b"");
107            k.message(PATH, |_| {});
108        }
109    });
110    for indexed in [true, false] {
111        for (name, value) in properties {
112            match &value.value_type {
113                Some(ValueType::ArrayValue(array)) if array.values.is_empty() => {
114                    if indexed {
115                        w.message(PROPERTY, |p| {
116                            p.uint(MEANING, EMPTY_LIST as u64);
117                            p.bytes(NAME, name.as_bytes());
118                            p.uint(MULTIPLE, 0);
119                            p.message(VALUE, |_| {});
120                        });
121                    }
122                }
123                Some(ValueType::ArrayValue(array)) => {
124                    for element in &array.values {
125                        if !element.exclude_from_indexes == indexed {
126                            property(w, name, element, true);
127                        }
128                    }
129                }
130                _ => {
131                    if !value.exclude_from_indexes == indexed {
132                        property(w, name, value, false);
133                    }
134                }
135            }
136        }
137    }
138    w.message(ENTITY_GROUP, |g| {
139        if let Some(root) = key.and_then(|key| key.path.first()) {
140            g.group(ELEMENT, |e| {
141                path_element(e, root, ELEMENT_TYPE, ELEMENT_ID, ELEMENT_NAME);
142            });
143        }
144    });
145}
146
147fn path_element(w: &mut Writer, element: &PathElement, kind: u32, id: u32, name: u32) {
148    w.bytes(kind, element.kind.as_bytes());
149    match &element.id_type {
150        Some(IdType::Id(n)) => w.int(id, *n),
151        Some(IdType::Name(text)) => w.bytes(name, text.as_bytes()),
152        None => {}
153    }
154}
155
156/// The meaning a property is written with, and its URI.
157fn meaning(value: &Value) -> (Option<i32>, Option<&'static [u8]>) {
158    if value.meaning == ZLIB {
159        return (Some(BLOB), Some(ZLIB_URI));
160    }
161    if value.meaning != 0 {
162        return (Some(value.meaning), None);
163    }
164    let meaning = match &value.value_type {
165        Some(ValueType::TimestampValue(_)) => Some(GD_WHEN),
166        Some(ValueType::GeoPointValue(_)) => Some(GEORSS_POINT),
167        Some(ValueType::EntityValue(_)) => Some(ENTITY_PROTO),
168        Some(ValueType::BlobValue(_)) if value.exclude_from_indexes => Some(BLOB),
169        Some(ValueType::BlobValue(_)) => Some(BYTESTRING),
170        _ => None,
171    };
172    (meaning, None)
173}
174
175fn property(w: &mut Writer, name: &str, value: &Value, multiple: bool) {
176    let field = if value.exclude_from_indexes {
177        RAW_PROPERTY
178    } else {
179        PROPERTY
180    };
181    let (meaning, uri) = meaning(value);
182    w.message(field, |p| {
183        if let Some(meaning) = meaning {
184            #[allow(clippy::cast_sign_loss)]
185            p.uint(MEANING, meaning as u64);
186        }
187        if let Some(uri) = uri {
188            p.bytes(MEANING_URI, uri);
189        }
190        p.bytes(NAME, name.as_bytes());
191        p.uint(MULTIPLE, u64::from(multiple));
192        p.message(VALUE, |v| property_value(v, value));
193    });
194}
195
196fn property_value(w: &mut Writer, value: &Value) {
197    match &value.value_type {
198        Some(ValueType::BooleanValue(b)) => w.uint(BOOLEAN, u64::from(*b)),
199        Some(ValueType::IntegerValue(i)) => w.int(INT64, *i),
200        Some(ValueType::DoubleValue(d)) => w.double(DOUBLE, if *d == 0.0 { 0.0 } else { *d }),
201        Some(ValueType::TimestampValue(ts)) => w.int(INT64, micros(ts)),
202        Some(ValueType::StringValue(s)) => w.bytes(STRING, s.as_bytes()),
203        Some(ValueType::BlobValue(b)) => w.bytes(STRING, b),
204        Some(ValueType::GeoPointValue(point)) => w.group(POINT, |p| {
205            p.double(POINT_X, point.latitude);
206            p.double(POINT_Y, point.longitude);
207        }),
208        Some(ValueType::KeyValue(key)) => w.group(REFERENCE, |r| {
209            let partition = key.partition_id.clone().unwrap_or_default();
210            r.bytes(REFERENCE_APP, partition.project_id.as_bytes());
211            for element in &key.path {
212                r.group(REFERENCE_ELEMENT, |e| {
213                    path_element(e, element, REFERENCE_TYPE, REFERENCE_ID, REFERENCE_NAME);
214                });
215            }
216            if !partition.namespace_id.is_empty() {
217                r.bytes(REFERENCE_NAME_SPACE, partition.namespace_id.as_bytes());
218            }
219            if !partition.database_id.is_empty() {
220                r.bytes(REFERENCE_DATABASE_ID, partition.database_id.as_bytes());
221            }
222        }),
223        Some(ValueType::EntityValue(nested)) => {
224            let mut inner = Writer::default();
225            entity(&mut inner, nested.key.as_ref(), &nested.properties);
226            w.bytes(STRING, &inner.into_bytes());
227        }
228        // Null; arrays are expanded by the caller.
229        _ => {}
230    }
231}
232
233fn micros(ts: &Timestamp) -> i64 {
234    ts.seconds
235        .saturating_mul(1_000_000)
236        .saturating_add(i64::from(ts.nanos / 1000))
237}
238
239/// Decodes an entity, as written by the official emulator or by [`encode`]: its key and its
240/// properties.
241pub fn decode(bytes: &[u8]) -> Result<(Key, Properties), Malformed> {
242    let (key, properties) = decode_entity(bytes)?;
243    let key = key.ok_or(Malformed)?;
244    if key.path.is_empty() {
245        return Err(Malformed);
246    }
247    Ok((key, properties))
248}
249
250fn decode_entity(bytes: &[u8]) -> Result<(Option<Key>, Properties), Malformed> {
251    let mut key = None;
252    let mut properties = Properties::new();
253    for field in Reader::new(bytes) {
254        let (number, value) = field?;
255        match number {
256            KEY => key = decode_key(value)?,
257            PROPERTY => decode_property(&mut properties, value, false)?,
258            RAW_PROPERTY => decode_property(&mut properties, value, true)?,
259            _ => {}
260        }
261    }
262    Ok((key, properties))
263}
264
265/// A key: `None` for the empty one a keyless entity value holds.
266fn decode_key(field: Field<'_>) -> Result<Option<Key>, Malformed> {
267    let mut partition = PartitionId::default();
268    let mut path = Vec::new();
269    for field in field.fields().ok_or(Malformed)? {
270        let (number, value) = field?;
271        match number {
272            APP => partition.project_id = text(value)?,
273            PATH => {
274                for field in value.fields().ok_or(Malformed)? {
275                    let (number, value) = field?;
276                    if number == ELEMENT {
277                        path.push(decode_element(
278                            value,
279                            ELEMENT_TYPE,
280                            ELEMENT_ID,
281                            ELEMENT_NAME,
282                        )?);
283                    }
284                }
285            }
286            NAME_SPACE => partition.namespace_id = text(value)?,
287            DATABASE_ID => partition.database_id = text(value)?,
288            _ => {}
289        }
290    }
291    if partition.project_id.is_empty() && path.is_empty() {
292        return Ok(None);
293    }
294    Ok(Some(Key {
295        partition_id: Some(partition),
296        path,
297    }))
298}
299
300fn text(field: Field<'_>) -> Result<String, Malformed> {
301    String::from_utf8(field.as_bytes().ok_or(Malformed)?.to_vec()).map_err(|_| Malformed)
302}
303
304fn decode_element(
305    field: Field<'_>,
306    kind: u32,
307    id: u32,
308    name: u32,
309) -> Result<PathElement, Malformed> {
310    let mut element = PathElement::default();
311    for field in field.fields().ok_or(Malformed)? {
312        let (number, value) = field?;
313        if number == kind {
314            element.kind = text(value)?;
315        } else if number == id {
316            #[allow(clippy::cast_possible_wrap)]
317            let n = value.as_varint().ok_or(Malformed)? as i64;
318            element.id_type = Some(IdType::Id(n));
319        } else if number == name {
320            element.id_type = Some(IdType::Name(text(value)?));
321        }
322    }
323    Ok(element)
324}
325
326fn decode_property(
327    properties: &mut Properties,
328    field: Field<'_>,
329    raw: bool,
330) -> Result<(), Malformed> {
331    let (mut meaning, mut uri, mut name, mut multiple, mut bytes) =
332        (0i32, None, None, false, &[][..]);
333    for field in field.fields().ok_or(Malformed)? {
334        let (number, field) = field?;
335        match number {
336            #[allow(clippy::cast_possible_truncation)]
337            MEANING => meaning = field.as_varint().ok_or(Malformed)? as i32,
338            MEANING_URI => uri = Some(field.as_bytes().ok_or(Malformed)?),
339            NAME => name = Some(text(field)?),
340            MULTIPLE => multiple = field.as_varint().ok_or(Malformed)? != 0,
341            VALUE => bytes = field.as_bytes().ok_or(Malformed)?,
342            _ => {}
343        }
344    }
345    let name = name.ok_or(Malformed)?;
346    if meaning == EMPTY_LIST {
347        properties.insert(name, array(Vec::new()));
348        return Ok(());
349    }
350    let mut value = decode_value(bytes, meaning)?;
351    value.exclude_from_indexes = raw;
352    value.meaning = match (meaning, uri) {
353        (BLOB, Some(ZLIB_URI)) => ZLIB,
354        (GD_WHEN | GEORSS_POINT | ENTITY_PROTO | BLOB | BYTESTRING, _) => 0,
355        (meaning, _) => meaning,
356    };
357    if multiple {
358        let entry = properties.entry(name).or_insert_with(|| array(Vec::new()));
359        if let Some(ValueType::ArrayValue(array)) = &mut entry.value_type {
360            array.values.push(value);
361        }
362    } else {
363        properties.insert(name, value);
364    }
365    Ok(())
366}
367
368fn array(values: Vec<Value>) -> Value {
369    Value {
370        value_type: Some(ValueType::ArrayValue(ArrayValue { values })),
371        ..Value::default()
372    }
373}
374
375fn decode_value(bytes: &[u8], meaning: i32) -> Result<Value, Malformed> {
376    let mut value_type = Some(ValueType::NullValue(0));
377    for field in Reader::new(bytes) {
378        let (number, field) = field?;
379        value_type = Some(match number {
380            INT64 => {
381                #[allow(clippy::cast_possible_wrap)]
382                let n = field.as_varint().ok_or(Malformed)? as i64;
383                if meaning == GD_WHEN {
384                    ValueType::TimestampValue(Timestamp {
385                        seconds: n.div_euclid(1_000_000),
386                        #[allow(clippy::cast_possible_truncation)]
387                        nanos: (n.rem_euclid(1_000_000) * 1000) as i32,
388                    })
389                } else {
390                    ValueType::IntegerValue(n)
391                }
392            }
393            BOOLEAN => ValueType::BooleanValue(field.as_varint().ok_or(Malformed)? != 0),
394            DOUBLE => ValueType::DoubleValue(f64::from_bits(field.as_fixed64().ok_or(Malformed)?)),
395            STRING => {
396                let data = field.as_bytes().ok_or(Malformed)?;
397                match meaning {
398                    ENTITY_PROTO => {
399                        let (key, properties) = decode_entity(data)?;
400                        ValueType::EntityValue(Entity { key, properties })
401                    }
402                    BLOB | BYTESTRING | ZLIB => ValueType::BlobValue(data.to_vec()),
403                    _ => ValueType::StringValue(
404                        String::from_utf8(data.to_vec()).map_err(|_| Malformed)?,
405                    ),
406                }
407            }
408            POINT => {
409                let (mut latitude, mut longitude) = (0.0, 0.0);
410                for field in field.fields().ok_or(Malformed)? {
411                    let (number, field) = field?;
412                    match number {
413                        POINT_X => latitude = f64::from_bits(field.as_fixed64().ok_or(Malformed)?),
414                        POINT_Y => longitude = f64::from_bits(field.as_fixed64().ok_or(Malformed)?),
415                        _ => {}
416                    }
417                }
418                ValueType::GeoPointValue(LatLng {
419                    latitude,
420                    longitude,
421                })
422            }
423            REFERENCE => {
424                let mut partition = PartitionId::default();
425                let mut path = Vec::new();
426                for field in field.fields().ok_or(Malformed)? {
427                    let (number, field) = field?;
428                    match number {
429                        REFERENCE_APP => partition.project_id = text(field)?,
430                        REFERENCE_ELEMENT => path.push(decode_element(
431                            field,
432                            REFERENCE_TYPE,
433                            REFERENCE_ID,
434                            REFERENCE_NAME,
435                        )?),
436                        REFERENCE_NAME_SPACE => partition.namespace_id = text(field)?,
437                        REFERENCE_DATABASE_ID => partition.database_id = text(field)?,
438                        _ => {}
439                    }
440                }
441                ValueType::KeyValue(Key {
442                    partition_id: Some(partition),
443                    path,
444                })
445            }
446            _ => continue,
447        });
448    }
449    Ok(Value {
450        value_type,
451        ..Value::default()
452    })
453}
454
455#[cfg(test)]
456mod tests {
457    use super::*;
458
459    #[test]
460    fn an_entity_reads_back() {
461        let key = Key {
462            partition_id: Some(PartitionId {
463                project_id: "p".into(),
464                namespace_id: "ns".into(),
465                ..PartitionId::default()
466            }),
467            path: vec![PathElement {
468                kind: "T".into(),
469                id_type: Some(IdType::Id(7)),
470            }],
471        };
472        let mut properties = Properties::new();
473        properties.insert(
474            "s".into(),
475            Value {
476                value_type: Some(ValueType::StringValue("x".into())),
477                ..Value::default()
478            },
479        );
480        properties.insert(
481            "b".into(),
482            Value {
483                value_type: Some(ValueType::BlobValue(vec![1])),
484                exclude_from_indexes: true,
485                ..Value::default()
486            },
487        );
488        let (read_key, read) = decode(&encode(&key, &properties)).unwrap();
489        assert_eq!(read_key, key);
490        assert_eq!(read, properties);
491    }
492}