// 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.

#[cfg(not(feature = "std"))]
use alloc::{boxed::Box, format, string::ToString, vec::Vec};

use core::fmt::{self, Display};
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
#[cfg(feature = "visitor")]
use sqlparser_derive::{Visit, VisitMut};

use crate::{
    ast::display_separated,
    display_utils::{indented_list, Indent, SpaceOrNewline},
};

use super::{
    display_comma_separated, helpers::attached_token::AttachedToken, query::InputFormatClause,
    Assignment, Expr, FromTable, Ident, InsertAliases, MysqlInsertPriority, ObjectName, OnInsert,
    OptimizerHint, OrderByExpr, Query, SelectInto, SelectItem, Setting, SqliteOnConflict,
    TableAliasWithoutColumns, TableFactor, TableObject, TableWithJoins, UpdateTableFromKind,
    Values,
};

/// INSERT statement.
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
pub struct Insert {
    /// Token for the `INSERT` keyword (or its substitutes)
    pub insert_token: AttachedToken,
    /// Query optimizer hints
    ///
    /// [MySQL](https://dev.mysql.com/doc/refman/8.4/en/optimizer-hints.html)
    /// [Oracle](https://docs.oracle.com/en/database/oracle/oracle-database/21/sqlrf/Comments.html#GUID-D316D545-89E2-4D54-977F-FC97815CD62E)
    pub optimizer_hints: Vec<OptimizerHint>,
    /// Only for Sqlite
    pub or: Option<SqliteOnConflict>,
    /// Only for mysql
    pub ignore: bool,
    /// INTO - optional keyword
    pub into: bool,
    /// TABLE
    pub table: TableObject,
    /// `table_name as foo` (for PostgreSQL)
    /// `table_name foo` (for Oracle)
    pub table_alias: Option<TableAliasWithoutColumns>,
    /// COLUMNS
    pub columns: Vec<ObjectName>,
    /// Overwrite (Hive)
    pub overwrite: bool,
    /// A SQL query that specifies what to insert
    pub source: Option<Box<Query>>,
    /// MySQL `INSERT INTO ... SET`
    /// See: <https://dev.mysql.com/doc/refman/8.4/en/insert.html>
    pub assignments: Vec<Assignment>,
    /// partitioned insert (Hive)
    pub partitioned: Option<Vec<Expr>>,
    /// Columns defined after PARTITION
    pub after_columns: Vec<Ident>,
    /// whether the insert has the table keyword (Hive)
    pub has_table_keyword: bool,
    /// ON INSERT
    pub on: Option<OnInsert>,
    /// RETURNING
    pub returning: Option<Vec<SelectItem>>,
    /// OUTPUT (MSSQL)
    /// See <https://learn.microsoft.com/en-us/sql/t-sql/queries/output-clause-transact-sql>
    pub output: Option<OutputClause>,
    /// Only for mysql
    pub replace_into: bool,
    /// Only for mysql
    pub priority: Option<MysqlInsertPriority>,
    /// Only for mysql
    pub insert_alias: Option<InsertAliases>,
    /// Settings used for ClickHouse.
    ///
    /// ClickHouse syntax: `INSERT INTO tbl SETTINGS format_template_resultset = '/some/path/resultset.format'`
    ///
    /// [ClickHouse `INSERT INTO`](https://clickhouse.com/docs/en/sql-reference/statements/insert-into)
    pub settings: Option<Vec<Setting>>,
    /// Format for `INSERT` statement when not using standard SQL format. Can be e.g. `CSV`,
    /// `JSON`, `JSONAsString`, `LineAsString` and more.
    ///
    /// ClickHouse syntax: `INSERT INTO tbl FORMAT JSONEachRow {"foo": 1, "bar": 2}, {"foo": 3}`
    ///
    /// [ClickHouse formats JSON insert](https://clickhouse.com/docs/en/interfaces/formats#json-inserting-data)
    pub format_clause: Option<InputFormatClause>,
    /// For Snowflake multi-table insert: specifies the type (`ALL` or `FIRST`)
    ///
    /// - `None` means this is a regular single-table INSERT
    /// - `Some(All)` means `INSERT ALL` (all matching WHEN clauses are executed)
    /// - `Some(First)` means `INSERT FIRST` (only the first matching WHEN clause is executed)
    ///
    /// See: <https://docs.snowflake.com/en/sql-reference/sql/insert-multi-table>
    pub multi_table_insert_type: Option<MultiTableInsertType>,
    /// For multi-table insert: additional INTO clauses (unconditional)
    ///
    /// Used for `INSERT ALL INTO t1 INTO t2 ... SELECT ...`
    ///
    /// See: <https://docs.snowflake.com/en/sql-reference/sql/insert-multi-table>
    pub multi_table_into_clauses: Vec<MultiTableInsertIntoClause>,
    /// For conditional multi-table insert: WHEN clauses
    ///
    /// Used for `INSERT ALL/FIRST WHEN cond THEN INTO t1 ... SELECT ...`
    ///
    /// See: <https://docs.snowflake.com/en/sql-reference/sql/insert-multi-table>
    pub multi_table_when_clauses: Vec<MultiTableInsertWhenClause>,
    /// For conditional multi-table insert: ELSE clause
    ///
    /// See: <https://docs.snowflake.com/en/sql-reference/sql/insert-multi-table>
    pub multi_table_else_clause: Option<Vec<MultiTableInsertIntoClause>>,
}

