This is an automated email from the ASF dual-hosted git repository.

ppa pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/ignite-3.git


The following commit(s) were added to refs/heads/main by this push:
     new 171fa1220c IGNITE-20976 Sql. Fixed clone method for DDL/DML abstract 
syntax tree nodes (#2915)
171fa1220c is described below

commit 171fa1220c7834b63e5190898c850f9325559106
Author: Max Zhuravkov <[email protected]>
AuthorDate: Thu Dec 7 13:06:52 2023 +0200

    IGNITE-20976 Sql. Fixed clone method for DDL/DML abstract syntax tree nodes 
(#2915)
---
 modules/sql-engine/src/main/codegen/config.fmpp    |   1 +
 .../src/main/codegen/includes/parserImpls.ftl      |  12 +-
 .../prepare/ddl/DdlSqlToCommandConverter.java      |  12 +-
 .../engine/sql/IgniteAbstractSqlAlterTable.java    |  28 ++---
 .../sql/engine/sql/IgniteAbstractSqlAlterZone.java |  28 ++---
 ...mmitTransaction.java => IgniteDdlOperator.java} |  37 ++----
 .../sql/engine/sql/IgniteSqlAlterColumn.java       |  55 +++++---
 .../engine/sql/IgniteSqlAlterTableAddColumn.java   |  28 ++++-
 .../engine/sql/IgniteSqlAlterTableDropColumn.java  |  22 +++-
 .../sql/engine/sql/IgniteSqlAlterZoneRenameTo.java |  21 +++-
 .../sql/engine/sql/IgniteSqlAlterZoneSet.java      |  21 +++-
 .../sql/engine/sql/IgniteSqlCommitTransaction.java |  28 ++++-
 .../sql/engine/sql/IgniteSqlCreateIndex.java       |  48 +++++--
 .../sql/engine/sql/IgniteSqlCreateTable.java       |  39 ++++--
 .../sql/engine/sql/IgniteSqlCreateTableOption.java |  22 +++-
 .../sql/engine/sql/IgniteSqlCreateZone.java        |  37 ++++--
 .../internal/sql/engine/sql/IgniteSqlDelete.java   |  80 ++++++++++++
 .../sql/engine/sql/IgniteSqlDropIndex.java         |  44 +++++--
 ...iteSqlDropZone.java => IgniteSqlDropTable.java} |  67 ++++++----
 .../internal/sql/engine/sql/IgniteSqlDropZone.java |  42 +++++--
 .../internal/sql/engine/sql/IgniteSqlMerge.java    |  82 ++++++++++++
 .../internal/sql/engine/sql/IgniteSqlParser.java   |  29 ++++-
 ...nsaction.java => IgniteSqlSpecialOperator.java} |  33 ++---
 .../sql/engine/sql/IgniteSqlStartTransaction.java  |  38 ++++--
 .../internal/sql/engine/sql/IgniteSqlUpdate.java   |  87 +++++++++++++
 .../sql/engine/sql/IgniteSqlZoneOption.java        |  22 +++-
 .../sql/engine/planner/AbstractPlannerTest.java    |   2 +-
 .../sql/engine/sql/AbstractDdlParserTest.java      |  28 ++++-
 .../sql/DistributionZoneSqlDdlParserTest.java      |  22 ++--
 .../sql/engine/sql/IgniteSqlParserTest.java        |  47 +++++++
 .../engine/sql/SqlAlterColumnDdlParserTest.java    | 102 ++++++++-------
 .../internal/sql/engine/sql/SqlDdlParserTest.java  | 139 +++++++++++++++++++--
 .../sql/SqlTransactionControlParserTest.java       |  20 +--
 33 files changed, 1022 insertions(+), 301 deletions(-)

diff --git a/modules/sql-engine/src/main/codegen/config.fmpp 
b/modules/sql-engine/src/main/codegen/config.fmpp
index 8b96cb9876..b0f28d861c 100644
--- a/modules/sql-engine/src/main/codegen/config.fmpp
+++ b/modules/sql-engine/src/main/codegen/config.fmpp
@@ -43,6 +43,7 @@ data: {
       "org.apache.ignite.internal.sql.engine.sql.IgniteSqlCreateZone",
       "org.apache.ignite.internal.sql.engine.sql.IgniteSqlZoneOption",
       "org.apache.ignite.internal.sql.engine.sql.IgniteSqlDropIndex",
+      "org.apache.ignite.internal.sql.engine.sql.IgniteSqlDropTable",
       "org.apache.ignite.internal.sql.engine.sql.IgniteSqlDropZone",
       "org.apache.ignite.internal.sql.engine.sql.IgniteSqlStartTransaction",
       
"org.apache.ignite.internal.sql.engine.sql.IgniteSqlStartTransactionMode",
diff --git a/modules/sql-engine/src/main/codegen/includes/parserImpls.ftl 
b/modules/sql-engine/src/main/codegen/includes/parserImpls.ftl
index 902ed7cc54..ba18c9c40b 100644
--- a/modules/sql-engine/src/main/codegen/includes/parserImpls.ftl
+++ b/modules/sql-engine/src/main/codegen/includes/parserImpls.ftl
@@ -314,7 +314,7 @@ SqlDrop SqlDropTable(Span s, boolean replace) :
 }
 {
     <TABLE> ifExists = IfExistsOpt() id = CompoundIdentifier() {
-        return SqlDdlNodes.dropTable(s.end(this), ifExists, id);
+        return new IgniteSqlDropTable(s.end(this), ifExists, id);
     }
 }
 
@@ -445,26 +445,26 @@ SqlNode SqlAlterColumn(Span s, SqlIdentifier tableId, 
boolean ifExists) :
     (
         LOOKAHEAD(2)
         <SET> <DATA> <TYPE> { s.add(this); } type = DataTypeEx() nullable = 
NullableOptDefaultNull() dflt = DefaultLiteralOrNull() {
-            return new IgniteSqlAlterColumn(s.end(this), ifExists, tableId, 
id, type, dflt, nullable == null ? null : !nullable);
+            return new IgniteSqlAlterColumn(s.end(this), ifExists, tableId, 
id, type, false, dflt, nullable == null ? null : !nullable);
         }
     |
         LOOKAHEAD(2)
         <SET> <NOT> <NULL> {
-            return new IgniteSqlAlterColumn(s.end(this), ifExists, tableId, 
id, null, null, true);
+            return new IgniteSqlAlterColumn(s.end(this), ifExists, tableId, 
id, null, false, null, true);
         }
     |
         LOOKAHEAD(2)
         <DROP> <NOT> <NULL> {
-            return new IgniteSqlAlterColumn(s.end(this), ifExists, tableId, 
id, null, null, false);
+            return new IgniteSqlAlterColumn(s.end(this), ifExists, tableId, 
id, null, false, null, false);
         }
     |
         <SET> <DEFAULT_> { s.add(this); } dflt = Literal()
         {
-            return new IgniteSqlAlterColumn(s.end(this), ifExists, tableId, 
id, null, dflt, null);
+            return new IgniteSqlAlterColumn(s.end(this), ifExists, tableId, 
id, null, false, dflt, null);
         }
     |
         <DROP> <DEFAULT_> {
-            return new IgniteSqlAlterColumn(s.end(this), ifExists, tableId, 
id, null, SqlLiteral.createNull(s.end(this)), null);
+            return new IgniteSqlAlterColumn(s.end(this), ifExists, tableId, 
id, null, true, SqlLiteral.createNull(s.end(this)), null);
         }
     )
 }
diff --git 
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/ddl/DdlSqlToCommandConverter.java
 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/ddl/DdlSqlToCommandConverter.java
index 5d894ed495..8455b69071 100644
--- 
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/ddl/DdlSqlToCommandConverter.java
+++ 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/ddl/DdlSqlToCommandConverter.java
@@ -62,7 +62,6 @@ import org.apache.calcite.sql.SqlNodeList;
 import org.apache.calcite.sql.SqlUnknownLiteral;
 import org.apache.calcite.sql.ddl.SqlColumnDeclaration;
 import org.apache.calcite.sql.ddl.SqlDdlNodes;
-import org.apache.calcite.sql.ddl.SqlDropTable;
 import org.apache.calcite.sql.ddl.SqlKeyConstraint;
 import org.apache.calcite.sql.parser.SqlParserPos;
 import org.apache.calcite.sql.type.SqlTypeName;
@@ -84,6 +83,7 @@ import 
org.apache.ignite.internal.sql.engine.sql.IgniteSqlCreateTable;
 import org.apache.ignite.internal.sql.engine.sql.IgniteSqlCreateTableOption;
 import org.apache.ignite.internal.sql.engine.sql.IgniteSqlCreateZone;
 import org.apache.ignite.internal.sql.engine.sql.IgniteSqlDropIndex;
+import org.apache.ignite.internal.sql.engine.sql.IgniteSqlDropTable;
 import org.apache.ignite.internal.sql.engine.sql.IgniteSqlDropZone;
 import org.apache.ignite.internal.sql.engine.sql.IgniteSqlIndexType;
 import org.apache.ignite.internal.sql.engine.sql.IgniteSqlZoneOption;
@@ -200,8 +200,8 @@ public class DdlSqlToCommandConverter {
             return convertCreateTable((IgniteSqlCreateTable) ddlNode, ctx);
         }
 
