Coverage Report

Created: 2024-10-13 08:39

/Users/andrewlamb/Software/datafusion/datafusion/expr/src/logical_plan/ddl.rs
Line
Count
Source (jump to first uncovered line)
1
// Licensed to the Apache Software Foundation (ASF) under one
2
// or more contributor license agreements.  See the NOTICE file
3
// distributed with this work for additional information
4
// regarding copyright ownership.  The ASF licenses this file
5
// to you under the Apache License, Version 2.0 (the
6
// "License"); you may not use this file except in compliance
7
// with the License.  You may obtain a copy of the License at
8
//
9
//   http://www.apache.org/licenses/LICENSE-2.0
10
//
11
// Unless required by applicable law or agreed to in writing,
12
// software distributed under the License is distributed on an
13
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14
// KIND, either express or implied.  See the License for the
15
// specific language governing permissions and limitations
16
// under the License.
17
18
use crate::{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
use crate::expr::Sort;
28
use arrow::datatypes::DataType;
29
use datafusion_common::{Constraints, DFSchemaRef, SchemaReference, TableReference};
30
use sqlparser::ast::Ident;
31
32
/// Various types of DDL  (CREATE / DROP) catalog manipulation
33
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Hash)]
34
pub enum DdlStatement {
35
    /// Creates an external table.
36
    CreateExternalTable(CreateExternalTable),
37
    /// Creates an in memory table.
38
    CreateMemoryTable(CreateMemoryTable),
39
    /// Creates a new view.
40
    CreateView(CreateView),
41
    /// Creates a new catalog schema.
42
    CreateCatalogSchema(CreateCatalogSchema),
43
    /// Creates a new catalog (aka "Database").
44
    CreateCatalog(CreateCatalog),
45
    /// Creates a new index.
46
    CreateIndex(CreateIndex),
47
    /// Drops a table.
48
    DropTable(DropTable),
49
    /// Drops a view.
50
    DropView(DropView),
51
    /// Drops a catalog schema
52
    DropCatalogSchema(DropCatalogSchema),
53
    /// Create function statement
54
    CreateFunction(CreateFunction),
55
    /// Drop function statement
56
    DropFunction(DropFunction),
57
}
58
59
impl DdlStatement {
60
    /// Get a reference to the logical plan's schema
61
0
    pub fn schema(&self) -> &DFSchemaRef {
62
0
        match self {
63
0
            DdlStatement::CreateExternalTable(CreateExternalTable { schema, .. }) => {
64
0
                schema
65
            }
66
0
            DdlStatement::CreateMemoryTable(CreateMemoryTable { input, .. })
67
0
            | DdlStatement::CreateView(CreateView { input, .. }) => input.schema(),
68
0
            DdlStatement::CreateCatalogSchema(CreateCatalogSchema { schema, .. }) => {
69
0
                schema
70
            }
71
0
            DdlStatement::CreateCatalog(CreateCatalog { schema, .. }) => schema,
72
0
            DdlStatement::CreateIndex(CreateIndex { schema, .. }) => schema,
73
0
            DdlStatement::DropTable(DropTable { schema, .. }) => schema,
74
0
            DdlStatement::DropView(DropView { schema, .. }) => schema,
75
0
            DdlStatement::DropCatalogSchema(DropCatalogSchema { schema, .. }) => schema,
76
0
            DdlStatement::CreateFunction(CreateFunction { schema, .. }) => schema,
77
0
            DdlStatement::DropFunction(DropFunction { schema, .. }) => schema,
78
        }
79
0
    }
80
81
    /// Return a descriptive string describing the type of this
82
    /// [`DdlStatement`]
83
0
    pub fn name(&self) -> &str {
84
0
        match self {
85
0
            DdlStatement::CreateExternalTable(_) => "CreateExternalTable",
86
0
            DdlStatement::CreateMemoryTable(_) => "CreateMemoryTable",
87
0
            DdlStatement::CreateView(_) => "CreateView",
88
0
            DdlStatement::CreateCatalogSchema(_) => "CreateCatalogSchema",
89
0
            DdlStatement::CreateCatalog(_) => "CreateCatalog",
90
0
            DdlStatement::CreateIndex(_) => "CreateIndex",
91
0
            DdlStatement::DropTable(_) => "DropTable",
92
0
            DdlStatement::DropView(_) => "DropView",
93
0
            DdlStatement::DropCatalogSchema(_) => "DropCatalogSchema",
94
0
            DdlStatement::CreateFunction(_) => "CreateFunction",
95
0
            DdlStatement::DropFunction(_) => "DropFunction",
96
        }
97
0
    }
98
99
    /// Return all inputs for this plan
100
0
    pub fn inputs(&self) -> Vec<&LogicalPlan> {
101
0
        match self {
102
0
            DdlStatement::CreateExternalTable(_) => vec![],
103
0
            DdlStatement::CreateCatalogSchema(_) => vec![],
104
0
            DdlStatement::CreateCatalog(_) => vec![],
105
0
            DdlStatement::CreateMemoryTable(CreateMemoryTable { input, .. }) => {
106
0
                vec![input]
107
            }
108
0
            DdlStatement::CreateView(CreateView { input, .. }) => vec![input],
109
0
            DdlStatement::CreateIndex(_) => vec![],
110
0
            DdlStatement::DropTable(_) => vec![],
111
0
            DdlStatement::DropView(_) => vec![],
112
0
            DdlStatement::DropCatalogSchema(_) => vec![],
113
0
            DdlStatement::CreateFunction(_) => vec![],
114
0
            DdlStatement::DropFunction(_) => vec![],
115
        }
116
0
    }
117
118
    /// Return a `format`able structure with the a human readable
119
    /// description of this LogicalPlan node per node, not including
120
    /// children.
121
    ///
122
    /// See [crate::LogicalPlan::display] for an example
123
0
    pub fn display(&self) -> impl fmt::Display + '_ {
124
        struct Wrapper<'a>(&'a DdlStatement);
