## 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/expr/src/logical_plan/ddl.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::{Expr, LogicalPlan, SortExpr, Volatility};
    19
    use std::cmp::Ordering;
    20
    use std::collections::HashMap;
    21
    use std::sync::Arc;
    22
    use std::{
    23
        fmt::{self, Display},
    24
        hash::{Hash, Hasher},
    25
    };
    26
    27
    #[cfg(not(feature = "sql"))]
    28
    use crate::expr::Ident;
    29
    use crate::expr::Sort;
    30
    use arrow::datatypes::DataType;
    31
    use datafusion_common::tree_node::{Transformed, TreeNodeContainer, TreeNodeRecursion};
    32
    use datafusion_common::{
    33
        Constraints, DFSchemaRef, Result, SchemaReference, TableReference,
    34
    };
    35
    #[cfg(feature = "sql")]
    36
    use sqlparser::ast::Ident;
    37
    38
    /// Various types of DDL  (CREATE / DROP) catalog manipulation
    39
    #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Hash)]
    40
    pub enum DdlStatement {
    41
        /// Creates an external table.
    42
        CreateExternalTable(CreateExternalTable),
    43
        /// Creates an in memory table.
    44
        CreateMemoryTable(CreateMemoryTable),
    45
        /// Creates a new view.
    46
        CreateView(CreateView),
    47
        /// Creates a new catalog schema.
    48
        CreateCatalogSchema(CreateCatalogSchema),
    49
        /// Creates a new catalog (aka "Database").
    50
        CreateCatalog(CreateCatalog),
    51
        /// Creates a new index.
    52
        CreateIndex(CreateIndex),
    53
        /// Drops a table.
    54
        DropTable(DropTable),
    55
        /// Drops a view.
    56
        DropView(DropView),
    57
        /// Drops a catalog schema
    58
        DropCatalogSchema(DropCatalogSchema),
    59
        /// Create function statement
    60
        CreateFunction(CreateFunction),
    61
        /// Drop function statement
    62
        DropFunction(DropFunction),
    63
    }
    64
    65
    impl DdlStatement {
    66
        /// Get a reference to the logical plan's schema
    67
    0
        pub fn schema(&self) -> &DFSchemaRef {
    68
    0
            match self {
    69
    0
                DdlStatement::CreateExternalTable(CreateExternalTable { schema, .. }) => {
    70
    0
                    schema
    71
                }
    72
    0
                DdlStatement::CreateMemoryTable(CreateMemoryTable { input, .. })
    73
    0
                | DdlStatement::CreateView(CreateView { input, .. }) => input.schema(),
    74
    0
                DdlStatement::CreateCatalogSchema(CreateCatalogSchema { schema, .. }) => {
    75
    0
                    schema
    76
                }
    77
    0
                DdlStatement::CreateCatalog(CreateCatalog { schema, .. }) => schema,
    78
    0
                DdlStatement::CreateIndex(CreateIndex { schema, .. }) => schema,
    79
    0
                DdlStatement::DropTable(DropTable { schema, .. }) => schema,
    80
    0
                DdlStatement::DropView(DropView { schema, .. }) => schema,
    81
    0
                DdlStatement::DropCatalogSchema(DropCatalogSchema { schema, .. }) => schema,
    82
    0
                DdlStatement::CreateFunction(CreateFunction { schema, .. }) => schema,
    83
    0
                DdlStatement::DropFunction(DropFunction { schema, .. }) => schema,
    84
            }
    85
    0
        }
    86
    87
        /// Return a descriptive string describing the type of this
    88
        /// [`DdlStatement`]
    89
    0
        pub fn name(&self) -> &str {
    90
    0
            match self {
    91
    0
                DdlStatement::CreateExternalTable(_) => "CreateExternalTable",
    92
    0
                DdlStatement::CreateMemoryTable(_) => "CreateMemoryTable",
    93
    0
                DdlStatement::CreateView(_) => "CreateView",
    94
    0
                DdlStatement::CreateCatalogSchema(_) => "CreateCatalogSchema",
    95
    0
                DdlStatement::CreateCatalog(_) => "CreateCatalog",
    96
    0
                DdlStatement::CreateIndex(_) => "CreateIndex",
    97
    0
                DdlStatement::DropTable(_) => "DropTable",
    98
    0
                DdlStatement::DropView(_) => "DropView",
    99
    0
                DdlStatement::DropCatalogSchema(_) => "DropCatalogSchema",
    100
    0
                DdlStatement::CreateFunction(_) => "CreateFunction",
    101
    0
                DdlStatement::DropFunction(_) => "DropFunction",
    102
            }
    103
    0
        }
    104
    105
        /// Return all inputs for this plan
    106
    0
        pub fn inputs(&self) -> Vec<&LogicalPlan> {
    107
    0
            match self {
    108
    0
                DdlStatement::CreateExternalTable(_) => vec![],
    109
    0
                DdlStatement::CreateCatalogSchema(_) => vec![],
    110
    0
                DdlStatement::CreateCatalog(_) => vec![],
    111
    0
                DdlStatement::CreateMemoryTable(CreateMemoryTable { input, .. }) => {
    112
    0
                    vec![input]
    113
                }
    114
    0
                DdlStatement::CreateView(CreateView { input, .. }) => vec![input],
    115
    0
                DdlStatement::CreateIndex(_) => vec![],
    116
    0
                DdlStatement::DropTable(_) => vec![],
    117
    0
                DdlStatement::DropView(_) => vec![],
    118
    0
                DdlStatement::DropCatalogSchema(_) => vec![],
    119
    0
                DdlStatement::CreateFunction(_) => vec![],
    120
    0
                DdlStatement::DropFunction(_) => vec![],
    121
            }
    122
    0
        }
    123
    124
        /// Return a `format`able structure with the a human readable
    125
        /// description of this LogicalPlan node per node, not including
    126
        /// children.
    127
        ///
    128
        /// See [crate::LogicalPlan::display] for an example
    129
    0
        pub fn display(&self) -> impl Display + '_ {
    130
            struct Wrapper<'a>(&'a DdlStatement);
    131
            impl Display for Wrapper<'_> {
    132
    0
                fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
    133
    0
                    match self.0 {
    134
                        DdlStatement::CreateExternalTable(CreateExternalTable {
    135
    0
                            ref name,
    136
    0
                            constraints,
    137
                            ..
    138
                        }) => {
    139
    0
                            if constraints.is_empty() {
    140
    0
                                write!(f, "CreateExternalTable: {name:?}")
    141
                            } else {
    142
    0
                                write!(f, "CreateExternalTable: {name:?} {constraints}")
    143
                            }
    144
                        }
    145
                        DdlStatement::CreateMemoryTable(CreateMemoryTable {
    146
    0
                            name,
    147
    0
                            constraints,
    148
                            ..
    149
                        }) => {
    150
    0
                            if constraints.is_empty() {
    151
    0
                                write!(f, "CreateMemoryTable: {name:?}")
    152
                            } else {
    153
    0
                                write!(f, "CreateMemoryTable: {name:?} {constraints}")
    154
                            }
    155
                        }
    156
    0
                        DdlStatement::CreateView(CreateView { name, .. }) => {
    157
    0
                            write!(f, "CreateView: {name:?}")
    158
                        }
    159
                        DdlStatement::CreateCatalogSchema(CreateCatalogSchema {
    160
    0
                            schema_name,
    161
                            ..
    162
                        }) => {
    163
    0
                            write!(f, "CreateCatalogSchema: {schema_name:?}")
    164
                        }
    165
                        DdlStatement::CreateCatalog(CreateCatalog {
    166
    0
                            catalog_name, ..
    167
                        }) => {
    168
    0
                            write!(f, "CreateCatalog: {catalog_name:?}")
    169
                        }
    170
    0
                        DdlStatement::CreateIndex(CreateIndex { name, .. }) => {
    171
    0
                            write!(f, "CreateIndex: {name:?}")
    172
                        }
    173
                        DdlStatement::DropTable(DropTable {
    174
    0
                            name, if_exists, ..
    175
                        }) => {
    176
    0
                            write!(f, "DropTable: {name:?} if not exist:={if_exists}")
    177
                        }
    178
                        DdlStatement::DropView(DropView {
    179
    0
                            name, if_exists, ..
    180
                        }) => {
    181
    0
                            write!(f, "DropView: {name:?} if not exist:={if_exists}")
    182
                        }
    183
                        DdlStatement::DropCatalogSchema(DropCatalogSchema {
    184
    0
                            name,
    185
    0
                            if_exists,
    186
    0
                            cascade,
    187
                            ..
    188
                        }) => {
    189
    0
                            write!(f, "DropCatalogSchema: {name:?} if not exist:={if_exists} cascade:={cascade}")
    190
                        }
    191
    0
                        DdlStatement::CreateFunction(CreateFunction { name, .. }) => {
    192
    0
                            write!(f, "CreateFunction: name {name:?}")
    193
                        }
    194
    0
                        DdlStatement::DropFunction(DropFunction { name, .. }) => {
    195
    0
                            write!(f, "DropFunction: name {name:?}")
    196
                        }
    197
                    }
    198
    0
                }
    199
            }
    200
    0
            Wrapper(self)
    201
    0
        }
    202
    }
    203
    204
    /// Creates an external table.
    205
    #[derive(Debug, Clone, PartialEq, Eq)]
    206
    pub struct CreateExternalTable {
    207
        /// The table schema
    208
        pub schema: DFSchemaRef,
    209
        /// The table name
    210
        pub name: TableReference,
    211
        /// The physical location
    212
        pub location: String,
    213
        /// The file type of physical file
    214
        pub file_type: String,
    215
        /// Partition Columns
    216
        pub table_partition_cols: Vec<String>,
    217
        /// Option to not error if table already exists
    218
        pub if_not_exists: bool,
    219
        /// Option to replace table content if table already exists
    220
        pub or_replace: bool,
    221
        /// Whether the table is a temporary table
    222
        pub temporary: bool,
    223
        /// SQL used to create the table, if available
    224
        pub definition: Option<String>,
    225
        /// Order expressions supplied by user
    226
        pub order_exprs: Vec<Vec<Sort>>,
    227
        /// Whether the table is an infinite streams
    228
        pub unbounded: bool,
    229
        /// Table(provider) specific options
    230
        pub options: HashMap<String, String>,
    231
        /// The list of constraints in the schema, such as primary key, unique, etc.
    232
        pub constraints: Constraints,
    233
        /// Default values for columns
    234
        pub column_defaults: HashMap<String, Expr>,
    235
    }
    236
    237
    // Hashing refers to a subset of fields considered in PartialEq.
    238
    impl Hash for CreateExternalTable {
    239
    0
        fn hash<H: Hasher>(&self, state: &mut H) {
    240
    0
            self.schema.hash(state);
    241
    0
            self.name.hash(state);
    242
    0
            self.location.hash(state);
    243
    0
            self.file_type.hash(state);
    244
    0
            self.table_partition_cols.hash(state);
    245
    0
            self.if_not_exists.hash(state);
    246
    0
            self.definition.hash(state);
    247
    0
            self.order_exprs.hash(state);
    248
    0
            self.unbounded.hash(state);
    249
    0
            self.options.len().hash(state); // HashMap is not hashable
    250
    0
        }
    251
    }
    252
    253
    // Manual implementation needed because of `schema`, `options`, and `column_defaults` fields.
    254
    // Comparison excludes these fields.
    255
    impl PartialOrd for CreateExternalTable {
    256
    0
        fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
    257
            #[derive(PartialEq, PartialOrd)]
    258
            struct ComparableCreateExternalTable<'a> {
    259
                /// The table name
    260
                pub name: &'a TableReference,
    261
                /// The physical location
    262
                pub location: &'a String,
    263
                /// The file type of physical file
    264
                pub file_type: &'a String,
    265
                /// Partition Columns
    266
                pub table_partition_cols: &'a Vec<String>,
    267
                /// Option to not error if table already exists
    268
                pub if_not_exists: &'a bool,
    269
                /// SQL used to create the table, if available
    270
                pub definition: &'a Option<String>,
    271
                /// Order expressions supplied by user
    272
                pub order_exprs: &'a Vec<Vec<Sort>>,
    273
                /// Whether the table is an infinite streams
    274
                pub unbounded: &'a bool,
    275
                /// The list of constraints in the schema, such as primary key, unique, etc.
    276
                pub constraints: &'a Constraints,
    277
            }
    278
    0
            let comparable_self = ComparableCreateExternalTable {
    279
    0
                name: &self.name,
    280
    0
                location: &self.location,
    281
    0
                file_type: &self.file_type,
    282
    0
                table_partition_cols: &self.table_partition_cols,
    283
    0
                if_not_exists: &self.if_not_exists,
    284
    0
                definition: &self.definition,
    285
    0
                order_exprs: &self.order_exprs,
    286
    0
                unbounded: &self.unbounded,
    287
    0
                constraints: &self.constraints,
    288
    0
            };
    289
    0
            let comparable_other = ComparableCreateExternalTable {
    290
    0
                name: &other.name,
    291
    0
                location: &other.location,
    292
    0
                file_type: &other.file_type,
    293
    0
                table_partition_cols: &other.table_partition_cols,
    294
    0
                if_not_exists: &other.if_not_exists,
    295
    0
                definition: &other.definition,
    296
    0
                order_exprs: &other.order_exprs,
    297
    0
                unbounded: &other.unbounded,
    298
    0
                constraints: &other.constraints,
    299
    0
            };
    300
    0
            comparable_self
    301
    0
                .partial_cmp(&comparable_other)
    302
                // TODO (https://github.com/apache/datafusion/issues/17477) avoid recomparing all fields
    303
    0
                .filter(|cmp| *cmp != Ordering::Equal || self == other)
    304
    0
        }
    305
    }
    306
    307
    /// Creates an in memory table.
    308
    #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Hash)]
    309
    pub struct CreateMemoryTable {
    310
        /// The table name
    311
        pub name: TableReference,
    312
        /// The list of constraints in the schema, such as primary key, unique, etc.
    313
        pub constraints: Constraints,
    314
        /// The logical plan
    315
        pub input: Arc<LogicalPlan>,
    316
        /// Option to not error if table already exists
    317
        pub if_not_exists: bool,
    318
        /// Option to replace table content if table already exists
    319
        pub or_replace: bool,
    320
        /// Default values for columns
    321
        pub column_defaults: Vec<(String, Expr)>,
    322
        /// Whether the table is `TableType::Temporary`
    323
        pub temporary: bool,
    324
    }
    325
    326
    /// Creates a view.
    327
    #[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Hash)]
    328
    pub struct CreateView {
    329
        /// The table name
    330
        pub name: TableReference,
    331
        /// The logical plan
    332
        pub input: Arc<LogicalPlan>,
    333
        /// Option to not error if table already exists
    334
        pub or_replace: bool,
    335
        /// SQL used to create the view, if available
    336
        pub definition: Option<String>,
    337
        /// Whether the view is ephemeral
    338
        pub temporary: bool,
    339
    }
    340
    341
    /// Creates a catalog (aka "Database").
    342
    #[derive(Debug, Clone, PartialEq, Eq, Hash)]
    343
    pub struct CreateCatalog {
    344
        /// The catalog name
    345
        pub catalog_name: String,
    346
        /// Do nothing (except issuing a notice) if a schema with the same name already exists
    347
        pub if_not_exists: bool,
    348
        /// Empty schema
    349
        pub schema: DFSchemaRef,
    350
    }
    351
    352
    // Manual implementation needed because of `schema` field. Comparison excludes this field.
    353
    impl PartialOrd for CreateCatalog {
    354
    0
        fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
    355
    0
            match self.catalog_name.partial_cmp(&other.catalog_name) {
    356
    0
                Some(Ordering::Equal) => self.if_not_exists.partial_cmp(&other.if_not_exists),
    357
    0
                cmp => cmp,
    358
            }
    359
            // TODO (https://github.com/apache/datafusion/issues/17477) avoid recomparing all fields
    360
    0
            .filter(|cmp| *cmp != Ordering::Equal || self == other)
    361
    0
        }
    362
    }
    363
    364
    /// Creates a schema.
    365
    #[derive(Debug, Clone, PartialEq, Eq, Hash)]
    366
    pub struct CreateCatalogSchema {
    367
        /// The table schema
    368
        pub schema_name: String,
    369
        /// Do nothing (except issuing a notice) if a schema with the same name already exists
    370
        pub if_not_exists: bool,
    371
        /// Empty schema
    372
        pub schema: DFSchemaRef,
    373
    }
    374
    375
    // Manual implementation needed because of `schema` field. Comparison excludes this field.
    376
    impl PartialOrd for CreateCatalogSchema {
    377
    0
        fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
    378
    0
            match self.schema_name.partial_cmp(&other.schema_name) {
    379
    0
                Some(Ordering::Equal) => self.if_not_exists.partial_cmp(&other.if_not_exists),
    380
    0
                cmp => cmp,
    381
            }
    382
            // TODO (https://github.com/apache/datafusion/issues/17477) avoid recomparing all fields
    383
    0
            .filter(|cmp| *cmp != Ordering::Equal || self == other)
    384
    0
        }
    385
    }
    386
    387
    /// Drops a table.
    388
    #[derive(Debug, Clone, PartialEq, Eq, Hash)]
    389
    pub struct DropTable {
    390
        /// The table name
    391
        pub name: TableReference,
    392
        /// If the table exists
    393
        pub if_exists: bool,
    394
        /// Dummy schema
    395
        pub schema: DFSchemaRef,
    396
    }
    397
    398
    // Manual implementation needed because of `schema` field. Comparison excludes this field.
    399
    impl PartialOrd for DropTable {
    400
    0
        fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
    401
    0
            match self.name.partial_cmp(&other.name) {
    402
    0
                Some(Ordering::Equal) => self.if_exists.partial_cmp(&other.if_exists),
    403
    0
                cmp => cmp,
    404
            }
    405
            // TODO (https://github.com/apache/datafusion/issues/17477) avoid recomparing all fields
    406
    0
            .filter(|cmp| *cmp != Ordering::Equal || self == other)
    407
    0
        }
    408
    }
    409
    410
    /// Drops a view.
    411
    #[derive(Debug, Clone, PartialEq, Eq, Hash)]
    412
    pub struct DropView {
    413
        /// The view name
    414
        pub name: TableReference,
    415
        /// If the view exists
    416
        pub if_exists: bool,
    417
        /// Dummy schema
    418
        pub schema: DFSchemaRef,
    419
    }
    420
    421
    // Manual implementation needed because of `schema` field. Comparison excludes this field.
    422
    impl PartialOrd for DropView {
    423
    0
        fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
    424
    0
            match self.name.partial_cmp(&other.name) {
    425
    0
                Some(Ordering::Equal) => self.if_exists.partial_cmp(&other.if_exists),
    426
    0
                cmp => cmp,
    427
            }
    428
            // TODO (https://github.com/apache/datafusion/issues/17477) avoid recomparing all fields
    429
    0
            .filter(|cmp| *cmp != Ordering::Equal || self == other)
    430
    0
        }
    431
    }
    432
    433
    /// Drops a schema
    434
    #[derive(Debug, Clone, PartialEq, Eq, Hash)]
    435
    pub struct DropCatalogSchema {
    436
        /// The schema name
    437
        pub name: SchemaReference,
    438
        /// If the schema exists
    439
        pub if_exists: bool,
    440
        /// Whether drop should cascade
    441
        pub cascade: bool,
    442
        /// Dummy schema
    443
        pub schema: DFSchemaRef,
    444
    }
    445
    446
    // Manual implementation needed because of `schema` field. Comparison excludes this field.
    447
    impl PartialOrd for DropCatalogSchema {
    448
    0
        fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
    449
    0
            match self.name.partial_cmp(&other.name) {
    450
    0
                Some(Ordering::Equal) => match self.if_exists.partial_cmp(&other.if_exists) {
    451
    0
                    Some(Ordering::Equal) => self.cascade.partial_cmp(&other.cascade),
    452
    0
                    cmp => cmp,
    453
                },
    454
    0
                cmp => cmp,
    455
            }
    456
            // TODO (https://github.com/apache/datafusion/issues/17477) avoid recomparing all fields
    457
    0
            .filter(|cmp| *cmp != Ordering::Equal || self == other)
    458
    0
        }
    459
    }
    460
    461
    /// Arguments passed to the `CREATE FUNCTION` statement
    462
    ///
    463
    /// These statements are turned into executable functions using [`FunctionFactory`]
    464
    ///
    465
    /// # Notes
    466
    ///
    467
    /// This structure purposely mirrors the structure in sqlparser's
    468
    /// [`sqlparser::ast::Statement::CreateFunction`], but does not use it directly
    469
    /// to avoid a dependency on sqlparser in the core crate.
    470
    ///
    471
    ///
    472
    /// [`FunctionFactory`]: https://docs.rs/datafusion/latest/datafusion/execution/context/trait.FunctionFactory.html
    473
    #[derive(Clone, PartialEq, Eq, Hash, Debug)]
    474
    pub struct CreateFunction {
    475
        pub or_replace: bool,
    476
        pub temporary: bool,
    477
        pub name: String,
    478
        pub args: Option<Vec<OperateFunctionArg>>,
    479
        pub return_type: Option<DataType>,
    480
        pub params: CreateFunctionBody,
    481
        /// Dummy schema
    482
        pub schema: DFSchemaRef,
    483
    }
    484
    485
    // Manual implementation needed because of `schema` field. Comparison excludes this field.
    486
    impl PartialOrd for CreateFunction {
    487
    0
        fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
    488
            #[derive(PartialEq, PartialOrd)]
    489
            struct ComparableCreateFunction<'a> {
    490
                pub or_replace: &'a bool,
    491
                pub temporary: &'a bool,
    492
                pub name: &'a String,
    493
                pub args: &'a Option<Vec<OperateFunctionArg>>,
    494
                pub return_type: &'a Option<DataType>,
    495
                pub params: &'a CreateFunctionBody,
    496
            }
    497
    0
            let comparable_self = ComparableCreateFunction {
    498
    0
                or_replace: &self.or_replace,
    499
    0
                temporary: &self.temporary,
    500
    0
                name: &self.name,
    501
    0
                args: &self.args,
    502
    0
                return_type: &self.return_type,
    503
    0
                params: &self.params,
    504
    0
            };
    505
    0
            let comparable_other = ComparableCreateFunction {
    506
    0
                or_replace: &other.or_replace,
    507
    0
                temporary: &other.temporary,
    508
    0
                name: &other.name,
    509
    0
                args: &other.args,
    510
    0
                return_type: &other.return_type,
    511
    0
                params: &other.params,
    512
    0
            };
    513
    0
            comparable_self
    514
    0
                .partial_cmp(&comparable_other)
    515
                // TODO (https://github.com/apache/datafusion/issues/17477) avoid recomparing all fields
    516
    0
                .filter(|cmp| *cmp != Ordering::Equal || self == other)
    517
    0
        }
    518
    }
    519
    520
    /// Part of the `CREATE FUNCTION` statement
    521
    ///
    522
    /// See [`CreateFunction`] for details
    523
    #[derive(Clone, PartialEq, Eq, PartialOrd, Hash, Debug)]
    524
    pub struct OperateFunctionArg {
    525
        // TODO: figure out how to support mode
    526
        // pub mode: Option<ArgMode>,
    527
        pub name: Option<Ident>,
    528
        pub data_type: DataType,
    529
        pub default_expr: Option<Expr>,
    530
    }
    531
    532
    impl<'a> TreeNodeContainer<'a, Expr> for OperateFunctionArg {
    533
    0
        fn apply_elements<F: FnMut(&'a Expr) -> Result<TreeNodeRecursion>>(
    534
    0
            &'a self,
    535
    0
            f: F,
    536
    0
        ) -> Result<TreeNodeRecursion> {
    537
    0
            self.default_expr.apply_elements(f)
    538
    0
        }
    539
    540
    0
        fn map_elements<F: FnMut(Expr) -> Result<Transformed<Expr>>>(
    541
    0
            self,
    542
    0
            f: F,
    543
    0
        ) -> Result<Transformed<Self>> {
    544
    0
            self.default_expr.map_elements(f)?.map_data(|default_expr| {
    545
    0
                Ok(Self {
    546
    0
                    default_expr,
    547
    0
                    ..self
    548
    0
                })
    549
    0
            })
    550
    0
        }
    551
    }
    552
    553
    /// Part of the `CREATE FUNCTION` statement
    554
    ///
    555
    /// See [`CreateFunction`] for details
    556
    #[derive(Clone, PartialEq, Eq, PartialOrd, Hash, Debug)]
    557
    pub struct CreateFunctionBody {
    558
        /// LANGUAGE lang_name
    559
        pub language: Option<Ident>,
    560
        /// IMMUTABLE | STABLE | VOLATILE
    561
        pub behavior: Option<Volatility>,
    562
        /// RETURN or AS function body
    563
        pub function_body: Option<Expr>,
    564
    }
    565
    566
    impl<'a> TreeNodeContainer<'a, Expr> for CreateFunctionBody {
    567
    0
        fn apply_elements<F: FnMut(&'a Expr) -> Result<TreeNodeRecursion>>(
    568
    0
            &'a self,
    569
    0
            f: F,
    570
    0
        ) -> Result<TreeNodeRecursion> {
    571
    0
            self.function_body.apply_elements(f)
    572
    0
        }
    573
    574
    0
        fn map_elements<F: FnMut(Expr) -> Result<Transformed<Expr>>>(
    575
    0
            self,
    576
    0
            f: F,
    577
    0
        ) -> Result<Transformed<Self>> {
    578
    0
            self.function_body
    579
    0
                .map_elements(f)?
    580
    0
                .map_data(|function_body| {
    581
    0
                    Ok(Self {
    582
    0
                        function_body,
    583
    0
                        ..self
    584
    0
                    })
    585
    0
                })
    586
    0
        }
    587
    }
    588
    589
    #[derive(Clone, PartialEq, Eq, Hash, Debug)]
    590
    pub struct DropFunction {
    591
        pub name: String,
    592
        pub if_exists: bool,
    593
        pub schema: DFSchemaRef,
    594
    }
    595
    596
    impl PartialOrd for DropFunction {
    597
    0
        fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
    598
    0
            match self.name.partial_cmp(&other.name) {
    599
    0
                Some(Ordering::Equal) => self.if_exists.partial_cmp(&other.if_exists),
    600
    0
                cmp => cmp,
    601
            }
    602
            // TODO (https://github.com/apache/datafusion/issues/17477) avoid recomparing all fields
    603
    0
            .filter(|cmp| *cmp != Ordering::Equal || self == other)
    604
    0
        }
    605
    }
    606
    607
    #[derive(Clone, PartialEq, Eq, Hash, Debug)]
    608
    pub struct CreateIndex {
    609
        pub name: Option<String>,
    610
        pub table: TableReference,
    611
        pub using: Option<String>,
    612
        pub columns: Vec<SortExpr>,
    613
        pub unique: bool,
    614
        pub if_not_exists: bool,
    615
        pub schema: DFSchemaRef,
    616
    }
    617
    618
    // Manual implementation needed because of `schema` field. Comparison excludes this field.
    619
    impl PartialOrd for CreateIndex {
    620
    0
        fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
    621
            #[derive(PartialEq, PartialOrd)]
    622
            struct ComparableCreateIndex<'a> {
    623
                pub name: &'a Option<String>,
    624
                pub table: &'a TableReference,
    625
                pub using: &'a Option<String>,
    626
                pub columns: &'a Vec<SortExpr>,
    627
                pub unique: &'a bool,
    628
                pub if_not_exists: &'a bool,
    629
            }
    630
    0
            let comparable_self = ComparableCreateIndex {
    631
    0
                name: &self.name,
    632
    0
                table: &self.table,
    633
    0
                using: &self.using,
    634
    0
                columns: &self.columns,
    635
    0
                unique: &self.unique,
    636
    0
                if_not_exists: &self.if_not_exists,
    637
    0
            };
    638
    0
            let comparable_other = ComparableCreateIndex {
    639
    0
                name: &other.name,
    640
    0
                table: &other.table,
    641
    0
                using: &other.using,
    642
    0
                columns: &other.columns,
    643
    0
                unique: &other.unique,
    644
    0
                if_not_exists: &other.if_not_exists,
    645
    0
            };
    646
    0
            comparable_self
    647
    0
                .partial_cmp(&comparable_other)
    648
                // TODO (https://github.com/apache/datafusion/issues/17477) avoid recomparing all fields
    649
    0
                .filter(|cmp| *cmp != Ordering::Equal || self == other)
    650
    0
        }
    651
    }
    652
    653
    #[cfg(test)]
    654
    mod test {
    655
        use crate::{CreateCatalog, DdlStatement, DropView};
    656
        use datafusion_common::{DFSchema, DFSchemaRef, TableReference};
    657
        use std::cmp::Ordering;
    658
    659
        #[test]
    660
        fn test_partial_ord() {
    661
            let catalog = DdlStatement::CreateCatalog(CreateCatalog {
    662
                catalog_name: "name".to_string(),
    663
                if_not_exists: false,
    664
                schema: DFSchemaRef::new(DFSchema::empty()),
    665
            });
    666
            let catalog_2 = DdlStatement::CreateCatalog(CreateCatalog {
    667
                catalog_name: "name".to_string(),
    668
                if_not_exists: true,
    669
                schema: DFSchemaRef::new(DFSchema::empty()),
    670
            });
    671
    672
            assert_eq!(catalog.partial_cmp(&catalog_2), Some(Ordering::Less));
    673
    674
            let drop_view = DdlStatement::DropView(DropView {
    675
                name: TableReference::from("table"),
    676
                if_exists: false,
    677
                schema: DFSchemaRef::new(DFSchema::empty()),
    678
            });
    679
    680
            assert_eq!(drop_view.partial_cmp(&catalog), Some(Ordering::Greater));
    681
        }
    682
    }