-        if (ddlNode instanceof SqlDropTable) {
-            return convertDropTable((SqlDropTable) ddlNode, ctx);
+        if (ddlNode instanceof IgniteSqlDropTable) {
+            return convertDropTable((IgniteSqlDropTable) ddlNode, ctx);
         }
 
         if (ddlNode instanceof IgniteSqlAlterTableAddColumn) {
@@ -479,11 +479,11 @@ public class DdlSqlToCommandConverter {
      * @param dropTblNode Root node of the given AST.
      * @param ctx Planning context.
      */
-    private DropTableCommand convertDropTable(SqlDropTable dropTblNode, 
PlanningContext ctx) {
+    private DropTableCommand convertDropTable(IgniteSqlDropTable dropTblNode, 
PlanningContext ctx) {
         DropTableCommand dropTblCmd = new DropTableCommand();
 
-        dropTblCmd.schemaName(deriveSchemaName(dropTblNode.name, ctx));
-        dropTblCmd.tableName(deriveObjectName(dropTblNode.name, ctx, 
"tableName"));
+        dropTblCmd.schemaName(deriveSchemaName(dropTblNode.name(), ctx));
+        dropTblCmd.tableName(deriveObjectName(dropTblNode.name(), ctx, 
"tableName"));
         dropTblCmd.ifTableExists(dropTblNode.ifExists);
 
         return dropTblCmd;
diff --git 
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/sql/IgniteAbstractSqlAlterTable.java
 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/sql/IgniteAbstractSqlAlterTable.java
index 7e8547a78d..413875103f 100644
--- 
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/sql/IgniteAbstractSqlAlterTable.java
+++ 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/sql/IgniteAbstractSqlAlterTable.java
@@ -20,9 +20,6 @@ package org.apache.ignite.internal.sql.engine.sql;
 import java.util.Objects;
 import org.apache.calcite.sql.SqlDdl;
 import org.apache.calcite.sql.SqlIdentifier;
-import org.apache.calcite.sql.SqlKind;
-import org.apache.calcite.sql.SqlOperator;
-import org.apache.calcite.sql.SqlSpecialOperator;
 import org.apache.calcite.sql.SqlWriter;
 import org.apache.calcite.sql.parser.SqlParserPos;
 
@@ -33,30 +30,24 @@ public abstract class IgniteAbstractSqlAlterTable extends 
SqlDdl {
     /** SqlNode identifier name. */
     protected final SqlIdentifier name;
 
-    /** If exist flag. */
-    protected final boolean ifExists;
-
-    /** Alter operator. */
-    private static final SqlOperator OPERATOR =
-            new SqlSpecialOperator("ALTER TABLE", SqlKind.ALTER_TABLE);
-
     /** Constructor. */
-    public IgniteAbstractSqlAlterTable(SqlParserPos pos, boolean ifExists, 
SqlIdentifier tblName) {
-        super(OPERATOR, pos);
-        this.ifExists = ifExists;
+    protected IgniteAbstractSqlAlterTable(IgniteDdlOperator operator, 
SqlParserPos pos, SqlIdentifier tblName) {
+        super(operator, pos);
         name = Objects.requireNonNull(tblName, "table name");
     }
 
     /** {@inheritDoc} */
-    @Override public SqlOperator getOperator() {
-        return OPERATOR;
+    @Override
+    public IgniteDdlOperator getOperator() {
+        return (IgniteDdlOperator) super.getOperator();
     }
 
     /** {@inheritDoc} */
-    @Override public void unparse(SqlWriter writer, int leftPrec, int 
rightPrec) {
+    @Override
+    public void unparse(SqlWriter writer, int leftPrec, int rightPrec) {
         writer.keyword(getOperator().getName());
 
-        if (ifExists) {
+        if (ifExists()) {
             writer.keyword("IF EXISTS");
         }
 
@@ -85,6 +76,7 @@ public abstract class IgniteAbstractSqlAlterTable extends 
SqlDdl {
      * @return Whether the IF EXISTS is specified.
      */
     public boolean ifExists() {
-        return ifExists;
+        IgniteDdlOperator operator = getOperator();
+        return operator.existFlag();
     }
 }
diff --git 
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/sql/IgniteAbstractSqlAlterZone.java
 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/sql/IgniteAbstractSqlAlterZone.java
index 70a45270fc..665cc6f7bd 100644
--- 
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/sql/IgniteAbstractSqlAlterZone.java
+++ 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/sql/IgniteAbstractSqlAlterZone.java
@@ -19,9 +19,6 @@ package org.apache.ignite.internal.sql.engine.sql;
 
 import org.apache.calcite.sql.SqlDdl;
 import org.apache.calcite.sql.SqlIdentifier;
-import org.apache.calcite.sql.SqlKind;
-import org.apache.calcite.sql.SqlOperator;
-import org.apache.calcite.sql.SqlSpecialOperator;
 import org.apache.calcite.sql.SqlWriter;
 import org.apache.calcite.sql.parser.SqlParserPos;
 
@@ -30,19 +27,12 @@ import org.apache.calcite.sql.parser.SqlParserPos;
  */
 public abstract class IgniteAbstractSqlAlterZone extends SqlDdl {
 
-    /** Alter operator. */
-    private static final SqlOperator OPERATOR =
-            new SqlSpecialOperator("ALTER ZONE", SqlKind.OTHER_DDL);
-
     protected final SqlIdentifier name;
 
-    protected final boolean ifExists;
-
     /** Constructor. */
-    public IgniteAbstractSqlAlterZone(SqlParserPos pos, SqlIdentifier name, 
boolean ifExists) {
-        super(OPERATOR, pos);
+    protected IgniteAbstractSqlAlterZone(IgniteDdlOperator operator, 
SqlParserPos pos, SqlIdentifier name) {
+        super(operator, pos);
         this.name = name;
-        this.ifExists = ifExists;
     }
 
     /** The name of a distribution zone to alter. **/
@@ -51,10 +41,17 @@ public abstract class IgniteAbstractSqlAlterZone extends 
SqlDdl {
     }
 
     /** {@inheritDoc} */
-    @Override public void unparse(SqlWriter writer, int leftPrec, int 
rightPrec) {
+    @Override
+    public IgniteDdlOperator getOperator() {
+        return (IgniteDdlOperator) super.getOperator();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void unparse(SqlWriter writer, int leftPrec, int rightPrec) {
         writer.keyword(getOperator().getName());
 
-        if (ifExists) {
+        if (ifExists()) {
             writer.keyword("IF EXISTS");
         }
 
@@ -71,6 +68,7 @@ public abstract class IgniteAbstractSqlAlterZone extends 
SqlDdl {
 
     /** Returns whether IF EXISTS was specified or not. **/
     public boolean ifExists() {
-        return ifExists;
+        IgniteDdlOperator operator = (IgniteDdlOperator) getOperator();
+        return operator.existFlag();
     }
 }
diff --git 
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/sql/IgniteSqlCommitTransaction.java
 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/sql/IgniteDdlOperator.java
similarity index 59%
copy from 
modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/sql/IgniteSqlCommitTransaction.java
copy to 
modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/sql/IgniteDdlOperator.java
index 7dd51d317c..39bfe6e06e 100644
--- 
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/sql/IgniteSqlCommitTransaction.java
+++ 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/sql/IgniteDdlOperator.java
@@ -17,42 +17,31 @@
 
 package org.apache.ignite.internal.sql.engine.sql;
 
-import java.util.List;
 import org.apache.calcite.sql.SqlCall;
 import org.apache.calcite.sql.SqlKind;
+import org.apache.calcite.sql.SqlLiteral;
 import org.apache.calcite.sql.SqlNode;
-import org.apache.calcite.sql.SqlOperator;
 import org.apache.calcite.sql.SqlSpecialOperator;
-import org.apache.calcite.sql.SqlWriter;
 import org.apache.calcite.sql.parser.SqlParserPos;
+import org.checkerframework.checker.nullness.qual.Nullable;
 
-/**
- *  Parse tree for {@code COMMIT} statement.
- */
-public class IgniteSqlCommitTransaction extends SqlCall {
-
-    /** Commit transaction operator. */
-    private static final SqlOperator OPERATOR = new 
SqlSpecialOperator("COMMIT", SqlKind.COMMIT);
+/** Base class for DDL operators with exists/not exists flag. */
+public abstract class IgniteDdlOperator extends SqlSpecialOperator {
 
-    public IgniteSqlCommitTransaction(SqlParserPos pos) {
-        super(pos);
-    }
+    private final boolean existFlag;
 
-    /** {@inheritDoc} */
-    @Override
-    public SqlOperator getOperator() {
-        return OPERATOR;
+    /** Constructor. */
+    public IgniteDdlOperator(String name, SqlKind kind, boolean existFlag) {
+        super(name, kind);
+        this.existFlag = existFlag;
     }
 
-    /** {@inheritDoc} */
-    @Override
-    public List<SqlNode> getOperandList() {
-        return List.of();
+    /** Exist/not exist flag. */
+    public boolean existFlag() {
+        return existFlag;
     }
 
     /** {@inheritDoc} */
     @Override
-    public void unparse(SqlWriter writer, int leftPrec, int rightPrec) {
-        writer.keyword(getOperator().getName());
-    }
+    public abstract SqlCall createCall(@Nullable SqlLiteral functionQualifier, 
SqlParserPos pos, @Nullable SqlNode... operands);
 }
diff --git 
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/sql/IgniteSqlAlterColumn.java
 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/sql/IgniteSqlAlterColumn.java
index 2f32d4b513..346111e97a 100644
--- 
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/sql/IgniteSqlAlterColumn.java
+++ 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/sql/IgniteSqlAlterColumn.java
@@ -18,13 +18,14 @@
 package org.apache.ignite.internal.sql.engine.sql;
 
 import java.util.List;
+import org.apache.calcite.sql.SqlCall;
 import org.apache.calcite.sql.SqlDataTypeSpec;
 import org.apache.calcite.sql.SqlIdentifier;
+import org.apache.calcite.sql.SqlKind;
 import org.apache.calcite.sql.SqlLiteral;
 import org.apache.calcite.sql.SqlNode;
 import org.apache.calcite.sql.SqlWriter;
 import org.apache.calcite.sql.parser.SqlParserPos;
-import org.apache.calcite.sql.type.SqlTypeName;
 import org.apache.calcite.util.ImmutableNullableList;
 import org.jetbrains.annotations.Nullable;
 
@@ -32,8 +33,32 @@ import org.jetbrains.annotations.Nullable;
  * Parse tree for {@code ALTER TABLE ... ALTER COLUMN} statement.
  */
 public class IgniteSqlAlterColumn extends IgniteAbstractSqlAlterTable {
+
+    /** ALTER TABLE .. ALTER COLUMN operator. */
+    protected static class Operator extends IgniteDdlOperator {
+
+        private final boolean dropDefault;
+
+        private final Boolean notNull;
+
+        /** Constructor. */
+        protected Operator(boolean ifExists, boolean dropDefault, Boolean 
notNull) {
+            super("ALTER TABLE", SqlKind.ALTER_TABLE, ifExists);
+            this.dropDefault = dropDefault;
+            this.notNull = notNull;
+        }
+
+        /** {@inheritDoc} */
+        @Override
+        public SqlCall createCall(@Nullable SqlLiteral functionQualifier, 
SqlParserPos pos, @Nullable SqlNode... operands) {
+            return new IgniteSqlAlterColumn(pos, existFlag(), (SqlIdentifier) 
operands[0], (SqlIdentifier) operands[1],
+                    (SqlDataTypeSpec) operands[2], dropDefault, operands[3], 
notNull);
+        }
+    }
+
     private final SqlIdentifier columnName;
     private final SqlDataTypeSpec type;
+    private final boolean dropDefault;
     private final SqlNode dflt;
     private final Boolean notNull;
 
@@ -43,14 +68,16 @@ public class IgniteSqlAlterColumn extends 
IgniteAbstractSqlAlterTable {
             boolean ifExists,
             SqlIdentifier tblName,
             SqlIdentifier columnName,
-            SqlDataTypeSpec type,
-            SqlNode dflt,
-            Boolean notNull
+            @Nullable SqlDataTypeSpec type,
+            boolean dropDefault,
+            @Nullable SqlNode dflt,
+            @Nullable Boolean notNull
     ) {
-        super(pos, ifExists, tblName);
+        super(new Operator(ifExists, dropDefault, notNull), pos, tblName);
 
         this.columnName = columnName;
         this.type = type;
+        this.dropDefault = dropDefault;
         this.dflt = dflt;
         this.notNull = notNull;
     }
@@ -89,12 +116,14 @@ public class IgniteSqlAlterColumn extends 
IgniteAbstractSqlAlterTable {
     }
 
     /** {@inheritDoc} */
-    @Override public List<SqlNode> getOperandList() {
+    @Override
+    public List<SqlNode> getOperandList() {
         return ImmutableNullableList.of(name, columnName, type, dflt);
     }
 
     /** {@inheritDoc} */
-    @Override protected void unparseAlterTableOperation(SqlWriter writer, int 
leftPrec, int rightPrec) {
+    @Override
+    protected void unparseAlterTableOperation(SqlWriter writer, int leftPrec, 
int rightPrec) {
         writer.keyword("ALTER");
         writer.keyword("COLUMN");
 
@@ -127,14 +156,12 @@ public class IgniteSqlAlterColumn extends 
IgniteAbstractSqlAlterTable {
             writer.keyword("NOT NULL");
         }
 
-        if (dflt != null) {
-            if (dflt instanceof SqlLiteral && ((SqlLiteral) 
dflt).getTypeName() == SqlTypeName.NULL) {
-                writer.keyword("DROP DEFAULT");
-            } else {
-                writer.keyword("SET DEFAULT");
+        if (dropDefault) {
+            writer.keyword("DROP DEFAULT");
+        } else if (dflt != null) {
+            writer.keyword("SET DEFAULT");
 
-                dflt.unparse(writer, leftPrec, rightPrec);
-            }
+            dflt.unparse(writer, leftPrec, rightPrec);
         }
     }
 }
diff --git 
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/sql/IgniteSqlAlterTableAddColumn.java
 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/sql/IgniteSqlAlterTableAddColumn.java
index 641ae536f3..92b5502b73 100644
--- 
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/sql/IgniteSqlAlterTableAddColumn.java
+++ 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/sql/IgniteSqlAlterTableAddColumn.java
@@ -19,34 +19,56 @@ package org.apache.ignite.internal.sql.engine.sql;
 
 import java.util.List;
 import java.util.Objects;
+import org.apache.calcite.sql.SqlCall;
 import org.apache.calcite.sql.SqlIdentifier;
+import org.apache.calcite.sql.SqlKind;
+import org.apache.calcite.sql.SqlLiteral;
 import org.apache.calcite.sql.SqlNode;
 import org.apache.calcite.sql.SqlNodeList;
 import org.apache.calcite.sql.SqlWriter;
 import org.apache.calcite.sql.parser.SqlParserPos;
 import org.apache.calcite.util.ImmutableNullableList;
+import org.checkerframework.checker.nullness.qual.Nullable;
 
 /**
  * Parse tree for {@code ALTER TABLE ... ADD COLUMN} statement.
  */
 public class IgniteSqlAlterTableAddColumn extends IgniteAbstractSqlAlterTable {
+
+    /** ALTER TABLE ... ADD COLUMN operator. */
+    protected static class Operator extends IgniteDdlOperator {
+
+        /** Constructor. */
+        protected Operator(boolean existFlag) {
+            super("ALTER TABLE", SqlKind.ALTER_TABLE, existFlag);
+        }
+
+        /** {@inheritDoc} */
+        @Override
+        public SqlCall createCall(@Nullable SqlLiteral functionQualifier, 
SqlParserPos pos, @Nullable SqlNode... operands) {
+            return new IgniteSqlAlterTableAddColumn(pos, existFlag(), 
(SqlIdentifier) operands[0], (SqlNodeList) operands[1]);
+        }
+    }
+
     /** Introduced columns. */
     private final SqlNodeList columns;
 
     /** Constructor. */
     public IgniteSqlAlterTableAddColumn(SqlParserPos pos, boolean ifExists, 
SqlIdentifier tblName,
             SqlNodeList columns) {
-        super(pos, ifExists, tblName);
+        super(new Operator(ifExists), pos, tblName);
         this.columns = Objects.requireNonNull(columns, "columns list");
     }
 
     /** {@inheritDoc} */
-    @Override public List<SqlNode> getOperandList() {
+    @Override
+    public List<SqlNode> getOperandList() {
         return ImmutableNullableList.of(name, columns);
     }
 
     /** {@inheritDoc} */
-    @Override protected void unparseAlterTableOperation(SqlWriter writer, int 
leftPrec, int rightPrec) {
+    @Override
+    protected void unparseAlterTableOperation(SqlWriter writer, int leftPrec, 
int rightPrec) {
         writer.keyword("ADD");
         writer.keyword("COLUMN");
 
diff --git 
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/sql/IgniteSqlAlterTableDropColumn.java
 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/sql/IgniteSqlAlterTableDropColumn.java
index b5b26640ec..293c643253 100644
--- 
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/sql/IgniteSqlAlterTableDropColumn.java
+++ 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/sql/IgniteSqlAlterTableDropColumn.java
@@ -19,24 +19,44 @@ package org.apache.ignite.internal.sql.engine.sql;
 
 import java.util.List;
 import java.util.Objects;
+import org.apache.calcite.sql.SqlCall;
 import org.apache.calcite.sql.SqlIdentifier;
+import org.apache.calcite.sql.SqlKind;
+import org.apache.calcite.sql.SqlLiteral;
 import org.apache.calcite.sql.SqlNode;
 import org.apache.calcite.sql.SqlNodeList;
 import org.apache.calcite.sql.SqlWriter;
 import org.apache.calcite.sql.parser.SqlParserPos;
 import org.apache.calcite.util.ImmutableNullableList;
+import org.checkerframework.checker.nullness.qual.Nullable;
 
 /**
  * Parse tree for {@code ALTER TABLE ... DROP COLUMN} statement.
  */
 public class IgniteSqlAlterTableDropColumn extends IgniteAbstractSqlAlterTable 
{
+
+    /** ALTER TABLE operator. */
+    protected static class Operator extends IgniteDdlOperator {
+
+        /** Constructor. */
+        protected Operator(boolean existFlag) {
+            super("ALTER TABLE", SqlKind.ALTER_TABLE, existFlag);
+        }
+
+        /** {@inheritDoc} */
+        @Override
+        public SqlCall createCall(@Nullable SqlLiteral functionQualifier, 
SqlParserPos pos, @Nullable SqlNode... operands) {
+            return new IgniteSqlAlterTableDropColumn(pos, existFlag(), 
(SqlIdentifier) operands[0], (SqlNodeList) operands[1]);
+        }
+    }
+
     /** Columns to drop. */
     private final SqlNodeList columns;
 
     /** Constructor. */
     public IgniteSqlAlterTableDropColumn(SqlParserPos pos, boolean ifExists, 
SqlIdentifier tblName,
             SqlNodeList columns) {
-        super(pos, ifExists, tblName);
+        super(new Operator(ifExists), pos, tblName);
         this.columns = Objects.requireNonNull(columns, "columns list");
     }
 
diff --git 
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/sql/IgniteSqlAlterZoneRenameTo.java
 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/sql/IgniteSqlAlterZoneRenameTo.java
index 20c7a4efaa..fa8fb621bc 100644
--- 
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/sql/IgniteSqlAlterZoneRenameTo.java
+++ 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/sql/IgniteSqlAlterZoneRenameTo.java
@@ -19,22 +19,41 @@ package org.apache.ignite.internal.sql.engine.sql;
 
 import java.util.List;
 import java.util.Objects;
+import org.apache.calcite.sql.SqlCall;
 import org.apache.calcite.sql.SqlIdentifier;
+import org.apache.calcite.sql.SqlKind;
+import org.apache.calcite.sql.SqlLiteral;
 import org.apache.calcite.sql.SqlNode;
 import org.apache.calcite.sql.SqlWriter;
 import org.apache.calcite.sql.parser.SqlParserPos;
 import org.apache.calcite.util.ImmutableNullableList;
+import org.checkerframework.checker.nullness.qual.Nullable;
 
 /**
  * Parse tree for {@code ALTER ZONE RENAME TO} statement.
  */
 public class IgniteSqlAlterZoneRenameTo extends IgniteAbstractSqlAlterZone {
 
+    /** ALTER ZONE RENAME TO operator. */
+    protected static class Operator extends IgniteDdlOperator {
+
+        /** Constructor. */
+        protected Operator(boolean existsFlag) {
+            super("ALTER ZONE", SqlKind.OTHER_DDL, existsFlag);
+        }
+
+        /** {@inheritDoc} */
+        @Override
+        public SqlCall createCall(@Nullable SqlLiteral functionQualifier, 
SqlParserPos pos, @Nullable SqlNode... operands) {
+            return new IgniteSqlAlterZoneRenameTo(pos, (SqlIdentifier) 
operands[0], (SqlIdentifier) operands[1], existFlag());
+        }
+    }
+
     private final SqlIdentifier newName;
 
     /** Constructor. */
     public IgniteSqlAlterZoneRenameTo(SqlParserPos pos, SqlIdentifier name, 
SqlIdentifier newName, boolean ifExists) {
-        super(pos, name, ifExists);
+        super(new Operator(ifExists), pos, name);
         this.newName = Objects.requireNonNull(newName, "newName");
     }
 
diff --git 
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/sql/IgniteSqlAlterZoneSet.java
 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/sql/IgniteSqlAlterZoneSet.java
index 432ad3a836..f3ba2d956a 100644
--- 
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/sql/IgniteSqlAlterZoneSet.java
+++ 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/sql/IgniteSqlAlterZoneSet.java
@@ -19,23 +19,42 @@ package org.apache.ignite.internal.sql.engine.sql;
 
 import java.util.List;
 import java.util.Objects;
+import org.apache.calcite.sql.SqlCall;
 import org.apache.calcite.sql.SqlIdentifier;
+import org.apache.calcite.sql.SqlKind;
+import org.apache.calcite.sql.SqlLiteral;
 import org.apache.calcite.sql.SqlNode;
 import org.apache.calcite.sql.SqlNodeList;
 import org.apache.calcite.sql.SqlWriter;
 import org.apache.calcite.sql.parser.SqlParserPos;
 import org.apache.calcite.util.ImmutableNullableList;
+import org.checkerframework.checker.nullness.qual.Nullable;
 
 /**
  * Parse tree for {@code ALTER ZONE SET} statement.
  */
 public class IgniteSqlAlterZoneSet extends IgniteAbstractSqlAlterZone {
 
+    /** ALTER ZONE SET operator. */
+    protected static class Operator extends IgniteDdlOperator {
+
+        /** Constructor. */
+        private Operator(boolean existFlag) {
+            super("ALTER ZONE", SqlKind.OTHER_DDL, existFlag);
+        }
+
+        /** {@inheritDoc} */
+        @Override
+        public SqlCall createCall(@Nullable SqlLiteral functionQualifier, 
SqlParserPos pos, @Nullable SqlNode... operands) {
+            return new IgniteSqlAlterZoneSet(pos, (SqlIdentifier) operands[0], 
(SqlNodeList) operands[1], existFlag());
+        }
+    }
+
     private final SqlNodeList optionList;
 
     /** Constructor. */
     public IgniteSqlAlterZoneSet(SqlParserPos pos, SqlIdentifier name, 
SqlNodeList optionList, boolean ifExists) {
-        super(pos, name, ifExists);
+        super(new Operator(ifExists), pos, name);
         this.optionList = Objects.requireNonNull(optionList, "optionList");
     }
 
diff --git 
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/sql/IgniteSqlCommitTransaction.java
 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/sql/IgniteSqlCommitTransaction.java
index 7dd51d317c..5e2f5bab93 100644
--- 
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/sql/IgniteSqlCommitTransaction.java
+++ 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/sql/IgniteSqlCommitTransaction.java
@@ -20,28 +20,46 @@ package org.apache.ignite.internal.sql.engine.sql;
 import java.util.List;
 import org.apache.calcite.sql.SqlCall;
 import org.apache.calcite.sql.SqlKind;
+import org.apache.calcite.sql.SqlLiteral;
 import org.apache.calcite.sql.SqlNode;
 import org.apache.calcite.sql.SqlOperator;
-import org.apache.calcite.sql.SqlSpecialOperator;
 import org.apache.calcite.sql.SqlWriter;
 import org.apache.calcite.sql.parser.SqlParserPos;
+import org.checkerframework.checker.nullness.qual.Nullable;
 
 /**
- *  Parse tree for {@code COMMIT} statement.
+ * Parse tree for {@code COMMIT} statement.
  */
 public class IgniteSqlCommitTransaction extends SqlCall {
 
+    /** COMMIT operator. */
+    protected static class Operator extends IgniteSqlSpecialOperator {
+
+        /** Constructor. */
+        protected Operator() {
+            super("COMMIT", SqlKind.COMMIT);
+        }
+
+        /** {@inheritDoc} */
+        @Override
+        public SqlCall createCall(@Nullable SqlLiteral functionQualifier, 
SqlParserPos pos,
+                @Nullable SqlNode... operands) {
+            return new IgniteSqlCommitTransaction(pos);
+        }
+    }
+
     /** Commit transaction operator. */
-    private static final SqlOperator OPERATOR = new 
SqlSpecialOperator("COMMIT", SqlKind.COMMIT);
+    private static final SqlOperator OPERATOR = new Operator();
 
+    /** Constructor. */
     public IgniteSqlCommitTransaction(SqlParserPos pos) {
         super(pos);
     }
 
     /** {@inheritDoc} */
     @Override
-    public SqlOperator getOperator() {
-        return OPERATOR;
+    public IgniteSqlSpecialOperator getOperator() {
+        return (IgniteSqlSpecialOperator) OPERATOR;
     }
 
     /** {@inheritDoc} */
diff --git 
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/sql/IgniteSqlCreateIndex.java
 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/sql/IgniteSqlCreateIndex.java
index 3a7986f4e6..f7917069a4 100644
--- 
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/sql/IgniteSqlCreateIndex.java
+++ 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/sql/IgniteSqlCreateIndex.java
@@ -23,22 +23,42 @@ import org.apache.calcite.sql.SqlCall;
 import org.apache.calcite.sql.SqlCreate;
 import org.apache.calcite.sql.SqlIdentifier;
 import org.apache.calcite.sql.SqlKind;
+import org.apache.calcite.sql.SqlLiteral;
 import org.apache.calcite.sql.SqlNode;
 import org.apache.calcite.sql.SqlNodeList;
-import org.apache.calcite.sql.SqlOperator;
-import org.apache.calcite.sql.SqlSpecialOperator;
 import org.apache.calcite.sql.SqlWriter;
 import org.apache.calcite.sql.parser.SqlParserPos;
 import org.apache.calcite.util.ImmutableNullableList;
+import org.checkerframework.checker.nullness.qual.Nullable;
 
 /**
  * Parse tree for {@code CREATE INDEX} statement.
  */
 public class IgniteSqlCreateIndex extends SqlCreate {
+
+    /** CREATE INDEX operator. */
+    protected static class Operator extends IgniteDdlOperator {
+
+        private final IgniteSqlIndexType indexType;
+
+        /** Constructor. */
+        protected Operator(IgniteSqlIndexType indexType, boolean existFlag) {
+            super("CREATE INDEX", SqlKind.CREATE_INDEX, existFlag);
+            this.indexType = indexType;
+        }
+
+        /** {@inheritDoc} */
+        @Override
+        public SqlCall createCall(@Nullable SqlLiteral functionQualifier, 
SqlParserPos pos, @Nullable SqlNode... operands) {
+            return new IgniteSqlCreateIndex(pos, existFlag(), (SqlIdentifier) 
operands[0], (SqlIdentifier) operands[1],
+                    indexType, (SqlNodeList) operands[2]);
+        }
+    }
+
     /** Idx name. */
     private final SqlIdentifier idxName;
 
-    /** Table name.  */
+    /** Table name. */
     private final SqlIdentifier tblName;
 
     private final IgniteSqlIndexType type;
@@ -46,14 +66,11 @@ public class IgniteSqlCreateIndex extends SqlCreate {
     /** Columns involved. */
     private final SqlNodeList columnList;
 
-    /** SqlOperator type. */
-    private static final SqlOperator OPERATOR =
-            new SqlSpecialOperator("CREATE INDEX", SqlKind.CREATE_INDEX);
 
     /** Creates a SqlCreateIndex. */
     public IgniteSqlCreateIndex(SqlParserPos pos, boolean ifNotExists, 
SqlIdentifier idxName, SqlIdentifier tblName,
             IgniteSqlIndexType type, SqlNodeList columnList) {
-        super(OPERATOR, pos, false, ifNotExists);
+        super(new Operator(type, ifNotExists), pos, false, ifNotExists);
         this.idxName = Objects.requireNonNull(idxName, "index name");
         this.tblName = Objects.requireNonNull(tblName, "table name");
         this.type = Objects.requireNonNull(type, "type");
@@ -61,17 +78,25 @@ public class IgniteSqlCreateIndex extends SqlCreate {
     }
 
     /** {@inheritDoc} */
-    @Override public List<SqlNode> getOperandList() {
+    @Override
+    public IgniteDdlOperator getOperator() {
+        return (IgniteDdlOperator) super.getOperator();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public List<SqlNode> getOperandList() {
         return ImmutableNullableList.of(idxName, tblName, columnList);
     }
 
     /** {@inheritDoc} */
-    @Override public void unparse(SqlWriter writer, int leftPrec, int 
rightPrec) {
+    @Override
+    public void unparse(SqlWriter writer, int leftPrec, int rightPrec) {
         writer.keyword("CREATE");
 
         writer.keyword("INDEX");
 
-        if (ifNotExists) {
+        if (ifNotExists()) {
             writer.keyword("IF NOT EXISTS");
         }
 
@@ -126,6 +151,7 @@ public class IgniteSqlCreateIndex extends SqlCreate {
     }
 
     public boolean ifNotExists() {
-        return ifNotExists;
+        Operator operator = (Operator) getOperator();
+        return operator.existFlag();
     }
 }
diff --git 
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/sql/IgniteSqlCreateTable.java
 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/sql/IgniteSqlCreateTable.java
index a907b0b90b..68cdfdb86e 100644
--- 
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/sql/IgniteSqlCreateTable.java
+++ 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/sql/IgniteSqlCreateTable.java
@@ -19,13 +19,13 @@ package org.apache.ignite.internal.sql.engine.sql;
 
 import java.util.List;
 import java.util.Objects;
+import org.apache.calcite.sql.SqlCall;
 import org.apache.calcite.sql.SqlCreate;
 import org.apache.calcite.sql.SqlIdentifier;
 import org.apache.calcite.sql.SqlKind;
+import org.apache.calcite.sql.SqlLiteral;
 import org.apache.calcite.sql.SqlNode;
 import org.apache.calcite.sql.SqlNodeList;
-import org.apache.calcite.sql.SqlOperator;
-import org.apache.calcite.sql.SqlSpecialOperator;
 import org.apache.calcite.sql.SqlWriter;
 import org.apache.calcite.sql.parser.SqlParserPos;
 import org.apache.calcite.util.ImmutableNullableList;
@@ -35,6 +35,24 @@ import org.jetbrains.annotations.Nullable;
  * Parse tree for {@code CREATE TABLE} statement with Ignite specific features.
  */
 public class IgniteSqlCreateTable extends SqlCreate {
+
+    /** CREATE TABLE operator. */
+    protected static class Operator extends IgniteDdlOperator {
+
+        /** Constructor. */
+        protected Operator(boolean existFlag) {
+            super("CREATE TABLE", SqlKind.CREATE_TABLE, existFlag);
+        }
+
+        /** {@inheritDoc} */
+        @Override
+        public SqlCall createCall(@Nullable SqlLiteral functionQualifier, 
SqlParserPos pos,
+                @Nullable SqlNode... operands) {
+            return new IgniteSqlCreateTable(pos, existFlag(), (SqlIdentifier) 
operands[0], (SqlNodeList) operands[1],
+                    (SqlNodeList) operands[2], (SqlNodeList) operands[3]);
+        }
+    }
+
     private final SqlIdentifier name;
 
     private final @Nullable SqlNodeList columnList;
@@ -43,8 +61,6 @@ public class IgniteSqlCreateTable extends SqlCreate {
 
     private final @Nullable SqlNodeList createOptionList;
 
-    private static final SqlOperator OPERATOR = new SqlSpecialOperator("CREATE 
TABLE", SqlKind.CREATE_TABLE);
-
     /** Creates a SqlCreateTable. */
     public IgniteSqlCreateTable(
             SqlParserPos pos,
@@ -54,7 +70,7 @@ public class IgniteSqlCreateTable extends SqlCreate {
             @Nullable SqlNodeList colocationColumns,
             @Nullable SqlNodeList createOptionList
     ) {
-        super(OPERATOR, pos, false, ifNotExists);
+        super(new Operator(ifNotExists), pos, false, ifNotExists);
 
         this.name = Objects.requireNonNull(name, "name");
         this.columnList = columnList;
@@ -62,11 +78,17 @@ public class IgniteSqlCreateTable extends SqlCreate {
         this.createOptionList = createOptionList;
     }
 
+    /** {@inheritDoc} */
+    @Override
+    public IgniteDdlOperator getOperator() {
+        return (IgniteDdlOperator) super.getOperator();
+    }
+
     /** {@inheritDoc} */
     @SuppressWarnings("nullness")
     @Override
     public List<SqlNode> getOperandList() {
-        return ImmutableNullableList.of(name, columnList, createOptionList);
+        return ImmutableNullableList.of(name, columnList, colocationColumns, 
createOptionList);
     }
 
     /** {@inheritDoc} */
@@ -74,7 +96,7 @@ public class IgniteSqlCreateTable extends SqlCreate {
     public void unparse(SqlWriter writer, int leftPrec, int rightPrec) {
         writer.keyword("CREATE");
         writer.keyword("TABLE");
-        if (ifNotExists) {
+        if (ifNotExists()) {
             writer.keyword("IF NOT EXISTS");
         }
 
@@ -135,6 +157,7 @@ public class IgniteSqlCreateTable extends SqlCreate {
      * Get whether the IF NOT EXISTS is specified.
      */
     public boolean ifNotExists() {
-        return ifNotExists;
+        Operator operator = (Operator) getOperator();
+        return operator.existFlag();
     }
 }
diff --git 
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/sql/IgniteSqlCreateTableOption.java
 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/sql/IgniteSqlCreateTableOption.java
index a0b5cb2f66..a9e92afbbb 100644
--- 
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/sql/IgniteSqlCreateTableOption.java
+++ 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/sql/IgniteSqlCreateTableOption.java
@@ -21,20 +21,36 @@ import java.util.List;
 import org.apache.calcite.sql.SqlCall;
 import org.apache.calcite.sql.SqlIdentifier;
 import org.apache.calcite.sql.SqlKind;
+import org.apache.calcite.sql.SqlLiteral;
 import org.apache.calcite.sql.SqlNode;
 import org.apache.calcite.sql.SqlOperator;
-import org.apache.calcite.sql.SqlSpecialOperator;
 import org.apache.calcite.sql.SqlWriter;
 import org.apache.calcite.sql.parser.SqlParserPos;
 import org.apache.calcite.sql.util.SqlVisitor;
 import org.apache.calcite.sql.validate.SqlValidator;
 import org.apache.calcite.sql.validate.SqlValidatorScope;
 import org.apache.calcite.util.Litmus;
+import org.checkerframework.checker.nullness.qual.Nullable;
 
 /** An AST node representing option to create table with. */
 public class IgniteSqlCreateTableOption extends SqlCall {
-    private static final SqlOperator OPERATOR =
-            new SqlSpecialOperator("TableOption", SqlKind.OTHER);
+
+    /** Table option. */
+    protected static class Operator extends IgniteSqlSpecialOperator {
+
+        /** Constructor. */
+        protected Operator() {
+            super("TableOption", SqlKind.OTHER);
+        }
+
+        /** {@inheritDoc} */
+        @Override
+        public SqlCall createCall(@Nullable SqlLiteral functionQualifier, 
SqlParserPos pos, @Nullable SqlNode... operands) {
+            return new IgniteSqlCreateTableOption((SqlIdentifier) operands[0], 
operands[1], pos);
+        }
+    }
+
+    private static final SqlOperator OPERATOR = new Operator();
 
     /** Option key. */
     private final SqlIdentifier key;
diff --git 
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/sql/IgniteSqlCreateZone.java
 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/sql/IgniteSqlCreateZone.java
index ba293e306b..2aedf2f40c 100644
--- 
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/sql/IgniteSqlCreateZone.java
+++ 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/sql/IgniteSqlCreateZone.java
@@ -19,13 +19,13 @@ package org.apache.ignite.internal.sql.engine.sql;
 
 import java.util.List;
 import java.util.Objects;
+import org.apache.calcite.sql.SqlCall;
 import org.apache.calcite.sql.SqlCreate;
 import org.apache.calcite.sql.SqlIdentifier;
 import org.apache.calcite.sql.SqlKind;
+import org.apache.calcite.sql.SqlLiteral;
 import org.apache.calcite.sql.SqlNode;
 import org.apache.calcite.sql.SqlNodeList;
-import org.apache.calcite.sql.SqlOperator;
-import org.apache.calcite.sql.SqlSpecialOperator;
 import org.apache.calcite.sql.SqlWriter;
 import org.apache.calcite.sql.parser.SqlParserPos;
 import org.apache.calcite.util.ImmutableNullableList;
@@ -35,14 +35,31 @@ import org.jetbrains.annotations.Nullable;
  * Parse tree for {@code CREATE ZONE} statement with Ignite specific features.
  */
 public class IgniteSqlCreateZone extends SqlCreate {
+
+    /** CREATE ZONE operator. */
+    protected static class Operator extends IgniteDdlOperator {
+
+        /** Constructor. */
+        protected Operator(boolean existFlag) {
+            super("CREATE ZONE", SqlKind.OTHER_DDL, existFlag);
+        }
+
+        /** {@inheritDoc} */
+        @Override
+        public SqlCall createCall(@Nullable SqlLiteral functionQualifier,
+                SqlParserPos pos, @Nullable SqlNode... operands) {
+
+            return new IgniteSqlCreateZone(pos, existFlag(), (SqlIdentifier) 
operands[0],
+                    (SqlNodeList) operands[1], (SqlIdentifier) operands[2]);
+        }
+    }
+
     private final SqlIdentifier name;
 
     private final @Nullable SqlIdentifier engineName;
 
     private final @Nullable SqlNodeList createOptionList;
 
-    private static final SqlOperator OPERATOR = new SqlSpecialOperator("CREATE 
ZONE", SqlKind.OTHER_DDL);
-
     /** Creates a SqlCreateZone. */
     public IgniteSqlCreateZone(
             SqlParserPos pos,
@@ -51,7 +68,7 @@ public class IgniteSqlCreateZone extends SqlCreate {
             @Nullable SqlNodeList createOptionList,
             @Nullable SqlIdentifier engineName
     ) {
-        super(OPERATOR, pos, false, ifNotExists);
+        super(new Operator(ifNotExists), pos, false, ifNotExists);
 
         assert engineName == null || engineName.isSimple() : engineName;
 
@@ -60,11 +77,17 @@ public class IgniteSqlCreateZone extends SqlCreate {
         this.engineName = engineName;
     }
 
+    /** {@inheritDoc} */
+    @Override
+    public IgniteDdlOperator getOperator() {
+        return (IgniteDdlOperator) super.getOperator();
+    }
+
     /** {@inheritDoc} */
     @SuppressWarnings("nullness")
     @Override
     public List<SqlNode> getOperandList() {
-        return ImmutableNullableList.of(name, createOptionList);
+        return ImmutableNullableList.of(name, createOptionList, engineName);
     }
 
     /** {@inheritDoc} */
@@ -72,7 +95,7 @@ public class IgniteSqlCreateZone extends SqlCreate {
     public void unparse(SqlWriter writer, int leftPrec, int rightPrec) {
         writer.keyword("CREATE");
         writer.keyword("ZONE");
-        if (ifNotExists) {
+        if (ifNotExists()) {
             writer.keyword("IF NOT EXISTS");
         }
 
diff --git 
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/sql/IgniteSqlDelete.java
 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/sql/IgniteSqlDelete.java
new file mode 100644
index 0000000000..4143309d81
--- /dev/null
+++ 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/sql/IgniteSqlDelete.java
@@ -0,0 +1,80 @@
+/*
+ * 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.
+ */
+
+package org.apache.ignite.internal.sql.engine.sql;
+
+import java.util.List;
+import org.apache.calcite.sql.SqlCall;
+import org.apache.calcite.sql.SqlDelete;
+import org.apache.calcite.sql.SqlIdentifier;
+import org.apache.calcite.sql.SqlKind;
+import org.apache.calcite.sql.SqlLiteral;
+import org.apache.calcite.sql.SqlNode;
+import org.apache.calcite.sql.SqlOperator;
+import org.apache.calcite.sql.SqlSelect;
+import org.apache.calcite.sql.SqlSpecialOperator;
+import org.apache.calcite.sql.parser.SqlParserPos;
+import org.apache.calcite.util.ImmutableNullableList;
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+/**
+ * {@code DELETE} statement. Fixes incomplete operator of original {@link 
SqlDelete}.
+ */
+public class IgniteSqlDelete extends SqlDelete {
+
+    /** DELETE operator. */
+    private static final class Operator extends SqlSpecialOperator {
+
+        private Operator() {
+            super("DELETE", SqlKind.DELETE);
+        }
+
+        /** {@inheritDoc} */
+        @Override
+        public SqlCall createCall(@Nullable SqlLiteral functionQualifier, 
SqlParserPos pos, @Nullable SqlNode... operands) {
+            return new IgniteSqlDelete(pos, operands[0], operands[1], 
(SqlSelect) operands[2], (SqlIdentifier) operands[3]);
+        }
+    }
+
+    private static final SqlOperator OPERATOR = new Operator();
+
+    /** Constructor. */
+    public IgniteSqlDelete(SqlParserPos pos, SqlNode targetTable,
+            @Nullable SqlNode condition,
+            @Nullable SqlSelect sourceSelect,
+            @Nullable SqlIdentifier alias) {
+        super(pos, targetTable, condition, sourceSelect, alias);
+    }
+
+    /** Constructor. */
+    public IgniteSqlDelete(SqlDelete node) {
+        this(node.getParserPosition(), node.getTargetTable(), 
node.getCondition(), node.getSourceSelect(), node.getAlias());
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public SqlOperator getOperator() {
+        return OPERATOR;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public List<SqlNode> getOperandList() {
+        return ImmutableNullableList.of(getTargetTable(), getCondition(), 
getSourceSelect(), getAlias());
+    }
+
+}
diff --git 
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/sql/IgniteSqlDropIndex.java
 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/sql/IgniteSqlDropIndex.java
index bb7a653dbb..2bbfe94ff8 100644
--- 
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/sql/IgniteSqlDropIndex.java
+++ 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/sql/IgniteSqlDropIndex.java
@@ -19,43 +19,64 @@ package org.apache.ignite.internal.sql.engine.sql;
 
 import java.util.List;
 import java.util.Objects;
+import org.apache.calcite.sql.SqlCall;
 import org.apache.calcite.sql.SqlDrop;
 import org.apache.calcite.sql.SqlIdentifier;
 import org.apache.calcite.sql.SqlKind;
+import org.apache.calcite.sql.SqlLiteral;
 import org.apache.calcite.sql.SqlNode;
-import org.apache.calcite.sql.SqlOperator;
-import org.apache.calcite.sql.SqlSpecialOperator;
 import org.apache.calcite.sql.SqlWriter;
 import org.apache.calcite.sql.parser.SqlParserPos;
 import org.apache.calcite.util.ImmutableNullableList;
+import org.checkerframework.checker.nullness.qual.Nullable;
 
 /**
  * Parse tree for {@code DROP INDEX} statement.
  */
 public class IgniteSqlDropIndex extends SqlDrop {
+
+    /** DROP INDEX operator. */
+    protected static class Operator extends IgniteDdlOperator {
+
+        /** Constructor. */
+        protected Operator(boolean existFlag) {
+            super("DROP INDEX", SqlKind.DROP_INDEX, existFlag);
+        }
+
+        /** {@inheritDoc} */
+        @Override
+        public SqlCall createCall(@Nullable SqlLiteral functionQualifier, 
SqlParserPos pos, @Nullable SqlNode... operands) {
+            return new IgniteSqlDropIndex(pos, existFlag(), (SqlIdentifier) 
operands[0]);
+        }
+    }
+
     /** Index name. */
     private final SqlIdentifier indexName;
 
-    /** Sql operator. */
-    private static final SqlOperator OPERATOR =
-            new SqlSpecialOperator("DROP INDEX", SqlKind.DROP_INDEX);
-
     /** Constructor. */
     public IgniteSqlDropIndex(SqlParserPos pos, boolean ifExists, 
SqlIdentifier idxName) {
-        super(OPERATOR, pos, ifExists);
+        super(new Operator(ifExists), pos, ifExists);
         indexName = Objects.requireNonNull(idxName, "index name");
     }
 
     /** {@inheritDoc} */
-    @Override public List<SqlNode> getOperandList() {
+    @Override
+    public IgniteDdlOperator getOperator() {
+        return (IgniteDdlOperator) super.getOperator();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public List<SqlNode> getOperandList() {
         return ImmutableNullableList.of(indexName);
     }
 
     /** {@inheritDoc} */
-    @Override public void unparse(SqlWriter writer, int leftPrec, int 
rightPrec) {
+    @Override
+    public void unparse(SqlWriter writer, int leftPrec, int rightPrec) {
         writer.keyword(getOperator().getName()); // "DROP ..."
 
-        if (ifExists) {
+        if (ifExists()) {
             writer.keyword("IF EXISTS");
         }
 
@@ -67,6 +88,7 @@ public class IgniteSqlDropIndex extends SqlDrop {
     }
 
     public boolean ifExists() {
-        return ifExists;
+        Operator operator = (Operator) getOperator();
+        return operator.existFlag();
     }
 }
diff --git 
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/sql/IgniteSqlDropZone.java
 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/sql/IgniteSqlDropTable.java
similarity index 53%
copy from 
modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/sql/IgniteSqlDropZone.java
copy to 
modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/sql/IgniteSqlDropTable.java
index c4573e9df5..cfc3f3e9b9 100644
--- 
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/sql/IgniteSqlDropZone.java
+++ 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/sql/IgniteSqlDropTable.java
@@ -18,55 +18,74 @@
 package org.apache.ignite.internal.sql.engine.sql;
 
 import java.util.List;
-import java.util.Objects;
+import org.apache.calcite.sql.SqlCall;
 import org.apache.calcite.sql.SqlDrop;
 import org.apache.calcite.sql.SqlIdentifier;
 import org.apache.calcite.sql.SqlKind;
+import org.apache.calcite.sql.SqlLiteral;
 import org.apache.calcite.sql.SqlNode;
-import org.apache.calcite.sql.SqlOperator;
-import org.apache.calcite.sql.SqlSpecialOperator;
 import org.apache.calcite.sql.SqlWriter;
 import org.apache.calcite.sql.parser.SqlParserPos;
 import org.apache.calcite.util.ImmutableNullableList;
+import org.checkerframework.checker.nullness.qual.Nullable;
 
 /**
- * Parse tree for {@code DROP ZONE} statement.
+ * Parse tree for {@code DROP TABLE} statement.
  */
-public class IgniteSqlDropZone extends SqlDrop {
-    /** Zone name. */
-    private final SqlIdentifier name;
+public class IgniteSqlDropTable extends SqlDrop {
+
+    /** DROP TABLE operator. */
+    protected static class Operator extends IgniteDdlOperator {
+
+        /** Constructor. */
+        public Operator(boolean existFlag) {
+            super("DROP TABLE", SqlKind.DROP_TABLE, existFlag);
+        }
 
-    /** Sql operator. */
-    private static final SqlOperator OPERATOR = new SqlSpecialOperator("DROP 
ZONE", SqlKind.OTHER_DDL);
+        /** {@inheritDoc} */
+        @Override
+        public SqlCall createCall(@Nullable SqlLiteral functionQualifier, 
SqlParserPos pos,
+                @Nullable SqlNode... operands) {
+            return new IgniteSqlDropTable(pos, existFlag(), (SqlIdentifier) 
operands[0]);
+        }
+    }
+
+    private final SqlIdentifier name;
 
     /** Constructor. */
-    public IgniteSqlDropZone(SqlParserPos pos, boolean ifExists, SqlIdentifier 
name) {
-        super(OPERATOR, pos, ifExists);
+    public IgniteSqlDropTable(SqlParserPos pos, boolean ifExists, 
SqlIdentifier name) {
+        super(new Operator(ifExists), pos, ifExists);
 
-        this.name = Objects.requireNonNull(name, "zone name");
+        this.name = name;
     }
 
     /** {@inheritDoc} */
-    @Override public List<SqlNode> getOperandList() {
+    @Override
+    public List<SqlNode> getOperandList() {
         return ImmutableNullableList.of(name);
     }
 
+    /** Returns table name. */
+    public SqlIdentifier name() {
+        return name;
+    }
+
+    /** Whether "IF EXISTS" was specified. */
+    public boolean ifExists() {
+        IgniteDdlOperator operator = (IgniteDdlOperator) getOperator();
+        return operator.existFlag();
+    }
+
     /** {@inheritDoc} */
-    @Override public void unparse(SqlWriter writer, int leftPrec, int 
rightPrec) {
-        writer.keyword(getOperator().getName()); // "DROP ..."
+    @Override
+    public void unparse(SqlWriter writer, int leftPrec, int rightPrec) {
+        writer.keyword("DROP");
+        writer.keyword("TABLE");
 
-        if (ifExists) {
+        if (ifExists()) {
             writer.keyword("IF EXISTS");
         }
 
         name.unparse(writer, leftPrec, rightPrec);
     }
-
-    public SqlIdentifier name() {
-        return name;
-    }
-
-    public boolean ifExists() {
-        return ifExists;
-    }
 }
diff --git 
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/sql/IgniteSqlDropZone.java
 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/sql/IgniteSqlDropZone.java
index c4573e9df5..a515acaa78 100644
--- 
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/sql/IgniteSqlDropZone.java
+++ 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/sql/IgniteSqlDropZone.java
@@ -19,40 +19,63 @@ package org.apache.ignite.internal.sql.engine.sql;
 
 import java.util.List;
 import java.util.Objects;
+import org.apache.calcite.sql.SqlCall;
 import org.apache.calcite.sql.SqlDrop;
 import org.apache.calcite.sql.SqlIdentifier;
 import org.apache.calcite.sql.SqlKind;
+import org.apache.calcite.sql.SqlLiteral;
 import org.apache.calcite.sql.SqlNode;
-import org.apache.calcite.sql.SqlOperator;
-import org.apache.calcite.sql.SqlSpecialOperator;
 import org.apache.calcite.sql.SqlWriter;
 import org.apache.calcite.sql.parser.SqlParserPos;
 import org.apache.calcite.util.ImmutableNullableList;
+import org.checkerframework.checker.nullness.qual.Nullable;
 
 /**
  * Parse tree for {@code DROP ZONE} statement.
  */
 public class IgniteSqlDropZone extends SqlDrop {
+
+    /** DROP ZONE operator. */
+    protected static class Operator extends IgniteDdlOperator {
+
+        /** Constructor. */
+        protected Operator(boolean existFlag) {
+            super("DROP ZONE", SqlKind.OTHER_DDL, existFlag);
+        }
+
+        /** {@inheritDoc} */
+        @Override
+        public SqlCall createCall(@Nullable SqlLiteral functionQualifier, 
SqlParserPos pos,
+                @Nullable SqlNode... operands) {
+            return new IgniteSqlDropZone(pos, existFlag(), (SqlIdentifier) 
operands[0]);
+        }
+    }
+
     /** Zone name. */
     private final SqlIdentifier name;
 
-    /** Sql operator. */
-    private static final SqlOperator OPERATOR = new SqlSpecialOperator("DROP 
ZONE", SqlKind.OTHER_DDL);
-
     /** Constructor. */
     public IgniteSqlDropZone(SqlParserPos pos, boolean ifExists, SqlIdentifier 
name) {
-        super(OPERATOR, pos, ifExists);
+        super(new Operator(ifExists), pos, ifExists);
 
         this.name = Objects.requireNonNull(name, "zone name");
     }
 
     /** {@inheritDoc} */
-    @Override public List<SqlNode> getOperandList() {
+    @Override
+    public IgniteDdlOperator getOperator() {
+        return (IgniteDdlOperator) super.getOperator();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public List<SqlNode> getOperandList() {
         return ImmutableNullableList.of(name);
     }
 
     /** {@inheritDoc} */
-    @Override public void unparse(SqlWriter writer, int leftPrec, int 
rightPrec) {
+    @Override
+    public void unparse(SqlWriter writer, int leftPrec, int rightPrec) {
         writer.keyword(getOperator().getName()); // "DROP ..."
 
         if (ifExists) {
@@ -67,6 +90,7 @@ public class IgniteSqlDropZone extends SqlDrop {
     }
 
     public boolean ifExists() {
-        return ifExists;
+        Operator operator = (Operator) getOperator();
+        return operator.existFlag();
     }
 }
diff --git 
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/sql/IgniteSqlMerge.java
 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/sql/IgniteSqlMerge.java
new file mode 100644
index 0000000000..4989ffe575
--- /dev/null
+++ 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/sql/IgniteSqlMerge.java
@@ -0,0 +1,82 @@
+/*
+ * 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.
+ */
+
+package org.apache.ignite.internal.sql.engine.sql;
+
+import org.apache.calcite.sql.SqlCall;
+import org.apache.calcite.sql.SqlIdentifier;
+import org.apache.calcite.sql.SqlInsert;
+import org.apache.calcite.sql.SqlKind;
+import org.apache.calcite.sql.SqlLiteral;
+import org.apache.calcite.sql.SqlMerge;
+import org.apache.calcite.sql.SqlNode;
+import org.apache.calcite.sql.SqlOperator;
+import org.apache.calcite.sql.SqlSelect;
+import org.apache.calcite.sql.SqlSpecialOperator;
+import org.apache.calcite.sql.SqlUpdate;
+import org.apache.calcite.sql.parser.SqlParserPos;
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+/**
+ * {@code MERGE} statement, Fixes incomplete operator of original {@link 
SqlMerge}.
+ */
+public class IgniteSqlMerge extends SqlMerge {
+
+    /** MERGE operator. */
+    protected static class Operator extends SqlSpecialOperator {
+
+        /** Constructor. */
+        protected Operator() {
+            super("MERGE", SqlKind.MERGE);
+        }
+
+        /** {@inheritDoc} */
+        @Override
+        public SqlCall createCall(@Nullable SqlLiteral functionQualifier, 
SqlParserPos pos, @Nullable SqlNode... operands) {
+            return new IgniteSqlMerge(pos, operands[0], operands[1],
+                    operands[2], (SqlUpdate) operands[3],
+                    (SqlInsert) operands[4], (SqlSelect) operands[5],
+                    (SqlIdentifier) operands[6]);
+        }
+    }
+
+    private static final Operator OPERATOR = new Operator();
+
+    /** Constructor. */
+    public IgniteSqlMerge(SqlParserPos pos, SqlNode targetTable,
+            SqlNode condition, SqlNode source,
+            @Nullable SqlUpdate updateCall,
+            @Nullable SqlInsert insertCall,
+            @Nullable SqlSelect sourceSelect,
+            @Nullable SqlIdentifier alias) {
+        super(pos, targetTable, condition, source, updateCall, insertCall, 
sourceSelect, alias);
+    }
+
+    /** Constructor. */
+    public IgniteSqlMerge(SqlMerge node) {
+        this(node.getParserPosition(), node.getTargetTable(), 
node.getCondition(),
+                node.getSourceTableRef(), node.getUpdateCall(), 
node.getInsertCall(),
+                node.getSourceSelect(), node.getAlias()
+        );
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public SqlOperator getOperator() {
+        return OPERATOR;
+    }
+}
diff --git 
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/sql/IgniteSqlParser.java
 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/sql/IgniteSqlParser.java
index 6f3b97edec..e19ab9e812 100644
--- 
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/sql/IgniteSqlParser.java
+++ 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/sql/IgniteSqlParser.java
@@ -21,9 +21,14 @@ import static 
org.apache.ignite.internal.lang.IgniteStringFormatter.format;
 import static org.apache.ignite.lang.ErrorGroups.Sql.STMT_PARSE_ERR;
 
 import java.io.Reader;
+import java.util.List;
 import org.apache.calcite.config.Lex;
+import org.apache.calcite.sql.SqlDelete;
+import org.apache.calcite.sql.SqlKind;
+import org.apache.calcite.sql.SqlMerge;
 import org.apache.calcite.sql.SqlNode;
 import org.apache.calcite.sql.SqlNodeList;
+import org.apache.calcite.sql.SqlUpdate;
 import org.apache.calcite.sql.parser.SqlAbstractParserImpl;
 import org.apache.calcite.sql.parser.SqlParseException;
 import org.apache.calcite.sql.parser.SqlParser;
@@ -94,7 +99,15 @@ public final class IgniteSqlParser  {
             Integer dynamicParamsCount = 
InternalIgniteSqlParser.dynamicParamCount.get();
             assert dynamicParamsCount != null : "dynamicParamCount has not 
been updated";
 
-            return mode.createResult(nodeList.getList(), dynamicParamsCount);
+            List<SqlNode> list = nodeList.getList();
+
+            for (int i = 0; i < list.size(); i++) {
+                SqlNode original = list.get(i);
+                SqlNode node = fixNodesIfNecessary(original);
+                list.set(i, node);
+            }
+
+            return mode.createResult(list, dynamicParamsCount);
         } catch (SqlParseException e) {
             throw convertException(e);
         } finally {
@@ -246,4 +259,18 @@ public final class IgniteSqlParser  {
             }
         }
     }
+
+    private static SqlNode fixNodesIfNecessary(SqlNode node) {
+        // Create copies of DML nodes because original use incomplete 
implementation
+        // of SqlOperator that does not provide implementation of createCall 
method.
+        if (node.getKind() == SqlKind.DELETE) {
+            return new IgniteSqlDelete((SqlDelete) node);
+        } else if (node.getKind() == SqlKind.UPDATE) {
+            return new IgniteSqlUpdate((SqlUpdate) node);
+        } else if (node.getKind() == SqlKind.MERGE) {
+            return new IgniteSqlMerge((SqlMerge) node);
+        } else {
+            return node;
+        }
+    }
 }
diff --git 
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/sql/IgniteSqlCommitTransaction.java
 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/sql/IgniteSqlSpecialOperator.java
similarity index 59%
copy from 
modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/sql/IgniteSqlCommitTransaction.java
copy to 
modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/sql/IgniteSqlSpecialOperator.java
index 7dd51d317c..ac6d1f6c3c 100644
--- 
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/sql/IgniteSqlCommitTransaction.java
+++ 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/sql/IgniteSqlSpecialOperator.java
@@ -17,42 +17,25 @@
 
 package org.apache.ignite.internal.sql.engine.sql;
 
-import java.util.List;
 import org.apache.calcite.sql.SqlCall;
 import org.apache.calcite.sql.SqlKind;
+import org.apache.calcite.sql.SqlLiteral;
 import org.apache.calcite.sql.SqlNode;
-import org.apache.calcite.sql.SqlOperator;
 import org.apache.calcite.sql.SqlSpecialOperator;
-import org.apache.calcite.sql.SqlWriter;
 import org.apache.calcite.sql.parser.SqlParserPos;
+import org.checkerframework.checker.nullness.qual.Nullable;
 
 /**
- *  Parse tree for {@code COMMIT} statement.
+ * Base class for SQL operators.
  */
-public class IgniteSqlCommitTransaction extends SqlCall {
+public abstract class IgniteSqlSpecialOperator extends SqlSpecialOperator {
 
-    /** Commit transaction operator. */
-    private static final SqlOperator OPERATOR = new 
SqlSpecialOperator("COMMIT", SqlKind.COMMIT);
-
-    public IgniteSqlCommitTransaction(SqlParserPos pos) {
-        super(pos);
-    }
-
-    /** {@inheritDoc} */
-    @Override
-    public SqlOperator getOperator() {
-        return OPERATOR;
+    /** Constructor. */
+    public IgniteSqlSpecialOperator(String name, SqlKind kind) {
+        super(name, kind);
     }
 
     /** {@inheritDoc} */
     @Override
-    public List<SqlNode> getOperandList() {
-        return List.of();
-    }
-
-    /** {@inheritDoc} */
-    @Override
-    public void unparse(SqlWriter writer, int leftPrec, int rightPrec) {
-        writer.keyword(getOperator().getName());
-    }
+    public abstract SqlCall createCall(@Nullable SqlLiteral functionQualifier, 
SqlParserPos pos, @Nullable SqlNode... operands);
 }
diff --git 
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/sql/IgniteSqlStartTransaction.java
 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/sql/IgniteSqlStartTransaction.java
index 6d71e2c7a9..c2f868d39a 100644
--- 
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/sql/IgniteSqlStartTransaction.java
+++ 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/sql/IgniteSqlStartTransaction.java
@@ -20,38 +20,54 @@ package org.apache.ignite.internal.sql.engine.sql;
 import java.util.List;
 import org.apache.calcite.sql.SqlCall;
 import org.apache.calcite.sql.SqlKind;
+import org.apache.calcite.sql.SqlLiteral;
 import org.apache.calcite.sql.SqlNode;
 import org.apache.calcite.sql.SqlOperator;
-import org.apache.calcite.sql.SqlSpecialOperator;
 import org.apache.calcite.sql.SqlWriter;
 import org.apache.calcite.sql.parser.SqlParserPos;
+import org.checkerframework.checker.nullness.qual.Nullable;
 
 /**
- *  Parse tree for {@code START TRANSACTION } statement.
+ * Parse tree for {@code START TRANSACTION } statement.
  */
 public class IgniteSqlStartTransaction extends SqlCall {
 
-    /** Start transaction operator. */
-    private static final SqlOperator OPERATOR = new SqlSpecialOperator("START 
TRANSACTION", SqlKind.OTHER);
+    /** START TRANSACTION operator. */
+    protected static class Operator extends IgniteSqlSpecialOperator {
 
-    private final IgniteSqlStartTransactionMode mode;
+        private final IgniteSqlStartTransactionMode mode;
+
+        /** Constructor. */
+        protected Operator(IgniteSqlStartTransactionMode mode) {
+            super("START TRANSACTION", SqlKind.OTHER);
+            this.mode = mode;
+        }
+
+        /** {@inheritDoc} */
+        @Override
+        public SqlCall createCall(@Nullable SqlLiteral functionQualifier, 
SqlParserPos pos, @Nullable SqlNode... operands) {
+            return new IgniteSqlStartTransaction(pos, mode);
+        }
+    }
+
+    private final Operator operator;
 
     /** Creates a IgniteSqlStartTransaction. */
     public IgniteSqlStartTransaction(SqlParserPos pos, 
IgniteSqlStartTransactionMode mode) {
         super(pos);
 
-        this.mode = mode;
+        this.operator = new Operator(mode);
     }
 
-    /** Returns the transaction mode .*/
+    /** Returns the transaction mode . */
     public IgniteSqlStartTransactionMode getMode() {
-        return mode;
+        return operator.mode;
     }
 
     /** {@inheritDoc} */
     @Override
     public SqlOperator getOperator() {
-        return OPERATOR;
+        return operator;
     }
 
     /** {@inheritDoc} */
@@ -65,7 +81,7 @@ public class IgniteSqlStartTransaction extends SqlCall {
     public void unparse(SqlWriter writer, int leftPrec, int rightPrec) {
         writer.keyword(getOperator().getName());
 
-        switch (mode) {
+        switch (operator.mode) {
             case READ_ONLY:
                 writer.keyword("READ ONLY");
                 break;
@@ -75,7 +91,7 @@ public class IgniteSqlStartTransaction extends SqlCall {
             case IMPLICIT_READ_WRITE:
                 break;
             default:
-                throw new IllegalStateException("Unexpected start transaction 
mode: " + mode);
+                throw new IllegalStateException("Unexpected start transaction 
mode: " + operator.mode);
         }
     }
 }
diff --git 
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/sql/IgniteSqlUpdate.java
 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/sql/IgniteSqlUpdate.java
new file mode 100644
index 0000000000..92967de3b9
--- /dev/null
+++ 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/sql/IgniteSqlUpdate.java
@@ -0,0 +1,87 @@
+/*
+ * 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.
+ */
+
+package org.apache.ignite.internal.sql.engine.sql;
+
+import java.util.List;
+import org.apache.calcite.sql.SqlCall;
+import org.apache.calcite.sql.SqlIdentifier;
+import org.apache.calcite.sql.SqlKind;
+import org.apache.calcite.sql.SqlLiteral;
+import org.apache.calcite.sql.SqlNode;
+import org.apache.calcite.sql.SqlNodeList;
+import org.apache.calcite.sql.SqlOperator;
+import org.apache.calcite.sql.SqlSelect;
+import org.apache.calcite.sql.SqlUpdate;
+import org.apache.calcite.sql.parser.SqlParserPos;
+import org.apache.calcite.util.ImmutableNullableList;
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+/**
+ * {@code UPDATE} statement. Fixes incomplete operator of original {@link 
SqlUpdate}.
+ */
+public class IgniteSqlUpdate extends SqlUpdate {
+
+    /** UPDATE operator. */
+    protected static final class Operator extends IgniteSqlSpecialOperator {
+
+        /** Constructor. */
+        protected Operator() {
+            super("UPDATE", SqlKind.UPDATE);
+        }
+
+        /** {@inheritDoc} */
+        @Override
+        public SqlCall createCall(@Nullable SqlLiteral functionQualifier, 
SqlParserPos pos, @Nullable SqlNode... operands) {
+            return new IgniteSqlUpdate(pos, operands[0], (SqlNodeList) 
operands[1],
+                    (SqlNodeList) operands[2], operands[3],
+                    (SqlSelect) operands[4], (SqlIdentifier) operands[5]);
+        }
+    }
+
+    private static final Operator OPERATOR = new Operator();
+
+    /** Constructor. */
+    public IgniteSqlUpdate(SqlParserPos pos, SqlNode targetTable,
+            SqlNodeList targetColumnList, SqlNodeList sourceExpressionList,
+            @Nullable SqlNode condition,
+            @Nullable SqlSelect sourceSelect,
+            @Nullable SqlIdentifier alias) {
+        super(pos, targetTable, targetColumnList, sourceExpressionList, 
condition, sourceSelect, alias);
+    }
+
+    /** Constructor.*/
+    public IgniteSqlUpdate(SqlUpdate node) {
+        this(node.getParserPosition(), node.getTargetTable(), 
node.getTargetColumnList(),
+                node.getSourceExpressionList(), node.getCondition(),
+                node.getSourceSelect(), node.getAlias());
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public SqlOperator getOperator() {
+        return OPERATOR;
+    }
+
+    /** {@inheritDoc} */
+    @SuppressWarnings("nullness")
+    @Override
+    public List<@Nullable SqlNode> getOperandList() {
+        return ImmutableNullableList.of(getTargetTable(), 
getTargetColumnList(), getSourceExpressionList(),
+                getCondition(), getSourceSelect(), getAlias());
+    }
+}
diff --git 
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/sql/IgniteSqlZoneOption.java
 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/sql/IgniteSqlZoneOption.java
index cdcaf0a7d2..1a523acac9 100644
--- 
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/sql/IgniteSqlZoneOption.java
+++ 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/sql/IgniteSqlZoneOption.java
@@ -21,20 +21,36 @@ import java.util.List;
 import org.apache.calcite.sql.SqlCall;
 import org.apache.calcite.sql.SqlIdentifier;
 import org.apache.calcite.sql.SqlKind;
+import org.apache.calcite.sql.SqlLiteral;
 import org.apache.calcite.sql.SqlNode;
 import org.apache.calcite.sql.SqlOperator;
-import org.apache.calcite.sql.SqlSpecialOperator;
 import org.apache.calcite.sql.SqlWriter;
 import org.apache.calcite.sql.parser.SqlParserPos;
 import org.apache.calcite.sql.util.SqlVisitor;
 import org.apache.calcite.sql.validate.SqlValidator;
 import org.apache.calcite.sql.validate.SqlValidatorScope;
 import org.apache.calcite.util.Litmus;
+import org.checkerframework.checker.nullness.qual.Nullable;
 
 /** An AST node representing option in CREATE ZONE and ALTER ZONE statements. 
*/
 public class IgniteSqlZoneOption extends SqlCall {
-    private static final SqlOperator OPERATOR =
-            new SqlSpecialOperator("ZoneOption", SqlKind.OTHER);
+
+    /** ZONE option operator. */
+    protected static class Operator extends IgniteSqlSpecialOperator {
+
+        /** Constructor. */
+        protected Operator() {
+            super("ZoneOption", SqlKind.OTHER);
+        }
+
+        /** {@inheritDoc} */
+        @Override
+        public SqlCall createCall(@Nullable SqlLiteral functionQualifier, 
SqlParserPos pos, @Nullable SqlNode... operands) {
+            return new IgniteSqlZoneOption((SqlIdentifier) operands[0], 
operands[1], pos);
+        }
+    }
+
+    private static final SqlOperator OPERATOR = new Operator();
 
     /** Option key. */
     private final SqlIdentifier key;
diff --git 
a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/planner/AbstractPlannerTest.java
 
b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/planner/AbstractPlannerTest.java
index 7e662c00b1..02f968fc96 100644
--- 
a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/planner/AbstractPlannerTest.java
+++ 
b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/planner/AbstractPlannerTest.java
@@ -225,7 +225,7 @@ public abstract class AbstractPlannerTest extends 
IgniteAbstractTest {
         return plannerCtx(sql, Collections.singleton(publicSchema), null, 
List.of(), disabledRules);
     }
 
-    private PlanningContext plannerCtx(
+    protected PlanningContext plannerCtx(
             String sql,
             Collection<IgniteSchema> schemas,
             HintStrategyTable hintStrategies,
diff --git 
a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/sql/AbstractDdlParserTest.java
 
b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/sql/AbstractDdlParserTest.java
index 9135cc01b3..49af15c98c 100644
--- 
a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/sql/AbstractDdlParserTest.java
+++ 
b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/sql/AbstractDdlParserTest.java
@@ -17,8 +17,12 @@
 
 package org.apache.ignite.internal.sql.engine.sql;
 
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
 import java.util.function.Predicate;
 import org.apache.calcite.sql.SqlNode;
+import org.apache.calcite.sql.SqlWriter;
+import org.apache.calcite.sql.pretty.SqlPrettyWriter;
 import org.hamcrest.CustomMatcher;
 import org.hamcrest.Matcher;
 
@@ -34,7 +38,11 @@ public abstract class AbstractDdlParserTest {
      */
     protected SqlNode parse(String stmt) {
         StatementParseResult parseResult = IgniteSqlParser.parse(stmt, 
StatementParseResult.MODE);
-        return parseResult.statement();
+        SqlNode statement = parseResult.statement();
+
+        SqlNode clone = statement.clone(statement.getParserPosition());
+        assertEquals(statement.toString(), clone.toString(), "clone fails");
+        return statement;
     }
 
     /**
@@ -54,4 +62,22 @@ public abstract class AbstractDdlParserTest {
             }
         };
     }
+
+    /**
+     * Compares the result of calling {@link SqlNode#unparse(SqlWriter, int, 
int)}} on the given node with the expected string.
+     * Also compares the expected string on a cloned node.
+     */
+    protected static void expectUnparsed(SqlNode node, String expectedStmt) {
+        SqlPrettyWriter w = new SqlPrettyWriter();
+        node.unparse(w, 0, 0);
+
+        assertEquals(expectedStmt, w.toString(), "Unparsed does not match");
+
+        w.reset();
+
+        // Verify that clone works correctly.
+        SqlNode cloned = node.clone(node.getParserPosition());
+        cloned.unparse(w, 0, 0);
+        assertEquals(expectedStmt, w.toString(), "Unparsed does not match for 
cloned node");
+    }
 }
diff --git 
a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/sql/DistributionZoneSqlDdlParserTest.java
 
b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/sql/DistributionZoneSqlDdlParserTest.java
index 29a35777b8..b07edee544 100644
--- 
a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/sql/DistributionZoneSqlDdlParserTest.java
+++ 
b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/sql/DistributionZoneSqlDdlParserTest.java
@@ -21,7 +21,6 @@ import static 
org.apache.ignite.internal.sql.engine.util.SqlTestUtils.assertThro
 import static org.hamcrest.CoreMatchers.instanceOf;
 import static org.hamcrest.CoreMatchers.is;
 import static org.hamcrest.MatcherAssert.assertThat;
-import static org.hamcrest.Matchers.equalTo;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertFalse;
 import static org.junit.jupiter.api.Assertions.assertInstanceOf;
@@ -33,8 +32,6 @@ import java.util.List;
 import java.util.Objects;
 import org.apache.calcite.sql.SqlLiteral;
 import org.apache.calcite.sql.SqlNode;
-import org.apache.calcite.sql.SqlWriter;
-import org.apache.calcite.sql.pretty.SqlPrettyWriter;
 import org.apache.ignite.internal.sql.engine.prepare.ddl.ZoneOptionEnum;
 import org.apache.ignite.lang.ErrorGroups.Sql;
 import org.hamcrest.Matchers;
@@ -56,17 +53,21 @@ public class DistributionZoneSqlDdlParserTest extends 
AbstractDdlParserTest {
         assertThat(createZone.name().names, is(List.of("TEST_ZONE")));
         assertFalse(createZone.ifNotExists());
         assertNull(createZone.createOptionList());
+        expectUnparsed(createZone, "CREATE ZONE \"TEST_ZONE\"");
 
         // Fully qualified name.
         createZone = parseCreateZone("create zone public.test_zone");
         assertThat(createZone.name().names, is(List.of("PUBLIC", 
"TEST_ZONE")));
+        expectUnparsed(createZone, "CREATE ZONE \"PUBLIC\".\"TEST_ZONE\"");
 
         // Quoted identifier.
         createZone = parseCreateZone("create zone \"public\".\"test_Zone\"");
         assertThat(createZone.name().names, is(List.of("public", 
"test_Zone")));
+        expectUnparsed(createZone, "CREATE ZONE \"public\".\"test_Zone\"");
 
         createZone = parseCreateZone("create zone \"public-test_Zone\"");
         assertThat(createZone.name().names, is(List.of("public-test_Zone")));
+        expectUnparsed(createZone, "CREATE ZONE \"public-test_Zone\"");
     }
 
     /**
@@ -78,6 +79,8 @@ public class DistributionZoneSqlDdlParserTest extends 
AbstractDdlParserTest {
 
         assertTrue(createZone.ifNotExists());
         assertNull(createZone.createOptionList());
+
+        expectUnparsed(createZone, "CREATE ZONE IF NOT EXISTS \"TEST_ZONE\"");
     }
 
     /**
@@ -131,10 +134,13 @@ public class DistributionZoneSqlDdlParserTest extends 
AbstractDdlParserTest {
         assertThat(dropZone.name().names, is(List.of("TEST_ZONE")));
         assertFalse(dropZone.ifExists());
 
+        expectUnparsed(node, "DROP ZONE \"TEST_ZONE\"");
+
         // Fully qualified name.
         dropZone = ((IgniteSqlDropZone) parse("drop zone public.test_zone"));
 
         assertThat(dropZone.name().names, is(List.of("PUBLIC", "TEST_ZONE")));
+        expectUnparsed(dropZone, "DROP ZONE \"PUBLIC\".\"TEST_ZONE\"");
     }
 
     /**
@@ -276,16 +282,6 @@ public class DistributionZoneSqlDdlParserTest extends 
AbstractDdlParserTest {
         return assertInstanceOf(IgniteSqlAlterZoneRenameTo.class, node);
     }
 
-    /**
-     * Compares the result of calling {@link SqlNode#unparse(SqlWriter, int, 
int)}} on the given node with the expected string.
-     */
-    private static void expectUnparsed(SqlNode node, String expectedStmt) {
-        SqlPrettyWriter w = new SqlPrettyWriter();
-        node.unparse(w, 0, 0);
-
-        assertThat(w.toString(), equalTo(expectedStmt));
-    }
-
     private void assertThatZoneOptionPresent(List<SqlNode> optionList, 
ZoneOptionEnum name, Object expVal) {
         assertThat(optionList, Matchers.hasItem(ofTypeMatching(
                 name + "=" + expVal,
diff --git 
a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/sql/IgniteSqlParserTest.java
 
b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/sql/IgniteSqlParserTest.java
index cb5c48ad59..6b28e27570 100644
--- 
a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/sql/IgniteSqlParserTest.java
+++ 
b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/sql/IgniteSqlParserTest.java
@@ -23,6 +23,8 @@ import static org.junit.jupiter.api.Assertions.assertNotNull;
 
 import java.util.Arrays;
 import java.util.List;
+import org.apache.calcite.sql.SqlNode;
+import org.apache.calcite.sql.parser.SqlParserPos;
 import org.apache.ignite.lang.ErrorGroups.Sql;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.params.ParameterizedTest;
@@ -148,6 +150,51 @@ public class IgniteSqlParserTest {
                 () -> IgniteSqlParser.parse("SELECT decimal '2a'", 
StatementParseResult.MODE));
     }
 
+    @ParameterizedTest
+    @MethodSource("dmlStatements")
+    public void testDmlStatementsClone(String stmt) {
+        StatementParseResult single = IgniteSqlParser.parse(stmt, 
StatementParseResult.MODE);
+
+        SqlNode statement = single.statement();
+        SqlParserPos pos = statement.getParserPosition();
+
+        assertEquals(statement.clone(pos).toString(), statement.toString());
+    }
+
+    @ParameterizedTest
+    @MethodSource("dmlStatements")
+    public void testScriptDmlStatementsClone(String stmt) {
+        ScriptParseResult script = IgniteSqlParser.parse(stmt, 
ScriptParseResult.MODE);
+
+        StatementParseResult single = script.results().get(0);
+
+        SqlNode statement = single.statement();
+        SqlParserPos pos = statement.getParserPosition();
+
+        assertEquals(statement.clone(pos).toString(), statement.toString());
+    }
+
+    private static List<Arguments> dmlStatements() {
+        return List.of(
+                Arguments.of("UPDATE t SET x = 1"),
+                Arguments.of("UPDATE t SET x = 1 WHERE y = 2"),
+
+                Arguments.of("DELETE FROM t"),
+                Arguments.of("DELETE FROM t WHERE y = 2"),
+
+                Arguments.of("MERGE INTO T2 dst USING t1 src ON dst.c1 = 
src.c1"
+                        + " WHEN MATCHED THEN UPDATE SET c2 = 1"),
+
+                Arguments.of("MERGE INTO T2 dst USING t1 src ON dst.c1 = 
src.c1 "
+                        + "WHEN NOT MATCHED THEN INSERT (c1, c2, c3) VALUES 
(src.c1, src.c2, 1)"),
+
+                Arguments.of("MERGE INTO T2 dst USING t1 src ON dst.c1 = 
src.c1 "
+                        + "WHEN MATCHED THEN UPDATE SET c2 = 1 "
+                        + "WHEN NOT MATCHED THEN INSERT (c1, c2, c3) VALUES 
(src.c1, src.c2, 1)")
+
+        );
+    }
+
     private static List<Arguments> multiStatementQueries() {
         return List.of(
                 Arguments.of(
diff --git 
a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/sql/SqlAlterColumnDdlParserTest.java
 
b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/sql/SqlAlterColumnDdlParserTest.java
index 14c72d6a4c..10fad945ae 100644
--- 
a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/sql/SqlAlterColumnDdlParserTest.java
+++ 
b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/sql/SqlAlterColumnDdlParserTest.java
@@ -24,13 +24,9 @@ import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.Matchers.equalTo;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
 
-import java.util.List;
 import org.apache.calcite.sql.SqlLiteral;
 import org.apache.calcite.sql.SqlNode;
-import org.apache.calcite.sql.pretty.SqlFormatOptions;
-import org.apache.calcite.sql.pretty.SqlPrettyWriter;
 import org.apache.calcite.sql.type.SqlTypeName;
-import org.apache.ignite.internal.lang.IgniteStringFormatter;
 import org.apache.ignite.lang.ErrorGroups.Sql;
 import org.jetbrains.annotations.Nullable;
 import org.junit.jupiter.api.Test;
@@ -39,9 +35,6 @@ import org.junit.jupiter.api.Test;
  * Test suite to verify parsing of the {@code ALTER TABLE ... ALTER COLUMN} 
DDL commands.
  */
 public class SqlAlterColumnDdlParserTest extends AbstractDdlParserTest {
-    private static final String TABLE_NAME = "TEST_TABLE";
-    private static final String COLUMN_NAME = "TEST_COLUMN";
-    private static final String QUERY_PREFIX = 
IgniteStringFormatter.format("ALTER TABLE {} ALTER COLUMN {} ", TABLE_NAME, 
COLUMN_NAME);
 
     /**
      * Verifies parsing of {@code ALTER TABLE ... ALTER COLUMN ... SET/DROP 
NOT NULL} statement.
@@ -53,8 +46,13 @@ public class SqlAlterColumnDdlParserTest extends 
AbstractDdlParserTest {
      */
     @Test
     public void testNotNull() {
-        assertThat(parseAlterColumn("SET NOT NULL").notNull(), is(true));
-        assertThat(parseAlterColumn("DROP NOT NULL").notNull(), is(false));
+        IgniteSqlAlterColumn alterColumn = parseAlterColumn("ALTER TABLE t 
ALTER COLUMN a SET NOT NULL");
+        assertThat(alterColumn.notNull(), is(true));
+        expectUnparsed(alterColumn, "ALTER TABLE \"T\" ALTER COLUMN \"A\" SET 
NOT NULL");
+
+        alterColumn = parseAlterColumn("ALTER TABLE t ALTER COLUMN a DROP NOT 
NULL");
+        assertThat(alterColumn.notNull(), is(false));
+        expectUnparsed(alterColumn, "ALTER TABLE \"T\" ALTER COLUMN \"A\" DROP 
NOT NULL");
     }
 
     /**
@@ -71,17 +69,24 @@ public class SqlAlterColumnDdlParserTest extends 
AbstractDdlParserTest {
      */
     @Test
     public void testDefault() {
-        checkDefaultIsNull(parseAlterColumn("DROP DEFAULT").expression());
-        checkDefaultIsNull(parseAlterColumn("SET DEFAULT NULL", "DROP 
DEFAULT").expression());
+        IgniteSqlAlterColumn alterColumn = parseAlterColumn("ALTER TABLE t 
ALTER COLUMN a DROP DEFAULT");
+        checkDefaultIsNull(alterColumn.expression());
+        expectUnparsed(alterColumn, "ALTER TABLE \"T\" ALTER COLUMN \"A\" DROP 
DEFAULT");
+
+        alterColumn = parseAlterColumn("ALTER TABLE t ALTER COLUMN a SET 
DEFAULT NULL");
+        checkDefaultIsNull(alterColumn.expression());
+        expectUnparsed(alterColumn, "ALTER TABLE \"T\" ALTER COLUMN \"A\" SET 
DEFAULT NULL");
 
-        SqlNode dflt = parseAlterColumn("SET DEFAULT 10").expression();
+        alterColumn = parseAlterColumn("ALTER TABLE t ALTER COLUMN a SET 
DEFAULT 10");
+        SqlNode dflt = alterColumn.expression();
         assertThat(dflt, instanceOf(SqlLiteral.class));
         assertThat(((SqlLiteral) dflt).getValueAs(Integer.class), equalTo(10));
+        expectUnparsed(alterColumn, "ALTER TABLE \"T\" ALTER COLUMN \"A\" SET 
DEFAULT 10");
 
         assertThrowsSqlException(
                 Sql.STMT_PARSE_ERR,
                 "Failed to parse query: Encountered \"FUNC\"",
-                () -> parse(QUERY_PREFIX + "SET DEFAULT FUNC"));
+                () -> parse("ALTER TABLE t ALTER COLUMN a SET DEFAULT FUNC"));
     }
 
     /**
@@ -96,22 +101,42 @@ public class SqlAlterColumnDdlParserTest extends 
AbstractDdlParserTest {
      */
     @Test
     public void testSetDataType() {
-        validateDataType("SET DATA TYPE INTEGER", "INTEGER", null, null);
-        validateDataType("SET DATA TYPE INTEGER NOT NULL", "INTEGER", true, 
null);
-        validateDataType("SET DATA TYPE INTEGER NULL", "INTEGER", false, null);
-        validateDataType("SET DATA TYPE INTEGER DEFAULT -1", "INTEGER", null, 
-1L);
-        validateDataType("SET DATA TYPE INTEGER DEFAULT NULL", "INTEGER", 
null, null);
-        validateDataType("SET DATA TYPE INTEGER NOT NULL DEFAULT -1", 
"INTEGER", true, -1);
-        validateDataType("SET DATA TYPE INTEGER NULL DEFAULT NULL", "INTEGER", 
false, null);
+        IgniteSqlAlterColumn alterColumn = parseAlterColumn("ALTER TABLE t 
ALTER COLUMN c SET DATA TYPE INTEGER");
+        expectDataType(alterColumn, "INTEGER", null, null);
+        expectUnparsed(alterColumn, "ALTER TABLE \"T\" ALTER COLUMN \"C\" SET 
DATA TYPE INTEGER");
+
+        alterColumn = parseAlterColumn("ALTER TABLE t ALTER COLUMN c SET DATA 
TYPE INTEGER NOT NULL");
+        expectDataType(alterColumn, "INTEGER", true, null);
+        expectUnparsed(alterColumn, "ALTER TABLE \"T\" ALTER COLUMN \"C\" SET 
DATA TYPE INTEGER NOT NULL");
+
+        alterColumn = parseAlterColumn("ALTER TABLE t ALTER COLUMN c SET DATA 
TYPE INTEGER NULL");
+        expectDataType(alterColumn, "INTEGER", false, null);
+        expectUnparsed(alterColumn, "ALTER TABLE \"T\" ALTER COLUMN \"C\" SET 
DATA TYPE INTEGER NULL");
+
+        alterColumn = parseAlterColumn("ALTER TABLE t ALTER COLUMN c SET DATA 
TYPE INTEGER DEFAULT -1");
+        expectDataType(alterColumn, "INTEGER", null, -1);
+        expectUnparsed(alterColumn, "ALTER TABLE \"T\" ALTER COLUMN \"C\" SET 
DATA TYPE INTEGER DEFAULT -1");
+
+        alterColumn = parseAlterColumn("ALTER TABLE t ALTER COLUMN c SET DATA 
TYPE INTEGER NOT NULL DEFAULT -1");
+        expectDataType(alterColumn, "INTEGER", true, -1);
+        expectUnparsed(alterColumn, "ALTER TABLE \"T\" ALTER COLUMN \"C\" SET 
DATA TYPE INTEGER NOT NULL DEFAULT -1");
+
+        alterColumn = parseAlterColumn("ALTER TABLE t ALTER COLUMN c SET DATA 
TYPE INTEGER DEFAULT NULL");
+        expectDataType(alterColumn, "INTEGER", null, null);
+        expectUnparsed(alterColumn, "ALTER TABLE \"T\" ALTER COLUMN \"C\" SET 
DATA TYPE INTEGER DEFAULT NULL");
+
+        alterColumn = parseAlterColumn("ALTER TABLE t ALTER COLUMN c SET DATA 
TYPE INTEGER NULL DEFAULT NULL");
+        expectDataType(alterColumn, "INTEGER", false, null);
+        expectUnparsed(alterColumn, "ALTER TABLE \"T\" ALTER COLUMN \"C\" SET 
DATA TYPE INTEGER NULL DEFAULT NULL");
 
         assertThrowsSqlException(
                 Sql.STMT_PARSE_ERR,
                 "Failed to parse query: Encountered \"FUNC\"",
-                () -> parse(QUERY_PREFIX + "SET DATA TYPE INTEGER DEFAULT 
FUNC"));
+                () -> parse("ALTER TABLE t ALTER COLUMN a SET DATA TYPE 
INTEGER DEFAULT FUNC"));
     }
 
-    private void validateDataType(String querySuffix, @Nullable String 
typeName, @Nullable Boolean notNull, @Nullable Object expDefault) {
-        IgniteSqlAlterColumn alterColumn = parseAlterColumn(querySuffix);
+    private void expectDataType(IgniteSqlAlterColumn alterColumn,
+            @Nullable String typeName, @Nullable Boolean notNull, @Nullable 
Object expDefault) {
 
         assertNotNull(alterColumn.dataType());
         assertThat(alterColumn.dataType().getTypeName().getSimple(), 
equalTo(typeName));
@@ -134,35 +159,6 @@ public class SqlAlterColumnDdlParserTest extends 
AbstractDdlParserTest {
     }
 
     private IgniteSqlAlterColumn parseAlterColumn(String querySuffix) {
-        return parseAlterColumn(querySuffix, null);
-    }
-
-    private IgniteSqlAlterColumn parseAlterColumn(String querySuffix, 
@Nullable String unparseQuerySuffix) {
-        String query = QUERY_PREFIX + querySuffix;
-
-        SqlNode node = parse(query);
-        assertThat(node, instanceOf(IgniteSqlAlterColumn.class));
-
-        IgniteSqlAlterColumn alterColumn = (IgniteSqlAlterColumn) node;
-
-        assertThat(alterColumn.name().names, is(List.of(TABLE_NAME)));
-        assertThat(alterColumn.columnName().getSimple(), equalTo(COLUMN_NAME));
-
-        // Validate unparsed expression.
-        assertThat(unparse(alterColumn), equalTo(unparseQuerySuffix == null ? 
query : QUERY_PREFIX + unparseQuerySuffix));
-
-        return alterColumn;
-    }
-
-    private String unparse(SqlNode node) {
-        SqlPrettyWriter writer = new SqlPrettyWriter();
-        SqlFormatOptions opts = new SqlFormatOptions();
-
-        opts.setQuoteAllIdentifiers(false);
-        writer.setFormatOptions(opts);
-
-        node.unparse(writer, 0, 0);
-
-        return writer.toString();
+        return (IgniteSqlAlterColumn) parse(querySuffix);
     }
 }
diff --git 
a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/sql/SqlDdlParserTest.java
 
b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/sql/SqlDdlParserTest.java
index 928052d79d..340d00ab43 100644
--- 
a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/sql/SqlDdlParserTest.java
+++ 
b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/sql/SqlDdlParserTest.java
@@ -19,7 +19,6 @@ package org.apache.ignite.internal.sql.engine.sql;
 
 import static java.util.Collections.singleton;
 import static 
org.apache.ignite.internal.sql.engine.util.SqlTestUtils.assertThrowsSqlException;
-import static org.hamcrest.CoreMatchers.endsWith;
 import static org.hamcrest.CoreMatchers.hasItem;
 import static org.hamcrest.CoreMatchers.instanceOf;
 import static org.hamcrest.CoreMatchers.is;
@@ -39,7 +38,6 @@ import org.apache.calcite.sql.SqlLiteral;
 import org.apache.calcite.sql.SqlNode;
 import org.apache.calcite.sql.ddl.SqlColumnDeclaration;
 import org.apache.calcite.sql.ddl.SqlKeyConstraint;
-import org.apache.calcite.sql.pretty.SqlPrettyWriter;
 import org.apache.ignite.lang.ErrorGroups.Sql;
 import org.hamcrest.CustomMatcher;
 import org.hamcrest.Matcher;
@@ -66,6 +64,11 @@ public class SqlDdlParserTest extends AbstractDdlParserTest {
         assertThat(createTable.ifNotExists, is(false));
         assertThat(createTable.columnList(), hasItem(columnWithName("ID")));
         assertThat(createTable.columnList(), hasItem(columnWithName("VAL")));
+
+        expectUnparsed(node, "CREATE TABLE \"MY_TABLE\" ("
+                + "\"ID\" INTEGER, "
+                + "\"VAL\" VARCHAR)"
+        );
     }
 
     /**
@@ -95,6 +98,12 @@ public class SqlDdlParserTest extends AbstractDdlParserTest {
                         .matches(constraint.getOperandList().get(1))
                         && constraint.getOperandList().get(0) == null
                         && constraint.isA(singleton(SqlKind.PRIMARY_KEY)))));
+
+        expectUnparsed(node, "CREATE TABLE \"MY_TABLE\" ("
+                + "PRIMARY KEY (\"ID\"), "
+                + "\"ID\" VARCHAR DEFAULT (\"GEN_RANDOM_UUID\"), "
+                + "\"VAL\" VARCHAR)"
+        );
     }
 
     /**
@@ -114,6 +123,11 @@ public class SqlDdlParserTest extends 
AbstractDdlParserTest {
         assertThat(createTable.ifNotExists, is(false));
         assertThat(createTable.columnList(), hasItem(columnWithName("Id")));
         assertThat(createTable.columnList(), hasItem(columnWithName("Val")));
+
+        expectUnparsed(node, "CREATE TABLE \"My_Table\" "
+                + "(\"Id\" INTEGER, "
+                + "\"Val\" VARCHAR)"
+        );
     }
 
     /**
@@ -133,6 +147,10 @@ public class SqlDdlParserTest extends 
AbstractDdlParserTest {
         assertThat(createTable.ifNotExists, is(true));
         assertThat(createTable.columnList(), hasItem(columnWithName("ID")));
         assertThat(createTable.columnList(), hasItem(columnWithName("VAL")));
+
+        expectUnparsed(node, "CREATE TABLE IF NOT EXISTS \"MY_TABLE\" ("
+                + "\"ID\" INTEGER, \"VAL\" VARCHAR)"
+        );
     }
 
     /**
@@ -156,6 +174,12 @@ public class SqlDdlParserTest extends 
AbstractDdlParserTest {
                         .matches(constraint.getOperandList().get(1))
                         && constraint.getOperandList().get(0) == null
                         && constraint.isA(singleton(SqlKind.PRIMARY_KEY)))));
+
+        expectUnparsed(node, "CREATE TABLE \"MY_TABLE\" ("
+                + "PRIMARY KEY (\"ID\"), "
+                + "\"ID\" INTEGER, "
+                + "\"VAL\" VARCHAR)"
+        );
     }
 
     /**
@@ -179,6 +203,12 @@ public class SqlDdlParserTest extends 
AbstractDdlParserTest {
                         .matches(constraint.getOperandList().get(1))
                         && constraint.getOperandList().get(0) == null
                         && constraint.isA(singleton(SqlKind.PRIMARY_KEY)))));
+
+        expectUnparsed(node, "CREATE TABLE \"MY_TABLE\" "
+                + "(\"ID\" INTEGER, "
+                + "\"VAL\" VARCHAR, "
+                + "PRIMARY KEY (\"ID\"))"
+        );
     }
 
     /**
@@ -202,6 +232,12 @@ public class SqlDdlParserTest extends 
AbstractDdlParserTest {
                         .matches(constraint.getOperandList().get(1))
                         && "PK_KEY".equals(((SqlIdentifier) 
constraint.getOperandList().get(0)).names.get(0))
                         && constraint.isA(singleton(SqlKind.PRIMARY_KEY)))));
+
+        expectUnparsed(node, "CREATE TABLE \"MY_TABLE\" ("
+                + "\"ID\" INTEGER, "
+                + "\"VAL\" VARCHAR, "
+                + "CONSTRAINT \"PK_KEY\" PRIMARY KEY (\"ID\"))"
+        );
     }
 
     /**
@@ -227,6 +263,12 @@ public class SqlDdlParserTest extends 
AbstractDdlParserTest {
                         .matches(constraint.getOperandList().get(1))
                         && constraint.getOperandList().get(0) == null
                         && constraint.isA(singleton(SqlKind.PRIMARY_KEY)))));
+
+        expectUnparsed(node, "CREATE TABLE \"MY_TABLE\" ("
+                + "\"ID1\" INTEGER, \"ID2\" INTEGER, "
+                + "\"VAL\" VARCHAR, "
+                + "PRIMARY KEY (\"ID1\", \"ID2\"))"
+        );
     }
 
     /**
@@ -254,10 +296,11 @@ public class SqlDdlParserTest extends 
AbstractDdlParserTest {
                 equalTo(List.of("ID2", "ID1"))
         );
 
-        SqlPrettyWriter w = new SqlPrettyWriter();
-        createTable.unparse(w, 0, 0);
-
-        assertThat(w.toString(), endsWith("COLOCATE BY (\"ID2\", \"ID1\")"));
+        expectUnparsed(createTable, "CREATE TABLE \"MY_TABLE\" ("
+                + "\"ID0\" INTEGER, \"ID1\" INTEGER, "
+                + "\"ID2\" INTEGER, \"VAL\" INTEGER, PRIMARY KEY (\"ID0\", 
\"ID1\", \"ID2\")"
+                + ") COLOCATE BY (\"ID2\", \"ID1\")"
+        );
 
         createTable = parseCreateTable(
                 "CREATE TABLE MY_TABLE(ID0 INT, ID1 INT, ID2 INT, VAL INT, 
PRIMARY KEY (ID0, ID1, ID2)) COLOCATE (ID0)"
@@ -279,10 +322,11 @@ public class SqlDdlParserTest extends 
AbstractDdlParserTest {
                         + "partitions=3"
         );
 
-        w = new SqlPrettyWriter();
-        createTable.unparse(w, 0, 0);
-
-        assertThat(w.toString(), endsWith("COLOCATE BY (\"ID0\") WITH 
\"REPLICAS\" = 2, \"PARTITIONS\" = 3"));
+        expectUnparsed(createTable, "CREATE TABLE \"MY_TABLE\" ("
+                + "\"ID0\" INTEGER, \"ID1\" INTEGER, "
+                + "\"ID2\" INTEGER, \"VAL\" INTEGER, PRIMARY KEY (\"ID0\", 
\"ID1\", \"ID2\")"
+                + ") COLOCATE BY (\"ID0\") WITH \"REPLICAS\" = 2, 
\"PARTITIONS\" = 3"
+        );
     }
 
     @Test
@@ -301,6 +345,11 @@ public class SqlDdlParserTest extends 
AbstractDdlParserTest {
         assertThatOptionPresent(createTable.createOptionList().getList(), 
"REPLICAS", 2);
         assertThatOptionPresent(createTable.createOptionList().getList(), 
"PARTITIONS", 3);
         assertThatOptionPresent(createTable.createOptionList().getList(), 
"PRIMARY_ZONE", "zone123");
+
+        expectUnparsed(node, "CREATE TABLE \"MY_TABLE\" ("
+                + "\"ID\" INTEGER"
+                + ") WITH \"REPLICAS\" = 2, \"PARTITIONS\" = 3, 
\"PRIMARY_ZONE\" = 'zone123'"
+        );
     }
 
     @Test
@@ -319,6 +368,8 @@ public class SqlDdlParserTest extends AbstractDdlParserTest 
{
         assertThat(createIndex.type(), is(IgniteSqlIndexType.IMPLICIT_TREE));
         assertThat(createIndex.columnList(), hasItem(ofTypeMatching("col", 
SqlIdentifier.class,
                 id -> id.isSimple() && id.getSimple().equals("COL"))));
+
+        expectUnparsed(node, "CREATE INDEX \"MY_INDEX\" ON \"MY_TABLE\" 
(\"COL\")");
     }
 
     @Test
@@ -343,6 +394,8 @@ public class SqlDdlParserTest extends AbstractDdlParserTest 
{
                         && bc.getOperandList().get(0) instanceof SqlIdentifier
                         && ((SqlIdentifier) 
bc.getOperandList().get(0)).isSimple()
                         && ((SqlIdentifier) 
bc.getOperandList().get(0)).getSimple().equals("COL2"))));
+
+        expectUnparsed(node, "CREATE INDEX \"MY_INDEX\" ON \"MY_TABLE\" 
(\"COL1\", \"COL2\" DESC)");
     }
 
     @Test
@@ -369,6 +422,8 @@ public class SqlDdlParserTest extends AbstractDdlParserTest 
{
                         && bc.getOperandList().get(0) instanceof SqlIdentifier
                         && ((SqlIdentifier) 
bc.getOperandList().get(0)).isSimple()
                         && ((SqlIdentifier) 
bc.getOperandList().get(0)).getSimple().equals("COL3"))));
+
+        expectUnparsed(node, "CREATE INDEX \"MY_INDEX\" ON \"MY_TABLE\" USING 
TREE (\"COL1\", \"COL2\", \"COL3\" DESC)");
     }
 
     @Test
@@ -387,6 +442,8 @@ public class SqlDdlParserTest extends AbstractDdlParserTest 
{
         assertThat(createIndex.type(), is(IgniteSqlIndexType.HASH));
         assertThat(createIndex.columnList(), hasItem(ofTypeMatching("col", 
SqlIdentifier.class,
                 id -> id.isSimple() && id.getSimple().equals("COL"))));
+
+        expectUnparsed(node, "CREATE INDEX \"MY_INDEX\" ON \"MY_TABLE\" USING 
HASH (\"COL\")");
     }
 
     @Test
@@ -412,6 +469,8 @@ public class SqlDdlParserTest extends AbstractDdlParserTest 
{
         assertThat(createIndex.indexName().getSimple(), is("MY_INDEX"));
         assertThat(createIndex.tableName().names, is(List.of("MY_TABLE")));
         assertThat(createIndex.ifNotExists, is(true));
+
+        expectUnparsed(node, "CREATE INDEX IF NOT EXISTS \"MY_INDEX\" ON 
\"MY_TABLE\" (\"COL\")");
     }
 
     @Test
@@ -426,6 +485,8 @@ public class SqlDdlParserTest extends AbstractDdlParserTest 
{
 
         assertThat(createIndex.indexName().getSimple(), is("MY_INDEX"));
         assertThat(createIndex.tableName().names, is(List.of("MY_SCHEMA", 
"MY_TABLE")));
+
+        expectUnparsed(node, "CREATE INDEX \"MY_INDEX\" ON 
\"MY_SCHEMA\".\"MY_TABLE\" (\"COL\")");
     }
 
     @Test
@@ -462,6 +523,58 @@ public class SqlDdlParserTest extends 
AbstractDdlParserTest {
                         && ((SqlIdentifier) ((SqlBasicCall) 
bc.getOperandList().get(0)).getOperandList().get(0)).isSimple()
                         && ((SqlIdentifier) ((SqlBasicCall) 
bc.getOperandList().get(0)).getOperandList().get(0))
                                 .getSimple().equals("COL3"))));
+
+        expectUnparsed(node, "CREATE INDEX \"MY_INDEX\" ON \"MY_TABLE\" ("
+                + "\"COL1\" NULLS FIRST, \"COL2\" NULLS LAST, \"COL3\" DESC 
NULLS FIRST)"
+        );
+    }
+
+    @Test
+    public void dropTable() {
+        var query = "drop table my_table";
+
+        SqlNode node = parse(query);
+
+        assertThat(node, instanceOf(IgniteSqlDropTable.class));
+
+        var dropIndex = (IgniteSqlDropTable) node;
+
+        assertThat(dropIndex.ifExists(), is(false));
+        assertThat(dropIndex.name().names, is(List.of("MY_TABLE")));
+
+        expectUnparsed(node, "DROP TABLE \"MY_TABLE\"");
+    }
+
+    @Test
+    public void dropTableSchemaSpecified() {
+        var query = "drop table my_schema.my_table";
+
+        SqlNode node = parse(query);
+
+        assertThat(node, instanceOf(IgniteSqlDropTable.class));
+
+        var dropIndex = (IgniteSqlDropTable) node;
+
+        assertThat(dropIndex.ifExists(), is(false));
+        assertThat(dropIndex.name().names, is(List.of("MY_SCHEMA", 
"MY_TABLE")));
+
+        expectUnparsed(node, "DROP TABLE \"MY_SCHEMA\".\"MY_TABLE\"");
+    }
+
+    @Test
+    public void dropTableIfExists() {
+        var query = "drop table if exists my_table";
+
+        SqlNode node = parse(query);
+
+        assertThat(node, instanceOf(IgniteSqlDropTable.class));
+
+        var dropIndex = (IgniteSqlDropTable) node;
+
+        assertThat(dropIndex.ifExists(), is(true));
+        assertThat(dropIndex.name().names, is(List.of("MY_TABLE")));
+
+        expectUnparsed(node, "DROP TABLE IF EXISTS \"MY_TABLE\"");
     }
 
     @Test
@@ -476,6 +589,8 @@ public class SqlDdlParserTest extends AbstractDdlParserTest 
{
 
         assertThat(dropIndex.ifExists(), is(false));
         assertThat(dropIndex.indexName().names, is(List.of("MY_INDEX")));
+
+        expectUnparsed(node, "DROP INDEX \"MY_INDEX\"");
     }
 
     @Test
@@ -490,6 +605,8 @@ public class SqlDdlParserTest extends AbstractDdlParserTest 
{
 
         assertThat(dropIndex.ifExists(), is(false));
         assertThat(dropIndex.indexName().names, is(List.of("MY_SCHEMA", 
"MY_INDEX")));
+
+        expectUnparsed(node, "DROP INDEX \"MY_SCHEMA\".\"MY_INDEX\"");
     }
 
     @Test
@@ -504,6 +621,8 @@ public class SqlDdlParserTest extends AbstractDdlParserTest 
{
 
         assertThat(dropIndex.ifExists(), is(true));
         assertThat(dropIndex.indexName().names, is(List.of("MY_INDEX")));
+
+        expectUnparsed(node, "DROP INDEX IF EXISTS \"MY_INDEX\"");
     }
 
     /**
diff --git 
a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/sql/SqlTransactionControlParserTest.java
 
b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/sql/SqlTransactionControlParserTest.java
index 7650b0bcbf..42993f3fa5 100644
--- 
a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/sql/SqlTransactionControlParserTest.java
+++ 
b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/sql/SqlTransactionControlParserTest.java
@@ -22,7 +22,6 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertInstanceOf;
 
 import org.apache.calcite.sql.SqlNode;
-import org.apache.calcite.sql.pretty.SqlPrettyWriter;
 import org.apache.ignite.lang.ErrorGroups.Sql;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.params.ParameterizedTest;
@@ -31,7 +30,7 @@ import org.junit.jupiter.params.provider.CsvSource;
 /**
  * Tests for transaction control SQL statements.
  */
-public class SqlTransactionControlParserTest {
+public class SqlTransactionControlParserTest extends AbstractDdlParserTest {
 
     @ParameterizedTest
     @CsvSource({
@@ -40,7 +39,7 @@ public class SqlTransactionControlParserTest {
             "START TRANSACTION,IMPLICIT_READ_WRITE",
     })
     public void testStartTx(String sqlStmt, IgniteSqlStartTransactionMode 
mode) {
-        SqlNode node = parseStatement(sqlStmt);
+        SqlNode node = parse(sqlStmt);
         IgniteSqlStartTransaction start = 
assertInstanceOf(IgniteSqlStartTransaction.class, node);
         assertEquals(mode, start.getMode());
 
@@ -49,27 +48,16 @@ public class SqlTransactionControlParserTest {
 
     @Test
     public void testStartRejectInvalid() {
-        assertThrowsSqlException(Sql.STMT_PARSE_ERR, " Encountered \"XY\"", () 
-> parseStatement("START TRANSACTION XY"));
+        assertThrowsSqlException(Sql.STMT_PARSE_ERR, " Encountered \"XY\"", () 
-> parse("START TRANSACTION XY"));
     }
 
     @Test
     public void testCommit() {
         String sql = "COMMIT";
 
-        SqlNode node = parseStatement(sql);
+        SqlNode node = parse(sql);
         assertInstanceOf(IgniteSqlCommitTransaction.class, node);
 
         expectUnparsed(node, sql);
     }
-
-    private static SqlNode parseStatement(String sqlStmt) {
-        return IgniteSqlParser.parse(sqlStmt, 
StatementParseResult.MODE).statement();
-    }
-
-    private static void expectUnparsed(SqlNode node, String expected) {
-        SqlPrettyWriter writer = new SqlPrettyWriter();
-        node.unparse(writer, 0, 0);
-
-        assertEquals(expected, writer.toString(), "Unparse does not match");
-    }
 }

Reply via email to