impl Display for Insert {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        // SQLite OR conflict has a special format: INSERT OR ... INTO table_name
        let table_name = if let Some(table_alias) = &self.table_alias {
            format!(
                "{table} {as_keyword}{alias}",
                table = self.table,
                as_keyword = if table_alias.explicit { "AS " } else { "" },
                alias = table_alias.alias
            )
        } else {
            self.table.to_string()
        };

        if let Some(on_conflict) = self.or {
            f.write_str("INSERT")?;
            for hint in &self.optimizer_hints {
                write!(f, " {hint}")?;
            }
            write!(f, " {on_conflict} INTO {table_name} ")?;
        } else {
            write!(
                f,
                "{start}",
                start = if self.replace_into {
                    "REPLACE"
                } else {
                    "INSERT"
                }
            )?;
            for hint in &self.optimizer_hints {
                write!(f, " {hint}")?;
            }
            if let Some(priority) = self.priority {
                write!(f, " {priority}")?;
            }

            if self.ignore {
                write!(f, " IGNORE")?;
            }

            if self.overwrite {
                write!(f, " OVERWRITE")?;
            }

            if let Some(insert_type) = &self.multi_table_insert_type {
                write!(f, " {}", insert_type)?;
            }

            if self.into {
                write!(f, " INTO")?;
            }

            if self.has_table_keyword {
                write!(f, " TABLE")?;
            }

            if !table_name.is_empty() {
                write!(f, " {table_name} ")?;
            }
        }

        if !self.columns.is_empty() {
            write!(f, "({})", display_comma_separated(&self.columns))?;
            SpaceOrNewline.fmt(f)?;
        }

        if let Some(ref parts) = self.partitioned {
            if !parts.is_empty() {
                write!(f, "PARTITION ({})", display_comma_separated(parts))?;
                SpaceOrNewline.fmt(f)?;
            }
        }

        if !self.after_columns.is_empty() {
            write!(f, "({})", display_comma_separated(&self.after_columns))?;
            SpaceOrNewline.fmt(f)?;
        }

        if let Some(output) = &self.output {
            write!(f, "{output}")?;
            SpaceOrNewline.fmt(f)?;
        }

        if let Some(settings) = &self.settings {
            write!(f, "SETTINGS {}", display_comma_separated(settings))?;
            SpaceOrNewline.fmt(f)?;
        }

        for into_clause in &self.multi_table_into_clauses {
            SpaceOrNewline.fmt(f)?;
            write!(f, "{}", into_clause)?;
        }

        for when_clause in &self.multi_table_when_clauses {
            SpaceOrNewline.fmt(f)?;
            write!(f, "{}", when_clause)?;
        }

        if let Some(else_clauses) = &self.multi_table_else_clause {
            SpaceOrNewline.fmt(f)?;
            write!(f, "ELSE")?;
            for into_clause in else_clauses {
                SpaceOrNewline.fmt(f)?;
                write!(f, "{}", into_clause)?;
            }
        }

