datafusion_common/
table_reference.rs

1// Licensed to the Apache Software Foundation (ASF) under one
2// or more contributor license agreements.  See the NOTICE file
3// distributed with this work for additional information
4// regarding copyright ownership.  The ASF licenses this file
5// to you under the Apache License, Version 2.0 (the
6// "License"); you may not use this file except in compliance
7// with the License.  You may obtain a copy of the License at
8//
9//   http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing,
12// software distributed under the License is distributed on an
13// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14// KIND, either express or implied.  See the License for the
15// specific language governing permissions and limitations
16// under the License.
17
18use crate::utils::{parse_identifiers_normalized, quote_identifier};
19use std::sync::Arc;
20
21/// A fully resolved path to a table of the form "catalog.schema.table"
22#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
23pub struct ResolvedTableReference {
24    /// The catalog (aka database) containing the table
25    pub catalog: Arc<str>,
26    /// The schema containing the table
27    pub schema: Arc<str>,
28    /// The table name
29    pub table: Arc<str>,
30}
31
32impl std::fmt::Display for ResolvedTableReference {
33    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
34        write!(f, "{}.{}.{}", self.catalog, self.schema, self.table)
35    }
36}
37
38/// A multi part identifier (path) to a table that may require further
39/// resolution (e.g. `foo.bar`).
40///
41/// [`TableReference`]s are cheap to `clone()` as they are implemented with
42/// `Arc`.
43///
44/// See [`ResolvedTableReference`] for a fully resolved table reference.
45///
46/// # Creating [`TableReference`]
47///
48/// When converting strings to [`TableReference`]s, the string is parsed as
49/// though it were a SQL identifier, normalizing (convert to lowercase) any
50/// unquoted identifiers.  [`TableReference::bare`] creates references without
51/// applying normalization semantics.
52///
53/// # Examples
54/// ```
55/// # use datafusion_common::TableReference;
56/// // Get a table reference to 'mytable'
57/// let table_reference = TableReference::from("mytable");
58/// assert_eq!(table_reference, TableReference::bare("mytable"));
59///
60/// // Get a table reference to 'mytable' (note the capitalization)
61/// let table_reference = TableReference::from("MyTable");
62/// assert_eq!(table_reference, TableReference::bare("mytable"));
63///
64/// // Get a table reference to 'MyTable' (note the capitalization) using double quotes
65/// // (programmatically it is better to use `TableReference::bare` for this)
66/// let table_reference = TableReference::from(r#""MyTable""#);
67/// assert_eq!(table_reference, TableReference::bare("MyTable"));
68///
69/// // Get a table reference to 'myschema.mytable' (note the capitalization)
70/// let table_reference = TableReference::from("MySchema.MyTable");
71/// assert_eq!(table_reference, TableReference::partial("myschema", "mytable"));
72///```
73#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
74pub enum TableReference {
75    /// An unqualified table reference, e.g. "table"
76    Bare {
77        /// The table name
78        table: Arc<str>,
79    },
80    /// A partially resolved table reference, e.g. "schema.table"
81    Partial {
82        /// The schema containing the table
83        schema: Arc<str>,
84        /// The table name
85        table: Arc<str>,
86    },
87    /// A fully resolved table reference, e.g. "catalog.schema.table"
88    Full {
89        /// The catalog (aka database) containing the table
90        catalog: Arc<str>,
91        /// The schema containing the table
92        schema: Arc<str>,
93        /// The table name
94        table: Arc<str>,
95    },
96}
97
98impl std::fmt::Display for TableReference {
99    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
100        match self {
101            TableReference::Bare { table } => write!(f, "{table}"),
102            TableReference::Partial { schema, table } => {
103                write!(f, "{schema}.{table}")
104            }
105            TableReference::Full {
106                catalog,
107                schema,
108                table,
109            } => write!(f, "{catalog}.{schema}.{table}"),
110        }
111    }
112}
113
114impl TableReference {
115    /// Convenience method for creating a typed none `None`
116    pub fn none() -> Option<TableReference> {
117        None
118    }
119
120    /// Convenience method for creating a [`TableReference::Bare`]
121    ///
122    /// As described on [`TableReference`] this does *NO* normalization at
123    /// all, so "Foo.Bar" stays as a reference to the table named
124    /// "Foo.Bar" (rather than "foo"."bar")
125    pub fn bare(table: impl Into<Arc<str>>) -> TableReference {
126        TableReference::Bare {
127            table: table.into(),
128        }
129    }
130
131    /// Convenience method for creating a [`TableReference::Partial`].
132    ///
133    /// Note: *NO* normalization is applied to the schema or table name.
134    pub fn partial(
135        schema: impl Into<Arc<str>>,
136        table: impl Into<Arc<str>>,
137    ) -> TableReference {
138        TableReference::Partial {
139            schema: schema.into(),
140            table: table.into(),
141        }
142    }
143
144    /// Convenience method for creating a [`TableReference::Full`]
145    ///
146    /// Note: *NO* normalization is applied to the catalog, schema or table
147    /// name.
148    pub fn full(
149        catalog: impl Into<Arc<str>>,
150        schema: impl Into<Arc<str>>,
151        table: impl Into<Arc<str>>,
152    ) -> TableReference {
153        TableReference::Full {
154            catalog: catalog.into(),
155            schema: schema.into(),
156            table: table.into(),
157        }
158    }
159
160    /// Retrieve the table name, regardless of qualification.
161    pub fn table(&self) -> &str {
162        match self {
163            Self::Full { table, .. }
164            | Self::Partial { table, .. }
165            | Self::Bare { table } => table,
166        }
167    }
168
169    /// Retrieve the schema name if [`Self::Partial]` or [`Self::`Full`],
170    /// `None` otherwise.
171    pub fn schema(&self) -> Option<&str> {
172        match self {
173            Self::Full { schema, .. } | Self::Partial { schema, .. } => Some(schema),
174            _ => None,
175        }
176    }
177
178    /// Retrieve the catalog name if  [`Self::Full`], `None` otherwise.
179    pub fn catalog(&self) -> Option<&str> {
180        match self {
181            Self::Full { catalog, .. } => Some(catalog),
182            _ => None,
183        }
184    }
185
186    /// Compare with another [`TableReference`] as if both are resolved.
187    /// This allows comparing across variants. If a field is not present
188    /// in both variants being compared then it is ignored in the comparison.
189    ///
190    /// e.g. this allows a [`TableReference::Bare`] to be considered equal to a
191    /// fully qualified [`TableReference::Full`] if the table names match.
192    pub fn resolved_eq(&self, other: &Self) -> bool {
193        match self {
194            TableReference::Bare { table } => **table == *other.table(),
195            TableReference::Partial { schema, table } => {
196                **table == *other.table() && other.schema().is_none_or(|s| *s == **schema)
197            }
198            TableReference::Full {
199                catalog,
200                schema,
201                table,
202            } => {
203                **table == *other.table()
204                    && other.schema().is_none_or(|s| *s == **schema)
205                    && other.catalog().is_none_or(|c| *c == **catalog)
206            }
207        }
208    }
209
210    /// Given a default catalog and schema, ensure this table reference is fully
211    /// resolved
212    pub fn resolve(
213        self,
214        default_catalog: &str,
215        default_schema: &str,
216    ) -> ResolvedTableReference {
217        match self {
218            Self::Full {
219                catalog,
220                schema,
221                table,
222            } => ResolvedTableReference {
223                catalog,
224                schema,
225                table,
226            },
227            Self::Partial { schema, table } => ResolvedTableReference {
228                catalog: default_catalog.into(),
229                schema,
230                table,
231            },
232            Self::Bare { table } => ResolvedTableReference {
233                catalog: default_catalog.into(),
234                schema: default_schema.into(),
235                table,
236            },
237        }
238    }
239
240    /// Forms a string where the identifiers are quoted
241    ///
242    /// # Example
243    /// ```
244    /// # use datafusion_common::TableReference;
245    /// let table_reference = TableReference::partial("myschema", "mytable");
246    /// assert_eq!(table_reference.to_quoted_string(), "myschema.mytable");
247    ///
248    /// let table_reference = TableReference::partial("MySchema", "MyTable");
249    /// assert_eq!(table_reference.to_quoted_string(), r#""MySchema"."MyTable""#);
250    /// ```
251    pub fn to_quoted_string(&self) -> String {
252        match self {
253            TableReference::Bare { table } => quote_identifier(table).to_string(),
254            TableReference::Partial { schema, table } => {
255                format!("{}.{}", quote_identifier(schema), quote_identifier(table))
256            }
257            TableReference::Full {
258                catalog,
259                schema,
260                table,
261            } => format!(
262                "{}.{}.{}",
263                quote_identifier(catalog),
264                quote_identifier(schema),
265                quote_identifier(table)
266            ),
267        }
268    }
269
270    /// Forms a [`TableReference`] by parsing `s` as a multipart SQL
271    /// identifier. See docs on [`TableReference`] for more details.
272    pub fn parse_str(s: &str) -> Self {
273        let mut parts = parse_identifiers_normalized(s, false);
274
275        match parts.len() {
276            1 => Self::Bare {
277                table: parts.remove(0).into(),
278            },
279            2 => Self::Partial {
280                schema: parts.remove(0).into(),
281                table: parts.remove(0).into(),
282            },
283            3 => Self::Full {
284                catalog: parts.remove(0).into(),
285                schema: parts.remove(0).into(),
286                table: parts.remove(0).into(),
287            },
288            _ => Self::Bare { table: s.into() },
289        }
290    }
291
292    /// Decompose a [`TableReference`] to separate parts. The result vector contains
293    /// at most three elements in the following sequence:
294    /// ```no_rust
295    /// [<catalog>, <schema>, table]
296    /// ```
297    pub fn to_vec(&self) -> Vec<String> {
298        match self {
299            TableReference::Bare { table } => vec![table.to_string()],
300            TableReference::Partial { schema, table } => {
301                vec![schema.to_string(), table.to_string()]
302            }
303            TableReference::Full {
304                catalog,
305                schema,
306                table,
307            } => vec![catalog.to_string(), schema.to_string(), table.to_string()],
308        }
309    }
310}
311
312/// Parse a string into a TableReference, normalizing where appropriate
313///
314/// See full details on [`TableReference::parse_str`]
315impl<'a> From<&'a str> for TableReference {
316    fn from(s: &'a str) -> Self {
317        Self::parse_str(s)
318    }
319}
320
321impl<'a> From<&'a String> for TableReference {
322    fn from(s: &'a String) -> Self {
323        Self::parse_str(s)
324    }
325}
326
327impl From<String> for TableReference {
328    fn from(s: String) -> Self {
329        Self::parse_str(&s)
330    }
331}
332
333impl From<ResolvedTableReference> for TableReference {
334    fn from(resolved: ResolvedTableReference) -> Self {
335        Self::Full {
336            catalog: resolved.catalog,
337            schema: resolved.schema,
338            table: resolved.table,
339        }
340    }
341}
342
343#[cfg(test)]
344mod tests {
345    use super::*;
346
347    #[test]
348    fn test_table_reference_from_str_normalizes() {
349        let expected = TableReference::Full {
350            catalog: "catalog".into(),
351            schema: "FOO\".bar".into(),
352            table: "table".into(),
353        };
354        let actual = TableReference::from("catalog.\"FOO\"\".bar\".TABLE");
355        assert_eq!(expected, actual);
356
357        let expected = TableReference::Partial {
358            schema: "FOO\".bar".into(),
359            table: "table".into(),
360        };
361        let actual = TableReference::from("\"FOO\"\".bar\".TABLE");
362        assert_eq!(expected, actual);
363
364        let expected = TableReference::Bare {
365            table: "table".into(),
366        };
367        let actual = TableReference::from("TABLE");
368        assert_eq!(expected, actual);
369
370        // if fail to parse, take entire input string as identifier
371        let expected = TableReference::Bare {
372            table: "TABLE()".into(),
373        };
374        let actual = TableReference::from("TABLE()");
375        assert_eq!(expected, actual);
376    }
377
378    #[test]
379    fn test_table_reference_to_vector() {
380        let table_reference = TableReference::parse_str("table");
381        assert_eq!(vec!["table".to_string()], table_reference.to_vec());
382
383        let table_reference = TableReference::parse_str("schema.table");
384        assert_eq!(
385            vec!["schema".to_string(), "table".to_string()],
386            table_reference.to_vec()
387        );
388
389        let table_reference = TableReference::parse_str("catalog.schema.table");
390        assert_eq!(
391            vec![
392                "catalog".to_string(),
393                "schema".to_string(),
394                "table".to_string()
395            ],
396            table_reference.to_vec()
397        );
398    }
399}