blob: 3163a8b16c8dc437ad38cd0ce8707e6aa10af648 [file]
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
use crate::utils::parse_identifiers_normalized;
use crate::utils::quote_identifier;
use std::sync::Arc;
/// A fully resolved path to a table of the form "catalog.schema.table"
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct ResolvedTableReference {
/// The catalog (aka database) containing the table
pub catalog: Arc<str>,
/// The schema containing the table
pub schema: Arc<str>,
/// The table name
pub table: Arc<str>,
}
impl std::fmt::Display for ResolvedTableReference {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}.{}.{}", self.catalog, self.schema, self.table)
}
}
/// A multi part identifier (path) to a table that may require further
/// resolution (e.g. `foo.bar`).
///
/// [`TableReference`]s are cheap to `clone()` as they are implemented with
/// `Arc`.
///
/// See [`ResolvedTableReference`] for a fully resolved table reference.
///
/// # Creating [`TableReference`]
///
/// When converting strings to [`TableReference`]s, the string is parsed as
/// though it were a SQL identifier, normalizing (convert to lowercase) any
/// unquoted identifiers. [`TableReference::bare`] creates references without
/// applying normalization semantics.
///
/// # Examples
/// ```
/// # use datafusion_common::TableReference;
/// // Get a table reference to 'mytable'
/// let table_reference = TableReference::from("mytable");
/// assert_eq!(table_reference, TableReference::bare("mytable"));
///
/// // Get a table reference to 'mytable' (note the capitalization)
/// let table_reference = TableReference::from("MyTable");
/// assert_eq!(table_reference, TableReference::bare("mytable"));
///
/// // Get a table reference to 'MyTable' (note the capitalization) using double quotes
/// // (programmatically it is better to use `TableReference::bare` for this)
/// let table_reference = TableReference::from(r#""MyTable""#);
/// assert_eq!(table_reference, TableReference::bare("MyTable"));
///
/// // Get a table reference to 'myschema.mytable' (note the capitalization)
/// let table_reference = TableReference::from("MySchema.MyTable");
/// assert_eq!(
/// table_reference,
/// TableReference::partial("myschema", "mytable")
/// );
/// ```
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum TableReference {
/// An unqualified table reference, e.g. "table"
Bare {
/// The table name
table: Arc<str>,
},
/// A partially resolved table reference, e.g. "schema.table"
Partial {
/// The schema containing the table
schema: Arc<str>,
/// The table name
table: Arc<str>,
},
/// A fully resolved table reference, e.g. "catalog.schema.table"
Full {
/// The catalog (aka database) containing the table
catalog: Arc<str>,
/// The schema containing the table
schema: Arc<str>,
/// The table name
table: Arc<str>,
},
}
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 TableReference {
/// Convenience method for creating a typed none `None`
pub fn none() -> Option<TableReference> {
None
}
/// Convenience method for creating a [`TableReference::Bare`]
///
/// As described on [`TableReference`] this does *NO* normalization at
/// all, so "Foo.Bar" stays as a reference to the table named
/// "Foo.Bar" (rather than "foo"."bar")
pub fn bare(table: impl Into<Arc<str>>) -> TableReference {
TableReference::Bare {
table: table.into(),
}
}
/// Convenience method for creating a [`TableReference::Partial`].
///
/// Note: *NO* normalization is applied to the schema or table name.
pub fn partial(
schema: impl Into<Arc<str>>,
table: impl Into<Arc<str>>,
) -> TableReference {
TableReference::Partial {
schema: schema.into(),
table: table.into(),
}
}
/// Convenience method for creating a [`TableReference::Full`]
///
/// Note: *NO* normalization is applied to the catalog, schema or table
/// name.
pub fn full(
catalog: impl Into<Arc<str>>,
schema: impl Into<Arc<str>>,
table: impl Into<Arc<str>>,
) -> TableReference {
TableReference::Full {
catalog: catalog.into(),
schema: schema.into(),
table: table.into(),
}
}
/// Retrieve the table name, regardless of qualification.
pub fn table(&self) -> &str {
match self {
Self::Full { table, .. }
| Self::Partial { table, .. }
| Self::Bare { table } => table,
}
}
/// Retrieve the schema name if [`Self::Partial]` or [`Self::`Full`],
/// `None` otherwise.
pub fn schema(&self) -> Option<&str> {
match self {
Self::Full { schema, .. } | Self::Partial { schema, .. } => Some(schema),
_ => None,
}
}
/// Retrieve the catalog name if [`Self::Full`], `None` otherwise.
pub fn catalog(&self) -> Option<&str> {
match self {
Self::Full { catalog, .. } => Some(catalog),
_ => None,
}
}
/// Compare with another [`TableReference`] as if both are resolved.
/// This allows comparing across variants. If a field is not present
/// in both variants being compared then it is ignored in the comparison.
///
/// e.g. this allows a [`TableReference::Bare`] to be considered equal to a
/// fully qualified [`TableReference::Full`] if the table names match.
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().is_none_or(|s| *s == **schema)
}
TableReference::Full {
catalog,
schema,
table,
} => {
**table == *other.table()
&& other.schema().is_none_or(|s| *s == **schema)
&& other.catalog().is_none_or(|c| *c == **catalog)
}
}
}
/// Given a default catalog and schema, ensure this table reference is fully
/// resolved
pub fn resolve(
self,
default_catalog: &str,
default_schema: &str,
) -> ResolvedTableReference {
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,
},
}
}
/// Forms a string where the identifiers are quoted
///
/// # Example
/// ```
/// # use datafusion_common::TableReference;
/// let table_reference = TableReference::partial("myschema", "mytable");
/// assert_eq!(table_reference.to_quoted_string(), "myschema.mytable");
///
/// let table_reference = TableReference::partial("MySchema", "MyTable");
/// assert_eq!(
/// table_reference.to_quoted_string(),
/// r#""MySchema"."MyTable""#
/// );
/// ```
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)
),
}
}
/// Forms a [`TableReference`] by parsing `s` as a multipart SQL
/// identifier, normalizing `s` to lowercase.
/// See docs on [`TableReference`] for more details.
pub fn parse_str(s: &str) -> Self {
Self::parse_str_normalized(s, false)
}
/// Forms a [`TableReference`] by parsing `s` as a multipart SQL
/// identifier, normalizing `s` to lowercase if `ignore_case` is `false`.
/// See docs on [`TableReference`] for more details.
pub fn parse_str_normalized(s: &str, ignore_case: bool) -> Self {
let table_parts = parse_identifiers_normalized(s, ignore_case);
Self::from_vec(table_parts).unwrap_or_else(|| Self::Bare { table: s.into() })
}
/// Consume a vector of identifier parts to compose a [`TableReference`]. The input vector
/// should contain 1 <= N <= 3 elements in the following sequence:
/// ```no_rust
/// [<catalog>, <schema>, table]
/// ```
fn from_vec(mut parts: Vec<String>) -> Option<Self> {
match parts.len() {
1 => Some(Self::Bare {
table: parts.pop()?.into(),
}),
2 => Some(Self::Partial {
table: parts.pop()?.into(),
schema: parts.pop()?.into(),
}),
3 => Some(Self::Full {
table: parts.pop()?.into(),
schema: parts.pop()?.into(),
catalog: parts.pop()?.into(),
}),
_ => None,
}
}
/// Decompose a [`TableReference`] to separate parts. The result vector contains
/// at most three elements in the following sequence:
/// ```no_rust
/// [<catalog>, <schema>, table]
/// ```
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()],
}
}
}
/// Parse a string into a TableReference, normalizing where appropriate
///
/// See full details on [`TableReference::parse_str`]
impl<'a> From<&'a str> for TableReference {
fn from(s: &'a str) -> Self {
Self::parse_str(s)
}
}
impl<'a> From<&'a String> for TableReference {
fn from(s: &'a String) -> Self {
Self::parse_str(s)
}
}
impl From<String> for TableReference {
fn from(s: String) -> Self {
Self::parse_str(&s)
}
}
impl From<ResolvedTableReference> for TableReference {
fn from(resolved: ResolvedTableReference) -> 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: "catalog".into(),
schema: "FOO\".bar".into(),
table: "table".into(),
};
let actual = TableReference::from("catalog.\"FOO\"\".bar\".TABLE");
assert_eq!(expected, actual);
let expected = TableReference::Partial {
schema: "FOO\".bar".into(),
table: "table".into(),
};
let actual = TableReference::from("\"FOO\"\".bar\".TABLE");
assert_eq!(expected, actual);
let expected = TableReference::Bare {
table: "table".into(),
};
let actual = TableReference::from("TABLE");
assert_eq!(expected, actual);
// Disable this test for non-sql features so that we don't need to reproduce
// things like table function upper case conventions, since those will not
// be used if SQL is not selected.
#[cfg(feature = "sql")]
{
// if fail to parse, take entire input string as identifier
let expected = TableReference::Bare {
table: "TABLE()".into(),
};
let actual = TableReference::from("TABLE()");
assert_eq!(expected, actual);
}
}
#[test]
fn test_table_reference_to_vector() {
let table_reference = TableReference::from("table");
assert_eq!(vec!["table".to_string()], table_reference.to_vec());
let table_reference = TableReference::from("schema.table");
assert_eq!(
vec!["schema".to_string(), "table".to_string()],
table_reference.to_vec()
);
let table_reference = TableReference::from("catalog.schema.table");
assert_eq!(
vec![
"catalog".to_string(),
"schema".to_string(),
"table".to_string()
],
table_reference.to_vec()
);
}
}