        if let Some(source) = &self.source {
            if !self.multi_table_into_clauses.is_empty()
                || !self.multi_table_when_clauses.is_empty()
            {
                SpaceOrNewline.fmt(f)?;
            }
            source.fmt(f)?;
        } else if !self.assignments.is_empty() {
            write!(f, "SET")?;
            indented_list(f, &self.assignments)?;
        } else if let Some(format_clause) = &self.format_clause {
            format_clause.fmt(f)?;
        } else if self.columns.is_empty() {
            write!(f, "DEFAULT VALUES")?;
        }

        if let Some(insert_alias) = &self.insert_alias {
            write!(f, " AS {0}", insert_alias.row_alias)?;

            if let Some(col_aliases) = &insert_alias.col_aliases {
                if !col_aliases.is_empty() {
                    write!(f, " ({})", display_comma_separated(col_aliases))?;
                }
            }
        }

        if let Some(on) = &self.on {
            write!(f, "{on}")?;
        }

        if let Some(returning) = &self.returning {
            SpaceOrNewline.fmt(f)?;
            f.write_str("RETURNING")?;
            indented_list(f, returning)?;
        }

        Ok(())
    }
}

/// DELETE statement.
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
pub struct Delete {
    /// Token for the `DELETE` keyword
    pub delete_token: AttachedToken,
    /// Query optimizer hints
    ///
    /// [MySQL](https://dev.mysql.com/doc/refman/8.4/en/optimizer-hints.html)
    /// [Oracle](https://docs.oracle.com/en/database/oracle/oracle-database/21/sqlrf/Comments.html#GUID-D316D545-89E2-4D54-977F-FC97815CD62E)
    pub optimizer_hints: Vec<OptimizerHint>,
    /// Multi tables delete are supported in mysql
    pub tables: Vec<ObjectName>,
    /// FROM
    pub from: FromTable,
    /// USING (Snowflake, Postgres, MySQL)
    pub using: Option<Vec<TableWithJoins>>,
    /// WHERE
    pub selection: Option<Expr>,
    /// RETURNING
    pub returning: Option<Vec<SelectItem>>,
    /// OUTPUT (MSSQL)
    /// See <https://learn.microsoft.com/en-us/sql/t-sql/queries/output-clause-transact-sql>
    pub output: Option<OutputClause>,
    /// ORDER BY (MySQL)
    pub order_by: Vec<OrderByExpr>,
    /// LIMIT (MySQL)
    pub limit: Option<Expr>,
}

impl Display for Delete {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.write_str("DELETE")?;
        for hint in &self.optimizer_hints {
            f.write_str(" ")?;
            hint.fmt(f)?;
        }
        if !self.tables.is_empty() {
            indented_list(f, &self.tables)?;
        }
        match &self.from {
            FromTable::WithFromKeyword(from) => {
                f.write_str(" FROM")?;
                indented_list(f, from)?;
            }
            FromTable::WithoutKeyword(from) => {
                indented_list(f, from)?;
            }
        }
        if let Some(output) = &self.output {
            SpaceOrNewline.fmt(f)?;
            write!(f, "{output}")?;
        }
        if let Some(using) = &self.using {
            SpaceOrNewline.fmt(f)?;
            f.write_str("USING")?;
            indented_list(f, using)?;
        }
        if let Some(selection) = &self.selection {
            SpaceOrNewline.fmt(f)?;
            f.write_str("WHERE")?;
            SpaceOrNewline.fmt(f)?;
            Indent(selection).fmt(f)?;
        }
        if let Some(returning) = &self.returning {
            SpaceOrNewline.fmt(f)?;
            f.write_str("RETURNING")?;
            indented_list(f, returning)?;
        }
        if !self.order_by.is_empty() {
            SpaceOrNewline.fmt(f)?;
            f.write_str("ORDER BY")?;
            indented_list(f, &self.order_by)?;
        }
        if let Some(limit) = &self.limit {
            SpaceOrNewline.fmt(f)?;
            f.write_str("LIMIT")?;
            SpaceOrNewline.fmt(f)?;
            Indent(limit).fmt(f)?;
        }
        Ok(())
    }
}