125
        impl<'a> Display for Wrapper<'a> {
126
0
            fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
127
0
                match self.0 {
128
                    DdlStatement::CreateExternalTable(CreateExternalTable {
129
0
                        ref name,
130
0
                        constraints,
131
0
                        ..
132
0
                    }) => {
133
0
                        write!(f, "CreateExternalTable: {name:?}{constraints}")
134
                    }
135
                    DdlStatement::CreateMemoryTable(CreateMemoryTable {
136
0
                        name,
137
0
                        constraints,
138
0
                        ..
139
0
                    }) => {
140
0
                        write!(f, "CreateMemoryTable: {name:?}{constraints}")
141
                    }
142
0
                    DdlStatement::CreateView(CreateView { name, .. }) => {
143
0
                        write!(f, "CreateView: {name:?}")
144
                    }
145
                    DdlStatement::CreateCatalogSchema(CreateCatalogSchema {
146
0
                        schema_name,
147
0
                        ..
148
0
                    }) => {
149
0
                        write!(f, "CreateCatalogSchema: {schema_name:?}")
150
                    }
151
                    DdlStatement::CreateCatalog(CreateCatalog {
152
0
                        catalog_name, ..
153
0
                    }) => {
154
0
                        write!(f, "CreateCatalog: {catalog_name:?}")
155
                    }
156
0
                    DdlStatement::CreateIndex(CreateIndex { name, .. }) => {
157
0
                        write!(f, "CreateIndex: {name:?}")
158
                    }
159
                    DdlStatement::DropTable(DropTable {
160
0
                        name, if_exists, ..
161
0
                    }) => {
162
0
                        write!(f, "DropTable: {name:?} if not exist:={if_exists}")
163
                    }
164
                    DdlStatement::DropView(DropView {
165
0
                        name, if_exists, ..
166
0
                    }) => {
167
0
                        write!(f, "DropView: {name:?} if not exist:={if_exists}")
168
                    }
169
                    DdlStatement::DropCatalogSchema(DropCatalogSchema {
170
0
                        name,
171
0
                        if_exists,
172
0
                        cascade,
173
0
                        ..
174
0
                    }) => {
175
0
                        write!(f, "DropCatalogSchema: {name:?} if not exist:={if_exists} cascade:={cascade}")
176
                    }
177
0
                    DdlStatement::CreateFunction(CreateFunction { name, .. }) => {
178
0
                        write!(f, "CreateFunction: name {name:?}")
179
                    }
180
0
                    DdlStatement::DropFunction(DropFunction { name, .. }) => {
181
0
                        write!(f, "CreateFunction: name {name:?}")
182
                    }
183
                }
184
0
            }
