## Coverage Report

#### Created: 2025-11-22 06:00

next uncovered line (L), next uncovered region (R), next uncovered branch (B)

    /Users/andrewlamb/Software/datafusion/datafusion/common/src/table_reference.rs

    Line
    Count
    Source
    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;
    19
    use crate::utils::quote_identifier;
    20
    use 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)]
    24
    pub 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
    33
    impl std::fmt::Display for ResolvedTableReference {
    34
    0
        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
    35
    0
            write!(f, "{}.{}.{}", self.catalog, self.schema, self.table)
    36
    0
        }
    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)]
    78
    pub 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
    102
    impl std::fmt::Display for TableReference {
    103
    0
        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
    104
    0
            match self {
    105
    0
                TableReference::Bare { table } => write!(f, "{table}"),
    106
    0
                TableReference::Partial { schema, table } => {
    107
    0
                    write!(f, "{schema}.{table}")
    108
                }
    109
                TableReference::Full {
    110
    0
                    catalog,
    111
    0
                    schema,
    112
    0
                    table,
    113
    0
                } => write!(f, "{catalog}.{schema}.{table}"),
    114
            }
    115
    0
        }
    116
    }
    117
    118
    impl TableReference {
    119
        /// Convenience method for creating a typed none `None`
    120
    0
        pub fn none() -> Option<TableReference> {
    121
    0
            None
    122
    0
        }
    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
    0
        pub fn bare(table: impl Into<Arc<str>>) -> TableReference {
    130
    0
            TableReference::Bare {
    131
    0
                table: table.into(),
    132
    0
            }
    133
    0
        }
    134
    135
        /// Convenience method for creating a [`TableReference::Partial`].
    136
        ///
    137
        /// Note: *NO* normalization is applied to the schema or table name.
    138
    0
        pub fn partial(
    139
    0
            schema: impl Into<Arc<str>>,
    140
    0
            table: impl Into<Arc<str>>,
    141
    0
        ) -> TableReference {
    142
    0
            TableReference::Partial {
    143
    0
                schema: schema.into(),
    144
    0
                table: table.into(),
    145
    0
            }
    146
    0
        }
    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
    0
        pub fn full(
    153
    0
            catalog: impl Into<Arc<str>>,
    154
    0
            schema: impl Into<Arc<str>>,
    155
    0
            table: impl Into<Arc<str>>,
    156
    0
        ) -> TableReference {
    157
    0
            TableReference::Full {
    158
    0
                catalog: catalog.into(),
    159
    0
                schema: schema.into(),
    160
    0
                table: table.into(),
    161
    0
            }
    162
    0
        }
    163
    164
        /// Retrieve the table name, regardless of qualification.
    165
    0
        pub fn table(&self) -> &str {
    166
    0
            match self {
    167
    0
                Self::Full { table, .. }
    168
    0
                | Self::Partial { table, .. }
    169
    0
                | Self::Bare { table } => table,
    170
            }
    171
    0
        }
    172
    173
        /// Retrieve the schema name if [`Self::Partial]` or [`Self::`Full`],
    174
        /// `None` otherwise.
    175
    0
        pub fn schema(&self) -> Option<&str> {
    176
    0
            match self {
    177
    0
                Self::Full { schema, .. } | Self::Partial { schema, .. } => Some(schema),
    178
    0
                _ => None,
    179
            }
    180
    0
        }
    181
    182
        /// Retrieve the catalog name if  [`Self::Full`], `None` otherwise.
    183
    0
        pub fn catalog(&self) -> Option<&str> {
    184
    0
            match self {
    185
    0
                Self::Full { catalog, .. } => Some(catalog),
    186
    0
                _ => None,
    187
            }
    188
    0
        }
    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
    0
        pub fn resolved_eq(&self, other: &Self) -> bool {
    197
    0
            match self {
    198
    0
                TableReference::Bare { table } => **table == *other.table(),
    199
    0
                TableReference::Partial { schema, table } => {
    200
    0
                    **table == *other.table() && other.schema().is_none_or(|s| *s == **schema)
    201
                }
    202
                TableReference::Full {
    203
    0
                    catalog,
    204
    0
                    schema,
    205
    0
                    table,
    206
                } => {
    207
    0
                    **table == *other.table()
    208
    0
                        && other.schema().is_none_or(|s| *s == **schema)
    209
    0
                        && other.catalog().is_none_or(|c| *c == **catalog)
    210
                }
    211
            }
    212
    0
        }
    213
    214
        /// Given a default catalog and schema, ensure this table reference is fully
    215
        /// resolved
    216
    0
        pub fn resolve(
    217
    0
            self,
    218
    0
            default_catalog: &str,
    219
    0
            default_schema: &str,
    220
    0
        ) -> ResolvedTableReference {
    221
    0
            match self {
    222
                Self::Full {
    223
    0
                    catalog,
    224
    0
                    schema,
    225
    0
                    table,
    226
    0
                } => ResolvedTableReference {
    227
    0
                    catalog,
    228
    0
                    schema,
    229
    0
                    table,
    230
    0
                },
    231
    0
                Self::Partial { schema, table } => ResolvedTableReference {
    232
    0
                    catalog: default_catalog.into(),
    233
    0
                    schema,
    234
    0
                    table,
    235
    0
                },
    236
    0
                Self::Bare { table } => ResolvedTableReference {
    237
    0
                    catalog: default_catalog.into(),
    238
    0
                    schema: default_schema.into(),
    239
    0
                    table,
    240
    0
                },
    241
            }
    242
    0
        }
    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
    0
        pub fn to_quoted_string(&self) -> String {
    259
    0
            match self {
    260
    0
                TableReference::Bare { table } => quote_identifier(table).to_string(),
    261
    0
                TableReference::Partial { schema, table } => {
    262
    0
                    format!("{}.{}", quote_identifier(schema), quote_identifier(table))
    263
                }
    264
                TableReference::Full {
    265
    0
                    catalog,
    266
    0
                    schema,
    267
    0
                    table,
    268
    0
                } => format!(
    269
    0
                    "{}.{}.{}",
    270
    0
                    quote_identifier(catalog),
    271
    0
                    quote_identifier(schema),
    272
    0
                    quote_identifier(table)
    273
                ),
    274
            }
    275
    0
        }
    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
    0
        pub fn parse_str(s: &str) -> Self {
    281
    0
            Self::parse_str_normalized(s, false)
    282
    0
        }
    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
    0
        pub fn parse_str_normalized(s: &str, ignore_case: bool) -> Self {
    288
    0
            let table_parts = parse_identifiers_normalized(s, ignore_case);
    289
    290
    0
            Self::from_vec(table_parts).unwrap_or_else(|| Self::Bare { table: s.into() })
    291
    0
        }
    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
    0
        fn from_vec(mut parts: Vec<String>) -> Option<Self> {
    299
    0
            match parts.len() {
    300
                1 => Some(Self::Bare {
    301
    0
                    table: parts.pop()?.into(),
    302
                }),
    303
                2 => Some(Self::Partial {
    304
    0
                    table: parts.pop()?.into(),
    305
    0
                    schema: parts.pop()?.into(),
    306
                }),
    307
                3 => Some(Self::Full {
    308
    0
                    table: parts.pop()?.into(),
    309
    0
                    schema: parts.pop()?.into(),
    310
    0
                    catalog: parts.pop()?.into(),
    311
                }),
    312
    0
                _ => None,
    313
            }
    314
    0
        }
    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
    0
        pub fn to_vec(&self) -> Vec<String> {
    322
    0
            match self {
    323
    0
                TableReference::Bare { table } => vec![table.to_string()],
    324
    0
                TableReference::Partial { schema, table } => {
    325
    0
                    vec![schema.to_string(), table.to_string()]
    326
                }
    327
                TableReference::Full {
    328
    0
                    catalog,
    329
    0
                    schema,
    330
    0
                    table,
    331
    0
                } => vec![catalog.to_string(), schema.to_string(), table.to_string()],
    332
            }
    333
    0
        }
    334
    }
    335
    336
    /// Parse a string into a TableReference, normalizing where appropriate
    337
    ///
    338
    /// See full details on [`TableReference::parse_str`]
    339
    impl<'a> From<&'a str> for TableReference {
    340
    0
        fn from(s: &'a str) -> Self {
    341
    0
            Self::parse_str(s)
    342
    0
        }
    343
    }
    344
    345
    impl<'a> From<&'a String> for TableReference {
    346
    0
        fn from(s: &'a String) -> Self {
    347
    0
            Self::parse_str(s)
    348
    0
        }
    349
    }
    350
    351
    impl From<String> for TableReference {
    352
    0
        fn from(s: String) -> Self {
    353
    0
            Self::parse_str(&s)
    354
    0
        }
    355
    }
    356
    357
    impl From<ResolvedTableReference> for TableReference {
    358
    0
        fn from(resolved: ResolvedTableReference) -> Self {
    359
    0
            Self::Full {
    360
    0
                catalog: resolved.catalog,
    361
    0
                schema: resolved.schema,
    362
    0
                table: resolved.table,
    363
    0
            }
    364
    0
        }
    365
    }
    366
    367
    #[cfg(test)]
    368
    mod 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
    }