/// UPDATE statement.
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
pub struct Update {
    /// Token for the `UPDATE` keyword
    pub update_token: AttachedToken,
    /// Query optimizer hints
    ///
    /// [MySQL](https://dev.mysql.com/doc/refman/8.4/en/optimizer-hints.html)
    /// [Oracle](https://docs.oracle.com/en/database/oracle/oracle-database/21/sqlrf/Comments.html#GUID-D316D545-89E2-4D54-977F-FC97815CD62E)
    pub optimizer_hints: Vec<OptimizerHint>,
    /// TABLE
    pub table: TableWithJoins,
    /// Column assignments
    pub assignments: Vec<Assignment>,
    /// Table which provide value to be set
    pub from: Option<UpdateTableFromKind>,
    /// WHERE
    pub selection: Option<Expr>,
    /// RETURNING
    pub returning: Option<Vec<SelectItem>>,
    /// OUTPUT (MSSQL)
    /// See <https://learn.microsoft.com/en-us/sql/t-sql/queries/output-clause-transact-sql>
    pub output: Option<OutputClause>,
    /// SQLite-specific conflict resolution clause
    pub or: Option<SqliteOnConflict>,
    /// ORDER BY (MySQL extension for single-table UPDATE)
    /// See <https://dev.mysql.com/doc/refman/8.4/en/update.html>
    pub order_by: Vec<OrderByExpr>,
    /// LIMIT
    pub limit: Option<Expr>,
}

impl Display for Update {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        f.write_str("UPDATE")?;
        for hint in &self.optimizer_hints {
            f.write_str(" ")?;
            hint.fmt(f)?;
        }
        f.write_str(" ")?;
        if let Some(or) = &self.or {
            or.fmt(f)?;
            f.write_str(" ")?;
        }
        self.table.fmt(f)?;
        if let Some(UpdateTableFromKind::BeforeSet(from)) = &self.from {
            SpaceOrNewline.fmt(f)?;
            f.write_str("FROM")?;
            indented_list(f, from)?;
        }
        if !self.assignments.is_empty() {
            SpaceOrNewline.fmt(f)?;
            f.write_str("SET")?;
            indented_list(f, &self.assignments)?;
        }
        if let Some(output) = &self.output {
            SpaceOrNewline.fmt(f)?;
            write!(f, "{output}")?;
        }
        if let Some(UpdateTableFromKind::AfterSet(from)) = &self.from {
            SpaceOrNewline.fmt(f)?;
            f.write_str("FROM")?;
            indented_list(f, from)?;
        }
        if let Some(selection) = &self.selection {
            SpaceOrNewline.fmt(f)?;
            f.write_str("WHERE")?;
            SpaceOrNewline.fmt(f)?;
            Indent(selection).fmt(f)?;
        }
        if let Some(returning) = &self.returning {
            SpaceOrNewline.fmt(f)?;
            f.write_str("RETURNING")?;
            indented_list(f, returning)?;
        }
        if !self.order_by.is_empty() {
            SpaceOrNewline.fmt(f)?;
            f.write_str("ORDER BY")?;
            indented_list(f, &self.order_by)?;
        }
        if let Some(limit) = &self.limit {
            SpaceOrNewline.fmt(f)?;
            write!(f, "LIMIT {limit}")?;
        }
        Ok(())
    }
}

/// A `MERGE` statement.
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
pub struct Merge {
    /// The `MERGE` token that starts the statement.
    pub merge_token: AttachedToken,
    /// Query optimizer hints
    ///
    /// [Oracle](https://docs.oracle.com/en/database/oracle/oracle-database/21/sqlrf/Comments.html#GUID-D316D545-89E2-4D54-977F-FC97815CD62E)
    pub optimizer_hints: Vec<OptimizerHint>,
    /// optional INTO keyword
    pub into: bool,
    /// Specifies the table to merge
    pub table: TableFactor,
    /// Specifies the table or subquery to join with the target table
    pub source: TableFactor,
    /// Specifies the expression on which to join the target table and source
    pub on: Box<Expr>,
    /// Specifies the actions to perform when values match or do not match.
    pub clauses: Vec<MergeClause>,
    /// Specifies the output to save changes in MSSQL
    pub output: Option<OutputClause>,
}

