use crate::utils::{parse_identifiers_normalized, quote_identifier};
use std::borrow::Cow;
#[derive(Debug, Clone)]
pub struct ResolvedTableReference<'a> {
pub catalog: Cow<'a, str>,
pub schema: Cow<'a, str>,
pub table: Cow<'a, str>,
}
impl<'a> std::fmt::Display for ResolvedTableReference<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}.{}.{}", self.catalog, self.schema, self.table)
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum TableReference<'a> {
Bare {
table: Cow<'a, str>,
},
Partial {
schema: Cow<'a, str>,
table: Cow<'a, str>,
},
Full {
catalog: Cow<'a, str>,
schema: Cow<'a, str>,
table: Cow<'a, str>,
},
}
pub type OwnedTableReference = TableReference<'static>;
impl std::fmt::Display for TableReference<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
TableReference::Bare { table } => write!(f, "{table}"),
TableReference::Partial { schema, table } => {
write!(f, "{schema}.{table}")
}
TableReference::Full {
catalog,
schema,
table,
} => write!(f, "{catalog}.{schema}.{table}"),
}
}
}
impl<'a> TableReference<'a> {
pub fn none() -> Option<TableReference<'a>> {
None
}
pub fn bare(table: impl Into<Cow<'a, str>>) -> TableReference<'a> {
TableReference::Bare {
table: table.into(),
}
}
pub fn partial(
schema: impl Into<Cow<'a, str>>,
table: impl Into<Cow<'a, str>>,
) -> TableReference<'a> {
TableReference::Partial {
schema: schema.into(),
table: table.into(),
}
}
pub fn full(
catalog: impl Into<Cow<'a, str>>,
schema: impl Into<Cow<'a, str>>,
table: impl Into<Cow<'a, str>>,
) -> TableReference<'a> {
TableReference::Full {
catalog: catalog.into(),
schema: schema.into(),
table: table.into(),
}
}
pub fn table(&self) -> &str {
match self {
Self::Full { table, .. }
| Self::Partial { table, .. }
| Self::Bare { table } => table,
}
}
pub fn schema(&self) -> Option<&str> {
match self {
Self::Full { schema, .. } | Self::Partial { schema, .. } => Some(schema),
_ => None,
}
}
pub fn catalog(&self) -> Option<&str> {
match self {
Self::Full { catalog, .. } => Some(catalog),
_ => None,
}
}
pub fn resolved_eq(&self, other: &Self) -> bool {
match self {
TableReference::Bare { table } => table == other.table(),
TableReference::Partial { schema, table } => {
table == other.table() && other.schema().map_or(true, |s| s == schema)
}
TableReference::Full {
catalog,
schema,
table,
} => {
table == other.table()
&& other.schema().map_or(true, |s| s == schema)
&& other.catalog().map_or(true, |c| c == catalog)
}
}
}
pub fn resolve(
self,
default_catalog: &'a str,
default_schema: &'a str,
) -> ResolvedTableReference<'a> {
match self {
Self::Full {
catalog,
schema,
table,
} => ResolvedTableReference {
catalog,
schema,
table,
},
Self::Partial { schema, table } => ResolvedTableReference {
catalog: default_catalog.into(),
schema,
table,
},
Self::Bare { table } => ResolvedTableReference {
catalog: default_catalog.into(),
schema: default_schema.into(),
table,
},
}
}
pub fn to_owned_reference(&self) -> OwnedTableReference {
match self {
Self::Full {
catalog,
schema,
table,
} => OwnedTableReference::Full {
catalog: catalog.to_string().into(),
schema: schema.to_string().into(),
table: table.to_string().into(),
},
Self::Partial { schema, table } => OwnedTableReference::Partial {
schema: schema.to_string().into(),
table: table.to_string().into(),
},
Self::Bare { table } => OwnedTableReference::Bare {
table: table.to_string().into(),
},
}
}
pub fn to_quoted_string(&self) -> String {
match self {
TableReference::Bare { table } => quote_identifier(table).to_string(),
TableReference::Partial { schema, table } => {
format!("{}.{}", quote_identifier(schema), quote_identifier(table))
}
TableReference::Full {
catalog,
schema,
table,
} => format!(
"{}.{}.{}",
quote_identifier(catalog),
quote_identifier(schema),
quote_identifier(table)
),
}
}
pub fn parse_str(s: &'a str) -> Self {
let mut parts = parse_identifiers_normalized(s);
match parts.len() {
1 => Self::Bare {
table: parts.remove(0).into(),
},
2 => Self::Partial {
schema: parts.remove(0).into(),
table: parts.remove(0).into(),
},
3 => Self::Full {
catalog: parts.remove(0).into(),
schema: parts.remove(0).into(),
table: parts.remove(0).into(),
},
_ => Self::Bare { table: s.into() },
}
}
pub fn to_vec(&self) -> Vec<String> {
match self {
TableReference::Bare { table } => vec![table.to_string()],
TableReference::Partial { schema, table } => {
vec![schema.to_string(), table.to_string()]
}
TableReference::Full {
catalog,
schema,
table,
} => vec![catalog.to_string(), schema.to_string(), table.to_string()],
}
}
}
impl From<String> for OwnedTableReference {
fn from(s: String) -> Self {
TableReference::parse_str(&s).to_owned_reference()
}
}
impl<'a> From<&'a OwnedTableReference> for TableReference<'a> {
fn from(value: &'a OwnedTableReference) -> Self {
match value {
OwnedTableReference::Bare { table } => TableReference::Bare {
table: Cow::Borrowed(table),
},
OwnedTableReference::Partial { schema, table } => TableReference::Partial {
schema: Cow::Borrowed(schema),
table: Cow::Borrowed(table),
},
OwnedTableReference::Full {
catalog,
schema,
table,
} => TableReference::Full {
catalog: Cow::Borrowed(catalog),
schema: Cow::Borrowed(schema),
table: Cow::Borrowed(table),
},
}
}
}
impl<'a> From<&'a str> for TableReference<'a> {
fn from(s: &'a str) -> Self {
Self::parse_str(s)
}
}
impl<'a> From<&'a String> for TableReference<'a> {
fn from(s: &'a String) -> Self {
Self::parse_str(s)
}
}
impl<'a> From<ResolvedTableReference<'a>> for TableReference<'a> {
fn from(resolved: ResolvedTableReference<'a>) -> Self {
Self::Full {
catalog: resolved.catalog,
schema: resolved.schema,
table: resolved.table,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_table_reference_from_str_normalizes() {
let expected = TableReference::Full {
catalog: Cow::Owned("catalog".to_string()),
schema: Cow::Owned("FOO\".bar".to_string()),
table: Cow::Owned("table".to_string()),
};
let actual = TableReference::from("catalog.\"FOO\"\".bar\".TABLE");
assert_eq!(expected, actual);
let expected = TableReference::Partial {
schema: Cow::Owned("FOO\".bar".to_string()),
table: Cow::Owned("table".to_string()),
};
let actual = TableReference::from("\"FOO\"\".bar\".TABLE");
assert_eq!(expected, actual);
let expected = TableReference::Bare {
table: Cow::Owned("table".to_string()),
};
let actual = TableReference::from("TABLE");
assert_eq!(expected, actual);
let expected = TableReference::Bare {
table: Cow::Owned("TABLE()".to_string()),
};
let actual = TableReference::from("TABLE()");
assert_eq!(expected, actual);
}
#[test]
fn test_table_reference_to_vector() {
let table_reference = TableReference::parse_str("table");
assert_eq!(vec!["table".to_string()], table_reference.to_vec());
let table_reference = TableReference::parse_str("schema.table");
assert_eq!(
vec!["schema".to_string(), "table".to_string()],
table_reference.to_vec()
);
let table_reference = TableReference::parse_str("catalog.schema.table");
assert_eq!(
vec![
"catalog".to_string(),
"schema".to_string(),
"table".to_string()
],
table_reference.to_vec()
);
}
}