Skip to main content

uqa_storage/catalog/graph_observations/
labels.rs

1//
2// Unified Query Algebra
3//
4// Copyright (c) 2023-2026 Cognica, Inc.
5//
6
7//! Physical label registry projection excludes autonomous counters from semantic label definitions.
8
9mod projection;
10
11use sha2::{Digest, Sha256};
12
13use super::{GraphDefinitionKey, GraphDefinitionKind, GraphIdentifierNamespace};
14use crate::mvcc::{SerializableKeySpace, SerializablePredicate};
15use crate::{read_control::StorageReadControl, KeyValueBatch, StorageBackendResult};
16
17#[derive(Clone, Copy)]
18pub enum GraphLabelName<'a> {
19    Named(&'a str),
20    Reserved(u32),
21}
22
23/// Labels have a graph-specific logical object. An opaque legacy registry change can invalidate that object without interfering with another graph's label readers.
24pub struct GraphLabelDefinitionKey {
25    object: [u8; 16],
26    key: Option<[u8; 33]>,
27}
28
29impl GraphLabelDefinitionKey {
30    pub fn new(
31        namespace: GraphIdentifierNamespace,
32        graph: &str,
33        label: Option<GraphLabelName<'_>>,
34    ) -> Self {
35        let mut digest = Sha256::new();
36        digest.update(namespace.serializable_scope_object());
37        digest.update(b"graph-labels");
38        digest.update(graph.as_bytes());
39        let mut object = [0; 16];
40        object.copy_from_slice(&digest.finalize()[..16]);
41        if object == [0; 16] {
42            object[15] = 1;
43        }
44        let key = label.map(|label| {
45            let mut key = [0; 33];
46            match label {
47                GraphLabelName::Named(name) => {
48                    key[0] = b'n';
49                    key[1..].copy_from_slice(&Sha256::digest(name.as_bytes()));
50                }
51                GraphLabelName::Reserved(id) => {
52                    key[0] = b'd';
53                    key[1..5].copy_from_slice(&id.to_be_bytes());
54                }
55            }
56            key
57        });
58        Self { object, key }
59    }
60
61    pub fn predicate(&self) -> SerializablePredicate<'_> {
62        self.key.as_ref().map_or_else(
63            || SerializablePredicate::object(self.object),
64            |key| SerializablePredicate::point(self.object, SerializableKeySpace::Graph, key),
65        )
66    }
67}
68
69/// Compare already evaluated registry records under the original allowance. Catalog APIs continue to accept opaque legacy records; their replacement conservatively changes the selected graph's whole label object.
70pub fn observe_label_registry_change(
71    namespace: GraphIdentifierNamespace,
72    graph: &str,
73    old: Option<&str>,
74    new: Option<&str>,
75    batch: &mut dyn KeyValueBatch,
76    control: &StorageReadControl,
77) -> StorageBackendResult<()> {
78    if batch.serializable_participant().is_none() || old == new {
79        return Ok(());
80    }
81    control.check()?;
82    batch.observe_serializable_write(
83        GraphDefinitionKey::new(namespace, GraphDefinitionKind::LabelRegistry, Some(graph))
84            .predicate(),
85    )?;
86    let key = GraphLabelDefinitionKey::new(namespace, graph, None);
87    let (Some(old), Some(new)) = (
88        projection::Registry::decode(old, control)?,
89        projection::Registry::decode(new, control)?,
90    ) else {
91        return batch.observe_serializable_write(key.predicate());
92    };
93    for id in [1_u32, 2] {
94        if old.dropped(id) != new.dropped(id) || old.reserved_alias(id) != new.reserved_alias(id) {
95            batch.observe_serializable_write(
96                GraphLabelDefinitionKey::new(namespace, graph, Some(GraphLabelName::Reserved(id)))
97                    .predicate(),
98            )?;
99        }
100    }
101    old.visit_changes(&new, control, |name| {
102        let mut bytes = [b'n'; 33];
103        bytes[1..].copy_from_slice(name);
104        batch.observe_serializable_write(SerializablePredicate::point(
105            key.object,
106            SerializableKeySpace::Graph,
107            &bytes,
108        ))
109    })
110}