blob: 74fe7a2d009d07dde40bb96040809fa5e63e7d0e [file]
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
use crate::{Expr, LogicalPlan, SortExpr, Volatility};
use std::cmp::Ordering;
use std::collections::HashMap;
use std::sync::Arc;
use std::{
fmt::{self, Display},
hash::{Hash, Hasher},
};
#[cfg(not(feature = "sql"))]
use crate::expr::Ident;
use crate::expr::Sort;
use arrow::datatypes::DataType;
use datafusion_common::tree_node::{Transformed, TreeNodeContainer, TreeNodeRecursion};
use datafusion_common::{
Constraints, DFSchemaRef, Result, SchemaReference, TableReference,
};
#[cfg(feature = "sql")]
use sqlparser::ast::Ident;
/// Various types of DDL (CREATE / DROP) catalog manipulation
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Hash)]
pub enum DdlStatement {
/// Creates an external table.
CreateExternalTable(CreateExternalTable),
/// Creates an in memory table.
CreateMemoryTable(CreateMemoryTable),
/// Creates a new view.
CreateView(CreateView),
/// Creates a new catalog schema.
CreateCatalogSchema(CreateCatalogSchema),
/// Creates a new catalog (aka "Database").
CreateCatalog(CreateCatalog),
/// Creates a new index.
CreateIndex(CreateIndex),
/// Drops a table.
DropTable(DropTable),
/// Drops a view.
DropView(DropView),
/// Drops a catalog schema
DropCatalogSchema(DropCatalogSchema),
/// Create function statement
CreateFunction(CreateFunction),
/// Drop function statement
DropFunction(DropFunction),
}
impl DdlStatement {
/// Get a reference to the logical plan's schema
pub fn schema(&self) -> &DFSchemaRef {
match self {
DdlStatement::CreateExternalTable(CreateExternalTable { schema, .. }) => {
schema
}
DdlStatement::CreateMemoryTable(CreateMemoryTable { input, .. })
| DdlStatement::CreateView(CreateView { input, .. }) => input.schema(),
DdlStatement::CreateCatalogSchema(CreateCatalogSchema { schema, .. }) => {
schema
}
DdlStatement::CreateCatalog(CreateCatalog { schema, .. }) => schema,
DdlStatement::CreateIndex(CreateIndex { schema, .. }) => schema,
DdlStatement::DropTable(DropTable { schema, .. }) => schema,
DdlStatement::DropView(DropView { schema, .. }) => schema,
DdlStatement::DropCatalogSchema(DropCatalogSchema { schema, .. }) => schema,
DdlStatement::CreateFunction(CreateFunction { schema, .. }) => schema,
DdlStatement::DropFunction(DropFunction { schema, .. }) => schema,
}
}
/// Return a descriptive string describing the type of this
/// [`DdlStatement`]
pub fn name(&self) -> &str {
match self {
DdlStatement::CreateExternalTable(_) => "CreateExternalTable",
DdlStatement::CreateMemoryTable(_) => "CreateMemoryTable",
DdlStatement::CreateView(_) => "CreateView",
DdlStatement::CreateCatalogSchema(_) => "CreateCatalogSchema",
DdlStatement::CreateCatalog(_) => "CreateCatalog",
DdlStatement::CreateIndex(_) => "CreateIndex",
DdlStatement::DropTable(_) => "DropTable",
DdlStatement::DropView(_) => "DropView",
DdlStatement::DropCatalogSchema(_) => "DropCatalogSchema",
DdlStatement::CreateFunction(_) => "CreateFunction",
DdlStatement::DropFunction(_) => "DropFunction",
}
}
/// Return all inputs for this plan
pub fn inputs(&self) -> Vec<&LogicalPlan> {
match self {
DdlStatement::CreateExternalTable(_) => vec![],
DdlStatement::CreateCatalogSchema(_) => vec![],
DdlStatement::CreateCatalog(_) => vec![],
DdlStatement::CreateMemoryTable(CreateMemoryTable { input, .. }) => {
vec![input]
}
DdlStatement::CreateView(CreateView { input, .. }) => vec![input],
DdlStatement::CreateIndex(_) => vec![],
DdlStatement::DropTable(_) => vec![],
DdlStatement::DropView(_) => vec![],
DdlStatement::DropCatalogSchema(_) => vec![],
DdlStatement::CreateFunction(_) => vec![],
DdlStatement::DropFunction(_) => vec![],
}
}
/// Return a `format`able structure with the a human readable
/// description of this LogicalPlan node per node, not including
/// children.
///
/// See [crate::LogicalPlan::display] for an example
pub fn display(&self) -> impl Display + '_ {
struct Wrapper<'a>(&'a DdlStatement);
impl Display for Wrapper<'_> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self.0 {
DdlStatement::CreateExternalTable(CreateExternalTable {
ref name,
constraints,
..
}) => {
if constraints.is_empty() {
write!(f, "CreateExternalTable: {name:?}")
} else {
write!(f, "CreateExternalTable: {name:?} {constraints}")
}
}
DdlStatement::CreateMemoryTable(CreateMemoryTable {
name,
constraints,
..
}) => {
if constraints.is_empty() {
write!(f, "CreateMemoryTable: {name:?}")
} else {
write!(f, "CreateMemoryTable: {name:?} {constraints}")
}
}
DdlStatement::CreateView(CreateView { name, .. }) => {
write!(f, "CreateView: {name:?}")
}
DdlStatement::CreateCatalogSchema(CreateCatalogSchema {
schema_name,
..
}) => {
write!(f, "CreateCatalogSchema: {schema_name:?}")
}
DdlStatement::CreateCatalog(CreateCatalog {
catalog_name, ..
}) => {
write!(f, "CreateCatalog: {catalog_name:?}")
}
DdlStatement::CreateIndex(CreateIndex { name, .. }) => {
write!(f, "CreateIndex: {name:?}")
}
DdlStatement::DropTable(DropTable {
name, if_exists, ..
}) => {
write!(f, "DropTable: {name:?} if not exist:={if_exists}")
}
DdlStatement::DropView(DropView {
name, if_exists, ..
}) => {
write!(f, "DropView: {name:?} if not exist:={if_exists}")
}
DdlStatement::DropCatalogSchema(DropCatalogSchema {
name,
if_exists,
cascade,
..
}) => {
write!(f, "DropCatalogSchema: {name:?} if not exist:={if_exists} cascade:={cascade}")
}
DdlStatement::CreateFunction(CreateFunction { name, .. }) => {
write!(f, "CreateFunction: name {name:?}")
}
DdlStatement::DropFunction(DropFunction { name, .. }) => {
write!(f, "DropFunction: name {name:?}")
}
}
}
}
Wrapper(self)
}
}
/// Creates an external table.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CreateExternalTable {
/// The table schema
pub schema: DFSchemaRef,
/// The table name
pub name: TableReference,
/// The physical location
pub location: String,
/// The file type of physical file
pub file_type: String,
/// Partition Columns
pub table_partition_cols: Vec<String>,
/// Option to not error if table already exists
pub if_not_exists: bool,
/// Option to replace table content if table already exists
pub or_replace: bool,
/// Whether the table is a temporary table
pub temporary: bool,
/// SQL used to create the table, if available
pub definition: Option<String>,
/// Order expressions supplied by user
pub order_exprs: Vec<Vec<Sort>>,
/// Whether the table is an infinite streams
pub unbounded: bool,
/// Table(provider) specific options
pub options: HashMap<String, String>,
/// The list of constraints in the schema, such as primary key, unique, etc.
pub constraints: Constraints,
/// Default values for columns
pub column_defaults: HashMap<String, Expr>,
}
// Hashing refers to a subset of fields considered in PartialEq.
impl Hash for CreateExternalTable {
fn hash<H: Hasher>(&self, state: &mut H) {
self.schema.hash(state);
self.name.hash(state);
self.location.hash(state);
self.file_type.hash(state);
self.table_partition_cols.hash(state);
self.if_not_exists.hash(state);
self.definition.hash(state);
self.order_exprs.hash(state);
self.unbounded.hash(state);
self.options.len().hash(state); // HashMap is not hashable
}
}
// Manual implementation needed because of `schema`, `options`, and `column_defaults` fields.
// Comparison excludes these fields.
impl PartialOrd for CreateExternalTable {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
#[derive(PartialEq, PartialOrd)]
struct ComparableCreateExternalTable<'a> {
/// The table name
pub name: &'a TableReference,
/// The physical location
pub location: &'a String,
/// The file type of physical file
pub file_type: &'a String,
/// Partition Columns
pub table_partition_cols: &'a Vec<String>,
/// Option to not error if table already exists
pub if_not_exists: &'a bool,
/// SQL used to create the table, if available
pub definition: &'a Option<String>,
/// Order expressions supplied by user
pub order_exprs: &'a Vec<Vec<Sort>>,
/// Whether the table is an infinite streams
pub unbounded: &'a bool,
/// The list of constraints in the schema, such as primary key, unique, etc.
pub constraints: &'a Constraints,
}
let comparable_self = ComparableCreateExternalTable {
name: &self.name,
location: &self.location,
file_type: &self.file_type,
table_partition_cols: &self.table_partition_cols,
if_not_exists: &self.if_not_exists,
definition: &self.definition,
order_exprs: &self.order_exprs,
unbounded: &self.unbounded,
constraints: &self.constraints,
};
let comparable_other = ComparableCreateExternalTable {
name: &other.name,
location: &other.location,
file_type: &other.file_type,
table_partition_cols: &other.table_partition_cols,
if_not_exists: &other.if_not_exists,
definition: &other.definition,
order_exprs: &other.order_exprs,
unbounded: &other.unbounded,
constraints: &other.constraints,
};
comparable_self
.partial_cmp(&comparable_other)
// TODO (https://github.com/apache/datafusion/issues/17477) avoid recomparing all fields
.filter(|cmp| *cmp != Ordering::Equal || self == other)
}
}
/// Creates an in memory table.
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Hash)]
pub struct CreateMemoryTable {
/// The table name
pub name: TableReference,
/// The list of constraints in the schema, such as primary key, unique, etc.
pub constraints: Constraints,
/// The logical plan
pub input: Arc<LogicalPlan>,
/// Option to not error if table already exists
pub if_not_exists: bool,
/// Option to replace table content if table already exists
pub or_replace: bool,
/// Default values for columns
pub column_defaults: Vec<(String, Expr)>,
/// Whether the table is `TableType::Temporary`
pub temporary: bool,
}
/// Creates a view.
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Hash)]
pub struct CreateView {
/// The table name
pub name: TableReference,
/// The logical plan
pub input: Arc<LogicalPlan>,
/// Option to not error if table already exists
pub or_replace: bool,
/// SQL used to create the view, if available
pub definition: Option<String>,
/// Whether the view is ephemeral
pub temporary: bool,
}
/// Creates a catalog (aka "Database").
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct CreateCatalog {
/// The catalog name
pub catalog_name: String,
/// Do nothing (except issuing a notice) if a schema with the same name already exists
pub if_not_exists: bool,
/// Empty schema
pub schema: DFSchemaRef,
}
// Manual implementation needed because of `schema` field. Comparison excludes this field.
impl PartialOrd for CreateCatalog {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
match self.catalog_name.partial_cmp(&other.catalog_name) {
Some(Ordering::Equal) => self.if_not_exists.partial_cmp(&other.if_not_exists),
cmp => cmp,
}
// TODO (https://github.com/apache/datafusion/issues/17477) avoid recomparing all fields
.filter(|cmp| *cmp != Ordering::Equal || self == other)
}
}
/// Creates a schema.
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct CreateCatalogSchema {
/// The table schema
pub schema_name: String,
/// Do nothing (except issuing a notice) if a schema with the same name already exists
pub if_not_exists: bool,
/// Empty schema
pub schema: DFSchemaRef,
}
// Manual implementation needed because of `schema` field. Comparison excludes this field.
impl PartialOrd for CreateCatalogSchema {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
match self.schema_name.partial_cmp(&other.schema_name) {
Some(Ordering::Equal) => self.if_not_exists.partial_cmp(&other.if_not_exists),
cmp => cmp,
}
// TODO (https://github.com/apache/datafusion/issues/17477) avoid recomparing all fields
.filter(|cmp| *cmp != Ordering::Equal || self == other)
}
}
/// Drops a table.
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct DropTable {
/// The table name
pub name: TableReference,
/// If the table exists
pub if_exists: bool,
/// Dummy schema
pub schema: DFSchemaRef,
}
// Manual implementation needed because of `schema` field. Comparison excludes this field.
impl PartialOrd for DropTable {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
match self.name.partial_cmp(&other.name) {
Some(Ordering::Equal) => self.if_exists.partial_cmp(&other.if_exists),
cmp => cmp,
}
// TODO (https://github.com/apache/datafusion/issues/17477) avoid recomparing all fields
.filter(|cmp| *cmp != Ordering::Equal || self == other)
}
}
/// Drops a view.
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct DropView {
/// The view name
pub name: TableReference,
/// If the view exists
pub if_exists: bool,
/// Dummy schema
pub schema: DFSchemaRef,
}
// Manual implementation needed because of `schema` field. Comparison excludes this field.
impl PartialOrd for DropView {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
match self.name.partial_cmp(&other.name) {
Some(Ordering::Equal) => self.if_exists.partial_cmp(&other.if_exists),
cmp => cmp,
}
// TODO (https://github.com/apache/datafusion/issues/17477) avoid recomparing all fields
.filter(|cmp| *cmp != Ordering::Equal || self == other)
}
}
/// Drops a schema
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct DropCatalogSchema {
/// The schema name
pub name: SchemaReference,
/// If the schema exists
pub if_exists: bool,
/// Whether drop should cascade
pub cascade: bool,
/// Dummy schema
pub schema: DFSchemaRef,
}
// Manual implementation needed because of `schema` field. Comparison excludes this field.
impl PartialOrd for DropCatalogSchema {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
match self.name.partial_cmp(&other.name) {
Some(Ordering::Equal) => match self.if_exists.partial_cmp(&other.if_exists) {
Some(Ordering::Equal) => self.cascade.partial_cmp(&other.cascade),
cmp => cmp,
},
cmp => cmp,
}
// TODO (https://github.com/apache/datafusion/issues/17477) avoid recomparing all fields
.filter(|cmp| *cmp != Ordering::Equal || self == other)
}
}
/// Arguments passed to the `CREATE FUNCTION` statement
///
/// These statements are turned into executable functions using [`FunctionFactory`]
///
/// # Notes
///
/// This structure purposely mirrors the structure in sqlparser's
/// [`sqlparser::ast::Statement::CreateFunction`], but does not use it directly
/// to avoid a dependency on sqlparser in the core crate.
///
///
/// [`FunctionFactory`]: https://docs.rs/datafusion/latest/datafusion/execution/context/trait.FunctionFactory.html
#[derive(Clone, PartialEq, Eq, Hash, Debug)]
pub struct CreateFunction {
pub or_replace: bool,
pub temporary: bool,
pub name: String,
pub args: Option<Vec<OperateFunctionArg>>,
pub return_type: Option<DataType>,
pub params: CreateFunctionBody,
/// Dummy schema
pub schema: DFSchemaRef,
}
// Manual implementation needed because of `schema` field. Comparison excludes this field.
impl PartialOrd for CreateFunction {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
#[derive(PartialEq, PartialOrd)]
struct ComparableCreateFunction<'a> {
pub or_replace: &'a bool,
pub temporary: &'a bool,
pub name: &'a String,
pub args: &'a Option<Vec<OperateFunctionArg>>,
pub return_type: &'a Option<DataType>,
pub params: &'a CreateFunctionBody,
}
let comparable_self = ComparableCreateFunction {
or_replace: &self.or_replace,
temporary: &self.temporary,
name: &self.name,
args: &self.args,
return_type: &self.return_type,
params: &self.params,
};
let comparable_other = ComparableCreateFunction {
or_replace: &other.or_replace,
temporary: &other.temporary,
name: &other.name,
args: &other.args,
return_type: &other.return_type,
params: &other.params,
};
comparable_self
.partial_cmp(&comparable_other)
// TODO (https://github.com/apache/datafusion/issues/17477) avoid recomparing all fields
.filter(|cmp| *cmp != Ordering::Equal || self == other)
}
}
/// Part of the `CREATE FUNCTION` statement
///
/// See [`CreateFunction`] for details
#[derive(Clone, PartialEq, Eq, PartialOrd, Hash, Debug)]
pub struct OperateFunctionArg {
// TODO: figure out how to support mode
// pub mode: Option<ArgMode>,
pub name: Option<Ident>,
pub data_type: DataType,
pub default_expr: Option<Expr>,
}
impl<'a> TreeNodeContainer<'a, Expr> for OperateFunctionArg {
fn apply_elements<F: FnMut(&'a Expr) -> Result<TreeNodeRecursion>>(
&'a self,
f: F,
) -> Result<TreeNodeRecursion> {
self.default_expr.apply_elements(f)
}
fn map_elements<F: FnMut(Expr) -> Result<Transformed<Expr>>>(
self,
f: F,
) -> Result<Transformed<Self>> {
self.default_expr.map_elements(f)?.map_data(|default_expr| {
Ok(Self {
default_expr,
..self
})
})
}
}
/// Part of the `CREATE FUNCTION` statement
///
/// See [`CreateFunction`] for details
#[derive(Clone, PartialEq, Eq, PartialOrd, Hash, Debug)]
pub struct CreateFunctionBody {
/// LANGUAGE lang_name
pub language: Option<Ident>,
/// IMMUTABLE | STABLE | VOLATILE
pub behavior: Option<Volatility>,
/// RETURN or AS function body
pub function_body: Option<Expr>,
}
impl<'a> TreeNodeContainer<'a, Expr> for CreateFunctionBody {
fn apply_elements<F: FnMut(&'a Expr) -> Result<TreeNodeRecursion>>(
&'a self,
f: F,
) -> Result<TreeNodeRecursion> {
self.function_body.apply_elements(f)
}
fn map_elements<F: FnMut(Expr) -> Result<Transformed<Expr>>>(
self,
f: F,
) -> Result<Transformed<Self>> {
self.function_body
.map_elements(f)?
.map_data(|function_body| {
Ok(Self {
function_body,
..self
})
})
}
}
#[derive(Clone, PartialEq, Eq, Hash, Debug)]
pub struct DropFunction {
pub name: String,
pub if_exists: bool,
pub schema: DFSchemaRef,
}
impl PartialOrd for DropFunction {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
match self.name.partial_cmp(&other.name) {
Some(Ordering::Equal) => self.if_exists.partial_cmp(&other.if_exists),
cmp => cmp,
}
// TODO (https://github.com/apache/datafusion/issues/17477) avoid recomparing all fields
.filter(|cmp| *cmp != Ordering::Equal || self == other)
}
}
#[derive(Clone, PartialEq, Eq, Hash, Debug)]
pub struct CreateIndex {
pub name: Option<String>,
pub table: TableReference,
pub using: Option<String>,
pub columns: Vec<SortExpr>,
pub unique: bool,
pub if_not_exists: bool,
pub schema: DFSchemaRef,
}
// Manual implementation needed because of `schema` field. Comparison excludes this field.
impl PartialOrd for CreateIndex {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
#[derive(PartialEq, PartialOrd)]
struct ComparableCreateIndex<'a> {
pub name: &'a Option<String>,
pub table: &'a TableReference,
pub using: &'a Option<String>,
pub columns: &'a Vec<SortExpr>,
pub unique: &'a bool,
pub if_not_exists: &'a bool,
}
let comparable_self = ComparableCreateIndex {
name: &self.name,
table: &self.table,
using: &self.using,
columns: &self.columns,
unique: &self.unique,
if_not_exists: &self.if_not_exists,
};
let comparable_other = ComparableCreateIndex {
name: &other.name,
table: &other.table,
using: &other.using,
columns: &other.columns,
unique: &other.unique,
if_not_exists: &other.if_not_exists,
};
comparable_self
.partial_cmp(&comparable_other)
// TODO (https://github.com/apache/datafusion/issues/17477) avoid recomparing all fields
.filter(|cmp| *cmp != Ordering::Equal || self == other)
}
}
#[cfg(test)]
mod test {
use crate::{CreateCatalog, DdlStatement, DropView};
use datafusion_common::{DFSchema, DFSchemaRef, TableReference};
use std::cmp::Ordering;
#[test]
fn test_partial_ord() {
let catalog = DdlStatement::CreateCatalog(CreateCatalog {
catalog_name: "name".to_string(),
if_not_exists: false,
schema: DFSchemaRef::new(DFSchema::empty()),
});
let catalog_2 = DdlStatement::CreateCatalog(CreateCatalog {
catalog_name: "name".to_string(),
if_not_exists: true,
schema: DFSchemaRef::new(DFSchema::empty()),
});
assert_eq!(catalog.partial_cmp(&catalog_2), Some(Ordering::Less));
let drop_view = DdlStatement::DropView(DropView {
name: TableReference::from("table"),
if_exists: false,
schema: DFSchemaRef::new(DFSchema::empty()),
});
assert_eq!(drop_view.partial_cmp(&catalog), Some(Ordering::Greater));
}
}