185
        }
186
0
        Wrapper(self)
187
0
    }
188
}
189
190
/// Creates an external table.
191
#[derive(Debug, Clone, PartialEq, Eq)]
192
pub struct CreateExternalTable {
193
    /// The table schema
194
    pub schema: DFSchemaRef,
195
    /// The table name
196
    pub name: TableReference,
197
    /// The physical location
198
    pub location: String,
199
    /// The file type of physical file
200
    pub file_type: String,
201
    /// Partition Columns
202
    pub table_partition_cols: Vec<String>,
203
    /// Option to not error if table already exists
204
    pub if_not_exists: bool,
205
    /// SQL used to create the table, if available
206
    pub definition: Option<String>,
207
    /// Order expressions supplied by user
208
    pub order_exprs: Vec<Vec<Sort>>,
209
    /// Whether the table is an infinite streams
210
    pub unbounded: bool,
211
    /// Table(provider) specific options
212
    pub options: HashMap<String, String>,
213
    /// The list of constraints in the schema, such as primary key, unique, etc.
214
    pub constraints: Constraints,
215
    /// Default values for columns
216
    pub column_defaults: HashMap<String, Expr>,
217
}
218
219
// Hashing refers to a subset of fields considered in PartialEq.
220
impl Hash for CreateExternalTable {
221
0
    fn hash<H: Hasher>(&self, state: &mut H) {
222
0
        self.schema.hash(state);
223
0
        self.name.hash(state);
224
0
        self.location.hash(state);
225
0
        self.file_type.hash(state);
226
0
        self.table_partition_cols.hash(state);
227
0
        self.if_not_exists.hash(state);
228
0
        self.definition.hash(state);
229
0
        self.order_exprs.hash(state);
230
0
        self.unbounded.hash(state);
231
0
        self.options.len().hash(state); // HashMap is not hashable
232
0
    }
233
}
234
235
// Manual implementation needed because of `schema`, `options`, and `column_defaults` fields.
236
// Comparison excludes these fields.
237
impl PartialOrd for CreateExternalTable {
238
0
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
239
        #[derive(PartialEq, PartialOrd)]
240
        struct ComparableCreateExternalTable<'a> {
241
            /// The table name
242
            pub name: &'a TableReference,
243
            /// The physical location
244
            pub location: &'a String,
245
            /// The file type of physical file
246
            pub file_type: &'a String,
247
            /// Partition Columns
248
            pub table_partition_cols: &'a Vec<String>,
249
            /// Option to not error if table already exists
250
            pub if_not_exists: &'a bool,
251
            /// SQL used to create the table, if available
252
            pub definition: &'a Option<String>,
253
            /// Order expressions supplied by user
254
            pub order_exprs: &'a Vec<Vec<Sort>>,
255
            /// Whether the table is an infinite streams
256
            pub unbounded: &'a bool,
257
            /// The list of constraints in the schema, such as primary key, unique, etc.
258
            pub constraints: &'a Constraints,
259
        }
260
0
        let comparable_self = ComparableCreateExternalTable {
261
0
            name: &self.name,
262
0
            location: &self.location,
263
0
            file_type: &self.file_type,
264
0
            table_partition_cols: &self.table_partition_cols,
265
0
            if_not_exists: &self.if_not_exists,
266
0
            definition: &self.definition,
267
0
            order_exprs: &self.order_exprs,
268
0
            unbounded: &self.unbounded,
269
0
            constraints: &self.constraints,
270
0
        };
