use core::fmt;
use crate::casing::IdentifierCasing;
use crate::error::Error;
use sqlparser::ast::{Ident, Insert, ObjectName, TableFactor, TableObject};
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct TableReference {
#[cfg_attr(
feature = "serde",
serde(serialize_with = "crate::serde_support::opt_ident")
)]
pub catalog: Option<Ident>,
#[cfg_attr(
feature = "serde",
serde(serialize_with = "crate::serde_support::opt_ident")
)]
pub schema: Option<Ident>,
#[cfg_attr(
feature = "serde",
serde(serialize_with = "crate::serde_support::ident")
)]
pub name: Ident,
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct TableRead {
pub reference: TableReference,
pub resolution: ResolutionKind,
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct TableWrite {
pub reference: TableReference,
pub resolution: ResolutionKind,
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct ColumnReference {
pub table: Option<TableReference>,
#[cfg_attr(
feature = "serde",
serde(serialize_with = "crate::serde_support::ident")
)]
pub name: Ident,
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct ColumnRead {
pub reference: ColumnReference,
pub resolution: ResolutionKind,
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct ColumnWrite {
pub reference: ColumnReference,
pub resolution: ResolutionKind,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub enum ResolutionKind {
Cataloged,
Inferred,
Ambiguous,
Unresolved,
}
impl TableReference {
pub(crate) fn try_from_name(name: &ObjectName) -> Result<Self, Error> {
let parts = name
.0
.iter()
.map(|part| part.as_ident())
.collect::<Option<Vec<&Ident>>>()
.ok_or_else(|| {
Error::AnalysisError(format!(
"table name `{name}` is not a plain identifier path"
))
})?;
match parts.as_slice() {
[] => Err(Error::AnalysisError(
"ObjectName has no identifiers".to_string(),
)),
[n] => Ok(TableReference {
catalog: None,
schema: None,
name: (*n).clone(),
}),
[schema, n] => Ok(TableReference {
catalog: None,
schema: Some((*schema).clone()),
name: (*n).clone(),
}),
[catalog, schema, n] => Ok(TableReference {
catalog: Some((*catalog).clone()),
schema: Some((*schema).clone()),
name: (*n).clone(),
}),
_ => Err(Error::AnalysisError(
"Too many identifiers provided".to_string(),
)),
}
}
pub(crate) fn format_list(tables: &[Self]) -> String {
tables
.iter()
.map(|t| t.to_string())
.collect::<Vec<_>>()
.join(", ")
}
pub(crate) fn try_from_parts(parts: &[Ident]) -> Option<Self> {
match parts {
[name] => Some(TableReference {
catalog: None,
schema: None,
name: name.clone(),
}),
[schema, name] => Some(TableReference {
catalog: None,
schema: Some(schema.clone()),
name: name.clone(),
}),
[catalog, schema, name] => Some(TableReference {
catalog: Some(catalog.clone()),
schema: Some(schema.clone()),
name: name.clone(),
}),
_ => None,
}
}
pub(crate) fn from_insert_with_alias(value: &Insert) -> Result<(Self, Option<Ident>), Error> {
let name = match &value.table {
TableObject::TableName(object_name) => object_name,
TableObject::TableFunction(function) => &function.name,
};
Ok((Self::try_from_name(name)?, value.table_alias.clone()))
}
pub(crate) fn from_table_factor_with_alias(
table: &TableFactor,
) -> Result<(Self, Option<Ident>), Error> {
match table {
TableFactor::Table { name, alias, .. } => Ok((
Self::try_from_name(name)?,
alias.as_ref().map(|a| a.name.clone()),
)),
_ => Err(Error::AnalysisError(
"TableFactor variant other than Table cannot be converted to a TableReference"
.to_string(),
)),
}
}
}
impl fmt::Display for TableReference {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut parts = Vec::new();
if let Some(catalog) = &self.catalog {
parts.push(catalog.to_string());
}
if let Some(schema) = &self.schema {
parts.push(schema.to_string());
}
parts.push(self.name.to_string());
write!(f, "{}", parts.join("."))
}
}
impl fmt::Display for ColumnReference {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match &self.table {
Some(table) => write!(f, "{table}.{}", self.name),
None => write!(f, "{}", self.name),
}
}
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct TableIdentityKey {
catalog: Option<String>,
schema: Option<String>,
name: String,
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct ColumnIdentityKey {
table: Option<TableIdentityKey>,
name: String,
}
impl TableReference {
pub fn identity_key(&self, casing: &IdentifierCasing) -> TableIdentityKey {
let fold = |ident: &Ident| casing.table.normalize(ident);
TableIdentityKey {
catalog: self.catalog.as_ref().map(&fold),
schema: self.schema.as_ref().map(&fold),
name: fold(&self.name),
}
}
pub fn same_table(&self, other: &Self, casing: &IdentifierCasing) -> bool {
self.identity_key(casing) == other.identity_key(casing)
}
}
impl ColumnReference {
pub fn identity_key(&self, casing: &IdentifierCasing) -> ColumnIdentityKey {
ColumnIdentityKey {
table: self.table.as_ref().map(|t| t.identity_key(casing)),
name: casing.column.normalize(&self.name),
}
}
pub fn same_column(&self, other: &Self, casing: &IdentifierCasing) -> bool {
self.identity_key(casing) == other.identity_key(casing)
}
}
impl TryFrom<&Insert> for TableReference {
type Error = Error;
fn try_from(value: &Insert) -> Result<Self, Self::Error> {
Self::from_insert_with_alias(value).map(|(table, _)| table)
}
}
impl TryFrom<&TableFactor> for TableReference {
type Error = Error;
fn try_from(table: &TableFactor) -> Result<Self, Self::Error> {
Self::from_table_factor_with_alias(table).map(|(table, _)| table)
}
}
impl TryFrom<&ObjectName> for TableReference {
type Error = Error;
fn try_from(obj_name: &ObjectName) -> Result<Self, Self::Error> {
Self::try_from_name(obj_name)
}
}
#[cfg(test)]
mod tests {
use super::*;
use sqlparser::ast::{SetExpr, Statement};
use sqlparser::dialect::GenericDialect;
use sqlparser::parser::Parser;
fn first_table_factor(from: &str) -> TableFactor {
let sql = format!("SELECT 1 FROM {from}");
let mut stmts = Parser::parse_sql(&GenericDialect {}, &sql).unwrap();
let Statement::Query(query) = stmts.remove(0) else {
panic!("expected a query");
};
let SetExpr::Select(select) = *query.body else {
panic!("expected a SELECT");
};
select.from.into_iter().next().unwrap().relation
}
#[test]
fn try_from_object_name_keeps_catalog_schema_name_and_displays_all_parts() {
let factor = first_table_factor("cat.sch.tbl");
let TableFactor::Table { name, .. } = &factor else {
panic!("expected a table factor");
};
let reference = TableReference::try_from(name).unwrap();
assert_eq!(reference.catalog.as_ref().unwrap().value, "cat");
assert_eq!(reference.schema.as_ref().unwrap().value, "sch");
assert_eq!(reference.name.value, "tbl");
assert_eq!(reference.to_string(), "cat.sch.tbl");
}
#[test]
fn try_from_table_factor_converts_a_table_and_rejects_a_derived_factor() {
let table = first_table_factor("a.b");
let reference = TableReference::try_from(&table).unwrap();
assert_eq!(reference.schema.as_ref().unwrap().value, "a");
assert_eq!(reference.name.value, "b");
let derived = first_table_factor("(SELECT 1) AS d");
assert!(matches!(
TableReference::try_from(&derived),
Err(Error::AnalysisError(_))
));
}
#[test]
fn try_from_insert_takes_the_target_name() {
let mut stmts =
Parser::parse_sql(&GenericDialect {}, "INSERT INTO a.b VALUES (1)").unwrap();
let Statement::Insert(insert) = stmts.remove(0) else {
panic!("expected an insert");
};
let reference = TableReference::try_from(&insert).unwrap();
assert_eq!(reference.schema.as_ref().unwrap().value, "a");
assert_eq!(reference.name.value, "b");
}
}