## 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
    }