You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ignite.apache.org by ko...@apache.org on 2025/05/29 11:04:18 UTC

(ignite-3) branch main updated: IGNITE-25471 Sql. Introduce custom infrastructure for rel explanation (#5916)

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 e1074283d8f IGNITE-25471 Sql. Introduce custom infrastructure for rel explanation (#5916)
e1074283d8f is described below

commit e1074283d8f247e498ee310bdee7d5ba069c5da0
Author: korlov42 <ko...@apache.org>
AuthorDate: Thu May 29 14:04:13 2025 +0300

    IGNITE-25471 Sql. Introduce custom infrastructure for rel explanation (#5916)
---
 .../internal/sql/engine/ItOrToUnionRuleTest.java   |  14 +-
 .../internal/sql/engine/ItSecondaryIndexTest.java  |   2 +-
 .../integrationTest/sql/group1/explain/modify.test | 200 +++++-----
 .../integrationTest/sql/group1/explain/scan.test   | 135 ++++---
 .../internal/sql/engine/prepare/ExplainUtils.java  |  45 ---
 .../sql/engine/prepare/KeyValueGetPlan.java        |   1 +
 .../sql/engine/prepare/KeyValueModifyPlan.java     |   1 +
 .../internal/sql/engine/prepare/MultiStepPlan.java |   1 +
 .../sql/engine/prepare/SelectCountPlan.java        |   1 +
 .../sql/engine/rel/AbstractIgniteJoin.java         |   8 +
 .../internal/sql/engine/rel/AbstractIndexScan.java |  13 +
 .../internal/sql/engine/rel/IgniteAggregate.java   |  17 +
 .../engine/rel/IgniteCorrelatedNestedLoopJoin.java |   6 +
 .../internal/sql/engine/rel/IgniteExchange.java    |   6 +
 .../internal/sql/engine/rel/IgniteFilter.java      |   6 +
 .../internal/sql/engine/rel/IgniteIndexScan.java   |  12 +
 .../internal/sql/engine/rel/IgniteKeyValueGet.java |   7 +
 .../sql/engine/rel/IgniteKeyValueModify.java       |  23 +-
 .../internal/sql/engine/rel/IgniteLimit.java       |  14 +
 .../internal/sql/engine/rel/IgniteMergeJoin.java   |   5 +-
 .../internal/sql/engine/rel/IgniteProject.java     |   6 +
 .../ignite/internal/sql/engine/rel/IgniteRel.java  |  13 +
 .../internal/sql/engine/rel/IgniteSelectCount.java |  16 +
 .../ignite/internal/sql/engine/rel/IgniteSort.java |  14 +
 .../sql/engine/rel/IgniteSystemViewScan.java       |   7 +
 .../sql/engine/rel/IgniteTableFunctionScan.java    |   6 +
 .../internal/sql/engine/rel/IgniteTableModify.java |  12 +-
 .../internal/sql/engine/rel/IgniteTableScan.java   |   7 +
 .../sql/engine/rel/IgniteTrimExchange.java         |   6 +
 .../internal/sql/engine/rel/IgniteValues.java      |   7 +
 .../engine/rel/ProjectableFilterableTableScan.java |  39 +-
 .../rel/agg/IgniteColocatedSortAggregate.java      |   6 +
 .../sql/engine/rel/agg/IgniteMapSortAggregate.java |   6 +
 .../engine/rel/agg/IgniteReduceAggregateBase.java  |  24 +-
 .../engine/rel/agg/IgniteReduceSortAggregate.java  |   6 +
 .../sql/engine/rel/explain/ExplainUtils.java       |  83 ++++
 .../sql/engine/rel/explain/IgniteRelWriter.java    | 223 +++++++++++
 .../engine/rel/explain/RelTreeToTextWriter.java    | 439 +++++++++++++++++++++
 .../sql/engine/rel/set/IgniteIntersect.java        |   6 +
 .../internal/sql/engine/rel/set/IgniteMinus.java   |   6 +
 .../src/test/resources/tpcds/plan/q64.plan         | 289 +++++++-------
 .../test/resources/tpcds/plan/q64_colocated.plan   | 289 +++++++-------
 .../src/test/resources/tpch/plan/q1.plan           |  21 +-
 .../src/test/resources/tpch/plan/q21.plan          |  81 ++--
 .../src/test/resources/tpch/plan/q5.plan           |  43 +-
 .../src/test/resources/tpch/plan/q7.plan           |  46 +--
 .../src/test/resources/tpch/plan/q8.plan           |  62 +--
 .../src/test/resources/tpch/plan/q9.plan           |  41 +-
 .../internal/sql/engine/util/QueryChecker.java     |   9 +-
 49 files changed, 1670 insertions(+), 660 deletions(-)

diff --git a/modules/sql-engine/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItOrToUnionRuleTest.java b/modules/sql-engine/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItOrToUnionRuleTest.java
index b2b8e83a425..d1c8e209e69 100644
--- a/modules/sql-engine/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItOrToUnionRuleTest.java
+++ b/modules/sql-engine/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItOrToUnionRuleTest.java
@@ -99,7 +99,7 @@ public class ItOrToUnionRuleTest extends BaseSqlIntegrationTest {
                 + "WHERE category = 'Video' "
                 + "OR subcategory ='Camera Lens'")
                 .disableRules("LogicalTableScanConverterRule")
-                .matches(containsUnion(true))
+                .matches(containsUnion())
                 .matches(containsIndexScan("PUBLIC", "PRODUCTS", "IDX_CATEGORY"))
                 .matches(containsIndexScan("PUBLIC", "PRODUCTS", "IDX_SUBCATEGORY"))
                 .returns(3, "Photo", 1, "Camera Lens", 12, "Lens 1")
@@ -120,7 +120,7 @@ public class ItOrToUnionRuleTest extends BaseSqlIntegrationTest {
                 + "WHERE category = 'Photo' "
                 + "OR (subcat_id > 12 AND subcat_id < 22)")
                 .disableRules("LogicalTableScanConverterRule")
-                .matches(containsUnion(true))
+                .matches(containsUnion())
                 .matches(containsIndexScan("PUBLIC", "PRODUCTS", "IDX_CATEGORY"))
                 .matches(containsIndexScan("PUBLIC", "PRODUCTS", "IDX_SUBCAT_ID"))
                 .returns(1, "Photo", 1, "Camera Media", 11, "Media 1")
@@ -143,7 +143,7 @@ public class ItOrToUnionRuleTest extends BaseSqlIntegrationTest {
                 + "WHERE subcategory = 'Camera Lens' "
                 + "OR subcategory = 'Other'")
                 .disableRules("LogicalTableScanConverterRule")
-                .matches(not(containsUnion(true)))
+                .matches(not(containsUnion()))
                 .matches(containsIndexScan("PUBLIC", "PRODUCTS", "IDX_SUBCATEGORY"))
                 .matches(containsString("searchBounds: [MultiBounds"))
                 .returns(3, "Photo", 1, "Camera Lens", 12, "Lens 1")
@@ -162,7 +162,7 @@ public class ItOrToUnionRuleTest extends BaseSqlIntegrationTest {
                 + "FROM products "
                 + "WHERE cat_id > 1 "
                 + "OR subcat_id < 10")
-                .matches(not(containsUnion(true)))
+                .matches(not(containsUnion()))
                 .matches(containsTableScan("PUBLIC", "PRODUCTS"))
                 .returns(5, "Video", 2, "Camera Media", 21, "Media 3")
                 .returns(6, "Video", 2, "Camera Lens", 22, "Lens 3")
@@ -177,7 +177,7 @@ public class ItOrToUnionRuleTest extends BaseSqlIntegrationTest {
     @Test
     public void testUnionRuleNotApplicable() {
         assertQuery("SELECT * FROM products WHERE name = 'Canon' OR subcat_id = 22")
-                .matches(not(containsUnion(true)))
+                .matches(not(containsUnion()))
                 .matches(containsTableScan("PUBLIC", "PRODUCTS"))
                 .returns(7, "Video", 1, null, 0, "Canon")
                 .returns(6, "Video", 2, "Camera Lens", 22, "Lens 3")
@@ -193,7 +193,7 @@ public class ItOrToUnionRuleTest extends BaseSqlIntegrationTest {
                 + "FROM products "
                 + "WHERE name = 'Canon' "
                 + "OR category = 'Video'")
-                .matches(not(containsUnion(true)))
+                .matches(not(containsUnion()))
                 .matches(containsTableScan("PUBLIC", "PRODUCTS"))
                 .returns(5, "Video", 2, "Camera Media", 21, "Media 3")
                 .returns(6, "Video", 2, "Camera Lens", 22, "Lens 3")
@@ -210,7 +210,7 @@ public class ItOrToUnionRuleTest extends BaseSqlIntegrationTest {
                 + "FROM products "
                 + "WHERE name = 'Canon' "
                 + "OR name = 'Sony'")
-                .matches(not(containsUnion(true)))
+                .matches(not(containsUnion()))
                 .matches(containsTableScan("PUBLIC", "PRODUCTS"))
                 .returns(7, "Video", 1, null, 0, "Canon")
                 .check();
diff --git a/modules/sql-engine/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItSecondaryIndexTest.java b/modules/sql-engine/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItSecondaryIndexTest.java
index 0982f6f1744..50ce4f1792f 100644
--- a/modules/sql-engine/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItSecondaryIndexTest.java
+++ b/modules/sql-engine/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItSecondaryIndexTest.java
@@ -599,7 +599,7 @@ public class ItSecondaryIndexTest extends BaseSqlIntegrationTest {
     public void testOrCondition5() {
         assertQuery("SELECT * FROM Developer WHERE depId=1 OR name='Mozart'")
                 .disableRules("LogicalTableScanConverterRule")
-                .matches(containsUnion(true))
+                .matches(containsUnion())
                 .matches(containsIndexScan("PUBLIC", "DEVELOPER", DEPID_IDX))
                 .returns(1, "Mozart", 3, "Vienna", 33)
                 .returns(3, "Bach", 1, "Leipzig", 55)
diff --git a/modules/sql-engine/src/integrationTest/sql/group1/explain/modify.test b/modules/sql-engine/src/integrationTest/sql/group1/explain/modify.test
index 0a385815757..20244004a21 100644
--- a/modules/sql-engine/src/integrationTest/sql/group1/explain/modify.test
+++ b/modules/sql-engine/src/integrationTest/sql/group1/explain/modify.test
@@ -7,8 +7,9 @@ INSERT INTO test_table VALUES (1, 2, 3)
 ----
 KeyValueModify
     table: PUBLIC.TEST_TABLE
-    operation: PUT
-    expressions: [1, 2, 3]
+    fieldNames: [ROWCOUNT]
+    sourceExpression: [1, 2, 3]
+    type: INSERT
     est: (rows=1)
 
 # insert single tuple with literals different order
@@ -17,8 +18,9 @@ INSERT INTO test_table (c2, c3, c1) VALUES (1, 2, 3)
 ----
 KeyValueModify
     table: PUBLIC.TEST_TABLE
-    operation: PUT
-    expressions: [3, 1, 2]
+    fieldNames: [ROWCOUNT]
+    sourceExpression: [3, 1, 2]
+    type: INSERT
     est: (rows=1)
 
 # insert single tuple mixed
@@ -27,8 +29,9 @@ INSERT INTO test_table VALUES (1, LENGTH(rand_uuid()::VARCHAR), 3)
 ----
 KeyValueModify
     table: PUBLIC.TEST_TABLE
-    operation: PUT
-    expressions: [1, LENGTH(CAST(RAND_UUID()):VARCHAR CHARACTER SET "UTF-8" NOT NULL), 3]
+    fieldNames: [ROWCOUNT]
+    sourceExpression: [1, LENGTH(CAST(RAND_UUID()):VARCHAR CHARACTER SET "UTF-8" NOT NULL), 3]
+    type: INSERT
     est: (rows=1)
 
 # https://issues.apache.org/jira/browse/IGNITE-25454
@@ -42,26 +45,27 @@ plan
 INSERT INTO test_table VALUES (1, 2, 3), (2, 2, 3)
 ----
 Project
-    fields: [ROWCOUNT]
-    exprs: [CAST($f0):BIGINT NOT NULL]
+    fieldNames: [ROWCOUNT]
+    projection: [CAST($f0):BIGINT NOT NULL]
     est: (rows=1)
   ColocatedHashAggregate
+      fieldNames: [$f0]
       group: []
-      aggs: [$SUM0(ROWCOUNT)]
+      aggregation: [$SUM0(ROWCOUNT)]
       est: (rows=1)
     Exchange
         distribution: single
         est: (rows=2)
       TableModify
-          table: [PUBLIC, TEST_TABLE]
-          operation: INSERT
-          flattened: false
+          table: PUBLIC.TEST_TABLE
+          fieldNames: [ROWCOUNT]
+          type: INSERT
           est: (rows=2)
         TrimExchange
             distribution: affinity[tableId=18, zoneId=18][0]
             est: (rows=2)
           Values
-              type: [#0: C1 INTEGER, #1: C2 INTEGER, #2: C3 INTEGER]
+              fieldNames: [C1, C2, C3]
               tuples: [[1, 2, 3], [2, 2, 3]]
               est: (rows=2)
 
@@ -76,44 +80,44 @@ plan
 INSERT INTO test_table VALUES (1, LENGTH(rand_uuid()::VARCHAR), 3), (2, LENGTH(rand_uuid()::VARCHAR), 3)
 ----
 Project
-    fields: [ROWCOUNT]
-    exprs: [CAST($f0):BIGINT NOT NULL]
+    fieldNames: [ROWCOUNT]
+    projection: [CAST($f0):BIGINT NOT NULL]
     est: (rows=2)
   ColocatedHashAggregate
+      fieldNames: [$f0]
       group: []
-      aggs: [$SUM0(ROWCOUNT)]
+      aggregation: [$SUM0(ROWCOUNT)]
       est: (rows=2)
     Exchange
         distribution: single
         est: (rows=2)
       TableModify
-          table: [PUBLIC, TEST_TABLE]
-          operation: INSERT
-          flattened: false
+          table: PUBLIC.TEST_TABLE
+          fieldNames: [ROWCOUNT]
+          type: INSERT
           est: (rows=2)
         UnionAll
-            all: true
             est: (rows=2)
           Exchange
               distribution: affinity[tableId=18, zoneId=18][0]
               est: (rows=1)
             Project
-                fields: [EXPR$0, EXPR$1, EXPR$2]
-                exprs: [1, LENGTH(CAST(RAND_UUID()):VARCHAR CHARACTER SET "UTF-8" NOT NULL), 3]
+                fieldNames: [EXPR$0, EXPR$1, EXPR$2]
+                projection: [1, LENGTH(CAST(RAND_UUID()):VARCHAR CHARACTER SET "UTF-8" NOT NULL), 3]
                 est: (rows=1)
               Values
-                  type: [#0: ZERO INTEGER]
+                  fieldNames: [ZERO]
                   tuples: [[0]]
                   est: (rows=1)
           Exchange
               distribution: affinity[tableId=18, zoneId=18][0]
               est: (rows=1)
             Project
-                fields: [EXPR$0, EXPR$1, EXPR$2]
-                exprs: [2, LENGTH(CAST(RAND_UUID()):VARCHAR CHARACTER SET "UTF-8" NOT NULL), 3]
+                fieldNames: [EXPR$0, EXPR$1, EXPR$2]
+                projection: [2, LENGTH(CAST(RAND_UUID()):VARCHAR CHARACTER SET "UTF-8" NOT NULL), 3]
                 est: (rows=1)
               Values
-                  type: [#0: ZERO INTEGER]
+                  fieldNames: [ZERO]
                   tuples: [[0]]
                   est: (rows=1)
 
@@ -122,25 +126,26 @@ plan
 DELETE FROM test_table WHERE c1 = 1
 ----
 Project
-    fields: [ROWCOUNT]
-    exprs: [CAST($f0):BIGINT NOT NULL]
+    fieldNames: [ROWCOUNT]
+    projection: [CAST($f0):BIGINT NOT NULL]
     est: (rows=1)
   ColocatedHashAggregate
+      fieldNames: [$f0]
       group: []
-      aggs: [$SUM0(ROWCOUNT)]
+      aggregation: [$SUM0(ROWCOUNT)]
       est: (rows=1)
     Exchange
         distribution: single
         est: (rows=1)
       TableModify
-          table: [PUBLIC, TEST_TABLE]
-          operation: DELETE
-          flattened: false
+          table: PUBLIC.TEST_TABLE
+          fieldNames: [ROWCOUNT]
+          type: DELETE
           est: (rows=1)
         TableScan
             table: PUBLIC.TEST_TABLE
-            filters: =(C1, 1)
-            fields: [C1]
+            predicate: =(C1, 1)
+            fieldNames: [C1]
             est: (rows=1)
 
 # delete by key complex
@@ -148,52 +153,54 @@ plan
 DELETE FROM test_table WHERE c1 in (1, 2, 3)
 ----
 Project
-    fields: [ROWCOUNT]
-    exprs: [CAST($f0):BIGINT NOT NULL]
+    fieldNames: [ROWCOUNT]
+    projection: [CAST($f0):BIGINT NOT NULL]
     est: (rows=1)
   ColocatedHashAggregate
+      fieldNames: [$f0]
       group: []
-      aggs: [$SUM0(ROWCOUNT)]
+      aggregation: [$SUM0(ROWCOUNT)]
       est: (rows=1)
     Exchange
         distribution: single
         est: (rows=1)
       TableModify
-          table: [PUBLIC, TEST_TABLE]
-          operation: DELETE
-          flattened: false
+          table: PUBLIC.TEST_TABLE
+          fieldNames: [ROWCOUNT]
+          type: DELETE
           est: (rows=1)
         TableScan
             table: PUBLIC.TEST_TABLE
-            filters: SEARCH(C1, Sarg[1, 2, 3])
-            fields: [C1]
+            predicate: SEARCH(C1, Sarg[1, 2, 3])
+            fieldNames: [C1]
             est: (rows=1)
 
+
 # delete with predicate by arbitrary column
 plan
 DELETE FROM test_table WHERE c3 in (1, 2, 3)
 ----
 Project
-    fields: [ROWCOUNT]
-    exprs: [CAST($f0):BIGINT NOT NULL]
+    fieldNames: [ROWCOUNT]
+    projection: [CAST($f0):BIGINT NOT NULL]
     est: (rows=1)
   ColocatedHashAggregate
+      fieldNames: [$f0]
       group: []
-      aggs: [$SUM0(ROWCOUNT)]
+      aggregation: [$SUM0(ROWCOUNT)]
       est: (rows=1)
     Exchange
         distribution: single
         est: (rows=1)
       TableModify
-          table: [PUBLIC, TEST_TABLE]
-          operation: DELETE
-          flattened: false
+          table: PUBLIC.TEST_TABLE
+          fieldNames: [ROWCOUNT]
+          type: DELETE
           est: (rows=1)
         TableScan
             table: PUBLIC.TEST_TABLE
-            filters: SEARCH(C3, Sarg[1, 2, 3])
-            fields: [C1]
-            projects: [C1]
+            predicate: SEARCH(C3, Sarg[1, 2, 3])
+            fieldNames: [C1]
             est: (rows=1)
 
 # update with predicate by key simple
@@ -201,28 +208,27 @@ plan
 UPDATE test_table SET c2 = 2 WHERE c1 = 1
 ----
 Project
-    fields: [ROWCOUNT]
-    exprs: [CAST($f0):BIGINT NOT NULL]
+    fieldNames: [ROWCOUNT]
+    projection: [CAST($f0):BIGINT NOT NULL]
     est: (rows=1)
   ColocatedHashAggregate
+      fieldNames: [$f0]
       group: []
-      aggs: [$SUM0(ROWCOUNT)]
+      aggregation: [$SUM0(ROWCOUNT)]
       est: (rows=1)
     Exchange
         distribution: single
         est: (rows=1)
       TableModify
-          table: [PUBLIC, TEST_TABLE]
-          operation: UPDATE
-          updateColumnList: [C2]
-          sourceExpressionList: [C2]
-          flattened: false
+          table: PUBLIC.TEST_TABLE
+          fieldNames: [ROWCOUNT]
+          type: UPDATE
           est: (rows=1)
         TableScan
             table: PUBLIC.TEST_TABLE
-            filters: =(C1, 1)
-            fields: [C1, C2, C3, EXPR$0]
-            projects: [C1, C2, C3, 2]
+            predicate: =(C1, 1)
+            fieldNames: [C1, C2, C3, EXPR$0]
+            projection: [C1, C2, C3, 2]
             est: (rows=1)
 
 # update with predicate by key complex
@@ -230,28 +236,27 @@ plan
 UPDATE test_table SET c2 = 2 WHERE c1 in (1, 2, 3)
 ----
 Project
-    fields: [ROWCOUNT]
-    exprs: [CAST($f0):BIGINT NOT NULL]
+    fieldNames: [ROWCOUNT]
+    projection: [CAST($f0):BIGINT NOT NULL]
     est: (rows=1)
   ColocatedHashAggregate
+      fieldNames: [$f0]
       group: []
-      aggs: [$SUM0(ROWCOUNT)]
+      aggregation: [$SUM0(ROWCOUNT)]
       est: (rows=1)
     Exchange
         distribution: single
         est: (rows=1)
       TableModify
-          table: [PUBLIC, TEST_TABLE]
-          operation: UPDATE
-          updateColumnList: [C2]
-          sourceExpressionList: [C2]
-          flattened: false
+          table: PUBLIC.TEST_TABLE
+          fieldNames: [ROWCOUNT]
+          type: UPDATE
           est: (rows=1)
         TableScan
             table: PUBLIC.TEST_TABLE
-            filters: SEARCH(C1, Sarg[1, 2, 3])
-            fields: [C1, C2, C3, EXPR$0]
-            projects: [C1, C2, C3, 2]
+            predicate: SEARCH(C1, Sarg[1, 2, 3])
+            fieldNames: [C1, C2, C3, EXPR$0]
+            projection: [C1, C2, C3, 2]
             est: (rows=1)
 
 # update with predicate by arbitrary column
@@ -259,28 +264,27 @@ plan
 UPDATE test_table SET c2 = 2 WHERE c3 in (1, 2, 3)
 ----
 Project
-    fields: [ROWCOUNT]
-    exprs: [CAST($f0):BIGINT NOT NULL]
+    fieldNames: [ROWCOUNT]
+    projection: [CAST($f0):BIGINT NOT NULL]
     est: (rows=1)
   ColocatedHashAggregate
+      fieldNames: [$f0]
       group: []
-      aggs: [$SUM0(ROWCOUNT)]
+      aggregation: [$SUM0(ROWCOUNT)]
       est: (rows=1)
     Exchange
         distribution: single
         est: (rows=1)
       TableModify
-          table: [PUBLIC, TEST_TABLE]
-          operation: UPDATE
-          updateColumnList: [C2]
-          sourceExpressionList: [C2]
-          flattened: false
+          table: PUBLIC.TEST_TABLE
+          fieldNames: [ROWCOUNT]
+          type: UPDATE
           est: (rows=1)
         TableScan
             table: PUBLIC.TEST_TABLE
-            filters: SEARCH(C3, Sarg[1, 2, 3])
-            fields: [C1, C2, C3, EXPR$0]
-            projects: [C1, C2, C3, 2]
+            predicate: SEARCH(C3, Sarg[1, 2, 3])
+            fieldNames: [C1, C2, C3, EXPR$0]
+            projection: [C1, C2, C3, 2]
             est: (rows=1)
 
 # https://issues.apache.org/jira/browse/IGNITE-25454
@@ -298,42 +302,42 @@ USING (SELECT c1, c2, c3 FROM test_table) src
  WHEN NOT MATCHED THEN INSERT VALUES (1, 2, 3)
 ----
 Project
-    fields: [ROWCOUNT]
-    exprs: [CAST($f0):BIGINT NOT NULL]
+    fieldNames: [ROWCOUNT]
+    projection: [CAST($f0):BIGINT NOT NULL]
     est: (rows=1)
   ColocatedHashAggregate
+      fieldNames: [$f0]
       group: []
-      aggs: [$SUM0(ROWCOUNT)]
+      aggregation: [$SUM0(ROWCOUNT)]
       est: (rows=1)
     Exchange
         distribution: single
         est: (rows=1)
       TableModify
-          table: [PUBLIC, TEST_TABLE]
-          operation: MERGE
-          updateColumnList: [C2]
-          flattened: false
+          table: PUBLIC.TEST_TABLE
+          fieldNames: [ROWCOUNT]
+          type: MERGE
           est: (rows=1)
         Exchange
             distribution: affinity[tableId=18, zoneId=18][0]
             est: (rows=1)
           Project
-              fields: [$f0, $f1, $f2, C10, C20, C30, $f6]
-              exprs: [1, 2, 3, C1, C2, C3, *(C20, 2)]
+              fieldNames: [$f0, $f1, $f2, C10, C20, C30, $f6]
+              projection: [1, 2, 3, C1, C2, C3, *(C20, 2)]
               est: (rows=1)
             HashJoin
-                condition: =(C1, $f3)
-                joinType: right
+                predicate: =(C1, $f3)
+                type: right
                 est: (rows=1)
               TableScan
                   table: PUBLIC.TEST_TABLE
-                  fields: [C1, C2, C3]
+                  fieldNames: [C1, C2, C3]
                   est: (rows=1)
               Exchange
                   distribution: affinity[tableId=18, zoneId=18][1]
                   est: (rows=1)
                 TableScan
                     table: PUBLIC.TEST_TABLE
-                    fields: [C2, $f3]
-                    projects: [C2, *(C1, 2)]
+                    fieldNames: [C2, $f3]
+                    projection: [C2, *(C1, 2)]
                     est: (rows=1)
diff --git a/modules/sql-engine/src/integrationTest/sql/group1/explain/scan.test b/modules/sql-engine/src/integrationTest/sql/group1/explain/scan.test
index c1c7af33d49..40c5eb63594 100644
--- a/modules/sql-engine/src/integrationTest/sql/group1/explain/scan.test
+++ b/modules/sql-engine/src/integrationTest/sql/group1/explain/scan.test
@@ -10,7 +10,7 @@ Exchange
     est: (rows=1)
   TableScan
       table: PUBLIC.TEST_TABLE
-      fields: [C1, C2, C3]
+      fieldNames: [C1, C2, C3]
       est: (rows=1)
 
 # select with trimming projection, no renames
@@ -22,7 +22,7 @@ Exchange
     est: (rows=1)
   TableScan
       table: PUBLIC.TEST_TABLE
-      fields: [C1, C2]
+      fieldNames: [C1, C2]
       est: (rows=1)
 
 # select with trimming projection and renames
@@ -34,7 +34,7 @@ Exchange
     est: (rows=1)
   TableScan
       table: PUBLIC.TEST_TABLE
-      fields: [RENAMED_C1, C2]
+      fieldNames: [RENAMED_C1, C2]
       est: (rows=1)
 
 # select with expression projection, no renames
@@ -46,8 +46,8 @@ Exchange
     est: (rows=1)
   TableScan
       table: PUBLIC.TEST_TABLE
-      fields: [EXPR$0, C2]
-      projects: [+(C1, C3), C2]
+      fieldNames: [EXPR$0, C2]
+      projection: [+(C1, C3), C2]
       est: (rows=1)
 
 # select with expression projection and renames
@@ -59,8 +59,8 @@ Exchange
     est: (rows=1)
   TableScan
       table: PUBLIC.TEST_TABLE
-      fields: [SUM_OF_C1_AND_C3, C2]
-      projects: [+(C1, C3), C2]
+      fieldNames: [SUM_OF_C1_AND_C3, C2]
+      projection: [+(C1, C3), C2]
       est: (rows=1)
 
 # Similar set of tests but for IndexScan
@@ -78,7 +78,7 @@ Exchange
       table: PUBLIC.TEST_TABLE
       index: TEST_TABLE_IDX
       type: SORTED
-      fields: [C1, C2, C3]
+      fieldNames: [C1, C2, C3]
       collation: [C1 ASC, C2 ASC, C3 ASC]
       est: (rows=1)
 
@@ -93,7 +93,7 @@ Exchange
       table: PUBLIC.TEST_TABLE
       index: TEST_TABLE_IDX
       type: SORTED
-      fields: [C1, C2]
+      fieldNames: [C1, C2]
       collation: [C1 ASC, C2 ASC]
       est: (rows=1)
 
@@ -108,8 +108,8 @@ Exchange
       table: PUBLIC.TEST_TABLE
       index: TEST_TABLE_IDX
       type: SORTED
-      fields: [RENAMED_C1, C2]
-      collation: [C1 ASC, C2 ASC]
+      fieldNames: [RENAMED_C1, C2]
+      collation: [RENAMED_C1 ASC, C2 ASC]
       est: (rows=1)
 
 # select with expression projection, no renames
@@ -123,8 +123,8 @@ Exchange
       table: PUBLIC.TEST_TABLE
       index: TEST_TABLE_IDX
       type: SORTED
-      fields: [EXPR$0, C2]
-      projects: [+(C1, C3), C2]
+      fieldNames: [EXPR$0, C2]
+      projection: [+(C1, C3), C2]
       collation: []
       est: (rows=1)
 
@@ -139,8 +139,8 @@ Exchange
       table: PUBLIC.TEST_TABLE
       index: TEST_TABLE_IDX
       type: SORTED
-      fields: [SUM_OF_C1_AND_C3, C2]
-      projects: [+(C1, C3), C2]
+      fieldNames: [SUM_OF_C1_AND_C3, C2]
+      projection: [+(C1, C3), C2]
       collation: []
       est: (rows=1)
 
@@ -151,7 +151,7 @@ SELECT * FROM test_table WHERE c1 = 1
 ----
 KeyValueGet
     table: PUBLIC.TEST_TABLE
-    fields: [C1, C2, C3]
+    fieldNames: [C1, C2, C3]
     key: [1]
     est: (rows=1)
 
@@ -161,7 +161,7 @@ SELECT c1, c2 FROM test_table WHERE c1 = 1
 ----
 KeyValueGet
     table: PUBLIC.TEST_TABLE
-    fields: [C1, C2]
+    fieldNames: [C1, C2]
     key: [1]
     est: (rows=1)
 
@@ -171,7 +171,7 @@ SELECT c1 AS renamed_c1, c2 FROM test_table WHERE c1 = 1
 ----
 KeyValueGet
     table: PUBLIC.TEST_TABLE
-    fields: [C1, C2]
+    fieldNames: [C1, C2]
     key: [1]
     est: (rows=1)
 
@@ -181,8 +181,8 @@ SELECT c1 + c3, c2 FROM test_table WHERE c1 = 1
 ----
 KeyValueGet
     table: PUBLIC.TEST_TABLE
-    fields: [EXPR$0, C2]
-    projects: [+(C1, C3), C2]
+    fieldNames: [EXPR$0, C2]
+    projection: [+(C1, C3), C2]
     key: [1]
     est: (rows=1)
 
@@ -192,8 +192,8 @@ SELECT c1 + c3 AS sum_of_c1_and_c3, c2 FROM test_table WHERE c1 = 1
 ----
 KeyValueGet
     table: PUBLIC.TEST_TABLE
-    fields: [SUM_OF_C1_AND_C3, C2]
-    projects: [+(C1, C3), C2]
+    fieldNames: [SUM_OF_C1_AND_C3, C2]
+    projection: [+(C1, C3), C2]
     key: [1]
     est: (rows=1)
 
@@ -204,7 +204,7 @@ SELECT * FROM system.system_views
 ----
 SystemViewScan
     table: SYSTEM.SYSTEM_VIEWS
-    fields: [VIEW_ID, SCHEMA_NAME, VIEW_NAME, VIEW_TYPE, ID, SCHEMA, NAME, TYPE]
+    fieldNames: [VIEW_ID, SCHEMA_NAME, VIEW_NAME, VIEW_TYPE, ID, SCHEMA, NAME, TYPE]
     est: (rows=100)
 
 # select with trimming projection, no renames
@@ -213,7 +213,7 @@ SELECT view_id, view_name FROM system.system_views
 ----
 SystemViewScan
     table: SYSTEM.SYSTEM_VIEWS
-    fields: [VIEW_ID, VIEW_NAME]
+    fieldNames: [VIEW_ID, VIEW_NAME]
     est: (rows=100)
 
 # select with trimming projection and renames
@@ -222,7 +222,7 @@ SELECT view_id AS renamed_view_id, view_name FROM system.system_views
 ----
 SystemViewScan
     table: SYSTEM.SYSTEM_VIEWS
-    fields: [RENAMED_VIEW_ID, VIEW_NAME]
+    fieldNames: [RENAMED_VIEW_ID, VIEW_NAME]
     est: (rows=100)
 
 # select with expression projection, no renames
@@ -231,8 +231,8 @@ SELECT view_id + 5, view_name FROM system.system_views
 ----
 SystemViewScan
     table: SYSTEM.SYSTEM_VIEWS
-    fields: [EXPR$0, VIEW_NAME]
-    projects: [+(VIEW_ID, 5), VIEW_NAME]
+    fieldNames: [EXPR$0, VIEW_NAME]
+    projection: [+(VIEW_ID, 5), VIEW_NAME]
     est: (rows=100)
 
 # select with expression projection and renames
@@ -241,8 +241,8 @@ SELECT view_id + 5 AS adjusted_view_id, view_name FROM system.system_views
 ----
 SystemViewScan
     table: SYSTEM.SYSTEM_VIEWS
-    fields: [ADJUSTED_VIEW_ID, VIEW_NAME]
-    projects: [+(VIEW_ID, 5), VIEW_NAME]
+    fieldNames: [ADJUSTED_VIEW_ID, VIEW_NAME]
+    projection: [+(VIEW_ID, 5), VIEW_NAME]
     est: (rows=100)
 
 # Complex query where column of source table is renamed, and this renamed column is referenced by
@@ -256,32 +256,33 @@ SELECT
   FROM (SELECT c1, c1 as renamed_c1 FROM test_table) as t
 ----
 Project
-    fields: [EXPR$0]
-    exprs: [EXPR$0]
+    fieldNames: [EXPR$0]
+    projection: [EXPR$0]
     est: (rows=1)
   CorrelatedNestedLoopJoin
-      condition: true
-      joinType: left
-      variablesSet: [$cor0]
+      predicate: true
+      type: left
+      correlates: [$cor0]
       est: (rows=1)
     Exchange
         distribution: single
         est: (rows=1)
       TableScan
           table: PUBLIC.TEST_TABLE
-          fields: [RENAMED_C1]
+          fieldNames: [RENAMED_C1]
           est: (rows=1)
     ColocatedHashAggregate
+        fieldNames: [EXPR$0]
         group: []
-        aggs: [EXPR$0=COUNT()]
+        aggregation: [COUNT()]
         est: (rows=1)
       Exchange
           distribution: single
           est: (rows=1)
         TableScan
             table: PUBLIC.TEST_TABLE
-            filters: =(C1, $cor0.RENAMED_C1)
-            fields: [C1]
+            predicate: =(C1, $cor0.RENAMED_C1)
+            fieldNames: [C1]
             est: (rows=1)
 
 # Complex query where expression involving a column of source table is renamed, and this renamed expression is referenced by
@@ -295,33 +296,34 @@ SELECT
   FROM (SELECT c1, c1 * 2 as doubled_c1 FROM test_table) as t
 ----
 Project
-    fields: [EXPR$0]
-    exprs: [EXPR$0]
+    fieldNames: [EXPR$0]
+    projection: [EXPR$0]
     est: (rows=1)
   CorrelatedNestedLoopJoin
-      condition: true
-      joinType: left
-      variablesSet: [$cor0]
+      predicate: true
+      type: left
+      correlates: [$cor0]
       est: (rows=1)
     Exchange
         distribution: single
         est: (rows=1)
       TableScan
           table: PUBLIC.TEST_TABLE
-          fields: [DOUBLED_C1]
-          projects: [*(C1, 2)]
+          fieldNames: [DOUBLED_C1]
+          projection: [*(C1, 2)]
           est: (rows=1)
     ColocatedHashAggregate
+        fieldNames: [EXPR$0]
         group: []
-        aggs: [EXPR$0=COUNT()]
+        aggregation: [COUNT()]
         est: (rows=1)
       Exchange
           distribution: single
           est: (rows=1)
         TableScan
             table: PUBLIC.TEST_TABLE
-            filters: =(C1, $cor0.DOUBLED_C1)
-            fields: [C1]
+            predicate: =(C1, $cor0.DOUBLED_C1)
+            fieldNames: [C1]
             est: (rows=1)
 
 # table scan with simple predicate
@@ -333,9 +335,8 @@ Exchange
     est: (rows=1)
   TableScan
       table: PUBLIC.TEST_TABLE
-      filters: =(C3, 1)
-      fields: [C1]
-      projects: [C1]
+      predicate: =(C3, 1)
+      fieldNames: [C1]
       est: (rows=1)
 
 # table scan with complex predicate
@@ -347,9 +348,8 @@ Exchange
     est: (rows=1)
   TableScan
       table: PUBLIC.TEST_TABLE
-      filters: AND(SEARCH(C3, Sarg[1, 2, 3]), >(CAST(CURRENT_TIMESTAMP):TIMESTAMP(0) NOT NULL, 1970-01-01 00:00:00))
-      fields: [C1]
-      projects: [C1]
+      predicate: AND(SEARCH(C3, Sarg[1, 2, 3]), >(CAST(CURRENT_TIMESTAMP):TIMESTAMP(0) NOT NULL, 1970-01-01 00:00:00))
+      fieldNames: [C1]
       est: (rows=1)
 
 # index scan with simple predicate
@@ -363,9 +363,9 @@ Exchange
       table: PUBLIC.TEST_TABLE
       index: TEST_TABLE_IDX
       type: SORTED
+      predicate: >(C1, 1)
       searchBounds: [RangeBounds [lowerBound=1, upperBound=null:INTEGER, lowerInclude=false, upperInclude=false]]
-      filters: >(C1, 1)
-      fields: [C1]
+      fieldNames: [C1]
       collation: [C1 ASC]
       est: (rows=1)
 
@@ -380,9 +380,9 @@ Exchange
       table: PUBLIC.TEST_TABLE
       index: TEST_TABLE_IDX
       type: SORTED
+      predicate: AND(SEARCH(C1, Sarg[1, 2, 3]), >(CAST(CURRENT_TIMESTAMP):TIMESTAMP(0) NOT NULL, 1970-01-01 00:00:00))
       searchBounds: [MultiBounds [bounds=ArrayList [ExactBounds [bound=1], ExactBounds [bound=2], ExactBounds [bound=3]]]]
-      filters: AND(SEARCH(C1, Sarg[1, 2, 3]), >(CAST(CURRENT_TIMESTAMP):TIMESTAMP(0) NOT NULL, 1970-01-01 00:00:00))
-      fields: [C1]
+      fieldNames: [C1]
       collation: [C1 ASC]
       est: (rows=1)
 
@@ -400,10 +400,9 @@ Exchange
       table: PUBLIC.TEST_TABLE
       index: HASH_IDX
       type: HASH
+      predicate: =(C2, 10)
       searchBounds: [ExactBounds [bound=10]]
-      filters: =(C2, 10)
-      fields: [C1]
-      projects: [C1]
+      fieldNames: [C1]
       est: (rows=1)
 
 statement ok
@@ -420,7 +419,7 @@ Exchange
       table: PUBLIC.TEST_TABLE
       index: TEST_TABLE_DIFFERENT_COLLATION_IDX
       type: SORTED
-      fields: [C1, C2]
+      fieldNames: [C1, C2]
       collation: [C2 DESC NULLS LAST]
       est: (rows=1)
 
@@ -430,8 +429,8 @@ SELECT view_id, view_name FROM system.system_views WHERE view_id = 5
 ----
 SystemViewScan
     table: SYSTEM.SYSTEM_VIEWS
-    filters: =(VIEW_ID, 5)
-    fields: [VIEW_ID, VIEW_NAME]
+    predicate: =(VIEW_ID, 5)
+    fieldNames: [VIEW_ID, VIEW_NAME]
     est: (rows=33)
 
 # table scan with complex predicate
@@ -440,8 +439,8 @@ SELECT view_id, view_name FROM system.system_views WHERE view_id IN (1, 2, 3) AN
 ----
 SystemViewScan
     table: SYSTEM.SYSTEM_VIEWS
-    filters: AND(SEARCH(VIEW_ID, Sarg[1, 2, 3]), >(CAST(CURRENT_TIMESTAMP):TIMESTAMP(0) NOT NULL, 1970-01-01 00:00:00))
-    fields: [VIEW_ID, VIEW_NAME]
+    predicate: AND(SEARCH(VIEW_ID, Sarg[1, 2, 3]), >(CAST(CURRENT_TIMESTAMP):TIMESTAMP(0) NOT NULL, 1970-01-01 00:00:00))
+    fieldNames: [VIEW_ID, VIEW_NAME]
     est: (rows=38)
 
 # table function scan
@@ -449,8 +448,8 @@ plan
 SELECT * FROM system_range(1, 10)
 ----
 TableFunctionScan
+    fieldNames: [X]
     invocation: SYSTEM_RANGE(1, 10)
-    rowType: RecordType(BIGINT X)
     est: (rows=100)
 
 # another table function scan
@@ -458,6 +457,6 @@ plan
 SELECT * FROM system_range(1, LENGTH(rand_uuid()::VARCHAR))
 ----
 TableFunctionScan
+    fieldNames: [X]
     invocation: SYSTEM_RANGE(1, LENGTH(CAST(RAND_UUID()):VARCHAR CHARACTER SET "UTF-8" NOT NULL))
-    rowType: RecordType(BIGINT X)
     est: (rows=100)
diff --git a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/ExplainUtils.java b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/ExplainUtils.java
deleted file mode 100644
index 456dd6f0abc..00000000000
--- a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/ExplainUtils.java
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * 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;
-
-import java.io.PrintWriter;
-import java.io.StringWriter;
-import org.apache.calcite.rel.RelWriter;
-import org.apache.ignite.internal.sql.engine.rel.IgniteRel;
-
-/** Set of utility method used during EXPLAIN evaluation. */
-public final class ExplainUtils {
-    private ExplainUtils() {
-        throw new AssertionError("Should not be called");
-    }
-
-    /** Returns {@code true} when printer is compiling EXPLAIN output, return {@code false} otherwise (i.e. node is being serialised). */ 
-    public static boolean forExplain(RelWriter pw) {
-        return pw instanceof ExplainRelAsTextWriter;
-    }
-
-    /** Converts given relation to string representation suitable for EXPLAIN command output. */
-    public static String toString(IgniteRel rel) {
-        StringWriter sw = new StringWriter();
-        RelWriter planWriter = new ExplainRelAsTextWriter(new PrintWriter(sw));
-
-        rel.explain(planWriter);
-
-        return sw.toString();
-    }
-}
diff --git a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/KeyValueGetPlan.java b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/KeyValueGetPlan.java
index b8a290b36ef..e3b924e36d9 100644
--- a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/KeyValueGetPlan.java
+++ b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/KeyValueGetPlan.java
@@ -49,6 +49,7 @@ import org.apache.ignite.internal.sql.engine.exec.exp.SqlRowProvider;
 import org.apache.ignite.internal.sql.engine.exec.row.RowSchema;
 import org.apache.ignite.internal.sql.engine.rel.IgniteKeyValueGet;
 import org.apache.ignite.internal.sql.engine.rel.IgniteRel;
+import org.apache.ignite.internal.sql.engine.rel.explain.ExplainUtils;
 import org.apache.ignite.internal.sql.engine.schema.IgniteTable;
 import org.apache.ignite.internal.sql.engine.util.Cloner;
 import org.apache.ignite.internal.sql.engine.util.Commons;
diff --git a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/KeyValueModifyPlan.java b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/KeyValueModifyPlan.java
index b6f2585da06..e0954f6b153 100644
--- a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/KeyValueModifyPlan.java
+++ b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/KeyValueModifyPlan.java
@@ -35,6 +35,7 @@ import org.apache.ignite.internal.sql.engine.exec.UpdatableTable;
 import org.apache.ignite.internal.sql.engine.exec.exp.SqlRowProvider;
 import org.apache.ignite.internal.sql.engine.rel.IgniteKeyValueModify;
 import org.apache.ignite.internal.sql.engine.rel.IgniteRel;
+import org.apache.ignite.internal.sql.engine.rel.explain.ExplainUtils;
 import org.apache.ignite.internal.sql.engine.schema.IgniteTable;
 import org.apache.ignite.internal.sql.engine.util.Cloner;
 import org.apache.ignite.internal.sql.engine.util.Commons;
diff --git a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/MultiStepPlan.java b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/MultiStepPlan.java
index 1806e1402c5..6a27730ecd9 100644
--- a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/MultiStepPlan.java
+++ b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/MultiStepPlan.java
@@ -19,6 +19,7 @@ package org.apache.ignite.internal.sql.engine.prepare;
 
 import org.apache.ignite.internal.sql.engine.SqlQueryType;
 import org.apache.ignite.internal.sql.engine.rel.IgniteRel;
+import org.apache.ignite.internal.sql.engine.rel.explain.ExplainUtils;
 import org.apache.ignite.internal.sql.engine.util.Cloner;
 import org.apache.ignite.internal.sql.engine.util.Commons;
 import org.apache.ignite.sql.ResultSetMetadata;
diff --git a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/SelectCountPlan.java b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/SelectCountPlan.java
index d777c704882..3d489f9c52a 100644
--- a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/SelectCountPlan.java
+++ b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/SelectCountPlan.java
@@ -43,6 +43,7 @@ import org.apache.ignite.internal.sql.engine.exec.exp.SqlProjection;
 import org.apache.ignite.internal.sql.engine.exec.row.RowSchema;
 import org.apache.ignite.internal.sql.engine.rel.IgniteRel;
 import org.apache.ignite.internal.sql.engine.rel.IgniteSelectCount;
+import org.apache.ignite.internal.sql.engine.rel.explain.ExplainUtils;
 import org.apache.ignite.internal.sql.engine.schema.IgniteTable;
 import org.apache.ignite.internal.sql.engine.util.Cloner;
 import org.apache.ignite.internal.sql.engine.util.Commons;
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 b812d7d16d7..9cfe5f055a0 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
@@ -46,6 +46,7 @@ import org.apache.calcite.util.Pair;
 import org.apache.calcite.util.Util;
 import org.apache.calcite.util.mapping.IntPair;
 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.TraitUtils;
@@ -276,4 +277,11 @@ public abstract class AbstractIgniteJoin extends Join implements TraitsAwareIgni
                 (left2Right ? right : left).getRowType().getFieldCount()
         );
     }
+
+    @Override
+    public IgniteRelWriter explain(IgniteRelWriter writer) {
+        return writer
+                .addPredicate(condition, getRowType())
+                .addJoinType(joinType);
+    }
 }
diff --git a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rel/AbstractIndexScan.java b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rel/AbstractIndexScan.java
index fa16fa297cf..0dc9ae66d28 100644
--- a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rel/AbstractIndexScan.java
+++ b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rel/AbstractIndexScan.java
@@ -38,6 +38,7 @@ import org.apache.ignite.internal.sql.engine.externalize.RelInputEx;
 import org.apache.ignite.internal.sql.engine.metadata.cost.IgniteCost;
 import org.apache.ignite.internal.sql.engine.prepare.bounds.MultiBounds;
 import org.apache.ignite.internal.sql.engine.prepare.bounds.SearchBounds;
+import org.apache.ignite.internal.sql.engine.rel.explain.IgniteRelWriter;
 import org.apache.ignite.internal.sql.engine.schema.IgniteIndex;
 import org.apache.ignite.internal.sql.engine.schema.IgniteIndex.Type;
 import org.apache.ignite.internal.sql.engine.util.Commons;
@@ -184,4 +185,16 @@ public abstract class AbstractIndexScan extends ProjectableFilterableTableScan {
     public List<SearchBounds> searchBounds() {
         return searchBounds;
     }
+
+    @Override
+    public IgniteRelWriter explainAttributes(IgniteRelWriter writer) {
+        super.explainAttributes(writer)
+                .addIndex(idxName, type);
+
+        if (searchBounds != null) {
+            writer.addSearchBounds(searchBounds);
+        }
+
+        return writer;
+    }
 }
diff --git a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rel/IgniteAggregate.java b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rel/IgniteAggregate.java
index 0447d3945b3..568c089deff 100644
--- a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rel/IgniteAggregate.java
+++ b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rel/IgniteAggregate.java
@@ -29,9 +29,11 @@ import org.apache.calcite.rel.RelNode;
 import org.apache.calcite.rel.core.Aggregate;
 import org.apache.calcite.rel.core.AggregateCall;
 import org.apache.calcite.rel.metadata.RelMetadataQuery;
+import org.apache.calcite.rel.type.RelDataType;
 import org.apache.calcite.util.ImmutableBitSet;
 import org.apache.ignite.internal.sql.engine.metadata.cost.IgniteCost;
 import org.apache.ignite.internal.sql.engine.metadata.cost.IgniteCostFactory;
+import org.apache.ignite.internal.sql.engine.rel.explain.IgniteRelWriter;
 
 /**
  * IgniteAggregate.
@@ -140,4 +142,19 @@ public abstract class IgniteAggregate extends Aggregate implements IgniteRel {
                 0
         );
     }
+
+    @Override
+    public IgniteRelWriter explain(IgniteRelWriter writer) {
+        RelDataType rowType = getInput().getRowType();
+
+        writer
+                .addGroup(groupSet, rowType)
+                .addAggregation(aggCalls, rowType);
+
+        if (getGroupType() != Group.SIMPLE) {
+            writer.addGroupSets(groupSets, rowType);
+        }
+
+        return writer;
+    }
 }
diff --git a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rel/IgniteCorrelatedNestedLoopJoin.java b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rel/IgniteCorrelatedNestedLoopJoin.java
index 58aa8ea7b8f..4a7246eba39 100644
--- a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rel/IgniteCorrelatedNestedLoopJoin.java
+++ b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rel/IgniteCorrelatedNestedLoopJoin.java
@@ -38,6 +38,7 @@ import org.apache.calcite.rex.RexNode;
 import org.apache.calcite.util.Pair;
 import org.apache.ignite.internal.sql.engine.metadata.cost.IgniteCost;
 import org.apache.ignite.internal.sql.engine.metadata.cost.IgniteCostFactory;
+import org.apache.ignite.internal.sql.engine.rel.explain.IgniteRelWriter;
 import org.apache.ignite.internal.sql.engine.trait.TraitUtils;
 import org.apache.ignite.internal.sql.engine.util.Commons;
 
@@ -189,4 +190,9 @@ public class IgniteCorrelatedNestedLoopJoin extends AbstractIgniteJoin {
     public String getRelTypeName() {
         return REL_TYPE_NAME;
     }
+
+    @Override
+    public IgniteRelWriter explain(IgniteRelWriter writer) {
+        return super.explain(writer).addCorrelatedVariables(variablesSet);
+    }
 }
diff --git a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rel/IgniteExchange.java b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rel/IgniteExchange.java
index a9797bf6bd6..5b07686175d 100644
--- a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rel/IgniteExchange.java
+++ b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rel/IgniteExchange.java
@@ -32,6 +32,7 @@ import org.apache.calcite.rel.core.Exchange;
 import org.apache.calcite.rel.metadata.RelMetadataQuery;
 import org.apache.ignite.internal.sql.engine.metadata.cost.IgniteCost;
 import org.apache.ignite.internal.sql.engine.metadata.cost.IgniteCostFactory;
+import org.apache.ignite.internal.sql.engine.rel.explain.IgniteRelWriter;
 import org.apache.ignite.internal.sql.engine.trait.IgniteDistribution;
 
 /**
@@ -111,4 +112,9 @@ public class IgniteExchange extends Exchange implements IgniteRel {
     public String getRelTypeName() {
         return REL_TYPE_NAME;
     }
+
+    @Override
+    public IgniteRelWriter explain(IgniteRelWriter writer) {
+        return writer.addDistribution(distribution(), getRowType());
+    }
 }
diff --git a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rel/IgniteFilter.java b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rel/IgniteFilter.java
index 93fa7c34b3d..00f060cedad 100644
--- a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rel/IgniteFilter.java
+++ b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rel/IgniteFilter.java
@@ -31,6 +31,7 @@ import org.apache.calcite.rel.metadata.RelMetadataQuery;
 import org.apache.calcite.rex.RexNode;
 import org.apache.calcite.util.Pair;
 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.TraitUtils;
 import org.apache.ignite.internal.sql.engine.trait.TraitsAwareIgniteRel;
 
@@ -112,4 +113,9 @@ public class IgniteFilter extends Filter implements TraitsAwareIgniteRel {
     @Override public String getRelTypeName() {
         return REL_TYPE_NAME;
     }
+
+    @Override
+    public IgniteRelWriter explain(IgniteRelWriter writer) {
+        return writer.addPredicate(condition, getInput().getRowType());
+    }
 }
diff --git a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rel/IgniteIndexScan.java b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rel/IgniteIndexScan.java
index 185eaf2e278..2d8f1075ca9 100644
--- a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rel/IgniteIndexScan.java
+++ b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rel/IgniteIndexScan.java
@@ -29,6 +29,7 @@ import org.apache.calcite.rel.RelWriter;
 import org.apache.calcite.rex.RexNode;
 import org.apache.calcite.util.ImmutableBitSet;
 import org.apache.ignite.internal.sql.engine.prepare.bounds.SearchBounds;
+import org.apache.ignite.internal.sql.engine.rel.explain.IgniteRelWriter;
 import org.apache.ignite.internal.sql.engine.schema.IgniteIndex;
 import org.apache.ignite.internal.sql.engine.schema.IgniteIndex.Type;
 import org.jetbrains.annotations.Nullable;
@@ -184,4 +185,15 @@ public class IgniteIndexScan extends AbstractIndexScan implements SourceAwareIgn
     public String getRelTypeName() {
         return REL_TYPE_NAME;
     }
+
+    @Override
+    public IgniteRelWriter explain(IgniteRelWriter writer) {
+        explainAttributes(writer);
+
+        if (type == Type.SORTED) {
+            writer.addCollation(collation, getRowType());
+        }
+
+        return writer;
+    }
 }
diff --git a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rel/IgniteKeyValueGet.java b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rel/IgniteKeyValueGet.java
index 0bbf4d84678..9ab1de2f56d 100644
--- a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rel/IgniteKeyValueGet.java
+++ b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rel/IgniteKeyValueGet.java
@@ -28,6 +28,7 @@ import org.apache.calcite.rex.RexNode;
 import org.apache.calcite.util.ImmutableBitSet;
 import org.apache.ignite.internal.sql.engine.exec.TxAttributes;
 import org.apache.ignite.internal.sql.engine.exec.mapping.MappingService;
+import org.apache.ignite.internal.sql.engine.rel.explain.IgniteRelWriter;
 import org.apache.ignite.internal.tx.InternalTransaction;
 import org.jetbrains.annotations.Nullable;
 
@@ -129,4 +130,10 @@ public class IgniteKeyValueGet extends ProjectableFilterableTableScan implements
     public List<RexNode> keyExpressions() {
         return keyExpressions;
     }
+
+    @Override
+    public IgniteRelWriter explain(IgniteRelWriter writer) {
+        return explainAttributes(writer)
+                .addKeyExpression(keyExpressions);
+    }
 }
diff --git a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rel/IgniteKeyValueModify.java b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rel/IgniteKeyValueModify.java
index 1f1d163ce5b..67dfbcea1b6 100644
--- a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rel/IgniteKeyValueModify.java
+++ b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rel/IgniteKeyValueModify.java
@@ -17,8 +17,6 @@
 
 package org.apache.ignite.internal.sql.engine.rel;
 
-import static org.apache.ignite.internal.sql.engine.prepare.ExplainUtils.forExplain;
-
 import java.util.List;
 import org.apache.calcite.plan.RelOptCluster;
 import org.apache.calcite.plan.RelOptTable;
@@ -28,12 +26,14 @@ import org.apache.calcite.rel.AbstractRelNode;
 import org.apache.calcite.rel.RelInput;
 import org.apache.calcite.rel.RelNode;
 import org.apache.calcite.rel.RelWriter;
+import org.apache.calcite.rel.core.TableModify;
 import org.apache.calcite.rel.type.RelDataType;
 import org.apache.calcite.rex.RexNode;
 import org.apache.calcite.rex.RexShuttle;
 import org.apache.calcite.sql.SqlKind;
 import org.apache.ignite.internal.sql.engine.exec.TxAttributes;
 import org.apache.ignite.internal.sql.engine.exec.mapping.MappingService;
+import org.apache.ignite.internal.sql.engine.rel.explain.IgniteRelWriter;
 import org.apache.ignite.internal.tx.InternalTransaction;
 
 /**
@@ -56,7 +56,13 @@ public class IgniteKeyValueModify extends AbstractRelNode implements IgniteRel {
 
     /** Enumeration of supported modification operations. */
     public enum Operation {
-        PUT
+        PUT(TableModify.Operation.INSERT);
+
+        private final TableModify.Operation op;
+
+        Operation(TableModify.Operation op) {
+            this.op = op;
+        }
     }
 
     private final RelOptTable table;
@@ -125,8 +131,7 @@ public class IgniteKeyValueModify extends AbstractRelNode implements IgniteRel {
 
     @Override public RelWriter explainTerms(RelWriter pw) {
         return super.explainTerms(pw)
-                .itemIf("table", table.getQualifiedName(), !forExplain(pw))
-                .itemIf("table", table, forExplain(pw))
+                .item("table", table.getQualifiedName())
                 .item("operation", operation)
                 .item("expressions", expressions);
     }
@@ -146,4 +151,12 @@ public class IgniteKeyValueModify extends AbstractRelNode implements IgniteRel {
     public List<RexNode> expressions() {
         return expressions;
     }
+
+    @Override
+    public IgniteRelWriter explain(IgniteRelWriter writer) {
+        return writer
+                .addTable(table)
+                .addSourceExpressions(expressions)
+                .addModifyOperationType(operation.op);
+    }
 }
diff --git a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rel/IgniteLimit.java b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rel/IgniteLimit.java
index 60b1ec07a6b..55283d9df8b 100644
--- a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rel/IgniteLimit.java
+++ b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rel/IgniteLimit.java
@@ -36,6 +36,7 @@ import org.apache.calcite.rex.RexNode;
 import org.apache.calcite.rex.RexShuttle;
 import org.apache.calcite.util.Pair;
 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.IgniteDistributions;
 import org.apache.ignite.internal.sql.engine.trait.TraitUtils;
 import org.jetbrains.annotations.Nullable;
@@ -208,4 +209,17 @@ public class IgniteLimit extends SingleRel implements IgniteRel {
     public String getRelTypeName() {
         return REL_TYPE_NAME;
     }
+
+    @Override
+    public IgniteRelWriter explain(IgniteRelWriter writer) {
+        if (offset != null) {
+            writer.addOffset(offset);
+        }
+
+        if (fetch != null) {
+            writer.addFetch(fetch);
+        }
+
+        return writer;
+    }
 }
diff --git a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rel/IgniteMergeJoin.java b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rel/IgniteMergeJoin.java
index ab23020b95a..aeea60471e0 100644
--- a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rel/IgniteMergeJoin.java
+++ b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rel/IgniteMergeJoin.java
@@ -51,7 +51,6 @@ import org.apache.calcite.util.mapping.IntPair;
 import org.apache.ignite.internal.sql.engine.externalize.RelInputEx;
 import org.apache.ignite.internal.sql.engine.metadata.cost.IgniteCost;
 import org.apache.ignite.internal.sql.engine.metadata.cost.IgniteCostFactory;
-import org.apache.ignite.internal.sql.engine.prepare.ExplainUtils;
 import org.apache.ignite.internal.sql.engine.trait.TraitUtils;
 import org.apache.ignite.internal.sql.engine.util.Commons;
 
@@ -302,8 +301,8 @@ public class IgniteMergeJoin extends AbstractIgniteJoin {
     @Override
     public RelWriter explainTerms(RelWriter pw) {
         return super.explainTerms(pw)
-                .itemIf("leftCollation", leftCollation, !ExplainUtils.forExplain(pw))
-                .itemIf("rightCollation", rightCollation, !ExplainUtils.forExplain(pw));
+                .item("leftCollation", leftCollation)
+                .item("rightCollation", rightCollation);
     }
 
     /** {@inheritDoc} */
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 1671d38c2b0..0232cd8f47f 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
@@ -52,6 +52,7 @@ import org.apache.calcite.util.Pair;
 import org.apache.calcite.util.Util;
 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.TraitUtils;
 import org.apache.ignite.internal.sql.engine.trait.TraitsAwareIgniteRel;
@@ -251,4 +252,9 @@ public class IgniteProject extends Project implements TraitsAwareIgniteRel {
     public String getRelTypeName() {
         return REL_TYPE_NAME;
     }
+
+    @Override
+    public IgniteRelWriter explain(IgniteRelWriter writer) {
+        return writer.addProjection(exps, getInput().getRowType());
+    }
 }
diff --git a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rel/IgniteRel.java b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rel/IgniteRel.java
index 79080bc93ed..5f780967aa0 100644
--- a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rel/IgniteRel.java
+++ b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rel/IgniteRel.java
@@ -23,6 +23,7 @@ import org.apache.calcite.plan.RelTraitSet;
 import org.apache.calcite.rel.PhysicalNode;
 import org.apache.calcite.rel.RelCollation;
 import org.apache.calcite.util.Pair;
+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.TraitUtils;
 
@@ -46,6 +47,18 @@ public interface IgniteRel extends PhysicalNode {
      */
     IgniteRel clone(RelOptCluster cluster, List<IgniteRel> inputs);
 
+    /**
+     * Writes attributes of the relation to a given {@link IgniteRelWriter writer}.
+     *
+     * <p>Every rel should include their own attributes while inputs will be processed from outside.
+     *
+     * @param writer Writer to write relation attributes.
+     * @return The same write for chaining.
+     */
+    default IgniteRelWriter explain(IgniteRelWriter writer) {
+        return writer;
+    }
+
     /**
      * Get node distribution.
      */
diff --git a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rel/IgniteSelectCount.java b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rel/IgniteSelectCount.java
index d16d5923cad..29cf8522736 100644
--- a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rel/IgniteSelectCount.java
+++ b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rel/IgniteSelectCount.java
@@ -29,11 +29,14 @@ import org.apache.calcite.rel.RelNode;
 import org.apache.calcite.rel.RelWriter;
 import org.apache.calcite.rel.metadata.RelMetadataQuery;
 import org.apache.calcite.rel.type.RelDataType;
+import org.apache.calcite.rel.type.RelDataTypeFactory;
 import org.apache.calcite.rex.RexNode;
 import org.apache.calcite.rex.RexShuttle;
 import org.apache.calcite.rex.RexUtil;
+import org.apache.calcite.sql.type.SqlTypeName;
 import org.apache.ignite.internal.sql.engine.exec.mapping.MappingService;
 import org.apache.ignite.internal.sql.engine.metadata.cost.IgniteCostFactory;
+import org.apache.ignite.internal.sql.engine.rel.explain.IgniteRelWriter;
 import org.apache.ignite.internal.sql.engine.util.Commons;
 import org.checkerframework.checker.nullness.qual.Nullable;
 
@@ -152,4 +155,17 @@ public class IgniteSelectCount extends AbstractRelNode implements IgniteRel {
 
         return costFactory.makeTinyCost();
     }
+
+    @Override
+    public IgniteRelWriter explain(IgniteRelWriter writer) {
+        RelDataTypeFactory typeFactory = getCluster().getTypeFactory();
+
+        RelDataType rowType = typeFactory.builder()
+                .add("$COUNT_ALL", typeFactory.createSqlType(SqlTypeName.BIGINT))
+                .build();
+
+        return writer
+                .addTable(table)
+                .addProjection(expressions, rowType);
+    }
 }
diff --git a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rel/IgniteSort.java b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rel/IgniteSort.java
index ba30ebdae83..96caf9227af 100644
--- a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rel/IgniteSort.java
+++ b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rel/IgniteSort.java
@@ -38,6 +38,7 @@ import org.apache.calcite.util.Pair;
 import org.apache.calcite.util.Util;
 import org.apache.ignite.internal.sql.engine.metadata.cost.IgniteCost;
 import org.apache.ignite.internal.sql.engine.metadata.cost.IgniteCostFactory;
+import org.apache.ignite.internal.sql.engine.rel.explain.IgniteRelWriter;
 import org.apache.ignite.internal.sql.engine.trait.IgniteDistributions;
 import org.apache.ignite.internal.sql.engine.trait.TraitUtils;
 import org.jetbrains.annotations.Nullable;
@@ -197,4 +198,17 @@ public class IgniteSort extends Sort implements IgniteRel {
     public String getRelTypeName() {
         return REL_TYPE_NAME;
     }
+
+    @Override
+    public IgniteRelWriter explain(IgniteRelWriter writer) {
+        if (offset != null) {
+            writer.addOffset(offset);
+        }
+
+        if (fetch != null) {
+            writer.addFetch(fetch);
+        }
+
+        return writer.addCollation(collation, getRowType());
+    }
 }
diff --git a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rel/IgniteSystemViewScan.java b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rel/IgniteSystemViewScan.java
index 5c516cb580d..94ce468aa92 100644
--- a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rel/IgniteSystemViewScan.java
+++ b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rel/IgniteSystemViewScan.java
@@ -28,6 +28,7 @@ import org.apache.calcite.rel.RelWriter;
 import org.apache.calcite.rel.hint.RelHint;
 import org.apache.calcite.rex.RexNode;
 import org.apache.calcite.util.ImmutableBitSet;
+import org.apache.ignite.internal.sql.engine.rel.explain.IgniteRelWriter;
 import org.jetbrains.annotations.Nullable;
 
 /**
@@ -143,4 +144,10 @@ public class IgniteSystemViewScan extends ProjectableFilterableTableScan impleme
     public String getRelTypeName() {
         return REL_TYPE_NAME;
     }
+
+    @Override
+    public IgniteRelWriter explain(
+            IgniteRelWriter writer) {
+        return explainAttributes(writer);
+    }
 }
diff --git a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rel/IgniteTableFunctionScan.java b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rel/IgniteTableFunctionScan.java
index e6a9a841673..1376d53d231 100644
--- a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rel/IgniteTableFunctionScan.java
+++ b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rel/IgniteTableFunctionScan.java
@@ -34,6 +34,7 @@ import org.apache.calcite.rel.metadata.RelMetadataQuery;
 import org.apache.calcite.rel.type.RelDataType;
 import org.apache.calcite.rex.RexNode;
 import org.apache.ignite.internal.sql.engine.exec.mapping.QuerySplitter;
+import org.apache.ignite.internal.sql.engine.rel.explain.IgniteRelWriter;
 
 /**
  * Relational operator for table function scan.
@@ -148,4 +149,9 @@ public class IgniteTableFunctionScan extends TableFunctionScan implements Source
     public String getRelTypeName() {
         return REL_TYPE_NAME;
     }
+
+    @Override
+    public IgniteRelWriter explain(IgniteRelWriter writer) {
+        return writer.addInvocation(getCall());
+    }
 }
diff --git a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rel/IgniteTableModify.java b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rel/IgniteTableModify.java
index b1957d98959..26b65da4be3 100644
--- a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rel/IgniteTableModify.java
+++ b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rel/IgniteTableModify.java
@@ -17,8 +17,6 @@
 
 package org.apache.ignite.internal.sql.engine.rel;
 
-import static org.apache.ignite.internal.sql.engine.prepare.ExplainUtils.forExplain;
-
 import java.util.List;
 import org.apache.calcite.plan.RelOptCluster;
 import org.apache.calcite.plan.RelOptTable;
@@ -29,6 +27,7 @@ import org.apache.calcite.rel.RelWriter;
 import org.apache.calcite.rel.core.TableModify;
 import org.apache.calcite.rex.RexNode;
 import org.apache.calcite.rex.RexShuttle;
+import org.apache.ignite.internal.sql.engine.rel.explain.IgniteRelWriter;
 import org.apache.ignite.internal.sql.engine.schema.IgniteTable;
 import org.apache.ignite.internal.sql.engine.util.Commons;
 import org.jetbrains.annotations.Nullable;
@@ -198,7 +197,7 @@ public class IgniteTableModify extends TableModify implements SourceAwareIgniteR
     public RelWriter explainTerms(RelWriter pw) {
         // for correct rel obtaining from ExecutionServiceImpl#physNodesCache.
         return super.explainTerms(pw)
-                .itemIf("tableId", Integer.toString(getTable().unwrap(IgniteTable.class).id()), !forExplain(pw))
+                .item("tableId", Integer.toString(getTable().unwrap(IgniteTable.class).id()))
                 .itemIf("sourceId", sourceId, sourceId != -1);
 
     }
@@ -214,4 +213,11 @@ public class IgniteTableModify extends TableModify implements SourceAwareIgniteR
     public String getRelTypeName() {
         return REL_TYPE_NAME;
     }
+
+    @Override
+    public IgniteRelWriter explain(IgniteRelWriter writer) {
+        return writer
+                .addTable(table)
+                .addModifyOperationType(getOperation());
+    }
 }
diff --git a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rel/IgniteTableScan.java b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rel/IgniteTableScan.java
index 180cdd13c0a..6991a31f9e5 100644
--- a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rel/IgniteTableScan.java
+++ b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rel/IgniteTableScan.java
@@ -28,6 +28,7 @@ import org.apache.calcite.rel.RelWriter;
 import org.apache.calcite.rel.hint.RelHint;
 import org.apache.calcite.rex.RexNode;
 import org.apache.calcite.util.ImmutableBitSet;
+import org.apache.ignite.internal.sql.engine.rel.explain.IgniteRelWriter;
 import org.jetbrains.annotations.Nullable;
 
 /**
@@ -173,4 +174,10 @@ public class IgniteTableScan extends ProjectableFilterableTableScan implements S
     @Override public String getRelTypeName() {
         return REL_TYPE_NAME;
     }
+
+    @Override
+    public IgniteRelWriter explain(
+            IgniteRelWriter writer) {
+        return explainAttributes(writer);
+    }
 }
diff --git a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rel/IgniteTrimExchange.java b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rel/IgniteTrimExchange.java
index 2eabf8735a9..42e8cab3fe7 100644
--- a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rel/IgniteTrimExchange.java
+++ b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rel/IgniteTrimExchange.java
@@ -32,6 +32,7 @@ import org.apache.calcite.rel.RelWriter;
 import org.apache.calcite.rel.core.Exchange;
 import org.apache.calcite.rel.metadata.RelMetadataQuery;
 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;
 
 /**
@@ -143,4 +144,9 @@ public class IgniteTrimExchange extends Exchange implements SourceAwareIgniteRel
     public String getRelTypeName() {
         return REL_TYPE_NAME;
     }
+
+    @Override
+    public IgniteRelWriter explain(IgniteRelWriter writer) {
+        return writer.addDistribution(distribution(), getRowType());
+    }
 }
diff --git a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rel/IgniteValues.java b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rel/IgniteValues.java
index d3a7ee56618..e301b0b02ea 100644
--- a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rel/IgniteValues.java
+++ b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rel/IgniteValues.java
@@ -30,6 +30,8 @@ import org.apache.calcite.rel.core.Values;
 import org.apache.calcite.rel.type.RelDataType;
 import org.apache.calcite.rex.RexLiteral;
 import org.apache.ignite.internal.sql.engine.exec.mapping.QuerySplitter;
+import org.apache.ignite.internal.sql.engine.rel.explain.IgniteRelWriter;
+import org.apache.ignite.internal.sql.engine.util.Commons;
 
 /**
  * IgniteValues.
@@ -138,4 +140,9 @@ public class IgniteValues extends Values implements SourceAwareIgniteRel {
     public String getRelTypeName() {
         return REL_TYPE_NAME;
     }
+
+    @Override
+    public IgniteRelWriter explain(IgniteRelWriter writer) {
+        return writer.addTuples(Commons.cast(tuples));
+    }
 }
diff --git a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rel/ProjectableFilterableTableScan.java b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rel/ProjectableFilterableTableScan.java
index a5d25e3819c..7956b24ae05 100644
--- a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rel/ProjectableFilterableTableScan.java
+++ b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rel/ProjectableFilterableTableScan.java
@@ -18,7 +18,6 @@
 package org.apache.ignite.internal.sql.engine.rel;
 
 import static org.apache.calcite.sql.validate.SqlValidatorUtil.ATTEMPT_SUGGESTER;
-import static org.apache.ignite.internal.sql.engine.prepare.ExplainUtils.forExplain;
 import static org.apache.ignite.internal.sql.engine.util.RexUtils.builder;
 import static org.apache.ignite.internal.sql.engine.util.RexUtils.replaceLocalRefs;
 
@@ -43,10 +42,12 @@ import org.apache.calcite.rex.RexLocalRef;
 import org.apache.calcite.rex.RexNode;
 import org.apache.calcite.rex.RexShuttle;
 import org.apache.calcite.rex.RexUtil;
+import org.apache.calcite.sql.SqlKind;
 import org.apache.calcite.util.ControlFlowException;
 import org.apache.calcite.util.ImmutableBitSet;
 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.schema.IgniteDataSource;
 import org.apache.ignite.internal.sql.engine.schema.IgniteTable;
 import org.apache.ignite.internal.sql.engine.type.IgniteTypeFactory;
@@ -133,9 +134,8 @@ public abstract class ProjectableFilterableTableScan extends TableScan {
     @Override
     public RelWriter explainTerms(RelWriter pw) {
         return explainTerms0(pw
-                .itemIf("table", table.getQualifiedName(), !forExplain(pw))
-                .itemIf("table", table, forExplain(pw))
-                .itemIf("tableId", Integer.toString(table.unwrap(IgniteDataSource.class).id()), !forExplain(pw)));
+                .item("table", table.getQualifiedName())
+                .item("tableId", Integer.toString(table.unwrap(IgniteDataSource.class).id())));
     }
 
     /** {@inheritDoc} */
@@ -157,10 +157,9 @@ public abstract class ProjectableFilterableTableScan extends TableScan {
                     RexUtil.expandSearch(getCluster().getRexBuilder(), null, condition));
         }
 
-        return pw.itemIf("fields", getRowType().getFieldNames(), forExplain(pw))
-                .itemIf("names", names, names != null && !forExplain(pw))
+        return pw.itemIf("names", names, names != null)
                 .itemIf("projects", projects, projects != null)
-                .itemIf("requiredColumns", requiredColumns, !forExplain(pw) && requiredColumns != null);
+                .itemIf("requiredColumns", requiredColumns, requiredColumns != null);
     }
 
     /** {@inheritDoc} */
@@ -239,4 +238,30 @@ public abstract class ProjectableFilterableTableScan extends TableScan {
 
         return RexUtil.composeConjunction(builder(getCluster()), conjunctions, true);
     }
+
+    protected IgniteRelWriter explainAttributes(IgniteRelWriter writer) {
+        writer.addTable(table);
+
+        if (condition != null || projects != null) {
+            RelDataType rowType = getTable().getRowType();
+            if (requiredColumns != null && requiredColumns.cardinality() < rowType.getFieldCount()) {
+                RelDataTypeFactory tf = getCluster().getTypeFactory();
+                IgniteDataSource dataSource = getTable().unwrap(IgniteDataSource.class);
+
+                assert dataSource != null;
+
+                rowType = dataSource.getRowType(tf, requiredColumns);
+            }
+
+            if (condition != null) {
+                writer.addPredicate(condition, rowType);
+            }
+
+            if (projects != null && projects.stream().anyMatch(p -> !p.isA(SqlKind.LOCAL_REF))) {
+                writer.addProjection(projects, rowType);
+            }
+        }
+
+        return writer;
+    }
 }
diff --git a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rel/agg/IgniteColocatedSortAggregate.java b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rel/agg/IgniteColocatedSortAggregate.java
index e4b54340917..05ae5bb3bb2 100644
--- a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rel/agg/IgniteColocatedSortAggregate.java
+++ b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rel/agg/IgniteColocatedSortAggregate.java
@@ -33,6 +33,7 @@ import org.apache.calcite.rel.metadata.RelMetadataQuery;
 import org.apache.calcite.util.ImmutableBitSet;
 import org.apache.ignite.internal.sql.engine.rel.IgniteRel;
 import org.apache.ignite.internal.sql.engine.rel.IgniteRelVisitor;
+import org.apache.ignite.internal.sql.engine.rel.explain.IgniteRelWriter;
 import org.apache.ignite.internal.sql.engine.trait.TraitUtils;
 
 /**
@@ -117,4 +118,9 @@ public class IgniteColocatedSortAggregate extends IgniteColocatedAggregateBase i
     @Override public String getRelTypeName() {
         return REL_TYPE_NAME;
     }
+
+    @Override
+    public IgniteRelWriter explain(IgniteRelWriter writer) {
+        return super.explain(writer).addCollation(collation, getRowType());
+    }
 }
diff --git a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rel/agg/IgniteMapSortAggregate.java b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rel/agg/IgniteMapSortAggregate.java
index 5d456773129..430c1f6a13e 100644
--- a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rel/agg/IgniteMapSortAggregate.java
+++ b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rel/agg/IgniteMapSortAggregate.java
@@ -35,6 +35,7 @@ import org.apache.calcite.util.ImmutableBitSet;
 import org.apache.ignite.internal.sql.engine.rel.IgniteConvention;
 import org.apache.ignite.internal.sql.engine.rel.IgniteRel;
 import org.apache.ignite.internal.sql.engine.rel.IgniteRelVisitor;
+import org.apache.ignite.internal.sql.engine.rel.explain.IgniteRelWriter;
 import org.apache.ignite.internal.sql.engine.trait.TraitUtils;
 import org.apache.ignite.internal.sql.engine.type.IgniteTypeFactory;
 import org.apache.ignite.internal.sql.engine.util.Commons;
@@ -145,4 +146,9 @@ public class IgniteMapSortAggregate extends IgniteMapAggregateBase implements Ig
     @Override public String getRelTypeName() {
         return REL_TYPE_NAME;
     }
+
+    @Override
+    public IgniteRelWriter explain(IgniteRelWriter writer) {
+        return super.explain(writer).addCollation(collation, getRowType());
+    }
 }
diff --git a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rel/agg/IgniteReduceAggregateBase.java b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rel/agg/IgniteReduceAggregateBase.java
index c9fd91ded94..adb9e4ae22b 100644
--- a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rel/agg/IgniteReduceAggregateBase.java
+++ b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rel/agg/IgniteReduceAggregateBase.java
@@ -17,8 +17,6 @@
 
 package org.apache.ignite.internal.sql.engine.rel.agg;
 
-import static org.apache.ignite.internal.sql.engine.prepare.ExplainUtils.forExplain;
-
 import java.util.List;
 import org.apache.calcite.linq4j.Ord;
 import org.apache.calcite.plan.RelOptCluster;
@@ -36,6 +34,7 @@ import org.apache.calcite.util.ImmutableBitSet;
 import org.apache.calcite.util.Pair;
 import org.apache.calcite.util.Util;
 import org.apache.ignite.internal.sql.engine.rel.IgniteConvention;
+import org.apache.ignite.internal.sql.engine.rel.explain.IgniteRelWriter;
 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;
@@ -106,7 +105,7 @@ public abstract class IgniteReduceAggregateBase extends SingleRel implements Tra
                 .itemIf("rowType", rowType, pw.getDetailLevel() == SqlExplainLevel.ALL_ATTRIBUTES)
                 .item("group", groupSet)
                 .itemIf("groups", groupSets, Group.induce(groupSet, groupSets) != Group.SIMPLE)
-                .itemIf("aggs", aggCalls, pw.nest() && (!forExplain(pw) || !aggCalls.isEmpty()));
+                .itemIf("aggs", aggCalls, pw.nest());
 
         if (!pw.nest()) {
             for (Ord<AggregateCall> ord : Ord.zip(aggCalls)) {
@@ -175,4 +174,23 @@ public abstract class IgniteReduceAggregateBase extends SingleRel implements Tra
         // cluster parallelism at the model (devide source rows by nodes for partitioned data).
         return mq.getRowCount(getInput());
     }
+
+    @Override
+    public IgniteRelWriter explain(IgniteRelWriter writer) {
+        RelDataType rowType = getInput().getRowType();
+
+        writer
+                .addGroup(groupSet, rowType)
+                .addAggregation(aggCalls, rowType);
+
+        if (getGroupType() != Group.SIMPLE) {
+            writer.addGroupSets(groupSets, rowType);
+        }
+
+        return writer;
+    }
+
+    public Group getGroupType() {
+        return Group.induce(groupSet, groupSets);
+    }
 }
diff --git a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rel/agg/IgniteReduceSortAggregate.java b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rel/agg/IgniteReduceSortAggregate.java
index 094eec595f0..4ead13badeb 100644
--- a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rel/agg/IgniteReduceSortAggregate.java
+++ b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rel/agg/IgniteReduceSortAggregate.java
@@ -35,6 +35,7 @@ import org.apache.ignite.internal.sql.engine.metadata.cost.IgniteCost;
 import org.apache.ignite.internal.sql.engine.metadata.cost.IgniteCostFactory;
 import org.apache.ignite.internal.sql.engine.rel.IgniteRel;
 import org.apache.ignite.internal.sql.engine.rel.IgniteRelVisitor;
+import org.apache.ignite.internal.sql.engine.rel.explain.IgniteRelWriter;
 import org.apache.ignite.internal.sql.engine.trait.TraitUtils;
 
 /**
@@ -147,4 +148,9 @@ public class IgniteReduceSortAggregate extends IgniteReduceAggregateBase impleme
     public String getRelTypeName() {
         return REL_TYPE_NAME;
     }
+
+    @Override
+    public IgniteRelWriter explain(IgniteRelWriter writer) {
+        return super.explain(writer).addCollation(collation, getRowType());
+    }
 }
diff --git a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rel/explain/ExplainUtils.java b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rel/explain/ExplainUtils.java
new file mode 100644
index 00000000000..06b6144ed45
--- /dev/null
+++ b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rel/explain/ExplainUtils.java
@@ -0,0 +1,83 @@
+/*
+ * 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.rel.explain;
+
+import org.apache.calcite.rel.type.RelDataType;
+import org.apache.calcite.rex.RexBiVisitor;
+import org.apache.calcite.rex.RexInputRef;
+import org.apache.calcite.rex.RexLocalRef;
+import org.apache.calcite.rex.RexNode;
+import org.apache.calcite.rex.RexShuttle;
+import org.apache.calcite.rex.RexSlot;
+import org.apache.calcite.rex.RexVisitor;
+import org.apache.ignite.internal.sql.engine.rel.IgniteRel;
+import org.jetbrains.annotations.Nullable;
+
+/** Set of utility method used during EXPLAIN evaluation. */
+public final class ExplainUtils {
+    private ExplainUtils() {
+        throw new AssertionError("Should not be called");
+    }
+
+    /** Converts given relation to string representation suitable for EXPLAIN command output. */
+    public static String toString(IgniteRel rel) {
+        return RelTreeToTextWriter.dumpTree(rel);
+    }
+
+    static RexShuttle inputRefRewriter(RelDataType rowType) {
+        return new RexShuttle() {
+            @Override
+            public RexNode visitLocalRef(RexLocalRef ref) {
+                return new NamedRexSlot(rowType.getFieldNames().get(ref.getIndex()), ref.getIndex(), ref.getType());
+            }
+
+            @Override
+            public RexNode visitInputRef(RexInputRef ref) {
+                return new NamedRexSlot(rowType.getFieldNames().get(ref.getIndex()), ref.getIndex(), ref.getType());
+            }
+        };
+    }
+
+    private static class NamedRexSlot extends RexSlot {
+        NamedRexSlot(String name, int index, RelDataType type) {
+            super(name, index, type);
+        }
+
+        @Override
+        public <R> R accept(RexVisitor<R> visitor) {
+            throw new AssertionError("Should not be called");
+        }
+
+        @Override
+        public <R, P> R accept(RexBiVisitor<R, P> visitor, P arg) {
+            throw new AssertionError("Should not be called");
+        }
+
+        @Override
+        public boolean equals(@Nullable Object obj) {
+            return this == obj
+                    || obj instanceof NamedRexSlot
+                    && index == ((NamedRexSlot) obj).index;
+        }
+
+        @Override
+        public int hashCode() {
+            return index;
+        }
+    }
+}
diff --git a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rel/explain/IgniteRelWriter.java b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rel/explain/IgniteRelWriter.java
new file mode 100644
index 00000000000..dea62dad2ea
--- /dev/null
+++ b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rel/explain/IgniteRelWriter.java
@@ -0,0 +1,223 @@
+/*
+ * 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.rel.explain;
+
+import java.util.List;
+import java.util.Set;
+import org.apache.calcite.plan.RelOptTable;
+import org.apache.calcite.rel.RelCollation;
+import org.apache.calcite.rel.core.AggregateCall;
+import org.apache.calcite.rel.core.CorrelationId;
+import org.apache.calcite.rel.core.JoinRelType;
+import org.apache.calcite.rel.core.TableModify.Operation;
+import org.apache.calcite.rel.type.RelDataType;
+import org.apache.calcite.rex.RexLiteral;
+import org.apache.calcite.rex.RexNode;
+import org.apache.calcite.util.ImmutableBitSet;
+import org.apache.ignite.internal.sql.engine.prepare.bounds.SearchBounds;
+import org.apache.ignite.internal.sql.engine.rel.IgniteCorrelatedNestedLoopJoin;
+import org.apache.ignite.internal.sql.engine.rel.IgniteKeyValueGet;
+import org.apache.ignite.internal.sql.engine.rel.IgniteKeyValueModify;
+import org.apache.ignite.internal.sql.engine.rel.IgniteRel;
+import org.apache.ignite.internal.sql.engine.rel.IgniteTableFunctionScan;
+import org.apache.ignite.internal.sql.engine.schema.IgniteIndex;
+import org.apache.ignite.internal.sql.engine.trait.IgniteDistribution;
+
+/**
+ * A fluent interface for explaining the structure and metadata of an {@link IgniteRel}
+ * (relational operator) node.
+ */
+@SuppressWarnings("UnusedReturnValue")
+public interface IgniteRelWriter {
+
+    /**
+     * Adds a list of projection expressions to the current relational node.
+     *
+     * @param projections List of {@link RexNode} expressions representing the projected output.
+     * @param rowType The row type against which field indexes in the projections should be resolved.
+     * @return This writer instance for chaining.
+     */
+    IgniteRelWriter addProjection(List<RexNode> projections, RelDataType rowType);
+
+    /**
+     * Adds a filtering predicate applied to the input rows of the relational node.
+     *
+     * @param condition A {@link RexNode} representing the filtering condition.
+     * @param rowType The row type against which field indexes in the predicate should be resolved.
+     * @return This writer instance for chaining.
+     */
+    IgniteRelWriter addPredicate(RexNode condition, RelDataType rowType);
+
+    /**
+     * Adds table metadata to the current relational node.
+     *
+     * @param table A {@link RelOptTable} representing the referenced table.
+     * @return This writer instance for chaining.
+     */
+    IgniteRelWriter addTable(RelOptTable table);
+
+    /**
+     * Adds index metadata, including name and type, associated with the relational node.
+     *
+     * @param name The name of the index.
+     * @param type The type of the index, from {@link IgniteIndex.Type}.
+     * @return This writer instance for chaining.
+     */
+    IgniteRelWriter addIndex(String name, IgniteIndex.Type type);
+
+    /**
+     * Adds collation (sort order) information for the node’s output rows.
+     *
+     * @param collation The {@link RelCollation} defining sort directions.
+     * @param rowType The row type against which field indexes in the collation should be resolved.
+     * @return This writer instance for chaining.
+     */
+    IgniteRelWriter addCollation(RelCollation collation, RelDataType rowType);
+
+    /**
+     * Adds literal tuples that represent either static data or example results.
+     *
+     * @param tuples A list of row tuples, each consisting of {@link RexLiteral} values.
+     * @return This writer instance for chaining.
+     */
+    IgniteRelWriter addTuples(List<List<RexLiteral>> tuples);
+
+    /**
+     * Adds a child relational node (i.e., an input) to the current node.
+     *
+     * @param rel The child {@link IgniteRel} node.
+     * @return This writer instance for chaining.
+     */
+    IgniteRelWriter addChild(IgniteRel rel);
+
+    /**
+     * Adds join type metadata for join operations.
+     *
+     * @param joinType The {@link JoinRelType} (e.g., INNER, LEFT, SEMI).
+     * @return This writer instance for chaining.
+     */
+    IgniteRelWriter addJoinType(JoinRelType joinType);
+
+    /**
+     * Adds distribution information that describes how the rows are distributed across nodes.
+     *
+     * @param distribution The {@link IgniteDistribution} strategy.
+     * @param rowType The row type against which field indexes in the distribution should be resolved.
+     * @return This writer instance for chaining.
+     */
+    IgniteRelWriter addDistribution(IgniteDistribution distribution, RelDataType rowType);
+
+    /**
+     * Adds source expressions used for row computations. Mainly used by {@link IgniteKeyValueModify}.
+     *
+     * @param expressions List of {@link RexNode} expressions representing source values.
+     * @return This writer instance for chaining.
+     */
+    IgniteRelWriter addSourceExpressions(List<RexNode> expressions);
+
+    /**
+     * Adds the type of data modification operation (e.g., INSERT, UPDATE, DELETE).
+     *
+     * @param operation The type of modification operation.
+     * @return This writer instance for chaining.
+     */
+    IgniteRelWriter addModifyOperationType(Operation operation);
+
+    /**
+     * Adds grouping key information used in aggregation operations.
+     *
+     * @param groupSet The {@link ImmutableBitSet} representing grouping key indexes.
+     * @param rowType The row type against which field indexes in the group set should be resolved.
+     * @return This writer instance for chaining.
+     */
+    IgniteRelWriter addGroup(ImmutableBitSet groupSet, RelDataType rowType);
+
+    /**
+     * Adds multiple group sets used for advanced grouping (e.g., ROLLUP or CUBE).
+     *
+     * @param groupSets List of {@link ImmutableBitSet} instances.
+     * @param rowType The row type against which field indexes in the group set should be resolved.
+     * @return This writer instance for chaining.
+     */
+    IgniteRelWriter addGroupSets(List<ImmutableBitSet> groupSets, RelDataType rowType);
+
+    /**
+     * Adds aggregation function calls applied to grouped data.
+     *
+     * @param aggCalls List of {@link AggregateCall} objects defining aggregations.
+     * @param rowType The row type against which field indexes in the aggregate should be resolved.
+     * @return This writer instance for chaining.
+     */
+    IgniteRelWriter addAggregation(List<AggregateCall> aggCalls, RelDataType rowType);
+
+    /**
+     * Adds expressions forming a key for {@link IgniteKeyValueGet lookup-by-a-key} node.
+     *
+     * @param expressions List of {@link RexNode} expressions representing key.
+     * @return This writer instance for chaining.
+     */
+    IgniteRelWriter addKeyExpression(List<RexNode> expressions);
+
+    /**
+     * Adds correlated variables which are set by {@link IgniteCorrelatedNestedLoopJoin}.
+     *
+     * @param variablesSet Set of {@link CorrelationId} identifiers.
+     * @return This writer instance for chaining.
+     */
+    IgniteRelWriter addCorrelatedVariables(Set<CorrelationId> variablesSet);
+
+    /**
+     * Adds search bounds used for index look ups.
+     *
+     * @param searchBounds List of {@link SearchBounds} representing boundaries of the range scan or point look up.
+     * @return This writer instance for chaining.
+     */
+    IgniteRelWriter addSearchBounds(List<SearchBounds> searchBounds);
+
+    /**
+     * Adds an invocation of an expressions. Mainly used by {@link IgniteTableFunctionScan} rel.
+     *
+     * @param call A {@link RexNode} representing the invocation.
+     * @return This writer instance for chaining.
+     */
+    IgniteRelWriter addInvocation(RexNode call);
+
+    /**
+     * Adds an expressions denoting how many rows should be skipped before emitting a result.
+     *
+     * @param offset A {@link RexNode} representing the number of rows to skip.
+     * @return This writer instance for chaining.
+     */
+    IgniteRelWriter addOffset(RexNode offset);
+
+    /**
+     * Adds an expressions denoting how many rows should be returned.
+     *
+     * @param fetch A {@link RexNode} representing the number of rows to return.
+     * @return This writer instance for chaining.
+     */
+    IgniteRelWriter addFetch(RexNode fetch);
+
+    /**
+     * Adds a notion whether set operation does rows deduplication or not.
+     *
+     * @param all A flag denoting whether rows will be de-duplicated.
+     * @return This writer instance for chaining.
+     */
+    IgniteRelWriter addAll(boolean all);
+}
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
new file mode 100644
index 00000000000..d8af939ab78
--- /dev/null
+++ b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rel/explain/RelTreeToTextWriter.java
@@ -0,0 +1,439 @@
+/*
+ * 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.rel.explain;
+
+import static org.apache.ignite.internal.sql.engine.rel.explain.ExplainUtils.inputRefRewriter;
+
+import it.unimi.dsi.fastutil.objects.ObjectIntPair;
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Deque;
+import java.util.EnumMap;
+import java.util.List;
+import java.util.Set;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+import org.apache.calcite.avatica.util.Spacer;
+import org.apache.calcite.linq4j.Ord;
+import org.apache.calcite.plan.RelOptTable;
+import org.apache.calcite.rel.RelCollation;
+import org.apache.calcite.rel.RelNode;
+import org.apache.calcite.rel.core.AggregateCall;
+import org.apache.calcite.rel.core.CorrelationId;
+import org.apache.calcite.rel.core.JoinRelType;
+import org.apache.calcite.rel.core.SetOp;
+import org.apache.calcite.rel.core.TableModify;
+import org.apache.calcite.rel.metadata.RelMetadataQuery;
+import org.apache.calcite.rel.type.RelDataType;
+import org.apache.calcite.rex.RexLiteral;
+import org.apache.calcite.rex.RexNode;
+import org.apache.calcite.rex.RexShuttle;
+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.IgniteDistribution;
+import org.apache.ignite.internal.sql.engine.util.Commons;
+import org.apache.ignite.lang.util.IgniteNameUtils;
+import org.apache.ignite.table.QualifiedNameHelper;
+
+class RelTreeToTextWriter {
+    private static final int NEXT_OPERATOR_INDENT = 2;
+    private static final int OPERATOR_ATTRIBUTES_INDENT = 2 * NEXT_OPERATOR_INDENT;
+
+    private static boolean needToAddFieldNames(IgniteRel rel) {
+        List<RelNode> inputs = rel.getInputs();
+
+        // Current rel is leaf node of the plan, therefore it's data source.
+        // Every data source introduces their own row type, so let's include field names.
+        if (inputs.isEmpty()) {
+            return true;
+        }
+
+        // Set operations doesn't change the row.
+        if (rel instanceof SetOp) {
+            return false;
+        }
+
+        // In case of rel with single input we can do fast check.
+        // Reference comparison is correct because typeFactory must intern every type.
+        if (inputs.size() == 1 && rel.getRowType() == inputs.get(0).getRowType()) {
+            // We want to include field names only if there were changes in number of columns, their order, or their names.
+            // Therefore if row types are not equal we should double check that changes were not [only] in the column types.
+            return false;
+        }
+
+        assert inputs.size() == 1 || inputs.size() == 2;
+
+        List<String> inputNames = new ArrayList<>(inputs.get(0).getRowType().getFieldNames());
+
+        // Join might do rename when both side contains the column with the same name.
+        if (inputs.size() == 2) {
+            inputNames.addAll(inputs.get(1).getRowType().getFieldNames());
+        }
+
+        return !rel.getRowType().getFieldNames().equals(inputNames);
+    }
+
+    private static RelInfoHolder collectRelInfo(IgniteRel rel) {
+        RelInfoHolder infoHolder = new RelInfoHolder(rel);
+
+        rel.explain(infoHolder);
+
+        if (!infoHolder.attributes.containsKey(AttributeName.FIELD_NAMES) && needToAddFieldNames(rel)) {
+            infoHolder.attributes.put(AttributeName.FIELD_NAMES, rel.getRowType().getFieldNames().toString());
+        }
+
+        rel.getInputs().forEach(input -> infoHolder.addChild((IgniteRel) input));
+
+        return infoHolder;
+    }
+
+    private enum AttributeName {
+        TABLE("table"),
+        INDEX_NAME("index"),
+        INDEX_TYPE("type"),
+        PREDICATE("predicate"),
+        SEARCH_BOUNDS("searchBounds"),
+        FIELD_NAMES("fieldNames"),
+        PROJECTION("projection"),
+        COLLATION("collation"),
+        TUPLES("tuples"),
+        JOIN_TYPE("type"),
+        DISTRIBUTION("distribution"),
+        SOURCE_EXPRESSIONS("sourceExpression"),
+        MODIFY_OPERATION_TYPE("type"),
+        GROUP("group"),
+        GROUP_SETS("groupSets"),
+        AGGREGATION("aggregation"),
+        KEY_EXPRESSIONS("key"),
+        CORRELATED_VARIABLES("correlates"),
+        INVOCATION("invocation"),
+        OFFSET("offset"),
+        FETCH("fetch"),
+        ALL("all");
+
+        private final String label;
+
+        AttributeName(String label) {
+            this.label = label;
+        }
+    }
+
+    private static class RelInfoHolder implements IgniteRelWriter {
+        private final IgniteRel rel;
+
+        private final EnumMap<AttributeName, String> attributes = new EnumMap<>(AttributeName.class);
+        private final List<RelInfoHolder> inputs = new ArrayList<>();
+
+        RelInfoHolder(IgniteRel rel) {
+            this.rel = rel;
+        }
+
+        @Override
+        public IgniteRelWriter addChild(IgniteRel rel) {
+            RelInfoHolder infoHolder = collectRelInfo(rel);
+
+            inputs.add(infoHolder);
+
+            return this;
+        }
+
+        @Override
+        public IgniteRelWriter addProjection(List<RexNode> projections, RelDataType rowType) {
+            RexShuttle inputRefRewriter = inputRefRewriter(rowType);
+
+            Function<RexNode, RexNode> f = in -> in.accept(inputRefRewriter);
+
+            attributes.put(AttributeName.PROJECTION, Commons.transform(projections, f).toString());
+
+            return this;
+        }
+
+        @Override
+        public IgniteRelWriter addPredicate(RexNode condition, RelDataType rowType) {
+            attributes.put(AttributeName.PREDICATE, condition.accept(inputRefRewriter(rowType)).toString());
+
+            return this;
+        }
+
+        @Override
+        public IgniteRelWriter addTable(RelOptTable table) {
+            List<String> parts = table.getQualifiedName();
+
+            assert parts.size() == 2 : parts;
+
+            attributes.put(AttributeName.TABLE, QualifiedNameHelper.fromNormalized(parts.get(0), parts.get(1)).toCanonicalForm());
+
+            return this;
+        }
+
+        @Override
+        public IgniteRelWriter addIndex(String name, IgniteIndex.Type type) {
+            attributes.put(AttributeName.INDEX_NAME, IgniteNameUtils.quoteIfNeeded(name));
+            attributes.put(AttributeName.INDEX_TYPE, type.name());
+
+            return this;
+        }
+
+        @Override
+        public IgniteRelWriter addCollation(RelCollation collation, RelDataType rowType) {
+            attributes.put(AttributeName.COLLATION, beautifyCollation(collation, rowType).toString());
+
+            return this;
+        }
+
+        @Override
+        public IgniteRelWriter addTuples(List<List<RexLiteral>> tuples) {
+            attributes.put(AttributeName.TUPLES, tuples.toString());
+
+            return this;
+        }
+
+        @Override
+        public IgniteRelWriter addJoinType(JoinRelType joinType) {
+            attributes.put(AttributeName.JOIN_TYPE, joinType.lowerName);
+
+            return this;
+        }
+
+        @Override
+        public IgniteRelWriter addDistribution(IgniteDistribution distribution, RelDataType rowType) {
+            attributes.put(AttributeName.DISTRIBUTION, distribution.toString());
+
+            return this;
+        }
+
+        @Override
+        public IgniteRelWriter addSourceExpressions(List<RexNode> expressions) {
+            attributes.put(AttributeName.SOURCE_EXPRESSIONS, expressions.toString());
+
+            return this;
+        }
+
+        @Override
+        public IgniteRelWriter addModifyOperationType(TableModify.Operation operation) {
+            attributes.put(AttributeName.MODIFY_OPERATION_TYPE, operation.toString());
+
+            return this;
+        }
+
+        @Override
+        public IgniteRelWriter addGroup(ImmutableBitSet groupSet, RelDataType rowType) {
+            attributes.put(AttributeName.GROUP, beautifyBitSet(groupSet, rowType).toString());
+
+            return this;
+        }
+
+        @Override
+        public IgniteRelWriter addGroupSets(List<ImmutableBitSet> groupSets, RelDataType rowType) {
+            List<List<String>> sets = groupSets.stream()
+                    .map(groupSet -> beautifyBitSet(groupSet, rowType))
+                    .collect(Collectors.toList());
+
+            attributes.put(AttributeName.GROUP_SETS, sets.toString());
+
+            return this;
+        }
+
+        @Override
+        public IgniteRelWriter addAggregation(List<AggregateCall> aggCalls, RelDataType rowType) {
+            RexShuttle inputRefRewriter = inputRefRewriter(rowType);
+
+            List<String> calls = aggCalls.stream()
+                    .map(call -> beautifyAggCall(call, inputRefRewriter, rowType))
+                    .collect(Collectors.toList());
+
+            attributes.put(AttributeName.AGGREGATION, calls.toString());
+
+            return this;
+        }
+
+        @Override
+        public IgniteRelWriter addKeyExpression(List<RexNode> expressions) {
+            attributes.put(AttributeName.KEY_EXPRESSIONS, expressions.toString());
+
+            return this;
+        }
+
+        @Override
+        public IgniteRelWriter addCorrelatedVariables(Set<CorrelationId> variablesSet) {
+            attributes.put(AttributeName.CORRELATED_VARIABLES, variablesSet.toString());
+
+            return this;
+        }
+
+        @Override
+        public IgniteRelWriter addSearchBounds(List<SearchBounds> searchBounds) {
+            attributes.put(AttributeName.SEARCH_BOUNDS, searchBounds.toString());
+
+            return this;
+        }
+
+        @Override
+        public IgniteRelWriter addInvocation(RexNode call) {
+            attributes.put(AttributeName.INVOCATION, call.toString());
+
+            return this;
+        }
+
+        @Override
+        public IgniteRelWriter addOffset(RexNode offset) {
+            attributes.put(AttributeName.OFFSET, offset.toString());
+
+            return this;
+        }
+
+        @Override
+        public IgniteRelWriter addFetch(RexNode fetch) {
+            attributes.put(AttributeName.FETCH, fetch.toString());
+
+            return this;
+        }
+
+        @Override
+        public IgniteRelWriter addAll(boolean all) {
+            attributes.put(AttributeName.ALL, String.valueOf(all));
+
+            return this;
+        }
+    }
+
+    static String dumpTree(IgniteRel rootRel) {
+        RelInfoHolder root = collectRelInfo(rootRel);
+
+        Deque<ObjectIntPair<RelInfoHolder>> explanationStack = new ArrayDeque<>();
+        explanationStack.add(ObjectIntPair.of(root, 0));
+
+        StringBuilder sb = new StringBuilder();
+        Spacer spacer = new Spacer();
+        while (!explanationStack.isEmpty()) {
+            ObjectIntPair<RelInfoHolder> infoHolderWithLevel = explanationStack.pollLast();
+
+            RelInfoHolder infoHolder = infoHolderWithLevel.first();
+            int level = infoHolderWithLevel.secondInt();
+
+            for (int i = infoHolder.inputs.size() - 1; i >= 0; i--) {
+                explanationStack.add(ObjectIntPair.of(infoHolder.inputs.get(i), level + 1));
+            }
+
+            spacer.set(level * NEXT_OPERATOR_INDENT);
+
+            spacer.spaces(sb).append(infoHolder.rel.getRelTypeName());
+
+            spacer.add(OPERATOR_ATTRIBUTES_INDENT);
+
+            for (AttributeName name : AttributeName.values()) {
+                String attributeValue = infoHolder.attributes.get(name);
+
+                if (attributeValue == null) {
+                    continue;
+                }
+
+                sb.append(System.lineSeparator());
+
+                spacer.spaces(sb);
+
+                sb.append(name.label).append(": ").append(attributeValue);
+            }
+
+            sb.append(System.lineSeparator());
+            spacer.spaces(sb);
+
+            RelMetadataQuery mq = infoHolder.rel.getCluster().getMetadataQuery();
+            sb.append("est: (rows=").append(
+                    BigDecimal.valueOf(mq.getRowCount(infoHolder.rel)).setScale(0, RoundingMode.HALF_UP)
+            ).append(')');
+
+            spacer.subtract(OPERATOR_ATTRIBUTES_INDENT);
+
+            sb.append(System.lineSeparator());
+        }
+
+        return sb.toString();
+    }
+
+    private static List<String> beautifyBitSet(ImmutableBitSet bitSet, RelDataType rowType) {
+        return bitSet.stream()
+                .mapToObj(rowType.getFieldNames()::get)
+                .collect(Collectors.toList());
+    }
+
+    private static String beautifyAggCall(AggregateCall call, RexShuttle inputRefRewriter, RelDataType rowType) {
+        StringBuilder buf = new StringBuilder(call.getAggregation().toString());
+        buf.append('(');
+
+        if (call.isApproximate()) {
+            buf.append("APPROXIMATE ");
+        }
+        if (call.isDistinct()) {
+            buf.append(call.getArgList().isEmpty() ? "DISTINCT" : "DISTINCT ");
+        }
+        int i = -1;
+        for (RexNode rexNode : call.rexList) {
+            if (++i > 0) {
+                buf.append(", ");
+            }
+            buf.append(rexNode.accept(inputRefRewriter));
+        }
+        for (Integer arg : call.getArgList()) {
+            if (++i > 0) {
+                buf.append(", ");
+            }
+            buf.append(rowType.getFieldNames().get(arg));
+        }
+        buf.append(')');
+        if (call.distinctKeys != null) {
+            buf.append(" WITHIN DISTINCT (");
+            for (Ord<Integer> key : Ord.zip(call.distinctKeys)) {
+                buf.append(key.i > 0 ? ", " : "");
+                buf.append(rowType.getFieldNames().get(key.e));
+            }
+            buf.append(')');
+        }
+        if (call.hasCollation()) {
+            buf.append(" WITHIN GROUP (");
+            buf.append(beautifyCollation(call.collation, rowType));
+            buf.append(')');
+        }
+        if (call.hasFilter()) {
+            buf.append(" FILTER ");
+            buf.append(rowType.getFieldNames().get(call.filterArg));
+        }
+        return buf.toString();
+    }
+
+    private static List<String> beautifyCollation(RelCollation collation, RelDataType rowType) {
+        return collation.getFieldCollations().stream().map(fc -> {
+            StringBuilder sb = new StringBuilder(rowType.getFieldNames().get(fc.getFieldIndex()))
+                    .append(' ').append(fc.direction.shortString);
+
+            if (fc.nullDirection != fc.direction.defaultNullDirection()) {
+                sb.append(" NULLS ").append(fc.nullDirection);
+            }
+
+            return sb.toString();
+        }).collect(Collectors.toList());
+    }
+
+    private RelTreeToTextWriter() {
+        throw new AssertionError("Should not be called");
+    }
+}
diff --git a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rel/set/IgniteIntersect.java b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rel/set/IgniteIntersect.java
index 99f3051151b..8cd40d84e6c 100644
--- a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rel/set/IgniteIntersect.java
+++ b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rel/set/IgniteIntersect.java
@@ -27,6 +27,7 @@ import org.apache.calcite.rel.RelNode;
 import org.apache.calcite.rel.core.Intersect;
 import org.apache.calcite.rel.metadata.RelMetadataQuery;
 import org.apache.ignite.internal.sql.engine.rel.IgniteConvention;
+import org.apache.ignite.internal.sql.engine.rel.explain.IgniteRelWriter;
 import org.apache.ignite.internal.sql.engine.trait.TraitUtils;
 
 /**
@@ -67,4 +68,9 @@ public abstract class IgniteIntersect extends Intersect implements IgniteSetOp {
     public boolean all() {
         return all;
     }
+
+    @Override
+    public IgniteRelWriter explain(IgniteRelWriter writer) {
+        return writer.addAll(all);
+    }
 }
diff --git a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rel/set/IgniteMinus.java b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rel/set/IgniteMinus.java
index 26ab768070d..d6f534e5f59 100644
--- a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rel/set/IgniteMinus.java
+++ b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/rel/set/IgniteMinus.java
@@ -27,6 +27,7 @@ import org.apache.calcite.rel.RelNode;
 import org.apache.calcite.rel.core.Minus;
 import org.apache.calcite.rel.metadata.RelMetadataQuery;
 import org.apache.ignite.internal.sql.engine.rel.IgniteConvention;
+import org.apache.ignite.internal.sql.engine.rel.explain.IgniteRelWriter;
 import org.apache.ignite.internal.sql.engine.trait.TraitUtils;
 
 /**
@@ -78,4 +79,9 @@ public abstract class IgniteMinus extends Minus implements IgniteSetOp {
     public boolean all() {
         return all;
     }
+
+    @Override
+    public IgniteRelWriter explain(IgniteRelWriter writer) {
+        return writer.addAll(all);
+    }
 }
diff --git a/modules/sql-engine/src/test/resources/tpcds/plan/q64.plan b/modules/sql-engine/src/test/resources/tpcds/plan/q64.plan
index 0054340b8e0..04cdf2ea539 100644
--- a/modules/sql-engine/src/test/resources/tpcds/plan/q64.plan
+++ b/modules/sql-engine/src/test/resources/tpcds/plan/q64.plan
@@ -2,472 +2,489 @@ Sort
     collation: [PRODUCT_NAME ASC, STORE_NAME ASC, CNT0 ASC, S11 ASC, S12 ASC]
     est: (rows=21)
   Project
-      fields: [PRODUCT_NAME, STORE_NAME, STORE_ZIP, B_STREET_NUMBER, B_STREET_NAME, B_CITY, B_ZIP, C_STREET_NUMBER, C_STREET_NAME, C_CITY, C_ZIP, SYEAR, CNT, S11, S21, S31, S12, S22, S32, SYEAR0, CNT0]
-      exprs: [PRODUCT_NAME, STORE_NAME, STORE_ZIP, B_STREET_NUMBER, B_STREET_NAME, B_CITY, B_ZIP, C_STREET_NUMBER, C_STREET_NAME, C_CITY, C_ZIP, SYEAR, CNT, S1, S2, S3, S10, S20, S30, SYEAR0, CNT0]
+      fieldNames: [PRODUCT_NAME, STORE_NAME, STORE_ZIP, B_STREET_NUMBER, B_STREET_NAME, B_CITY, B_ZIP, C_STREET_NUMBER, C_STREET_NAME, C_CITY, C_ZIP, SYEAR, CNT, S11, S21, S31, S12, S22, S32, SYEAR0, CNT0]
+      projection: [PRODUCT_NAME, STORE_NAME, STORE_ZIP, B_STREET_NUMBER, B_STREET_NAME, B_CITY, B_ZIP, C_STREET_NUMBER, C_STREET_NAME, C_CITY, C_ZIP, SYEAR, CNT, S1, S2, S3, S10, S20, S30, SYEAR0, CNT0]
       est: (rows=21)
     HashJoin
-        condition: AND(=(ITEM_SK, ITEM_SK0), <=(CNT0, CNT), =(STORE_NAME, STORE_NAME0), =(STORE_ZIP, STORE_ZIP0))
-        joinType: inner
+        predicate: AND(=(ITEM_SK, ITEM_SK0), <=(CNT0, CNT), =(STORE_NAME, STORE_NAME0), =(STORE_ZIP, STORE_ZIP0))
+        fieldNames: [PRODUCT_NAME, ITEM_SK, STORE_NAME, STORE_ZIP, B_STREET_NUMBER, B_STREET_NAME, B_CITY, B_ZIP, C_STREET_NUMBER, C_STREET_NAME, C_CITY, C_ZIP, SYEAR, FSYEAR, S2YEAR, CNT, S1, S2, S3, PRODUCT_NAME0, ITEM_SK0, STORE_NAME0, STORE_ZIP0, B_STREET_NUMBER0, B_STREET_NAME0, B_CITY0, B_ZIP0, C_STREET_NUMBER0, C_STREET_NAME0, C_CITY0, C_ZIP0, SYEAR0, FSYEAR0, S2YEAR0, CNT0, S10, S20, S30]
+        type: inner
         est: (rows=21)
       ColocatedHashAggregate
+          fieldNames: [PRODUCT_NAME, ITEM_SK, STORE_NAME, STORE_ZIP, B_STREET_NUMBER, B_STREET_NAME, B_CITY, B_ZIP, C_STREET_NUMBER, C_STREET_NAME, C_CITY, C_ZIP, SYEAR, FSYEAR, S2YEAR, CNT, S1, S2, S3]
           group: [PRODUCT_NAME, ITEM_SK, STORE_NAME, STORE_ZIP, B_STREET_NUMBER, B_STREET_NAME, B_CITY, B_ZIP, C_STREET_NUMBER, C_STREET_NAME, C_CITY, C_ZIP, SYEAR, FSYEAR, S2YEAR]
-          aggs: [CNT=COUNT(), S1=SUM(SS_WHOLESALE_COST), S2=SUM(SS_LIST_PRICE), S3=SUM(SS_COUPON_AMT)]
+          aggregation: [COUNT(), SUM(SS_WHOLESALE_COST), SUM(SS_LIST_PRICE), SUM(SS_COUPON_AMT)]
           est: (rows=2887)
         Project
-            fields: [PRODUCT_NAME, ITEM_SK, STORE_NAME, STORE_ZIP, B_STREET_NUMBER, B_STREET_NAME, B_CITY, B_ZIP, C_STREET_NUMBER, C_STREET_NAME, C_CITY, C_ZIP, SYEAR, FSYEAR, S2YEAR, SS_WHOLESALE_COST, SS_LIST_PRICE, SS_COUPON_AMT]
-            exprs: [I_PRODUCT_NAME, I_ITEM_SK, S_STORE_NAME, S_ZIP, CA_STREET_NUMBER, CA_STREET_NAME, CA_CITY, CA_ZIP, CA_STREET_NUMBER0, CA_STREET_NAME0, CA_CITY0, CA_ZIP0, D_YEAR00, D_YEAR, D_YEAR0, SS_WHOLESALE_COST, SS_LIST_PRICE, SS_COUPON_AMT]
+            fieldNames: [PRODUCT_NAME, ITEM_SK, STORE_NAME, STORE_ZIP, B_STREET_NUMBER, B_STREET_NAME, B_CITY, B_ZIP, C_STREET_NUMBER, C_STREET_NAME, C_CITY, C_ZIP, SYEAR, FSYEAR, S2YEAR, SS_WHOLESALE_COST, SS_LIST_PRICE, SS_COUPON_AMT]
+            projection: [I_PRODUCT_NAME, I_ITEM_SK, S_STORE_NAME, S_ZIP, CA_STREET_NUMBER, CA_STREET_NAME, CA_CITY, CA_ZIP, CA_STREET_NUMBER0, CA_STREET_NAME0, CA_CITY0, CA_ZIP0, D_YEAR00, D_YEAR, D_YEAR0, SS_WHOLESALE_COST, SS_LIST_PRICE, SS_COUPON_AMT]
             est: (rows=2992)
           HashJoin
-              condition: AND(=(SS_ITEM_SK, SR_ITEM_SK), =(SS_TICKET_NUMBER, SR_TICKET_NUMBER))
-              joinType: inner
+              predicate: AND(=(SS_ITEM_SK, SR_ITEM_SK), =(SS_TICKET_NUMBER, SR_TICKET_NUMBER))
+              type: inner
               est: (rows=2992)
             Exchange
                 distribution: single
                 est: (rows=287514)
               TableScan
                   table: PUBLIC.STORE_RETURNS
-                  fields: [SR_ITEM_SK, SR_TICKET_NUMBER]
+                  fieldNames: [SR_ITEM_SK, SR_TICKET_NUMBER]
                   est: (rows=287514)
             HashJoin
-                condition: =(C_FIRST_SALES_DATE_SK, D_DATE_SK)
-                joinType: inner
+                predicate: =(C_FIRST_SALES_DATE_SK, D_DATE_SK)
+                fieldNames: [D_DATE_SK, D_YEAR, D_DATE_SK0, D_YEAR0, CA_ADDRESS_SK, CA_STREET_NUMBER, CA_STREET_NAME, CA_CITY, CA_ZIP, CA_ADDRESS_SK0, CA_STREET_NUMBER0, CA_STREET_NAME0, CA_CITY0, CA_ZIP0, HD_DEMO_SK, HD_INCOME_BAND_SK, HD_DEMO_SK0, HD_INCOME_BAND_SK0, CD_DEMO_SK, CD_MARITAL_STATUS, CD_DEMO_SK0, CD_MARITAL_STATUS0, C_CUSTOMER_SK, C_CURRENT_CDEMO_SK, C_CURRENT_HDEMO_SK, C_CURRENT_ADDR_SK, C_FIRST_SHIPTO_DATE_SK, C_FIRST_SALES_DATE_SK, SS_SOLD_DATE_SK, SS_ITEM_SK, SS_CUSTO [...]
+                type: inner
                 est: (rows=90684167)
               Exchange
                   distribution: single
                   est: (rows=73049)
                 TableScan
                     table: PUBLIC.DATE_DIM
-                    fields: [D_DATE_SK, D_YEAR]
+                    fieldNames: [D_DATE_SK, D_YEAR]
                     est: (rows=73049)
               HashJoin
-                  condition: =(C_FIRST_SHIPTO_DATE_SK, D_DATE_SK)
-                  joinType: inner
+                  predicate: =(C_FIRST_SHIPTO_DATE_SK, D_DATE_SK)
+                  fieldNames: [D_DATE_SK, D_YEAR, CA_ADDRESS_SK, CA_STREET_NUMBER, CA_STREET_NAME, CA_CITY, CA_ZIP, CA_ADDRESS_SK0, CA_STREET_NUMBER0, CA_STREET_NAME0, CA_CITY0, CA_ZIP0, HD_DEMO_SK, HD_INCOME_BAND_SK, HD_DEMO_SK0, HD_INCOME_BAND_SK0, CD_DEMO_SK, CD_MARITAL_STATUS, CD_DEMO_SK0, CD_MARITAL_STATUS0, C_CUSTOMER_SK, C_CURRENT_CDEMO_SK, C_CURRENT_HDEMO_SK, C_CURRENT_ADDR_SK, C_FIRST_SHIPTO_DATE_SK, C_FIRST_SALES_DATE_SK, SS_SOLD_DATE_SK, SS_ITEM_SK, SS_CUSTOMER_SK, SS_CDEMO_SK [...]
+                  type: inner
                   est: (rows=90684167)
                 Exchange
                     distribution: single
                     est: (rows=73049)
                   TableScan
                       table: PUBLIC.DATE_DIM
-                      fields: [D_DATE_SK, D_YEAR]
+                      fieldNames: [D_DATE_SK, D_YEAR]
                       est: (rows=73049)
                 HashJoin
-                    condition: =(SS_STORE_SK, S_STORE_SK)
-                    joinType: inner
+                    predicate: =(SS_STORE_SK, S_STORE_SK)
+                    type: inner
                     est: (rows=90684167)
                   HashJoin
-                      condition: =(SS_PROMO_SK, P_PROMO_SK)
-                      joinType: inner
+                      predicate: =(SS_PROMO_SK, P_PROMO_SK)
+                      type: inner
                       est: (rows=90684167)
                     HashJoin
-                        condition: =(SS_ADDR_SK, CA_ADDRESS_SK)
-                        joinType: inner
+                        predicate: =(SS_ADDR_SK, CA_ADDRESS_SK)
+                        fieldNames: [CA_ADDRESS_SK, CA_STREET_NUMBER, CA_STREET_NAME, CA_CITY, CA_ZIP, CA_ADDRESS_SK0, CA_STREET_NUMBER0, CA_STREET_NAME0, CA_CITY0, CA_ZIP0, HD_DEMO_SK, HD_INCOME_BAND_SK, HD_DEMO_SK0, HD_INCOME_BAND_SK0, CD_DEMO_SK, CD_MARITAL_STATUS, CD_DEMO_SK0, CD_MARITAL_STATUS0, C_CUSTOMER_SK, C_CURRENT_CDEMO_SK, C_CURRENT_HDEMO_SK, C_CURRENT_ADDR_SK, C_FIRST_SHIPTO_DATE_SK, C_FIRST_SALES_DATE_SK, SS_SOLD_DATE_SK, SS_ITEM_SK, SS_CUSTOMER_SK, SS_CDEMO_SK, SS_HDEMO_SK [...]
+                        type: inner
                         est: (rows=90684167)
                       Exchange
                           distribution: single
                           est: (rows=50000)
                         TableScan
                             table: PUBLIC.CUSTOMER_ADDRESS
-                            fields: [CA_ADDRESS_SK, CA_STREET_NUMBER, CA_STREET_NAME, CA_CITY, CA_ZIP]
+                            fieldNames: [CA_ADDRESS_SK, CA_STREET_NUMBER, CA_STREET_NAME, CA_CITY, CA_ZIP]
                             est: (rows=50000)
                       HashJoin
-                          condition: =(C_CURRENT_ADDR_SK, CA_ADDRESS_SK)
-                          joinType: inner
+                          predicate: =(C_CURRENT_ADDR_SK, CA_ADDRESS_SK)
+                          type: inner
                           est: (rows=90684167)
                         Exchange
                             distribution: single
                             est: (rows=50000)
                           TableScan
                               table: PUBLIC.CUSTOMER_ADDRESS
-                              fields: [CA_ADDRESS_SK, CA_STREET_NUMBER, CA_STREET_NAME, CA_CITY, CA_ZIP]
+                              fieldNames: [CA_ADDRESS_SK, CA_STREET_NUMBER, CA_STREET_NAME, CA_CITY, CA_ZIP]
                               est: (rows=50000)
                         HashJoin
-                            condition: =(HD_INCOME_BAND_SK, IB_INCOME_BAND_SK0)
-                            joinType: inner
+                            predicate: =(HD_INCOME_BAND_SK, IB_INCOME_BAND_SK0)
+                            fieldNames: [HD_DEMO_SK, HD_INCOME_BAND_SK, HD_DEMO_SK0, HD_INCOME_BAND_SK0, CD_DEMO_SK, CD_MARITAL_STATUS, CD_DEMO_SK0, CD_MARITAL_STATUS0, C_CUSTOMER_SK, C_CURRENT_CDEMO_SK, C_CURRENT_HDEMO_SK, C_CURRENT_ADDR_SK, C_FIRST_SHIPTO_DATE_SK, C_FIRST_SALES_DATE_SK, SS_SOLD_DATE_SK, SS_ITEM_SK, SS_CUSTOMER_SK, SS_CDEMO_SK, SS_HDEMO_SK, SS_ADDR_SK, SS_STORE_SK, SS_PROMO_SK, SS_TICKET_NUMBER, SS_WHOLESALE_COST, SS_LIST_PRICE, SS_COUPON_AMT, I_ITEM_SK, I_CURRENT_PRICE [...]
+                            type: inner
                             est: (rows=90684167)
                           HashJoin
-                              condition: =(SS_HDEMO_SK, HD_DEMO_SK)
-                              joinType: inner
+                              predicate: =(SS_HDEMO_SK, HD_DEMO_SK)
+                              fieldNames: [HD_DEMO_SK, HD_INCOME_BAND_SK, HD_DEMO_SK0, HD_INCOME_BAND_SK0, CD_DEMO_SK, CD_MARITAL_STATUS, CD_DEMO_SK0, CD_MARITAL_STATUS0, C_CUSTOMER_SK, C_CURRENT_CDEMO_SK, C_CURRENT_HDEMO_SK, C_CURRENT_ADDR_SK, C_FIRST_SHIPTO_DATE_SK, C_FIRST_SALES_DATE_SK, SS_SOLD_DATE_SK, SS_ITEM_SK, SS_CUSTOMER_SK, SS_CDEMO_SK, SS_HDEMO_SK, SS_ADDR_SK, SS_STORE_SK, SS_PROMO_SK, SS_TICKET_NUMBER, SS_WHOLESALE_COST, SS_LIST_PRICE, SS_COUPON_AMT, I_ITEM_SK, I_CURRENT_PRI [...]
+                              type: inner
                               est: (rows=90684167)
                             Exchange
                                 distribution: single
                                 est: (rows=7200)
                               TableScan
                                   table: PUBLIC.HOUSEHOLD_DEMOGRAPHICS
-                                  fields: [HD_DEMO_SK, HD_INCOME_BAND_SK]
+                                  fieldNames: [HD_DEMO_SK, HD_INCOME_BAND_SK]
                                   est: (rows=7200)
                             HashJoin
-                                condition: =(HD_INCOME_BAND_SK, IB_INCOME_BAND_SK)
-                                joinType: inner
+                                predicate: =(HD_INCOME_BAND_SK, IB_INCOME_BAND_SK)
+                                type: inner
                                 est: (rows=90684167)
                               HashJoin
-                                  condition: =(C_CURRENT_HDEMO_SK, HD_DEMO_SK)
-                                  joinType: inner
+                                  predicate: =(C_CURRENT_HDEMO_SK, HD_DEMO_SK)
+                                  type: inner
                                   est: (rows=90684167)
                                 Exchange
                                     distribution: single
                                     est: (rows=7200)
                                   TableScan
                                       table: PUBLIC.HOUSEHOLD_DEMOGRAPHICS
-                                      fields: [HD_DEMO_SK, HD_INCOME_BAND_SK]
+                                      fieldNames: [HD_DEMO_SK, HD_INCOME_BAND_SK]
                                       est: (rows=7200)
                                 HashJoin
-                                    condition: AND(<>(CD_MARITAL_STATUS, CD_MARITAL_STATUS0), =(SS_CDEMO_SK, CD_DEMO_SK))
-                                    joinType: inner
+                                    predicate: AND(<>(CD_MARITAL_STATUS, CD_MARITAL_STATUS0), =(SS_CDEMO_SK, CD_DEMO_SK))
+                                    fieldNames: [CD_DEMO_SK, CD_MARITAL_STATUS, CD_DEMO_SK0, CD_MARITAL_STATUS0, C_CUSTOMER_SK, C_CURRENT_CDEMO_SK, C_CURRENT_HDEMO_SK, C_CURRENT_ADDR_SK, C_FIRST_SHIPTO_DATE_SK, C_FIRST_SALES_DATE_SK, SS_SOLD_DATE_SK, SS_ITEM_SK, SS_CUSTOMER_SK, SS_CDEMO_SK, SS_HDEMO_SK, SS_ADDR_SK, SS_STORE_SK, SS_PROMO_SK, SS_TICKET_NUMBER, SS_WHOLESALE_COST, SS_LIST_PRICE, SS_COUPON_AMT, I_ITEM_SK, I_CURRENT_PRICE, I_COLOR, I_PRODUCT_NAME, CS_ITEM_SK, SALE, REFUND, D_D [...]
+                                    type: inner
                                     est: (rows=90684167)
                                   Exchange
                                       distribution: single
                                       est: (rows=1920800)
                                     TableScan
                                         table: PUBLIC.CUSTOMER_DEMOGRAPHICS
-                                        fields: [CD_DEMO_SK, CD_MARITAL_STATUS]
+                                        fieldNames: [CD_DEMO_SK, CD_MARITAL_STATUS]
                                         est: (rows=1920800)
                                   HashJoin
-                                      condition: =(C_CURRENT_CDEMO_SK, CD_DEMO_SK)
-                                      joinType: inner
+                                      predicate: =(C_CURRENT_CDEMO_SK, CD_DEMO_SK)
+                                      type: inner
                                       est: (rows=129548810)
                                     Exchange
                                         distribution: single
                                         est: (rows=1920800)
                                       TableScan
                                           table: PUBLIC.CUSTOMER_DEMOGRAPHICS
-                                          fields: [CD_DEMO_SK, CD_MARITAL_STATUS]
+                                          fieldNames: [CD_DEMO_SK, CD_MARITAL_STATUS]
                                           est: (rows=1920800)
                                     HashJoin
-                                        condition: =(SS_CUSTOMER_SK, C_CUSTOMER_SK)
-                                        joinType: inner
+                                        predicate: =(SS_CUSTOMER_SK, C_CUSTOMER_SK)
+                                        type: inner
                                         est: (rows=129548810)
                                       Exchange
                                           distribution: single
                                           est: (rows=100000)
                                         TableScan
                                             table: PUBLIC.CUSTOMER
-                                            fields: [C_CUSTOMER_SK, C_CURRENT_CDEMO_SK, C_CURRENT_HDEMO_SK, C_CURRENT_ADDR_SK, C_FIRST_SHIPTO_DATE_SK, C_FIRST_SALES_DATE_SK]
+                                            fieldNames: [C_CUSTOMER_SK, C_CURRENT_CDEMO_SK, C_CURRENT_HDEMO_SK, C_CURRENT_ADDR_SK, C_FIRST_SHIPTO_DATE_SK, C_FIRST_SALES_DATE_SK]
                                             est: (rows=100000)
                                       HashJoin
-                                          condition: =(SS_SOLD_DATE_SK, D_DATE_SK)
-                                          joinType: inner
+                                          predicate: =(SS_SOLD_DATE_SK, D_DATE_SK)
+                                          type: inner
                                           est: (rows=129548810)
                                         HashJoin
-                                            condition: =(SS_ITEM_SK, CS_ITEM_SK)
-                                            joinType: inner
+                                            predicate: =(SS_ITEM_SK, CS_ITEM_SK)
+                                            type: inner
                                             est: (rows=389035465)
                                           HashJoin
-                                              condition: =(SS_ITEM_SK, I_ITEM_SK)
-                                              joinType: inner
+                                              predicate: =(SS_ITEM_SK, I_ITEM_SK)
+                                              type: inner
                                               est: (rows=180025)
                                             Exchange
                                                 distribution: single
                                                 est: (rows=2880404)
                                               TableScan
                                                   table: PUBLIC.STORE_SALES
-                                                  fields: [SS_SOLD_DATE_SK, SS_ITEM_SK, SS_CUSTOMER_SK, SS_CDEMO_SK, SS_HDEMO_SK, SS_ADDR_SK, SS_STORE_SK, SS_PROMO_SK, SS_TICKET_NUMBER, SS_WHOLESALE_COST, SS_LIST_PRICE, SS_COUPON_AMT]
+                                                  fieldNames: [SS_SOLD_DATE_SK, SS_ITEM_SK, SS_CUSTOMER_SK, SS_CDEMO_SK, SS_HDEMO_SK, SS_ADDR_SK, SS_STORE_SK, SS_PROMO_SK, SS_TICKET_NUMBER, SS_WHOLESALE_COST, SS_LIST_PRICE, SS_COUPON_AMT]
                                                   est: (rows=2880404)
                                             Exchange
                                                 distribution: single
                                                 est: (rows=1125)
                                               TableScan
                                                   table: PUBLIC.ITEM
-                                                  filters: AND(SEARCH(I_COLOR, Sarg[_UTF-8'azure':VARCHAR(20) CHARACTER SET "UTF-8", _UTF-8'blush':VARCHAR(20) CHARACTER SET "UTF-8", _UTF-8'gainsboro':VARCHAR(20) CHARACTER SET "UTF-8", _UTF-8'hot':VARCHAR(20) CHARACTER SET "UTF-8", _UTF-8'lemon':VARCHAR(20) CHARACTER SET "UTF-8", _UTF-8'misty':VARCHAR(20) CHARACTER SET "UTF-8"]:VARCHAR(20) CHARACTER SET "UTF-8"), >=(CAST(I_CURRENT_PRICE):DECIMAL(12, 2), 80.00), <=(CAST(I_CURRENT_PRICE):DE [...]
-                                                  fields: [I_ITEM_SK, I_CURRENT_PRICE, I_COLOR, I_PRODUCT_NAME]
+                                                  predicate: AND(SEARCH(I_COLOR, Sarg[_UTF-8'azure':VARCHAR(20) CHARACTER SET "UTF-8", _UTF-8'blush':VARCHAR(20) CHARACTER SET "UTF-8", _UTF-8'gainsboro':VARCHAR(20) CHARACTER SET "UTF-8", _UTF-8'hot':VARCHAR(20) CHARACTER SET "UTF-8", _UTF-8'lemon':VARCHAR(20) CHARACTER SET "UTF-8", _UTF-8'misty':VARCHAR(20) CHARACTER SET "UTF-8"]:VARCHAR(20) CHARACTER SET "UTF-8"), >=(CAST(I_CURRENT_PRICE):DECIMAL(12, 2), 80.00), <=(CAST(I_CURRENT_PRICE): [...]
+                                                  fieldNames: [I_ITEM_SK, I_CURRENT_PRICE, I_COLOR, I_PRODUCT_NAME]
                                                   est: (rows=1125)
                                           Filter
-                                              condition: >(SALE, *(2, REFUND))
+                                              predicate: >(SALE, *(2, REFUND))
                                               est: (rows=14407)
                                             ReduceHashAggregate
-                                                rowType: RecordType(INTEGER CS_ITEM_SK, DECIMAL(32767, 2) SALE, DECIMAL(32767, 2) REFUND)
+                                                fieldNames: [CS_ITEM_SK, SALE, REFUND]
                                                 group: [CS_ITEM_SK]
-                                                aggs: [SALE=SUM(_ACC0), REFUND=SUM(_ACC1)]
+                                                aggregation: [SUM(_ACC0), SUM(_ACC1)]
                                                 est: (rows=28813)
                                               Exchange
                                                   distribution: single
                                                   est: (rows=28813)
                                                 MapHashAggregate
+                                                    fieldNames: [CS_ITEM_SK, _ACC0, _ACC1, _GROUP_ID]
                                                     group: [CS_ITEM_SK]
-                                                    aggs: [SALE=SUM(CS_EXT_LIST_PRICE), REFUND=SUM($f2)]
+                                                    aggregation: [SUM(CS_EXT_LIST_PRICE), SUM($f2)]
                                                     est: (rows=28813)
                                                   Project
-                                                      fields: [CS_ITEM_SK, CS_EXT_LIST_PRICE, $f2]
-                                                      exprs: [CS_ITEM_SK, CS_EXT_LIST_PRICE, +(+(CR_REFUNDED_CASH, CR_REVERSED_CHARGE), CR_STORE_CREDIT)]
+                                                      fieldNames: [CS_ITEM_SK, CS_EXT_LIST_PRICE, $f2]
+                                                      projection: [CS_ITEM_SK, CS_EXT_LIST_PRICE, +(+(CR_REFUNDED_CASH, CR_REVERSED_CHARGE), CR_STORE_CREDIT)]
                                                       est: (rows=144067)
                                                     HashJoin
-                                                        condition: AND(=(CS_ITEM_SK, CR_ITEM_SK), =(CS_ORDER_NUMBER, CR_ORDER_NUMBER))
-                                                        joinType: inner
+                                                        predicate: AND(=(CS_ITEM_SK, CR_ITEM_SK), =(CS_ORDER_NUMBER, CR_ORDER_NUMBER))
+                                                        type: inner
                                                         est: (rows=144067)
                                                       TableScan
                                                           table: PUBLIC.CATALOG_SALES
-                                                          fields: [CS_ITEM_SK, CS_ORDER_NUMBER, CS_EXT_LIST_PRICE]
+                                                          fieldNames: [CS_ITEM_SK, CS_ORDER_NUMBER, CS_EXT_LIST_PRICE]
                                                           est: (rows=1441548)
                                                       Exchange
                                                           distribution: affinity[tableId=51, zoneId=51][0, 1]
                                                           est: (rows=144067)
                                                         TableScan
                                                             table: PUBLIC.CATALOG_RETURNS
-                                                            fields: [CR_ITEM_SK, CR_ORDER_NUMBER, CR_REFUNDED_CASH, CR_REVERSED_CHARGE, CR_STORE_CREDIT]
+                                                            fieldNames: [CR_ITEM_SK, CR_ORDER_NUMBER, CR_REFUNDED_CASH, CR_REVERSED_CHARGE, CR_STORE_CREDIT]
                                                             est: (rows=144067)
                                         Exchange
                                             distribution: single
                                             est: (rows=24325)
                                           TableScan
                                               table: PUBLIC.DATE_DIM
-                                              filters: =(D_YEAR, 1999)
-                                              fields: [D_DATE_SK, D_YEAR]
+                                              predicate: =(D_YEAR, 1999)
+                                              fieldNames: [D_DATE_SK, D_YEAR]
                                               est: (rows=24325)
                               Exchange
                                   distribution: single
                                   est: (rows=20)
                                 TableScan
                                     table: PUBLIC.INCOME_BAND
-                                    fields: [IB_INCOME_BAND_SK]
+                                    fieldNames: [IB_INCOME_BAND_SK]
                                     est: (rows=20)
                           Exchange
                               distribution: single
                               est: (rows=20)
                             TableScan
                                 table: PUBLIC.INCOME_BAND
-                                fields: [IB_INCOME_BAND_SK]
+                                fieldNames: [IB_INCOME_BAND_SK]
                                 est: (rows=20)
                     Exchange
                         distribution: single
                         est: (rows=300)
                       TableScan
                           table: PUBLIC.PROMOTION
-                          fields: [P_PROMO_SK]
+                          fieldNames: [P_PROMO_SK]
                           est: (rows=300)
                   Exchange
                       distribution: single
                       est: (rows=12)
                     TableScan
                         table: PUBLIC.STORE
-                        fields: [S_STORE_SK, S_STORE_NAME, S_ZIP]
+                        fieldNames: [S_STORE_SK, S_STORE_NAME, S_ZIP]
                         est: (rows=12)
       ColocatedHashAggregate
+          fieldNames: [PRODUCT_NAME, ITEM_SK, STORE_NAME, STORE_ZIP, B_STREET_NUMBER, B_STREET_NAME, B_CITY, B_ZIP, C_STREET_NUMBER, C_STREET_NAME, C_CITY, C_ZIP, SYEAR, FSYEAR, S2YEAR, CNT, S1, S2, S3]
           group: [PRODUCT_NAME, ITEM_SK, STORE_NAME, STORE_ZIP, B_STREET_NUMBER, B_STREET_NAME, B_CITY, B_ZIP, C_STREET_NUMBER, C_STREET_NAME, C_CITY, C_ZIP, SYEAR, FSYEAR, S2YEAR]
-          aggs: [CNT=COUNT(), S1=SUM(SS_WHOLESALE_COST), S2=SUM(SS_LIST_PRICE), S3=SUM(SS_COUPON_AMT)]
+          aggregation: [COUNT(), SUM(SS_WHOLESALE_COST), SUM(SS_LIST_PRICE), SUM(SS_COUPON_AMT)]
           est: (rows=2887)
         Project
-            fields: [PRODUCT_NAME, ITEM_SK, STORE_NAME, STORE_ZIP, B_STREET_NUMBER, B_STREET_NAME, B_CITY, B_ZIP, C_STREET_NUMBER, C_STREET_NAME, C_CITY, C_ZIP, SYEAR, FSYEAR, S2YEAR, SS_WHOLESALE_COST, SS_LIST_PRICE, SS_COUPON_AMT]
-            exprs: [I_PRODUCT_NAME, I_ITEM_SK, S_STORE_NAME, S_ZIP, CA_STREET_NUMBER, CA_STREET_NAME, CA_CITY, CA_ZIP, CA_STREET_NUMBER0, CA_STREET_NAME0, CA_CITY0, CA_ZIP0, D_YEAR00, D_YEAR, D_YEAR0, SS_WHOLESALE_COST, SS_LIST_PRICE, SS_COUPON_AMT]
+            fieldNames: [PRODUCT_NAME, ITEM_SK, STORE_NAME, STORE_ZIP, B_STREET_NUMBER, B_STREET_NAME, B_CITY, B_ZIP, C_STREET_NUMBER, C_STREET_NAME, C_CITY, C_ZIP, SYEAR, FSYEAR, S2YEAR, SS_WHOLESALE_COST, SS_LIST_PRICE, SS_COUPON_AMT]
+            projection: [I_PRODUCT_NAME, I_ITEM_SK, S_STORE_NAME, S_ZIP, CA_STREET_NUMBER, CA_STREET_NAME, CA_CITY, CA_ZIP, CA_STREET_NUMBER0, CA_STREET_NAME0, CA_CITY0, CA_ZIP0, D_YEAR00, D_YEAR, D_YEAR0, SS_WHOLESALE_COST, SS_LIST_PRICE, SS_COUPON_AMT]
             est: (rows=2992)
           HashJoin
-              condition: AND(=(SS_ITEM_SK, SR_ITEM_SK), =(SS_TICKET_NUMBER, SR_TICKET_NUMBER))
-              joinType: inner
+              predicate: AND(=(SS_ITEM_SK, SR_ITEM_SK), =(SS_TICKET_NUMBER, SR_TICKET_NUMBER))
+              type: inner
               est: (rows=2992)
             Exchange
                 distribution: single
                 est: (rows=287514)
               TableScan
                   table: PUBLIC.STORE_RETURNS
-                  fields: [SR_ITEM_SK, SR_TICKET_NUMBER]
+                  fieldNames: [SR_ITEM_SK, SR_TICKET_NUMBER]
                   est: (rows=287514)
             HashJoin
-                condition: =(C_FIRST_SALES_DATE_SK, D_DATE_SK)
-                joinType: inner
+                predicate: =(C_FIRST_SALES_DATE_SK, D_DATE_SK)
+                fieldNames: [D_DATE_SK, D_YEAR, D_DATE_SK0, D_YEAR0, CA_ADDRESS_SK, CA_STREET_NUMBER, CA_STREET_NAME, CA_CITY, CA_ZIP, CA_ADDRESS_SK0, CA_STREET_NUMBER0, CA_STREET_NAME0, CA_CITY0, CA_ZIP0, HD_DEMO_SK, HD_INCOME_BAND_SK, HD_DEMO_SK0, HD_INCOME_BAND_SK0, CD_DEMO_SK, CD_MARITAL_STATUS, CD_DEMO_SK0, CD_MARITAL_STATUS0, C_CUSTOMER_SK, C_CURRENT_CDEMO_SK, C_CURRENT_HDEMO_SK, C_CURRENT_ADDR_SK, C_FIRST_SHIPTO_DATE_SK, C_FIRST_SALES_DATE_SK, SS_SOLD_DATE_SK, SS_ITEM_SK, SS_CUSTO [...]
+                type: inner
                 est: (rows=90684167)
               Exchange
                   distribution: single
                   est: (rows=73049)
                 TableScan
                     table: PUBLIC.DATE_DIM
-                    fields: [D_DATE_SK, D_YEAR]
+                    fieldNames: [D_DATE_SK, D_YEAR]
                     est: (rows=73049)
               HashJoin
-                  condition: =(C_FIRST_SHIPTO_DATE_SK, D_DATE_SK)
-                  joinType: inner
+                  predicate: =(C_FIRST_SHIPTO_DATE_SK, D_DATE_SK)
+                  fieldNames: [D_DATE_SK, D_YEAR, CA_ADDRESS_SK, CA_STREET_NUMBER, CA_STREET_NAME, CA_CITY, CA_ZIP, CA_ADDRESS_SK0, CA_STREET_NUMBER0, CA_STREET_NAME0, CA_CITY0, CA_ZIP0, HD_DEMO_SK, HD_INCOME_BAND_SK, HD_DEMO_SK0, HD_INCOME_BAND_SK0, CD_DEMO_SK, CD_MARITAL_STATUS, CD_DEMO_SK0, CD_MARITAL_STATUS0, C_CUSTOMER_SK, C_CURRENT_CDEMO_SK, C_CURRENT_HDEMO_SK, C_CURRENT_ADDR_SK, C_FIRST_SHIPTO_DATE_SK, C_FIRST_SALES_DATE_SK, SS_SOLD_DATE_SK, SS_ITEM_SK, SS_CUSTOMER_SK, SS_CDEMO_SK [...]
+                  type: inner
                   est: (rows=90684167)
                 Exchange
                     distribution: single
                     est: (rows=73049)
                   TableScan
                       table: PUBLIC.DATE_DIM
-                      fields: [D_DATE_SK, D_YEAR]
+                      fieldNames: [D_DATE_SK, D_YEAR]
                       est: (rows=73049)
                 HashJoin
-                    condition: =(SS_STORE_SK, S_STORE_SK)
-                    joinType: inner
+                    predicate: =(SS_STORE_SK, S_STORE_SK)
+                    type: inner
                     est: (rows=90684167)
                   HashJoin
-                      condition: =(SS_PROMO_SK, P_PROMO_SK)
-                      joinType: inner
+                      predicate: =(SS_PROMO_SK, P_PROMO_SK)
+                      type: inner
                       est: (rows=90684167)
                     HashJoin
-                        condition: =(SS_ADDR_SK, CA_ADDRESS_SK)
-                        joinType: inner
+                        predicate: =(SS_ADDR_SK, CA_ADDRESS_SK)
+                        fieldNames: [CA_ADDRESS_SK, CA_STREET_NUMBER, CA_STREET_NAME, CA_CITY, CA_ZIP, CA_ADDRESS_SK0, CA_STREET_NUMBER0, CA_STREET_NAME0, CA_CITY0, CA_ZIP0, HD_DEMO_SK, HD_INCOME_BAND_SK, HD_DEMO_SK0, HD_INCOME_BAND_SK0, CD_DEMO_SK, CD_MARITAL_STATUS, CD_DEMO_SK0, CD_MARITAL_STATUS0, C_CUSTOMER_SK, C_CURRENT_CDEMO_SK, C_CURRENT_HDEMO_SK, C_CURRENT_ADDR_SK, C_FIRST_SHIPTO_DATE_SK, C_FIRST_SALES_DATE_SK, SS_SOLD_DATE_SK, SS_ITEM_SK, SS_CUSTOMER_SK, SS_CDEMO_SK, SS_HDEMO_SK [...]
+                        type: inner
                         est: (rows=90684167)
                       Exchange
                           distribution: single
                           est: (rows=50000)
                         TableScan
                             table: PUBLIC.CUSTOMER_ADDRESS
-                            fields: [CA_ADDRESS_SK, CA_STREET_NUMBER, CA_STREET_NAME, CA_CITY, CA_ZIP]
+                            fieldNames: [CA_ADDRESS_SK, CA_STREET_NUMBER, CA_STREET_NAME, CA_CITY, CA_ZIP]
                             est: (rows=50000)
                       HashJoin
-                          condition: =(C_CURRENT_ADDR_SK, CA_ADDRESS_SK)
-                          joinType: inner
+                          predicate: =(C_CURRENT_ADDR_SK, CA_ADDRESS_SK)
+                          type: inner
                           est: (rows=90684167)
                         Exchange
                             distribution: single
                             est: (rows=50000)
                           TableScan
                               table: PUBLIC.CUSTOMER_ADDRESS
-                              fields: [CA_ADDRESS_SK, CA_STREET_NUMBER, CA_STREET_NAME, CA_CITY, CA_ZIP]
+                              fieldNames: [CA_ADDRESS_SK, CA_STREET_NUMBER, CA_STREET_NAME, CA_CITY, CA_ZIP]
                               est: (rows=50000)
                         HashJoin
-                            condition: =(HD_INCOME_BAND_SK, IB_INCOME_BAND_SK0)
-                            joinType: inner
+                            predicate: =(HD_INCOME_BAND_SK, IB_INCOME_BAND_SK0)
+                            fieldNames: [HD_DEMO_SK, HD_INCOME_BAND_SK, HD_DEMO_SK0, HD_INCOME_BAND_SK0, CD_DEMO_SK, CD_MARITAL_STATUS, CD_DEMO_SK0, CD_MARITAL_STATUS0, C_CUSTOMER_SK, C_CURRENT_CDEMO_SK, C_CURRENT_HDEMO_SK, C_CURRENT_ADDR_SK, C_FIRST_SHIPTO_DATE_SK, C_FIRST_SALES_DATE_SK, SS_SOLD_DATE_SK, SS_ITEM_SK, SS_CUSTOMER_SK, SS_CDEMO_SK, SS_HDEMO_SK, SS_ADDR_SK, SS_STORE_SK, SS_PROMO_SK, SS_TICKET_NUMBER, SS_WHOLESALE_COST, SS_LIST_PRICE, SS_COUPON_AMT, I_ITEM_SK, I_CURRENT_PRICE [...]
+                            type: inner
                             est: (rows=90684167)
                           HashJoin
-                              condition: =(SS_HDEMO_SK, HD_DEMO_SK)
-                              joinType: inner
+                              predicate: =(SS_HDEMO_SK, HD_DEMO_SK)
+                              fieldNames: [HD_DEMO_SK, HD_INCOME_BAND_SK, HD_DEMO_SK0, HD_INCOME_BAND_SK0, CD_DEMO_SK, CD_MARITAL_STATUS, CD_DEMO_SK0, CD_MARITAL_STATUS0, C_CUSTOMER_SK, C_CURRENT_CDEMO_SK, C_CURRENT_HDEMO_SK, C_CURRENT_ADDR_SK, C_FIRST_SHIPTO_DATE_SK, C_FIRST_SALES_DATE_SK, SS_SOLD_DATE_SK, SS_ITEM_SK, SS_CUSTOMER_SK, SS_CDEMO_SK, SS_HDEMO_SK, SS_ADDR_SK, SS_STORE_SK, SS_PROMO_SK, SS_TICKET_NUMBER, SS_WHOLESALE_COST, SS_LIST_PRICE, SS_COUPON_AMT, I_ITEM_SK, I_CURRENT_PRI [...]
+                              type: inner
                               est: (rows=90684167)
                             Exchange
                                 distribution: single
                                 est: (rows=7200)
                               TableScan
                                   table: PUBLIC.HOUSEHOLD_DEMOGRAPHICS
-                                  fields: [HD_DEMO_SK, HD_INCOME_BAND_SK]
+                                  fieldNames: [HD_DEMO_SK, HD_INCOME_BAND_SK]
                                   est: (rows=7200)
                             HashJoin
-                                condition: =(HD_INCOME_BAND_SK, IB_INCOME_BAND_SK)
-                                joinType: inner
+                                predicate: =(HD_INCOME_BAND_SK, IB_INCOME_BAND_SK)
+                                type: inner
                                 est: (rows=90684167)
                               HashJoin
-                                  condition: =(C_CURRENT_HDEMO_SK, HD_DEMO_SK)
-                                  joinType: inner
+                                  predicate: =(C_CURRENT_HDEMO_SK, HD_DEMO_SK)
+                                  type: inner
                                   est: (rows=90684167)
                                 Exchange
                                     distribution: single
                                     est: (rows=7200)
                                   TableScan
                                       table: PUBLIC.HOUSEHOLD_DEMOGRAPHICS
-                                      fields: [HD_DEMO_SK, HD_INCOME_BAND_SK]
+                                      fieldNames: [HD_DEMO_SK, HD_INCOME_BAND_SK]
                                       est: (rows=7200)
                                 HashJoin
-                                    condition: AND(<>(CD_MARITAL_STATUS, CD_MARITAL_STATUS0), =(SS_CDEMO_SK, CD_DEMO_SK))
-                                    joinType: inner
+                                    predicate: AND(<>(CD_MARITAL_STATUS, CD_MARITAL_STATUS0), =(SS_CDEMO_SK, CD_DEMO_SK))
+                                    fieldNames: [CD_DEMO_SK, CD_MARITAL_STATUS, CD_DEMO_SK0, CD_MARITAL_STATUS0, C_CUSTOMER_SK, C_CURRENT_CDEMO_SK, C_CURRENT_HDEMO_SK, C_CURRENT_ADDR_SK, C_FIRST_SHIPTO_DATE_SK, C_FIRST_SALES_DATE_SK, SS_SOLD_DATE_SK, SS_ITEM_SK, SS_CUSTOMER_SK, SS_CDEMO_SK, SS_HDEMO_SK, SS_ADDR_SK, SS_STORE_SK, SS_PROMO_SK, SS_TICKET_NUMBER, SS_WHOLESALE_COST, SS_LIST_PRICE, SS_COUPON_AMT, I_ITEM_SK, I_CURRENT_PRICE, I_COLOR, I_PRODUCT_NAME, CS_ITEM_SK, SALE, REFUND, D_D [...]
+                                    type: inner
                                     est: (rows=90684167)
                                   Exchange
                                       distribution: single
                                       est: (rows=1920800)
                                     TableScan
                                         table: PUBLIC.CUSTOMER_DEMOGRAPHICS
-                                        fields: [CD_DEMO_SK, CD_MARITAL_STATUS]
+                                        fieldNames: [CD_DEMO_SK, CD_MARITAL_STATUS]
                                         est: (rows=1920800)
                                   HashJoin
-                                      condition: =(C_CURRENT_CDEMO_SK, CD_DEMO_SK)
-                                      joinType: inner
+                                      predicate: =(C_CURRENT_CDEMO_SK, CD_DEMO_SK)
+                                      type: inner
                                       est: (rows=129548810)
                                     Exchange
                                         distribution: single
                                         est: (rows=1920800)
                                       TableScan
                                           table: PUBLIC.CUSTOMER_DEMOGRAPHICS
-                                          fields: [CD_DEMO_SK, CD_MARITAL_STATUS]
+                                          fieldNames: [CD_DEMO_SK, CD_MARITAL_STATUS]
                                           est: (rows=1920800)
                                     HashJoin
-                                        condition: =(SS_CUSTOMER_SK, C_CUSTOMER_SK)
-                                        joinType: inner
+                                        predicate: =(SS_CUSTOMER_SK, C_CUSTOMER_SK)
+                                        type: inner
                                         est: (rows=129548810)
                                       Exchange
                                           distribution: single
                                           est: (rows=100000)
                                         TableScan
                                             table: PUBLIC.CUSTOMER
-                                            fields: [C_CUSTOMER_SK, C_CURRENT_CDEMO_SK, C_CURRENT_HDEMO_SK, C_CURRENT_ADDR_SK, C_FIRST_SHIPTO_DATE_SK, C_FIRST_SALES_DATE_SK]
+                                            fieldNames: [C_CUSTOMER_SK, C_CURRENT_CDEMO_SK, C_CURRENT_HDEMO_SK, C_CURRENT_ADDR_SK, C_FIRST_SHIPTO_DATE_SK, C_FIRST_SALES_DATE_SK]
                                             est: (rows=100000)
                                       HashJoin
-                                          condition: =(SS_SOLD_DATE_SK, D_DATE_SK)
-                                          joinType: inner
+                                          predicate: =(SS_SOLD_DATE_SK, D_DATE_SK)
+                                          type: inner
                                           est: (rows=129548810)
                                         HashJoin
-                                            condition: =(SS_ITEM_SK, CS_ITEM_SK)
-                                            joinType: inner
+                                            predicate: =(SS_ITEM_SK, CS_ITEM_SK)
+                                            type: inner
                                             est: (rows=389035465)
                                           HashJoin
-                                              condition: =(SS_ITEM_SK, I_ITEM_SK)
-                                              joinType: inner
+                                              predicate: =(SS_ITEM_SK, I_ITEM_SK)
+                                              type: inner
                                               est: (rows=180025)
                                             Exchange
                                                 distribution: single
                                                 est: (rows=2880404)
                                               TableScan
                                                   table: PUBLIC.STORE_SALES
-                                                  fields: [SS_SOLD_DATE_SK, SS_ITEM_SK, SS_CUSTOMER_SK, SS_CDEMO_SK, SS_HDEMO_SK, SS_ADDR_SK, SS_STORE_SK, SS_PROMO_SK, SS_TICKET_NUMBER, SS_WHOLESALE_COST, SS_LIST_PRICE, SS_COUPON_AMT]
+                                                  fieldNames: [SS_SOLD_DATE_SK, SS_ITEM_SK, SS_CUSTOMER_SK, SS_CDEMO_SK, SS_HDEMO_SK, SS_ADDR_SK, SS_STORE_SK, SS_PROMO_SK, SS_TICKET_NUMBER, SS_WHOLESALE_COST, SS_LIST_PRICE, SS_COUPON_AMT]
                                                   est: (rows=2880404)
                                             Exchange
                                                 distribution: single
                                                 est: (rows=1125)
                                               TableScan
                                                   table: PUBLIC.ITEM
-                                                  filters: AND(SEARCH(I_COLOR, Sarg[_UTF-8'azure':VARCHAR(20) CHARACTER SET "UTF-8", _UTF-8'blush':VARCHAR(20) CHARACTER SET "UTF-8", _UTF-8'gainsboro':VARCHAR(20) CHARACTER SET "UTF-8", _UTF-8'hot':VARCHAR(20) CHARACTER SET "UTF-8", _UTF-8'lemon':VARCHAR(20) CHARACTER SET "UTF-8", _UTF-8'misty':VARCHAR(20) CHARACTER SET "UTF-8"]:VARCHAR(20) CHARACTER SET "UTF-8"), >=(CAST(I_CURRENT_PRICE):DECIMAL(12, 2), 80.00), <=(CAST(I_CURRENT_PRICE):DE [...]
-                                                  fields: [I_ITEM_SK, I_CURRENT_PRICE, I_COLOR, I_PRODUCT_NAME]
+                                                  predicate: AND(SEARCH(I_COLOR, Sarg[_UTF-8'azure':VARCHAR(20) CHARACTER SET "UTF-8", _UTF-8'blush':VARCHAR(20) CHARACTER SET "UTF-8", _UTF-8'gainsboro':VARCHAR(20) CHARACTER SET "UTF-8", _UTF-8'hot':VARCHAR(20) CHARACTER SET "UTF-8", _UTF-8'lemon':VARCHAR(20) CHARACTER SET "UTF-8", _UTF-8'misty':VARCHAR(20) CHARACTER SET "UTF-8"]:VARCHAR(20) CHARACTER SET "UTF-8"), >=(CAST(I_CURRENT_PRICE):DECIMAL(12, 2), 80.00), <=(CAST(I_CURRENT_PRICE): [...]
+                                                  fieldNames: [I_ITEM_SK, I_CURRENT_PRICE, I_COLOR, I_PRODUCT_NAME]
                                                   est: (rows=1125)
                                           Filter
-                                              condition: >(SALE, *(2, REFUND))
+                                              predicate: >(SALE, *(2, REFUND))
                                               est: (rows=14407)
                                             ReduceHashAggregate
-                                                rowType: RecordType(INTEGER CS_ITEM_SK, DECIMAL(32767, 2) SALE, DECIMAL(32767, 2) REFUND)
+                                                fieldNames: [CS_ITEM_SK, SALE, REFUND]
                                                 group: [CS_ITEM_SK]
-                                                aggs: [SALE=SUM(_ACC0), REFUND=SUM(_ACC1)]
+                                                aggregation: [SUM(_ACC0), SUM(_ACC1)]
                                                 est: (rows=28813)
                                               Exchange
                                                   distribution: single
                                                   est: (rows=28813)
                                                 MapHashAggregate
+                                                    fieldNames: [CS_ITEM_SK, _ACC0, _ACC1, _GROUP_ID]
                                                     group: [CS_ITEM_SK]
-                                                    aggs: [SALE=SUM(CS_EXT_LIST_PRICE), REFUND=SUM($f2)]
+                                                    aggregation: [SUM(CS_EXT_LIST_PRICE), SUM($f2)]
                                                     est: (rows=28813)
                                                   Project
-                                                      fields: [CS_ITEM_SK, CS_EXT_LIST_PRICE, $f2]
-                                                      exprs: [CS_ITEM_SK, CS_EXT_LIST_PRICE, +(+(CR_REFUNDED_CASH, CR_REVERSED_CHARGE), CR_STORE_CREDIT)]
+                                                      fieldNames: [CS_ITEM_SK, CS_EXT_LIST_PRICE, $f2]
+                                                      projection: [CS_ITEM_SK, CS_EXT_LIST_PRICE, +(+(CR_REFUNDED_CASH, CR_REVERSED_CHARGE), CR_STORE_CREDIT)]
                                                       est: (rows=144067)
                                                     HashJoin
-                                                        condition: AND(=(CS_ITEM_SK, CR_ITEM_SK), =(CS_ORDER_NUMBER, CR_ORDER_NUMBER))
-                                                        joinType: inner
+                                                        predicate: AND(=(CS_ITEM_SK, CR_ITEM_SK), =(CS_ORDER_NUMBER, CR_ORDER_NUMBER))
+                                                        type: inner
                                                         est: (rows=144067)
                                                       TableScan
                                                           table: PUBLIC.CATALOG_SALES
-                                                          fields: [CS_ITEM_SK, CS_ORDER_NUMBER, CS_EXT_LIST_PRICE]
+                                                          fieldNames: [CS_ITEM_SK, CS_ORDER_NUMBER, CS_EXT_LIST_PRICE]
                                                           est: (rows=1441548)
                                                       Exchange
                                                           distribution: affinity[tableId=51, zoneId=51][0, 1]
                                                           est: (rows=144067)
                                                         TableScan
                                                             table: PUBLIC.CATALOG_RETURNS
-                                                            fields: [CR_ITEM_SK, CR_ORDER_NUMBER, CR_REFUNDED_CASH, CR_REVERSED_CHARGE, CR_STORE_CREDIT]
+                                                            fieldNames: [CR_ITEM_SK, CR_ORDER_NUMBER, CR_REFUNDED_CASH, CR_REVERSED_CHARGE, CR_STORE_CREDIT]
                                                             est: (rows=144067)
                                         Exchange
                                             distribution: single
                                             est: (rows=24325)
                                           TableScan
                                               table: PUBLIC.DATE_DIM
-                                              filters: =(D_YEAR, +(1999, 1))
-                                              fields: [D_DATE_SK, D_YEAR]
+                                              predicate: =(D_YEAR, +(1999, 1))
+                                              fieldNames: [D_DATE_SK, D_YEAR]
                                               est: (rows=24325)
                               Exchange
                                   distribution: single
                                   est: (rows=20)
                                 TableScan
                                     table: PUBLIC.INCOME_BAND
-                                    fields: [IB_INCOME_BAND_SK]
+                                    fieldNames: [IB_INCOME_BAND_SK]
                                     est: (rows=20)
                           Exchange
                               distribution: single
                               est: (rows=20)
                             TableScan
                                 table: PUBLIC.INCOME_BAND
-                                fields: [IB_INCOME_BAND_SK]
+                                fieldNames: [IB_INCOME_BAND_SK]
                                 est: (rows=20)
                     Exchange
                         distribution: single
                         est: (rows=300)
                       TableScan
                           table: PUBLIC.PROMOTION
-                          fields: [P_PROMO_SK]
+                          fieldNames: [P_PROMO_SK]
                           est: (rows=300)
                   Exchange
                       distribution: single
                       est: (rows=12)
                     TableScan
                         table: PUBLIC.STORE
-                        fields: [S_STORE_SK, S_STORE_NAME, S_ZIP]
+                        fieldNames: [S_STORE_SK, S_STORE_NAME, S_ZIP]
                         est: (rows=12)
diff --git a/modules/sql-engine/src/test/resources/tpcds/plan/q64_colocated.plan b/modules/sql-engine/src/test/resources/tpcds/plan/q64_colocated.plan
index 063f1df99b5..d2dcc91fa18 100644
--- a/modules/sql-engine/src/test/resources/tpcds/plan/q64_colocated.plan
+++ b/modules/sql-engine/src/test/resources/tpcds/plan/q64_colocated.plan
@@ -2,466 +2,483 @@ Sort
     collation: [PRODUCT_NAME ASC, STORE_NAME ASC, CNT0 ASC, S11 ASC, S12 ASC]
     est: (rows=21)
   Project
-      fields: [PRODUCT_NAME, STORE_NAME, STORE_ZIP, B_STREET_NUMBER, B_STREET_NAME, B_CITY, B_ZIP, C_STREET_NUMBER, C_STREET_NAME, C_CITY, C_ZIP, SYEAR, CNT, S11, S21, S31, S12, S22, S32, SYEAR0, CNT0]
-      exprs: [PRODUCT_NAME, STORE_NAME, STORE_ZIP, B_STREET_NUMBER, B_STREET_NAME, B_CITY, B_ZIP, C_STREET_NUMBER, C_STREET_NAME, C_CITY, C_ZIP, SYEAR, CNT, S1, S2, S3, S10, S20, S30, SYEAR0, CNT0]
+      fieldNames: [PRODUCT_NAME, STORE_NAME, STORE_ZIP, B_STREET_NUMBER, B_STREET_NAME, B_CITY, B_ZIP, C_STREET_NUMBER, C_STREET_NAME, C_CITY, C_ZIP, SYEAR, CNT, S11, S21, S31, S12, S22, S32, SYEAR0, CNT0]
+      projection: [PRODUCT_NAME, STORE_NAME, STORE_ZIP, B_STREET_NUMBER, B_STREET_NAME, B_CITY, B_ZIP, C_STREET_NUMBER, C_STREET_NAME, C_CITY, C_ZIP, SYEAR, CNT, S1, S2, S3, S10, S20, S30, SYEAR0, CNT0]
       est: (rows=21)
     HashJoin
-        condition: AND(=(ITEM_SK, ITEM_SK0), <=(CNT0, CNT), =(STORE_NAME, STORE_NAME0), =(STORE_ZIP, STORE_ZIP0))
-        joinType: inner
+        predicate: AND(=(ITEM_SK, ITEM_SK0), <=(CNT0, CNT), =(STORE_NAME, STORE_NAME0), =(STORE_ZIP, STORE_ZIP0))
+        fieldNames: [PRODUCT_NAME, ITEM_SK, STORE_NAME, STORE_ZIP, B_STREET_NUMBER, B_STREET_NAME, B_CITY, B_ZIP, C_STREET_NUMBER, C_STREET_NAME, C_CITY, C_ZIP, SYEAR, FSYEAR, S2YEAR, CNT, S1, S2, S3, PRODUCT_NAME0, ITEM_SK0, STORE_NAME0, STORE_ZIP0, B_STREET_NUMBER0, B_STREET_NAME0, B_CITY0, B_ZIP0, C_STREET_NUMBER0, C_STREET_NAME0, C_CITY0, C_ZIP0, SYEAR0, FSYEAR0, S2YEAR0, CNT0, S10, S20, S30]
+        type: inner
         est: (rows=21)
       ColocatedHashAggregate
+          fieldNames: [PRODUCT_NAME, ITEM_SK, STORE_NAME, STORE_ZIP, B_STREET_NUMBER, B_STREET_NAME, B_CITY, B_ZIP, C_STREET_NUMBER, C_STREET_NAME, C_CITY, C_ZIP, SYEAR, FSYEAR, S2YEAR, CNT, S1, S2, S3]
           group: [PRODUCT_NAME, ITEM_SK, STORE_NAME, STORE_ZIP, B_STREET_NUMBER, B_STREET_NAME, B_CITY, B_ZIP, C_STREET_NUMBER, C_STREET_NAME, C_CITY, C_ZIP, SYEAR, FSYEAR, S2YEAR]
-          aggs: [CNT=COUNT(), S1=SUM(SS_WHOLESALE_COST), S2=SUM(SS_LIST_PRICE), S3=SUM(SS_COUPON_AMT)]
+          aggregation: [COUNT(), SUM(SS_WHOLESALE_COST), SUM(SS_LIST_PRICE), SUM(SS_COUPON_AMT)]
           est: (rows=2887)
         Project
-            fields: [PRODUCT_NAME, ITEM_SK, STORE_NAME, STORE_ZIP, B_STREET_NUMBER, B_STREET_NAME, B_CITY, B_ZIP, C_STREET_NUMBER, C_STREET_NAME, C_CITY, C_ZIP, SYEAR, FSYEAR, S2YEAR, SS_WHOLESALE_COST, SS_LIST_PRICE, SS_COUPON_AMT]
-            exprs: [I_PRODUCT_NAME, I_ITEM_SK, S_STORE_NAME, S_ZIP, CA_STREET_NUMBER, CA_STREET_NAME, CA_CITY, CA_ZIP, CA_STREET_NUMBER0, CA_STREET_NAME0, CA_CITY0, CA_ZIP0, D_YEAR00, D_YEAR, D_YEAR0, SS_WHOLESALE_COST, SS_LIST_PRICE, SS_COUPON_AMT]
+            fieldNames: [PRODUCT_NAME, ITEM_SK, STORE_NAME, STORE_ZIP, B_STREET_NUMBER, B_STREET_NAME, B_CITY, B_ZIP, C_STREET_NUMBER, C_STREET_NAME, C_CITY, C_ZIP, SYEAR, FSYEAR, S2YEAR, SS_WHOLESALE_COST, SS_LIST_PRICE, SS_COUPON_AMT]
+            projection: [I_PRODUCT_NAME, I_ITEM_SK, S_STORE_NAME, S_ZIP, CA_STREET_NUMBER, CA_STREET_NAME, CA_CITY, CA_ZIP, CA_STREET_NUMBER0, CA_STREET_NAME0, CA_CITY0, CA_ZIP0, D_YEAR00, D_YEAR, D_YEAR0, SS_WHOLESALE_COST, SS_LIST_PRICE, SS_COUPON_AMT]
             est: (rows=2992)
           HashJoin
-              condition: AND(=(SS_ITEM_SK, SR_ITEM_SK), =(SS_TICKET_NUMBER, SR_TICKET_NUMBER))
-              joinType: inner
+              predicate: AND(=(SS_ITEM_SK, SR_ITEM_SK), =(SS_TICKET_NUMBER, SR_TICKET_NUMBER))
+              type: inner
               est: (rows=2992)
             Exchange
                 distribution: single
                 est: (rows=287514)
               TableScan
                   table: PUBLIC.STORE_RETURNS
-                  fields: [SR_ITEM_SK, SR_TICKET_NUMBER]
+                  fieldNames: [SR_ITEM_SK, SR_TICKET_NUMBER]
                   est: (rows=287514)
             HashJoin
-                condition: =(C_FIRST_SALES_DATE_SK, D_DATE_SK)
-                joinType: inner
+                predicate: =(C_FIRST_SALES_DATE_SK, D_DATE_SK)
+                fieldNames: [D_DATE_SK, D_YEAR, D_DATE_SK0, D_YEAR0, CA_ADDRESS_SK, CA_STREET_NUMBER, CA_STREET_NAME, CA_CITY, CA_ZIP, CA_ADDRESS_SK0, CA_STREET_NUMBER0, CA_STREET_NAME0, CA_CITY0, CA_ZIP0, HD_DEMO_SK, HD_INCOME_BAND_SK, HD_DEMO_SK0, HD_INCOME_BAND_SK0, CD_DEMO_SK, CD_MARITAL_STATUS, CD_DEMO_SK0, CD_MARITAL_STATUS0, C_CUSTOMER_SK, C_CURRENT_CDEMO_SK, C_CURRENT_HDEMO_SK, C_CURRENT_ADDR_SK, C_FIRST_SHIPTO_DATE_SK, C_FIRST_SALES_DATE_SK, SS_SOLD_DATE_SK, SS_ITEM_SK, SS_CUSTO [...]
+                type: inner
                 est: (rows=90684167)
               Exchange
                   distribution: single
                   est: (rows=73049)
                 TableScan
                     table: PUBLIC.DATE_DIM
-                    fields: [D_DATE_SK, D_YEAR]
+                    fieldNames: [D_DATE_SK, D_YEAR]
                     est: (rows=73049)
               HashJoin
-                  condition: =(C_FIRST_SHIPTO_DATE_SK, D_DATE_SK)
-                  joinType: inner
+                  predicate: =(C_FIRST_SHIPTO_DATE_SK, D_DATE_SK)
+                  fieldNames: [D_DATE_SK, D_YEAR, CA_ADDRESS_SK, CA_STREET_NUMBER, CA_STREET_NAME, CA_CITY, CA_ZIP, CA_ADDRESS_SK0, CA_STREET_NUMBER0, CA_STREET_NAME0, CA_CITY0, CA_ZIP0, HD_DEMO_SK, HD_INCOME_BAND_SK, HD_DEMO_SK0, HD_INCOME_BAND_SK0, CD_DEMO_SK, CD_MARITAL_STATUS, CD_DEMO_SK0, CD_MARITAL_STATUS0, C_CUSTOMER_SK, C_CURRENT_CDEMO_SK, C_CURRENT_HDEMO_SK, C_CURRENT_ADDR_SK, C_FIRST_SHIPTO_DATE_SK, C_FIRST_SALES_DATE_SK, SS_SOLD_DATE_SK, SS_ITEM_SK, SS_CUSTOMER_SK, SS_CDEMO_SK [...]
+                  type: inner
                   est: (rows=90684167)
                 Exchange
                     distribution: single
                     est: (rows=73049)
                   TableScan
                       table: PUBLIC.DATE_DIM
-                      fields: [D_DATE_SK, D_YEAR]
+                      fieldNames: [D_DATE_SK, D_YEAR]
                       est: (rows=73049)
                 HashJoin
-                    condition: =(SS_STORE_SK, S_STORE_SK)
-                    joinType: inner
+                    predicate: =(SS_STORE_SK, S_STORE_SK)
+                    type: inner
                     est: (rows=90684167)
                   HashJoin
-                      condition: =(SS_PROMO_SK, P_PROMO_SK)
-                      joinType: inner
+                      predicate: =(SS_PROMO_SK, P_PROMO_SK)
+                      type: inner
                       est: (rows=90684167)
                     HashJoin
-                        condition: =(SS_ADDR_SK, CA_ADDRESS_SK)
-                        joinType: inner
+                        predicate: =(SS_ADDR_SK, CA_ADDRESS_SK)
+                        fieldNames: [CA_ADDRESS_SK, CA_STREET_NUMBER, CA_STREET_NAME, CA_CITY, CA_ZIP, CA_ADDRESS_SK0, CA_STREET_NUMBER0, CA_STREET_NAME0, CA_CITY0, CA_ZIP0, HD_DEMO_SK, HD_INCOME_BAND_SK, HD_DEMO_SK0, HD_INCOME_BAND_SK0, CD_DEMO_SK, CD_MARITAL_STATUS, CD_DEMO_SK0, CD_MARITAL_STATUS0, C_CUSTOMER_SK, C_CURRENT_CDEMO_SK, C_CURRENT_HDEMO_SK, C_CURRENT_ADDR_SK, C_FIRST_SHIPTO_DATE_SK, C_FIRST_SALES_DATE_SK, SS_SOLD_DATE_SK, SS_ITEM_SK, SS_CUSTOMER_SK, SS_CDEMO_SK, SS_HDEMO_SK [...]
+                        type: inner
                         est: (rows=90684167)
                       Exchange
                           distribution: single
                           est: (rows=50000)
                         TableScan
                             table: PUBLIC.CUSTOMER_ADDRESS
-                            fields: [CA_ADDRESS_SK, CA_STREET_NUMBER, CA_STREET_NAME, CA_CITY, CA_ZIP]
+                            fieldNames: [CA_ADDRESS_SK, CA_STREET_NUMBER, CA_STREET_NAME, CA_CITY, CA_ZIP]
                             est: (rows=50000)
                       HashJoin
-                          condition: =(C_CURRENT_ADDR_SK, CA_ADDRESS_SK)
-                          joinType: inner
+                          predicate: =(C_CURRENT_ADDR_SK, CA_ADDRESS_SK)
+                          type: inner
                           est: (rows=90684167)
                         Exchange
                             distribution: single
                             est: (rows=50000)
                           TableScan
                               table: PUBLIC.CUSTOMER_ADDRESS
-                              fields: [CA_ADDRESS_SK, CA_STREET_NUMBER, CA_STREET_NAME, CA_CITY, CA_ZIP]
+                              fieldNames: [CA_ADDRESS_SK, CA_STREET_NUMBER, CA_STREET_NAME, CA_CITY, CA_ZIP]
                               est: (rows=50000)
                         HashJoin
-                            condition: =(HD_INCOME_BAND_SK, IB_INCOME_BAND_SK0)
-                            joinType: inner
+                            predicate: =(HD_INCOME_BAND_SK, IB_INCOME_BAND_SK0)
+                            fieldNames: [HD_DEMO_SK, HD_INCOME_BAND_SK, HD_DEMO_SK0, HD_INCOME_BAND_SK0, CD_DEMO_SK, CD_MARITAL_STATUS, CD_DEMO_SK0, CD_MARITAL_STATUS0, C_CUSTOMER_SK, C_CURRENT_CDEMO_SK, C_CURRENT_HDEMO_SK, C_CURRENT_ADDR_SK, C_FIRST_SHIPTO_DATE_SK, C_FIRST_SALES_DATE_SK, SS_SOLD_DATE_SK, SS_ITEM_SK, SS_CUSTOMER_SK, SS_CDEMO_SK, SS_HDEMO_SK, SS_ADDR_SK, SS_STORE_SK, SS_PROMO_SK, SS_TICKET_NUMBER, SS_WHOLESALE_COST, SS_LIST_PRICE, SS_COUPON_AMT, I_ITEM_SK, I_CURRENT_PRICE [...]
+                            type: inner
                             est: (rows=90684167)
                           HashJoin
-                              condition: =(SS_HDEMO_SK, HD_DEMO_SK)
-                              joinType: inner
+                              predicate: =(SS_HDEMO_SK, HD_DEMO_SK)
+                              fieldNames: [HD_DEMO_SK, HD_INCOME_BAND_SK, HD_DEMO_SK0, HD_INCOME_BAND_SK0, CD_DEMO_SK, CD_MARITAL_STATUS, CD_DEMO_SK0, CD_MARITAL_STATUS0, C_CUSTOMER_SK, C_CURRENT_CDEMO_SK, C_CURRENT_HDEMO_SK, C_CURRENT_ADDR_SK, C_FIRST_SHIPTO_DATE_SK, C_FIRST_SALES_DATE_SK, SS_SOLD_DATE_SK, SS_ITEM_SK, SS_CUSTOMER_SK, SS_CDEMO_SK, SS_HDEMO_SK, SS_ADDR_SK, SS_STORE_SK, SS_PROMO_SK, SS_TICKET_NUMBER, SS_WHOLESALE_COST, SS_LIST_PRICE, SS_COUPON_AMT, I_ITEM_SK, I_CURRENT_PRI [...]
+                              type: inner
                               est: (rows=90684167)
                             Exchange
                                 distribution: single
                                 est: (rows=7200)
                               TableScan
                                   table: PUBLIC.HOUSEHOLD_DEMOGRAPHICS
-                                  fields: [HD_DEMO_SK, HD_INCOME_BAND_SK]
+                                  fieldNames: [HD_DEMO_SK, HD_INCOME_BAND_SK]
                                   est: (rows=7200)
                             HashJoin
-                                condition: =(HD_INCOME_BAND_SK, IB_INCOME_BAND_SK)
-                                joinType: inner
+                                predicate: =(HD_INCOME_BAND_SK, IB_INCOME_BAND_SK)
+                                type: inner
                                 est: (rows=90684167)
                               HashJoin
-                                  condition: =(C_CURRENT_HDEMO_SK, HD_DEMO_SK)
-                                  joinType: inner
+                                  predicate: =(C_CURRENT_HDEMO_SK, HD_DEMO_SK)
+                                  type: inner
                                   est: (rows=90684167)
                                 Exchange
                                     distribution: single
                                     est: (rows=7200)
                                   TableScan
                                       table: PUBLIC.HOUSEHOLD_DEMOGRAPHICS
-                                      fields: [HD_DEMO_SK, HD_INCOME_BAND_SK]
+                                      fieldNames: [HD_DEMO_SK, HD_INCOME_BAND_SK]
                                       est: (rows=7200)
                                 HashJoin
-                                    condition: AND(<>(CD_MARITAL_STATUS, CD_MARITAL_STATUS0), =(SS_CDEMO_SK, CD_DEMO_SK))
-                                    joinType: inner
+                                    predicate: AND(<>(CD_MARITAL_STATUS, CD_MARITAL_STATUS0), =(SS_CDEMO_SK, CD_DEMO_SK))
+                                    fieldNames: [CD_DEMO_SK, CD_MARITAL_STATUS, CD_DEMO_SK0, CD_MARITAL_STATUS0, C_CUSTOMER_SK, C_CURRENT_CDEMO_SK, C_CURRENT_HDEMO_SK, C_CURRENT_ADDR_SK, C_FIRST_SHIPTO_DATE_SK, C_FIRST_SALES_DATE_SK, SS_SOLD_DATE_SK, SS_ITEM_SK, SS_CUSTOMER_SK, SS_CDEMO_SK, SS_HDEMO_SK, SS_ADDR_SK, SS_STORE_SK, SS_PROMO_SK, SS_TICKET_NUMBER, SS_WHOLESALE_COST, SS_LIST_PRICE, SS_COUPON_AMT, I_ITEM_SK, I_CURRENT_PRICE, I_COLOR, I_PRODUCT_NAME, CS_ITEM_SK, SALE, REFUND, D_D [...]
+                                    type: inner
                                     est: (rows=90684167)
                                   Exchange
                                       distribution: single
                                       est: (rows=1920800)
                                     TableScan
                                         table: PUBLIC.CUSTOMER_DEMOGRAPHICS
-                                        fields: [CD_DEMO_SK, CD_MARITAL_STATUS]
+                                        fieldNames: [CD_DEMO_SK, CD_MARITAL_STATUS]
                                         est: (rows=1920800)
                                   HashJoin
-                                      condition: =(C_CURRENT_CDEMO_SK, CD_DEMO_SK)
-                                      joinType: inner
+                                      predicate: =(C_CURRENT_CDEMO_SK, CD_DEMO_SK)
+                                      type: inner
                                       est: (rows=129548810)
                                     Exchange
                                         distribution: single
                                         est: (rows=1920800)
                                       TableScan
                                           table: PUBLIC.CUSTOMER_DEMOGRAPHICS
-                                          fields: [CD_DEMO_SK, CD_MARITAL_STATUS]
+                                          fieldNames: [CD_DEMO_SK, CD_MARITAL_STATUS]
                                           est: (rows=1920800)
                                     HashJoin
-                                        condition: =(SS_CUSTOMER_SK, C_CUSTOMER_SK)
-                                        joinType: inner
+                                        predicate: =(SS_CUSTOMER_SK, C_CUSTOMER_SK)
+                                        type: inner
                                         est: (rows=129548810)
                                       Exchange
                                           distribution: single
                                           est: (rows=100000)
                                         TableScan
                                             table: PUBLIC.CUSTOMER
-                                            fields: [C_CUSTOMER_SK, C_CURRENT_CDEMO_SK, C_CURRENT_HDEMO_SK, C_CURRENT_ADDR_SK, C_FIRST_SHIPTO_DATE_SK, C_FIRST_SALES_DATE_SK]
+                                            fieldNames: [C_CUSTOMER_SK, C_CURRENT_CDEMO_SK, C_CURRENT_HDEMO_SK, C_CURRENT_ADDR_SK, C_FIRST_SHIPTO_DATE_SK, C_FIRST_SALES_DATE_SK]
                                             est: (rows=100000)
                                       HashJoin
-                                          condition: =(SS_SOLD_DATE_SK, D_DATE_SK)
-                                          joinType: inner
+                                          predicate: =(SS_SOLD_DATE_SK, D_DATE_SK)
+                                          type: inner
                                           est: (rows=129548810)
                                         HashJoin
-                                            condition: =(SS_ITEM_SK, CS_ITEM_SK)
-                                            joinType: inner
+                                            predicate: =(SS_ITEM_SK, CS_ITEM_SK)
+                                            type: inner
                                             est: (rows=389035465)
                                           HashJoin
-                                              condition: =(SS_ITEM_SK, I_ITEM_SK)
-                                              joinType: inner
+                                              predicate: =(SS_ITEM_SK, I_ITEM_SK)
+                                              type: inner
                                               est: (rows=180025)
                                             Exchange
                                                 distribution: single
                                                 est: (rows=2880404)
                                               TableScan
                                                   table: PUBLIC.STORE_SALES
-                                                  fields: [SS_SOLD_DATE_SK, SS_ITEM_SK, SS_CUSTOMER_SK, SS_CDEMO_SK, SS_HDEMO_SK, SS_ADDR_SK, SS_STORE_SK, SS_PROMO_SK, SS_TICKET_NUMBER, SS_WHOLESALE_COST, SS_LIST_PRICE, SS_COUPON_AMT]
+                                                  fieldNames: [SS_SOLD_DATE_SK, SS_ITEM_SK, SS_CUSTOMER_SK, SS_CDEMO_SK, SS_HDEMO_SK, SS_ADDR_SK, SS_STORE_SK, SS_PROMO_SK, SS_TICKET_NUMBER, SS_WHOLESALE_COST, SS_LIST_PRICE, SS_COUPON_AMT]
                                                   est: (rows=2880404)
                                             Exchange
                                                 distribution: single
                                                 est: (rows=1125)
                                               TableScan
                                                   table: PUBLIC.ITEM
-                                                  filters: AND(SEARCH(I_COLOR, Sarg[_UTF-8'azure':VARCHAR(20) CHARACTER SET "UTF-8", _UTF-8'blush':VARCHAR(20) CHARACTER SET "UTF-8", _UTF-8'gainsboro':VARCHAR(20) CHARACTER SET "UTF-8", _UTF-8'hot':VARCHAR(20) CHARACTER SET "UTF-8", _UTF-8'lemon':VARCHAR(20) CHARACTER SET "UTF-8", _UTF-8'misty':VARCHAR(20) CHARACTER SET "UTF-8"]:VARCHAR(20) CHARACTER SET "UTF-8"), >=(CAST(I_CURRENT_PRICE):DECIMAL(12, 2), 80.00), <=(CAST(I_CURRENT_PRICE):DE [...]
-                                                  fields: [I_ITEM_SK, I_CURRENT_PRICE, I_COLOR, I_PRODUCT_NAME]
+                                                  predicate: AND(SEARCH(I_COLOR, Sarg[_UTF-8'azure':VARCHAR(20) CHARACTER SET "UTF-8", _UTF-8'blush':VARCHAR(20) CHARACTER SET "UTF-8", _UTF-8'gainsboro':VARCHAR(20) CHARACTER SET "UTF-8", _UTF-8'hot':VARCHAR(20) CHARACTER SET "UTF-8", _UTF-8'lemon':VARCHAR(20) CHARACTER SET "UTF-8", _UTF-8'misty':VARCHAR(20) CHARACTER SET "UTF-8"]:VARCHAR(20) CHARACTER SET "UTF-8"), >=(CAST(I_CURRENT_PRICE):DECIMAL(12, 2), 80.00), <=(CAST(I_CURRENT_PRICE): [...]
+                                                  fieldNames: [I_ITEM_SK, I_CURRENT_PRICE, I_COLOR, I_PRODUCT_NAME]
                                                   est: (rows=1125)
                                           Filter
-                                              condition: >(SALE, *(2, REFUND))
+                                              predicate: >(SALE, *(2, REFUND))
                                               est: (rows=14407)
                                             ReduceHashAggregate
-                                                rowType: RecordType(INTEGER CS_ITEM_SK, DECIMAL(32767, 2) SALE, DECIMAL(32767, 2) REFUND)
+                                                fieldNames: [CS_ITEM_SK, SALE, REFUND]
                                                 group: [CS_ITEM_SK]
-                                                aggs: [SALE=SUM(_ACC0), REFUND=SUM(_ACC1)]
+                                                aggregation: [SUM(_ACC0), SUM(_ACC1)]
                                                 est: (rows=28813)
                                               Exchange
                                                   distribution: single
                                                   est: (rows=28813)
                                                 MapHashAggregate
+                                                    fieldNames: [CS_ITEM_SK, _ACC0, _ACC1, _GROUP_ID]
                                                     group: [CS_ITEM_SK]
-                                                    aggs: [SALE=SUM(CS_EXT_LIST_PRICE), REFUND=SUM($f2)]
+                                                    aggregation: [SUM(CS_EXT_LIST_PRICE), SUM($f2)]
                                                     est: (rows=28813)
                                                   Project
-                                                      fields: [CS_ITEM_SK, CS_EXT_LIST_PRICE, $f2]
-                                                      exprs: [CS_ITEM_SK, CS_EXT_LIST_PRICE, +(+(CR_REFUNDED_CASH, CR_REVERSED_CHARGE), CR_STORE_CREDIT)]
+                                                      fieldNames: [CS_ITEM_SK, CS_EXT_LIST_PRICE, $f2]
+                                                      projection: [CS_ITEM_SK, CS_EXT_LIST_PRICE, +(+(CR_REFUNDED_CASH, CR_REVERSED_CHARGE), CR_STORE_CREDIT)]
                                                       est: (rows=144067)
                                                     HashJoin
-                                                        condition: AND(=(CS_ITEM_SK, CR_ITEM_SK), =(CS_ORDER_NUMBER, CR_ORDER_NUMBER))
-                                                        joinType: inner
+                                                        predicate: AND(=(CS_ITEM_SK, CR_ITEM_SK), =(CS_ORDER_NUMBER, CR_ORDER_NUMBER))
+                                                        type: inner
                                                         est: (rows=144067)
                                                       TableScan
                                                           table: PUBLIC.CATALOG_SALES
-                                                          fields: [CS_ITEM_SK, CS_ORDER_NUMBER, CS_EXT_LIST_PRICE]
+                                                          fieldNames: [CS_ITEM_SK, CS_ORDER_NUMBER, CS_EXT_LIST_PRICE]
                                                           est: (rows=1441548)
                                                       TableScan
                                                           table: PUBLIC.CATALOG_RETURNS
-                                                          fields: [CR_ITEM_SK, CR_ORDER_NUMBER, CR_REFUNDED_CASH, CR_REVERSED_CHARGE, CR_STORE_CREDIT]
+                                                          fieldNames: [CR_ITEM_SK, CR_ORDER_NUMBER, CR_REFUNDED_CASH, CR_REVERSED_CHARGE, CR_STORE_CREDIT]
                                                           est: (rows=144067)
                                         Exchange
                                             distribution: single
                                             est: (rows=24325)
                                           TableScan
                                               table: PUBLIC.DATE_DIM
-                                              filters: =(D_YEAR, 1999)
-                                              fields: [D_DATE_SK, D_YEAR]
+                                              predicate: =(D_YEAR, 1999)
+                                              fieldNames: [D_DATE_SK, D_YEAR]
                                               est: (rows=24325)
                               Exchange
                                   distribution: single
                                   est: (rows=20)
                                 TableScan
                                     table: PUBLIC.INCOME_BAND
-                                    fields: [IB_INCOME_BAND_SK]
+                                    fieldNames: [IB_INCOME_BAND_SK]
                                     est: (rows=20)
                           Exchange
                               distribution: single
                               est: (rows=20)
                             TableScan
                                 table: PUBLIC.INCOME_BAND
-                                fields: [IB_INCOME_BAND_SK]
+                                fieldNames: [IB_INCOME_BAND_SK]
                                 est: (rows=20)
                     Exchange
                         distribution: single
                         est: (rows=300)
                       TableScan
                           table: PUBLIC.PROMOTION
-                          fields: [P_PROMO_SK]
+                          fieldNames: [P_PROMO_SK]
                           est: (rows=300)
                   Exchange
                       distribution: single
                       est: (rows=12)
                     TableScan
                         table: PUBLIC.STORE
-                        fields: [S_STORE_SK, S_STORE_NAME, S_ZIP]
+                        fieldNames: [S_STORE_SK, S_STORE_NAME, S_ZIP]
                         est: (rows=12)
       ColocatedHashAggregate
+          fieldNames: [PRODUCT_NAME, ITEM_SK, STORE_NAME, STORE_ZIP, B_STREET_NUMBER, B_STREET_NAME, B_CITY, B_ZIP, C_STREET_NUMBER, C_STREET_NAME, C_CITY, C_ZIP, SYEAR, FSYEAR, S2YEAR, CNT, S1, S2, S3]
           group: [PRODUCT_NAME, ITEM_SK, STORE_NAME, STORE_ZIP, B_STREET_NUMBER, B_STREET_NAME, B_CITY, B_ZIP, C_STREET_NUMBER, C_STREET_NAME, C_CITY, C_ZIP, SYEAR, FSYEAR, S2YEAR]
-          aggs: [CNT=COUNT(), S1=SUM(SS_WHOLESALE_COST), S2=SUM(SS_LIST_PRICE), S3=SUM(SS_COUPON_AMT)]
+          aggregation: [COUNT(), SUM(SS_WHOLESALE_COST), SUM(SS_LIST_PRICE), SUM(SS_COUPON_AMT)]
           est: (rows=2887)
         Project
-            fields: [PRODUCT_NAME, ITEM_SK, STORE_NAME, STORE_ZIP, B_STREET_NUMBER, B_STREET_NAME, B_CITY, B_ZIP, C_STREET_NUMBER, C_STREET_NAME, C_CITY, C_ZIP, SYEAR, FSYEAR, S2YEAR, SS_WHOLESALE_COST, SS_LIST_PRICE, SS_COUPON_AMT]
-            exprs: [I_PRODUCT_NAME, I_ITEM_SK, S_STORE_NAME, S_ZIP, CA_STREET_NUMBER, CA_STREET_NAME, CA_CITY, CA_ZIP, CA_STREET_NUMBER0, CA_STREET_NAME0, CA_CITY0, CA_ZIP0, D_YEAR00, D_YEAR, D_YEAR0, SS_WHOLESALE_COST, SS_LIST_PRICE, SS_COUPON_AMT]
+            fieldNames: [PRODUCT_NAME, ITEM_SK, STORE_NAME, STORE_ZIP, B_STREET_NUMBER, B_STREET_NAME, B_CITY, B_ZIP, C_STREET_NUMBER, C_STREET_NAME, C_CITY, C_ZIP, SYEAR, FSYEAR, S2YEAR, SS_WHOLESALE_COST, SS_LIST_PRICE, SS_COUPON_AMT]
+            projection: [I_PRODUCT_NAME, I_ITEM_SK, S_STORE_NAME, S_ZIP, CA_STREET_NUMBER, CA_STREET_NAME, CA_CITY, CA_ZIP, CA_STREET_NUMBER0, CA_STREET_NAME0, CA_CITY0, CA_ZIP0, D_YEAR00, D_YEAR, D_YEAR0, SS_WHOLESALE_COST, SS_LIST_PRICE, SS_COUPON_AMT]
             est: (rows=2992)
           HashJoin
-              condition: AND(=(SS_ITEM_SK, SR_ITEM_SK), =(SS_TICKET_NUMBER, SR_TICKET_NUMBER))
-              joinType: inner
+              predicate: AND(=(SS_ITEM_SK, SR_ITEM_SK), =(SS_TICKET_NUMBER, SR_TICKET_NUMBER))
+              type: inner
               est: (rows=2992)
             Exchange
                 distribution: single
                 est: (rows=287514)
               TableScan
                   table: PUBLIC.STORE_RETURNS
-                  fields: [SR_ITEM_SK, SR_TICKET_NUMBER]
+                  fieldNames: [SR_ITEM_SK, SR_TICKET_NUMBER]
                   est: (rows=287514)
             HashJoin
-                condition: =(C_FIRST_SALES_DATE_SK, D_DATE_SK)
-                joinType: inner
+                predicate: =(C_FIRST_SALES_DATE_SK, D_DATE_SK)
+                fieldNames: [D_DATE_SK, D_YEAR, D_DATE_SK0, D_YEAR0, CA_ADDRESS_SK, CA_STREET_NUMBER, CA_STREET_NAME, CA_CITY, CA_ZIP, CA_ADDRESS_SK0, CA_STREET_NUMBER0, CA_STREET_NAME0, CA_CITY0, CA_ZIP0, HD_DEMO_SK, HD_INCOME_BAND_SK, HD_DEMO_SK0, HD_INCOME_BAND_SK0, CD_DEMO_SK, CD_MARITAL_STATUS, CD_DEMO_SK0, CD_MARITAL_STATUS0, C_CUSTOMER_SK, C_CURRENT_CDEMO_SK, C_CURRENT_HDEMO_SK, C_CURRENT_ADDR_SK, C_FIRST_SHIPTO_DATE_SK, C_FIRST_SALES_DATE_SK, SS_SOLD_DATE_SK, SS_ITEM_SK, SS_CUSTO [...]
+                type: inner
                 est: (rows=90684167)
               Exchange
                   distribution: single
                   est: (rows=73049)
                 TableScan
                     table: PUBLIC.DATE_DIM
-                    fields: [D_DATE_SK, D_YEAR]
+                    fieldNames: [D_DATE_SK, D_YEAR]
                     est: (rows=73049)
               HashJoin
-                  condition: =(C_FIRST_SHIPTO_DATE_SK, D_DATE_SK)
-                  joinType: inner
+                  predicate: =(C_FIRST_SHIPTO_DATE_SK, D_DATE_SK)
+                  fieldNames: [D_DATE_SK, D_YEAR, CA_ADDRESS_SK, CA_STREET_NUMBER, CA_STREET_NAME, CA_CITY, CA_ZIP, CA_ADDRESS_SK0, CA_STREET_NUMBER0, CA_STREET_NAME0, CA_CITY0, CA_ZIP0, HD_DEMO_SK, HD_INCOME_BAND_SK, HD_DEMO_SK0, HD_INCOME_BAND_SK0, CD_DEMO_SK, CD_MARITAL_STATUS, CD_DEMO_SK0, CD_MARITAL_STATUS0, C_CUSTOMER_SK, C_CURRENT_CDEMO_SK, C_CURRENT_HDEMO_SK, C_CURRENT_ADDR_SK, C_FIRST_SHIPTO_DATE_SK, C_FIRST_SALES_DATE_SK, SS_SOLD_DATE_SK, SS_ITEM_SK, SS_CUSTOMER_SK, SS_CDEMO_SK [...]
+                  type: inner
                   est: (rows=90684167)
                 Exchange
                     distribution: single
                     est: (rows=73049)
                   TableScan
                       table: PUBLIC.DATE_DIM
-                      fields: [D_DATE_SK, D_YEAR]
+                      fieldNames: [D_DATE_SK, D_YEAR]
                       est: (rows=73049)
                 HashJoin
-                    condition: =(SS_STORE_SK, S_STORE_SK)
-                    joinType: inner
+                    predicate: =(SS_STORE_SK, S_STORE_SK)
+                    type: inner
                     est: (rows=90684167)
                   HashJoin
-                      condition: =(SS_PROMO_SK, P_PROMO_SK)
-                      joinType: inner
+                      predicate: =(SS_PROMO_SK, P_PROMO_SK)
+                      type: inner
                       est: (rows=90684167)
                     HashJoin
-                        condition: =(SS_ADDR_SK, CA_ADDRESS_SK)
-                        joinType: inner
+                        predicate: =(SS_ADDR_SK, CA_ADDRESS_SK)
+                        fieldNames: [CA_ADDRESS_SK, CA_STREET_NUMBER, CA_STREET_NAME, CA_CITY, CA_ZIP, CA_ADDRESS_SK0, CA_STREET_NUMBER0, CA_STREET_NAME0, CA_CITY0, CA_ZIP0, HD_DEMO_SK, HD_INCOME_BAND_SK, HD_DEMO_SK0, HD_INCOME_BAND_SK0, CD_DEMO_SK, CD_MARITAL_STATUS, CD_DEMO_SK0, CD_MARITAL_STATUS0, C_CUSTOMER_SK, C_CURRENT_CDEMO_SK, C_CURRENT_HDEMO_SK, C_CURRENT_ADDR_SK, C_FIRST_SHIPTO_DATE_SK, C_FIRST_SALES_DATE_SK, SS_SOLD_DATE_SK, SS_ITEM_SK, SS_CUSTOMER_SK, SS_CDEMO_SK, SS_HDEMO_SK [...]
+                        type: inner
                         est: (rows=90684167)
                       Exchange
                           distribution: single
                           est: (rows=50000)
                         TableScan
                             table: PUBLIC.CUSTOMER_ADDRESS
-                            fields: [CA_ADDRESS_SK, CA_STREET_NUMBER, CA_STREET_NAME, CA_CITY, CA_ZIP]
+                            fieldNames: [CA_ADDRESS_SK, CA_STREET_NUMBER, CA_STREET_NAME, CA_CITY, CA_ZIP]
                             est: (rows=50000)
                       HashJoin
-                          condition: =(C_CURRENT_ADDR_SK, CA_ADDRESS_SK)
-                          joinType: inner
+                          predicate: =(C_CURRENT_ADDR_SK, CA_ADDRESS_SK)
+                          type: inner
                           est: (rows=90684167)
                         Exchange
                             distribution: single
                             est: (rows=50000)
                           TableScan
                               table: PUBLIC.CUSTOMER_ADDRESS
-                              fields: [CA_ADDRESS_SK, CA_STREET_NUMBER, CA_STREET_NAME, CA_CITY, CA_ZIP]
+                              fieldNames: [CA_ADDRESS_SK, CA_STREET_NUMBER, CA_STREET_NAME, CA_CITY, CA_ZIP]
                               est: (rows=50000)
                         HashJoin
-                            condition: =(HD_INCOME_BAND_SK, IB_INCOME_BAND_SK0)
-                            joinType: inner
+                            predicate: =(HD_INCOME_BAND_SK, IB_INCOME_BAND_SK0)
+                            fieldNames: [HD_DEMO_SK, HD_INCOME_BAND_SK, HD_DEMO_SK0, HD_INCOME_BAND_SK0, CD_DEMO_SK, CD_MARITAL_STATUS, CD_DEMO_SK0, CD_MARITAL_STATUS0, C_CUSTOMER_SK, C_CURRENT_CDEMO_SK, C_CURRENT_HDEMO_SK, C_CURRENT_ADDR_SK, C_FIRST_SHIPTO_DATE_SK, C_FIRST_SALES_DATE_SK, SS_SOLD_DATE_SK, SS_ITEM_SK, SS_CUSTOMER_SK, SS_CDEMO_SK, SS_HDEMO_SK, SS_ADDR_SK, SS_STORE_SK, SS_PROMO_SK, SS_TICKET_NUMBER, SS_WHOLESALE_COST, SS_LIST_PRICE, SS_COUPON_AMT, I_ITEM_SK, I_CURRENT_PRICE [...]
+                            type: inner
                             est: (rows=90684167)
                           HashJoin
-                              condition: =(SS_HDEMO_SK, HD_DEMO_SK)
-                              joinType: inner
+                              predicate: =(SS_HDEMO_SK, HD_DEMO_SK)
+                              fieldNames: [HD_DEMO_SK, HD_INCOME_BAND_SK, HD_DEMO_SK0, HD_INCOME_BAND_SK0, CD_DEMO_SK, CD_MARITAL_STATUS, CD_DEMO_SK0, CD_MARITAL_STATUS0, C_CUSTOMER_SK, C_CURRENT_CDEMO_SK, C_CURRENT_HDEMO_SK, C_CURRENT_ADDR_SK, C_FIRST_SHIPTO_DATE_SK, C_FIRST_SALES_DATE_SK, SS_SOLD_DATE_SK, SS_ITEM_SK, SS_CUSTOMER_SK, SS_CDEMO_SK, SS_HDEMO_SK, SS_ADDR_SK, SS_STORE_SK, SS_PROMO_SK, SS_TICKET_NUMBER, SS_WHOLESALE_COST, SS_LIST_PRICE, SS_COUPON_AMT, I_ITEM_SK, I_CURRENT_PRI [...]
+                              type: inner
                               est: (rows=90684167)
                             Exchange
                                 distribution: single
                                 est: (rows=7200)
                               TableScan
                                   table: PUBLIC.HOUSEHOLD_DEMOGRAPHICS
-                                  fields: [HD_DEMO_SK, HD_INCOME_BAND_SK]
+                                  fieldNames: [HD_DEMO_SK, HD_INCOME_BAND_SK]
                                   est: (rows=7200)
                             HashJoin
-                                condition: =(HD_INCOME_BAND_SK, IB_INCOME_BAND_SK)
-                                joinType: inner
+                                predicate: =(HD_INCOME_BAND_SK, IB_INCOME_BAND_SK)
+                                type: inner
                                 est: (rows=90684167)
                               HashJoin
-                                  condition: =(C_CURRENT_HDEMO_SK, HD_DEMO_SK)
-                                  joinType: inner
+                                  predicate: =(C_CURRENT_HDEMO_SK, HD_DEMO_SK)
+                                  type: inner
                                   est: (rows=90684167)
                                 Exchange
                                     distribution: single
                                     est: (rows=7200)
                                   TableScan
                                       table: PUBLIC.HOUSEHOLD_DEMOGRAPHICS
-                                      fields: [HD_DEMO_SK, HD_INCOME_BAND_SK]
+                                      fieldNames: [HD_DEMO_SK, HD_INCOME_BAND_SK]
                                       est: (rows=7200)
                                 HashJoin
-                                    condition: AND(<>(CD_MARITAL_STATUS, CD_MARITAL_STATUS0), =(SS_CDEMO_SK, CD_DEMO_SK))
-                                    joinType: inner
+                                    predicate: AND(<>(CD_MARITAL_STATUS, CD_MARITAL_STATUS0), =(SS_CDEMO_SK, CD_DEMO_SK))
+                                    fieldNames: [CD_DEMO_SK, CD_MARITAL_STATUS, CD_DEMO_SK0, CD_MARITAL_STATUS0, C_CUSTOMER_SK, C_CURRENT_CDEMO_SK, C_CURRENT_HDEMO_SK, C_CURRENT_ADDR_SK, C_FIRST_SHIPTO_DATE_SK, C_FIRST_SALES_DATE_SK, SS_SOLD_DATE_SK, SS_ITEM_SK, SS_CUSTOMER_SK, SS_CDEMO_SK, SS_HDEMO_SK, SS_ADDR_SK, SS_STORE_SK, SS_PROMO_SK, SS_TICKET_NUMBER, SS_WHOLESALE_COST, SS_LIST_PRICE, SS_COUPON_AMT, I_ITEM_SK, I_CURRENT_PRICE, I_COLOR, I_PRODUCT_NAME, CS_ITEM_SK, SALE, REFUND, D_D [...]
+                                    type: inner
                                     est: (rows=90684167)
                                   Exchange
                                       distribution: single
                                       est: (rows=1920800)
                                     TableScan
                                         table: PUBLIC.CUSTOMER_DEMOGRAPHICS
-                                        fields: [CD_DEMO_SK, CD_MARITAL_STATUS]
+                                        fieldNames: [CD_DEMO_SK, CD_MARITAL_STATUS]
                                         est: (rows=1920800)
                                   HashJoin
-                                      condition: =(C_CURRENT_CDEMO_SK, CD_DEMO_SK)
-                                      joinType: inner
+                                      predicate: =(C_CURRENT_CDEMO_SK, CD_DEMO_SK)
+                                      type: inner
                                       est: (rows=129548810)
                                     Exchange
                                         distribution: single
                                         est: (rows=1920800)
                                       TableScan
                                           table: PUBLIC.CUSTOMER_DEMOGRAPHICS
-                                          fields: [CD_DEMO_SK, CD_MARITAL_STATUS]
+                                          fieldNames: [CD_DEMO_SK, CD_MARITAL_STATUS]
                                           est: (rows=1920800)
                                     HashJoin
-                                        condition: =(SS_CUSTOMER_SK, C_CUSTOMER_SK)
-                                        joinType: inner
+                                        predicate: =(SS_CUSTOMER_SK, C_CUSTOMER_SK)
+                                        type: inner
                                         est: (rows=129548810)
                                       Exchange
                                           distribution: single
                                           est: (rows=100000)
                                         TableScan
                                             table: PUBLIC.CUSTOMER
-                                            fields: [C_CUSTOMER_SK, C_CURRENT_CDEMO_SK, C_CURRENT_HDEMO_SK, C_CURRENT_ADDR_SK, C_FIRST_SHIPTO_DATE_SK, C_FIRST_SALES_DATE_SK]
+                                            fieldNames: [C_CUSTOMER_SK, C_CURRENT_CDEMO_SK, C_CURRENT_HDEMO_SK, C_CURRENT_ADDR_SK, C_FIRST_SHIPTO_DATE_SK, C_FIRST_SALES_DATE_SK]
                                             est: (rows=100000)
                                       HashJoin
-                                          condition: =(SS_SOLD_DATE_SK, D_DATE_SK)
-                                          joinType: inner
+                                          predicate: =(SS_SOLD_DATE_SK, D_DATE_SK)
+                                          type: inner
                                           est: (rows=129548810)
                                         HashJoin
-                                            condition: =(SS_ITEM_SK, CS_ITEM_SK)
-                                            joinType: inner
+                                            predicate: =(SS_ITEM_SK, CS_ITEM_SK)
+                                            type: inner
                                             est: (rows=389035465)
                                           HashJoin
-                                              condition: =(SS_ITEM_SK, I_ITEM_SK)
-                                              joinType: inner
+                                              predicate: =(SS_ITEM_SK, I_ITEM_SK)
+                                              type: inner
                                               est: (rows=180025)
                                             Exchange
                                                 distribution: single
                                                 est: (rows=2880404)
                                               TableScan
                                                   table: PUBLIC.STORE_SALES
-                                                  fields: [SS_SOLD_DATE_SK, SS_ITEM_SK, SS_CUSTOMER_SK, SS_CDEMO_SK, SS_HDEMO_SK, SS_ADDR_SK, SS_STORE_SK, SS_PROMO_SK, SS_TICKET_NUMBER, SS_WHOLESALE_COST, SS_LIST_PRICE, SS_COUPON_AMT]
+                                                  fieldNames: [SS_SOLD_DATE_SK, SS_ITEM_SK, SS_CUSTOMER_SK, SS_CDEMO_SK, SS_HDEMO_SK, SS_ADDR_SK, SS_STORE_SK, SS_PROMO_SK, SS_TICKET_NUMBER, SS_WHOLESALE_COST, SS_LIST_PRICE, SS_COUPON_AMT]
                                                   est: (rows=2880404)
                                             Exchange
                                                 distribution: single
                                                 est: (rows=1125)
                                               TableScan
                                                   table: PUBLIC.ITEM
-                                                  filters: AND(SEARCH(I_COLOR, Sarg[_UTF-8'azure':VARCHAR(20) CHARACTER SET "UTF-8", _UTF-8'blush':VARCHAR(20) CHARACTER SET "UTF-8", _UTF-8'gainsboro':VARCHAR(20) CHARACTER SET "UTF-8", _UTF-8'hot':VARCHAR(20) CHARACTER SET "UTF-8", _UTF-8'lemon':VARCHAR(20) CHARACTER SET "UTF-8", _UTF-8'misty':VARCHAR(20) CHARACTER SET "UTF-8"]:VARCHAR(20) CHARACTER SET "UTF-8"), >=(CAST(I_CURRENT_PRICE):DECIMAL(12, 2), 80.00), <=(CAST(I_CURRENT_PRICE):DE [...]
-                                                  fields: [I_ITEM_SK, I_CURRENT_PRICE, I_COLOR, I_PRODUCT_NAME]
+                                                  predicate: AND(SEARCH(I_COLOR, Sarg[_UTF-8'azure':VARCHAR(20) CHARACTER SET "UTF-8", _UTF-8'blush':VARCHAR(20) CHARACTER SET "UTF-8", _UTF-8'gainsboro':VARCHAR(20) CHARACTER SET "UTF-8", _UTF-8'hot':VARCHAR(20) CHARACTER SET "UTF-8", _UTF-8'lemon':VARCHAR(20) CHARACTER SET "UTF-8", _UTF-8'misty':VARCHAR(20) CHARACTER SET "UTF-8"]:VARCHAR(20) CHARACTER SET "UTF-8"), >=(CAST(I_CURRENT_PRICE):DECIMAL(12, 2), 80.00), <=(CAST(I_CURRENT_PRICE): [...]
+                                                  fieldNames: [I_ITEM_SK, I_CURRENT_PRICE, I_COLOR, I_PRODUCT_NAME]
                                                   est: (rows=1125)
                                           Filter
-                                              condition: >(SALE, *(2, REFUND))
+                                              predicate: >(SALE, *(2, REFUND))
                                               est: (rows=14407)
                                             ReduceHashAggregate
-                                                rowType: RecordType(INTEGER CS_ITEM_SK, DECIMAL(32767, 2) SALE, DECIMAL(32767, 2) REFUND)
+                                                fieldNames: [CS_ITEM_SK, SALE, REFUND]
                                                 group: [CS_ITEM_SK]
-                                                aggs: [SALE=SUM(_ACC0), REFUND=SUM(_ACC1)]
+                                                aggregation: [SUM(_ACC0), SUM(_ACC1)]
                                                 est: (rows=28813)
                                               Exchange
                                                   distribution: single
                                                   est: (rows=28813)
                                                 MapHashAggregate
+                                                    fieldNames: [CS_ITEM_SK, _ACC0, _ACC1, _GROUP_ID]
                                                     group: [CS_ITEM_SK]
-                                                    aggs: [SALE=SUM(CS_EXT_LIST_PRICE), REFUND=SUM($f2)]
+                                                    aggregation: [SUM(CS_EXT_LIST_PRICE), SUM($f2)]
                                                     est: (rows=28813)
                                                   Project
-                                                      fields: [CS_ITEM_SK, CS_EXT_LIST_PRICE, $f2]
-                                                      exprs: [CS_ITEM_SK, CS_EXT_LIST_PRICE, +(+(CR_REFUNDED_CASH, CR_REVERSED_CHARGE), CR_STORE_CREDIT)]
+                                                      fieldNames: [CS_ITEM_SK, CS_EXT_LIST_PRICE, $f2]
+                                                      projection: [CS_ITEM_SK, CS_EXT_LIST_PRICE, +(+(CR_REFUNDED_CASH, CR_REVERSED_CHARGE), CR_STORE_CREDIT)]
                                                       est: (rows=144067)
                                                     HashJoin
-                                                        condition: AND(=(CS_ITEM_SK, CR_ITEM_SK), =(CS_ORDER_NUMBER, CR_ORDER_NUMBER))
-                                                        joinType: inner
+                                                        predicate: AND(=(CS_ITEM_SK, CR_ITEM_SK), =(CS_ORDER_NUMBER, CR_ORDER_NUMBER))
+                                                        type: inner
                                                         est: (rows=144067)
                                                       TableScan
                                                           table: PUBLIC.CATALOG_SALES
-                                                          fields: [CS_ITEM_SK, CS_ORDER_NUMBER, CS_EXT_LIST_PRICE]
+                                                          fieldNames: [CS_ITEM_SK, CS_ORDER_NUMBER, CS_EXT_LIST_PRICE]
                                                           est: (rows=1441548)
                                                       TableScan
                                                           table: PUBLIC.CATALOG_RETURNS
-                                                          fields: [CR_ITEM_SK, CR_ORDER_NUMBER, CR_REFUNDED_CASH, CR_REVERSED_CHARGE, CR_STORE_CREDIT]
+                                                          fieldNames: [CR_ITEM_SK, CR_ORDER_NUMBER, CR_REFUNDED_CASH, CR_REVERSED_CHARGE, CR_STORE_CREDIT]
                                                           est: (rows=144067)
                                         Exchange
                                             distribution: single
                                             est: (rows=24325)
                                           TableScan
                                               table: PUBLIC.DATE_DIM
-                                              filters: =(D_YEAR, +(1999, 1))
-                                              fields: [D_DATE_SK, D_YEAR]
+                                              predicate: =(D_YEAR, +(1999, 1))
+                                              fieldNames: [D_DATE_SK, D_YEAR]
                                               est: (rows=24325)
                               Exchange
                                   distribution: single
                                   est: (rows=20)
                                 TableScan
                                     table: PUBLIC.INCOME_BAND
-                                    fields: [IB_INCOME_BAND_SK]
+                                    fieldNames: [IB_INCOME_BAND_SK]
                                     est: (rows=20)
                           Exchange
                               distribution: single
                               est: (rows=20)
                             TableScan
                                 table: PUBLIC.INCOME_BAND
-                                fields: [IB_INCOME_BAND_SK]
+                                fieldNames: [IB_INCOME_BAND_SK]
                                 est: (rows=20)
                     Exchange
                         distribution: single
                         est: (rows=300)
                       TableScan
                           table: PUBLIC.PROMOTION
-                          fields: [P_PROMO_SK]
+                          fieldNames: [P_PROMO_SK]
                           est: (rows=300)
                   Exchange
                       distribution: single
                       est: (rows=12)
                     TableScan
                         table: PUBLIC.STORE
-                        fields: [S_STORE_SK, S_STORE_NAME, S_ZIP]
+                        fieldNames: [S_STORE_SK, S_STORE_NAME, S_ZIP]
                         est: (rows=12)
diff --git a/modules/sql-engine/src/test/resources/tpch/plan/q1.plan b/modules/sql-engine/src/test/resources/tpch/plan/q1.plan
index 44154d7d383..cef36ac555f 100644
--- a/modules/sql-engine/src/test/resources/tpch/plan/q1.plan
+++ b/modules/sql-engine/src/test/resources/tpch/plan/q1.plan
@@ -2,32 +2,33 @@ Sort
     collation: [L_RETURNFLAG ASC, L_LINESTATUS ASC]
     est: (rows=1080219)
   Project
-      fields: [L_RETURNFLAG, L_LINESTATUS, SUM_QTY, SUM_BASE_PRICE, SUM_DISC_PRICE, SUM_CHARGE, AVG_QTY, AVG_PRICE, AVG_DISC, COUNT_ORDER]
-      exprs: [f0, f1, f0_2, f0_3, f0_4, f0_5, DECIMAL_DIVIDE(f0_6, f1_7, 29, 16), DECIMAL_DIVIDE(f0_8, f1_9, 29, 16), DECIMAL_DIVIDE(f0_10, f1_11, 29, 16), CAST(f0_12):BIGINT NOT NULL]
+      fieldNames: [L_RETURNFLAG, L_LINESTATUS, SUM_QTY, SUM_BASE_PRICE, SUM_DISC_PRICE, SUM_CHARGE, AVG_QTY, AVG_PRICE, AVG_DISC, COUNT_ORDER]
+      projection: [f0, f1, f0_2, f0_3, f0_4, f0_5, DECIMAL_DIVIDE(f0_6, f1_7, 29, 16), DECIMAL_DIVIDE(f0_8, f1_9, 29, 16), DECIMAL_DIVIDE(f0_10, f1_11, 29, 16), CAST(f0_12):BIGINT NOT NULL]
       est: (rows=1080219)
     ReduceHashAggregate
-        rowType: RecordType(VARCHAR(1) f0, VARCHAR(1) f1, DECIMAL(32767, 2) f0_2, DECIMAL(32767, 2) f0_3, DECIMAL(32767, 4) f0_4, DECIMAL(32767, 6) f0_5, DECIMAL(32767, 2) f0_6, DECIMAL(32767, 0) f1_7, DECIMAL(32767, 2) f0_8, DECIMAL(32767, 0) f1_9, DECIMAL(32767, 2) f0_10, DECIMAL(32767, 0) f1_11, BIGINT f0_12)
+        fieldNames: [f0, f1, f0_2, f0_3, f0_4, f0_5, f0_6, f1_7, f0_8, f1_9, f0_10, f1_11, f0_12]
         group: [0, 1]
-        aggs: [SUM_QTY=SUM(2), SUM_BASE_PRICE=SUM(3), SUM_DISC_PRICE=SUM(4), SUM_CHARGE=SUM(5), AVG_SUM6=SUM(6), AVG_SUM06=$SUM0(7), AVG_SUM8=SUM(8), AVG_SUM08=$SUM0(9), AVG_SUM10=SUM(10), AVG_SUM010=$SUM0(11), COUNT_12_MAP_SUM=$SUM0(12)]
+        aggregation: [SUM(2), SUM(3), SUM(4), SUM(5), SUM(6), $SUM0(7), SUM(8), $SUM0(9), SUM(10), $SUM0(11), $SUM0(12)]
         est: (rows=1080219)
       Exchange
           distribution: single
           est: (rows=1080219)
         Project
-            fields: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13]
-            exprs: [L_RETURNFLAG, L_LINESTATUS, _ACC0, _ACC1, _ACC2, _ACC3, _ACC4, CAST(_ACC5):DECIMAL(32767, 0) NOT NULL, _ACC6, CAST(_ACC7):DECIMAL(32767, 0) NOT NULL, _ACC8, CAST(_ACC9):DECIMAL(32767, 0) NOT NULL, _ACC10, _GROUP_ID]
+            fieldNames: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13]
+            projection: [L_RETURNFLAG, L_LINESTATUS, _ACC0, _ACC1, _ACC2, _ACC3, _ACC4, CAST(_ACC5):DECIMAL(32767, 0) NOT NULL, _ACC6, CAST(_ACC7):DECIMAL(32767, 0) NOT NULL, _ACC8, CAST(_ACC9):DECIMAL(32767, 0) NOT NULL, _ACC10, _GROUP_ID]
             est: (rows=1080219)
           MapHashAggregate
+              fieldNames: [L_RETURNFLAG, L_LINESTATUS, _ACC0, _ACC1, _ACC2, _ACC3, _ACC4, _ACC5, _ACC6, _ACC7, _ACC8, _ACC9, _ACC10, _GROUP_ID]
               group: [L_RETURNFLAG, L_LINESTATUS]
-              aggs: [SUM_QTY=SUM(L_QUANTITY), SUM_BASE_PRICE=SUM(L_EXTENDEDPRICE), SUM_DISC_PRICE=SUM($f4), SUM_CHARGE=SUM($f5), AVG_SUM6=SUM(L_QUANTITY), AVG_COUNT6=COUNT(L_QUANTITY), AVG_SUM8=SUM(L_EXTENDEDPRICE), AVG_COUNT8=COUNT(L_EXTENDEDPRICE), AVG_SUM10=SUM(L_DISCOUNT), AVG_COUNT10=COUNT(L_DISCOUNT), COUNT_ORDER=COUNT()]
+              aggregation: [SUM(L_QUANTITY), SUM(L_EXTENDEDPRICE), SUM($f4), SUM($f5), SUM(L_QUANTITY), COUNT(L_QUANTITY), SUM(L_EXTENDEDPRICE), COUNT(L_EXTENDEDPRICE), SUM(L_DISCOUNT), COUNT(L_DISCOUNT), COUNT()]
               est: (rows=1080219)
             IndexScan
                 table: PUBLIC.LINEITEM
                 index: L_SD
                 type: SORTED
+                predicate: <=(L_SHIPDATE, -(1998-12-01, 7776000000:INTERVAL DAY))
                 searchBounds: [RangeBounds [lowerBound=null, upperBound=-(1998-12-01, 7776000000:INTERVAL DAY), lowerInclude=true, upperInclude=true]]
-                filters: <=(L_SHIPDATE, -(1998-12-01, 7776000000:INTERVAL DAY))
-                fields: [L_RETURNFLAG, L_LINESTATUS, L_QUANTITY, L_EXTENDEDPRICE, $f4, $f5, L_DISCOUNT]
-                projects: [L_RETURNFLAG, L_LINESTATUS, L_QUANTITY, L_EXTENDEDPRICE, *(L_EXTENDEDPRICE, -(1, L_DISCOUNT)), *(*(L_EXTENDEDPRICE, -(1, L_DISCOUNT)), +(1, L_TAX)), L_DISCOUNT]
+                fieldNames: [L_RETURNFLAG, L_LINESTATUS, L_QUANTITY, L_EXTENDEDPRICE, $f4, $f5, L_DISCOUNT]
+                projection: [L_RETURNFLAG, L_LINESTATUS, L_QUANTITY, L_EXTENDEDPRICE, *(L_EXTENDEDPRICE, -(1, L_DISCOUNT)), *(*(L_EXTENDEDPRICE, -(1, L_DISCOUNT)), +(1, L_TAX)), L_DISCOUNT]
                 collation: []
                 est: (rows=3000608)
diff --git a/modules/sql-engine/src/test/resources/tpch/plan/q21.plan b/modules/sql-engine/src/test/resources/tpch/plan/q21.plan
index 0358daf1630..dd402239ab8 100644
--- a/modules/sql-engine/src/test/resources/tpch/plan/q21.plan
+++ b/modules/sql-engine/src/test/resources/tpch/plan/q21.plan
@@ -6,53 +6,54 @@ Limit
       fetch: 100
       est: (rows=100)
     ColocatedSortAggregate
-        group: [S_NAME]
-        aggs: [NUMWAIT=COUNT()]
+        fieldNames: [S_NAME, NUMWAIT]
         collation: [S_NAME ASC]
+        group: [S_NAME]
+        aggregation: [COUNT()]
         est: (rows=332203730726418)
       Project
-          fields: [S_NAME]
-          exprs: [S_NAME]
+          fieldNames: [S_NAME]
+          projection: [S_NAME]
           est: (rows=1661018653632089)
         Filter
-            condition: IS NULL(i)
+            predicate: IS NULL(i)
             est: (rows=1661018653632089)
           CorrelatedNestedLoopJoin
-              condition: true
-              joinType: left
-              variablesSet: [$cor5]
+              predicate: true
+              type: left
+              correlates: [$cor5]
               est: (rows=6644074614528357)
             CorrelatedNestedLoopJoin
-                condition: true
-                joinType: inner
-                variablesSet: [$cor6]
+                predicate: true
+                type: inner
+                correlates: [$cor6]
                 est: (rows=66493788407)
               Sort
                   collation: [S_NAME ASC]
                   est: (rows=332734)
                 Project
-                    fields: [S_NAME, L_ORDERKEY, L_SUPPKEY]
-                    exprs: [S_NAME, L_ORDERKEY, L_SUPPKEY]
+                    fieldNames: [S_NAME, L_ORDERKEY, L_SUPPKEY]
+                    projection: [S_NAME, L_ORDERKEY, L_SUPPKEY]
                     est: (rows=332734)
                   HashJoin
-                      condition: =(O_ORDERKEY, L_ORDERKEY)
-                      joinType: inner
+                      predicate: =(O_ORDERKEY, L_ORDERKEY)
+                      type: inner
                       est: (rows=332734)
                     HashJoin
-                        condition: =(S_SUPPKEY, L_SUPPKEY)
-                        joinType: inner
+                        predicate: =(S_SUPPKEY, L_SUPPKEY)
+                        type: inner
                         est: (rows=999202)
                       Exchange
                           distribution: single
                           est: (rows=3000608)
                         TableScan
                             table: PUBLIC.LINEITEM
-                            filters: >(L_RECEIPTDATE, L_COMMITDATE)
-                            fields: [L_ORDERKEY, L_SUPPKEY, L_COMMITDATE, L_RECEIPTDATE]
+                            predicate: >(L_RECEIPTDATE, L_COMMITDATE)
+                            fieldNames: [L_ORDERKEY, L_SUPPKEY, L_COMMITDATE, L_RECEIPTDATE]
                             est: (rows=3000608)
                       MergeJoin
-                          condition: =(S_NATIONKEY, N_NATIONKEY)
-                          joinType: inner
+                          predicate: =(S_NATIONKEY, N_NATIONKEY)
+                          type: inner
                           est: (rows=3330)
                         Exchange
                             distribution: single
@@ -61,7 +62,7 @@ Limit
                               table: PUBLIC.SUPPLIER
                               index: S_NK
                               type: SORTED
-                              fields: [S_SUPPKEY, S_NAME, S_NATIONKEY]
+                              fieldNames: [S_SUPPKEY, S_NAME, S_NATIONKEY]
                               collation: [S_NATIONKEY ASC]
                               est: (rows=10000)
                         Exchange
@@ -72,60 +73,64 @@ Limit
                               est: (rows=8)
                             TableScan
                                 table: PUBLIC.NATION
-                                filters: =(N_NAME, _UTF-8'SAUDI ARABIA')
-                                fields: [N_NATIONKEY, N_NAME]
+                                predicate: =(N_NAME, _UTF-8'SAUDI ARABIA')
+                                fieldNames: [N_NATIONKEY, N_NAME]
                                 est: (rows=8)
                     Exchange
                         distribution: single
                         est: (rows=499500)
                       TableScan
                           table: PUBLIC.ORDERS
-                          filters: =(O_ORDERSTATUS, _UTF-8'F')
-                          fields: [O_ORDERKEY, O_ORDERSTATUS]
+                          predicate: =(O_ORDERSTATUS, _UTF-8'F')
+                          fieldNames: [O_ORDERKEY, O_ORDERSTATUS]
                           est: (rows=499500)
               Project
-                  fields: []
-                  exprs: []
+                  fieldNames: []
+                  projection: []
                   est: (rows=199840)
                 ReduceHashAggregate
-                    rowType: RecordType(BOOLEAN i)
+                    fieldNames: [i]
                     group: [i]
+                    aggregation: []
                     est: (rows=199840)
                   Exchange
                       distribution: single
                       est: (rows=199840)
                     MapHashAggregate
+                        fieldNames: [i, _GROUP_ID]
                         group: [i]
-                        aggs: []
+                        aggregation: []
                         est: (rows=199840)
                       IndexScan
                           table: PUBLIC.LINEITEM
                           index: L_OK
                           type: SORTED
+                          predicate: AND(=(L_ORDERKEY, $cor6.L_ORDERKEY), <>(L_SUPPKEY, $cor6.L_SUPPKEY))
                           searchBounds: [ExactBounds [bound=$cor6.L_ORDERKEY]]
-                          filters: AND(=(L_ORDERKEY, $cor6.L_ORDERKEY), <>(L_SUPPKEY, $cor6.L_SUPPKEY))
-                          fields: [i]
-                          projects: [true]
+                          fieldNames: [i]
+                          projection: [true]
                           collation: []
                           est: (rows=999202)
             ReduceHashAggregate
-                rowType: RecordType(BOOLEAN i)
+                fieldNames: [i]
                 group: [i]
+                aggregation: []
                 est: (rows=99920)
               Exchange
                   distribution: single
                   est: (rows=99920)
                 MapHashAggregate
+                    fieldNames: [i, _GROUP_ID]
                     group: [i]
-                    aggs: []
+                    aggregation: []
                     est: (rows=99920)
                   IndexScan
                       table: PUBLIC.LINEITEM
                       index: L_OK
                       type: SORTED
+                      predicate: AND(=(L_ORDERKEY, $cor5.L_ORDERKEY), <>(L_SUPPKEY, $cor5.L_SUPPKEY), >(L_RECEIPTDATE, L_COMMITDATE))
                       searchBounds: [ExactBounds [bound=$cor5.L_ORDERKEY]]
-                      filters: AND(=(L_ORDERKEY, $cor5.L_ORDERKEY), <>(L_SUPPKEY, $cor5.L_SUPPKEY), >(L_RECEIPTDATE, L_COMMITDATE))
-                      fields: [i]
-                      projects: [true]
+                      fieldNames: [i]
+                      projection: [true]
                       collation: []
                       est: (rows=499601)
diff --git a/modules/sql-engine/src/test/resources/tpch/plan/q5.plan b/modules/sql-engine/src/test/resources/tpch/plan/q5.plan
index 9e39e15cd8d..c6dc504bd6c 100644
--- a/modules/sql-engine/src/test/resources/tpch/plan/q5.plan
+++ b/modules/sql-engine/src/test/resources/tpch/plan/q5.plan
@@ -2,74 +2,75 @@ Sort
     collation: [REVENUE DESC]
     est: (rows=69944)
   ColocatedHashAggregate
+      fieldNames: [N_NAME, REVENUE]
       group: [N_NAME]
-      aggs: [REVENUE=SUM($f1)]
+      aggregation: [SUM($f1)]
       est: (rows=69944)
     Project
-        fields: [N_NAME, $f1]
-        exprs: [N_NAME, *(L_EXTENDEDPRICE, -(1, L_DISCOUNT))]
+        fieldNames: [N_NAME, $f1]
+        projection: [N_NAME, *(L_EXTENDEDPRICE, -(1, L_DISCOUNT))]
         est: (rows=349721)
       HashJoin
-          condition: AND(=(C_NATIONKEY, S_NATIONKEY), =(L_SUPPKEY, S_SUPPKEY))
-          joinType: inner
+          predicate: AND(=(C_NATIONKEY, S_NATIONKEY), =(L_SUPPKEY, S_SUPPKEY))
+          type: inner
           est: (rows=349721)
         HashJoin
-            condition: =(L_ORDERKEY, O_ORDERKEY)
-            joinType: inner
+            predicate: =(L_ORDERKEY, O_ORDERKEY)
+            type: inner
             est: (rows=1500304)
           Exchange
               distribution: single
               est: (rows=6001215)
             TableScan
                 table: PUBLIC.LINEITEM
-                fields: [L_ORDERKEY, L_SUPPKEY, L_EXTENDEDPRICE, L_DISCOUNT]
+                fieldNames: [L_ORDERKEY, L_SUPPKEY, L_EXTENDEDPRICE, L_DISCOUNT]
                 est: (rows=6001215)
           HashJoin
-              condition: =(C_CUSTKEY, O_CUSTKEY)
-              joinType: inner
+              predicate: =(C_CUSTKEY, O_CUSTKEY)
+              type: inner
               est: (rows=375000)
             Exchange
                 distribution: single
                 est: (rows=375000)
               TableScan
                   table: PUBLIC.ORDERS
-                  filters: AND(>=(O_ORDERDATE, 1994-01-01), <(O_ORDERDATE, +(1994-01-01, 12:INTERVAL YEAR)))
-                  fields: [O_ORDERKEY, O_CUSTKEY, O_ORDERDATE]
+                  predicate: AND(>=(O_ORDERDATE, 1994-01-01), <(O_ORDERDATE, +(1994-01-01, 12:INTERVAL YEAR)))
+                  fieldNames: [O_ORDERKEY, O_CUSTKEY, O_ORDERDATE]
                   est: (rows=375000)
             Exchange
                 distribution: single
                 est: (rows=150000)
               TableScan
                   table: PUBLIC.CUSTOMER
-                  fields: [C_CUSTKEY, C_NATIONKEY]
+                  fieldNames: [C_CUSTKEY, C_NATIONKEY]
                   est: (rows=150000)
         HashJoin
-            condition: =(S_NATIONKEY, N_NATIONKEY)
-            joinType: inner
+            predicate: =(S_NATIONKEY, N_NATIONKEY)
+            type: inner
             est: (rows=3330)
           Exchange
               distribution: single
               est: (rows=10000)
             TableScan
                 table: PUBLIC.SUPPLIER
-                fields: [S_SUPPKEY, S_NATIONKEY]
+                fieldNames: [S_SUPPKEY, S_NATIONKEY]
                 est: (rows=10000)
           HashJoin
-              condition: =(N_REGIONKEY, R_REGIONKEY)
-              joinType: inner
+              predicate: =(N_REGIONKEY, R_REGIONKEY)
+              type: inner
               est: (rows=8)
             Exchange
                 distribution: single
                 est: (rows=25)
               TableScan
                   table: PUBLIC.NATION
-                  fields: [N_NATIONKEY, N_NAME, N_REGIONKEY]
+                  fieldNames: [N_NATIONKEY, N_NAME, N_REGIONKEY]
                   est: (rows=25)
             Exchange
                 distribution: single
                 est: (rows=2)
               TableScan
                   table: PUBLIC.REGION
-                  filters: =(R_NAME, _UTF-8'ASIA')
-                  fields: [R_REGIONKEY, R_NAME]
+                  predicate: =(R_NAME, _UTF-8'ASIA')
+                  fieldNames: [R_REGIONKEY, R_NAME]
                   est: (rows=2)
diff --git a/modules/sql-engine/src/test/resources/tpch/plan/q7.plan b/modules/sql-engine/src/test/resources/tpch/plan/q7.plan
index 5b4e29585bd..3d2c501b480 100644
--- a/modules/sql-engine/src/test/resources/tpch/plan/q7.plan
+++ b/modules/sql-engine/src/test/resources/tpch/plan/q7.plan
@@ -2,75 +2,77 @@ Sort
     collation: [SUPP_NATION ASC, CUST_NATION ASC, L_YEAR ASC]
     est: (rows=165618)
   ColocatedHashAggregate
+      fieldNames: [SUPP_NATION, CUST_NATION, L_YEAR, REVENUE]
       group: [SUPP_NATION, CUST_NATION, L_YEAR]
-      aggs: [REVENUE=SUM(VOLUME)]
+      aggregation: [SUM(VOLUME)]
       est: (rows=165618)
     Project
-        fields: [SUPP_NATION, CUST_NATION, L_YEAR, VOLUME]
-        exprs: [N_NAME, N_NAME0, EXTRACT(FLAG(YEAR), L_SHIPDATE), *(L_EXTENDEDPRICE, -(1, L_DISCOUNT))]
+        fieldNames: [SUPP_NATION, CUST_NATION, L_YEAR, VOLUME]
+        projection: [N_NAME, N_NAME0, EXTRACT(FLAG(YEAR), L_SHIPDATE), *(L_EXTENDEDPRICE, -(1, L_DISCOUNT))]
         est: (rows=339381)
       HashJoin
-          condition: AND(=(O_ORDERKEY, L_ORDERKEY), OR(=(N_NAME, _UTF-8'FRANCE'), =(N_NAME0, _UTF-8'FRANCE')), OR(=(N_NAME0, _UTF-8'GERMANY'), =(N_NAME, _UTF-8'GERMANY')))
-          joinType: inner
+          predicate: AND(=(O_ORDERKEY, L_ORDERKEY), OR(=(N_NAME, _UTF-8'FRANCE'), =(N_NAME0, _UTF-8'FRANCE')), OR(=(N_NAME0, _UTF-8'GERMANY'), =(N_NAME, _UTF-8'GERMANY')))
+          fieldNames: [L_ORDERKEY, L_SUPPKEY, L_EXTENDEDPRICE, L_DISCOUNT, L_SHIPDATE, S_SUPPKEY, S_NATIONKEY, N_NATIONKEY, N_NAME, O_ORDERKEY, O_CUSTKEY, C_CUSTKEY, C_NATIONKEY, N_NATIONKEY0, N_NAME0]
+          type: inner
           est: (rows=339381)
         HashJoin
-            condition: =(S_SUPPKEY, L_SUPPKEY)
-            joinType: inner
+            predicate: =(S_SUPPKEY, L_SUPPKEY)
+            type: inner
             est: (rows=852873)
           Exchange
               distribution: single
               est: (rows=1500304)
             TableScan
                 table: PUBLIC.LINEITEM
-                filters: SEARCH(L_SHIPDATE, Sarg[[1995-01-01..1996-12-31]])
-                fields: [L_ORDERKEY, L_SUPPKEY, L_EXTENDEDPRICE, L_DISCOUNT, L_SHIPDATE]
+                predicate: SEARCH(L_SHIPDATE, Sarg[[1995-01-01..1996-12-31]])
+                fieldNames: [L_ORDERKEY, L_SUPPKEY, L_EXTENDEDPRICE, L_DISCOUNT, L_SHIPDATE]
                 est: (rows=1500304)
           HashJoin
-              condition: =(S_NATIONKEY, N_NATIONKEY)
-              joinType: inner
+              predicate: =(S_NATIONKEY, N_NATIONKEY)
+              type: inner
               est: (rows=5685)
             Exchange
                 distribution: single
                 est: (rows=10000)
               TableScan
                   table: PUBLIC.SUPPLIER
-                  fields: [S_SUPPKEY, S_NATIONKEY]
+                  fieldNames: [S_SUPPKEY, S_NATIONKEY]
                   est: (rows=10000)
             Exchange
                 distribution: single
                 est: (rows=14)
               TableScan
                   table: PUBLIC.NATION
-                  filters: SEARCH(N_NAME, Sarg[_UTF-8'FRANCE':CHAR(7) CHARACTER SET "UTF-8", _UTF-8'GERMANY']:CHAR(7) CHARACTER SET "UTF-8")
-                  fields: [N_NATIONKEY, N_NAME]
+                  predicate: SEARCH(N_NAME, Sarg[_UTF-8'FRANCE':CHAR(7) CHARACTER SET "UTF-8", _UTF-8'GERMANY']:CHAR(7) CHARACTER SET "UTF-8")
+                  fieldNames: [N_NATIONKEY, N_NAME]
                   est: (rows=14)
         HashJoin
-            condition: =(C_CUSTKEY, O_CUSTKEY)
-            joinType: inner
+            predicate: =(C_CUSTKEY, O_CUSTKEY)
+            type: inner
             est: (rows=852700)
           Exchange
               distribution: single
               est: (rows=1500000)
             TableScan
                 table: PUBLIC.ORDERS
-                fields: [O_ORDERKEY, O_CUSTKEY]
+                fieldNames: [O_ORDERKEY, O_CUSTKEY]
                 est: (rows=1500000)
           HashJoin
-              condition: =(C_NATIONKEY, N_NATIONKEY)
-              joinType: inner
+              predicate: =(C_NATIONKEY, N_NATIONKEY)
+              type: inner
               est: (rows=85270)
             Exchange
                 distribution: single
                 est: (rows=150000)
               TableScan
                   table: PUBLIC.CUSTOMER
-                  fields: [C_CUSTKEY, C_NATIONKEY]
+                  fieldNames: [C_CUSTKEY, C_NATIONKEY]
                   est: (rows=150000)
             Exchange
                 distribution: single
                 est: (rows=14)
               TableScan
                   table: PUBLIC.NATION
-                  filters: SEARCH(N_NAME, Sarg[_UTF-8'FRANCE':CHAR(7) CHARACTER SET "UTF-8", _UTF-8'GERMANY']:CHAR(7) CHARACTER SET "UTF-8")
-                  fields: [N_NATIONKEY, N_NAME]
+                  predicate: SEARCH(N_NAME, Sarg[_UTF-8'FRANCE':CHAR(7) CHARACTER SET "UTF-8", _UTF-8'GERMANY']:CHAR(7) CHARACTER SET "UTF-8")
+                  fieldNames: [N_NATIONKEY, N_NAME]
                   est: (rows=14)
diff --git a/modules/sql-engine/src/test/resources/tpch/plan/q8.plan b/modules/sql-engine/src/test/resources/tpch/plan/q8.plan
index 465cbc5d37b..791b17a297d 100644
--- a/modules/sql-engine/src/test/resources/tpch/plan/q8.plan
+++ b/modules/sql-engine/src/test/resources/tpch/plan/q8.plan
@@ -2,101 +2,103 @@ Sort
     collation: [O_YEAR ASC]
     est: (rows=16635)
   Project
-      fields: [O_YEAR, MKT_SHARE]
-      exprs: [O_YEAR, /($f1, $f2)]
+      fieldNames: [O_YEAR, MKT_SHARE]
+      projection: [O_YEAR, /($f1, $f2)]
       est: (rows=16635)
     ColocatedHashAggregate
+        fieldNames: [O_YEAR, $f1, $f2]
         group: [O_YEAR]
-        aggs: [SUM($f1), SUM(VOLUME)]
+        aggregation: [SUM($f1), SUM(VOLUME)]
         est: (rows=16635)
       Project
-          fields: [O_YEAR, $f1, VOLUME]
-          exprs: [EXTRACT(FLAG(YEAR), O_ORDERDATE), CASE(=(N_NAME, _UTF-8'BRAZIL'), *(L_EXTENDEDPRICE, -(1, L_DISCOUNT)), 0.0000:DECIMAL(31, 4)), *(L_EXTENDEDPRICE, -(1, L_DISCOUNT))]
+          fieldNames: [O_YEAR, $f1, VOLUME]
+          projection: [EXTRACT(FLAG(YEAR), O_ORDERDATE), CASE(=(N_NAME, _UTF-8'BRAZIL'), *(L_EXTENDEDPRICE, -(1, L_DISCOUNT)), 0.0000:DECIMAL(31, 4)), *(L_EXTENDEDPRICE, -(1, L_DISCOUNT))]
           est: (rows=166367)
         HashJoin
-            condition: =(S_SUPPKEY, L_SUPPKEY)
-            joinType: inner
+            predicate: =(S_SUPPKEY, L_SUPPKEY)
+            fieldNames: [L_ORDERKEY, L_PARTKEY, L_SUPPKEY, L_EXTENDEDPRICE, L_DISCOUNT, O_ORDERKEY, O_CUSTKEY, O_ORDERDATE, C_CUSTKEY, C_NATIONKEY, N_NATIONKEY, N_REGIONKEY, R_REGIONKEY, R_NAME, P_PARTKEY, P_TYPE, S_SUPPKEY, S_NATIONKEY, N_NATIONKEY0, N_NAME]
+            type: inner
             est: (rows=166367)
           HashJoin
-              condition: =(P_PARTKEY, L_PARTKEY)
-              joinType: inner
+              predicate: =(P_PARTKEY, L_PARTKEY)
+              type: inner
               est: (rows=166367)
             HashJoin
-                condition: =(L_ORDERKEY, O_ORDERKEY)
-                joinType: inner
+                predicate: =(L_ORDERKEY, O_ORDERKEY)
+                type: inner
                 est: (rows=499601)
               Exchange
                   distribution: single
                   est: (rows=6001215)
                 TableScan
                     table: PUBLIC.LINEITEM
-                    fields: [L_ORDERKEY, L_PARTKEY, L_SUPPKEY, L_EXTENDEDPRICE, L_DISCOUNT]
+                    fieldNames: [L_ORDERKEY, L_PARTKEY, L_SUPPKEY, L_EXTENDEDPRICE, L_DISCOUNT]
                     est: (rows=6001215)
               HashJoin
-                  condition: =(O_CUSTKEY, C_CUSTKEY)
-                  joinType: inner
+                  predicate: =(O_CUSTKEY, C_CUSTKEY)
+                  type: inner
                   est: (rows=124875)
                 Exchange
                     distribution: single
                     est: (rows=375000)
                   TableScan
                       table: PUBLIC.ORDERS
-                      filters: SEARCH(O_ORDERDATE, Sarg[[1995-01-01..1996-12-31]])
-                      fields: [O_ORDERKEY, O_CUSTKEY, O_ORDERDATE]
+                      predicate: SEARCH(O_ORDERDATE, Sarg[[1995-01-01..1996-12-31]])
+                      fieldNames: [O_ORDERKEY, O_CUSTKEY, O_ORDERDATE]
                       est: (rows=375000)
                 HashJoin
-                    condition: =(C_NATIONKEY, N_NATIONKEY)
-                    joinType: inner
+                    predicate: =(C_NATIONKEY, N_NATIONKEY)
+                    type: inner
                     est: (rows=49950)
                   Exchange
                       distribution: single
                       est: (rows=150000)
                     TableScan
                         table: PUBLIC.CUSTOMER
-                        fields: [C_CUSTKEY, C_NATIONKEY]
+                        fieldNames: [C_CUSTKEY, C_NATIONKEY]
                         est: (rows=150000)
                   HashJoin
-                      condition: =(N_REGIONKEY, R_REGIONKEY)
-                      joinType: inner
+                      predicate: =(N_REGIONKEY, R_REGIONKEY)
+                      type: inner
                       est: (rows=8)
                     Exchange
                         distribution: single
                         est: (rows=25)
                       TableScan
                           table: PUBLIC.NATION
-                          fields: [N_NATIONKEY, N_REGIONKEY]
+                          fieldNames: [N_NATIONKEY, N_REGIONKEY]
                           est: (rows=25)
                     Exchange
                         distribution: single
                         est: (rows=2)
                       TableScan
                           table: PUBLIC.REGION
-                          filters: =(R_NAME, _UTF-8'AMERICA')
-                          fields: [R_REGIONKEY, R_NAME]
+                          predicate: =(R_NAME, _UTF-8'AMERICA')
+                          fieldNames: [R_REGIONKEY, R_NAME]
                           est: (rows=2)
             Exchange
                 distribution: single
                 est: (rows=66600)
               TableScan
                   table: PUBLIC.PART
-                  filters: =(P_TYPE, _UTF-8'ECONOMY ANODIZED STEEL')
-                  fields: [P_PARTKEY, P_TYPE]
+                  predicate: =(P_TYPE, _UTF-8'ECONOMY ANODIZED STEEL')
+                  fieldNames: [P_PARTKEY, P_TYPE]
                   est: (rows=66600)
           HashJoin
-              condition: =(S_NATIONKEY, N_NATIONKEY)
-              joinType: inner
+              predicate: =(S_NATIONKEY, N_NATIONKEY)
+              type: inner
               est: (rows=10000)
             Exchange
                 distribution: single
                 est: (rows=10000)
               TableScan
                   table: PUBLIC.SUPPLIER
-                  fields: [S_SUPPKEY, S_NATIONKEY]
+                  fieldNames: [S_SUPPKEY, S_NATIONKEY]
                   est: (rows=10000)
             Exchange
                 distribution: single
                 est: (rows=25)
               TableScan
                   table: PUBLIC.NATION
-                  fields: [N_NATIONKEY, N_NAME]
+                  fieldNames: [N_NATIONKEY, N_NAME]
                   est: (rows=25)
diff --git a/modules/sql-engine/src/test/resources/tpch/plan/q9.plan b/modules/sql-engine/src/test/resources/tpch/plan/q9.plan
index f7ad03fa5d1..e01182648f6 100644
--- a/modules/sql-engine/src/test/resources/tpch/plan/q9.plan
+++ b/modules/sql-engine/src/test/resources/tpch/plan/q9.plan
@@ -2,73 +2,74 @@ Sort
     collation: [NATION ASC, O_YEAR DESC]
     est: (rows=540109)
   ColocatedHashAggregate
+      fieldNames: [NATION, O_YEAR, SUM_PROFIT]
       group: [NATION, O_YEAR]
-      aggs: [SUM_PROFIT=SUM(AMOUNT)]
+      aggregation: [SUM(AMOUNT)]
       est: (rows=540109)
     Project
-        fields: [NATION, O_YEAR, AMOUNT]
-        exprs: [N_NAME, EXTRACT(FLAG(YEAR), O_ORDERDATE), -(*(L_EXTENDEDPRICE, -(1, L_DISCOUNT)), *(PS_SUPPLYCOST, L_QUANTITY))]
+        fieldNames: [NATION, O_YEAR, AMOUNT]
+        projection: [N_NAME, EXTRACT(FLAG(YEAR), O_ORDERDATE), -(*(L_EXTENDEDPRICE, -(1, L_DISCOUNT)), *(PS_SUPPLYCOST, L_QUANTITY))]
         est: (rows=1500304)
       HashJoin
-          condition: AND(=(PS_SUPPKEY, L_SUPPKEY), =(PS_PARTKEY, L_PARTKEY))
-          joinType: inner
+          predicate: AND(=(PS_SUPPKEY, L_SUPPKEY), =(PS_PARTKEY, L_PARTKEY))
+          type: inner
           est: (rows=1500304)
         HashJoin
-            condition: =(O_ORDERKEY, L_ORDERKEY)
-            joinType: inner
+            predicate: =(O_ORDERKEY, L_ORDERKEY)
+            type: inner
             est: (rows=1500304)
           HashJoin
-              condition: =(S_SUPPKEY, L_SUPPKEY)
-              joinType: inner
+              predicate: =(S_SUPPKEY, L_SUPPKEY)
+              type: inner
               est: (rows=1500304)
             HashJoin
-                condition: =(P_PARTKEY, L_PARTKEY)
-                joinType: inner
+                predicate: =(P_PARTKEY, L_PARTKEY)
+                type: inner
                 est: (rows=1500304)
               Exchange
                   distribution: single
                   est: (rows=6001215)
                 TableScan
                     table: PUBLIC.LINEITEM
-                    fields: [L_ORDERKEY, L_PARTKEY, L_SUPPKEY, L_QUANTITY, L_EXTENDEDPRICE, L_DISCOUNT]
+                    fieldNames: [L_ORDERKEY, L_PARTKEY, L_SUPPKEY, L_QUANTITY, L_EXTENDEDPRICE, L_DISCOUNT]
                     est: (rows=6001215)
               Exchange
                   distribution: single
                   est: (rows=50000)
                 TableScan
                     table: PUBLIC.PART
-                    filters: LIKE(P_NAME, _UTF-8'%green%')
-                    fields: [P_PARTKEY, P_NAME]
+                    predicate: LIKE(P_NAME, _UTF-8'%green%')
+                    fieldNames: [P_PARTKEY, P_NAME]
                     est: (rows=50000)
             HashJoin
-                condition: =(S_NATIONKEY, N_NATIONKEY)
-                joinType: inner
+                predicate: =(S_NATIONKEY, N_NATIONKEY)
+                type: inner
                 est: (rows=10000)
               Exchange
                   distribution: single
                   est: (rows=10000)
                 TableScan
                     table: PUBLIC.SUPPLIER
-                    fields: [S_SUPPKEY, S_NATIONKEY]
+                    fieldNames: [S_SUPPKEY, S_NATIONKEY]
                     est: (rows=10000)
               Exchange
                   distribution: single
                   est: (rows=25)
                 TableScan
                     table: PUBLIC.NATION
-                    fields: [N_NATIONKEY, N_NAME]
+                    fieldNames: [N_NATIONKEY, N_NAME]
                     est: (rows=25)
           Exchange
               distribution: single
               est: (rows=1500000)
             TableScan
                 table: PUBLIC.ORDERS
-                fields: [O_ORDERKEY, O_ORDERDATE]
+                fieldNames: [O_ORDERKEY, O_ORDERDATE]
                 est: (rows=1500000)
         Exchange
             distribution: single
             est: (rows=800000)
           TableScan
               table: PUBLIC.PARTSUPP
-              fields: [PS_PARTKEY, PS_SUPPKEY, PS_SUPPLYCOST]
+              fieldNames: [PS_PARTKEY, PS_SUPPKEY, PS_SUPPLYCOST]
               est: (rows=800000)
diff --git a/modules/sql-engine/src/testFixtures/java/org/apache/ignite/internal/sql/engine/util/QueryChecker.java b/modules/sql-engine/src/testFixtures/java/org/apache/ignite/internal/sql/engine/util/QueryChecker.java
index 8ab541e5b17..671cdb490d7 100644
--- a/modules/sql-engine/src/testFixtures/java/org/apache/ignite/internal/sql/engine/util/QueryChecker.java
+++ b/modules/sql-engine/src/testFixtures/java/org/apache/ignite/internal/sql/engine/util/QueryChecker.java
@@ -44,14 +44,9 @@ import org.hamcrest.core.SubstringMatcher;
 public interface QueryChecker {
     Object[] NULL_AS_VARARG = {null};
 
-    /** Creates a matcher that matches if the examined string contains the specified string anywhere. */
-    static Matcher<String> containsUnion(boolean all) {
-        return matchesOnce("UnionAll.*?all: " + all);
-    }
-
     /** Creates a matcher that matches if the examined string contains the specified string anywhere. */
     static Matcher<String> containsUnion() {
-        return matchesOnce("UnionAll.*?all:");
+        return matchesOnce("UnionAll");
     }
 
     /**
@@ -137,7 +132,7 @@ public interface QueryChecker {
      */
     static Matcher<String> containsProject(String schema, String tblName, String... names) {
         return matchesOnce("(Table|Index)Scan.*?table: " + QualifiedName.of(schema, tblName).toCanonicalForm()
-                + ".*?fields: \\[" + String.join(", ", List.of(names)) + "\\]");
+                + ".*?fieldNames: \\[" + String.join(", ", List.of(names)) + "\\]");
     }
 
     /**