271
0
        let comparable_other = ComparableCreateExternalTable {
272
0
            name: &other.name,
273
0
            location: &other.location,
274
0
            file_type: &other.file_type,
275
0
            table_partition_cols: &other.table_partition_cols,
276
0
            if_not_exists: &other.if_not_exists,
277
0
            definition: &other.definition,
278
0
            order_exprs: &other.order_exprs,
279
0
            unbounded: &other.unbounded,
280
0
            constraints: &other.constraints,
281
0
        };
282
0
        comparable_self.partial_cmp(&comparable_other)
283
0
    }
284
}
285
286
/// Creates an in memory table.
287
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Hash)]
288
pub struct CreateMemoryTable {
289
    /// The table name
290
    pub name: TableReference,
291
    /// The list of constraints in the schema, such as primary key, unique, etc.
292
    pub constraints: Constraints,
293
    /// The logical plan
294
    pub input: Arc<LogicalPlan>,
295
    /// Option to not error if table already exists
296
    pub if_not_exists: bool,
297
    /// Option to replace table content if table already exists
298
    pub or_replace: bool,
299
    /// Default values for columns
300
    pub column_defaults: Vec<(String, Expr)>,
301
}
302
303
/// Creates a view.
304
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Hash)]
305
pub struct CreateView {
306
    /// The table name
307
    pub name: TableReference,
308
    /// The logical plan
309
    pub input: Arc<LogicalPlan>,
310
    /// Option to not error if table already exists
311
    pub or_replace: bool,
312
    /// SQL used to create the view, if available
313
    pub definition: Option<String>,
314
}
315
316
/// Creates a catalog (aka "Database").
317
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
318
pub struct CreateCatalog {
319
    /// The catalog name
320
    pub catalog_name: String,
321
    /// Do nothing (except issuing a notice) if a schema with the same name already exists
322
    pub if_not_exists: bool,
323
    /// Empty schema
324
    pub schema: DFSchemaRef,
325
}
326
327
// Manual implementation needed because of `schema` field. Comparison excludes this field.
328
impl PartialOrd for CreateCatalog {
329
0
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
330
0
        match self.catalog_name.partial_cmp(&other.catalog_name) {
331
0
            Some(Ordering::Equal) => self.if_not_exists.partial_cmp(&other.if_not_exists),
332
0
            cmp => cmp,
333
        }
334
0
    }
335
}
336
337
/// Creates a schema.
338
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
339
pub struct CreateCatalogSchema {
340
    /// The table schema
341
    pub schema_name: String,
342
    /// Do nothing (except issuing a notice) if a schema with the same name already exists
343
    pub if_not_exists: bool,
344
    /// Empty schema
345
    pub schema: DFSchemaRef,
346
}
347
348
// Manual implementation needed because of `schema` field. Comparison excludes this field.
349
impl PartialOrd for CreateCatalogSchema {
350
0
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
351
0
        match self.schema_name.partial_cmp(&other.schema_name) {
352
0
            Some(Ordering::Equal) => self.if_not_exists.partial_cmp(&other.if_not_exists),
353
0
            cmp => cmp,
354
        }
355
0
    }
356
}
357
358
/// Drops a table.
359
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
360
pub struct DropTable {
361
    /// The table name
362
    pub name: TableReference,
363
    /// If the table exists
364
    pub if_exists: bool,
365
    /// Dummy schema
366
    pub schema: DFSchemaRef,
367
}
368
369
// Manual implementation needed because of `schema` field. Comparison excludes this field.
370
impl PartialOrd for DropTable {
371
0
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
372
0
        match self.name.partial_cmp(&other.name) {
373
0
            Some(Ordering::Equal) => self.if_exists.partial_cmp(&other.if_exists),
374
0
            cmp => cmp,
375
        }
376
0
    }