impl Display for Merge {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.write_str("MERGE")?;
        for hint in &self.optimizer_hints {
            write!(f, " {hint}")?;
        }
        if self.into {
            write!(f, " INTO")?;
        }
        write!(
            f,
            " {table} USING {source} ",
            table = self.table,
            source = self.source
        )?;
        write!(f, "ON {on} ", on = self.on)?;
        write!(f, "{}", display_separated(&self.clauses, " "))?;
        if let Some(ref output) = self.output {
            write!(f, " {output}")?;
        }
        Ok(())
    }
}

/// A `WHEN` clause within a `MERGE` Statement
///
/// Example:
/// ```sql
/// WHEN NOT MATCHED BY SOURCE AND product LIKE '%washer%' THEN DELETE
/// ```
/// [Snowflake](https://docs.snowflake.com/en/sql-reference/sql/merge)
/// [BigQuery](https://cloud.google.com/bigquery/docs/reference/standard-sql/dml-syntax#merge_statement)
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
pub struct MergeClause {
    /// The `WHEN` token that starts the sub-expression.
    pub when_token: AttachedToken,
    /// The type of `WHEN` clause.
    pub clause_kind: MergeClauseKind,
    /// An optional predicate to further restrict the clause.
    pub predicate: Option<Expr>,
    /// The action to perform when the clause is matched.
    pub action: MergeAction,
}

impl Display for MergeClause {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        let MergeClause {
            when_token: _,
            clause_kind,
            predicate,
            action,
        } = self;

        write!(f, "WHEN {clause_kind}")?;
        if let Some(pred) = predicate {
            write!(f, " AND {pred}")?;
        }
        write!(f, " THEN {action}")
    }
}

/// Variant of `WHEN` clause used within a `MERGE` Statement.
///
/// Example:
/// ```sql
/// MERGE INTO T USING U ON FALSE WHEN MATCHED THEN DELETE
/// ```
/// [Snowflake](https://docs.snowflake.com/en/sql-reference/sql/merge)
/// [BigQuery](https://cloud.google.com/bigquery/docs/reference/standard-sql/dml-syntax#merge_statement)
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Eq, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
pub enum MergeClauseKind {
    /// `WHEN MATCHED`
    Matched,
    /// `WHEN NOT MATCHED`
    NotMatched,
    /// `WHEN MATCHED BY TARGET`
    ///
    /// [BigQuery](https://cloud.google.com/bigquery/docs/reference/standard-sql/dml-syntax#merge_statement)
    NotMatchedByTarget,
    /// `WHEN MATCHED BY SOURCE`
    ///
    /// [BigQuery](https://cloud.google.com/bigquery/docs/reference/standard-sql/dml-syntax#merge_statement)
    NotMatchedBySource,
}

impl Display for MergeClauseKind {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self {
            MergeClauseKind::Matched => write!(f, "MATCHED"),
            MergeClauseKind::NotMatched => write!(f, "NOT MATCHED"),
            MergeClauseKind::NotMatchedByTarget => write!(f, "NOT MATCHED BY TARGET"),
            MergeClauseKind::NotMatchedBySource => write!(f, "NOT MATCHED BY SOURCE"),
        }
    }
}

/// Underlying statement of a `WHEN` clause within a `MERGE` Statement
///
/// Example
/// ```sql
/// INSERT (product, quantity) VALUES(product, quantity)
/// ```
///
/// [Snowflake](https://docs.snowflake.com/en/sql-reference/sql/merge)
/// [BigQuery](https://cloud.google.com/bigquery/docs/reference/standard-sql/dml-syntax#merge_statement)
/// [Oracle](https://docs.oracle.com/en/database/oracle/oracle-database/21/sqlrf/MERGE.html)
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
pub enum MergeAction {
    /// An `INSERT` clause
    ///
    /// Example:
    /// ```sql
    /// INSERT (product, quantity) VALUES(product, quantity)
    /// ```
    Insert(MergeInsertExpr),
    /// An `UPDATE` clause
    ///
    /// Example:
    /// ```sql
    /// UPDATE SET quantity = T.quantity + S.quantity
    /// ```
    Update(MergeUpdateExpr),
    /// A plain `DELETE` clause
    Delete {
        /// The `DELETE` token that starts the sub-expression.
        delete_token: AttachedToken,
    },
}

impl Display for MergeAction {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self {
            MergeAction::Insert(insert) => {
                write!(f, "INSERT {insert}")
            }
            MergeAction::Update(update) => {
                write!(f, "UPDATE {update}")
            }
            MergeAction::Delete { .. } => {
                write!(f, "DELETE")
            }
        }
    }
}

