Skip to main content

uqa_sql/routines/lifecycle/
restoration.rs

1//
2// Unified Query Algebra
3//
4// Copyright (c) 2023-2026 Cognica, Inc.
5//
6
7//! Validate durable routine names, object identities, signatures, and serialized dispatches.
8
9use super::routine_signature_label;
10use crate::{
11    ast::{CreateFunction, FunctionBody},
12    routines::routine_signature_types,
13};
14use std::collections::{BTreeMap, BTreeSet};
15use uqa_core::RelationIdentity;
16
17pub fn persisted_registry_relation(stored_name: &str) -> Result<RelationIdentity, String> {
18    RelationIdentity::from_legacy_name(stored_name)
19        .map_err(|error| format!("invalid persisted routine registry key `{stored_name}`: {error}"))
20}
21pub fn validate_persisted_routine_schema(
22    stored_name: &str,
23    relation: &RelationIdentity,
24    exists: bool,
25) -> Result<(), String> {
26    if !exists {
27        return Err(format!(
28            "persisted routine `{stored_name}` references missing schema `{}`",
29            relation.schema
30        ));
31    }
32    Ok(())
33}
34
35#[derive(Default)]
36pub struct RoutineRestoreBuilder {
37    definitions: BTreeMap<String, Vec<CreateFunction>>,
38    object_ids: BTreeSet<[u8; 16]>,
39}
40impl RoutineRestoreBuilder {
41    pub fn insert(
42        &mut self,
43        stored_name: &str,
44        stored_relation: &RelationIdentity,
45        mut def: CreateFunction,
46    ) -> Result<bool, String> {
47        let object_id = def
48            .object_id
49            .ok_or_else(|| format!("persisted routine `{stored_name}` has no object identity"))?;
50        if !self.object_ids.insert(object_id) {
51            return Err(format!(
52                "duplicate persisted routine object identity for `{stored_name}`"
53            ));
54        }
55        let mut migrated = false;
56        for parameter in &mut def.params {
57            if let Some(default) = &mut parameter.default {
58                migrated |= default.upgrade_legacy_serialized_dispatches();
59            }
60        }
61        if let FunctionBody::Statements(statements) = &mut def.body {
62            for statement in statements {
63                migrated |= statement.upgrade_legacy_serialized_dispatches();
64            }
65        }
66        let definition_relation =
67            RelationIdentity::from_legacy_name(&def.name).map_err(|error| {
68                format!(
69                    "invalid persisted routine definition name `{}`: {error}",
70                    def.name
71                )
72            })?;
73        if &definition_relation != stored_relation {
74            return Err(format!(
75                "persisted routine registry key `{stored_name}` does not match definition `{}`",
76                def.name
77            ));
78        }
79        let canonical_name = stored_relation.qualified_name();
80        def.name.clone_from(&canonical_name);
81        let signature = routine_signature_types(&def);
82        let definitions = self.definitions.entry(canonical_name.clone()).or_default();
83        if definitions
84            .iter()
85            .any(|existing| routine_signature_types(existing) == signature)
86        {
87            return Err(format!(
88                "duplicate persisted routine identity `{}`",
89                routine_signature_label(&canonical_name, &signature)
90            ));
91        }
92        definitions.push(def);
93        Ok(migrated)
94    }
95    pub fn into_definitions(self) -> BTreeMap<String, Vec<CreateFunction>> {
96        self.definitions
97    }
98}