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

amashenkov 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 0b54599735f IGNITE-25607 Sql. Invalid distribution for Exchange node 
in EXPLAIN PLAN (#6001)
0b54599735f is described below

commit 0b54599735f6011a3c317b9f887b0512129f8211
Author: Andrew V. Mashenkov <[email protected]>
AuthorDate: Tue Jun 10 15:48:54 2025 +0300

    IGNITE-25607 Sql. Invalid distribution for Exchange node in EXPLAIN PLAN 
(#6001)
---
 .../sql/engine/exec/DestinationFactory.java        |   5 +-
 .../exec/ExecutionDependencyResolverImpl.java      |   8 +-
 .../internal/sql/engine/externalize/RelJson.java   |  22 ++---
 .../pruning/PartitionPruningMetadataExtractor.java |   2 +-
 .../sql/engine/rel/AbstractIgniteJoin.java         |  17 ++--
 .../internal/sql/engine/rel/IgniteProject.java     |   4 +-
 .../engine/rel/explain/RelTreeToTextWriter.java    |   7 +-
 .../sql/engine/trait/DistributionFunction.java     |  70 ---------------
 .../sql/engine/trait/DistributionTrait.java        | 100 +++++++++++++++++++--
 .../sql/engine/trait/IgniteDistribution.java       |  12 +++
 .../sql/engine/trait/IgniteDistributions.java      |  25 +++++-
 .../exec/IdentityDistributionFunctionSelfTest.java |   2 -
 .../engine/exec/mapping/FragmentMappingTest.java   |   3 +-
 .../sql/engine/exec/mapping/FragmentPrinter.java   |   6 +-
 .../sql/engine/framework/TestBuilders.java         |   6 +-
 .../engine/planner/JoinColocationPlannerTest.java  |  10 +--
 .../engine/schema/SqlSchemaManagerImplTest.java    |  16 ++--
 17 files changed, 175 insertions(+), 140 deletions(-)

diff --git 
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/DestinationFactory.java
 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/DestinationFactory.java
index 834ef891ba8..00b18034cd0 100644
--- 
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/DestinationFactory.java
+++ 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/DestinationFactory.java
@@ -32,7 +32,6 @@ import 
org.apache.ignite.internal.sql.engine.schema.TableDescriptor;
 import org.apache.ignite.internal.sql.engine.trait.AllNodes;
 import org.apache.ignite.internal.sql.engine.trait.Destination;
 import org.apache.ignite.internal.sql.engine.trait.DistributionFunction;
-import 
org.apache.ignite.internal.sql.engine.trait.DistributionFunction.AffinityDistribution;
 import org.apache.ignite.internal.sql.engine.trait.Identity;
 import org.apache.ignite.internal.sql.engine.trait.IgniteDistribution;
 import org.apache.ignite.internal.sql.engine.trait.Partitioned;
@@ -92,10 +91,10 @@ class DestinationFactory<RowT> {
                     return new Identity<>(rowHandler, keys.get(0), 
group.nodeNames());
                 }
 
-                if (function.affinity()) {
+                if (distribution.isTableDistribution()) {
                     assert !nullOrEmpty(group.assignments());
 
-                    int tableId = ((AffinityDistribution) function).tableId();
+                    int tableId = distribution.tableId();
                     Supplier<PartitionCalculator> calculator = 
dependencies.partitionCalculator(tableId);
                     TableDescriptor tableDescriptor = 
dependencies.tableDescriptor(tableId);
 
diff --git 
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/ExecutionDependencyResolverImpl.java
 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/ExecutionDependencyResolverImpl.java
index c5344413d89..d6fb68016f7 100644
--- 
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/ExecutionDependencyResolverImpl.java
+++ 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/ExecutionDependencyResolverImpl.java
@@ -29,8 +29,6 @@ import 
org.apache.ignite.internal.sql.engine.rel.IgniteTableScan;
 import org.apache.ignite.internal.sql.engine.rel.IgniteTrimExchange;
 import org.apache.ignite.internal.sql.engine.schema.IgniteSystemView;
 import org.apache.ignite.internal.sql.engine.schema.IgniteTable;
-import org.apache.ignite.internal.sql.engine.trait.DistributionFunction;
-import 
org.apache.ignite.internal.sql.engine.trait.DistributionFunction.AffinityDistribution;
 import org.apache.ignite.internal.sql.engine.trait.IgniteDistribution;
 import org.apache.ignite.internal.sql.engine.trait.TraitUtils;
 
@@ -116,10 +114,8 @@ public class ExecutionDependencyResolverImpl implements 
ExecutionDependencyResol
             }
 
             private void resolveDistributionFunction(IgniteDistribution 
distribution) {
-                DistributionFunction function = distribution.function();
-
-                if (function.affinity()) {
-                    int tableId = ((AffinityDistribution) function).tableId();
+                if (distribution.isTableDistribution()) {
+                    int tableId = distribution.tableId();
 
                     resolveTable(catalogVersion, tableId);
                 }
diff --git 
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/externalize/RelJson.java
 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/externalize/RelJson.java
index e6af5c0bdd0..e2e1af2e988 100644
--- 
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/externalize/RelJson.java
+++ 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/externalize/RelJson.java
@@ -113,7 +113,6 @@ import 
org.apache.ignite.internal.sql.engine.prepare.bounds.RangeBounds;
 import org.apache.ignite.internal.sql.engine.prepare.bounds.SearchBounds;
 import org.apache.ignite.internal.sql.engine.rel.IgniteRel;
 import org.apache.ignite.internal.sql.engine.trait.DistributionFunction;
-import 
org.apache.ignite.internal.sql.engine.trait.DistributionFunction.AffinityDistribution;
 import org.apache.ignite.internal.sql.engine.trait.DistributionTrait;
 import org.apache.ignite.internal.sql.engine.trait.IgniteDistribution;
 import org.apache.ignite.internal.sql.engine.trait.IgniteDistributions;
@@ -540,12 +539,10 @@ class RelJson {
                 map.put("func", distribution.function().name());
                 map.put("keys", keys);
 
-                DistributionFunction function = distribution.function();
-
-                if (function.affinity()) {
-                    map.put("zoneId", ((AffinityDistribution) 
function).zoneId());
-                    map.put("tableId", ((AffinityDistribution) 
function).tableId());
-                    map.put("label", ((AffinityDistribution) 
function).label());
+                if (distribution.isTableDistribution()) {
+                    map.put("zoneId", distribution.zoneId());
+                    map.put("tableId", distribution.tableId());
+                    map.put("label", distribution.label());
                 }
 
                 return map;
@@ -692,10 +689,10 @@ class RelJson {
 
                 return IgniteDistributions.identity(keys.get(0));
             }
-            case "hash":
-                return IgniteDistributions.hash(keys, 
DistributionFunction.hash());
-            default: {
-                assert functionName.startsWith("affinity");
+            case "hash": {
+                if (map.get("tableId") == null) {
+                    return IgniteDistributions.hash(keys, 
DistributionFunction.hash());
+                }
 
                 int tableId = (int) map.get("tableId");
                 int zoneId = (int) map.get("zoneId");
@@ -703,6 +700,9 @@ class RelJson {
 
                 return IgniteDistributions.affinity(keys, tableId, zoneId, 
label);
             }
+            default: {
+                throw new IllegalStateException("Unsupported distribution 
function: " + functionName);
+            }
         }
     }
 
diff --git 
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/pruning/PartitionPruningMetadataExtractor.java
 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/pruning/PartitionPruningMetadataExtractor.java
index 73935936186..759cd6d9a12 100644
--- 
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/pruning/PartitionPruningMetadataExtractor.java
+++ 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/pruning/PartitionPruningMetadataExtractor.java
@@ -715,7 +715,7 @@ public class PartitionPruningMetadataExtractor extends 
IgniteRelShuttle {
 
     private static IntList distributionKeys(IgniteTable table) {
         IgniteDistribution distribution = table.distribution();
-        if (!distribution.function().affinity()) {
+        if (!distribution.isTableDistribution()) {
             return IntArrayList.of();
         }
 
diff --git 
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rel/AbstractIgniteJoin.java
 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rel/AbstractIgniteJoin.java
index 9cfe5f055a0..9f9eee74586 100644
--- 
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rel/AbstractIgniteJoin.java
+++ 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rel/AbstractIgniteJoin.java
@@ -49,6 +49,7 @@ import org.apache.calcite.util.mapping.Mappings;
 import org.apache.ignite.internal.sql.engine.rel.explain.IgniteRelWriter;
 import org.apache.ignite.internal.sql.engine.trait.DistributionFunction;
 import org.apache.ignite.internal.sql.engine.trait.IgniteDistribution;
+import org.apache.ignite.internal.sql.engine.trait.IgniteDistributions;
 import org.apache.ignite.internal.sql.engine.trait.TraitUtils;
 import org.apache.ignite.internal.sql.engine.trait.TraitsAwareIgniteRel;
 import org.apache.ignite.internal.sql.engine.util.Commons;
@@ -200,7 +201,7 @@ public abstract class AbstractIgniteJoin extends Join 
implements TraitsAwareIgni
             RelTraitSet nodeTraits,
             List<RelTraitSet> inputTraits
     ) {
-        // Tere are several rules:
+        // There are several rules:
         // 1) any join is possible on broadcast or single distribution
         // 2) hash distributed join is possible when join keys equal to source 
distribution keys
         // 3) hash and broadcast distributed tables can be joined when join 
keys equal to hash
@@ -230,15 +231,17 @@ public abstract class AbstractIgniteJoin extends Join 
implements TraitsAwareIgni
 
                 // We cannot provide random distribution without unique 
constraint on join keys,
                 // so, we require hash distribution (wich satisfies random 
distribution) instead.
-                DistributionFunction function = distrType == HASH_DISTRIBUTED
-                        ? distribution.function()
-                        : DistributionFunction.hash();
-
-                IgniteDistribution outDistr = hash(joinInfo.leftKeys, 
function);
+                IgniteDistribution outDistr = distrType == HASH_DISTRIBUTED
+                        ? IgniteDistributions.clone(distribution, 
joinInfo.leftKeys)
+                        : hash(joinInfo.leftKeys);
 
                 if (distrType != HASH_DISTRIBUTED || 
outDistr.satisfies(distribution)) {
+                    IgniteDistribution rightDistribution = distrType == 
HASH_DISTRIBUTED
+                            ? IgniteDistributions.clone(distribution, 
joinInfo.rightKeys)
+                            : hash(joinInfo.rightKeys);
+
                     return Pair.of(nodeTraits.replace(outDistr),
-                            List.of(left.replace(outDistr), 
right.replace(hash(joinInfo.rightKeys, function))));
+                            List.of(left.replace(outDistr), 
right.replace(rightDistribution)));
                 }
 
                 break;
diff --git 
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rel/IgniteProject.java
 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rel/IgniteProject.java
index 0232cd8f47f..9b5c6d599fd 100644
--- 
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rel/IgniteProject.java
+++ 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rel/IgniteProject.java
@@ -20,7 +20,6 @@ package org.apache.ignite.internal.sql.engine.rel;
 import static org.apache.calcite.rel.RelDistribution.Type.HASH_DISTRIBUTED;
 import static 
org.apache.ignite.internal.sql.engine.sql.fun.IgniteSqlOperatorTable.RAND_UUID;
 import static 
org.apache.ignite.internal.sql.engine.trait.IgniteDistributions.broadcast;
-import static 
org.apache.ignite.internal.sql.engine.trait.IgniteDistributions.hash;
 import static 
org.apache.ignite.internal.sql.engine.trait.IgniteDistributions.single;
 import static 
org.apache.ignite.internal.sql.engine.trait.TraitUtils.changeTraits;
 
@@ -54,6 +53,7 @@ import org.apache.calcite.util.mapping.Mappings;
 import org.apache.ignite.internal.sql.engine.metadata.cost.IgniteCost;
 import org.apache.ignite.internal.sql.engine.rel.explain.IgniteRelWriter;
 import org.apache.ignite.internal.sql.engine.trait.IgniteDistribution;
+import org.apache.ignite.internal.sql.engine.trait.IgniteDistributions;
 import org.apache.ignite.internal.sql.engine.trait.TraitUtils;
 import org.apache.ignite.internal.sql.engine.trait.TraitsAwareIgniteRel;
 
@@ -130,7 +130,7 @@ public class IgniteProject extends Project implements 
TraitsAwareIgniteRel {
         }
 
         if (srcKeys.size() == keys.size()) {
-            return Pair.of(nodeTraits, 
List.of(in.replace(hash(ImmutableIntList.of(srcKeys.elements()), 
distribution.function()))));
+            return Pair.of(nodeTraits, 
List.of(in.replace(IgniteDistributions.clone(distribution, srcKeys))));
         }
 
         return Pair.of(nodeTraits.replace(single()), 
List.of(in.replace(single())));
diff --git 
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rel/explain/RelTreeToTextWriter.java
 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rel/explain/RelTreeToTextWriter.java
index 5ff24e62a5a..a6eb1dc0dab 100644
--- 
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rel/explain/RelTreeToTextWriter.java
+++ 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rel/explain/RelTreeToTextWriter.java
@@ -49,7 +49,6 @@ import org.apache.calcite.util.ImmutableBitSet;
 import org.apache.ignite.internal.sql.engine.prepare.bounds.SearchBounds;
 import org.apache.ignite.internal.sql.engine.rel.IgniteRel;
 import org.apache.ignite.internal.sql.engine.schema.IgniteIndex;
-import 
org.apache.ignite.internal.sql.engine.trait.DistributionFunction.AffinityDistribution;
 import org.apache.ignite.internal.sql.engine.trait.IgniteDistribution;
 import org.apache.ignite.internal.sql.engine.util.Commons;
 import org.apache.ignite.lang.util.IgniteNameUtils;
@@ -436,11 +435,7 @@ class RelTreeToTextWriter {
 
     private static String beautifyDistribution(IgniteDistribution 
distribution, RelDataType rowType) {
         StringBuilder sb = new StringBuilder();
-        if (distribution.function().affinity()) {
-            sb.append(((AffinityDistribution) 
distribution.function()).label());
-        } else {
-            sb.append(distribution.function().name());
-        }
+        sb.append(distribution.label());
 
         if (!distribution.getKeys().isEmpty()) {
             sb.append(" by [");
diff --git 
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/trait/DistributionFunction.java
 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/trait/DistributionFunction.java
index 13292655ced..980e19c8ccf 100644
--- 
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/trait/DistributionFunction.java
+++ 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/trait/DistributionFunction.java
@@ -48,14 +48,6 @@ public abstract class DistributionFunction {
         return name = name0().intern();
     }
 
-    public boolean affinity() {
-        return false;
-    }
-
-    public static DistributionFunction affinity(int tableId, int zoneId, 
String label) {
-        return new AffinityDistribution(tableId, zoneId, label);
-    }
-
     /**
      * Get function name. This name used for equality checking and in {@link 
RelNode#getDigest()}.
      */
@@ -109,19 +101,6 @@ public abstract class DistributionFunction {
         return IdentityDistribution.INSTANCE;
     }
 
-    /**
-     * Satisfy.
-     * TODO Documentation https://issues.apache.org/jira/browse/IGNITE-15859
-     */
-    public static boolean satisfy(DistributionFunction f0, 
DistributionFunction f1) {
-        if (f0 == f1 || f0.name() == f1.name()) { // 
NOPMD.UseEqualsToCompareStrings
-            return true;
-        }
-
-        return f0 instanceof AffinityDistribution && f1 instanceof 
AffinityDistribution
-                && Objects.equals(((AffinityDistribution) f0).zoneId(), 
((AffinityDistribution) f1).zoneId());
-    }
-
     private static final class AnyDistribution extends DistributionFunction {
         public static final DistributionFunction INSTANCE = new 
AnyDistribution();
 
@@ -174,55 +153,6 @@ public abstract class DistributionFunction {
         public RelDistribution.Type type() {
             return RelDistribution.Type.HASH_DISTRIBUTED;
         }
-
-    }
-
-    /**
-     * Affinity distribution.
-     */
-    public static final class AffinityDistribution extends HashDistribution {
-        private final int tableId;
-
-        private final int zoneId;
-
-        private final String label;
-
-        /**
-         * Constructor.
-         *
-         * @param tableId Table ID.
-         * @param zoneId Distribution zone ID.
-         * @param label Human-readable label to show in EXPLAIN printout.
-         */
-        private AffinityDistribution(int tableId, int zoneId, String label) {
-            this.zoneId = zoneId;
-            this.tableId = tableId;
-            this.label = label;
-        }
-
-        /** {@inheritDoc} */
-        @Override
-        public boolean affinity() {
-            return true;
-        }
-
-        public int tableId() {
-            return tableId;
-        }
-
-        public int zoneId() {
-            return zoneId;
-        }
-
-        public String label() {
-            return label;
-        }
-
-        /** {@inheritDoc} */
-        @Override
-        protected String name0() {
-            return "affinity[tableId=" + tableId + ", zoneId=" + zoneId + ']';
-        }
     }
 
     /**
diff --git 
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/trait/DistributionTrait.java
 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/trait/DistributionTrait.java
index 23be8bea2e6..cdb29d3088a 100644
--- 
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/trait/DistributionTrait.java
+++ 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/trait/DistributionTrait.java
@@ -65,8 +65,16 @@ public final class DistributionTrait implements 
IgniteDistribution {
 
     private final ImmutableIntList keys;
 
+    private final boolean affinityFlag;
+
+    private final int tableId;
+
+    private final int zoneId;
+
+    private final String label;
+
     /**
-     * Constructor.
+     * Constructor non-hash distributions.
      *
      * @param function Distribution function.
      */
@@ -76,17 +84,48 @@ public final class DistributionTrait implements 
IgniteDistribution {
         this.function = function;
 
         keys = ImmutableIntList.of();
+        affinityFlag = false;
+        tableId = -1;
+        zoneId = -1;
+        label = function.name();
     }
 
     /**
-     * Constructor.
+     * Constructor for hash distribution.
      *
      * @param keys     Distribution keys.
      * @param function Distribution function.
      */
     DistributionTrait(List<Integer> keys, DistributionFunction function) {
+        this(keys, -1, -1, function.name(), function, false);
+    }
+
+    /**
+     * Constructor for affinity distribution.
+     *
+     * @param keys     Distribution keys.
+     * @param function Distribution function.
+     */
+    DistributionTrait(List<Integer> keys, int tableId, int zoneId, String 
label, DistributionFunction function) {
+        this(keys, tableId, zoneId, label, function, true);
+    }
+
+    private DistributionTrait(
+            List<Integer> keys,
+            int tableId,
+            int zoneId,
+            String label,
+            DistributionFunction function,
+            boolean affinityFlag
+    ) {
+        assert function.type() == HASH_DISTRIBUTED;
+
         this.keys = ImmutableIntList.copyOf(keys);
         this.function = function;
+        this.tableId = tableId;
+        this.zoneId = zoneId;
+        this.label = label;
+        this.affinityFlag = affinityFlag;
     }
 
     /** {@inheritDoc} */
@@ -107,6 +146,26 @@ public final class DistributionTrait implements 
IgniteDistribution {
         return keys;
     }
 
+    @Override
+    public boolean isTableDistribution() {
+        return affinityFlag;
+    }
+
+    @Override
+    public int tableId() {
+        return tableId;
+    }
+
+    @Override
+    public int zoneId() {
+        return zoneId;
+    }
+
+    @Override
+    public String label() {
+        return label;
+    }
+
     /** {@inheritDoc} */
     @Override
     public void register(RelOptPlanner planner) {
@@ -122,7 +181,12 @@ public final class DistributionTrait implements 
IgniteDistribution {
         if (o instanceof DistributionTrait) {
             DistributionTrait that = (DistributionTrait) o;
 
-            return Objects.equals(function, that.function) && 
Objects.equals(keys, that.keys);
+            return Objects.equals(function, that.function)
+                    && Objects.equals(keys, that.keys)
+                    && affinityFlag == that.affinityFlag
+                    && tableId == that.tableId
+                    && zoneId == that.zoneId
+                    && Objects.equals(label, that.label);
         }
 
         return false;
@@ -131,13 +195,15 @@ public final class DistributionTrait implements 
IgniteDistribution {
     /** {@inheritDoc} */
     @Override
     public int hashCode() {
-        return Objects.hash(function, keys);
+        return Objects.hash(function, keys, zoneId, tableId);
     }
 
     /** {@inheritDoc} */
     @Override
     public String toString() {
-        return function.name() + (function.type() == HASH_DISTRIBUTED ? keys : 
"");
+        return function.name()
+                + (function.type() == HASH_DISTRIBUTED ? keys : "")
+                + (isTableDistribution() ? "[zoneId=" + zoneId + ", tableId=" 
+ tableId + ']' : "");
     }
 
     /** {@inheritDoc} */
@@ -166,7 +232,9 @@ public final class DistributionTrait implements 
IgniteDistribution {
         if (getType() == other.getType()) {
             return getType() != HASH_DISTRIBUTED
                     || (Objects.equals(keys, other.keys)
-                    && DistributionFunction.satisfy(function, other.function));
+                    && affinityFlag == other.affinityFlag
+                    && zoneId == other.zoneId
+                    && Objects.equals(function, other.function));
         }
 
         if (other.getType() == RANDOM_DISTRIBUTED) {
@@ -191,7 +259,9 @@ public final class DistributionTrait implements 
IgniteDistribution {
 
         List<Integer> res = Mappings.apply2((Mapping) mapping, keys);
 
-        return IgniteDistributions.hash(ImmutableIntList.copyOf(res), 
function);
+        return affinityFlag
+                ? IgniteDistributions.affinity(res, tableId, zoneId, label)
+                : IgniteDistributions.hash(res, function);
     }
 
     /** {@inheritDoc} */
@@ -212,6 +282,22 @@ public final class DistributionTrait implements 
IgniteDistribution {
                 cmp = 
function.name().compareTo(distribution.function().name());
             }
 
+            if (cmp == 0) {
+                cmp = Boolean.compare(affinityFlag, 
distribution.isTableDistribution());
+            }
+
+            if (cmp == 0 && affinityFlag) {
+                cmp = Integer.compare(zoneId, distribution.zoneId());
+
+                if (cmp == 0) {
+                    cmp = Integer.compare(tableId, distribution.tableId());
+                }
+
+                if (cmp == 0) {
+                    cmp = label.compareTo(distribution.label());
+                }
+            }
+
             return cmp;
         }
 
diff --git 
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/trait/IgniteDistribution.java
 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/trait/IgniteDistribution.java
index f38be86972a..3ab7695f134 100644
--- 
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/trait/IgniteDistribution.java
+++ 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/trait/IgniteDistribution.java
@@ -30,6 +30,18 @@ public interface IgniteDistribution extends RelDistribution {
      */
     DistributionFunction function();
 
+    /** Returns {@code true} if this is table distribution, {@code false} 
otherwise. */
+    boolean isTableDistribution();
+
+    /** Returns zone id of table distribution. */
+    int zoneId();
+
+    /** Returns table id of table distribution. */
+    int tableId();
+
+    /** Returns distribution function label for EXPLAIN plan purposes.  */
+    String label();
+
     /** {@inheritDoc} */
     @Override
     ImmutableIntList getKeys();
diff --git 
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/trait/IgniteDistributions.java
 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/trait/IgniteDistributions.java
index 4fadbfbed90..4a28b59630d 100644
--- 
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/trait/IgniteDistributions.java
+++ 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/trait/IgniteDistributions.java
@@ -21,6 +21,7 @@ import static 
org.apache.ignite.internal.util.CollectionUtils.nullOrEmpty;
 
 import java.util.List;
 import org.apache.calcite.plan.RelTraitDef;
+import org.apache.calcite.rel.RelDistribution.Type;
 import org.apache.calcite.util.ImmutableIntList;
 
 /**
@@ -75,7 +76,9 @@ public class IgniteDistributions {
      * @return Affinity distribution.
      */
     public static IgniteDistribution affinity(List<Integer> keys, int tableId, 
int zoneId, String label) {
-        return hash(keys, DistributionFunction.affinity(tableId, zoneId, 
label));
+        assert !nullOrEmpty(keys) : "Hash-based distribution must have at 
least one key";
+
+        return canonize(new DistributionTrait(keys, tableId, zoneId, label, 
DistributionFunction.hash()));
     }
 
     /**
@@ -98,7 +101,7 @@ public class IgniteDistributions {
     public static IgniteDistribution hash(List<Integer> keys, 
DistributionFunction function) {
         assert !nullOrEmpty(keys) : "Hash-based distribution must have at 
least one key";
 
-        return canonize(new DistributionTrait(ImmutableIntList.copyOf(keys), 
function));
+        return canonize(new DistributionTrait(keys, function));
     }
 
     /**
@@ -111,6 +114,24 @@ public class IgniteDistributions {
         return canonize(new DistributionTrait(ImmutableIntList.of(key), 
DistributionFunction.identity()));
     }
 
+    /**
+     * Creates a distribution trait of the same hash function with given 
distribution keys.
+     *
+     * @param trait Distribution trait.
+     * @param keys Distribution keys ordinals. Should not be null or empty.
+     * @return Distribution trait.
+     */
+    public static IgniteDistribution clone(IgniteDistribution trait, 
List<Integer> keys) {
+        assert !nullOrEmpty(keys) : "Hash-based distribution must have at 
least one key";
+        assert trait.function().type() == Type.HASH_DISTRIBUTED;
+
+        DistributionTrait distributionTrait = trait.isTableDistribution()
+                ? new DistributionTrait(keys, trait.tableId(), trait.zoneId(), 
trait.label(), trait.function())
+                : new DistributionTrait(keys, trait.function());
+
+        return canonize(distributionTrait);
+    }
+
     /**
      * See {@link RelTraitDef#canonize(org.apache.calcite.plan.RelTrait)}.
      */
diff --git 
a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/exec/IdentityDistributionFunctionSelfTest.java
 
b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/exec/IdentityDistributionFunctionSelfTest.java
index 72fad396b0d..18643885bce 100644
--- 
a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/exec/IdentityDistributionFunctionSelfTest.java
+++ 
b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/exec/IdentityDistributionFunctionSelfTest.java
@@ -21,7 +21,6 @@ import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.Matchers.containsInAnyOrder;
 import static org.hamcrest.Matchers.equalTo;
 import static org.hamcrest.Matchers.instanceOf;
-import static org.hamcrest.Matchers.is;
 
 import it.unimi.dsi.fastutil.ints.Int2ObjectMaps;
 import it.unimi.dsi.fastutil.longs.LongList;
@@ -65,7 +64,6 @@ public class IdentityDistributionFunctionSelfTest {
         IdentityDistribution function = new IdentityDistribution();
 
         assertThat(function.type(), equalTo(Type.HASH_DISTRIBUTED));
-        assertThat(function.affinity(), is(false));
         assertThat(function.name(), equalTo("identity"));
     }
 
diff --git 
a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/exec/mapping/FragmentMappingTest.java
 
b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/exec/mapping/FragmentMappingTest.java
index 7eb5c0233ea..fb8d61a3cc8 100644
--- 
a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/exec/mapping/FragmentMappingTest.java
+++ 
b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/exec/mapping/FragmentMappingTest.java
@@ -48,7 +48,6 @@ import org.apache.ignite.internal.sql.engine.rel.IgniteRel;
 import org.apache.ignite.internal.sql.engine.schema.IgniteDataSource;
 import org.apache.ignite.internal.sql.engine.schema.IgniteSchema;
 import org.apache.ignite.internal.sql.engine.schema.IgniteTable;
-import 
org.apache.ignite.internal.sql.engine.trait.DistributionFunction.AffinityDistribution;
 import 
org.apache.ignite.internal.sql.engine.trait.DistributionFunction.IdentityDistribution;
 import org.apache.ignite.internal.sql.engine.trait.IgniteDistribution;
 import org.apache.ignite.internal.sql.engine.trait.IgniteDistributions;
@@ -392,7 +391,7 @@ public class FragmentMappingTest extends 
AbstractPlannerTest {
             }
 
             IgniteDistribution distributionToUse;
-            if (distribution.function() instanceof AffinityDistribution) {
+            if (distribution.isTableDistribution()) {
                 distributionToUse = TestBuilders.affinity(0, objectId, 
objectId);
             } else {
                 distributionToUse = distribution;
diff --git 
a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/exec/mapping/FragmentPrinter.java
 
b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/exec/mapping/FragmentPrinter.java
index f03bfc42ab0..ebda1421dad 100644
--- 
a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/exec/mapping/FragmentPrinter.java
+++ 
b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/exec/mapping/FragmentPrinter.java
@@ -45,7 +45,6 @@ import 
org.apache.ignite.internal.sql.engine.rel.IgniteTableScan;
 import org.apache.ignite.internal.sql.engine.schema.IgniteDataSource;
 import org.apache.ignite.internal.sql.engine.schema.IgniteTable;
 import org.apache.ignite.internal.sql.engine.schema.TableDescriptor;
-import 
org.apache.ignite.internal.sql.engine.trait.DistributionFunction.AffinityDistribution;
 import org.apache.ignite.internal.sql.engine.trait.IgniteDistribution;
 
 /**
@@ -320,9 +319,8 @@ final class FragmentPrinter extends IgniteRelShuttle {
     }
 
     private static String formatDistribution(IgniteDistribution distribution, 
TableDescriptorCollector collector) {
-        if (distribution.function() instanceof AffinityDistribution) {
-            AffinityDistribution f = (AffinityDistribution) 
distribution.function();
-            IgniteTable igniteTable = collector.tables.get(f.tableId());
+        if (distribution.isTableDistribution()) {
+            IgniteTable igniteTable = 
collector.tables.get(distribution.tableId());
 
             if (igniteTable == null) {
                 String error = format("Unknown tableId: {}. Existing: {}", 
collector.tables.keySet());
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 fff710fe71f..4cf2692f115 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
@@ -132,7 +132,6 @@ import 
org.apache.ignite.internal.sql.engine.schema.TableDescriptor;
 import org.apache.ignite.internal.sql.engine.schema.TableDescriptorImpl;
 import org.apache.ignite.internal.sql.engine.sql.ParserServiceImpl;
 import org.apache.ignite.internal.sql.engine.statistic.SqlStatisticManager;
-import org.apache.ignite.internal.sql.engine.trait.DistributionFunction;
 import org.apache.ignite.internal.sql.engine.trait.IgniteDistribution;
 import org.apache.ignite.internal.sql.engine.trait.IgniteDistributions;
 import org.apache.ignite.internal.sql.engine.util.Commons;
@@ -354,10 +353,7 @@ public class TestBuilders {
      * @return Affinity distribution.
      */
     public static IgniteDistribution affinity(int key, int tableId, int 
zoneId) {
-        return IgniteDistributions.hash(
-                ImmutableIntList.of(key),
-                DistributionFunction.affinity(tableId, zoneId, 
affinityDistributionLabel(tableId, zoneId))
-        );
+        return affinity(ImmutableIntList.of(key), tableId, zoneId);
     }
 
     /**
diff --git 
a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/planner/JoinColocationPlannerTest.java
 
b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/planner/JoinColocationPlannerTest.java
index 60f3077b6d7..1d36e7dcdef 100644
--- 
a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/planner/JoinColocationPlannerTest.java
+++ 
b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/planner/JoinColocationPlannerTest.java
@@ -68,7 +68,7 @@ public class JoinColocationPlannerTest extends 
AbstractPlannerTest {
         String invalidPlanMsg = "Invalid plan:\n" + RelOptUtil.toString(phys);
 
         assertThat(invalidPlanMsg, join, notNullValue());
-        assertThat(invalidPlanMsg, join.distribution().function().affinity(), 
is(true));
+        assertThat(invalidPlanMsg, join.distribution().isTableDistribution(), 
is(true));
         assertThat(invalidPlanMsg, join.getLeft(), 
instanceOf(IgniteIndexScan.class));
         assertThat(invalidPlanMsg, join.getRight(), 
instanceOf(IgniteIndexScan.class));
     }
@@ -96,7 +96,7 @@ public class JoinColocationPlannerTest extends 
AbstractPlannerTest {
 
         assertThat(invalidPlanMsg, joinNodes.size(), equalTo(1));
         assertThat(invalidPlanMsg, join, notNullValue());
-        assertThat(invalidPlanMsg, join.distribution().function().affinity(), 
is(true));
+        assertThat(invalidPlanMsg, join.distribution().isTableDistribution(), 
is(true));
     }
 
     /**
@@ -120,7 +120,7 @@ public class JoinColocationPlannerTest extends 
AbstractPlannerTest {
         String invalidPlanMsg = "Invalid plan:\n" + RelOptUtil.toString(phys);
 
         assertThat(invalidPlanMsg, join, notNullValue());
-        assertThat(invalidPlanMsg, join.distribution().function().affinity(), 
is(true));
+        assertThat(invalidPlanMsg, join.distribution().isTableDistribution(), 
is(true));
 
         if (!"MergeJoinConverter".equals(disabledRule)) {
             assertThat(invalidPlanMsg, join.getLeft(), 
instanceOf(IgniteIndexScan.class));
@@ -150,7 +150,7 @@ public class JoinColocationPlannerTest extends 
AbstractPlannerTest {
         String invalidPlanMsg = "Invalid plan:\n" + RelOptUtil.toString(phys);
 
         assertThat(invalidPlanMsg, join, notNullValue());
-        assertThat(invalidPlanMsg, join.distribution().function().affinity(), 
is(true));
+        assertThat(invalidPlanMsg, join.distribution().isTableDistribution(), 
is(true));
 
         if (!"MergeJoinConverter".equals(disabledRule)) {
             assertThat(invalidPlanMsg, join.getLeft(), 
instanceOf(IgniteIndexScan.class));
@@ -180,7 +180,7 @@ public class JoinColocationPlannerTest extends 
AbstractPlannerTest {
         String invalidPlanMsg = "Invalid plan:\n" + RelOptUtil.toString(phys);
 
         assertThat(invalidPlanMsg, join, notNullValue());
-        assertThat(invalidPlanMsg, join.distribution().function().affinity(), 
is(false));
+        assertThat(invalidPlanMsg, join.distribution().isTableDistribution(), 
is(false));
     }
 
     /**
diff --git 
a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/schema/SqlSchemaManagerImplTest.java
 
b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/schema/SqlSchemaManagerImplTest.java
index 4b73b5f81ac..f1c8dc4181b 100644
--- 
a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/schema/SqlSchemaManagerImplTest.java
+++ 
b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/schema/SqlSchemaManagerImplTest.java
@@ -82,7 +82,6 @@ import org.apache.ignite.internal.hlc.HybridClockImpl;
 import org.apache.ignite.internal.lang.IgniteInternalException;
 import org.apache.ignite.internal.manager.ComponentContext;
 import org.apache.ignite.internal.schema.DefaultValueGenerator;
-import org.apache.ignite.internal.sql.engine.framework.TestBuilders;
 import org.apache.ignite.internal.sql.engine.schema.IgniteIndex.Type;
 import org.apache.ignite.internal.sql.engine.statistic.SqlStatisticManager;
 import org.apache.ignite.internal.sql.engine.trait.IgniteDistribution;
@@ -474,30 +473,33 @@ public class SqlSchemaManagerImplTest extends 
BaseIgniteAbstractTest {
             IgniteTable table = getTable(unwrapSchema(schemaPlus), "T1");
             IgniteDistribution distribution = 
table.descriptor().distribution();
 
-            assertThat(distribution, equalTo(TestBuilders.affinity(
+            assertThat(distribution, equalTo(IgniteDistributions.affinity(
                     List.of(1),
                     table.id(),
-                    enabledColocation() ? table.zoneId() : table.id())));
+                    enabledColocation() ? table.zoneId() : table.id(),
+                    "table PUBLIC.T1 in zone \"Default\"")));
         }
 
         {
             IgniteTable table = getTable(unwrapSchema(schemaPlus), "T2");
             IgniteDistribution distribution = 
table.descriptor().distribution();
 
-            assertThat(distribution, equalTo(TestBuilders.affinity(
+            assertThat(distribution, equalTo(IgniteDistributions.affinity(
                     List.of(3, 1),
                     table.id(),
-                    enabledColocation() ? table.zoneId() : table.id())));
+                    enabledColocation() ? table.zoneId() : table.id(),
+                    "table PUBLIC.T2 in zone \"Default\"")));
         }
 
         {
             IgniteTable table = getTable(unwrapSchema(schemaPlus), "T3");
             IgniteDistribution distribution = 
table.descriptor().distribution();
 
-            assertThat(distribution, equalTo(TestBuilders.affinity(
+            assertThat(distribution, equalTo(IgniteDistributions.affinity(
                     List.of(2, 1, 0),
                     table.id(),
-                    enabledColocation() ? table.zoneId() : table.id())));
+                    enabledColocation() ? table.zoneId() : table.id(),
+                    "table PUBLIC.T3 in zone \"Default\"")));
         }
     }
 


Reply via email to