1use std::collections::{BTreeMap, BTreeSet};
2
3use serde::Serialize;
4use type_bridge_contract::capability::CapabilitySet;
5use type_bridge_contract::codec::{FormatVersion, to_canonical_json};
6use type_bridge_contract::fingerprint::SemanticProfileId;
7use type_bridge_contract::managed_scope::{
8 ManagedScopeBinding, ManagedScopeId, SemanticProfileFingerprint,
9};
10use type_bridge_contract::schema::{
11 AnnotationFactId, AnnotationKindId, AnnotationSubjectId, DeclaredSchema, InterfaceKind,
12 ManagedDeclaredIdentityFingerprint, ManagedSemanticSchemaFingerprint, SchemaAnnotationValue,
13 SchemaDiagnostic, SchemaDiagnostics, SchemaFact, SchemaFactId, SemanticProfile,
14 SemanticSchemaFingerprint,
15};
16use type_bridge_contract::value::Cardinality;
17
18use crate::TYPEDB_3_12_1_TIMEZONE_POLICY_ID;
19
20#[derive(Clone, Debug, Default, Eq, PartialEq)]
25pub struct ManagedSchemaScope {
26 facts: BTreeSet<SchemaFactId>,
27}
28
29impl ManagedSchemaScope {
30 #[must_use]
32 pub fn new(facts: impl IntoIterator<Item = SchemaFactId>) -> Self {
33 Self {
34 facts: facts.into_iter().collect(),
35 }
36 }
37
38 pub fn bind_exclusive(
40 scope_id: ManagedScopeId,
41 declared: &DeclaredSchema,
42 ) -> Result<BoundManagedSchemaScope, SchemaDiagnostics> {
43 let binding = ManagedScopeBinding::exclusive(scope_id).map_err(|diagnostic| {
44 SchemaDiagnostics::one(SchemaDiagnostic::new(diagnostic, None))
45 })?;
46 Ok(Self::new(declared.facts().map(SchemaFact::id)).bind(binding))
47 }
48
49 fn bind(self, binding: ManagedScopeBinding) -> BoundManagedSchemaScope {
50 BoundManagedSchemaScope {
51 selection: self,
52 binding,
53 }
54 }
55
56 #[must_use]
58 pub fn contains(&self, fact: &SchemaFactId) -> bool {
59 self.facts.contains(fact)
60 }
61
62 pub fn iter(&self) -> impl ExactSizeIterator<Item = &SchemaFactId> {
64 self.facts.iter()
65 }
66
67 #[must_use]
69 pub fn len(&self) -> usize {
70 self.facts.len()
71 }
72
73 #[must_use]
75 pub fn is_empty(&self) -> bool {
76 self.facts.is_empty()
77 }
78}
79
80#[derive(Clone, Debug, Eq, PartialEq)]
82pub struct BoundManagedSchemaScope {
83 selection: ManagedSchemaScope,
84 binding: ManagedScopeBinding,
85}
86
87impl BoundManagedSchemaScope {
88 pub const fn selection(&self) -> &ManagedSchemaScope {
90 &self.selection
91 }
92
93 pub const fn binding(&self) -> &ManagedScopeBinding {
95 &self.binding
96 }
97}
98
99#[derive(Serialize)]
100struct SemanticSchemaView<'a> {
101 format_version: FormatVersion,
102 #[serde(skip_serializing_if = "Option::is_none")]
103 managed_scope: Option<&'a ManagedScopeBinding>,
104 semantic_profile: &'a SemanticProfileId,
105 #[serde(skip_serializing_if = "Option::is_none")]
106 semantic_profile_fingerprint: Option<&'a SemanticProfileFingerprint>,
107 timezone_policy: &'static str,
108 required_capabilities: &'a CapabilitySet,
109 facts: Vec<SemanticFactView<'a>>,
110}
111
112#[derive(Serialize)]
113struct SemanticFactView<'a> {
114 id: SchemaFactId,
115 value: SemanticFactValue<'a>,
116}
117
118#[derive(Serialize)]
119#[serde(tag = "kind", content = "value", rename_all = "snake_case")]
120enum SemanticFactValue<'a> {
121 Direct(&'a SchemaFact),
122 TimeZoneRange {
123 subject: AnnotationSubjectId,
124 lower_utc_nanoseconds: Option<String>,
125 upper_utc_nanoseconds: Option<String>,
126 },
127 TimeZoneValues {
128 subject: AnnotationSubjectId,
129 utc_nanoseconds: Vec<String>,
130 },
131 MaterializedCardinality {
132 subject: AnnotationSubjectId,
133 cardinality: Cardinality,
134 },
135}
136
137#[derive(Serialize)]
138struct ManagedDeclaredView<'a> {
139 format_version: FormatVersion,
140 managed_scope: &'a ManagedScopeBinding,
141 required_capabilities: &'a CapabilitySet,
142 facts: Vec<&'a SchemaFact>,
143}
144
145pub fn canonical_semantic_schema_bytes(
147 declared: &DeclaredSchema,
148 profile: &SemanticProfileId,
149) -> Result<Vec<u8>, SchemaDiagnostics> {
150 canonical_semantic_schema_bytes_for_scope(declared, profile, None)
151}
152
153pub fn semantic_schema_fingerprint(
155 declared: &DeclaredSchema,
156 profile: &SemanticProfileId,
157) -> Result<SemanticSchemaFingerprint, SchemaDiagnostics> {
158 let canonical = canonical_semantic_schema_bytes(declared, profile)?;
159 SemanticSchemaFingerprint::compute(profile.clone(), &canonical)
160 .map_err(|diagnostic| SchemaDiagnostics::one(SchemaDiagnostic::new(diagnostic, None)))
161}
162
163pub fn managed_declared_identity_fingerprint(
165 declared: &DeclaredSchema,
166 scope: &BoundManagedSchemaScope,
167) -> Result<ManagedDeclaredIdentityFingerprint, SchemaDiagnostics> {
168 let canonical = canonical_managed_declared_identity_bytes(declared, scope)?;
169 ManagedDeclaredIdentityFingerprint::compute(&canonical)
170 .map_err(|diagnostic| SchemaDiagnostics::one(SchemaDiagnostic::new(diagnostic, None)))
171}
172
173pub fn canonical_managed_declared_identity_bytes(
175 declared: &DeclaredSchema,
176 scope: &BoundManagedSchemaScope,
177) -> Result<Vec<u8>, SchemaDiagnostics> {
178 let view = ManagedDeclaredView {
179 format_version: declared.format(),
180 managed_scope: scope.binding(),
181 required_capabilities: declared.required_capabilities(),
182 facts: declared
183 .facts()
184 .filter(|fact| scope.selection().contains(&fact.id()))
185 .collect(),
186 };
187 to_canonical_json(&view)
188 .map_err(|diagnostic| SchemaDiagnostics::one(SchemaDiagnostic::new(diagnostic, None)))
189}
190
191pub fn managed_semantic_schema_fingerprint(
193 declared: &DeclaredSchema,
194 profile: &SemanticProfileId,
195 scope: &BoundManagedSchemaScope,
196) -> Result<ManagedSemanticSchemaFingerprint, SchemaDiagnostics> {
197 let canonical = canonical_managed_semantic_schema_bytes(declared, profile, scope)?;
198 ManagedSemanticSchemaFingerprint::compute(profile.clone(), &canonical)
199 .map_err(|diagnostic| SchemaDiagnostics::one(SchemaDiagnostic::new(diagnostic, None)))
200}
201
202pub fn canonical_managed_semantic_schema_bytes(
204 declared: &DeclaredSchema,
205 profile: &SemanticProfileId,
206 scope: &BoundManagedSchemaScope,
207) -> Result<Vec<u8>, SchemaDiagnostics> {
208 canonical_semantic_schema_bytes_for_scope(declared, profile, Some(scope))
209}
210
211fn canonical_semantic_schema_bytes_for_scope(
212 declared: &DeclaredSchema,
213 profile: &SemanticProfileId,
214 scope: Option<&BoundManagedSchemaScope>,
215) -> Result<Vec<u8>, SchemaDiagnostics> {
216 let semantic_profile = SemanticProfile::resolve(profile)
217 .map_err(|diagnostic| SchemaDiagnostics::one(SchemaDiagnostic::new(diagnostic, None)))?;
218 let semantic_profile_fingerprint = scope
219 .map(|_| semantic_profile.content_fingerprint())
220 .transpose()
221 .map_err(|diagnostic| SchemaDiagnostics::one(SchemaDiagnostic::new(diagnostic, None)))?;
222 let facts = semantic_facts(
223 declared,
224 scope.map(BoundManagedSchemaScope::selection),
225 &semantic_profile,
226 );
227 let view = SemanticSchemaView {
228 format_version: declared.format(),
229 managed_scope: scope.map(BoundManagedSchemaScope::binding),
230 semantic_profile: profile,
231 semantic_profile_fingerprint: semantic_profile_fingerprint.as_ref(),
232 timezone_policy: TYPEDB_3_12_1_TIMEZONE_POLICY_ID,
233 required_capabilities: declared.required_capabilities(),
234 facts,
235 };
236 to_canonical_json(&view)
237 .map_err(|diagnostic| SchemaDiagnostics::one(SchemaDiagnostic::new(diagnostic, None)))
238}
239
240fn semantic_facts<'a>(
241 declared: &'a DeclaredSchema,
242 scope: Option<&ManagedSchemaScope>,
243 profile: &SemanticProfile,
244) -> Vec<SemanticFactView<'a>> {
245 let mut facts = BTreeMap::<SchemaFactId, SemanticFactValue<'a>>::new();
246 let mut interfaces = Vec::<(AnnotationSubjectId, InterfaceKind)>::new();
247 let key_owns = declared
248 .facts()
249 .filter(|fact| scope.is_none_or(|scope| scope.contains(&fact.id())))
250 .filter_map(|fact| match fact {
251 SchemaFact::Annotation(annotation)
252 if annotation.id().kind() == &AnnotationKindId::Key
253 && matches!(annotation.id().subject(), AnnotationSubjectId::Owns(_)) =>
254 {
255 Some(annotation.id().subject().clone())
256 }
257 _ => None,
258 })
259 .collect::<BTreeSet<_>>();
260
261 for fact in declared
262 .facts()
263 .filter(|fact| scope.is_none_or(|scope| scope.contains(&fact.id())))
264 {
265 let id = fact.id();
266 match fact {
267 SchemaFact::Owns(owns) => interfaces.push((
268 AnnotationSubjectId::Owns(owns.id().clone()),
269 InterfaceKind::Owns,
270 )),
271 SchemaFact::Relates(relates) => interfaces.push((
272 AnnotationSubjectId::Relates(relates.id().clone()),
273 InterfaceKind::Relates,
274 )),
275 SchemaFact::Plays(plays) => interfaces.push((
276 AnnotationSubjectId::Plays(plays.id().clone()),
277 InterfaceKind::Plays,
278 )),
279 _ => {}
280 }
281
282 let value = match fact {
283 SchemaFact::Annotation(annotation)
284 if annotation.id().kind() == &AnnotationKindId::Card =>
285 {
286 let Some(kind) = interface_kind(annotation.id().subject()) else {
287 facts.insert(id, SemanticFactValue::Direct(fact));
288 continue;
289 };
290 match annotation.value() {
291 SchemaAnnotationValue::Cardinality(cardinality)
292 if *cardinality
293 == profile.effective_cardinality(
294 kind,
295 None,
296 key_owns.contains(annotation.id().subject()),
297 ) =>
298 {
299 SemanticFactValue::MaterializedCardinality {
300 subject: annotation.id().subject().clone(),
301 cardinality: profile.effective_cardinality(
302 kind,
303 None,
304 key_owns.contains(annotation.id().subject()),
305 ),
306 }
307 }
308 _ => SemanticFactValue::Direct(fact),
309 }
310 }
311 SchemaFact::Annotation(annotation) => {
312 semantic_timezone_value(annotation).unwrap_or(SemanticFactValue::Direct(fact))
313 }
314 _ => SemanticFactValue::Direct(fact),
315 };
316 facts.insert(id, value);
317 }
318
319 for (subject, kind) in interfaces {
320 let id = SchemaFactId::Annotation(AnnotationFactId::new(
321 subject.clone(),
322 AnnotationKindId::Card,
323 ));
324 let cardinality = profile.effective_cardinality(kind, None, key_owns.contains(&subject));
325 facts
326 .entry(id)
327 .or_insert_with(|| SemanticFactValue::MaterializedCardinality {
328 subject,
329 cardinality,
330 });
331 }
332
333 facts
334 .into_iter()
335 .map(|(id, value)| SemanticFactView { id, value })
336 .collect()
337}
338
339fn semantic_timezone_value<'a>(
340 annotation: &'a type_bridge_contract::schema::AnnotationFact,
341) -> Option<SemanticFactValue<'a>> {
342 match annotation.value() {
343 SchemaAnnotationValue::Range(range) => {
344 let lower = range.lower().and_then(timezone_key);
345 let upper = range.upper().and_then(timezone_key);
346 (lower.is_some() || upper.is_some()).then(|| SemanticFactValue::TimeZoneRange {
347 subject: annotation.id().subject().clone(),
348 lower_utc_nanoseconds: lower,
349 upper_utc_nanoseconds: upper,
350 })
351 }
352 SchemaAnnotationValue::Values(values)
353 if values.iter().next().is_some_and(|value| {
354 matches!(
355 value,
356 type_bridge_contract::value::CanonicalValue::DateTimeTz(_)
357 )
358 }) =>
359 {
360 let mut keys = values.iter().filter_map(timezone_key).collect::<Vec<_>>();
361 keys.sort_by(|left, right| {
362 left.parse::<i128>()
363 .expect("semantic timezone key is an i128")
364 .cmp(
365 &right
366 .parse::<i128>()
367 .expect("semantic timezone key is an i128"),
368 )
369 });
370 Some(SemanticFactValue::TimeZoneValues {
371 subject: annotation.id().subject().clone(),
372 utc_nanoseconds: keys,
373 })
374 }
375 _ => None,
376 }
377}
378
379fn timezone_key(value: &type_bridge_contract::value::CanonicalValue) -> Option<String> {
380 let type_bridge_contract::value::CanonicalValue::DateTimeTz(value) = value else {
381 return None;
382 };
383 Some(value.semantic_utc_nanoseconds().to_string())
384}
385
386fn interface_kind(subject: &AnnotationSubjectId) -> Option<InterfaceKind> {
387 match subject {
388 AnnotationSubjectId::Owns(_) => Some(InterfaceKind::Owns),
389 AnnotationSubjectId::Relates(_) => Some(InterfaceKind::Relates),
390 AnnotationSubjectId::Plays(_) => Some(InterfaceKind::Plays),
391 _ => None,
392 }
393}