/// The type of expression used to insert rows within a `MERGE` statement.
///
/// [Snowflake](https://docs.snowflake.com/en/sql-reference/sql/merge)
/// [BigQuery](https://cloud.google.com/bigquery/docs/reference/standard-sql/dml-syntax#merge_statement)
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
pub enum MergeInsertKind {
    /// The insert expression is defined from an explicit `VALUES` clause
    ///
    /// Example:
    /// ```sql
    /// INSERT VALUES(product, quantity)
    /// ```
    Values(Values),
    /// The insert expression is defined using only the `ROW` keyword.
    ///
    /// Example:
    /// ```sql
    /// INSERT ROW
    /// ```
    /// [BigQuery](https://cloud.google.com/bigquery/docs/reference/standard-sql/dml-syntax#merge_statement)
    Row,
}

impl Display for MergeInsertKind {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self {
            MergeInsertKind::Values(values) => {
                write!(f, "{values}")
            }
            MergeInsertKind::Row => {
                write!(f, "ROW")
            }
        }
    }
}

/// The expression used to insert rows within a `MERGE` statement.
///
/// Examples
/// ```sql
/// INSERT (product, quantity) VALUES(product, quantity)
/// INSERT ROW
/// ```
///
/// [Snowflake](https://docs.snowflake.com/en/sql-reference/sql/merge)
/// [BigQuery](https://cloud.google.com/bigquery/docs/reference/standard-sql/dml-syntax#merge_statement)
/// [Oracle](https://docs.oracle.com/en/database/oracle/oracle-database/21/sqlrf/MERGE.html)
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
pub struct MergeInsertExpr {
    /// The `INSERT` token that starts the sub-expression.
    pub insert_token: AttachedToken,
    /// Columns (if any) specified by the insert.
    ///
    /// Example:
    /// ```sql
    /// INSERT (product, quantity) VALUES(product, quantity)
    /// INSERT (product, quantity) ROW
    /// ```
    pub columns: Vec<ObjectName>,
    /// The token, `[VALUES | ROW]` starting `kind`.
    pub kind_token: AttachedToken,
    /// The insert type used by the statement.
    pub kind: MergeInsertKind,
    /// An optional condition to restrict the insertion (Oracle specific)
    pub insert_predicate: Option<Expr>,
}

impl Display for MergeInsertExpr {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        if !self.columns.is_empty() {
            write!(f, "({}) ", display_comma_separated(self.columns.as_slice()))?;
        }
        write!(f, "{}", self.kind)?;
        if let Some(predicate) = self.insert_predicate.as_ref() {
            write!(f, " WHERE {}", predicate)?;
        }
        Ok(())
    }
}

/// The expression used to update rows within a `MERGE` statement.
///
/// Examples
/// ```sql
/// UPDATE SET quantity = T.quantity + S.quantity
/// ```
///
/// [Snowflake](https://docs.snowflake.com/en/sql-reference/sql/merge)
/// [BigQuery](https://cloud.google.com/bigquery/docs/reference/standard-sql/dml-syntax#merge_statement)
/// [Oracle](https://docs.oracle.com/en/database/oracle/oracle-database/21/sqlrf/MERGE.html)
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
pub struct MergeUpdateExpr {
    /// The `UPDATE` token that starts the sub-expression.
    pub update_token: AttachedToken,
    /// The update assiment expressions
    pub assignments: Vec<Assignment>,
    /// `where_clause` for the update (Oralce specific)
    pub update_predicate: Option<Expr>,
    /// `delete_clause` for the update "delete where" (Oracle specific)
    pub delete_predicate: Option<Expr>,
}

impl Display for MergeUpdateExpr {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "SET {}", display_comma_separated(&self.assignments))?;
        if let Some(predicate) = self.update_predicate.as_ref() {
            write!(f, " WHERE {predicate}")?;
        }
        if let Some(predicate) = self.delete_predicate.as_ref() {
            write!(f, " DELETE WHERE {predicate}")?;
        }
        Ok(())
    }
}

