{"record":{"id":"48770f55ade1ce2b","repo":"apache/shardingsphere","slug":"row-expression-can-only-compare-with-subquery","errorCode":null,"errorMessage":"Row expression can only compare with subquery","messagePattern":"Row expression can only compare with subquery","errorType":"exception","errorClass":"UnsupportedSQLOperationException","httpStatus":null,"severity":"error","filePath":"features/encrypt/core/src/main/java/org/apache/shardingsphere/encrypt/checker/cryptographic/SubqueryNestedInBinaryOperationEncryptorChecker.java","lineNumber":94,"sourceCode":"            checkEncryptorIsSame(rowColumnInfos.get(i), otherColumnInfos.get(i), encryptRule, scenario);\n        }\n    }\n    \n    private static List<ColumnSegmentBoundInfo> getRowExpressionColumnBoundInfos(final RowExpression rowExpression) {\n        List<ColumnSegmentBoundInfo> result = new ArrayList<>();\n        for (ExpressionSegment each : rowExpression.getItems()) {\n            if (each instanceof ColumnSegment) {\n                result.add(((ColumnSegment) each).getColumnBoundInfo());\n            }\n        }\n        return result;\n    }\n    \n    private static List<ColumnSegmentBoundInfo> getExpressionColumnBoundInfos(final ExpressionSegment expression, final int expectedSize) {\n        if (expression instanceof SubqueryExpressionSegment || expression instanceof SubquerySegment) {\n            return getSubqueryColumnBoundInfos(expression, expectedSize);\n        }\n        throw new UnsupportedSQLOperationException(\"Row expression can only compare with subquery\");\n    }\n    \n    private static List<ColumnSegmentBoundInfo> getSubqueryColumnBoundInfos(final ExpressionSegment expression, final int expectedSize) {\n        ShardingSpherePreconditions.checkState(isSubquerySegment(expression),\n                () -> new UnsupportedSQLOperationException(String.format(\"only support subquery segment, but got %s\", expression.getClass().getName())));\n        SubquerySegment subquerySegment = getSubquerySegment(expression);\n        Collection<ProjectionSegment> projections = subquerySegment.getSelect().getProjections().getProjections();\n        ShardingSpherePreconditions.checkState(projections.size() == expectedSize,\n                () -> new UnsupportedSQLOperationException(String.format(\"Subquery column count %d does not match row expression column count %d\", projections.size(), expectedSize)));\n        List<ColumnSegmentBoundInfo> result = new ArrayList<>();\n        for (ProjectionSegment each : projections) {\n            result.add(each instanceof ColumnProjectionSegment\n                    ? ((ColumnProjectionSegment) each).getColumn().getColumnBoundInfo()\n                    : new ColumnSegmentBoundInfo(new IdentifierValue(each.getColumnLabel())));\n        }\n        return result;\n    }\n    ","sourceCodeStart":76,"sourceCodeEnd":112,"githubUrl":"https://github.com/apache/shardingsphere/blob/e952770a215630a3659c75d64369168cd3e26b82/features/encrypt/core/src/main/java/org/apache/shardingsphere/encrypt/checker/cryptographic/SubqueryNestedInBinaryOperationEncryptorChecker.java#L76-L112","documentation":"SubqueryNestedInBinaryOperationEncryptorChecker validates row expressions compared against subqueries (e.g. (a,b) = (SELECT x,y ...)). getExpressionColumnBoundInfos only accepts SubqueryExpressionSegment or SubquerySegment; any other right-hand expression type throws UnsupportedSQLOperationException 'Row expression can only compare with subquery'.","triggerScenarios":"Executing an encrypt-ruled SQL where a RowExpression is compared with something that is neither a subquery expression nor a subquery segment — e.g. a row constructor on the right side ((a,b) = (c,d)), a function result, or a parser AST shape this checker does not recognize, while the table/columns involved are encrypt columns.","commonSituations":"Row-value constructor comparisons (SQL standard (a,b) = (c,d)) on encrypted columns; parser upgrades changing the AST node type emitted for parenthesized expressions; complex expressions mixing literals and columns against row expressions.","solutions":["Rewrite the row comparison as individual column comparisons (a = c AND b = d) for encrypted columns","If comparing with a subquery is intended, verify the subquery parses as a SubqueryExpressionSegment (check for parentheses/aliases that change AST shape)","Keep encrypted columns out of row-value constructor expressions","Upgrade ShardingSphere — checker coverage for row expression shapes grows between versions"],"exampleFix":"-- before\nSELECT * FROM t WHERE (c1, c2) = (SELECT x, y FROM s)\n-- after (if still unsupported for your AST shape)\nSELECT * FROM t WHERE c1 = (SELECT x FROM s) AND c2 = (SELECT y FROM s)","handlingStrategy":"validation","validationCode":"String upper = sql.toUpperCase();\nif (upper.matches(\"(?s).*\\\\(\\\\s*\\\\w+\\\\s*,\\\\s*\\\\w+\\\\s*\\\\)\\\\s*=\\\\s*\\\\(.*\\\\).*\")) { /* row-vs-row or unsupported shape: rewrite before sending */ }","typeGuard":null,"tryCatchPattern":"catch (UnsupportedSQLOperationException e) { if (e.getMessage().contains(\"Row expression can only compare with subquery\")) { /* rewrite as separate column comparisons and retry */ } }","preventionTips":["Avoid row-value constructors on encrypted columns","Keep subqueries syntactically simple so they parse as SubqueryExpressionSegment","Add a pre-deploy SQL lint for encrypted tables"],"tags":["encrypt","sql-checker","row-expression","subquery"],"backgroundTag":null,"analyzedSha":"e952770a215630a3659c75d64369168cd3e26b82","analyzedAt":"2026-08-14T13:54:53.392Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}