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}