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

zstan 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 c76d322e12 IGNITE-22171: Sql. Row cast fails with NPE (#4010)
c76d322e12 is described below

commit c76d322e12f079864ad1f93c4baa37ce568c2013
Author: Max Zhuravkov <[email protected]>
AuthorDate: Wed Jul 3 20:55:13 2024 +0300

    IGNITE-22171: Sql. Row cast fails with NPE (#4010)
---
 .../sql/basic_queries/test_simple_table.test       |   2 +-
 .../generic/test_in_list_of_single_element.test    |  10 +-
 .../integrationTest/sql/types/row/test_row.test    |  49 ++++++
 .../sql/engine/prepare/IgniteSqlValidator.java     | 183 +++++++++++++++------
 .../internal/sql/engine/util/IgniteResource.java   |   3 +
 .../sql/engine/planner/DynamicParametersTest.java  |  18 +-
 6 files changed, 205 insertions(+), 60 deletions(-)

diff --git 
a/modules/sql-engine/src/integrationTest/sql/basic_queries/test_simple_table.test
 
b/modules/sql-engine/src/integrationTest/sql/basic_queries/test_simple_table.test
index a509fc2e93..bdd66f9e1d 100644
--- 
a/modules/sql-engine/src/integrationTest/sql/basic_queries/test_simple_table.test
+++ 
b/modules/sql-engine/src/integrationTest/sql/basic_queries/test_simple_table.test
@@ -84,7 +84,7 @@ b     2
 b      2
 
 skipif ignite3
-# https://issues.apache.org/jira/browse/IGNITE-22171
+# https://issues.apache.org/jira/browse/IGNITE-22084
 query II rowsort
 SELECT * FROM (VALUES ROW(CAST(ROW(1, 2.0) AS ROW(x BIGINT, y DECIMAL(2, 
1)))));
 ----
diff --git 
a/modules/sql-engine/src/integrationTest/sql/function/generic/test_in_list_of_single_element.test
 
b/modules/sql-engine/src/integrationTest/sql/function/generic/test_in_list_of_single_element.test
index 2f127947c8..7526b10bc7 100644
--- 
a/modules/sql-engine/src/integrationTest/sql/function/generic/test_in_list_of_single_element.test
+++ 
b/modules/sql-engine/src/integrationTest/sql/function/generic/test_in_list_of_single_element.test
@@ -178,31 +178,37 @@ SELECT NULL IN (SELECT 1)
 ----
 null
 
+# https://issues.apache.org/jira/browse/IGNITE-22084: Sql. Add support for row 
data type.
 # Row value constructions
+skipif ignite3
 query T
 SELECT (1, 'hello', TIMESTAMP '2022-02-01 10:30:28') IN ((1, 'hello', 
TIMESTAMP '2022-02-01 10:30:28'));
 ----
 true
 
+# https://issues.apache.org/jira/browse/IGNITE-22084: Sql. Add support for row 
data type.
+skipif ignite3
 query T
 SELECT (1, 'hello') IN ((1, 'world'));
 ----
 false
 
 skipif ignite3
-# Ignored: https://issues.apache.org/jira/browse/IGNITE-22014
+# https://issues.apache.org/jira/browse/IGNITE-22084: Sql. Add support for row 
data type.
 query T
 SELECT (1, 'hello') IN ((1, NULL));
 ----
 NULL
 
+skipif ignite3
+# https://issues.apache.org/jira/browse/IGNITE-22084: Sql. Add support for row 
data type.
 query T
 SELECT (1, 'hello') NOT IN ((1, 'hello'));
 ----
 false
 
 skipif ignite3
-# Ignored: https://issues.apache.org/jira/browse/IGNITE-22014
+# https://issues.apache.org/jira/browse/IGNITE-22084: Sql. Add support for row 
data type.
 query T
 SELECT (1, NULL) IN ((1, NULL));
 ----
diff --git a/modules/sql-engine/src/integrationTest/sql/types/row/test_row.test 
b/modules/sql-engine/src/integrationTest/sql/types/row/test_row.test
index 6358642cf7..d08d86c364 100644
--- a/modules/sql-engine/src/integrationTest/sql/types/row/test_row.test
+++ b/modules/sql-engine/src/integrationTest/sql/types/row/test_row.test
@@ -18,11 +18,16 @@ INSERT INTO emp VALUES(3, 'Noah', 47, 1200)
 statement ok
 INSERT INTO emp VALUES(4, 'Dave', 42, 1700)
 
+skipif ignite3
+# https://issues.apache.org/jira/browse/IGNITE-22084: Sql. Add support for row 
data type.
+# Error ROW datatype is not supported
 query T
 SELECT empname FROM emp WHERE (empname, empage, salary) = ('Johnah', 47, 1200)
 ----
 Johnah
 
+skipif ignite3
+# https://issues.apache.org/jira/browse/IGNITE-22084: Sql. Add support for row 
data type.
 query T
 SELECT empname FROM emp WHERE ('Johnah', 47) IN ((empname, empage))
 ----
@@ -43,3 +48,47 @@ SELECT DISTINCT (empage, salary) FROM emp
 (42,1700)
 (47,1200)
 (47,2000)
+
+# https://issues.apache.org/jira/browse/IGNITE-22084: Sql. Add support for row 
data type.
+statement error: ROW datatype is not supported
+SELECT CAST(ROW(1, 2.0) AS ROW(x BIGINT, y DOUBLE))
+
+# https://issues.apache.org/jira/browse/IGNITE-22084: Sql. Add support for row 
data type.
+statement error: ROW datatype is not supported
+SELECT ROW(1, 2.0)
+
+# https://issues.apache.org/jira/browse/IGNITE-22084: Sql. Add support for row 
data type.
+statement error: ROW datatype is not supported
+SELECT (1, 2.0)
+
+# (empage, salary) uses the ROW operator
+statement ok
+SELECT empage, salary FROM emp GROUP BY (empage, salary)
+
+statement error: ROW datatype is not supported
+SELECT (1, 2) IN ((1,2))
+
+statement ok
+SELECT * FROM emp WHERE empid = ANY (VALUES (CAST(2 as BIGINT)), (CAST(1 as 
SMALLINT)))
+
+statement ok
+SELECT (VALUES (CAST(2 as BIGINT)))
+
+statement ok
+SELECT (VALUES ROW(CAST(2 as BIGINT)))
+
+statement ok
+SELECT (VALUES ROW(2))
+
+skipif ignite3
+# https://issues.apache.org/jira/browse/IGNITE-22613: Sql. Scalar subquery 
that returns more than one column produces unexpected validation error
+statement error: Scalar subquery returns more than 1 column
+SELECT (VALUES ROW(2, 4))
+
+# https://issues.apache.org/jira/browse/IGNITE-22084: Sql. Add support for row 
data type.
+statement error: ROW datatype is not supported
+SELECT (VALUES ROW(ROW(2)))
+
+# https://issues.apache.org/jira/browse/IGNITE-22084: Sql. Add support for row 
data type.
+statement error: ROW datatype is not supported
+SELECT * FROM (SELECT (1, true) as a) as t(x)
diff --git 
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/IgniteSqlValidator.java
 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/IgniteSqlValidator.java
index db4340c866..54b4bc193c 100644
--- 
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/IgniteSqlValidator.java
+++ 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/IgniteSqlValidator.java
@@ -29,6 +29,7 @@ import it.unimi.dsi.fastutil.ints.IntArraySet;
 import it.unimi.dsi.fastutil.ints.IntSet;
 import java.math.BigDecimal;
 import java.util.AbstractList;
+import java.util.ArrayDeque;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.EnumSet;
@@ -528,54 +529,6 @@ public class IgniteSqlValidator extends SqlValidatorImpl {
         super.validateAggregateParams(aggCall, filter, null, orderList, scope);
     }
 
-    /** {@inheritDoc} */
-    @Override
-    public void validateCall(
-            SqlCall call,
-            SqlValidatorScope scope
-    ) {
-        super.validateCall(call, scope);
-
-        SqlOperator operator = call.getOperator();
-
-        // IgniteCustomType:
-        // Since custom data types use ANY that is a catch all type for type 
checkers,
-        // if a function is called with custom data type argument does not 
belong to CUSTOM_TYPE_FUNCTIONS,
-        // then this should be considered a validation error.
-
-        if (call.getOperandList().isEmpty()
-                || !(operator instanceof SqlFunction)
-                || 
IgniteSqlOperatorTable.CUSTOM_TYPE_FUNCTIONS.contains(operator)) {
-            return;
-        }
-
-        for (SqlNode node : call.getOperandList()) {
-            RelDataType type = getValidatedNodeTypeIfKnown(node);
-            // Argument type is not known yet (alias) or it is not a custom 
data type.
-            if ((!(type instanceof IgniteCustomType))) {
-                continue;
-            }
-
-            String name = call.getOperator().getName();
-
-            // Call to getAllowedSignatures throws NPE, if operandTypeChecker 
is null.
-            if (operator.getOperandTypeChecker() != null) {
-                // If signatures are available, then return:
-                // Cannot apply 'F' to arguments of type 'F(<ARG_TYPE>)'. 
Supported form(s): 'F(<TYPE>)'
-                String allowedSignatures = operator.getAllowedSignatures();
-                throw newValidationError(call,
-                        RESOURCE.canNotApplyOp2Type(name,
-                                call.getCallSignature(this, scope),
-                                allowedSignatures));
-            } else {
-                // Otherwise return an error w/o supported forms:
-                // Cannot apply 'F' to arguments of type 'F(<ARG_TYPE>)'
-                throw newValidationError(call, 
IgniteResource.INSTANCE.canNotApplyOp2Type(name,
-                        call.getCallSignature(this, scope)));
-            }
-        }
-    }
-
     /** {@inheritDoc} */
     @Override
     public RelDataType deriveType(SqlValidatorScope scope, SqlNode expr) {
@@ -663,6 +616,10 @@ public class IgniteSqlValidator extends SqlValidatorImpl {
             RelDataType firstType;
             RelDataType returnType = super.deriveType(scope, ret);
 
+            if (returnType.isStruct()) {
+                throw newValidationError(expr, 
IgniteResource.INSTANCE.dataTypeIsNotSupported(returnType.getSqlTypeName().getName()));
+            }
+
             if (first instanceof SqlDynamicParam) {
                 SqlDynamicParam dynamicParam = (SqlDynamicParam) first;
                 firstType = deriveDynamicParamType(dynamicParam);
@@ -923,6 +880,126 @@ public class IgniteSqlValidator extends SqlValidatorImpl {
         return Commons.implicitPkEnabled() && 
Commons.IMPLICIT_PK_COL_NAME.equals(alias);
     }
 
+    // We use these scopes to filter out valid usages of a ROW operator.
+    private final ArrayDeque<CallScope> callScopes = new ArrayDeque<>();
+
+    /** {@inheritDoc} */
+    @Override
+    protected void validateValues(SqlCall node, RelDataType targetRowType, 
SqlValidatorScope scope) {
+        // TODO: https://issues.apache.org/jira/browse/IGNITE-22084: Sql. Add 
support for row data type.
+        // ROW operator is used in VALUES (row), (row1)
+        callScopes.push(CallScope.VALUES);
+        try {
+            super.validateValues(node, targetRowType, scope);
+        } finally {
+            callScopes.pop();
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected void validateGroupClause(SqlSelect select) {
+        // Calcite uses the ROW operator in the GROUP BY clause in the 
following cases:
+        // - GROUP BY GROUPING SET ((a, b), (c, d))
+        // - GROUP BY (a, b) (but GROUP BY a, b does not use the ROW operator)
+        //
+        // We need to make sure that the validator won't reject such clauses.
+        SqlNodeList group = select.getGroup() == null ? SqlNodeList.EMPTY : 
select.getGroup();
+        boolean rowInGroupScope = false;
+
+        for (SqlNode node : group) {
+            if (node.getKind() == SqlKind.GROUPING_SETS || node.getKind() == 
SqlKind.ROW) {
+                rowInGroupScope = true;
+                break;
+            }
+        }
+
+        if (!rowInGroupScope) {
+            super.validateGroupClause(select);
+        } else {
+            callScopes.push(CallScope.GROUP);
+
+            try {
+                super.validateGroupClause(select);
+            } finally {
+                callScopes.pop();
+            }
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void validateCall(SqlCall call, SqlValidatorScope scope) {
+        CallScope callScope = callScopes.peek();
+        boolean validatingRowOperator = call.getOperator() == 
SqlStdOperatorTable.ROW;
+        boolean insideValues = callScope == CallScope.VALUES;
+        boolean insideGroupClause = callScope == CallScope.GROUP;
+        boolean valuesCall = call.getOperator() == SqlStdOperatorTable.VALUES;
+
+        if (validatingRowOperator && !(insideValues || insideGroupClause)) {
+            throw newValidationError(call, 
IgniteResource.INSTANCE.dataTypeIsNotSupported(call.getOperator().getName()));
+        }
+
+        if (valuesCall) {
+            // VALUES in the WHERE clause in VALUES operator, which is not 
validated via validateValues method.
+            callScopes.push(CallScope.VALUES);
+        } else if (insideGroupClause) {
+            // Allow GROUPING SET ( (a,b), (c, d) ) and GROUP BY (a, b)
+            callScopes.push(CallScope.GROUP);
+        } else {
+            callScopes.push(CallScope.OTHER);
+        }
+
+        try {
+            super.validateCall(call, scope);
+
+            checkCallsWithCustomTypes(call, scope);
+        } finally {
+            callScopes.pop();
+        }
+    }
+
+    private void checkCallsWithCustomTypes(SqlCall call, SqlValidatorScope 
scope) {
+        SqlOperator operator = call.getOperator();
+
+        // IgniteCustomType:
+        // Since custom data types use ANY that is a catch all type for type 
checkers,
+        // if a function is called with custom data type argument does not 
belong to CUSTOM_TYPE_FUNCTIONS,
+        // then this should be considered a validation error.
+
+        if (call.getOperandList().isEmpty()
+                || !(operator instanceof SqlFunction)
+                || 
IgniteSqlOperatorTable.CUSTOM_TYPE_FUNCTIONS.contains(operator)) {
+            return;
+        }
+
+        for (SqlNode node : call.getOperandList()) {
+            RelDataType type = getValidatedNodeTypeIfKnown(node);
+            // Argument type is not known yet (alias) or it is not a custom 
data type.
+            if ((!(type instanceof IgniteCustomType))) {
+                continue;
+            }
+
+            String name = call.getOperator().getName();
+
+            // Call to getAllowedSignatures throws NPE, if operandTypeChecker 
is null.
+            if (operator.getOperandTypeChecker() != null) {
+                // If signatures are available, then return:
+                // Cannot apply 'F' to arguments of type 'F(<ARG_TYPE>)'. 
Supported form(s): 'F(<TYPE>)'
+                String allowedSignatures = operator.getAllowedSignatures();
+                throw newValidationError(call,
+                        RESOURCE.canNotApplyOp2Type(name,
+                                call.getCallSignature(this, scope),
+                                allowedSignatures));
+            } else {
+                // Otherwise return an error w/o supported forms:
+                // Cannot apply 'F' to arguments of type 'F(<ARG_TYPE>)'
+                throw newValidationError(call, 
IgniteResource.INSTANCE.canNotApplyOp2Type(name,
+                        call.getCallSignature(this, scope)));
+            }
+        }
+    }
+
     /** {@inheritDoc} */
     @Override
     protected void inferUnknownTypes(RelDataType inferredType, 
SqlValidatorScope scope, SqlNode node) {
@@ -1163,4 +1240,14 @@ public class IgniteSqlValidator extends SqlValidatorImpl 
{
             this.hasValue = false;
         }
     }
+
+    /**
+     * Scope to distinguish between different usages of the ROW operator.
+     */
+    // TODO: https://issues.apache.org/jira/browse/IGNITE-22084: Sql. Add 
support for row data type. Remove after row type is supported.
+    private enum CallScope {
+        VALUES,
+        GROUP,
+        OTHER
+    }
 }
diff --git 
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/util/IgniteResource.java
 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/util/IgniteResource.java
index ea95ab312f..774b20ee55 100644
--- 
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/util/IgniteResource.java
+++ 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/util/IgniteResource.java
@@ -82,6 +82,9 @@ public interface IgniteResource {
     @Resources.BaseMessage("CHAR datatype is not supported in table")
     Resources.ExInst<SqlValidatorException> 
charDataTypeIsNotSupportedInTable();
 
+    @BaseMessage("{0} datatype is not supported'")
+    ExInst<SqlValidatorException> dataTypeIsNotSupported(String a0);
+
     @Resources.BaseMessage("Length for type {0} must be at least 1")
     Resources.ExInst<SqlValidatorException> invalidStringLength(String 
typeName);
 
diff --git 
a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/planner/DynamicParametersTest.java
 
b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/planner/DynamicParametersTest.java
index 158a186f8f..1376a6b8fa 100644
--- 
a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/planner/DynamicParametersTest.java
+++ 
b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/planner/DynamicParametersTest.java
@@ -131,16 +131,16 @@ public class DynamicParametersTest extends 
AbstractPlannerTest {
 
                 sql("SELECT ? IN ('1', 2)", 2)
                         .parameterTypes(nullable(NativeTypes.INT32))
-                        .project("OR(=(?0, 1), =(?0, 2))"),
-
-                sql("SELECT (?,?) IN ((1,2))", 1, 2)
-                        .parameterTypes(nullable(NativeTypes.INT32), 
nullable(NativeTypes.INT32))
-                        .project("AND(=(?0, 1), =(?1, 2))"),
-
-                sql("SELECT (?,?) IN ((1,2))", "1", 
"2").fails(requireExplicitCast),
-                sql("SELECT (?,?) IN (('1', 2))", 1, 
"2").fails(requireExplicitCast),
-                sql("SELECT (?,?) IN ((1, '2'))", "1", 
"2").fails(requireExplicitCast)
+                        .project("OR(=(?0, 1), =(?0, 2))")
         );
+        // TODO https://issues.apache.org/jira/browse/IGNITE-22084: Sql. Add 
support for row data type.
+        // sql("SELECT (?,?) IN ((1,2))", 1, 2)
+        //        .parameterTypes(nullable(NativeTypes.INT32), 
nullable(NativeTypes.INT32))
+        //        .project("AND(=(?0, 1), =(?1, 2))"),
+
+        // sql("SELECT (?,?) IN ((1,2))", "1", "2").fails(requireExplicitCast),
+        // sql("SELECT (?,?) IN (('1', 2))", 1, 
"2").fails(requireExplicitCast),
+        // sql("SELECT (?,?) IN ((1, '2'))", "1", 
"2").fails(requireExplicitCast)
     }
 
     /** CASE expression. */

Reply via email to