datafusion_expr/logical_plan/
ddl.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::{Expr, LogicalPlan, SortExpr, Volatility};
19use std::cmp::Ordering;
20use std::collections::HashMap;
21use std::sync::Arc;
22use std::{
23    fmt::{self, Display},
24    hash::{Hash, Hasher},
25};
26
27#[cfg(not(feature = "sql"))]
28use crate::expr::Ident;
29use crate::expr::Sort;
30use arrow::datatypes::DataType;
31use datafusion_common::tree_node::{Transformed, TreeNodeContainer, TreeNodeRecursion};
32use datafusion_common::{
33    Constraints, DFSchemaRef, Result, SchemaReference, TableReference,
34};
35#[cfg(feature = "sql")]
36use sqlparser::ast::Ident;
37
38/// Various types of DDL  (CREATE / DROP) catalog manipulation
39#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Hash)]
40pub 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
65impl DdlStatement {
66    /// Get a reference to the logical plan's schema
67    pub fn schema(&self) -> &DFSchemaRef {
68        match self {
69            DdlStatement::CreateExternalTable(CreateExternalTable { schema, .. }) => {
70                schema
71            }
72            DdlStatement::CreateMemoryTable(CreateMemoryTable { input, .. })
73            | DdlStatement::CreateView(CreateView { input, .. }) => input.schema(),
74            DdlStatement::CreateCatalogSchema(CreateCatalogSchema { schema, .. }) => {
75                schema
76            }
77            DdlStatement::CreateCatalog(CreateCatalog { schema, .. }) => schema,
78            DdlStatement::CreateIndex(CreateIndex { schema, .. }) => schema,
79            DdlStatement::DropTable(DropTable { schema, .. }) => schema,
80            DdlStatement::DropView(DropView { schema, .. }) => schema,
81            DdlStatement::DropCatalogSchema(DropCatalogSchema { schema, .. }) => schema,
82            DdlStatement::CreateFunction(CreateFunction { schema, .. }) => schema,
83            DdlStatement::DropFunction(DropFunction { schema, .. }) => schema,
84        }
85    }
86
87    /// Return a descriptive string describing the type of this
88    /// [`DdlStatement`]
89    pub fn name(&self) -> &str {
90        match self {
91            DdlStatement::CreateExternalTable(_) => "CreateExternalTable",
92            DdlStatement::CreateMemoryTable(_) => "CreateMemoryTable",
93            DdlStatement::CreateView(_) => "CreateView",
94            DdlStatement::CreateCatalogSchema(_) => "CreateCatalogSchema",
95            DdlStatement::CreateCatalog(_) => "CreateCatalog",
96            DdlStatement::CreateIndex(_) => "CreateIndex",
97            DdlStatement::DropTable(_) => "DropTable",
98            DdlStatement::DropView(_) => "DropView",
99            DdlStatement::DropCatalogSchema(_) => "DropCatalogSchema",
100            DdlStatement::CreateFunction(_) => "CreateFunction",
101            DdlStatement::DropFunction(_) => "DropFunction",
102        }
103    }
104
105    /// Return all inputs for this plan
106    pub fn inputs(&self) -> Vec<&LogicalPlan> {
107        match self {
108            DdlStatement::CreateExternalTable(_) => vec![],
109            DdlStatement::CreateCatalogSchema(_) => vec![],
110            DdlStatement::CreateCatalog(_) => vec![],
111            DdlStatement::CreateMemoryTable(CreateMemoryTable { input, .. }) => {
112                vec![input]
113            }
114            DdlStatement::CreateView(CreateView { input, .. }) => vec![input],
115            DdlStatement::CreateIndex(_) => vec![],
116            DdlStatement::DropTable(_) => vec![],
117            DdlStatement::DropView(_) => vec![],
118            DdlStatement::DropCatalogSchema(_) => vec![],
119            DdlStatement::CreateFunction(_) => vec![],
120            DdlStatement::DropFunction(_) => vec![],
121        }
122    }
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    pub fn display(&self) -> impl Display + '_ {
130        struct Wrapper<'a>(&'a DdlStatement);
131        impl Display for Wrapper<'_> {
132            fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
133                match self.0 {
134                    DdlStatement::CreateExternalTable(CreateExternalTable {
135                        ref name,
136                        constraints,
137                        ..
138                    }) => {
139                        if constraints.is_empty() {
140                            write!(f, "CreateExternalTable: {name:?}")
141                        } else {
142                            write!(f, "CreateExternalTable: {name:?} {constraints}")
143                        }
144                    }
145                    DdlStatement::CreateMemoryTable(CreateMemoryTable {
146                        name,
147                        constraints,
148                        ..
149                    }) => {
150                        if constraints.is_empty() {
151                            write!(f, "CreateMemoryTable: {name:?}")
152                        } else {
153                            write!(f, "CreateMemoryTable: {name:?} {constraints}")
154                        }
155                    }
156                    DdlStatement::CreateView(CreateView { name, .. }) => {
157                        write!(f, "CreateView: {name:?}")
158                    }
159                    DdlStatement::CreateCatalogSchema(CreateCatalogSchema {
160                        schema_name,
161                        ..
162                    }) => {
163                        write!(f, "CreateCatalogSchema: {schema_name:?}")
164                    }
165                    DdlStatement::CreateCatalog(CreateCatalog {
166                        catalog_name, ..
167                    }) => {
168                        write!(f, "CreateCatalog: {catalog_name:?}")
169                    }
170                    DdlStatement::CreateIndex(CreateIndex { name, .. }) => {
171                        write!(f, "CreateIndex: {name:?}")
172                    }
173                    DdlStatement::DropTable(DropTable {
174                        name, if_exists, ..
175                    }) => {
176                        write!(f, "DropTable: {name:?} if not exist:={if_exists}")
177                    }
178                    DdlStatement::DropView(DropView {
179                        name, if_exists, ..
180                    }) => {
181                        write!(f, "DropView: {name:?} if not exist:={if_exists}")
182                    }
183                    DdlStatement::DropCatalogSchema(DropCatalogSchema {
184                        name,
185                        if_exists,
186                        cascade,
187                        ..
188                    }) => {
189                        write!(f, "DropCatalogSchema: {name:?} if not exist:={if_exists} cascade:={cascade}")
190                    }
191                    DdlStatement::CreateFunction(CreateFunction { name, .. }) => {
192                        write!(f, "CreateFunction: name {name:?}")
193                    }
194                    DdlStatement::DropFunction(DropFunction { name, .. }) => {
195                        write!(f, "DropFunction: name {name:?}")
196                    }
197                }
198            }
199        }
200        Wrapper(self)
201    }
202}
203
204/// Creates an external table.
205#[derive(Debug, Clone, PartialEq, Eq)]
206pub 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.
238impl Hash for CreateExternalTable {
239    fn hash<H: Hasher>(&self, state: &mut H) {
240        self.schema.hash(state);
241        self.name.hash(state);
242        self.location.hash(state);
243        self.file_type.hash(state);
244        self.table_partition_cols.hash(state);
245        self.if_not_exists.hash(state);
246        self.definition.hash(state);
247        self.order_exprs.hash(state);
248        self.unbounded.hash(state);
249        self.options.len().hash(state); // HashMap is not hashable
250    }
251}
252
253// Manual implementation needed because of `schema`, `options`, and `column_defaults` fields.
254// Comparison excludes these fields.
255impl PartialOrd for CreateExternalTable {
256    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        let comparable_self = ComparableCreateExternalTable {
279            name: &self.name,
280            location: &self.location,
281            file_type: &self.file_type,
282            table_partition_cols: &self.table_partition_cols,
283            if_not_exists: &self.if_not_exists,
284            definition: &self.definition,
285            order_exprs: &self.order_exprs,
286            unbounded: &self.unbounded,
287            constraints: &self.constraints,
288        };
289        let comparable_other = ComparableCreateExternalTable {
290            name: &other.name,
291            location: &other.location,
292            file_type: &other.file_type,
293            table_partition_cols: &other.table_partition_cols,
294            if_not_exists: &other.if_not_exists,
295            definition: &other.definition,
296            order_exprs: &other.order_exprs,
297            unbounded: &other.unbounded,
298            constraints: &other.constraints,
299        };
300        comparable_self
301            .partial_cmp(&comparable_other)
302            // TODO (https://github.com/apache/datafusion/issues/17477) avoid recomparing all fields
303            .filter(|cmp| *cmp != Ordering::Equal || self == other)
304    }
305}
306
307/// Creates an in memory table.
308#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Hash)]
309pub 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)]
328pub 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)]
343pub 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.
353impl PartialOrd for CreateCatalog {
354    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
355        match self.catalog_name.partial_cmp(&other.catalog_name) {
356            Some(Ordering::Equal) => self.if_not_exists.partial_cmp(&other.if_not_exists),
357            cmp => cmp,
358        }
359        // TODO (https://github.com/apache/datafusion/issues/17477) avoid recomparing all fields
360        .filter(|cmp| *cmp != Ordering::Equal || self == other)
361    }
362}
363
364/// Creates a schema.
365#[derive(Debug, Clone, PartialEq, Eq, Hash)]
366pub 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.
376impl PartialOrd for CreateCatalogSchema {
377    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
378        match self.schema_name.partial_cmp(&other.schema_name) {
379            Some(Ordering::Equal) => self.if_not_exists.partial_cmp(&other.if_not_exists),
380            cmp => cmp,
381        }
382        // TODO (https://github.com/apache/datafusion/issues/17477) avoid recomparing all fields
383        .filter(|cmp| *cmp != Ordering::Equal || self == other)
384    }
385}
386
387/// Drops a table.
388#[derive(Debug, Clone, PartialEq, Eq, Hash)]
389pub 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.
399impl PartialOrd for DropTable {
400    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
401        match self.name.partial_cmp(&other.name) {
402            Some(Ordering::Equal) => self.if_exists.partial_cmp(&other.if_exists),
403            cmp => cmp,
404        }
405        // TODO (https://github.com/apache/datafusion/issues/17477) avoid recomparing all fields
406        .filter(|cmp| *cmp != Ordering::Equal || self == other)
407    }
408}
409
410/// Drops a view.
411#[derive(Debug, Clone, PartialEq, Eq, Hash)]
412pub 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.
422impl PartialOrd for DropView {
423    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
424        match self.name.partial_cmp(&other.name) {
425            Some(Ordering::Equal) => self.if_exists.partial_cmp(&other.if_exists),
426            cmp => cmp,
427        }
428        // TODO (https://github.com/apache/datafusion/issues/17477) avoid recomparing all fields
429        .filter(|cmp| *cmp != Ordering::Equal || self == other)
430    }
431}
432
433/// Drops a schema
434#[derive(Debug, Clone, PartialEq, Eq, Hash)]
435pub 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.
447impl PartialOrd for DropCatalogSchema {
448    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
449        match self.name.partial_cmp(&other.name) {
450            Some(Ordering::Equal) => match self.if_exists.partial_cmp(&other.if_exists) {
451                Some(Ordering::Equal) => self.cascade.partial_cmp(&other.cascade),
452                cmp => cmp,
453            },
454            cmp => cmp,
455        }
456        // TODO (https://github.com/apache/datafusion/issues/17477) avoid recomparing all fields
457        .filter(|cmp| *cmp != Ordering::Equal || self == other)
458    }
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)]
474pub 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.
486impl PartialOrd for CreateFunction {
487    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        let comparable_self = ComparableCreateFunction {
498            or_replace: &self.or_replace,
499            temporary: &self.temporary,
500            name: &self.name,
501            args: &self.args,
502            return_type: &self.return_type,
503            params: &self.params,
504        };
505        let comparable_other = ComparableCreateFunction {
506            or_replace: &other.or_replace,
507            temporary: &other.temporary,
508            name: &other.name,
509            args: &other.args,
510            return_type: &other.return_type,
511            params: &other.params,
512        };
513        comparable_self
514            .partial_cmp(&comparable_other)
515            // TODO (https://github.com/apache/datafusion/issues/17477) avoid recomparing all fields
516            .filter(|cmp| *cmp != Ordering::Equal || self == other)
517    }
518}
519
520/// Part of the `CREATE FUNCTION` statement
521///
522/// See [`CreateFunction`] for details
523#[derive(Clone, PartialEq, Eq, PartialOrd, Hash, Debug)]
524pub 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
532impl<'a> TreeNodeContainer<'a, Expr> for OperateFunctionArg {
533    fn apply_elements<F: FnMut(&'a Expr) -> Result<TreeNodeRecursion>>(
534        &'a self,
535        f: F,
536    ) -> Result<TreeNodeRecursion> {
537        self.default_expr.apply_elements(f)
538    }
539
540    fn map_elements<F: FnMut(Expr) -> Result<Transformed<Expr>>>(
541        self,
542        f: F,
543    ) -> Result<Transformed<Self>> {
544        self.default_expr.map_elements(f)?.map_data(|default_expr| {
545            Ok(Self {
546                default_expr,
547                ..self
548            })
549        })
550    }
551}
552
553/// Part of the `CREATE FUNCTION` statement
554///
555/// See [`CreateFunction`] for details
556#[derive(Clone, PartialEq, Eq, PartialOrd, Hash, Debug)]
557pub 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
566impl<'a> TreeNodeContainer<'a, Expr> for CreateFunctionBody {
567    fn apply_elements<F: FnMut(&'a Expr) -> Result<TreeNodeRecursion>>(
568        &'a self,
569        f: F,
570    ) -> Result<TreeNodeRecursion> {
571        self.function_body.apply_elements(f)
572    }
573
574    fn map_elements<F: FnMut(Expr) -> Result<Transformed<Expr>>>(
575        self,
576        f: F,
577    ) -> Result<Transformed<Self>> {
578        self.function_body
579            .map_elements(f)?
580            .map_data(|function_body| {
581                Ok(Self {
582                    function_body,
583                    ..self
584                })
585            })
586    }
587}
588
589#[derive(Clone, PartialEq, Eq, Hash, Debug)]
590pub struct DropFunction {
591    pub name: String,
592    pub if_exists: bool,
593    pub schema: DFSchemaRef,
594}
595
596impl PartialOrd for DropFunction {
597    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
598        match self.name.partial_cmp(&other.name) {
599            Some(Ordering::Equal) => self.if_exists.partial_cmp(&other.if_exists),
600            cmp => cmp,
601        }
602        // TODO (https://github.com/apache/datafusion/issues/17477) avoid recomparing all fields
603        .filter(|cmp| *cmp != Ordering::Equal || self == other)
604    }
605}
606
607#[derive(Clone, PartialEq, Eq, Hash, Debug)]
608pub 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.
619impl PartialOrd for CreateIndex {
620    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        let comparable_self = ComparableCreateIndex {
631            name: &self.name,
632            table: &self.table,
633            using: &self.using,
634            columns: &self.columns,
635            unique: &self.unique,
636            if_not_exists: &self.if_not_exists,
637        };
638        let comparable_other = ComparableCreateIndex {
639            name: &other.name,
640            table: &other.table,
641            using: &other.using,
642            columns: &other.columns,
643            unique: &other.unique,
644            if_not_exists: &other.if_not_exists,
645        };
646        comparable_self
647            .partial_cmp(&comparable_other)
648            // TODO (https://github.com/apache/datafusion/issues/17477) avoid recomparing all fields
649            .filter(|cmp| *cmp != Ordering::Equal || self == other)
650    }
651}
652
653#[cfg(test)]
654mod 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}