377
}
378
379
/// Drops a view.
380
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
381
pub struct DropView {
382
    /// The view name
383
    pub name: TableReference,
384
    /// If the view exists
385
    pub if_exists: bool,
386
    /// Dummy schema
387
    pub schema: DFSchemaRef,
388
}
389
390
// Manual implementation needed because of `schema` field. Comparison excludes this field.
391
impl PartialOrd for DropView {
392
0
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
393
0
        match self.name.partial_cmp(&other.name) {
394
0
            Some(Ordering::Equal) => self.if_exists.partial_cmp(&other.if_exists),
395
0
            cmp => cmp,
396
        }
397
0
    }
398
}
399
400
/// Drops a schema
401
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
402
pub struct DropCatalogSchema {
403
    /// The schema name
404
    pub name: SchemaReference,
405
    /// If the schema exists
406
    pub if_exists: bool,
407
    /// Whether drop should cascade
408
    pub cascade: bool,
409
    /// Dummy schema
410
    pub schema: DFSchemaRef,
411
}
412
413
// Manual implementation needed because of `schema` field. Comparison excludes this field.
414
impl PartialOrd for DropCatalogSchema {
415
0
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
416
0
        match self.name.partial_cmp(&other.name) {
417
0
            Some(Ordering::Equal) => match self.if_exists.partial_cmp(&other.if_exists) {
418
0
                Some(Ordering::Equal) => self.cascade.partial_cmp(&other.cascade),
419
0
                cmp => cmp,
420
            },
421
0
            cmp => cmp,
422
        }
423
0
    }
424
}
425
426
/// Arguments passed to `CREATE FUNCTION`
427
///
428
/// Note this meant to be the same as from sqlparser's [`sqlparser::ast::Statement::CreateFunction`]
429
#[derive(Clone, PartialEq, Eq, Hash, Debug)]
430
pub struct CreateFunction {
431
    // TODO: There is open question should we expose sqlparser types or redefine them here?
432
    //       At the moment it make more sense to expose sqlparser types and leave
433
    //       user to convert them as needed
434
    pub or_replace: bool,
435
    pub temporary: bool,
436
    pub name: String,
437
    pub args: Option<Vec<OperateFunctionArg>>,
438
    pub return_type: Option<DataType>,
439
    pub params: CreateFunctionBody,
440
    /// Dummy schema
441
    pub schema: DFSchemaRef,
442
}
443
444
// Manual implementation needed because of `schema` field. Comparison excludes this field.
445
impl PartialOrd for CreateFunction {
446
0
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
447
        #[derive(PartialEq, PartialOrd)]
448
        struct ComparableCreateFunction<'a> {
449
            pub or_replace: &'a bool,
450
            pub temporary: &'a bool,
451
            pub name: &'a String,
452
            pub args: &'a Option<Vec<OperateFunctionArg>>,
453
            pub return_type: &'a Option<DataType>,
454
            pub params: &'a CreateFunctionBody,
455
        }
456
0
        let comparable_self = ComparableCreateFunction {
457
0
            or_replace: &self.or_replace,
458
0
            temporary: &self.temporary,
459
0
            name: &self.name,
460
0
            args: &self.args,
461
0
            return_type: &self.return_type,
462
0
            params: &self.params,
463
0
        };
464
0
        let comparable_other = ComparableCreateFunction {
465
0
            or_replace: &other.or_replace,
466
0
            temporary: &other.temporary,
467
0
            name: &other.name,
468
0
            args: &other.args,
469
0
            return_type: &other.return_type,
470
0
            params: &other.params,
471
0
        };
472
0
        comparable_self.partial_cmp(&comparable_other)
473
0
    }
