iceberg_rust_spec/spec/
identifier.rs

1/*!
2Defining the [Identifier] struct for identifying tables in an iceberg catalog.
3*/
4
5use core::fmt::{self, Display};
6use derive_getters::Getters;
7
8use serde_derive::{Deserialize, Serialize};
9
10use crate::error::Error;
11
12use super::namespace::Namespace;
13
14/// Seperator of different namespace levels.
15pub static SEPARATOR: &str = ".";
16
17///Identifies a table in an iceberg catalog.
18#[derive(Clone, Debug, Default, PartialEq, Eq, Hash, Serialize, Deserialize)]
19pub struct Identifier {
20    namespace: Namespace,
21    name: String,
22}
23
24impl Identifier {
25    /// Create new Identifier
26    pub fn new(namespace: &[String], name: &str) -> Self {
27        Self {
28            namespace: Namespace(namespace.to_vec()),
29            name: name.to_owned(),
30        }
31    }
32
33    ///Create Identifier
34    pub fn try_new(names: &[String], default_namespace: Option<&[String]>) -> Result<Self, Error> {
35        let mut parts = names.iter().rev();
36        let table_name = parts.next().ok_or(Error::InvalidFormat(format!(
37            "Identifier {names:?} is empty"
38        )))?;
39        if table_name.is_empty() {
40            return Err(Error::InvalidFormat(format!(
41                "Table name {table_name:?} is empty"
42            )));
43        }
44        let namespace: Vec<String> = parts.rev().map(ToOwned::to_owned).collect();
45        let namespace = if namespace.is_empty() {
46            default_namespace
47                .ok_or(Error::NotFound("Default namespace".to_owned()))?
48                .iter()
49                .map(ToOwned::to_owned)
50                .collect()
51        } else {
52            namespace
53        };
54        Ok(Identifier {
55            namespace: Namespace(namespace),
56            name: table_name.to_owned(),
57        })
58    }
59
60    ///Parse
61    pub fn parse(identifier: &str, default_namespace: Option<&[String]>) -> Result<Self, Error> {
62        let names = identifier
63            .split(SEPARATOR)
64            .map(ToOwned::to_owned)
65            .collect::<Vec<String>>();
66        Identifier::try_new(&names, default_namespace)
67    }
68    /// Return namespace of table
69    pub fn namespace(&self) -> &Namespace {
70        &self.namespace
71    }
72    /// Return name of table
73    pub fn name(&self) -> &str {
74        &self.name
75    }
76}
77
78impl Display for Identifier {
79    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
80        write!(f, "{}{}{}", self.namespace, SEPARATOR, self.name)
81    }
82}
83
84impl TryFrom<&str> for Identifier {
85    type Error = Error;
86    fn try_from(value: &str) -> Result<Self, Self::Error> {
87        Self::parse(value, None)
88    }
89}
90
91///Identifies a table in an iceberg catalog.
92#[derive(Clone, Debug, Default, PartialEq, Eq, Hash, Serialize, Deserialize, Getters)]
93pub struct FullIdentifier {
94    #[serde(skip_serializing_if = "Option::is_none")]
95    catalog: Option<String>,
96    namespace: Namespace,
97    name: String,
98}
99
100impl FullIdentifier {
101    pub fn new(catalog: Option<&str>, namespace: &[String], name: &str) -> Self {
102        Self {
103            catalog: catalog.map(ToString::to_string),
104            namespace: Namespace(namespace.to_owned()),
105            name: name.to_owned(),
106        }
107    }
108}
109
110impl From<&FullIdentifier> for Identifier {
111    fn from(value: &FullIdentifier) -> Self {
112        Identifier {
113            namespace: value.namespace.clone(),
114            name: value.name.clone(),
115        }
116    }
117}
118
119#[cfg(test)]
120mod tests {
121    use super::Identifier;
122
123    #[test]
124    fn test_new() {
125        let identifier = Identifier::try_new(
126            &[
127                "level1".to_string(),
128                "level2".to_string(),
129                "table".to_string(),
130            ],
131            None,
132        )
133        .unwrap();
134        assert_eq!(&format!("{identifier}"), "level1.level2.table");
135    }
136    #[test]
137    #[should_panic]
138    fn test_empty() {
139        let _ = Identifier::try_new(
140            &["level1".to_string(), "level2".to_string(), "".to_string()],
141            None,
142        )
143        .unwrap();
144    }
145    #[test]
146    #[should_panic]
147    fn test_empty_identifier() {
148        let _ = Identifier::try_new(&[], None).unwrap();
149    }
150    #[test]
151    fn test_parse() {
152        let identifier = Identifier::parse("level1.level2.table", None).unwrap();
153        assert_eq!(&format!("{identifier}"), "level1.level2.table");
154    }
155}