Coverage Report

Created: 2024-10-13 08:39

/Users/andrewlamb/Software/datafusion/datafusion/common/src/table_reference.rs
Line
Count
Source (jump to first uncovered line)
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
18
use crate::utils::{parse_identifiers_normalized, quote_identifier};
19
use std::sync::Arc;
20
21
/// A fully resolved path to a table of the form "catalog.schema.table"
22
#[derive(Debug, Clone)]
23
pub 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
32
impl std::fmt::Display for ResolvedTableReference {
33
0
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
34
0
        write!(f, "{}.{}.{}", self.catalog, self.schema, self.table)
35
0
    }
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)]
74
pub 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
98
impl std::fmt::Display for TableReference {
99
0
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
100
0
        match self {
101
0
            TableReference::Bare { table } => write!(f, "{table}"),
102
0
            TableReference::Partial { schema, table } => {
103
0
                write!(f, "{schema}.{table}")
104
            }
105
            TableReference::Full {
106
0
                catalog,
107
0
                schema,
108
0
                table,
109
0
            } => write!(f, "{catalog}.{schema}.{table}"),
110
        }
111
0
    }
112
}
113
114
impl TableReference {
115
    /// Convenience method for creating a typed none `None`
116
0
    pub fn none() -> Option<TableReference> {
117
0
        None
118
0
    }
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
0
    pub fn bare(table: impl Into<Arc<str>>) -> TableReference {
126
0
        TableReference::Bare {
127
0
            table: table.into(),
128
0
        }
129
0
    }
130
131
    /// Convenience method for creating a [`TableReference::Partial`].
132
    ///
133
    /// Note: *NO* normalization is applied to the schema or table name.
134
0
    pub fn partial(
135
0
        schema: impl Into<Arc<str>>,
136
0
        table: impl Into<Arc<str>>,
137
0
    ) -> TableReference {
138
0
        TableReference::Partial {
139
0
            schema: schema.into(),
140
0
            table: table.into(),
141
0
        }
142
0
    }
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
0
    pub fn full(
149
0
        catalog: impl Into<Arc<str>>,
150
0
        schema: impl Into<Arc<str>>,
151
0
        table: impl Into<Arc<str>>,
152
0
    ) -> TableReference {
153
0
        TableReference::Full {
154
0
            catalog: catalog.into(),
155
0
            schema: schema.into(),
156
0
            table: table.into(),
157
0
        }
158
0
    }
159
160
    /// Retrieve the table name, regardless of qualification.
161
0
    pub fn table(&self) -> &str {
162
0
        match self {
163
0
            Self::Full { table, .. }
164
0
            | Self::Partial { table, .. }
165
0
            | Self::Bare { table } => table,
166
0
        }
167
0
    }
168
169
    /// Retrieve the schema name if [`Self::Partial]` or [`Self::`Full`],
170
    /// `None` otherwise.
171
0
    pub fn schema(&self) -> Option<&str> {
172
0
        match self {
173
0
            Self::Full { schema, .. } | Self::Partial { schema, .. } => Some(schema),
174
0
            _ => None,
175
        }
176
0
    }
177
178
    /// Retrieve the catalog name if  [`Self::Full`], `None` otherwise.
179
0
    pub fn catalog(&self) -> Option<&str> {
180
0
        match self {
181
0
            Self::Full { catalog, .. } => Some(catalog),
182
0
            _ => None,
183
        }
184
0
    }
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
0
    pub fn resolved_eq(&self, other: &Self) -> bool {
193
0
        match self {
194
0
            TableReference::Bare { table } => **table == *other.table(),
195
0
            TableReference::Partial { schema, table } => {
196
0
                **table == *other.table()
197
0
                    && other.schema().map_or(true, |s| *s == **schema)
198
            }
199
            TableReference::Full {
200
0
                catalog,
201
0
                schema,
202
0
                table,
203
0
            } => {
204
0
                **table == *other.table()
205
0
                    && other.schema().map_or(true, |s| *s == **schema)
206
0
                    && other.catalog().map_or(true, |c| *c == **catalog)
207
            }
208
        }
209
0
    }
210
211
    /// Given a default catalog and schema, ensure this table reference is fully
212
    /// resolved
213
0
    pub fn resolve(
214
0
        self,
215
0
        default_catalog: &str,
216
0
        default_schema: &str,
217
0
    ) -> ResolvedTableReference {
218
0
        match self {
219
            Self::Full {
220
0
                catalog,
221
0
                schema,
222
0
                table,
223
0
            } => ResolvedTableReference {
224
0
                catalog,
225
0
                schema,
226
0
                table,
227
0
            },
228
0
            Self::Partial { schema, table } => ResolvedTableReference {
229
0
                catalog: default_catalog.into(),
230
0
                schema,
231
0
                table,
232
0
            },
233
0
            Self::Bare { table } => ResolvedTableReference {
234
0
                catalog: default_catalog.into(),
235
0
                schema: default_schema.into(),
236
0
                table,
237
0
            },
238
        }
239
0
    }
240
241
    /// Forms a string where the identifiers are quoted
242
    ///
243
    /// # Example
244
    /// ```
245
    /// # use datafusion_common::TableReference;
246
    /// let table_reference = TableReference::partial("myschema", "mytable");
247
    /// assert_eq!(table_reference.to_quoted_string(), "myschema.mytable");
248
    ///
249
    /// let table_reference = TableReference::partial("MySchema", "MyTable");
250
    /// assert_eq!(table_reference.to_quoted_string(), r#""MySchema"."MyTable""#);
251
    /// ```
252
0
    pub fn to_quoted_string(&self) -> String {
253
0
        match self {
254
0
            TableReference::Bare { table } => quote_identifier(table).to_string(),
255
0
            TableReference::Partial { schema, table } => {
256
0
                format!("{}.{}", quote_identifier(schema), quote_identifier(table))
257
            }
258
            TableReference::Full {
259
0
                catalog,
260
0
                schema,
261
0
                table,
262
0
            } => format!(
263
0
                "{}.{}.{}",
264
0
                quote_identifier(catalog),
265
0
                quote_identifier(schema),
266
0
                quote_identifier(table)
267
0
            ),
268
        }
269
0
    }
270
271
    /// Forms a [`TableReference`] by parsing `s` as a multipart SQL
272
    /// identifier. See docs on [`TableReference`] for more details.
273
0
    pub fn parse_str(s: &str) -> Self {
274
0
        let mut parts = parse_identifiers_normalized(s, false);
275
0
276
0
        match parts.len() {
277
0
            1 => Self::Bare {
278
0
                table: parts.remove(0).into(),
279
0
            },
280
0
            2 => Self::Partial {
281
0
                schema: parts.remove(0).into(),
282
0
                table: parts.remove(0).into(),
283
0
            },
284
0
            3 => Self::Full {
285
0
                catalog: parts.remove(0).into(),
286
0
                schema: parts.remove(0).into(),
287
0
                table: parts.remove(0).into(),
288
0
            },
289
0
            _ => Self::Bare { table: s.into() },
290
        }
291
0
    }
292
293
    /// Decompose a [`TableReference`] to separate parts. The result vector contains
294
    /// at most three elements in the following sequence:
295
    /// ```no_rust
296
    /// [<catalog>, <schema>, table]
297
    /// ```
298
0
    pub fn to_vec(&self) -> Vec<String> {
299
0
        match self {
300
0
            TableReference::Bare { table } => vec![table.to_string()],
301
0
            TableReference::Partial { schema, table } => {
302
0
                vec![schema.to_string(), table.to_string()]
303
            }
304
            TableReference::Full {
305
0
                catalog,
306
0
                schema,
307
0
                table,
308
0
            } => vec![catalog.to_string(), schema.to_string(), table.to_string()],
309
        }
310
0
    }
311
}
312
313
/// Parse a string into a TableReference, normalizing where appropriate
314
///
315
/// See full details on [`TableReference::parse_str`]
316
impl<'a> From<&'a str> for TableReference {
317
0
    fn from(s: &'a str) -> Self {
318
0
        Self::parse_str(s)
319
0
    }
320
}
321
322
impl<'a> From<&'a String> for TableReference {
323
0
    fn from(s: &'a String) -> Self {
324
0
        Self::parse_str(s)
325
0
    }
326
}
327
328
impl From<String> for TableReference {
329
0
    fn from(s: String) -> Self {
330
0
        Self::parse_str(&s)
331
0
    }
332
}
333
334
impl From<ResolvedTableReference> for TableReference {
335
0
    fn from(resolved: ResolvedTableReference) -> Self {
336
0
        Self::Full {
337
0
            catalog: resolved.catalog,
338
0
            schema: resolved.schema,
339
0
            table: resolved.table,
340
0
        }
341
0
    }
342
}
343
344
#[cfg(test)]
345
mod tests {
346
    use super::*;
347
348
    #[test]
349
    fn test_table_reference_from_str_normalizes() {
350
        let expected = TableReference::Full {
351
            catalog: "catalog".into(),
352
            schema: "FOO\".bar".into(),
353
            table: "table".into(),
354
        };
355
        let actual = TableReference::from("catalog.\"FOO\"\".bar\".TABLE");
356
        assert_eq!(expected, actual);
357
358
        let expected = TableReference::Partial {
359
            schema: "FOO\".bar".into(),
360
            table: "table".into(),
361
        };
362
        let actual = TableReference::from("\"FOO\"\".bar\".TABLE");
363
        assert_eq!(expected, actual);
364
365
        let expected = TableReference::Bare {
366
            table: "table".into(),
367
        };
368
        let actual = TableReference::from("TABLE");
369
        assert_eq!(expected, actual);
370
371
        // if fail to parse, take entire input string as identifier
372
        let expected = TableReference::Bare {
373
            table: "TABLE()".into(),
374
        };
375
        let actual = TableReference::from("TABLE()");
376
        assert_eq!(expected, actual);
377
    }
378
379
    #[test]
380
    fn test_table_reference_to_vector() {
381
        let table_reference = TableReference::parse_str("table");
382
        assert_eq!(vec!["table".to_string()], table_reference.to_vec());
383
384
        let table_reference = TableReference::parse_str("schema.table");
385
        assert_eq!(
386
            vec!["schema".to_string(), "table".to_string()],
387
            table_reference.to_vec()
388
        );
389
390
        let table_reference = TableReference::parse_str("catalog.schema.table");
391
        assert_eq!(
392
            vec![
393
                "catalog".to_string(),
394
                "schema".to_string(),
395
                "table".to_string()
396
            ],
397
            table_reference.to_vec()
398
        );
399
    }
400
}