You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ignite.apache.org by pp...@apache.org on 2022/12/05 09:58:54 UTC

[ignite-3] branch main updated: IGNITE-18157 Sql. Provide commands and handlers for distribution zones related operations (#1385)

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 085ebed682 IGNITE-18157 Sql. Provide commands and handlers for distribution zones related operations (#1385)
085ebed682 is described below

commit 085ebed682e0d4d7f26ca91266aa48e2dfd823fa
Author: Pavel Pereslegin <xx...@gmail.com>
AuthorDate: Mon Dec 5 12:58:48 2022 +0300

    IGNITE-18157 Sql. Provide commands and handlers for distribution zones related operations (#1385)
---
 .../java/org/apache/ignite/lang/ErrorGroups.java   |  35 ++--
 .../org/apache/ignite/internal/app/IgniteImpl.java |  11 +-
 modules/sql-engine/build.gradle                    |   1 +
 modules/sql-engine/pom.xml                         |   5 +
 .../internal/sql/engine/SqlQueryProcessor.java     |   7 +
 .../sql/engine/exec/ExecutionServiceImpl.java      |   4 +-
 .../sql/engine/exec/ddl/DdlCommandHandler.java     |  55 ++++-
 .../sql/engine/prepare/PrepareServiceImpl.java     |   4 +-
 ...OptionInfo.java => AbstractZoneDdlCommand.java} |  47 ++---
 .../sql/engine/prepare/ddl/CreateZoneCommand.java  | 113 ++++++++++
 .../{TableOptionInfo.java => DdlOptionInfo.java}   |  21 +-
 .../prepare/ddl/DdlSqlToCommandConverter.java      | 227 ++++++++++++++-------
 .../{TableOptionInfo.java => DropZoneCommand.java} |  40 +---
 .../sql/engine/sql/IgniteSqlCreateZone.java        |   2 +-
 .../internal/sql/engine/sql/IgniteSqlDropZone.java |   2 +-
 .../internal/sql/engine/StopCalciteModuleTest.java |   5 +
 .../sql/engine/exec/MockedStructuresTest.java      |   5 +
 .../ddl/DistributionZoneDdlCommandHandlerTest.java | 144 +++++++++++++
 .../ddl/AbstractDdlSqlToCommandConverterTest.java  |  66 ++++++
 .../prepare/ddl/DdlSqlToCommandConverterTest.java  | 104 ++--------
 .../DistributionZoneSqlToCommandConverterTest.java | 124 +++++++++++
 ....java => DistributionZoneSqlDdlParserTest.java} |   2 +-
 22 files changed, 750 insertions(+), 274 deletions(-)

diff --git a/modules/core/src/main/java/org/apache/ignite/lang/ErrorGroups.java b/modules/core/src/main/java/org/apache/ignite/lang/ErrorGroups.java
index 6d47deadeb..4b215db2a8 100755
--- a/modules/core/src/main/java/org/apache/ignite/lang/ErrorGroups.java
+++ b/modules/core/src/main/java/org/apache/ignite/lang/ErrorGroups.java
@@ -134,7 +134,7 @@ public class ErrorGroups {
         public static final int TOO_MANY_GROUPING_EXPRESSIONS_ERR = SQL_ERR_GROUP.registerErrorCode(12);
 
         /** Unsupported sql operation. */
-        public static final int USUPPORTED_SQL_OPERATION_KIND_ERR = SQL_ERR_GROUP.registerErrorCode(13);
+        public static final int UNSUPPORTED_SQL_OPERATION_KIND_ERR = SQL_ERR_GROUP.registerErrorCode(13);
 
         /** Unsupported DDL operation. */
         public static final int UNSUPPORTED_DDL_OPERATION_ERR = SQL_ERR_GROUP.registerErrorCode(14);
@@ -151,53 +151,50 @@ public class ErrorGroups {
         /** Table version not found. */
         public static final int TABLE_VER_NOT_FOUND_ERR = SQL_ERR_GROUP.registerErrorCode(18);
 
-        /** Invalid table option. */
-        public static final int TABLE_OPTION_ERR = SQL_ERR_GROUP.registerErrorCode(19);
-
         /** Query mapping error. */
-        public static final int QUERY_MAPPING_ERR = SQL_ERR_GROUP.registerErrorCode(20);
+        public static final int QUERY_MAPPING_ERR = SQL_ERR_GROUP.registerErrorCode(19);
 
         /** DDL execution error. */
-        public static final int DDL_EXEC_ERR = SQL_ERR_GROUP.registerErrorCode(21);
+        public static final int DDL_EXEC_ERR = SQL_ERR_GROUP.registerErrorCode(20);
 
         /** DML result error. */
-        public static final int INVALID_DML_RESULT_ERR = SQL_ERR_GROUP.registerErrorCode(22);
+        public static final int INVALID_DML_RESULT_ERR = SQL_ERR_GROUP.registerErrorCode(21);
 
         /** SQL data type to relational conversion error. */
-        public static final int SQL_TO_REL_CONVERSION_ERR = SQL_ERR_GROUP.registerErrorCode(23);
+        public static final int SQL_TO_REL_CONVERSION_ERR = SQL_ERR_GROUP.registerErrorCode(22);
 
         /** Relational expression serialization error. */
-        public static final int REL_SERIALIZATION_ERR = SQL_ERR_GROUP.registerErrorCode(24);
+        public static final int REL_SERIALIZATION_ERR = SQL_ERR_GROUP.registerErrorCode(23);
 
         /** Relational expression deserialization error. */
-        public static final int REL_DESERIALIZATION_ERR = SQL_ERR_GROUP.registerErrorCode(25);
+        public static final int REL_DESERIALIZATION_ERR = SQL_ERR_GROUP.registerErrorCode(24);
 
         /** Class not found error. */
-        public static final int CLASS_NOT_FOUND_ERR = SQL_ERR_GROUP.registerErrorCode(26);
+        public static final int CLASS_NOT_FOUND_ERR = SQL_ERR_GROUP.registerErrorCode(25);
 
         /** Expression compilation error. */
-        public static final int EXPRESSION_COMPILATION_ERR = SQL_ERR_GROUP.registerErrorCode(27);
+        public static final int EXPRESSION_COMPILATION_ERR = SQL_ERR_GROUP.registerErrorCode(26);
 
         /** Node left the cluster. */
-        public static final int NODE_LEFT_ERR = SQL_ERR_GROUP.registerErrorCode(28);
+        public static final int NODE_LEFT_ERR = SQL_ERR_GROUP.registerErrorCode(27);
 
         /** Message send error. */
-        public static final int MESSAGE_SEND_ERR = SQL_ERR_GROUP.registerErrorCode(29);
+        public static final int MESSAGE_SEND_ERR = SQL_ERR_GROUP.registerErrorCode(28);
 
         /** Operation aborted/interrupted error. */
-        public static final int OPERATION_INTERRUPTED_ERR = SQL_ERR_GROUP.registerErrorCode(30);
+        public static final int OPERATION_INTERRUPTED_ERR = SQL_ERR_GROUP.registerErrorCode(29);
 
         /** An error occurred while canceling the operation. */
-        public static final int CANCEL_OPERATION_ERR = SQL_ERR_GROUP.registerErrorCode(31);
+        public static final int CANCEL_OPERATION_ERR = SQL_ERR_GROUP.registerErrorCode(30);
 
         /** Session expired error. */
-        public static final int SESSION_EXPIRED_ERR = SQL_ERR_GROUP.registerErrorCode(32);
+        public static final int SESSION_EXPIRED_ERR = SQL_ERR_GROUP.registerErrorCode(31);
 
         /** Schema evaluation error. */
-        public static final int SCHEMA_EVALUATION_ERR = SQL_ERR_GROUP.registerErrorCode(33);
+        public static final int SCHEMA_EVALUATION_ERR = SQL_ERR_GROUP.registerErrorCode(32);
 
         /** Execution cancelled. */
-        public static final int EXECUTION_CANCELLED_ERR = SQL_ERR_GROUP.registerErrorCode(34);
+        public static final int EXECUTION_CANCELLED_ERR = SQL_ERR_GROUP.registerErrorCode(33);
     }
 
     /** Meta storage error group. */
diff --git a/modules/runner/src/main/java/org/apache/ignite/internal/app/IgniteImpl.java b/modules/runner/src/main/java/org/apache/ignite/internal/app/IgniteImpl.java
index 940b364058..58b4d7f4a3 100644
--- a/modules/runner/src/main/java/org/apache/ignite/internal/app/IgniteImpl.java
+++ b/modules/runner/src/main/java/org/apache/ignite/internal/app/IgniteImpl.java
@@ -412,6 +412,11 @@ public class IgniteImpl implements Ignite {
 
         indexManager = new IndexManager(tablesConfiguration, schemaManager, distributedTblMgr);
 
+        DistributionZonesConfiguration zonesConfiguration = clusterCfgMgr.configurationRegistry()
+                .getConfiguration(DistributionZonesConfiguration.KEY);
+
+        distributionZoneManager = new DistributionZoneManager(zonesConfiguration);
+
         qryEngine = new SqlQueryProcessor(
                 registry,
                 clusterSvc,
@@ -420,6 +425,7 @@ public class IgniteImpl implements Ignite {
                 schemaManager,
                 dataStorageMgr,
                 txManager,
+                distributionZoneManager,
                 () -> dataStorageModules.collectSchemasFields(modules.distributed().polymorphicSchemaExtensions()),
                 clock
         );
@@ -439,11 +445,6 @@ public class IgniteImpl implements Ignite {
                 sql,
                 () -> cmgMgr.clusterState().thenApply(s -> s.clusterTag().clusterId())
         );
-
-        DistributionZonesConfiguration zonesConfiguration = clusterCfgMgr.configurationRegistry()
-                .getConfiguration(DistributionZonesConfiguration.KEY);
-
-        distributionZoneManager = new DistributionZoneManager(zonesConfiguration);
     }
 
     private RestComponent createRestComponent(String name) {
diff --git a/modules/sql-engine/build.gradle b/modules/sql-engine/build.gradle
index 70af81ceeb..8653e6a368 100644
--- a/modules/sql-engine/build.gradle
+++ b/modules/sql-engine/build.gradle
@@ -32,6 +32,7 @@ dependencies {
     implementation project(':ignite-schema')
     implementation project(':ignite-transactions')
     implementation project(':ignite-replicator')
+    implementation project(':ignite-distribution-zones')
     implementation libs.jetbrains.annotations
     implementation libs.fastutil.core
     implementation libs.caffeine
diff --git a/modules/sql-engine/pom.xml b/modules/sql-engine/pom.xml
index 7eb4eca49f..6ad7ca90dc 100644
--- a/modules/sql-engine/pom.xml
+++ b/modules/sql-engine/pom.xml
@@ -57,6 +57,11 @@
             <artifactId>ignite-table</artifactId>
         </dependency>
 
+        <dependency>
+            <groupId>org.apache.ignite</groupId>
+            <artifactId>ignite-distribution-zones</artifactId>
+        </dependency>
+
         <dependency>
             <groupId>com.github.ben-manes.caffeine</groupId>
             <artifactId>caffeine</artifactId>
diff --git a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/SqlQueryProcessor.java b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/SqlQueryProcessor.java
index fd74d12688..1b98bca14c 100644
--- a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/SqlQueryProcessor.java
+++ b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/SqlQueryProcessor.java
@@ -43,6 +43,7 @@ import org.apache.calcite.sql.SqlNode;
 import org.apache.calcite.sql.SqlNodeList;
 import org.apache.calcite.tools.Frameworks;
 import org.apache.calcite.util.Pair;
+import org.apache.ignite.internal.distributionzones.DistributionZoneManager;
 import org.apache.ignite.internal.hlc.HybridClock;
 import org.apache.ignite.internal.hlc.HybridTimestamp;
 import org.apache.ignite.internal.index.IndexManager;
@@ -141,6 +142,9 @@ public class SqlQueryProcessor implements QueryProcessor {
     /** Transaction manager. */
     private final TxManager txManager;
 
+    /** Distribution zones manager. */
+    private final DistributionZoneManager distributionZoneManager;
+
     /** Clock. */
     private final HybridClock clock;
 
@@ -153,6 +157,7 @@ public class SqlQueryProcessor implements QueryProcessor {
             SchemaManager schemaManager,
             DataStorageManager dataStorageManager,
             TxManager txManager,
+            DistributionZoneManager distributionZoneManager,
             Supplier<Map<String, Map<String, Class<?>>>> dataStorageFieldsSupplier,
             HybridClock clock
     ) {
@@ -163,6 +168,7 @@ public class SqlQueryProcessor implements QueryProcessor {
         this.schemaManager = schemaManager;
         this.dataStorageManager = dataStorageManager;
         this.txManager = txManager;
+        this.distributionZoneManager = distributionZoneManager;
         this.dataStorageFieldsSupplier = dataStorageFieldsSupplier;
         this.clock = clock;
     }
@@ -208,6 +214,7 @@ public class SqlQueryProcessor implements QueryProcessor {
                 clusterSrvc.topologyService(),
                 msgSrvc,
                 sqlSchemaManager,
+                distributionZoneManager,
                 tableManager,
                 indexManager,
                 taskExecutor,
diff --git a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/ExecutionServiceImpl.java b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/ExecutionServiceImpl.java
index 540c9ec6bf..1d401738c7 100644
--- a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/ExecutionServiceImpl.java
+++ b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/ExecutionServiceImpl.java
@@ -40,6 +40,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.function.Function;
 import org.apache.calcite.tools.Frameworks;
 import org.apache.ignite.configuration.ConfigurationChangeException;
+import org.apache.ignite.internal.distributionzones.DistributionZoneManager;
 import org.apache.ignite.internal.hlc.HybridTimestamp;
 import org.apache.ignite.internal.index.IndexManager;
 import org.apache.ignite.internal.logger.IgniteLogger;
@@ -130,6 +131,7 @@ public class ExecutionServiceImpl<RowT> implements ExecutionService, TopologyEve
             TopologyService topSrvc,
             MessageService msgSrvc,
             SqlSchemaManager sqlSchemaManager,
+            DistributionZoneManager distributionZoneManager,
             TableManager tblManager,
             IndexManager indexManager,
             QueryTaskExecutor taskExecutor,
@@ -143,7 +145,7 @@ public class ExecutionServiceImpl<RowT> implements ExecutionService, TopologyEve
                 msgSrvc,
                 new MappingServiceImpl(topSrvc),
                 sqlSchemaManager,
-                new DdlCommandHandler(tblManager, indexManager, dataStorageManager),
+                new DdlCommandHandler(distributionZoneManager, tblManager, indexManager, dataStorageManager),
                 taskExecutor,
                 handler,
                 exchangeSrvc,
diff --git a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/ddl/DdlCommandHandler.java b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/ddl/DdlCommandHandler.java
index b3927b655d..487f6e4bfb 100644
--- a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/ddl/DdlCommandHandler.java
+++ b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/ddl/DdlCommandHandler.java
@@ -37,6 +37,10 @@ import java.util.function.BiFunction;
 import java.util.function.Consumer;
 import java.util.stream.Collectors;
 import org.apache.ignite.configuration.NamedListView;
+import org.apache.ignite.internal.distributionzones.DistributionZoneConfigurationParameters;
+import org.apache.ignite.internal.distributionzones.DistributionZoneManager;
+import org.apache.ignite.internal.distributionzones.exception.DistributionZoneAlreadyExistsException;
+import org.apache.ignite.internal.distributionzones.exception.DistributionZoneNotFoundException;
 import org.apache.ignite.internal.index.IndexManager;
 import org.apache.ignite.internal.schema.BitmaskNativeType;
 import org.apache.ignite.internal.schema.DecimalNativeType;
@@ -63,11 +67,13 @@ import org.apache.ignite.internal.sql.engine.prepare.ddl.AlterTableDropCommand;
 import org.apache.ignite.internal.sql.engine.prepare.ddl.ColumnDefinition;
 import org.apache.ignite.internal.sql.engine.prepare.ddl.CreateIndexCommand;
 import org.apache.ignite.internal.sql.engine.prepare.ddl.CreateTableCommand;
+import org.apache.ignite.internal.sql.engine.prepare.ddl.CreateZoneCommand;
 import org.apache.ignite.internal.sql.engine.prepare.ddl.DdlCommand;
 import org.apache.ignite.internal.sql.engine.prepare.ddl.DefaultValueDefinition.ConstantValue;
 import org.apache.ignite.internal.sql.engine.prepare.ddl.DefaultValueDefinition.FunctionCall;
 import org.apache.ignite.internal.sql.engine.prepare.ddl.DropIndexCommand;
 import org.apache.ignite.internal.sql.engine.prepare.ddl.DropTableCommand;
+import org.apache.ignite.internal.sql.engine.prepare.ddl.DropZoneCommand;
 import org.apache.ignite.internal.sql.engine.schema.IgniteIndex.Collation;
 import org.apache.ignite.internal.sql.engine.type.IgniteTypeFactory;
 import org.apache.ignite.internal.storage.DataStorageManager;
@@ -84,6 +90,8 @@ import org.apache.ignite.sql.SqlException;
 
 /** DDL commands handler. */
 public class DdlCommandHandler {
+    private final DistributionZoneManager distributionZoneManager;
+
     private final TableManager tableManager;
 
     private final IndexManager indexManager;
@@ -94,10 +102,12 @@ public class DdlCommandHandler {
      * Constructor.
      */
     public DdlCommandHandler(
+            DistributionZoneManager distributionZoneManager,
             TableManager tableManager,
             IndexManager indexManager,
             DataStorageManager dataStorageManager
     ) {
+        this.distributionZoneManager = distributionZoneManager;
         this.tableManager = tableManager;
         this.indexManager = indexManager;
         this.dataStorageManager = dataStorageManager;
@@ -119,6 +129,10 @@ public class DdlCommandHandler {
             return handleCreateIndex((CreateIndexCommand) cmd);
         } else if (cmd instanceof DropIndexCommand) {
             return handleDropIndex((DropIndexCommand) cmd);
+        } else if (cmd instanceof CreateZoneCommand) {
+            return handleCreateZone((CreateZoneCommand) cmd);
+        } else if (cmd instanceof DropZoneCommand) {
+            return handleDropZone((DropZoneCommand) cmd);
         } else {
             return failedFuture(new IgniteInternalCheckedException(UNSUPPORTED_DDL_OPERATION_ERR, "Unsupported DDL operation ["
                     + "cmdName=" + (cmd == null ? null : cmd.getClass().getSimpleName()) + "; "
@@ -137,6 +151,33 @@ public class DdlCommandHandler {
         }
     }
 
+    /** Handles create distribution zone command. */
+    private CompletableFuture<Boolean> handleCreateZone(CreateZoneCommand cmd) {
+        DistributionZoneConfigurationParameters.Builder zoneCfgBuilder =
+                new DistributionZoneConfigurationParameters.Builder(cmd.zoneName());
+
+        if (cmd.dataNodesAutoAdjust() != null) {
+            zoneCfgBuilder.dataNodesAutoAdjust(cmd.dataNodesAutoAdjust());
+        }
+
+        if (cmd.dataNodesAutoAdjustScaleUp() != null) {
+            zoneCfgBuilder.dataNodesAutoAdjustScaleUp(cmd.dataNodesAutoAdjustScaleUp());
+        }
+
+        if (cmd.dataNodesAutoAdjustScaleDown() != null) {
+            zoneCfgBuilder.dataNodesAutoAdjustScaleDown(cmd.dataNodesAutoAdjustScaleDown());
+        }
+
+        return distributionZoneManager.createZone(zoneCfgBuilder.build())
+                .handle(handleModificationResult(cmd.ifNotExists(), DistributionZoneAlreadyExistsException.class));
+    }
+
+    /** Handles drop distribution zone command. */
+    private CompletableFuture<Boolean> handleDropZone(DropZoneCommand cmd) {
+        return distributionZoneManager.dropZone(cmd.zoneName())
+                .handle(handleModificationResult(cmd.ifExists(), DistributionZoneNotFoundException.class));
+    }
+
     /** Handles create table command. */
     private CompletableFuture<Boolean> handleCreateTable(CreateTableCommand cmd) {
         Consumer<TableChange> tblChanger = tableChange -> {
@@ -170,14 +211,14 @@ public class DdlCommandHandler {
 
         return tableManager.createTableAsync(cmd.tableName(), tblChanger)
                 .thenApply(Objects::nonNull)
-                .handle(handleTableModificationResult(cmd.ifTableExists()));
+                .handle(handleModificationResult(cmd.ifTableExists(), TableAlreadyExistsException.class));
     }
 
     /** Handles drop table command. */
     private CompletableFuture<Boolean> handleDropTable(DropTableCommand cmd) {
         return tableManager.dropTableAsync(cmd.tableName())
                 .thenApply(v -> Boolean.TRUE)
-                .handle(handleTableModificationResult(cmd.ifTableExists()));
+                .handle(handleModificationResult(cmd.ifTableExists(), TableNotFoundException.class));
     }
 
     /** Handles add column command. */
@@ -187,7 +228,7 @@ public class DdlCommandHandler {
         }
 
         return addColumnInternal(cmd.tableName(), cmd.columns(), cmd.ifColumnNotExists())
-                .handle(handleTableModificationResult(cmd.ifTableExists()));
+                .handle(handleModificationResult(cmd.ifTableExists(), TableNotFoundException.class));
     }
 
     /** Handles drop column command. */
@@ -197,17 +238,17 @@ public class DdlCommandHandler {
         }
 
         return dropColumnInternal(cmd.tableName(), cmd.columns(), cmd.ifColumnExists())
-                .handle(handleTableModificationResult(cmd.ifTableExists()));
+                .handle(handleModificationResult(cmd.ifTableExists(), TableNotFoundException.class));
     }
 
-    private static BiFunction<Object, Throwable, Boolean> handleTableModificationResult(boolean ignoreTableExistenceErrors) {
+    private static BiFunction<Object, Throwable, Boolean> handleModificationResult(boolean ignoreExpectedError, Class<?> expErrCls) {
         return (val, err) -> {
             if (err == null) {
                 return val instanceof Boolean ? (Boolean) val : Boolean.TRUE;
-            } else if (ignoreTableExistenceErrors) {
+            } else if (ignoreExpectedError) {
                 Throwable err0 = err instanceof CompletionException ? err.getCause() : err;
 
-                if (err0 instanceof TableAlreadyExistsException || err0 instanceof TableNotFoundException) {
+                if (expErrCls.isAssignableFrom(err0.getClass())) {
                     return Boolean.FALSE;
                 }
             }
diff --git a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/PrepareServiceImpl.java b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/PrepareServiceImpl.java
index 7f79156fae..3ebc985bc1 100644
--- a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/PrepareServiceImpl.java
+++ b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/PrepareServiceImpl.java
@@ -20,7 +20,7 @@ package org.apache.ignite.internal.sql.engine.prepare;
 import static org.apache.ignite.internal.sql.engine.prepare.PlannerHelper.optimize;
 import static org.apache.ignite.internal.sql.engine.trait.TraitUtils.distributionPresent;
 import static org.apache.ignite.lang.ErrorGroups.Sql.QUERY_VALIDATION_ERR;
-import static org.apache.ignite.lang.ErrorGroups.Sql.USUPPORTED_SQL_OPERATION_KIND_ERR;
+import static org.apache.ignite.lang.ErrorGroups.Sql.UNSUPPORTED_SQL_OPERATION_KIND_ERR;
 
 import com.github.benmanes.caffeine.cache.Caffeine;
 import java.util.ArrayList;
@@ -172,7 +172,7 @@ public class PrepareServiceImpl implements PrepareService, SchemaUpdateListener
                     return prepareExplain(sqlNode, planningContext);
 
                 default:
-                    throw new IgniteInternalException(USUPPORTED_SQL_OPERATION_KIND_ERR, "Unsupported operation ["
+                    throw new IgniteInternalException(UNSUPPORTED_SQL_OPERATION_KIND_ERR, "Unsupported operation ["
                             + "sqlNodeKind=" + sqlNode.getKind() + "; "
                             + "querySql=\"" + planningContext.query() + "\"]");
             }
diff --git a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/ddl/TableOptionInfo.java b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/ddl/AbstractZoneDdlCommand.java
similarity index 50%
copy from modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/ddl/TableOptionInfo.java
copy to modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/ddl/AbstractZoneDdlCommand.java
index bccc783044..2be4e10ffe 100644
--- a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/ddl/TableOptionInfo.java
+++ b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/ddl/AbstractZoneDdlCommand.java
@@ -17,40 +17,29 @@
 
 package org.apache.ignite.internal.sql.engine.prepare.ddl;
 
-import java.util.function.BiConsumer;
-import java.util.function.Consumer;
-import org.jetbrains.annotations.Nullable;
-
 /**
- * Table option information for its processing.
+ * Abstract zone ddl command.
  */
-class TableOptionInfo<T> {
-    final String name;
+public class AbstractZoneDdlCommand implements DdlCommand {
+    /** Table zone. */
+    private String zoneName;
 
-    final Class<T> type;
+    /** Schema name where this new zone will be created. */
+    private String schemaName;
 
-    @Nullable
-    final Consumer<T> validator;
+    public String zoneName() {
+        return zoneName;
+    }
 
-    final BiConsumer<CreateTableCommand, T> setter;
+    public void zoneName(String zoneName) {
+        this.zoneName = zoneName;
+    }
+
+    public String schemaName() {
+        return schemaName;
+    }
 
-    /**
-     * Constructor.
-     *
-     * @param name Table option name.
-     * @param type Table option type.
-     * @param validator Table option value validator.
-     * @param setter Table option value setter.
-     */
-    TableOptionInfo(
-            String name,
-            Class<T> type,
-            @Nullable Consumer<T> validator,
-            BiConsumer<CreateTableCommand, T> setter
-    ) {
-        this.name = name;
-        this.type = type;
-        this.validator = validator;
-        this.setter = setter;
+    public void schemaName(String schemaName) {
+        this.schemaName = schemaName;
     }
 }
diff --git a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/ddl/CreateZoneCommand.java b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/ddl/CreateZoneCommand.java
new file mode 100644
index 0000000000..c1a5ca096c
--- /dev/null
+++ b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/ddl/CreateZoneCommand.java
@@ -0,0 +1,113 @@
+/*
+ * 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.prepare.ddl;
+
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * CREATE ZONE statement.
+ */
+public class CreateZoneCommand extends AbstractZoneDdlCommand {
+    /** Quietly ignore this command if zone is already exists. */
+    private boolean ifNotExists;
+
+    /** Replicas number. */
+    private Integer replicas;
+
+    /** Number of partitions. */
+    private Integer partitions;
+
+    /** Affinity function name. */
+    private String affinity;
+
+    /** Data nodes filter expression. */
+    private String nodeFiler;
+
+    /** Data nodes auto adjust timeout. */
+    private Integer dataNodesAutoAdjust;
+
+    /** Data nodes auto adjust scale up timeout. */
+    private Integer dataNodesAutoAdjustScaleUp;
+
+    /** Data nodes auto adjust scale down timeout. */
+    private Integer dataNodesAutoAdjustScaleDown;
+
+    public boolean ifNotExists() {
+        return ifNotExists;
+    }
+
+    public void ifNotExists(boolean ifNotExists) {
+        this.ifNotExists = ifNotExists;
+    }
+
+    @Nullable public Integer replicas() {
+        return replicas;
+    }
+
+    public void replicas(Integer replicas) {
+        this.replicas = replicas;
+    }
+
+    @Nullable public Integer partitions() {
+        return partitions;
+    }
+
+    public void partitions(Integer partitions) {
+        this.partitions = partitions;
+    }
+
+    @Nullable public String affinity() {
+        return affinity;
+    }
+
+    public void affinity(String affinity) {
+        this.affinity = affinity;
+    }
+
+    @Nullable public String nodeFilter() {
+        return nodeFiler;
+    }
+
+    public void nodeFilter(String nodeFiler) {
+        this.nodeFiler = nodeFiler;
+    }
+
+    @Nullable public Integer dataNodesAutoAdjust() {
+        return dataNodesAutoAdjust;
+    }
+
+    public void dataNodesAutoAdjust(Integer dataNodesAutoAdjust) {
+        this.dataNodesAutoAdjust = dataNodesAutoAdjust;
+    }
+
+    @Nullable public Integer dataNodesAutoAdjustScaleUp() {
+        return dataNodesAutoAdjustScaleUp;
+    }
+
+    public void dataNodesAutoAdjustScaleUp(Integer dataNodesAutoAdjustScaleUp) {
+        this.dataNodesAutoAdjustScaleUp = dataNodesAutoAdjustScaleUp;
+    }
+
+    @Nullable public Integer dataNodesAutoAdjustScaleDown() {
+        return dataNodesAutoAdjustScaleDown;
+    }
+
+    public void dataNodesAutoAdjustScaleDown(Integer dataNodesAutoAdjustScaleDown) {
+        this.dataNodesAutoAdjustScaleDown = dataNodesAutoAdjustScaleDown;
+    }
+}
diff --git a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/ddl/TableOptionInfo.java b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/ddl/DdlOptionInfo.java
similarity index 73%
copy from modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/ddl/TableOptionInfo.java
copy to modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/ddl/DdlOptionInfo.java
index bccc783044..dc12975126 100644
--- a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/ddl/TableOptionInfo.java
+++ b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/ddl/DdlOptionInfo.java
@@ -22,33 +22,28 @@ import java.util.function.Consumer;
 import org.jetbrains.annotations.Nullable;
 
 /**
- * Table option information for its processing.
+ * DDL option information for its processing.
  */
-class TableOptionInfo<T> {
-    final String name;
-
+class DdlOptionInfo<S, T> {
     final Class<T> type;
 
     @Nullable
     final Consumer<T> validator;
 
-    final BiConsumer<CreateTableCommand, T> setter;
+    final BiConsumer<S, T> setter;
 
     /**
      * Constructor.
      *
-     * @param name Table option name.
-     * @param type Table option type.
-     * @param validator Table option value validator.
-     * @param setter Table option value setter.
+     * @param type DDL option type.
+     * @param validator DDL option value validator.
+     * @param setter DDL option value updater.
      */
-    TableOptionInfo(
-            String name,
+    DdlOptionInfo(
             Class<T> type,
             @Nullable Consumer<T> validator,
-            BiConsumer<CreateTableCommand, T> setter
+            BiConsumer<S, T> setter
     ) {
-        this.name = name;
         this.type = type;
         this.validator = validator;
         this.setter = setter;
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 a8d2eeb1bd..ac7d4e7ea9 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
@@ -20,14 +20,21 @@ package org.apache.ignite.internal.sql.engine.prepare.ddl;
 import static java.util.function.Function.identity;
 import static java.util.stream.Collectors.toUnmodifiableMap;
 import static org.apache.ignite.internal.schema.configuration.storage.UnknownDataStorageConfigurationSchema.UNKNOWN_DATA_STORAGE;
+import static org.apache.ignite.internal.sql.engine.sql.IgniteSqlCreateZoneOptionEnum.AFFINITY_FUNCTION;
+import static org.apache.ignite.internal.sql.engine.sql.IgniteSqlCreateZoneOptionEnum.DATA_NODES_AUTO_ADJUST;
+import static org.apache.ignite.internal.sql.engine.sql.IgniteSqlCreateZoneOptionEnum.DATA_NODES_AUTO_ADJUST_SCALE_DOWN;
+import static org.apache.ignite.internal.sql.engine.sql.IgniteSqlCreateZoneOptionEnum.DATA_NODES_AUTO_ADJUST_SCALE_UP;
+import static org.apache.ignite.internal.sql.engine.sql.IgniteSqlCreateZoneOptionEnum.DATA_NODES_FILTER;
+import static org.apache.ignite.internal.sql.engine.sql.IgniteSqlCreateZoneOptionEnum.PARTITIONS;
+import static org.apache.ignite.internal.sql.engine.sql.IgniteSqlCreateZoneOptionEnum.REPLICAS;
 import static org.apache.ignite.internal.util.CollectionUtils.nullOrEmpty;
 import static org.apache.ignite.lang.ErrorGroups.Sql.PRIMARY_KEYS_MULTIPLE_ERR;
 import static org.apache.ignite.lang.ErrorGroups.Sql.PRIMARY_KEY_MISSING_ERR;
 import static org.apache.ignite.lang.ErrorGroups.Sql.QUERY_INVALID_ERR;
+import static org.apache.ignite.lang.ErrorGroups.Sql.QUERY_VALIDATION_ERR;
 import static org.apache.ignite.lang.ErrorGroups.Sql.SCHEMA_NOT_FOUND_ERR;
 import static org.apache.ignite.lang.ErrorGroups.Sql.SQL_TO_REL_CONVERSION_ERR;
 import static org.apache.ignite.lang.ErrorGroups.Sql.STORAGE_ENGINE_NOT_VALID_ERR;
-import static org.apache.ignite.lang.ErrorGroups.Sql.TABLE_OPTION_ERR;
 import static org.apache.ignite.lang.ErrorGroups.Sql.UNSUPPORTED_DDL_OPERATION_ERR;
 
 import java.math.BigDecimal;
@@ -35,7 +42,9 @@ import java.time.LocalDate;
 import java.time.LocalDateTime;
 import java.time.LocalTime;
 import java.time.ZoneOffset;
+import java.util.AbstractMap.SimpleEntry;
 import java.util.ArrayList;
+import java.util.EnumSet;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
@@ -44,7 +53,6 @@ import java.util.Set;
 import java.util.concurrent.TimeUnit;
 import java.util.function.Supplier;
 import java.util.stream.Collectors;
-import java.util.stream.Stream;
 import org.apache.calcite.rel.type.RelDataType;
 import org.apache.calcite.schema.ColumnStrategy;
 import org.apache.calcite.sql.SqlBasicTypeNameSpec;
@@ -74,10 +82,13 @@ import org.apache.ignite.internal.sql.engine.sql.IgniteSqlAlterTableDropColumn;
 import org.apache.ignite.internal.sql.engine.sql.IgniteSqlCreateIndex;
 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.IgniteSqlCreateZoneOption;
+import org.apache.ignite.internal.sql.engine.sql.IgniteSqlCreateZoneOptionEnum;
 import org.apache.ignite.internal.sql.engine.sql.IgniteSqlDropIndex;
+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.util.Commons;
-import org.apache.ignite.internal.util.ArrayUtils;
 import org.apache.ignite.lang.IgniteException;
 import org.apache.ignite.sql.SqlException;
 import org.jetbrains.annotations.Nullable;
@@ -96,17 +107,14 @@ public class DdlSqlToCommandConverter {
      */
     private final Map<String, String> dataStorageNames;
 
-    /**
-     * Mapping: Table option ID -> table option info.
-     *
-     * <p>Example for "replicas": {@code Map.of("REPLICAS", TableOptionInfo@123)}.
-     */
-    private final Map<String, TableOptionInfo<?>> tableOptionInfos;
+    /** Mapping: Table option ID -> DDL option info. */
+    private final Map<String, DdlOptionInfo<CreateTableCommand, ?>> tableOptionInfos;
 
-    /**
-     * Like {@link #tableOptionInfos}, but for each data storage name.
-     */
-    private final Map<String, Map<String, TableOptionInfo<?>>> dataStorageOptionInfos;
+    /** Like {@link #tableOptionInfos}, but for each data storage name. */
+    private final Map<String, Map<String, DdlOptionInfo<CreateTableCommand, ?>>> dataStorageOptionInfos;
+
+    /** Mapping: Zone option ID -> DDL option info. */
+    private final Map<IgniteSqlCreateZoneOptionEnum, DdlOptionInfo<CreateZoneCommand, ?>> zoneOptionInfos;
 
     /**
      * Constructor.
@@ -122,23 +130,36 @@ public class DdlSqlToCommandConverter {
 
         this.dataStorageNames = collectDataStorageNames(dataStorageFields.keySet());
 
-        this.tableOptionInfos = collectTableOptionInfos(
-                new TableOptionInfo<>("replicas", Integer.class, this::checkPositiveNumber, CreateTableCommand::replicas),
-                new TableOptionInfo<>("partitions", Integer.class, this::checkPositiveNumber, CreateTableCommand::partitions)
+        this.tableOptionInfos = Map.of(
+                REPLICAS.name(), new DdlOptionInfo<>(Integer.class, this::checkPositiveNumber, CreateTableCommand::replicas),
+                PARTITIONS.name(), new DdlOptionInfo<>(Integer.class, this::checkPositiveNumber, CreateTableCommand::partitions)
         );
 
         this.dataStorageOptionInfos = dataStorageFields.entrySet()
                 .stream()
                 .collect(toUnmodifiableMap(
                         Entry::getKey,
-                        e0 -> collectTableOptionInfos(
-                                e0.getValue().entrySet().stream()
-                                        .map(this::dataStorageFieldOptionInfo)
-                                        .toArray(TableOptionInfo[]::new)
-                        )
+                        e0 -> e0.getValue().entrySet().stream()
+                                .map(this::dataStorageFieldOptionInfo)
+                                .collect(toUnmodifiableMap(k -> k.getKey().toUpperCase(), Entry::getValue))
                 ));
 
-        dataStorageOptionInfos.forEach((k, v) -> checkDuplicates(v, tableOptionInfos));
+        dataStorageOptionInfos.values().forEach(v -> checkDuplicates(v.keySet(), tableOptionInfos.keySet()));
+
+        // CREATE ZONE options.
+        zoneOptionInfos = Map.of(
+                REPLICAS, new DdlOptionInfo<>(Integer.class, this::checkPositiveNumber, CreateZoneCommand::replicas),
+                PARTITIONS, new DdlOptionInfo<>(Integer.class, this::checkPositiveNumber, CreateZoneCommand::partitions),
+                AFFINITY_FUNCTION, new DdlOptionInfo<>(String.class, null, CreateZoneCommand::affinity),
+                DATA_NODES_FILTER, new DdlOptionInfo<>(String.class, null, CreateZoneCommand::nodeFilter),
+
+                DATA_NODES_AUTO_ADJUST,
+                    new DdlOptionInfo<>(Integer.class, this::checkPositiveNumber, CreateZoneCommand::dataNodesAutoAdjust),
+                DATA_NODES_AUTO_ADJUST_SCALE_UP,
+                    new DdlOptionInfo<>(Integer.class, this::checkPositiveNumber, CreateZoneCommand::dataNodesAutoAdjustScaleUp),
+                DATA_NODES_AUTO_ADJUST_SCALE_DOWN,
+                    new DdlOptionInfo<>(Integer.class, this::checkPositiveNumber, CreateZoneCommand::dataNodesAutoAdjustScaleDown)
+        );
     }
 
     /**
@@ -172,6 +193,14 @@ public class DdlSqlToCommandConverter {
             return convertDropIndex((IgniteSqlDropIndex) ddlNode, ctx);
         }
 
+        if (ddlNode instanceof IgniteSqlCreateZone) {
+            return convertCreateZone((IgniteSqlCreateZone) ddlNode, ctx);
+        }
+
+        if (ddlNode instanceof IgniteSqlDropZone) {
+            return convertDropZone((IgniteSqlDropZone) ddlNode, ctx);
+        }
+
         throw new SqlException(UNSUPPORTED_DDL_OPERATION_ERR, "Unsupported operation ["
                 + "sqlNodeKind=" + ddlNode.getKind() + "; "
                 + "querySql=\"" + ctx.query() + "\"]");
@@ -185,13 +214,16 @@ public class DdlSqlToCommandConverter {
      */
     private CreateTableCommand convertCreateTable(IgniteSqlCreateTable createTblNode, PlanningContext ctx) {
         CreateTableCommand createTblCmd = new CreateTableCommand();
+        String dataStorageName = deriveDataStorage(createTblNode.engineName(), ctx);
 
         createTblCmd.schemaName(deriveSchemaName(createTblNode.name(), ctx));
         createTblCmd.tableName(deriveObjectName(createTblNode.name(), ctx, "tableName"));
         createTblCmd.ifTableExists(createTblNode.ifNotExists());
-        createTblCmd.dataStorage(deriveDataStorage(createTblNode.engineName(), ctx));
+        createTblCmd.dataStorage(dataStorageName);
 
         if (createTblNode.createOptionList() != null) {
+            Map<String, DdlOptionInfo<CreateTableCommand, ?>> dsOptInfos = dataStorageOptionInfos.get(dataStorageName);
+
             for (SqlNode optionNode : createTblNode.createOptionList().getList()) {
                 IgniteSqlCreateTableOption option = (IgniteSqlCreateTableOption) optionNode;
 
@@ -199,13 +231,17 @@ public class DdlSqlToCommandConverter {
 
                 String optionKey = option.key().getSimple().toUpperCase();
 
-                if (tableOptionInfos.containsKey(optionKey)) {
-                    processTableOption(tableOptionInfos.get(optionKey), option, ctx, createTblCmd);
-                } else if (dataStorageOptionInfos.get(createTblCmd.dataStorage()).containsKey(optionKey)) {
-                    processTableOption(dataStorageOptionInfos.get(createTblCmd.dataStorage()).get(optionKey), option, ctx, createTblCmd);
+                DdlOptionInfo<CreateTableCommand, ?> tblOptionInfo = tableOptionInfos.get(optionKey);
+
+                if (tblOptionInfo == null) {
+                    tblOptionInfo = dsOptInfos.get(optionKey);
+                }
+
+                if (tblOptionInfo != null) {
+                    updateCommandOption("Table", optionKey, (SqlLiteral) option.value(), tblOptionInfo, ctx.query(), createTblCmd);
                 } else {
                     throw new IgniteException(
-                            TABLE_OPTION_ERR, String.format("Unexpected table option [option=%s, query=%s]", optionKey, ctx.query()));
+                            QUERY_VALIDATION_ERR, String.format("Unexpected table option [option=%s, query=%s]", optionKey, ctx.query()));
                 }
             }
         }
@@ -438,6 +474,61 @@ public class DdlSqlToCommandConverter {
         return dropCmd;
     }
 
+    /**
+     * Converts a given CreateZone AST to a CreateZone command.
+     *
+     * @param createZoneNode Root node of the given AST.
+     * @param ctx            Planning context.
+     */
+    private CreateZoneCommand convertCreateZone(IgniteSqlCreateZone createZoneNode, PlanningContext ctx) {
+        CreateZoneCommand createZoneCmd = new CreateZoneCommand();
+
+        createZoneCmd.schemaName(deriveSchemaName(createZoneNode.name(), ctx));
+        createZoneCmd.zoneName(deriveObjectName(createZoneNode.name(), ctx, "zoneName"));
+        createZoneCmd.ifNotExists(createZoneNode.ifNotExists());
+
+        if (createZoneNode.createOptionList() == null) {
+            return createZoneCmd;
+        }
+
+        Set<IgniteSqlCreateZoneOptionEnum> knownOptionNames = EnumSet.allOf(IgniteSqlCreateZoneOptionEnum.class);
+
+        for (SqlNode optionNode : createZoneNode.createOptionList().getList()) {
+            IgniteSqlCreateZoneOption option = (IgniteSqlCreateZoneOption) optionNode;
+            IgniteSqlCreateZoneOptionEnum optionName = option.key().symbolValue(IgniteSqlCreateZoneOptionEnum.class);
+
+            if (!knownOptionNames.remove(optionName)) {
+                throw new IgniteException(QUERY_VALIDATION_ERR,
+                        String.format("Duplicate DDL command option specified [option=%s, query=%s]", optionName, ctx.query()));
+            }
+
+            DdlOptionInfo<CreateZoneCommand, ?> zoneOptionInfo = zoneOptionInfos.get(optionName);
+
+            assert zoneOptionInfo != null : optionName;
+            assert option.value() instanceof SqlLiteral : option.value();
+
+            updateCommandOption("Zone", optionName, (SqlLiteral) option.value(), zoneOptionInfo, ctx.query(), createZoneCmd);
+        }
+
+        return createZoneCmd;
+    }
+
+    /**
+     * Converts a given DropZone AST to a DropZone command.
+     *
+     * @param dropZoneNode Root node of the given AST.
+     * @param ctx          Planning context.
+     */
+    private DropZoneCommand convertDropZone(IgniteSqlDropZone dropZoneNode, PlanningContext ctx) {
+        DropZoneCommand dropZoneCmd = new DropZoneCommand();
+
+        dropZoneCmd.schemaName(deriveSchemaName(dropZoneNode.name(), ctx));
+        dropZoneCmd.zoneName(deriveObjectName(dropZoneNode.name(), ctx, "zoneName"));
+        dropZoneCmd.ifExists(dropZoneNode.ifExists());
+
+        return dropZoneCmd;
+    }
+
     /** Derives a schema name from the compound identifier. */
     private String deriveSchemaName(SqlIdentifier id, PlanningContext ctx) {
         String schemaName;
@@ -495,32 +586,17 @@ public class DdlSqlToCommandConverter {
         return dataStorages.stream().collect(toUnmodifiableMap(String::toUpperCase, identity()));
     }
 
-    /**
-     * Collects a mapping of the ID of the table option to a table option info.
-     *
-     * <p>Example for "replicas": {@code Map.of("REPLICAS", TableOptionInfo@123)}.
-     *
-     * @param tableOptionInfos Table option information's.
-     * @throws IllegalStateException If there is a duplicate ID.
-     */
-    static Map<String, TableOptionInfo<?>> collectTableOptionInfos(TableOptionInfo<?>... tableOptionInfos) {
-        return ArrayUtils.nullOrEmpty(tableOptionInfos) ? Map.of() : Stream.of(tableOptionInfos).collect(toUnmodifiableMap(
-                tableOptionInfo -> tableOptionInfo.name.toUpperCase(),
-                identity()
-        ));
-    }
-
     /**
      * Checks that there are no ID duplicates.
      *
-     * @param tableOptionInfos0 Table options information.
-     * @param tableOptionInfos1 Table options information.
+     * @param set0 Set of string identifiers.
+     * @param set1 Set of string identifiers.
      * @throws IllegalStateException If there is a duplicate ID.
      */
-    static void checkDuplicates(Map<String, TableOptionInfo<?>> tableOptionInfos0, Map<String, TableOptionInfo<?>> tableOptionInfos1) {
-        for (String id : tableOptionInfos1.keySet()) {
-            if (tableOptionInfos0.containsKey(id)) {
-                throw new IllegalStateException("Duplicate id:" + id);
+    static void checkDuplicates(Set<String> set0, Set<String> set1) {
+        for (String id : set1) {
+            if (set0.contains(id)) {
+                throw new IllegalStateException("Duplicate id: " + id);
             }
         }
     }
@@ -550,51 +626,56 @@ public class DdlSqlToCommandConverter {
         return dataStorageNames.get(dataStorage);
     }
 
-    private void processTableOption(
-            TableOptionInfo tableOptionInfo,
-            IgniteSqlCreateTableOption option,
-            PlanningContext context,
-            CreateTableCommand createTableCommand
+    private <S, T> void updateCommandOption(
+            String sqlObjName,
+            Object optId,
+            SqlLiteral value,
+            DdlOptionInfo<S, T> optInfo,
+            String query,
+            S target
     ) {
-        assert option.value() instanceof SqlLiteral : option.value();
-
-        Object optionValue;
+        T value0;
 
         try {
-            optionValue = ((SqlLiteral) option.value()).getValueAs(tableOptionInfo.type);
+            value0 = value.getValueAs(optInfo.type);
         } catch (AssertionError | ClassCastException e) {
-            throw new IgniteException(TABLE_OPTION_ERR, String.format(
-                    "Unsuspected table option type [option=%s, expectedType=%s, query=%s]",
-                    option.key().getSimple(),
-                    tableOptionInfo.type.getSimpleName(),
-                    context.query())
+            throw new IgniteException(QUERY_VALIDATION_ERR, String.format(
+                    "Unsuspected %s option type [option=%s, expectedType=%s, query=%s]",
+                    sqlObjName.toLowerCase(),
+                    optId,
+                    optInfo.type.getSimpleName(),
+                    query)
             );
         }
 
-        if (tableOptionInfo.validator != null) {
+        if (optInfo.validator != null) {
             try {
-                tableOptionInfo.validator.accept(optionValue);
+                optInfo.validator.accept(value0);
             } catch (Throwable e) {
-                throw new IgniteException(TABLE_OPTION_ERR, String.format(
-                        "Table option validation failed [option=%s, err=%s, query=%s]",
-                        option.key().getSimple(),
+                throw new IgniteException(QUERY_VALIDATION_ERR, String.format(
+                        "%s option validation failed [option=%s, err=%s, query=%s]",
+                        sqlObjName,
+                        optId,
                         e.getMessage(),
-                        context.query()
+                        query
                 ), e);
             }
         }
 
-        tableOptionInfo.setter.accept(createTableCommand, optionValue);
+        optInfo.setter.accept(target, value0);
     }
 
     private void checkPositiveNumber(int num) {
         if (num < 0) {
-            throw new IgniteException(TABLE_OPTION_ERR, "Must be positive:" + num);
+            throw new IgniteException(QUERY_VALIDATION_ERR, "Must be positive:" + num);
         }
     }
 
-    private TableOptionInfo<?> dataStorageFieldOptionInfo(Entry<String, Class<?>> e) {
-        return new TableOptionInfo<>(e.getKey(), e.getValue(), null, (cmd, o) -> cmd.addDataStorageOption(e.getKey(), o));
+    private Entry<String, DdlOptionInfo<CreateTableCommand, ?>> dataStorageFieldOptionInfo(Entry<String, Class<?>> e) {
+        return new SimpleEntry<>(
+                e.getKey(),
+                new DdlOptionInfo<>(e.getValue(), null, (cmd, o) -> cmd.addDataStorageOption(e.getKey(), o))
+        );
     }
 
     private Type convertIndexType(IgniteSqlIndexType type) {
diff --git a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/ddl/TableOptionInfo.java b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/ddl/DropZoneCommand.java
similarity index 50%
rename from modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/ddl/TableOptionInfo.java
rename to modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/ddl/DropZoneCommand.java
index bccc783044..4186418bdb 100644
--- a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/ddl/TableOptionInfo.java
+++ b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/ddl/DropZoneCommand.java
@@ -17,40 +17,18 @@
 
 package org.apache.ignite.internal.sql.engine.prepare.ddl;
 
-import java.util.function.BiConsumer;
-import java.util.function.Consumer;
-import org.jetbrains.annotations.Nullable;
-
 /**
- * Table option information for its processing.
+ * DROP ZONE statement.
  */
-class TableOptionInfo<T> {
-    final String name;
-
-    final Class<T> type;
+public class DropZoneCommand extends AbstractZoneDdlCommand {
+    /** Quietly ignore this command if the zone does not exist. */
+    private boolean ifExists;
 
-    @Nullable
-    final Consumer<T> validator;
-
-    final BiConsumer<CreateTableCommand, T> setter;
+    public boolean ifExists() {
+        return ifExists;
+    }
 
-    /**
-     * Constructor.
-     *
-     * @param name Table option name.
-     * @param type Table option type.
-     * @param validator Table option value validator.
-     * @param setter Table option value setter.
-     */
-    TableOptionInfo(
-            String name,
-            Class<T> type,
-            @Nullable Consumer<T> validator,
-            BiConsumer<CreateTableCommand, T> setter
-    ) {
-        this.name = name;
-        this.type = type;
-        this.validator = validator;
-        this.setter = setter;
+    public void ifExists(boolean ifExists) {
+        this.ifExists = ifExists;
     }
 }
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 56e308e595..3a8739c15e 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
@@ -39,7 +39,7 @@ public class IgniteSqlCreateZone extends SqlCreate {
 
     private final @Nullable SqlNodeList createOptionList;
 
-    private static final SqlOperator OPERATOR = new SqlSpecialOperator("CREATE ZONE", SqlKind.OTHER);
+    private static final SqlOperator OPERATOR = new SqlSpecialOperator("CREATE ZONE", SqlKind.OTHER_DDL);
 
     /** Creates a SqlCreateZone. */
     public IgniteSqlCreateZone(
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 2df1fda7a2..c4573e9df5 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
@@ -37,7 +37,7 @@ public class IgniteSqlDropZone extends SqlDrop {
     private final SqlIdentifier name;
 
     /** Sql operator. */
-    private static final SqlOperator OPERATOR = new SqlSpecialOperator("DROP ZONE", SqlKind.OTHER);
+    private static final SqlOperator OPERATOR = new SqlSpecialOperator("DROP ZONE", SqlKind.OTHER_DDL);
 
     /** Constructor. */
     public IgniteSqlDropZone(SqlParserPos pos, boolean ifExists, SqlIdentifier name) {
diff --git a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/StopCalciteModuleTest.java b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/StopCalciteModuleTest.java
index 3cec811cb1..b766eb6039 100644
--- a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/StopCalciteModuleTest.java
+++ b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/StopCalciteModuleTest.java
@@ -44,6 +44,7 @@ import java.util.concurrent.Flow;
 import java.util.function.Consumer;
 import java.util.function.Function;
 import org.apache.ignite.configuration.ConfigurationValue;
+import org.apache.ignite.internal.distributionzones.DistributionZoneManager;
 import org.apache.ignite.internal.hlc.HybridClock;
 import org.apache.ignite.internal.index.IndexManager;
 import org.apache.ignite.internal.logger.IgniteLogger;
@@ -117,6 +118,9 @@ public class StopCalciteModuleTest {
     @Mock
     private TxManager txManager;
 
+    @Mock
+    private DistributionZoneManager distributionZoneManager;
+
     @Mock
     private TopologyService topologySrvc;
 
@@ -211,6 +215,7 @@ public class StopCalciteModuleTest {
                 schemaManager,
                 dataStorageManager,
                 txManager,
+                distributionZoneManager,
                 Map::of,
                 clock
         );
diff --git a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/exec/MockedStructuresTest.java b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/exec/MockedStructuresTest.java
index 49906280a7..fc0af30136 100644
--- a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/exec/MockedStructuresTest.java
+++ b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/exec/MockedStructuresTest.java
@@ -48,6 +48,7 @@ import org.apache.ignite.internal.configuration.notifications.ConfigurationStora
 import org.apache.ignite.internal.configuration.testframework.ConfigurationExtension;
 import org.apache.ignite.internal.configuration.testframework.InjectConfiguration;
 import org.apache.ignite.internal.configuration.testframework.InjectRevisionListenerHolder;
+import org.apache.ignite.internal.distributionzones.DistributionZoneManager;
 import org.apache.ignite.internal.hlc.HybridClock;
 import org.apache.ignite.internal.index.IndexManager;
 import org.apache.ignite.internal.metastorage.MetaStorageManager;
@@ -167,6 +168,9 @@ public class MockedStructuresTest extends IgniteAbstractTest {
     @Mock
     private ConfigurationRegistry configRegistry;
 
+    @Mock
+    private DistributionZoneManager distributionZoneManager;
+
     DataStorageManager dataStorageManager;
 
     SchemaManager schemaManager;
@@ -244,6 +248,7 @@ public class MockedStructuresTest extends IgniteAbstractTest {
                 schemaManager,
                 dataStorageManager,
                 tm,
+                distributionZoneManager,
                 () -> dataStorageModules.collectSchemasFields(
                         List.of(
                                 RocksDbDataStorageConfigurationSchema.class,
diff --git a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/exec/ddl/DistributionZoneDdlCommandHandlerTest.java b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/exec/ddl/DistributionZoneDdlCommandHandlerTest.java
new file mode 100644
index 0000000000..3d91c33de4
--- /dev/null
+++ b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/exec/ddl/DistributionZoneDdlCommandHandlerTest.java
@@ -0,0 +1,144 @@
+/*
+ * 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.exec.ddl;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.equalTo;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.atomic.AtomicReference;
+import org.apache.ignite.internal.distributionzones.DistributionZoneConfigurationParameters;
+import org.apache.ignite.internal.distributionzones.DistributionZoneManager;
+import org.apache.ignite.internal.index.IndexManager;
+import org.apache.ignite.internal.sql.engine.prepare.ddl.CreateZoneCommand;
+import org.apache.ignite.internal.sql.engine.prepare.ddl.DdlCommand;
+import org.apache.ignite.internal.sql.engine.prepare.ddl.DropZoneCommand;
+import org.apache.ignite.internal.storage.DataStorageManager;
+import org.apache.ignite.internal.table.distributed.TableManager;
+import org.apache.ignite.internal.testframework.IgniteAbstractTest;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.junit.jupiter.MockitoExtension;
+import org.mockito.junit.jupiter.MockitoSettings;
+import org.mockito.quality.Strictness;
+import org.mockito.stubbing.Answer;
+
+/**
+ * Tests distribution zone commands handling.
+ */
+@ExtendWith(MockitoExtension.class)
+@MockitoSettings(strictness = Strictness.LENIENT)
+public class DistributionZoneDdlCommandHandlerTest extends IgniteAbstractTest {
+    /** Holder of the result of the invoked method. */
+    private final AtomicReference<Object> invocationResultHolder = new AtomicReference<>();
+
+    @Mock
+    private TableManager tableManager;
+
+    @Mock
+    private IndexManager indexManager;
+
+    @Mock
+    private DataStorageManager dataStorageManager;
+
+    /** DDL commands handler. */
+    private DdlCommandHandler commandHandler;
+
+    /** Inner initialisation. */
+    @BeforeEach
+    void before() {
+        DistributionZoneManager distributionZoneManager = Mockito.mock(DistributionZoneManager.class,
+                (Answer<CompletableFuture<Void>>) invocationOnMock -> {
+                    invocationResultHolder.set(invocationOnMock.getArgument(0));
+
+                    return CompletableFuture.completedFuture(null);
+                });
+
+        commandHandler = new DdlCommandHandler(distributionZoneManager, tableManager, indexManager, dataStorageManager);
+    }
+
+
+    @Test
+    public void testCreateZone() {
+        CreateZoneCommand cmd = new CreateZoneCommand();
+        cmd.zoneName("test_zone");
+
+        DistributionZoneConfigurationParameters params = invokeHandler(cmd);
+
+        assertNotNull(params);
+        assertThat(params.name(), equalTo(cmd.zoneName()));
+    }
+
+    @Test
+    public void testCreateZoneOptions() {
+        String name = "test_zone";
+        int autoAdjust = 1;
+        int autoAdjustScaleUp = 2;
+        int autoAdjustScaleDown = 3;
+
+        // Invalid options combination.
+        CreateZoneCommand cmdInvalidOptions = new CreateZoneCommand();
+        cmdInvalidOptions.zoneName(name);
+        cmdInvalidOptions.dataNodesAutoAdjust(autoAdjust);
+        cmdInvalidOptions.dataNodesAutoAdjustScaleUp(autoAdjustScaleUp);
+        cmdInvalidOptions.dataNodesAutoAdjustScaleDown(autoAdjustScaleDown);
+
+        assertThrows(IllegalArgumentException.class, () -> invokeHandler(cmdInvalidOptions));
+
+        // Valid options combination.
+        CreateZoneCommand cmdValidArguments1 = new CreateZoneCommand();
+        cmdValidArguments1.zoneName(name);
+        cmdValidArguments1.dataNodesAutoAdjust(autoAdjust);
+
+        DistributionZoneConfigurationParameters params = invokeHandler(cmdValidArguments1);
+
+        assertNotNull(params);
+        assertThat(params.dataNodesAutoAdjust(), equalTo(autoAdjust));
+
+        // Another valid options combination.
+        CreateZoneCommand cmdValidArguments2 = new CreateZoneCommand();
+        cmdValidArguments2.zoneName(name);
+        cmdValidArguments2.dataNodesAutoAdjustScaleUp(autoAdjustScaleUp);
+        cmdValidArguments2.dataNodesAutoAdjustScaleDown(autoAdjustScaleDown);
+
+        params = invokeHandler(cmdValidArguments2);
+
+        assertThat(params.dataNodesAutoAdjustScaleUp(), equalTo(autoAdjustScaleUp));
+        assertThat(params.dataNodesAutoAdjustScaleDown(), equalTo(autoAdjustScaleDown));
+    }
+
+    @Test
+    public void testDropZone() {
+        DropZoneCommand cmd = new DropZoneCommand();
+        cmd.zoneName("test_zone");
+
+        String name = invokeHandler(cmd);
+        assertThat(name, equalTo(cmd.zoneName()));
+    }
+
+    private <T> T invokeHandler(DdlCommand cmd) {
+        commandHandler.handle(cmd);
+
+        return (T) invocationResultHolder.get();
+    }
+}
diff --git a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/prepare/ddl/AbstractDdlSqlToCommandConverterTest.java b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/prepare/ddl/AbstractDdlSqlToCommandConverterTest.java
new file mode 100644
index 0000000000..3943e897ff
--- /dev/null
+++ b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/prepare/ddl/AbstractDdlSqlToCommandConverterTest.java
@@ -0,0 +1,66 @@
+/*
+ * 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.prepare.ddl;
+
+import static org.apache.calcite.tools.Frameworks.newConfigBuilder;
+import static org.apache.ignite.internal.sql.engine.util.Commons.FRAMEWORK_CONFIG;
+
+import java.util.Map;
+import org.apache.calcite.sql.SqlNode;
+import org.apache.calcite.sql.parser.SqlParseException;
+import org.apache.calcite.sql.parser.SqlParser;
+import org.apache.calcite.tools.Frameworks;
+import org.apache.ignite.internal.generated.query.calcite.sql.IgniteSqlParserImpl;
+import org.apache.ignite.internal.sql.engine.prepare.PlanningContext;
+import org.apache.ignite.internal.sql.engine.schema.IgniteSchema;
+import org.apache.ignite.internal.sql.engine.util.BaseQueryContext;
+import org.apache.ignite.internal.testframework.BaseIgniteAbstractTest;
+
+/**
+ * Common methods for {@link DdlSqlToCommandConverter} testing.
+ */
+class AbstractDdlSqlToCommandConverterTest extends BaseIgniteAbstractTest {
+    /** DDL SQL to command converter. */
+    DdlSqlToCommandConverter converter = new DdlSqlToCommandConverter(Map.of(), () -> "default");
+
+    /**
+     * Parses a given statement and returns a resulting AST.
+     *
+     * @param stmt Statement to parse.
+     * @return An AST.
+     */
+    static SqlNode parse(String stmt) throws SqlParseException {
+        SqlParser parser = SqlParser.create(stmt, SqlParser.config().withParserFactory(IgniteSqlParserImpl.FACTORY));
+
+        return parser.parseStmt();
+    }
+
+    static PlanningContext createContext() {
+        var schemaName = "PUBLIC";
+        var schema = Frameworks.createRootSchema(false).add(schemaName, new IgniteSchema(schemaName));
+
+        return PlanningContext.builder()
+                .parentContext(BaseQueryContext.builder()
+                        .frameworkConfig(newConfigBuilder(FRAMEWORK_CONFIG)
+                                .defaultSchema(schema)
+                                .build())
+                        .build())
+                .query("")
+                .build();
+    }
+}
diff --git a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/prepare/ddl/DdlSqlToCommandConverterTest.java b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/prepare/ddl/DdlSqlToCommandConverterTest.java
index d8528a8cd0..4978465b89 100644
--- a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/prepare/ddl/DdlSqlToCommandConverterTest.java
+++ b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/prepare/ddl/DdlSqlToCommandConverterTest.java
@@ -17,11 +17,8 @@
 
 package org.apache.ignite.internal.sql.engine.prepare.ddl;
 
-import static org.apache.calcite.tools.Frameworks.newConfigBuilder;
 import static org.apache.ignite.internal.sql.engine.prepare.ddl.DdlSqlToCommandConverter.checkDuplicates;
 import static org.apache.ignite.internal.sql.engine.prepare.ddl.DdlSqlToCommandConverter.collectDataStorageNames;
-import static org.apache.ignite.internal.sql.engine.prepare.ddl.DdlSqlToCommandConverter.collectTableOptionInfos;
-import static org.apache.ignite.internal.sql.engine.util.Commons.FRAMEWORK_CONFIG;
 import static org.hamcrest.CoreMatchers.instanceOf;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.Matchers.allOf;
@@ -37,19 +34,11 @@ import java.util.Map;
 import java.util.Set;
 import java.util.function.Function;
 import org.apache.calcite.sql.SqlDdl;
-import org.apache.calcite.sql.SqlNode;
 import org.apache.calcite.sql.parser.SqlParseException;
-import org.apache.calcite.sql.parser.SqlParser;
 import org.apache.calcite.sql.type.SqlTypeName;
-import org.apache.calcite.tools.Frameworks;
-import org.apache.ignite.internal.generated.query.calcite.sql.IgniteSqlParserImpl;
-import org.apache.ignite.internal.sql.engine.prepare.PlanningContext;
 import org.apache.ignite.internal.sql.engine.prepare.ddl.DefaultValueDefinition.FunctionCall;
 import org.apache.ignite.internal.sql.engine.prepare.ddl.DefaultValueDefinition.Type;
-import org.apache.ignite.internal.sql.engine.schema.IgniteSchema;
-import org.apache.ignite.internal.sql.engine.util.BaseQueryContext;
 import org.apache.ignite.internal.sql.engine.util.Commons;
-import org.apache.ignite.internal.testframework.BaseIgniteAbstractTest;
 import org.apache.ignite.internal.testframework.IgniteTestUtils;
 import org.apache.ignite.internal.testframework.WithSystemProperty;
 import org.apache.ignite.lang.IgniteException;
@@ -62,7 +51,7 @@ import org.junit.jupiter.api.Test;
 /**
  * For {@link DdlSqlToCommandConverter} testing.
  */
-public class DdlSqlToCommandConverterTest extends BaseIgniteAbstractTest {
+public class DdlSqlToCommandConverterTest extends AbstractDdlSqlToCommandConverterTest {
     @AfterAll
     public static void resetStaticState() {
         IgniteTestUtils.setFieldValue(Commons.class, "implicitPkEnabled", null);
@@ -96,58 +85,22 @@ public class DdlSqlToCommandConverterTest extends BaseIgniteAbstractTest {
     }
 
     @Test
-    void testCollectTableOptionInfos() {
-        assertThat(collectTableOptionInfos(new TableOptionInfo[0]), equalTo(Map.of()));
-
-        TableOptionInfo<?> replicas = tableOptionInfo("replicas");
-
-        assertThat(
-                collectTableOptionInfos(replicas),
-                equalTo(Map.of("REPLICAS", replicas))
-        );
-
-        replicas = tableOptionInfo("REPLICAS");
-
-        assertThat(
-                collectTableOptionInfos(replicas),
-                equalTo(Map.of("REPLICAS", replicas))
-        );
-
-        replicas = tableOptionInfo("replicas");
-        TableOptionInfo<?> partitions = tableOptionInfo("partitions");
-
-        assertThat(
-                collectTableOptionInfos(replicas, partitions),
-                equalTo(Map.of("REPLICAS", replicas, "PARTITIONS", partitions))
-        );
-
-        TableOptionInfo<?> replicas0 = tableOptionInfo("replicas");
-        TableOptionInfo<?> replicas1 = tableOptionInfo("REPLICAS");
-
-        IllegalStateException exception = assertThrows(
-                IllegalStateException.class,
-                () -> collectTableOptionInfos(replicas0, replicas1)
-        );
-
-        assertThat(exception.getMessage(), startsWith("Duplicate key"));
-    }
-
-    @Test
-    void testCheckPositiveNumber() {
+    void testCheckDuplicates() {
         IllegalStateException exception = assertThrows(
                 IllegalStateException.class,
                 () -> checkDuplicates(
-                        collectTableOptionInfos(tableOptionInfo("replicas")),
-                        collectTableOptionInfos(tableOptionInfo("replicas"))
+                        Set.of("replicas", "affinity"),
+                        Set.of("partitions", "replicas")
                 )
         );
 
-        assertThat(exception.getMessage(), startsWith("Duplicate id"));
+        assertThat(exception.getMessage(), startsWith("Duplicate id: replicas"));
 
         assertDoesNotThrow(() -> checkDuplicates(
-                collectTableOptionInfos(tableOptionInfo("replicas")),
-                collectTableOptionInfos(tableOptionInfo("partitions"))
-        ));
+                        Set.of("replicas", "affinity"),
+                        Set.of("replicas0", "affinity0")
+                )
+        );
     }
 
     @Test
@@ -160,7 +113,7 @@ public class DdlSqlToCommandConverterTest extends BaseIgniteAbstractTest {
 
         var ex = assertThrows(
                 IgniteException.class,
-                () -> new DdlSqlToCommandConverter(Map.of(), () -> "default").convert((SqlDdl) node, createContext())
+                () -> converter.convert((SqlDdl) node, createContext())
         );
 
         assertThat(ex.getMessage(), containsString("Table without PRIMARY KEY is not supported"));
@@ -177,7 +130,7 @@ public class DdlSqlToCommandConverterTest extends BaseIgniteAbstractTest {
 
         var ex = assertThrows(
                 IgniteException.class,
-                () -> new DdlSqlToCommandConverter(Map.of(), () -> "default").convert((SqlDdl) node, createContext())
+                () -> converter.convert((SqlDdl) node, createContext())
         );
 
         assertThat(ex.getMessage(), containsString("Table without PRIMARY KEY is not supported"));
@@ -192,7 +145,7 @@ public class DdlSqlToCommandConverterTest extends BaseIgniteAbstractTest {
 
         assertThat(node, instanceOf(SqlDdl.class));
 
-        var cmd = new DdlSqlToCommandConverter(Map.of(), () -> "default").convert((SqlDdl) node, createContext());
+        var cmd = converter.convert((SqlDdl) node, createContext());
 
         assertThat(cmd, Matchers.instanceOf(CreateTableCommand.class));
 
@@ -224,7 +177,7 @@ public class DdlSqlToCommandConverterTest extends BaseIgniteAbstractTest {
 
         assertThat(node, instanceOf(SqlDdl.class));
 
-        var cmd = new DdlSqlToCommandConverter(Map.of(), () -> "default").convert((SqlDdl) node, createContext());
+        var cmd = converter.convert((SqlDdl) node, createContext());
 
         assertThat(cmd, Matchers.instanceOf(CreateTableCommand.class));
 
@@ -246,11 +199,6 @@ public class DdlSqlToCommandConverterTest extends BaseIgniteAbstractTest {
         );
     }
 
-    private TableOptionInfo tableOptionInfo(String name) {
-        return new TableOptionInfo<>(name, Object.class, null, (createTableCommand, o) -> {
-        });
-    }
-
     private static Matcher<ColumnDefinition> columnThat(String description, Function<ColumnDefinition, Boolean> checker) {
         return new CustomMatcher<>(description) {
             @Override
@@ -259,30 +207,4 @@ public class DdlSqlToCommandConverterTest extends BaseIgniteAbstractTest {
             }
         };
     }
-
-    /**
-     * Parses a given statement and returns a resulting AST.
-     *
-     * @param stmt Statement to parse.
-     * @return An AST.
-     */
-    private static SqlNode parse(String stmt) throws SqlParseException {
-        SqlParser parser = SqlParser.create(stmt, SqlParser.config().withParserFactory(IgniteSqlParserImpl.FACTORY));
-
-        return parser.parseStmt();
-    }
-
-    private static PlanningContext createContext() {
-        var schemaName = "PUBLIC";
-        var schema = Frameworks.createRootSchema(false).add(schemaName, new IgniteSchema(schemaName));
-
-        return PlanningContext.builder()
-                .parentContext(BaseQueryContext.builder()
-                        .frameworkConfig(newConfigBuilder(FRAMEWORK_CONFIG)
-                                .defaultSchema(schema)
-                                .build())
-                        .build())
-                .query("")
-                .build();
-    }
 }
diff --git a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/prepare/ddl/DistributionZoneSqlToCommandConverterTest.java b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/prepare/ddl/DistributionZoneSqlToCommandConverterTest.java
new file mode 100644
index 0000000000..95f0ec23c6
--- /dev/null
+++ b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/prepare/ddl/DistributionZoneSqlToCommandConverterTest.java
@@ -0,0 +1,124 @@
+/*
+ * 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.prepare.ddl;
+
+import static org.hamcrest.CoreMatchers.instanceOf;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.containsString;
+import static org.hamcrest.Matchers.equalTo;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import org.apache.calcite.sql.SqlDdl;
+import org.apache.calcite.sql.SqlNode;
+import org.apache.calcite.sql.parser.SqlParseException;
+import org.apache.ignite.lang.ErrorGroups.Sql;
+import org.apache.ignite.lang.IgniteException;
+import org.hamcrest.Matchers;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Tests the conversion of a sql zone definition to a command.
+ */
+public class DistributionZoneSqlToCommandConverterTest extends AbstractDdlSqlToCommandConverterTest {
+    @Test
+    public void testCreateZone() throws SqlParseException {
+        SqlNode node = parse("CREATE ZONE test");
+
+        assertThat(node, instanceOf(SqlDdl.class));
+
+        DdlCommand cmd = converter.convert((SqlDdl) node, createContext());
+
+        assertThat(cmd, Matchers.instanceOf(CreateZoneCommand.class));
+
+        CreateZoneCommand zoneCmd = (CreateZoneCommand) cmd;
+
+        assertThat(zoneCmd.zoneName(), equalTo("TEST"));
+    }
+
+    @Test
+    public void testCreateZoneWithOptions() throws SqlParseException {
+        SqlNode node = parse("CREATE ZONE test with "
+                + "partitions=2, "
+                + "replicas=3, "
+                + "affinity_function='rendezvous', "
+                + "data_nodes_filter='\"attr1\" && \"attr2\"', "
+                + "data_nodes_auto_adjust_scale_up=100, "
+                + "data_nodes_auto_adjust_scale_down=200, "
+                + "data_nodes_auto_adjust=300");
+
+        assertThat(node, instanceOf(SqlDdl.class));
+
+        DdlCommand cmd = converter.convert((SqlDdl) node, createContext());
+
+        CreateZoneCommand createZone = (CreateZoneCommand) cmd;
+
+        assertThat(createZone.partitions(), equalTo(2));
+        assertThat(createZone.replicas(), equalTo(3));
+        assertThat(createZone.affinity(), equalTo("rendezvous"));
+        assertThat(createZone.nodeFilter(), equalTo("\"attr1\" && \"attr2\""));
+        assertThat(createZone.dataNodesAutoAdjustScaleUp(), equalTo(100));
+        assertThat(createZone.dataNodesAutoAdjustScaleDown(), equalTo(200));
+        assertThat(createZone.dataNodesAutoAdjust(), equalTo(300));
+
+        // Check option validation.
+        IgniteException ex = assertThrows(
+                IgniteException.class,
+                () -> converter.convert((SqlDdl) parse("CREATE ZONE test with partitions=-1"), createContext())
+        );
+
+        assertThat(ex.code(), equalTo(Sql.QUERY_VALIDATION_ERR));
+        assertThat(ex.getMessage(), containsString("Zone option validation failed [option=PARTITIONS"));
+
+        ex = assertThrows(
+                IgniteException.class,
+                () -> converter.convert((SqlDdl) parse("CREATE ZONE test with replicas=-1"), createContext())
+        );
+
+        assertThat(ex.code(), equalTo(Sql.QUERY_VALIDATION_ERR));
+        assertThat(ex.getMessage(), containsString("Zone option validation failed [option=REPLICAS"));
+    }
+
+    @Test
+    public void testCreateZoneWithDuplicateOptions() throws SqlParseException {
+        SqlNode node = parse("CREATE ZONE test with partitions=2, replicas=0, PARTITIONS=1");
+
+        assertThat(node, instanceOf(SqlDdl.class));
+
+        IgniteException ex = assertThrows(
+                IgniteException.class,
+                () -> converter.convert((SqlDdl) node, createContext())
+        );
+
+        assertThat(ex.code(), equalTo(Sql.QUERY_VALIDATION_ERR));
+    }
+
+    @Test
+    public void testDropZone() throws SqlParseException {
+        SqlNode node = parse("DROP ZONE test");
+
+        assertThat(node, instanceOf(SqlDdl.class));
+
+        DdlCommand cmd = converter.convert((SqlDdl) node, createContext());
+
+        assertThat(cmd, Matchers.instanceOf(DropZoneCommand.class));
+
+        DropZoneCommand zoneCmd = (DropZoneCommand) cmd;
+
+        assertThat(zoneCmd.zoneName(), equalTo("TEST"));
+    }
+}
diff --git a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/sql/SqlDdlZoneParserTest.java b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/sql/DistributionZoneSqlDdlParserTest.java
similarity index 99%
rename from modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/sql/SqlDdlZoneParserTest.java
rename to modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/sql/DistributionZoneSqlDdlParserTest.java
index 442dcce2fa..737e3e8c28 100644
--- a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/sql/SqlDdlZoneParserTest.java
+++ b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/sql/DistributionZoneSqlDdlParserTest.java
@@ -40,7 +40,7 @@ import org.junit.jupiter.api.Test;
 /**
  * Test suite to verify parsing of the DDL "ZONE" commands.
  */
-public class SqlDdlZoneParserTest extends AbstractDdlParserTest {
+public class DistributionZoneSqlDdlParserTest extends AbstractDdlParserTest {
     /**
      * Parse simple CREATE ZONE statement.
      */