Skip to main content

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
27use crate::expr::Sort;
28#[cfg(not(feature = "sql"))]
29use crate::sql::Ident;
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. Boxed to keep `LogicalPlan` enum size down
42    /// — `CreateExternalTable` is ~312 bytes, dwarfing every other variant
43    /// in the plan tree and forcing the whole enum to that width.
44    CreateExternalTable(Box<CreateExternalTable>),
45    /// Creates an in memory table.
46    CreateMemoryTable(CreateMemoryTable),
47    /// Creates a new view.
48    CreateView(CreateView),
49    /// Creates a new catalog schema.
50    CreateCatalogSchema(CreateCatalogSchema),
51    /// Creates a new catalog (aka "Database").
52    CreateCatalog(CreateCatalog),
53    /// Creates a new index.
54    CreateIndex(CreateIndex),
55    /// Drops a table.
56    DropTable(DropTable),
57    /// Drops a view.
58    DropView(DropView),
59    /// Drops a catalog schema
60    DropCatalogSchema(DropCatalogSchema),
61    /// Create function statement. Boxed for the same reason as
62    /// [`Self::CreateExternalTable`] (~288 bytes).
63    CreateFunction(Box<CreateFunction>),
64    /// Drop function statement
65    DropFunction(DropFunction),
66}
67
68impl DdlStatement {
69    /// Get a reference to the logical plan's schema
70    pub fn schema(&self) -> &DFSchemaRef {
71        match self {
72            DdlStatement::CreateExternalTable(ce) => &ce.schema,
73            DdlStatement::CreateMemoryTable(CreateMemoryTable { input, .. })
74            | DdlStatement::CreateView(CreateView { input, .. }) => input.schema(),
75            DdlStatement::CreateCatalogSchema(CreateCatalogSchema { schema, .. }) => {
76                schema
77            }
78            DdlStatement::CreateCatalog(CreateCatalog { schema, .. }) => schema,
79            DdlStatement::CreateIndex(CreateIndex { schema, .. }) => schema,
80            DdlStatement::DropTable(DropTable { schema, .. }) => schema,
81            DdlStatement::DropView(DropView { schema, .. }) => schema,
82            DdlStatement::DropCatalogSchema(DropCatalogSchema { schema, .. }) => schema,
83            DdlStatement::CreateFunction(cf) => &cf.schema,
84            DdlStatement::DropFunction(DropFunction { schema, .. }) => schema,
85        }
86    }
87
88    /// Return a descriptive string describing the type of this
89    /// [`DdlStatement`]
90    pub fn name(&self) -> &str {
91        match self {
92            DdlStatement::CreateExternalTable(_) => "CreateExternalTable",
93            DdlStatement::CreateMemoryTable(_) => "CreateMemoryTable",
94            DdlStatement::CreateView(_) => "CreateView",
95            DdlStatement::CreateCatalogSchema(_) => "CreateCatalogSchema",
96            DdlStatement::CreateCatalog(_) => "CreateCatalog",
97            DdlStatement::CreateIndex(_) => "CreateIndex",
98            DdlStatement::DropTable(_) => "DropTable",
99            DdlStatement::DropView(_) => "DropView",
100            DdlStatement::DropCatalogSchema(_) => "DropCatalogSchema",
101            DdlStatement::CreateFunction(_) => "CreateFunction",
102            DdlStatement::DropFunction(_) => "DropFunction",
103        }
104    }
105
106    /// Return all inputs for this plan
107    pub fn inputs(&self) -> Vec<&LogicalPlan> {
108        match self {
109            DdlStatement::CreateExternalTable(_) => vec![],
110            DdlStatement::CreateCatalogSchema(_) => vec![],
111            DdlStatement::CreateCatalog(_) => vec![],
112            DdlStatement::CreateMemoryTable(CreateMemoryTable { input, .. }) => {
113                vec![input]
114            }
115            DdlStatement::CreateView(CreateView { input, .. }) => vec![input],
116            DdlStatement::CreateIndex(_) => vec![],
117            DdlStatement::DropTable(_) => vec![],
118            DdlStatement::DropView(_) => vec![],
119            DdlStatement::DropCatalogSchema(_) => vec![],
120            DdlStatement::CreateFunction(_) => vec![],
121            DdlStatement::DropFunction(_) => vec![],
122        }
123    }
124
125    /// Return a `format`able structure with the a human readable
126    /// description of this LogicalPlan node per node, not including
127    /// children.
128    ///
129    /// See [crate::LogicalPlan::display] for an example
130    pub fn display(&self) -> impl Display + '_ {
131        struct Wrapper<'a>(&'a DdlStatement);
132        impl Display for Wrapper<'_> {
133            fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
134                match self.0 {
135                    DdlStatement::CreateExternalTable(ce) => {
136                        let name = &ce.name;
137                        let constraints = &ce.constraints;
138                        if constraints.is_empty() {
139                            write!(f, "CreateExternalTable: {name:?}")
140                        } else {
141                            write!(f, "CreateExternalTable: {name:?} {constraints}")
142                        }
143                    }
144                    DdlStatement::CreateMemoryTable(CreateMemoryTable {
145                        name,
146                        constraints,
147                        ..
148                    }) => {
149                        if constraints.is_empty() {
150                            write!(f, "CreateMemoryTable: {name:?}")
151                        } else {
152                            write!(f, "CreateMemoryTable: {name:?} {constraints}")
153                        }
154                    }
155                    DdlStatement::CreateView(CreateView { name, .. }) => {
156                        write!(f, "CreateView: {name:?}")
157                    }
158                    DdlStatement::CreateCatalogSchema(CreateCatalogSchema {
159                        schema_name,
160                        ..
161                    }) => {
162                        write!(f, "CreateCatalogSchema: {schema_name:?}")
163                    }
164                    DdlStatement::CreateCatalog(CreateCatalog {
165                        catalog_name, ..
166                    }) => {
167                        write!(f, "CreateCatalog: {catalog_name:?}")
168                    }
169                    DdlStatement::CreateIndex(CreateIndex { name, .. }) => {
170                        write!(f, "CreateIndex: {name:?}")
171                    }
172                    DdlStatement::DropTable(DropTable {
173                        name, if_exists, ..
174                    }) => {
175                        write!(f, "DropTable: {name:?} if not exist:={if_exists}")
176                    }
177                    DdlStatement::DropView(DropView {
178                        name, if_exists, ..
179                    }) => {
180                        write!(f, "DropView: {name:?} if not exist:={if_exists}")
181                    }
182                    DdlStatement::DropCatalogSchema(DropCatalogSchema {
183                        name,
184                        if_exists,
185                        cascade,
186                        ..
187                    }) => {
188                        write!(
189                            f,
190                            "DropCatalogSchema: {name:?} if not exist:={if_exists} cascade:={cascade}"
191                        )
192                    }
193                    DdlStatement::CreateFunction(cf) => {
194                        let name = &cf.name;
195                        write!(f, "CreateFunction: name {name:?}")
196                    }
197                    DdlStatement::DropFunction(DropFunction { name, .. }) => {
198                        write!(f, "DropFunction: name {name:?}")
199                    }
200                }
201            }
202        }
203        Wrapper(self)
204    }
205}
206
207/// Creates an external table.
208#[derive(Debug, Clone, PartialEq, Eq)]
209pub struct CreateExternalTable {
210    /// The table schema
211    pub schema: DFSchemaRef,
212    /// The table name
213    pub name: TableReference,
214    /// The physical locations of the table files.
215    ///
216    /// More than one location may be supplied (for example
217    /// `CREATE EXTERNAL TABLE ... LOCATION ('a.parquet', 'b.parquet')`), in which
218    /// case the files are read together as a single table.
219    pub locations: Vec<String>,
220    /// The file type of physical file
221    pub file_type: String,
222    /// Partition Columns
223    pub table_partition_cols: Vec<String>,
224    /// Option to not error if table already exists
225    pub if_not_exists: bool,
226    /// Option to replace table content if table already exists
227    pub or_replace: bool,
228    /// Whether the table is a temporary table
229    pub temporary: bool,
230    /// SQL used to create the table, if available
231    pub definition: Option<String>,
232    /// Order expressions supplied by user
233    pub order_exprs: Vec<Vec<Sort>>,
234    /// Whether the table is an infinite streams
235    pub unbounded: bool,
236    /// Table(provider) specific options
237    pub options: HashMap<String, String>,
238    /// The list of constraints in the schema, such as primary key, unique, etc.
239    pub constraints: Constraints,
240    /// Default values for columns
241    pub column_defaults: HashMap<String, Expr>,
242}
243
244impl CreateExternalTable {
245    /// Creates a builder for [`CreateExternalTable`] with required fields.
246    ///
247    /// # Arguments
248    /// * `name` - The table name
249    /// * `location` - The physical location of the table files
250    /// * `file_type` - The file type (e.g., "parquet", "csv", "json")
251    /// * `schema` - The table schema
252    ///
253    /// # Example
254    /// ```
255    /// # use datafusion_expr::CreateExternalTable;
256    /// # use datafusion_common::{DFSchema, TableReference};
257    /// # use std::sync::Arc;
258    /// let table = CreateExternalTable::builder(
259    ///     TableReference::bare("my_table"),
260    ///     "/path/to/data",
261    ///     "parquet",
262    ///     Arc::new(DFSchema::empty())
263    /// ).build();
264    /// ```
265    pub fn builder(
266        name: impl Into<TableReference>,
267        location: impl Into<String>,
268        file_type: impl Into<String>,
269        schema: DFSchemaRef,
270    ) -> CreateExternalTableBuilder {
271        CreateExternalTableBuilder {
272            name: name.into(),
273            locations: vec![location.into()],
274            file_type: file_type.into(),
275            schema,
276            table_partition_cols: vec![],
277            if_not_exists: false,
278            or_replace: false,
279            temporary: false,
280            definition: None,
281            order_exprs: vec![],
282            unbounded: false,
283            options: HashMap::new(),
284            constraints: Default::default(),
285            column_defaults: HashMap::new(),
286        }
287    }
288}
289
290/// Builder for [`CreateExternalTable`] that provides a fluent API for construction.
291///
292/// Created via [`CreateExternalTable::builder`].
293#[derive(Debug, Clone)]
294pub struct CreateExternalTableBuilder {
295    name: TableReference,
296    locations: Vec<String>,
297    file_type: String,
298    schema: DFSchemaRef,
299    table_partition_cols: Vec<String>,
300    if_not_exists: bool,
301    or_replace: bool,
302    temporary: bool,
303    definition: Option<String>,
304    order_exprs: Vec<Vec<Sort>>,
305    unbounded: bool,
306    options: HashMap<String, String>,
307    constraints: Constraints,
308    column_defaults: HashMap<String, Expr>,
309}
310
311impl CreateExternalTableBuilder {
312    /// Set the partition columns
313    pub fn with_partition_cols(mut self, cols: Vec<String>) -> Self {
314        self.table_partition_cols = cols;
315        self
316    }
317
318    /// Set the physical locations of the table files, replacing the single
319    /// location supplied to [`CreateExternalTable::builder`].
320    ///
321    /// When more than one location is provided the files are read together as
322    /// a single table.
323    pub fn with_locations(mut self, locations: Vec<String>) -> Self {
324        self.locations = locations;
325        self
326    }
327
328    /// Set the if_not_exists flag
329    pub fn with_if_not_exists(mut self, if_not_exists: bool) -> Self {
330        self.if_not_exists = if_not_exists;
331        self
332    }
333
334    /// Set the or_replace flag
335    pub fn with_or_replace(mut self, or_replace: bool) -> Self {
336        self.or_replace = or_replace;
337        self
338    }
339
340    /// Set the temporary flag
341    pub fn with_temporary(mut self, temporary: bool) -> Self {
342        self.temporary = temporary;
343        self
344    }
345
346    /// Set the SQL definition
347    pub fn with_definition(mut self, definition: Option<String>) -> Self {
348        self.definition = definition;
349        self
350    }
351
352    /// Set the order expressions
353    pub fn with_order_exprs(mut self, order_exprs: Vec<Vec<Sort>>) -> Self {
354        self.order_exprs = order_exprs;
355        self
356    }
357
358    /// Set the unbounded flag
359    pub fn with_unbounded(mut self, unbounded: bool) -> Self {
360        self.unbounded = unbounded;
361        self
362    }
363
364    /// Set the table options
365    pub fn with_options(mut self, options: HashMap<String, String>) -> Self {
366        self.options = options;
367        self
368    }
369
370    /// Set the table constraints
371    pub fn with_constraints(mut self, constraints: Constraints) -> Self {
372        self.constraints = constraints;
373        self
374    }
375
376    /// Set the column defaults
377    pub fn with_column_defaults(
378        mut self,
379        column_defaults: HashMap<String, Expr>,
380    ) -> Self {
381        self.column_defaults = column_defaults;
382        self
383    }
384
385    /// Build the [`CreateExternalTable`]
386    pub fn build(self) -> CreateExternalTable {
387        CreateExternalTable {
388            schema: self.schema,
389            name: self.name,
390            locations: self.locations,
391            file_type: self.file_type,
392            table_partition_cols: self.table_partition_cols,
393            if_not_exists: self.if_not_exists,
394            or_replace: self.or_replace,
395            temporary: self.temporary,
396            definition: self.definition,
397            order_exprs: self.order_exprs,
398            unbounded: self.unbounded,
399            options: self.options,
400            constraints: self.constraints,
401            column_defaults: self.column_defaults,
402        }
403    }
404}
405
406// Hashing refers to a subset of fields considered in PartialEq.
407impl Hash for CreateExternalTable {
408    fn hash<H: Hasher>(&self, state: &mut H) {
409        self.schema.hash(state);
410        self.name.hash(state);
411        self.locations.hash(state);
412        self.file_type.hash(state);
413        self.table_partition_cols.hash(state);
414        self.if_not_exists.hash(state);
415        self.definition.hash(state);
416        self.order_exprs.hash(state);
417        self.unbounded.hash(state);
418        self.options.len().hash(state); // HashMap is not hashable
419    }
420}
421
422// Manual implementation needed because of `schema`, `options`, and `column_defaults` fields.
423// Comparison excludes these fields.
424impl PartialOrd for CreateExternalTable {
425    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
426        #[derive(PartialEq, PartialOrd)]
427        struct ComparableCreateExternalTable<'a> {
428            /// The table name
429            pub name: &'a TableReference,
430            /// The physical locations
431            pub locations: &'a Vec<String>,
432            /// The file type of physical file
433            pub file_type: &'a String,
434            /// Partition Columns
435            pub table_partition_cols: &'a Vec<String>,
436            /// Option to not error if table already exists
437            pub if_not_exists: &'a bool,
438            /// SQL used to create the table, if available
439            pub definition: &'a Option<String>,
440            /// Order expressions supplied by user
441            pub order_exprs: &'a Vec<Vec<Sort>>,
442            /// Whether the table is an infinite streams
443            pub unbounded: &'a bool,
444            /// The list of constraints in the schema, such as primary key, unique, etc.
445            pub constraints: &'a Constraints,
446        }
447        let comparable_self = ComparableCreateExternalTable {
448            name: &self.name,
449            locations: &self.locations,
450            file_type: &self.file_type,
451            table_partition_cols: &self.table_partition_cols,
452            if_not_exists: &self.if_not_exists,
453            definition: &self.definition,
454            order_exprs: &self.order_exprs,
455            unbounded: &self.unbounded,
456            constraints: &self.constraints,
457        };
458        let comparable_other = ComparableCreateExternalTable {
459            name: &other.name,
460            locations: &other.locations,
461            file_type: &other.file_type,
462            table_partition_cols: &other.table_partition_cols,
463            if_not_exists: &other.if_not_exists,
464            definition: &other.definition,
465            order_exprs: &other.order_exprs,
466            unbounded: &other.unbounded,
467            constraints: &other.constraints,
468        };
469        comparable_self
470            .partial_cmp(&comparable_other)
471            // TODO (https://github.com/apache/datafusion/issues/17477) avoid recomparing all fields
472            .filter(|cmp| *cmp != Ordering::Equal || self == other)
473    }
474}
475
476/// Creates an in memory table.
477#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Hash)]
478pub struct CreateMemoryTable {
479    /// The table name
480    pub name: TableReference,
481    /// The list of constraints in the schema, such as primary key, unique, etc.
482    pub constraints: Constraints,
483    /// The logical plan
484    pub input: Arc<LogicalPlan>,
485    /// Option to not error if table already exists
486    pub if_not_exists: bool,
487    /// Option to replace table content if table already exists
488    pub or_replace: bool,
489    /// Default values for columns
490    pub column_defaults: Vec<(String, Expr)>,
491    /// Whether the table is `TableType::Temporary`
492    pub temporary: bool,
493}
494
495/// Creates a view.
496#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Hash)]
497pub struct CreateView {
498    /// The table name
499    pub name: TableReference,
500    /// The logical plan
501    pub input: Arc<LogicalPlan>,
502    /// Option to not error if table already exists
503    pub or_replace: bool,
504    /// SQL used to create the view, if available
505    pub definition: Option<String>,
506    /// Whether the view is ephemeral
507    pub temporary: bool,
508}
509
510/// Creates a catalog (aka "Database").
511#[derive(Debug, Clone, PartialEq, Eq, Hash)]
512pub struct CreateCatalog {
513    /// The catalog name
514    pub catalog_name: String,
515    /// Do nothing (except issuing a notice) if a schema with the same name already exists
516    pub if_not_exists: bool,
517    /// Empty schema
518    pub schema: DFSchemaRef,
519}
520
521// Manual implementation needed because of `schema` field. Comparison excludes this field.
522impl PartialOrd for CreateCatalog {
523    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
524        match self.catalog_name.partial_cmp(&other.catalog_name) {
525            Some(Ordering::Equal) => self.if_not_exists.partial_cmp(&other.if_not_exists),
526            cmp => cmp,
527        }
528        // TODO (https://github.com/apache/datafusion/issues/17477) avoid recomparing all fields
529        .filter(|cmp| *cmp != Ordering::Equal || self == other)
530    }
531}
532
533/// Creates a schema.
534#[derive(Debug, Clone, PartialEq, Eq, Hash)]
535pub struct CreateCatalogSchema {
536    /// The table schema
537    pub schema_name: String,
538    /// Do nothing (except issuing a notice) if a schema with the same name already exists
539    pub if_not_exists: bool,
540    /// Empty schema
541    pub schema: DFSchemaRef,
542}
543
544// Manual implementation needed because of `schema` field. Comparison excludes this field.
545impl PartialOrd for CreateCatalogSchema {
546    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
547        match self.schema_name.partial_cmp(&other.schema_name) {
548            Some(Ordering::Equal) => self.if_not_exists.partial_cmp(&other.if_not_exists),
549            cmp => cmp,
550        }
551        // TODO (https://github.com/apache/datafusion/issues/17477) avoid recomparing all fields
552        .filter(|cmp| *cmp != Ordering::Equal || self == other)
553    }
554}
555
556/// Drops a table.
557#[derive(Debug, Clone, PartialEq, Eq, Hash)]
558pub struct DropTable {
559    /// The table name
560    pub name: TableReference,
561    /// If the table exists
562    pub if_exists: bool,
563    /// Dummy schema
564    pub schema: DFSchemaRef,
565}
566
567// Manual implementation needed because of `schema` field. Comparison excludes this field.
568impl PartialOrd for DropTable {
569    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
570        match self.name.partial_cmp(&other.name) {
571            Some(Ordering::Equal) => self.if_exists.partial_cmp(&other.if_exists),
572            cmp => cmp,
573        }
574        // TODO (https://github.com/apache/datafusion/issues/17477) avoid recomparing all fields
575        .filter(|cmp| *cmp != Ordering::Equal || self == other)
576    }
577}
578
579/// Drops a view.
580#[derive(Debug, Clone, PartialEq, Eq, Hash)]
581pub struct DropView {
582    /// The view name
583    pub name: TableReference,
584    /// If the view exists
585    pub if_exists: bool,
586    /// Dummy schema
587    pub schema: DFSchemaRef,
588}
589
590// Manual implementation needed because of `schema` field. Comparison excludes this field.
591impl PartialOrd for DropView {
592    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
593        match self.name.partial_cmp(&other.name) {
594            Some(Ordering::Equal) => self.if_exists.partial_cmp(&other.if_exists),
595            cmp => cmp,
596        }
597        // TODO (https://github.com/apache/datafusion/issues/17477) avoid recomparing all fields
598        .filter(|cmp| *cmp != Ordering::Equal || self == other)
599    }
600}
601
602/// Drops a schema
603#[derive(Debug, Clone, PartialEq, Eq, Hash)]
604pub struct DropCatalogSchema {
605    /// The schema name
606    pub name: SchemaReference,
607    /// If the schema exists
608    pub if_exists: bool,
609    /// Whether drop should cascade
610    pub cascade: bool,
611    /// Dummy schema
612    pub schema: DFSchemaRef,
613}
614
615// Manual implementation needed because of `schema` field. Comparison excludes this field.
616impl PartialOrd for DropCatalogSchema {
617    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
618        match self.name.partial_cmp(&other.name) {
619            Some(Ordering::Equal) => match self.if_exists.partial_cmp(&other.if_exists) {
620                Some(Ordering::Equal) => self.cascade.partial_cmp(&other.cascade),
621                cmp => cmp,
622            },
623            cmp => cmp,
624        }
625        // TODO (https://github.com/apache/datafusion/issues/17477) avoid recomparing all fields
626        .filter(|cmp| *cmp != Ordering::Equal || self == other)
627    }
628}
629
630/// Arguments passed to the `CREATE FUNCTION` statement
631///
632/// These statements are turned into executable functions using [`FunctionFactory`]
633///
634/// # Notes
635///
636/// This structure purposely mirrors the structure in sqlparser's
637/// [`sqlparser::ast::Statement::CreateFunction`], but does not use it directly
638/// to avoid a dependency on sqlparser in the core crate.
639///
640///
641/// [`FunctionFactory`]: https://docs.rs/datafusion/latest/datafusion/execution/context/trait.FunctionFactory.html
642#[derive(Clone, PartialEq, Eq, Hash, Debug)]
643pub struct CreateFunction {
644    pub or_replace: bool,
645    pub temporary: bool,
646    pub name: String,
647    pub args: Option<Vec<OperateFunctionArg>>,
648    pub return_type: Option<DataType>,
649    pub params: CreateFunctionBody,
650    /// Dummy schema
651    pub schema: DFSchemaRef,
652}
653
654// Manual implementation needed because of `schema` field. Comparison excludes this field.
655impl PartialOrd for CreateFunction {
656    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
657        #[derive(PartialEq, PartialOrd)]
658        struct ComparableCreateFunction<'a> {
659            pub or_replace: &'a bool,
660            pub temporary: &'a bool,
661            pub name: &'a String,
662            pub args: &'a Option<Vec<OperateFunctionArg>>,
663            pub return_type: &'a Option<DataType>,
664            pub params: &'a CreateFunctionBody,
665        }
666        let comparable_self = ComparableCreateFunction {
667            or_replace: &self.or_replace,
668            temporary: &self.temporary,
669            name: &self.name,
670            args: &self.args,
671            return_type: &self.return_type,
672            params: &self.params,
673        };
674        let comparable_other = ComparableCreateFunction {
675            or_replace: &other.or_replace,
676            temporary: &other.temporary,
677            name: &other.name,
678            args: &other.args,
679            return_type: &other.return_type,
680            params: &other.params,
681        };
682        comparable_self
683            .partial_cmp(&comparable_other)
684            // TODO (https://github.com/apache/datafusion/issues/17477) avoid recomparing all fields
685            .filter(|cmp| *cmp != Ordering::Equal || self == other)
686    }
687}
688
689/// Part of the `CREATE FUNCTION` statement
690///
691/// See [`CreateFunction`] for details
692#[derive(Clone, PartialEq, Eq, PartialOrd, Hash, Debug)]
693pub struct OperateFunctionArg {
694    // TODO: figure out how to support mode
695    // pub mode: Option<ArgMode>,
696    pub name: Option<Ident>,
697    pub data_type: DataType,
698    pub default_expr: Option<Expr>,
699}
700
701impl<'a> TreeNodeContainer<'a, Expr> for OperateFunctionArg {
702    fn apply_elements<F: FnMut(&'a Expr) -> Result<TreeNodeRecursion>>(
703        &'a self,
704        f: F,
705    ) -> Result<TreeNodeRecursion> {
706        self.default_expr.apply_elements(f)
707    }
708
709    fn map_elements<F: FnMut(Expr) -> Result<Transformed<Expr>>>(
710        self,
711        f: F,
712    ) -> Result<Transformed<Self>> {
713        self.default_expr.map_elements(f)?.map_data(|default_expr| {
714            Ok(Self {
715                default_expr,
716                ..self
717            })
718        })
719    }
720}
721
722/// Part of the `CREATE FUNCTION` statement
723///
724/// See [`CreateFunction`] for details
725#[derive(Clone, PartialEq, Eq, PartialOrd, Hash, Debug)]
726pub struct CreateFunctionBody {
727    /// LANGUAGE lang_name
728    pub language: Option<Ident>,
729    /// IMMUTABLE | STABLE | VOLATILE
730    pub behavior: Option<Volatility>,
731    /// RETURN or AS function body
732    pub function_body: Option<Expr>,
733}
734
735impl<'a> TreeNodeContainer<'a, Expr> for CreateFunctionBody {
736    fn apply_elements<F: FnMut(&'a Expr) -> Result<TreeNodeRecursion>>(
737        &'a self,
738        f: F,
739    ) -> Result<TreeNodeRecursion> {
740        self.function_body.apply_elements(f)
741    }
742
743    fn map_elements<F: FnMut(Expr) -> Result<Transformed<Expr>>>(
744        self,
745        f: F,
746    ) -> Result<Transformed<Self>> {
747        self.function_body
748            .map_elements(f)?
749            .map_data(|function_body| {
750                Ok(Self {
751                    function_body,
752                    ..self
753                })
754            })
755    }
756}
757
758#[derive(Clone, PartialEq, Eq, Hash, Debug)]
759pub struct DropFunction {
760    pub name: String,
761    pub if_exists: bool,
762    pub schema: DFSchemaRef,
763}
764
765impl PartialOrd for DropFunction {
766    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
767        match self.name.partial_cmp(&other.name) {
768            Some(Ordering::Equal) => self.if_exists.partial_cmp(&other.if_exists),
769            cmp => cmp,
770        }
771        // TODO (https://github.com/apache/datafusion/issues/17477) avoid recomparing all fields
772        .filter(|cmp| *cmp != Ordering::Equal || self == other)
773    }
774}
775
776#[derive(Clone, PartialEq, Eq, Hash, Debug)]
777pub struct CreateIndex {
778    pub name: Option<String>,
779    pub table: TableReference,
780    pub using: Option<String>,
781    pub columns: Vec<SortExpr>,
782    pub unique: bool,
783    pub if_not_exists: bool,
784    pub schema: DFSchemaRef,
785}
786
787// Manual implementation needed because of `schema` field. Comparison excludes this field.
788impl PartialOrd for CreateIndex {
789    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
790        #[derive(PartialEq, PartialOrd)]
791        struct ComparableCreateIndex<'a> {
792            pub name: &'a Option<String>,
793            pub table: &'a TableReference,
794            pub using: &'a Option<String>,
795            pub columns: &'a Vec<SortExpr>,
796            pub unique: &'a bool,
797            pub if_not_exists: &'a bool,
798        }
799        let comparable_self = ComparableCreateIndex {
800            name: &self.name,
801            table: &self.table,
802            using: &self.using,
803            columns: &self.columns,
804            unique: &self.unique,
805            if_not_exists: &self.if_not_exists,
806        };
807        let comparable_other = ComparableCreateIndex {
808            name: &other.name,
809            table: &other.table,
810            using: &other.using,
811            columns: &other.columns,
812            unique: &other.unique,
813            if_not_exists: &other.if_not_exists,
814        };
815        comparable_self
816            .partial_cmp(&comparable_other)
817            // TODO (https://github.com/apache/datafusion/issues/17477) avoid recomparing all fields
818            .filter(|cmp| *cmp != Ordering::Equal || self == other)
819    }
820}
821
822#[cfg(test)]
823mod test {
824    use crate::{CreateCatalog, DdlStatement, DropView};
825    use datafusion_common::{DFSchema, DFSchemaRef, TableReference};
826    use std::cmp::Ordering;
827
828    #[test]
829    fn test_partial_ord() {
830        let catalog = DdlStatement::CreateCatalog(CreateCatalog {
831            catalog_name: "name".to_string(),
832            if_not_exists: false,
833            schema: DFSchemaRef::new(DFSchema::empty()),
834        });
835        let catalog_2 = DdlStatement::CreateCatalog(CreateCatalog {
836            catalog_name: "name".to_string(),
837            if_not_exists: true,
838            schema: DFSchemaRef::new(DFSchema::empty()),
839        });
840
841        assert_eq!(catalog.partial_cmp(&catalog_2), Some(Ordering::Less));
842
843        let drop_view = DdlStatement::DropView(DropView {
844            name: TableReference::from("table"),
845            if_exists: false,
846            schema: DFSchemaRef::new(DFSchema::empty()),
847        });
848
849        assert_eq!(drop_view.partial_cmp(&catalog), Some(Ordering::Greater));
850    }
851}