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}