474
}
475
476
#[derive(Clone, PartialEq, Eq, PartialOrd, Hash, Debug)]
477
pub struct OperateFunctionArg {
478
    // TODO: figure out how to support mode
479
    // pub mode: Option<ArgMode>,
480
    pub name: Option<Ident>,
481
    pub data_type: DataType,
482
    pub default_expr: Option<Expr>,
483
}
484
#[derive(Clone, PartialEq, Eq, PartialOrd, Hash, Debug)]
485
pub struct CreateFunctionBody {
486
    /// LANGUAGE lang_name
487
    pub language: Option<Ident>,
488
    /// IMMUTABLE | STABLE | VOLATILE
489
    pub behavior: Option<Volatility>,
490
    /// RETURN or AS function body
491
    pub function_body: Option<Expr>,
492
}
493
494
#[derive(Clone, PartialEq, Eq, Hash, Debug)]
495
pub struct DropFunction {
496
    pub name: String,
497
    pub if_exists: bool,
498
    pub schema: DFSchemaRef,
499
}
500
501
impl PartialOrd for DropFunction {
502
0
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
503
0
        match self.name.partial_cmp(&other.name) {
504
0
            Some(Ordering::Equal) => self.if_exists.partial_cmp(&other.if_exists),
505
0
            cmp => cmp,
506
        }
507
0
    }
508
}
509
510
#[derive(Clone, PartialEq, Eq, Hash, Debug)]
511
pub struct CreateIndex {
512
    pub name: Option<String>,
513
    pub table: TableReference,
514
    pub using: Option<String>,
515
    pub columns: Vec<SortExpr>,
516
    pub unique: bool,
517
    pub if_not_exists: bool,
518
    pub schema: DFSchemaRef,
519
}
520
521
// Manual implementation needed because of `schema` field. Comparison excludes this field.
522
impl PartialOrd for CreateIndex {
523
0
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
524
        #[derive(PartialEq, PartialOrd)]
525
        struct ComparableCreateIndex<'a> {
526
            pub name: &'a Option<String>,
527
            pub table: &'a TableReference,
528
            pub using: &'a Option<String>,
529
            pub columns: &'a Vec<SortExpr>,
530
            pub unique: &'a bool,
531
            pub if_not_exists: &'a bool,
532
        }
533
0
        let comparable_self = ComparableCreateIndex {
534
0
            name: &self.name,
535
0
            table: &self.table,
536
0
            using: &self.using,
537
0
            columns: &self.columns,
538
0
            unique: &self.unique,
539
0
            if_not_exists: &self.if_not_exists,
540
0
        };
541
0
        let comparable_other = ComparableCreateIndex {
542
0
            name: &other.name,
543
0
            table: &other.table,
544
0
            using: &other.using,
545
0
            columns: &other.columns,
546
0
            unique: &other.unique,
547
0
            if_not_exists: &other.if_not_exists,
548
0
        };
549
0
        comparable_self.partial_cmp(&comparable_other)
550
0
    }
551
}
552
553
#[cfg(test)]
554
mod test {
555
    use crate::{CreateCatalog, DdlStatement, DropView};
556
    use datafusion_common::{DFSchema, DFSchemaRef, TableReference};
557
    use std::cmp::Ordering;
558
559
    #[test]
560
    fn test_partial_ord() {
561
        let catalog = DdlStatement::CreateCatalog(CreateCatalog {
562
            catalog_name: "name".to_string(),
563
            if_not_exists: false,
564
            schema: DFSchemaRef::new(DFSchema::empty()),
565
        });
566
        let catalog_2 = DdlStatement::CreateCatalog(CreateCatalog {
567
            catalog_name: "name".to_string(),
568
            if_not_exists: true,
569
            schema: DFSchemaRef::new(DFSchema::empty()),
570
        });
571
572
        assert_eq!(catalog.partial_cmp(&catalog_2), Some(Ordering::Less));
573
574
        let drop_view = DdlStatement::DropView(DropView {
575
            name: TableReference::from("table"),
576
            if_exists: false,
577
            schema: DFSchemaRef::new(DFSchema::empty()),
578
        });
579
580
        assert_eq!(drop_view.partial_cmp(&catalog), Some(Ordering::Greater));
581
    }
582
}