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

korlov 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 1cbcdf7e960 IGNITE-25824 Need STORAGE_PROFILE Validation in CREATE 
ZONE (#6200)
1cbcdf7e960 is described below

commit 1cbcdf7e96032f5e2fd397113e015a5aea22c091
Author: Mikhail Efremov <jakuten...@gmail.com>
AuthorDate: Mon Jul 14 15:30:15 2025 +0600

    IGNITE-25824 Need STORAGE_PROFILE Validation in CREATE ZONE (#6200)
---
 modules/sql-engine/build.gradle                    |   2 +-
 .../internal/sql/api/ItSqlCreateZoneTest.java      |  87 ++++++++++
 .../internal/sql/engine/ItCreateTableDdlTest.java  |  29 +++-
 .../internal/sql/engine/SqlQueryProcessor.java     |   9 +-
 .../sql/engine/prepare/PrepareServiceImpl.java     |   6 +-
 .../ddl/ClusterWideStorageProfileValidator.java    |  88 ++++++++++
 .../prepare/ddl/DdlSqlToCommandConverter.java      |  18 +-
 .../prepare/ddl/StorageProfileValidator.java       |  34 ++++
 .../sql/engine/exec/ExecutionServiceImplTest.java  |   2 +-
 .../sql/engine/framework/TestBuilders.java         |   2 +-
 .../ddl/AbstractDdlSqlToCommandConverterTest.java  |   4 +-
 .../prepare/ddl/DdlSqlToCommandConverterTest.java  |   6 +
 .../DistributionZoneSqlToCommandConverterTest.java | 184 +++++++++++++++++----
 13 files changed, 430 insertions(+), 41 deletions(-)

diff --git a/modules/sql-engine/build.gradle b/modules/sql-engine/build.gradle
index 31d060e12f4..e7ea8ed53ee 100644
--- a/modules/sql-engine/build.gradle
+++ b/modules/sql-engine/build.gradle
@@ -123,7 +123,7 @@ dependencies {
     integrationTestImplementation libs.awaitility
     integrationTestImplementation libs.netty.handler
     integrationTestImplementation project(':ignite-api')
-    integrationTestImplementation project(':ignite-schema')
+    integrationTestImplementation project(':ignite-metastorage')
     integrationTestImplementation project(':ignite-catalog')
     integrationTestImplementation project(':ignite-transactions')
     integrationTestImplementation project(':ignite-storage-api')
diff --git 
a/modules/sql-engine/src/integrationTest/java/org/apache/ignite/internal/sql/api/ItSqlCreateZoneTest.java
 
b/modules/sql-engine/src/integrationTest/java/org/apache/ignite/internal/sql/api/ItSqlCreateZoneTest.java
new file mode 100644
index 00000000000..b3cf44ad20e
--- /dev/null
+++ 
b/modules/sql-engine/src/integrationTest/java/org/apache/ignite/internal/sql/api/ItSqlCreateZoneTest.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.api;
+
+import static 
org.apache.ignite.internal.TestDefaultProfilesNames.DEFAULT_AIMEM_PROFILE_NAME;
+import static 
org.apache.ignite.internal.TestDefaultProfilesNames.DEFAULT_AIPERSIST_PROFILE_NAME;
+import static 
org.apache.ignite.internal.TestDefaultProfilesNames.DEFAULT_ROCKSDB_PROFILE_NAME;
+import static 
org.apache.ignite.internal.TestDefaultProfilesNames.DEFAULT_TEST_PROFILE_NAME;
+import static org.apache.ignite.internal.lang.IgniteStringFormatter.format;
+import static 
org.apache.ignite.internal.testframework.IgniteTestUtils.assertThrowsWithCode;
+import static org.apache.ignite.lang.ErrorGroups.Sql.STMT_VALIDATION_ERR;
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+
+import java.util.List;
+import org.apache.ignite.internal.ClusterPerTestIntegrationTest;
+import org.apache.ignite.sql.SqlException;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.Timeout;
+
+@Timeout(60)
+class ItSqlCreateZoneTest extends ClusterPerTestIntegrationTest {
+    private static final String ZONE_MANE = "test_zone";
+    private static final String NOT_EXISTED_PROFILE_NAME = 
"not-existed-profile";
+    private static final String EXTRA_PROFILE_NAME = "extra-profile";
+    /** Nodes bootstrap configuration pattern. */
+    private static final String NODE_BOOTSTRAP_CFG_TEMPLATE_WITH_EXTRA_PROFILE 
= "ignite {\n"
+            + "  network: {\n"
+            + "    port: {},\n"
+            + "    nodeFinder.netClusterNodes: [ {} ]\n"
+            + "  },\n"
+            + "  storage.profiles: {"
+            + "        " + DEFAULT_TEST_PROFILE_NAME + ".engine: test, "
+            + "        " + DEFAULT_AIPERSIST_PROFILE_NAME + ".engine: 
aipersist, "
+            + "        " + DEFAULT_AIMEM_PROFILE_NAME + ".engine: aimem, "
+            + "        " + EXTRA_PROFILE_NAME + ".engine: aipersist, "
+            + "        " + DEFAULT_ROCKSDB_PROFILE_NAME + ".engine: rocksdb"
+            + "  },\n"
+            + "  clientConnector.port: {},\n"
+            + "  rest.port: {},\n"
+            + "  failureHandler.dumpThreadsOnFailure: false\n"
+            + "}";
+
+    @Override
+    protected int initialNodes() {
+        return 1;
+    }
+
+    @Test
+    void testCreateZoneSucceedWithCorrectStorageProfileOnSameNode() {
+        assertDoesNotThrow(() -> createZoneQuery(0, "default"));
+    }
+
+    @Test
+    void testCreateZoneSucceedWithCorrectStorageProfileOnDifferentNode() {
+        cluster.startNode(1, NODE_BOOTSTRAP_CFG_TEMPLATE_WITH_EXTRA_PROFILE);
+        assertDoesNotThrow(() -> createZoneQuery(0, EXTRA_PROFILE_NAME));
+    }
+
+    @Test
+    void testCreateZoneFailedWithoutCorrectStorageProfileInCluster() {
+        assertThrowsWithCode(
+                SqlException.class,
+                STMT_VALIDATION_ERR,
+                () -> createZoneQuery(0, NOT_EXISTED_PROFILE_NAME),
+                "Some storage profiles don't exist [missedProfileNames=[" + 
NOT_EXISTED_PROFILE_NAME + "]]."
+        );
+    }
+
+    private  List<List<Object>> createZoneQuery(int nodeIdx, String 
storageProfile) {
+        return executeSql(nodeIdx, format("CREATE ZONE IF NOT EXISTS {} 
STORAGE PROFILES ['{}']", ZONE_MANE, storageProfile));
+    }
+}
diff --git 
a/modules/sql-engine/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItCreateTableDdlTest.java
 
b/modules/sql-engine/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItCreateTableDdlTest.java
index 90a721aa13e..79b6f0b2613 100644
--- 
a/modules/sql-engine/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItCreateTableDdlTest.java
+++ 
b/modules/sql-engine/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItCreateTableDdlTest.java
@@ -31,7 +31,7 @@ import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.Matchers.equalTo;
 import static org.hamcrest.Matchers.hasSize;
 import static org.hamcrest.Matchers.is;
-import static org.hamcrest.Matchers.notNullValue;
+import static org.hamcrest.Matchers.nullValue;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
 import static org.junit.jupiter.api.Assertions.assertThrows;
@@ -763,12 +763,33 @@ public class ItCreateTableDdlTest extends 
BaseSqlIntegrationTest {
 
     @Test
     public void creatingTableOnZoneReferencingNonExistingProfile() {
+        String zoneName = "test_zone";
         String tableName = "test_table";
+        String nonExistingProfileName = "no-such-profile";
 
-        sql("CREATE ZONE test_zone STORAGE PROFILES ['no-such-profile']");
-        sql("CREATE TABLE " + tableName + " (id INT PRIMARY KEY, val INT) ZONE 
test_zone");
+        // Try to create zone with not existed storage profile.
+        assertThrowsSqlException(
+                STMT_VALIDATION_ERR,
+                "Some storage profiles don't exist [missedProfileNames=[" + 
nonExistingProfileName + "]].",
+                () -> sql("CREATE ZONE \"" + zoneName + "\" STORAGE PROFILES 
['" + nonExistingProfileName + "']")
+        );
+
+        // Check that the zone wasn't created and table creation fails with 
zone not found reason.
+        assertThrowsSqlException(
+                STMT_VALIDATION_ERR,
+                "Distribution zone with name '" + zoneName + "' not found.",
+                () -> sql("CREATE TABLE " + tableName + " (id INT PRIMARY KEY, 
val INT) ZONE \"" + zoneName + "\"")
+        );
+
+        // Try to create table with default zone and wrong storage profile.
+        assertThrowsSqlException(
+                STMT_VALIDATION_ERR,
+                "Zone with name 'Default' does not contain table's storage 
profile [storageProfile='" + nonExistingProfileName + "'].",
+                () -> sql("CREATE TABLE " + tableName + " (id INT PRIMARY KEY, 
val INT) STORAGE PROFILE '" + nonExistingProfileName + "'")
+        );
 
+        // Verify that there still no the desired table.
         Table table = CLUSTER.aliveNode().tables().table(tableName);
-        assertThat(table, is(notNullValue()));
+        assertThat(table, is(nullValue()));
     }
 }
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 7fc5a8ba4c5..f2e11594072 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
@@ -85,6 +85,8 @@ import 
org.apache.ignite.internal.sql.engine.prepare.PrepareService;
 import org.apache.ignite.internal.sql.engine.prepare.PrepareServiceImpl;
 import org.apache.ignite.internal.sql.engine.prepare.QueryMetadata;
 import org.apache.ignite.internal.sql.engine.prepare.QueryPlan;
+import 
org.apache.ignite.internal.sql.engine.prepare.ddl.ClusterWideStorageProfileValidator;
+import 
org.apache.ignite.internal.sql.engine.prepare.ddl.DdlSqlToCommandConverter;
 import 
org.apache.ignite.internal.sql.engine.prepare.pruning.PartitionPrunerImpl;
 import org.apache.ignite.internal.sql.engine.schema.SqlSchemaManager;
 import org.apache.ignite.internal.sql.engine.schema.SqlSchemaManagerImpl;
@@ -281,6 +283,10 @@ public class SqlQueryProcessor implements QueryProcessor, 
SystemViewProvider {
         metricManager.registerSource(sqlQueryMetricSource);
         metricManager.enable(sqlQueryMetricSource);
 
+        var storageProfileValidator = new 
ClusterWideStorageProfileValidator(logicalTopologyService);
+
+        var ddlSqlToCommandConverter = new 
DdlSqlToCommandConverter(storageProfileValidator);
+
         var prepareSvc = registerService(PrepareServiceImpl.create(
                 nodeName,
                 CACHE_FACTORY,
@@ -288,7 +294,8 @@ public class SqlQueryProcessor implements QueryProcessor, 
SystemViewProvider {
                 metricManager,
                 clusterCfg,
                 nodeCfg,
-                sqlSchemaManager
+                sqlSchemaManager,
+                ddlSqlToCommandConverter
         ));
 
         var msgSrvc = registerService(new MessageServiceImpl(
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 7db2b2273be..21ac08029f3 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
@@ -144,6 +144,7 @@ public class PrepareServiceImpl implements PrepareService {
      * @param clusterCfg Cluster SQL configuration.
      * @param nodeCfg Node SQL configuration.
      * @param schemaManager Schema manager to use on validation phase to bind 
identifiers in AST with particular schema objects.
+     * @param ddlSqlToCommandConverter Converter from SQL DDL operators to 
catalog commands.
      */
     public static PrepareServiceImpl create(
             String nodeName,
@@ -152,13 +153,14 @@ public class PrepareServiceImpl implements PrepareService 
{
             MetricManager metricManager,
             SqlDistributedConfiguration clusterCfg,
             SqlLocalConfiguration nodeCfg,
-            SqlSchemaManager schemaManager
+            SqlSchemaManager schemaManager,
+            DdlSqlToCommandConverter ddlSqlToCommandConverter
     ) {
         return new PrepareServiceImpl(
                 nodeName,
                 clusterCfg.planner().estimatedNumberOfQueries().value(),
                 cacheFactory,
-                new DdlSqlToCommandConverter(),
+                ddlSqlToCommandConverter,
                 clusterCfg.planner().maxPlanningTimeMillis().value(),
                 nodeCfg.planner().threadCount().value(),
                 metricManager,
diff --git 
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/ddl/ClusterWideStorageProfileValidator.java
 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/ddl/ClusterWideStorageProfileValidator.java
new file mode 100644
index 00000000000..0f522cac493
--- /dev/null
+++ 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/ddl/ClusterWideStorageProfileValidator.java
@@ -0,0 +1,88 @@
+/*
+ * 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.ignite.internal.lang.IgniteStringFormatter.format;
+import static org.apache.ignite.lang.ErrorGroups.Sql.STMT_VALIDATION_ERR;
+
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Set;
+import org.apache.ignite.internal.cluster.management.topology.api.LogicalNode;
+import 
org.apache.ignite.internal.cluster.management.topology.api.LogicalTopologyService;
+import 
org.apache.ignite.internal.cluster.management.topology.api.LogicalTopologySnapshot;
+import org.apache.ignite.sql.SqlException;
+
+/**
+ * Storage profile names validator that checks presence of given to validate 
profile names across cluster.
+ */
+public class ClusterWideStorageProfileValidator implements 
StorageProfileValidator {
+    private final LogicalTopologyService logicalTopologyService;
+
+    public ClusterWideStorageProfileValidator(LogicalTopologyService 
logicalTopologyService) {
+        this.logicalTopologyService = logicalTopologyService;
+    }
+
+    @Override
+    public void validate(Collection<String> storageProfiles) {
+        LogicalTopologySnapshot localLogicalTopologySnapshot = 
logicalTopologyService.localLogicalTopology();
+
+        Set<String> missedStorageProfileNames = 
findStorageProfileNotPresentedInLogicalTopologySnapshot(
+                storageProfiles,
+                localLogicalTopologySnapshot
+        );
+
+        if (!missedStorageProfileNames.isEmpty()) {
+            throw new SqlException(STMT_VALIDATION_ERR, format(
+                    "Some storage profiles don't exist 
[missedProfileNames={}].",
+                    missedStorageProfileNames
+            ));
+        }
+    }
+
+
+    private static Set<String> 
findStorageProfileNotPresentedInLogicalTopologySnapshot(
+            Collection<String> storageProfiles,
+            LogicalTopologySnapshot snapshot
+    ) {
+        Set<String> topologyWideProfiles = 
extractStorageProfileNamesFromLogicalTopologySnapshot(snapshot);
+
+        Set<String> missedProfiles = new HashSet<>();
+
+        for (String profileName : storageProfiles) {
+            if (!topologyWideProfiles.contains(profileName)) {
+                missedProfiles.add(profileName);
+            }
+        }
+
+        return missedProfiles;
+    }
+
+    private static Set<String> 
extractStorageProfileNamesFromLogicalTopologySnapshot(LogicalTopologySnapshot 
snapshot) {
+        Set<LogicalNode> logicalNodes = snapshot.nodes();
+
+        // Assume default persistent + rocks + aimem profiles on each node in 
average.
+        Set<String> topologyWideProfiles = new HashSet<>(logicalNodes.size() * 
3);
+
+        for (LogicalNode logicalNode : logicalNodes) {
+            topologyWideProfiles.addAll(logicalNode.storageProfiles());
+        }
+
+        return topologyWideProfiles;
+    }
+}
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 735e0521ccf..548be018b93 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
@@ -162,10 +162,15 @@ public class DdlSqlToCommandConverter {
     /** Zone options set. */
     private final Set<String> knownZoneOptionNames;
 
+    /** Storage profiles validator. */
+    private final StorageProfileValidator storageProfileValidator;
+
     /**
      * Constructor.
+     *
+     * @param storageProfileValidator Storage profile names validator.
      */
-    public DdlSqlToCommandConverter() {
+    public DdlSqlToCommandConverter(StorageProfileValidator 
storageProfileValidator) {
         knownZoneOptionNames = EnumSet.allOf(ZoneOptionEnum.class)
                 .stream()
                 .map(Enum::name)
@@ -206,6 +211,8 @@ public class DdlSqlToCommandConverter {
         ));
 
         alterReplicasOptionInfo = new DdlOptionInfo<>(Integer.class, 
this::checkPositiveNumber, AlterZoneCommandBuilder::replicas);
+
+        this.storageProfileValidator = storageProfileValidator;
     }
 
     /**
@@ -723,11 +730,20 @@ public class DdlSqlToCommandConverter {
 
         List<StorageProfileParams> profiles = 
extractProfiles(createZoneNode.storageProfiles());
 
+        Set<String> storageProfileNames = new HashSet<>(profiles.size());
+
+        for (StorageProfileParams profile : profiles) {
+            storageProfileNames.add(profile.storageProfile());
+        }
+
+        storageProfileValidator.validate(storageProfileNames);
+
         builder.storageProfilesParams(profiles);
 
         return builder.build();
     }
 
+
     /**
      * Converts the given '{@code ALTER ZONE}' AST to the {@link 
AlterZoneCommand} catalog command.
      */
diff --git 
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/ddl/StorageProfileValidator.java
 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/ddl/StorageProfileValidator.java
new file mode 100644
index 00000000000..360f274b61a
--- /dev/null
+++ 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/ddl/StorageProfileValidator.java
@@ -0,0 +1,34 @@
+/*
+ * 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 java.util.Collection;
+
+/**
+ * Common validator for storage profile names.
+ */
+@FunctionalInterface
+public interface StorageProfileValidator {
+
+    /**
+     * Checks that provided storage profile names are existing and valid.
+     *
+     * @param storageProfiles Storage profile names to check.
+     */
+    void validate(Collection<String> storageProfiles);
+}
diff --git 
a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/exec/ExecutionServiceImplTest.java
 
b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/exec/ExecutionServiceImplTest.java
index 9d2d4925724..6d01feb0c41 100644
--- 
a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/exec/ExecutionServiceImplTest.java
+++ 
b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/exec/ExecutionServiceImplTest.java
@@ -241,7 +241,7 @@ public class ExecutionServiceImplTest extends 
BaseIgniteAbstractTest {
     }
 
     private void setupCluster(CacheFactory mappingCacheFactory, 
Function<String, QueryTaskExecutor> executorsFactory) {
-        DdlSqlToCommandConverter converter = new DdlSqlToCommandConverter();
+        DdlSqlToCommandConverter converter = new 
DdlSqlToCommandConverter(storageProfiles -> {});
 
         testCluster = new TestCluster();
         executionServices = nodeNames.stream()
diff --git 
a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/framework/TestBuilders.java
 
b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/framework/TestBuilders.java
index 47620d6058a..a07353a4df0 100644
--- 
a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/framework/TestBuilders.java
+++ 
b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/framework/TestBuilders.java
@@ -744,7 +744,7 @@ public class TestBuilders {
             ConcurrentMap<String, Long> tablesSize = new ConcurrentHashMap<>();
             var schemaManager = createSqlSchemaManager(catalogManager, 
tablesSize);
             var prepareService = new PrepareServiceImpl(clusterName, 0, 
CaffeineCacheFactory.INSTANCE,
-                    new DdlSqlToCommandConverter(), planningTimeout, 
PLANNING_THREAD_COUNT,
+                    new DdlSqlToCommandConverter(storageProfiles -> {}), 
planningTimeout, PLANNING_THREAD_COUNT,
                     new NoOpMetricManager(), schemaManager);
 
             Map<String, List<String>> systemViewsByNode = new HashMap<>();
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
index bf6d605b918..bebcc90f235 100644
--- 
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
@@ -43,9 +43,9 @@ import 
org.apache.ignite.internal.testframework.BaseIgniteAbstractTest;
 /**
  * Common methods for {@link DdlSqlToCommandConverter} testing.
  */
-class AbstractDdlSqlToCommandConverterTest extends BaseIgniteAbstractTest {
+abstract class AbstractDdlSqlToCommandConverterTest extends 
BaseIgniteAbstractTest {
     /** DDL SQL to command converter. */
-    final DdlSqlToCommandConverter converter = new DdlSqlToCommandConverter();
+    DdlSqlToCommandConverter converter;
 
     final Catalog catalog = mock(Catalog.class);
 
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 fb020940dd5..b60ea68418d 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
@@ -91,6 +91,7 @@ import org.hamcrest.CustomMatcher;
 import org.hamcrest.Matcher;
 import org.hamcrest.Matchers;
 import org.jetbrains.annotations.Nullable;
+import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Disabled;
 import org.junit.jupiter.api.DynamicTest;
 import org.junit.jupiter.api.Test;
@@ -107,6 +108,11 @@ import org.mockito.Mockito;
 public class DdlSqlToCommandConverterTest extends 
AbstractDdlSqlToCommandConverterTest {
     private static final Integer TEST_ZONE_ID = 100;
 
+    @BeforeEach
+    void setUp() {
+        converter = new DdlSqlToCommandConverter(storageProfiles -> {});
+    }
+
     @Test
     void testCheckDuplicates() {
         assertThrows(
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
index 5fb83f9a700..9a8ee625315 100644
--- 
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
@@ -21,14 +21,20 @@ import static 
org.apache.ignite.internal.catalog.CatalogService.DEFAULT_STORAGE_
 import static org.apache.ignite.internal.lang.IgniteStringFormatter.format;
 import static 
org.apache.ignite.internal.testframework.IgniteTestUtils.assertThrows;
 import static 
org.apache.ignite.internal.testframework.IgniteTestUtils.assertThrowsWithCode;
+import static org.apache.ignite.lang.ErrorGroups.Sql.STMT_VALIDATION_ERR;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.Matchers.equalTo;
 import static org.hamcrest.Matchers.hasSize;
 import static org.hamcrest.Matchers.instanceOf;
 import static org.hamcrest.Matchers.is;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
 
 import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
 import java.util.stream.Stream;
 import org.apache.calcite.sql.SqlDdl;
 import org.apache.calcite.sql.SqlNode;
@@ -46,16 +52,19 @@ import 
org.apache.ignite.internal.catalog.storage.AlterZoneEntry;
 import org.apache.ignite.internal.catalog.storage.DropZoneEntry;
 import org.apache.ignite.internal.catalog.storage.NewZoneEntry;
 import org.apache.ignite.internal.catalog.storage.SetDefaultZoneEntry;
+import org.apache.ignite.internal.cluster.management.topology.api.LogicalNode;
+import 
org.apache.ignite.internal.cluster.management.topology.api.LogicalTopologyService;
+import 
org.apache.ignite.internal.cluster.management.topology.api.LogicalTopologySnapshot;
+import org.apache.ignite.internal.network.ClusterNodeImpl;
 import org.apache.ignite.internal.partitiondistribution.DistributionAlgorithm;
-import org.apache.ignite.lang.ErrorGroups.Sql;
+import org.apache.ignite.network.NetworkAddress;
 import org.apache.ignite.sql.SqlException;
-import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.params.ParameterizedTest;
 import org.junit.jupiter.params.provider.Arguments;
 import org.junit.jupiter.params.provider.MethodSource;
 import org.junit.jupiter.params.provider.ValueSource;
-import org.mockito.Mockito;
 
 /**
  * Tests the conversion of a sql zone definition to a command.
@@ -78,8 +87,34 @@ public class DistributionZoneSqlToCommandConverterTest 
extends AbstractDdlSqlToC
             ZoneOptionEnum.CONSISTENCY_MODE
     );
 
-    @BeforeAll
-    public static void setUp() {
+    private static final String AIPERSIST_STORAGE_PROFILE = 
"segmented_aipersist";
+
+    private static final String ROCKSDB_STORAGE_PROFILE = "lru_rocks";
+
+    private static final List<String> NODE_DEFAULT_STORAGE_PROFILES = List.of(
+            DEFAULT_STORAGE_PROFILE,
+            AIPERSIST_STORAGE_PROFILE,
+            ROCKSDB_STORAGE_PROFILE
+    );
+
+    private LogicalTopologyService logicalTopologyService;
+
+    @BeforeEach
+    public void setUp() {
+        // Default mock
+        logicalTopologyService = mock(LogicalTopologyService.class);
+
+        LogicalTopologySnapshot defaultLogicalTopologySnapshot = new 
LogicalTopologySnapshot(
+                0,
+                IntStream.range(0, 2)
+                        .mapToObj(nodeIdx -> createLocalNode(nodeIdx, 
NODE_DEFAULT_STORAGE_PROFILES))
+                        .collect(Collectors.toList())
+        );
+
+        
when(logicalTopologyService.localLogicalTopology()).thenReturn(defaultLogicalTopologySnapshot);
+
+        converter = new DdlSqlToCommandConverter(new 
ClusterWideStorageProfileValidator(logicalTopologyService));
+
         assertThat(ZoneOptionEnum.values().length, is(NUMERIC_OPTIONS.size() + 
STRING_OPTIONS.size()));
     }
 
@@ -169,7 +204,7 @@ public class DistributionZoneSqlToCommandConverterTest 
extends AbstractDdlSqlToC
                             + "distribution_algorithm='rendezvous', "
                             + "data_nodes_filter='$[?(@.region == \"US\")]', "
                             + "data_nodes_auto_adjust=300, "
-                            + "storage_profiles='lru_rocks , 
segmented_aipersist ' "
+                            + "storage_profiles='" + ROCKSDB_STORAGE_PROFILE + 
" , " + AIPERSIST_STORAGE_PROFILE + " ' "
                     : "CREATE ZONE test "
                             + "(partitions 2, "
                             + "replicas 5, "
@@ -177,7 +212,7 @@ public class DistributionZoneSqlToCommandConverterTest 
extends AbstractDdlSqlToC
                             + "distribution algorithm 'rendezvous', "
                             + "nodes filter '$[?(@.region == \"US\")]', "
                             + "auto adjust 300) "
-                            + "storage profiles ['lru_rocks', 
'segmented_aipersist '] ";
+                            + "storage profiles ['" + ROCKSDB_STORAGE_PROFILE 
+ "' , '" + AIPERSIST_STORAGE_PROFILE + " '] ";
 
             CatalogCommand cmd = convert(sql);
             CatalogZoneDescriptor desc = invokeAndGetFirstEntry(cmd, 
NewZoneEntry.class).descriptor();
@@ -192,8 +227,8 @@ public class DistributionZoneSqlToCommandConverterTest 
extends AbstractDdlSqlToC
 
             List<CatalogStorageProfileDescriptor> storageProfiles = 
desc.storageProfiles().profiles();
             assertThat(storageProfiles, hasSize(2));
-            assertThat(storageProfiles.get(0).storageProfile(), 
equalTo("lru_rocks"));
-            assertThat(storageProfiles.get(1).storageProfile(), 
equalTo("segmented_aipersist"));
+            assertThat(storageProfiles.get(0).storageProfile(), 
equalTo(ROCKSDB_STORAGE_PROFILE));
+            assertThat(storageProfiles.get(1).storageProfile(), 
equalTo(AIPERSIST_STORAGE_PROFILE));
         }
 
         // Check remaining options.
@@ -202,11 +237,11 @@ public class DistributionZoneSqlToCommandConverterTest 
extends AbstractDdlSqlToC
                     ? "CREATE ZONE test with "
                             + "data_nodes_auto_adjust_scale_up=100, "
                             + "data_nodes_auto_adjust_scale_down=200, "
-                            + "storage_profiles='lru_rocks'"
+                            + "storage_profiles='" + ROCKSDB_STORAGE_PROFILE + 
"'"
                     : "CREATE ZONE test "
                             + "(auto scale up 100, "
                             + "auto scale down 200) "
-                            + "storage profiles ['lru_rocks']";
+                            + "storage profiles ['" + ROCKSDB_STORAGE_PROFILE 
+ "']";
 
             CatalogCommand cmd = convert(sql);
             CatalogZoneDescriptor desc = invokeAndGetFirstEntry(cmd, 
NewZoneEntry.class).descriptor();
@@ -251,6 +286,86 @@ public class DistributionZoneSqlToCommandConverterTest 
extends AbstractDdlSqlToC
         assertThat(desc.replicas(), 
equalTo(DistributionAlgorithm.ALL_REPLICAS));
     }
 
+    @ParameterizedTest(name = "with syntax = {0}")
+    @ValueSource(booleans = {true, false})
+    public void testSingleNonExistedStorageProfile(boolean withPresent) {
+        String nonExistedStorageProfileName = "not_existed_profile";
+
+        String sql = withPresent
+                ? "CREATE ZONE test WITH STORAGE_PROFILES='" + 
nonExistedStorageProfileName + "'"
+                : "CREATE ZONE test STORAGE PROFILES ['" + 
nonExistedStorageProfileName + "']";
+
+        expectStatementValidationError(
+                sql,
+                "Some storage profiles don't exist [missedProfileNames=[" + 
nonExistedStorageProfileName + "]]."
+        );
+    }
+
+    @ParameterizedTest(name = "with syntax = {0}")
+    @ValueSource(booleans = {true, false})
+    public void testSeveralNonExistedStorageProfiles(boolean withPresent) {
+        String nonExistedStorageProfileName1 = "not_existed_profile_1";
+        String nonExistedStorageProfileName2 = "not_existed_profile_2";
+
+        String sql = withPresent
+                ? "CREATE ZONE test WITH STORAGE_PROFILES='" + 
nonExistedStorageProfileName1 + ", " + nonExistedStorageProfileName2 + "'"
+                : "CREATE ZONE test STORAGE PROFILES ['" + 
nonExistedStorageProfileName1 + "', '" + nonExistedStorageProfileName2 + "']";
+
+        expectStatementValidationError(
+                sql,
+                "Some storage profiles don't exist [missedProfileNames=["
+                        + nonExistedStorageProfileName1 + ", "
+                        + nonExistedStorageProfileName2 + "]]."
+        );
+    }
+
+    @ParameterizedTest(name = "with syntax = {0}")
+    @ValueSource(booleans = {true, false})
+    public void testNonExistedStorageProfilesAmongExistedOnes(boolean 
withPresent) {
+        String nonExistedStorageProfileName = "not_existed_profile";
+
+        String sql = withPresent
+                ? "CREATE ZONE test WITH STORAGE_PROFILES='"
+                        + AIPERSIST_STORAGE_PROFILE + ", "
+                        + nonExistedStorageProfileName + ", "
+                        + ROCKSDB_STORAGE_PROFILE + "'"
+                : "CREATE ZONE test STORAGE PROFILES ['"
+                        + AIPERSIST_STORAGE_PROFILE + "', '"
+                        + nonExistedStorageProfileName + "', '"
+                        + ROCKSDB_STORAGE_PROFILE + "']";
+
+        expectStatementValidationError(
+                sql,
+                "Some storage profiles don't exist [missedProfileNames=[" + 
nonExistedStorageProfileName + "]]."
+        );
+    }
+
+    @ParameterizedTest(name = "with syntax = {0}")
+    @ValueSource(booleans = {true, false})
+    public void 
testExistedStorageProfileOnDisjointProfileSetsInLogicalTopologySnapshot(boolean 
withPresent) throws SqlParseException {
+        when(logicalTopologyService.localLogicalTopology()).thenReturn(new 
LogicalTopologySnapshot(
+                0,
+                List.of(
+                        createLocalNode(0, List.of(AIPERSIST_STORAGE_PROFILE)),
+                        createLocalNode(1, List.of(ROCKSDB_STORAGE_PROFILE)),
+                        createLocalNode(2, List.of(DEFAULT_STORAGE_PROFILE))
+                )
+        ));
+
+        String sql = withPresent
+                ? "CREATE ZONE test WITH STORAGE_PROFILES='" + 
DEFAULT_STORAGE_PROFILE + "'"
+                : "CREATE ZONE test STORAGE PROFILES ['" + 
DEFAULT_STORAGE_PROFILE + "']";
+
+        CatalogCommand cmd = convert(sql);
+
+        List<CatalogStorageProfileDescriptor> storageProfiles = 
invokeAndGetFirstEntry(cmd, NewZoneEntry.class)
+                .descriptor()
+                .storageProfiles()
+                .profiles();
+        assertThat(storageProfiles, hasSize(1));
+        assertThat(storageProfiles.get(0).storageProfile(), 
equalTo(DEFAULT_STORAGE_PROFILE));
+    }
+
     private static List<Arguments> defaultQuorum() {
         return List.of(
                 Arguments.of(1, 1),
@@ -345,7 +460,7 @@ public class DistributionZoneSqlToCommandConverterTest 
extends AbstractDdlSqlToC
 
         assertThat(cmd, instanceOf(RenameZoneCommand.class));
 
-        
Mockito.when(catalog.zone("TEST")).thenReturn(mock(CatalogZoneDescriptor.class));
+        
when(catalog.zone("TEST")).thenReturn(mock(CatalogZoneDescriptor.class));
 
         AlterZoneEntry entry = invokeAndGetFirstEntry(cmd, 
AlterZoneEntry.class);
 
@@ -361,7 +476,7 @@ public class DistributionZoneSqlToCommandConverterTest 
extends AbstractDdlSqlToC
 
         RenameZoneCommand zoneCmd = (RenameZoneCommand) cmd;
 
-        
Mockito.when(catalog.zone("TEST")).thenReturn(mock(CatalogZoneDescriptor.class));
+        
when(catalog.zone("TEST")).thenReturn(mock(CatalogZoneDescriptor.class));
 
         AlterZoneEntry entry = invokeAndGetFirstEntry(cmd, 
AlterZoneEntry.class);
 
@@ -381,10 +496,10 @@ public class DistributionZoneSqlToCommandConverterTest 
extends AbstractDdlSqlToC
 
         CatalogZoneDescriptor zoneMock = mock(CatalogZoneDescriptor.class);
 
-        Mockito.when(zoneMock.name()).thenReturn("TEST");
-        Mockito.when(zoneMock.filter()).thenReturn("");
+        when(zoneMock.name()).thenReturn("TEST");
+        when(zoneMock.filter()).thenReturn("");
 
-        Mockito.when(catalog.zone("TEST")).thenReturn(zoneMock);
+        when(catalog.zone("TEST")).thenReturn(zoneMock);
 
         CatalogZoneDescriptor desc = invokeAndGetFirstEntry(cmd, 
AlterZoneEntry.class).descriptor();
 
@@ -425,10 +540,10 @@ public class DistributionZoneSqlToCommandConverterTest 
extends AbstractDdlSqlToC
             assertThat(cmd, instanceOf(AlterZoneCommand.class));
 
             CatalogZoneDescriptor zoneMock = mock(CatalogZoneDescriptor.class);
-            Mockito.when(zoneMock.name()).thenReturn("TEST");
-            Mockito.when(zoneMock.filter()).thenReturn("");
+            when(zoneMock.name()).thenReturn("TEST");
+            when(zoneMock.filter()).thenReturn("");
 
-            Mockito.when(catalog.zone("TEST")).thenReturn(zoneMock);
+            when(catalog.zone("TEST")).thenReturn(zoneMock);
 
             CatalogZoneDescriptor desc = invokeAndGetFirstEntry(cmd, 
AlterZoneEntry.class).descriptor();
 
@@ -455,10 +570,10 @@ public class DistributionZoneSqlToCommandConverterTest 
extends AbstractDdlSqlToC
             assertThat(cmd, instanceOf(AlterZoneCommand.class));
 
             CatalogZoneDescriptor zoneMock = mock(CatalogZoneDescriptor.class);
-            Mockito.when(zoneMock.name()).thenReturn("TEST");
-            Mockito.when(zoneMock.filter()).thenReturn("");
+            when(zoneMock.name()).thenReturn("TEST");
+            when(zoneMock.filter()).thenReturn("");
 
-            Mockito.when(catalog.zone("TEST")).thenReturn(zoneMock);
+            when(catalog.zone("TEST")).thenReturn(zoneMock);
 
             CatalogZoneDescriptor desc = invokeAndGetFirstEntry(cmd, 
AlterZoneEntry.class).descriptor();
 
@@ -477,10 +592,10 @@ public class DistributionZoneSqlToCommandConverterTest 
extends AbstractDdlSqlToC
         assertThat(cmd, instanceOf(AlterZoneCommand.class));
 
         CatalogZoneDescriptor zoneMock = mock(CatalogZoneDescriptor.class);
-        Mockito.when(zoneMock.name()).thenReturn("TEST");
-        Mockito.when(zoneMock.filter()).thenReturn("");
+        when(zoneMock.name()).thenReturn("TEST");
+        when(zoneMock.filter()).thenReturn("");
 
-        Mockito.when(catalog.zone("TEST")).thenReturn(zoneMock);
+        when(catalog.zone("TEST")).thenReturn(zoneMock);
 
         CatalogZoneDescriptor desc = invokeAndGetFirstEntry(cmd, 
AlterZoneEntry.class).descriptor();
 
@@ -494,7 +609,7 @@ public class DistributionZoneSqlToCommandConverterTest 
extends AbstractDdlSqlToC
         assertThat(cmd, instanceOf(AlterZoneSetDefaultCommand.class));
 
         CatalogZoneDescriptor zoneMock = mock(CatalogZoneDescriptor.class);
-        Mockito.when(catalog.zone("TEST")).thenReturn(zoneMock);
+        when(catalog.zone("TEST")).thenReturn(zoneMock);
 
         SetDefaultZoneEntry entry = invokeAndGetFirstEntry(cmd, 
SetDefaultZoneEntry.class);
 
@@ -546,7 +661,7 @@ public class DistributionZoneSqlToCommandConverterTest 
extends AbstractDdlSqlToC
         assertThat(cmd, instanceOf(DropZoneCommand.class));
 
         CatalogZoneDescriptor zoneMock = mock(CatalogZoneDescriptor.class);
-        Mockito.when(catalog.zone("TEST")).thenReturn(zoneMock);
+        when(catalog.zone("TEST")).thenReturn(zoneMock);
 
         DropZoneEntry entry = invokeAndGetFirstEntry(cmd, DropZoneEntry.class);
 
@@ -676,9 +791,22 @@ public class DistributionZoneSqlToCommandConverterTest 
extends AbstractDdlSqlToC
     private void expectStatementValidationError(String sql, String 
errorMessageFragment) {
         assertThrowsWithCode(
                 SqlException.class,
-                Sql.STMT_VALIDATION_ERR,
+                STMT_VALIDATION_ERR,
                 () -> convert(sql),
                 errorMessageFragment
         );
     }
+
+    private static LogicalNode createLocalNode(int nodeIdx, List<String> 
storageProfiles) {
+        return new LogicalNode(
+                new ClusterNodeImpl(
+                        UUID.randomUUID(),
+                        "node" + nodeIdx,
+                        new NetworkAddress("127.0.0.1", 3344 + nodeIdx)
+                ),
+                Map.of(),
+                Map.of(),
+                storageProfiles
+        );
+    }
 }


Reply via email to