/// An `OUTPUT` clause on `MERGE`, `INSERT`, `UPDATE`, or `DELETE` (MSSQL).
///
/// Example:
/// OUTPUT $action, deleted.* INTO dbo.temp_products;
/// <https://learn.microsoft.com/en-us/sql/t-sql/queries/output-clause-transact-sql>
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
pub enum OutputClause {
    /// `OUTPUT` clause
    Output {
        /// The `OUTPUT` token that starts the sub-expression.
        output_token: AttachedToken,
        /// The select items to output
        select_items: Vec<SelectItem>,
        /// Optional `INTO` table to direct the output
        into_table: Option<SelectInto>,
    },
    /// `RETURNING` clause
    Returning {
        /// The `RETURNING` token that starts the sub-expression.
        returning_token: AttachedToken,
        /// The select items to return
        select_items: Vec<SelectItem>,
    },
}

impl fmt::Display for OutputClause {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self {
            OutputClause::Output {
                output_token: _,
                select_items,
                into_table,
            } => {
                f.write_str("OUTPUT ")?;
                display_comma_separated(select_items).fmt(f)?;
                if let Some(into_table) = into_table {
                    f.write_str(" ")?;
                    into_table.fmt(f)?;
                }
                Ok(())
            }
            OutputClause::Returning {
                returning_token: _,
                select_items,
            } => {
                f.write_str("RETURNING ")?;
                display_comma_separated(select_items).fmt(f)
            }
        }
    }
}

/// A WHEN clause in a conditional multi-table INSERT.
///
/// Syntax:
/// ```sql
/// WHEN n1 > 100 THEN
///   INTO t1
///   INTO t2 (c1, c2) VALUES (n1, n2)
/// ```
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
pub struct MultiTableInsertWhenClause {
    /// The condition for this WHEN clause
    pub condition: Expr,
    /// The INTO clauses to execute when the condition is true
    pub into_clauses: Vec<MultiTableInsertIntoClause>,
}

impl Display for MultiTableInsertWhenClause {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "WHEN {} THEN", self.condition)?;
        for into_clause in &self.into_clauses {
            SpaceOrNewline.fmt(f)?;
            write!(f, "{}", into_clause)?;
        }
        Ok(())
    }
}

/// An INTO clause in a multi-table INSERT.
///
/// Syntax:
/// ```sql
/// INTO <target_table> [ ( <target_col_name> [ , ... ] ) ] [ VALUES ( { <source_col_name> | DEFAULT | NULL } [ , ... ] ) ]
/// ```
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
pub struct MultiTableInsertIntoClause {
    /// The target table
    pub table_name: ObjectName,
    /// The target columns (optional)
    pub columns: Vec<Ident>,
    /// The VALUES clause (optional)
    pub values: Option<MultiTableInsertValues>,
}

impl Display for MultiTableInsertIntoClause {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "INTO {}", self.table_name)?;
        if !self.columns.is_empty() {
            write!(f, " ({})", display_comma_separated(&self.columns))?;
        }
        if let Some(values) = &self.values {
            write!(f, " VALUES ({})", display_comma_separated(&values.values))?;
        }
        Ok(())
    }
}

/// The VALUES clause in a multi-table INSERT INTO clause.
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
pub struct MultiTableInsertValues {
    /// The values to insert (can be column references, DEFAULT, or NULL)
    pub values: Vec<MultiTableInsertValue>,
}

/// A value in a multi-table INSERT VALUES clause.
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
pub enum MultiTableInsertValue {
    /// A column reference or expression from the source
    Expr(Expr),
    /// The DEFAULT keyword
    Default,
}

impl Display for MultiTableInsertValue {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            MultiTableInsertValue::Expr(expr) => write!(f, "{}", expr),
            MultiTableInsertValue::Default => write!(f, "DEFAULT"),
        }
    }
}

/// The type of multi-table INSERT statement(Snowflake).
///
/// See: <https://docs.snowflake.com/en/sql-reference/sql/insert-multi-table>
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
pub enum MultiTableInsertType {
    /// `INSERT ALL` - all matching WHEN clauses are executed
    All,
    /// `INSERT FIRST` - only the first matching WHEN clause is executed
    First,
}

impl Display for MultiTableInsertType {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            MultiTableInsertType::All => write!(f, "ALL"),
            MultiTableInsertType::First => write!(f, "FIRST"),
        }
    }
}
