1use std::fmt;
8
9use hidane_proto::google::firestore::v1::{MapValue, Value, value::ValueType};
10pub use hidane_proto::ordered::Fields;
11
12use crate::{hash_order::hash_order, order::is_vector, store::identical};
13
14#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
15pub struct FieldPath(Vec<String>);
16
17impl FieldPath {
18 pub fn parse(path: &str) -> Result<Self, String> {
20 let invalid = || {
21 format!(
22 "Invalid property path \"{path}\". Unquoted property paths must match regex \
23 ([a-zA-Z_][a-zA-Z_0-9]*), and quoted property paths must match regex \
24 (`(?:[^`\\\\]|(?:\\\\.))+`)"
25 )
26 };
27 let mut segments = Vec::new();
28 let mut chars = path.chars().peekable();
29 loop {
30 let mut segment = String::new();
31 if chars.peek() == Some(&'`') {
32 chars.next();
33 loop {
34 match chars.next() {
35 Some('`') => break,
36 Some('\\') => segment.push(chars.next().ok_or_else(invalid)?),
37 Some(c) => segment.push(c),
38 None => return Err(invalid()),
39 }
40 }
41 if segment.is_empty() {
42 return Err(invalid());
43 }
44 } else {
45 while let Some(&c) = chars.peek() {
46 if c == '.' {
47 break;
48 }
49 let valid = if segment.is_empty() {
50 c.is_ascii_alphabetic() || c == '_'
51 } else {
52 c.is_ascii_alphanumeric() || c == '_'
53 };
54 if !valid {
55 return Err(invalid());
56 }
57 segment.push(c);
58 chars.next();
59 }
60 if segment.is_empty() {
61 return Err(invalid());
62 }
63 }
64 segments.push(segment);
65 match chars.next() {
66 None => return Ok(Self(segments)),
67 Some('.') => {}
68 Some(_) => return Err(invalid()),
69 }
70 }
71 }
72
73 pub fn from_segments<I, S>(segments: I) -> Self
74 where
75 I: IntoIterator<Item = S>,
76 S: Into<String>,
77 {
78 Self(segments.into_iter().map(Into::into).collect())
79 }
80
81 pub fn segments(&self) -> &[String] {
82 &self.0
83 }
84}
85
86impl fmt::Display for FieldPath {
89 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
90 for (i, segment) in self.0.iter().enumerate() {
91 if i > 0 {
92 f.write_str(".")?;
93 }
94 let mut chars = segment.chars();
95 let simple = chars
96 .next()
97 .is_some_and(|c| c.is_ascii_alphabetic() || c == '_')
98 && chars.all(|c| c.is_ascii_alphanumeric() || c == '_');
99 if simple {
100 f.write_str(segment)?;
101 } else {
102 f.write_str("`")?;
103 for c in segment.chars() {
104 if c == '`' || c == '\\' {
105 f.write_str("\\")?;
106 }
107 write!(f, "{c}")?;
108 }
109 f.write_str("`")?;
110 }
111 }
112 Ok(())
113 }
114}
115
116pub fn get<'a>(fields: &'a Fields, path: &FieldPath) -> Option<&'a Value> {
119 let (last, parents) = path.0.split_last()?;
120 let mut current = fields;
121 for segment in parents {
122 current = map_fields(current.get(segment)?)?;
123 }
124 current.get(last)
125}
126
127pub fn set(fields: &mut Fields, path: &FieldPath, value: Value) {
130 let Some((last, parents)) = path.0.split_last() else {
131 return;
132 };
133 let mut current = fields;
134 for segment in parents {
135 let entry = current.entry(segment.clone()).or_insert_with(empty_map);
136 if map_fields(entry).is_none() {
137 *entry = empty_map();
138 }
139 let Some(ValueType::MapValue(map)) = &mut entry.value_type else {
140 unreachable!("just made it a map");
141 };
142 current = &mut map.fields;
143 }
144 current.insert(last.clone(), value);
145}
146
147pub fn delete(fields: &mut Fields, path: &FieldPath) {
149 let Some((last, parents)) = path.0.split_last() else {
150 return;
151 };
152 let mut current = fields;
153 for segment in parents {
154 match current.get_mut(segment).and_then(|v| v.value_type.as_mut()) {
155 Some(ValueType::MapValue(map)) if !is_vector(map) => current = &mut map.fields,
156 _ => return,
157 }
158 }
159 current.remove(last);
160}
161
162#[derive(Default)]
165struct MaskTree {
166 whole: bool,
168 children: Vec<(String, MaskTree)>,
169}
170
171impl MaskTree {
172 fn new(paths: &[FieldPath]) -> Self {
173 let mut root = Self::default();
174 for path in paths {
175 let mut node = &mut root;
176 for segment in path.segments() {
177 let at = match node.children.iter().position(|(name, _)| name == segment) {
178 Some(at) => at,
179 None => {
180 node.children.push((segment.clone(), Self::default()));
181 node.children.len() - 1
182 }
183 };
184 node = &mut node.children[at].1;
185 }
186 node.whole = true;
187 }
188 root
189 }
190
191 fn ordered(&self) -> impl Iterator<Item = &(String, MaskTree)> {
194 let names: Vec<&str> = self
195 .children
196 .iter()
197 .map(|(name, _)| name.as_str())
198 .collect();
199 hash_order(&names).into_iter().map(|i| &self.children[i])
200 }
201
202 fn flatten(&self, prefix: &mut Vec<String>, out: &mut Vec<FieldPath>) {
206 let (whole, below): (Vec<_>, Vec<_>) =
207 self.children.iter().partition(|(_, node)| node.whole);
208 let children: Vec<&(String, MaskTree)> = whole.into_iter().chain(below).collect();
209 let names: Vec<&str> = children.iter().map(|(name, _)| name.as_str()).collect();
210 for (name, below) in hash_order(&names).into_iter().map(|i| children[i]) {
211 prefix.push(name.clone());
212 if below.whole {
213 out.push(FieldPath(prefix.clone()));
214 } else {
215 below.flatten(prefix, out);
216 }
217 prefix.pop();
218 }
219 }
220}
221
222pub fn mask_paths(mask: &[FieldPath]) -> Vec<FieldPath> {
227 let mut out = Vec::new();
228 MaskTree::new(mask).flatten(&mut Vec::new(), &mut out);
229 out
230}
231
232fn in_hash_order<T>(mut entries: Vec<(String, T)>) -> impl Iterator<Item = (String, T)> {
234 let order = hash_order(&entries.iter().map(|(name, _)| name).collect::<Vec<_>>());
235 let mut taken: Vec<Option<(String, T)>> = entries.drain(..).map(Some).collect();
236 order.into_iter().filter_map(move |i| taken[i].take())
237}
238
239fn map_fields(value: &Value) -> Option<&Fields> {
241 match &value.value_type {
242 Some(ValueType::MapValue(map)) if !is_vector(map) => Some(&map.fields),
243 _ => None,
244 }
245}
246
247fn map_of(fields: Fields) -> Value {
248 Value {
249 value_type: Some(ValueType::MapValue(MapValue { fields })),
250 }
251}
252
253pub fn project(fields: &Fields, paths: &[FieldPath]) -> Fields {
257 project_level(fields, &MaskTree::new(paths), false)
258}
259
260pub fn project_keeping_maps(fields: &Fields, paths: &[FieldPath]) -> Fields {
265 project_level(fields, &MaskTree::new(paths), true)
266}
267
268fn project_level(fields: &Fields, mask: &MaskTree, keep_maps: bool) -> Fields {
269 let mut found = Vec::new();
270 for (name, below) in mask.ordered() {
271 let value = fields.get(name);
272 if below.whole {
273 if let Some(value) = value {
274 found.push((name.clone(), value.clone()));
275 }
276 } else if let Some(map) = value.and_then(map_fields) {
277 let inner = project_level(map, below, keep_maps);
278 if keep_maps || !inner.is_empty() {
279 found.push((name.clone(), map_of(inner)));
280 }
281 } else if keep_maps {
282 let inner = project_level(&Fields::default(), below, keep_maps);
283 found.push((name.clone(), map_of(inner)));
284 }
285 }
286 in_hash_order(found).collect()
287}
288
289pub fn apply_mask(existing: &mut Fields, input: &Fields, mask: &[FieldPath]) {
300 merge_level(existing, Some(input), &MaskTree::new(mask));
301}
302
303fn merge_level(existing: &mut Fields, input: Option<&Fields>, mask: &MaskTree) {
304 let mut changes = Vec::new();
305 let (whole, nested): (Vec<_>, Vec<_>) = mask.ordered().partition(|(_, below)| below.whole);
306 for (name, below) in whole.into_iter().chain(nested) {
307 let old = existing.get(name);
308 let given = input.and_then(|fields| fields.get(name));
309 let new = if below.whole {
310 given.cloned()
311 } else {
312 let mut inner = old.and_then(map_fields).cloned().unwrap_or_default();
313 merge_level(&mut inner, given.and_then(map_fields), below);
314 match old {
315 Some(old) if map_fields(old).is_some() || !inner.is_empty() => Some(map_of(inner)),
317 Some(old) => Some(old.clone()),
318 None if inner.is_empty() => None,
319 None => Some(map_of(inner)),
320 }
321 };
322 let changed = match (&new, old) {
324 (Some(new), Some(old)) => !identical(new, old),
325 (new, old) => new.is_some() || old.is_some(),
326 };
327 if changed {
328 changes.push((name.clone(), new));
329 }
330 }
331 for (name, new) in in_hash_order(changes) {
332 existing.remove(&name);
333 if let Some(value) = new {
334 existing.insert(name, value);
335 }
336 }
337}
338
339fn empty_map() -> Value {
340 Value {
341 value_type: Some(ValueType::MapValue(MapValue::default())),
342 }
343}
344
345#[cfg(test)]
346mod tests {
347 use super::*;
348
349 #[test]
350 fn mask_paths_as_rules_see_them() {
351 let listed = |mask: &[&str]| -> Vec<String> {
352 let mask: Vec<FieldPath> = mask.iter().map(|p| FieldPath::parse(p).unwrap()).collect();
353 mask_paths(&mask).iter().map(ToString::to_string).collect()
354 };
355 assert_eq!(listed(&["c", "b", "a"]), ["a", "b", "c"]);
357 assert_eq!(listed(&["n", "m.b", "z"]), ["z", "m.b", "n"]);
358 assert_eq!(listed(&["b", "a", "b"]), ["a", "b"]);
359 assert_eq!(listed(&["m.b", "m"]), ["m"]);
360 assert_eq!(listed(&["m.b.c", "m.a", "z"]), ["z", "m.a", "m.b.c"]);
361 assert_eq!(
362 listed(&["x.y", "a", "`q r`", "b.c.d"]),
363 ["a", "`q r`", "b.c.d", "x.y"]
364 );
365 assert_eq!(listed(&["b.c", "r", "B.x", "R"]), ["r", "R", "b.c", "B.x"]);
367 assert_eq!(listed(&["m.b.c", "m.r"]), ["m.r", "m.b.c"]);
368 assert!(listed(&[]).is_empty());
369 }
370
371 fn int(i: i64) -> Value {
372 Value {
373 value_type: Some(ValueType::IntegerValue(i)),
374 }
375 }
376
377 fn map(entries: &[(&str, Value)]) -> Value {
378 Value {
379 value_type: Some(ValueType::MapValue(MapValue {
380 fields: entries
381 .iter()
382 .map(|(k, v)| ((*k).to_owned(), v.clone()))
383 .collect(),
384 })),
385 }
386 }
387
388 fn fields(entries: &[(&str, Value)]) -> Fields {
389 entries
390 .iter()
391 .map(|(k, v)| ((*k).to_owned(), v.clone()))
392 .collect()
393 }
394
395 fn fp(s: &str) -> FieldPath {
396 FieldPath::parse(s).unwrap()
397 }
398
399 #[test]
400 fn display_is_the_canonical_form() {
401 for canonical in [
402 "a", "a.b", "_x9", "`a b`.c", "`1a`", "`x\\`y`", "`a\\\\b`", "`é`",
403 ] {
404 assert_eq!(fp(canonical).to_string(), canonical);
405 }
406 assert_eq!(fp("`a`.`b`").to_string(), "a.b");
407 }
408
409 #[test]
410 fn parses_unquoted_and_quoted_segments() {
411 assert_eq!(fp("a").segments(), ["a"]);
412 assert_eq!(fp("a.b_1._c").segments(), ["a", "b_1", "_c"]);
413 assert_eq!(fp("`a.b`.c").segments(), ["a.b", "c"]);
414 assert_eq!(fp("`x\\`y`").segments(), ["x`y"]);
415 assert_eq!(fp("`\\\\`").segments(), ["\\"]);
416 assert_eq!(fp("`1st`").segments(), ["1st"]);
417 }
418
419 #[test]
420 fn rejects_like_the_official_emulator() {
421 for bad in [
422 "", "a..b", ".a", "a.", "1a", "a-b", "``", "`a", "`a`b", "a b",
423 ] {
424 let err = FieldPath::parse(bad).unwrap_err();
425 assert!(
426 err.starts_with(&format!("Invalid property path \"{bad}\". Unquoted")),
427 "{bad}: {err}"
428 );
429 }
430 assert_eq!(
432 FieldPath::parse("a..b").unwrap_err(),
433 "Invalid property path \"a..b\". Unquoted property paths must match regex \
434 ([a-zA-Z_][a-zA-Z_0-9]*), and quoted property paths must match regex \
435 (`(?:[^`\\\\]|(?:\\\\.))+`)"
436 );
437 }
438
439 #[test]
440 fn update_mask_matches_the_official_emulator() {
441 let mut doc = fields(&[
444 ("a", int(1)),
445 ("b", int(2)),
446 ("c", map(&[("x", int(1)), ("y", int(2))])),
447 ]);
448 let input = fields(&[("a", int(10)), ("c", map(&[("x", int(5))])), ("e", int(9))]);
449 apply_mask(&mut doc, &input, &[fp("a"), fp("c.x"), fp("d")]);
450 assert_eq!(
451 doc,
452 fields(&[
453 ("a", int(10)),
454 ("b", int(2)),
455 ("c", map(&[("x", int(5)), ("y", int(2))])),
456 ])
457 );
458 }
459
460 #[test]
461 fn masks_on_missing_documents_create_only_the_masked_fields() {
462 let mut doc = Fields::new();
463 let input = fields(&[("a", int(1)), ("b", map(&[("x", int(1))]))]);
464 apply_mask(&mut doc, &input, &[fp("b.x")]);
465 assert_eq!(doc, fields(&[("b", map(&[("x", int(1))]))]));
466 }
467
468 #[test]
469 fn read_masks_project_nested_fields() {
470 let doc = fields(&[
471 ("a", int(1)),
472 ("b", map(&[("x", int(1)), ("y", int(2))])),
473 ("c", int(3)),
474 ]);
475 assert_eq!(
476 project(
477 &doc,
478 &[fp("b.x"), fp("c"), fp("missing"), fp("a.not_a_map")]
479 ),
480 fields(&[("b", map(&[("x", int(1))])), ("c", int(3))])
481 );
482 }
483
484 #[test]
485 fn enterprise_projections_keep_the_maps_on_the_way() {
486 let doc = fields(&[("a", int(1)), ("b", map(&[("x", int(1)), ("y", int(2))]))]);
487 let empty = || map(&[]);
488 assert_eq!(
490 project_keeping_maps(
491 &doc,
492 &[fp("b.x"), fp("missing"), fp("a.not_a_map"), fp("m.x.y")]
493 ),
494 project_keeping_maps(
495 &fields(&[
496 ("b", map(&[("x", int(1))])),
497 ("a", empty()),
498 ("m", map(&[("x", empty())])),
499 ]),
500 &[fp("b"), fp("a"), fp("m")]
501 )
502 );
503 }
504
505 fn layout(fields: &Fields) -> String {
507 fields
508 .iter()
509 .map(|(name, value)| match &value.value_type {
510 Some(ValueType::MapValue(map)) => format!("{name}{{{}}}", layout(&map.fields)),
511 _ => name.clone(),
512 })
513 .collect::<Vec<_>>()
514 .join(",")
515 }
516
517 #[test]
521 fn masked_fields_that_change_move_to_the_end_in_hash_order() {
522 let base = || {
523 fields(&[
524 ("zeta", int(1)),
525 ("alpha", int(2)),
526 ("mid", map(&[("z", int(3)), ("a", int(4))])),
527 ])
528 };
529 let mut doc = base();
530 apply_mask(
531 &mut doc,
532 &fields(&[("beta", int(9)), ("alpha", int(10))]),
533 &[fp("beta"), fp("alpha")],
534 );
535 assert_eq!(layout(&doc), "zeta,mid{z,a},alpha,beta");
536
537 let mut doc = base();
539 apply_mask(
540 &mut doc,
541 &fields(&[
542 ("deep", map(&[("x", map(&[("y", int(1))]))])),
543 ("alpha", int(2)),
544 ]),
545 &[fp("deep.x.y"), fp("alpha")],
546 );
547 assert_eq!(layout(&doc), "zeta,alpha,mid{z,a},deep{x{y}}");
548 }
549
550 #[test]
551 fn a_map_changed_inside_moves_to_the_end() {
552 let base = || {
553 fields(&[
554 ("mid", map(&[("z", int(3)), ("a", int(4))])),
555 ("zeta", int(1)),
556 ("alpha", int(2)),
557 ])
558 };
559 let mut doc = base();
560 apply_mask(
561 &mut doc,
562 &fields(&[("mid", map(&[("b", int(5))]))]),
563 &[fp("mid.b")],
564 );
565 assert_eq!(layout(&doc), "zeta,alpha,mid{z,a,b}");
566
567 let mut doc = base();
568 apply_mask(&mut doc, &Fields::new(), &[fp("mid.z")]);
569 assert_eq!(layout(&doc), "zeta,alpha,mid{a}");
570
571 let mut doc = fields(&[
572 ("p", map(&[("x", int(1)), ("y", int(2))])),
573 ("alpha", int(1)),
574 ("zeta", int(2)),
575 ("mid", int(3)),
576 ]);
577 apply_mask(
578 &mut doc,
579 &fields(&[("alpha", int(9))]),
580 &[fp("p.y"), fp("alpha")],
581 );
582 assert_eq!(layout(&doc), "zeta,mid,p{x},alpha");
583 }
584
585 #[test]
586 fn the_changed_fields_have_a_hash_map_of_their_own() {
587 let present = [
588 "str", "int", "dbl", "zeta", "alpha", "a", "b", "c", "x1", "x2", "x3",
589 ];
590 let doc_with = |extra: &[(&str, Value)]| {
591 let mut doc = fields(&[("keep", int(0))]);
592 for (n, name) in present.iter().enumerate() {
593 doc.insert((*name).to_owned(), int(n as i64));
594 }
595 doc.extend(extra.iter().map(|(k, v)| ((*k).to_owned(), v.clone())));
596 doc
597 };
598 let input: Fields = present
599 .iter()
600 .enumerate()
601 .map(|(n, name)| ((*name).to_owned(), int(n as i64 + 100)))
602 .collect();
603 let mask: Vec<FieldPath> = present
604 .iter()
605 .chain(&["gone1", "gone2"])
606 .map(|p| fp(p))
607 .collect();
608
609 let mut doc = doc_with(&[]);
611 apply_mask(&mut doc, &input, &mask);
612 assert_eq!(layout(&doc), "keep,zeta,str,a,b,c,alpha,x1,x2,x3,int,dbl");
613
614 let mut doc = doc_with(&[("gone1", int(1)), ("gone2", int(2))]);
616 apply_mask(&mut doc, &input, &mask);
617 assert_eq!(layout(&doc), "keep,zeta,a,b,c,int,dbl,str,alpha,x1,x2,x3");
618 }
619
620 #[test]
621 fn read_masks_return_fields_in_hash_order() {
622 let doc = fields(&[
623 ("str", int(1)),
624 ("mid", map(&[("z", int(1)), ("a", int(2))])),
625 ("zeta", int(3)),
626 ("alpha", int(4)),
627 ("a", int(5)),
628 ]);
629 let masked = |mask: &[&str]| {
630 layout(&project(
631 &doc,
632 &mask.iter().map(|p| fp(p)).collect::<Vec<_>>(),
633 ))
634 };
635 assert_eq!(masked(&["a", "alpha", "zeta"]), "zeta,a,alpha");
636 assert_eq!(masked(&["mid.a", "mid.z", "str"]), "str,mid{a,z}");
637 assert_eq!(masked(&["mid", "str"]), "str,mid{z,a}");
639 }
640
641 #[test]
642 fn set_replaces_non_map_intermediates() {
643 let mut doc = fields(&[("a", int(1))]);
644 set(&mut doc, &fp("a.b"), int(2));
645 assert_eq!(doc, fields(&[("a", map(&[("b", int(2))]))]));
646 }
647}