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