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

strongduanmu pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/shardingsphere.git


The following commit(s) were added to refs/heads/master by this push:
     new 02ea97fb2f8 Fix SQL NULL literal handling (#39776)
02ea97fb2f8 is described below

commit 02ea97fb2f8c7c42656994120672ff3343c5c527
Author: ZhangCheng <[email protected]>
AuthorDate: Wed Sep 9 11:28:45 2026 +0800

    Fix SQL NULL literal handling (#39776)
---
 .../engine/RowNumberPaginationContextEngine.java   |  3 ++
 .../engine/TopPaginationContextEngine.java         |  8 +++-
 .../RowNumberPaginationContextEngineTest.java      | 12 +++++
 .../engine/TopPaginationContextEngineTest.java     | 12 +++++
 .../OracleProjectionIdentifierExtractor.java       |  6 ++-
 .../OracleProjectionIdentifierExtractorTest.java   |  5 ++
 .../impl/LiteralExpressionConverter.java           |  2 +-
 .../type/ClickHouseDMLStatementVisitor.java        | 10 +++-
 .../statement/OpenGaussStatementVisitor.java       |  4 +-
 .../statement/OpenGaussStatementVisitorTest.java   | 42 +++++++++++++++++
 .../statement/type/OracleDMLStatementVisitor.java  |  2 +-
 .../statement/PostgreSQLStatementVisitor.java      |  4 +-
 .../statement/SQLServerStatementVisitor.java       |  2 +-
 .../statement/SQLServerStatementVisitorTest.java   | 42 +++++++++++++++++
 .../dml/expr/simple/LiteralExpressionSegment.java  |  9 ++++
 .../expr/simple/LiteralExpressionSegmentTest.java  | 54 ++++++++++++++++++++++
 .../src/main/resources/case/dml/clickhouse.xml     | 10 ++++
 .../main/resources/case/dml/select-group-by.xml    | 16 +++++++
 .../main/resources/case/dml/select-order-by.xml    | 16 +++++++
 .../resources/sql/supported/dml/clickhouse.xml     |  1 +
 .../sql/supported/dml/select-group-by.xml          |  1 +
 .../sql/supported/dml/select-order-by.xml          |  1 +
 22 files changed, 250 insertions(+), 12 deletions(-)

diff --git 
a/infra/binder/core/src/main/java/org/apache/shardingsphere/infra/binder/context/segment/select/pagination/engine/RowNumberPaginationContextEngine.java
 
b/infra/binder/core/src/main/java/org/apache/shardingsphere/infra/binder/context/segment/select/pagination/engine/RowNumberPaginationContextEngine.java
index 64a4b79a8a6..14f9fe9486b 100644
--- 
a/infra/binder/core/src/main/java/org/apache/shardingsphere/infra/binder/context/segment/select/pagination/engine/RowNumberPaginationContextEngine.java
+++ 
b/infra/binder/core/src/main/java/org/apache/shardingsphere/infra/binder/context/segment/select/pagination/engine/RowNumberPaginationContextEngine.java
@@ -108,6 +108,9 @@ public final class RowNumberPaginationContextEngine {
         RowNumberValueSegment offset = null;
         RowNumberValueSegment rowCount = null;
         for (BinaryOperationExpression each : rowNumberPredicates) {
+            if (each.getRight() instanceof LiteralExpressionSegment && 
((LiteralExpressionSegment) each.getRight()).isNullLiteral()) {
+                continue;
+            }
             String operator = each.getOperator();
             switch (operator) {
                 case ">":
diff --git 
a/infra/binder/core/src/main/java/org/apache/shardingsphere/infra/binder/context/segment/select/pagination/engine/TopPaginationContextEngine.java
 
b/infra/binder/core/src/main/java/org/apache/shardingsphere/infra/binder/context/segment/select/pagination/engine/TopPaginationContextEngine.java
index ae6061806eb..0de6808519c 100644
--- 
a/infra/binder/core/src/main/java/org/apache/shardingsphere/infra/binder/context/segment/select/pagination/engine/TopPaginationContextEngine.java
+++ 
b/infra/binder/core/src/main/java/org/apache/shardingsphere/infra/binder/context/segment/select/pagination/engine/TopPaginationContextEngine.java
@@ -85,12 +85,16 @@ public final class TopPaginationContextEngine {
         if (!(predicateSegment instanceof BinaryOperationExpression)) {
             return Optional.empty();
         }
+        ExpressionSegment right = ((BinaryOperationExpression) 
predicateSegment).getRight();
+        if (right instanceof LiteralExpressionSegment && 
((LiteralExpressionSegment) right).isNullLiteral()) {
+            return Optional.empty();
+        }
         String operator = ((BinaryOperationExpression) 
predicateSegment).getOperator();
         switch (operator) {
             case ">":
-                return 
Optional.of(createRowNumberValueSegment(((BinaryOperationExpression) 
predicateSegment).getRight(), false));
+                return Optional.of(createRowNumberValueSegment(right, false));
             case ">=":
-                return 
Optional.of(createRowNumberValueSegment(((BinaryOperationExpression) 
predicateSegment).getRight(), true));
+                return Optional.of(createRowNumberValueSegment(right, true));
             default:
                 return Optional.empty();
         }
diff --git 
a/infra/binder/core/src/test/java/org/apache/shardingsphere/infra/binder/context/segment/select/pagination/engine/RowNumberPaginationContextEngineTest.java
 
b/infra/binder/core/src/test/java/org/apache/shardingsphere/infra/binder/context/segment/select/pagination/engine/RowNumberPaginationContextEngineTest.java
index f287deb2a5c..0233e982033 100644
--- 
a/infra/binder/core/src/test/java/org/apache/shardingsphere/infra/binder/context/segment/select/pagination/engine/RowNumberPaginationContextEngineTest.java
+++ 
b/infra/binder/core/src/test/java/org/apache/shardingsphere/infra/binder/context/segment/select/pagination/engine/RowNumberPaginationContextEngineTest.java
@@ -129,6 +129,18 @@ class RowNumberPaginationContextEngineTest {
         assertThat(((NumberLiteralRowNumberValueSegment) 
paginationValueSegment.get()).getValue(), is(100L));
     }
     
+    @Test
+    void assertCreatePaginationContextWhenRowNumberValueIsNull() {
+        Projection projectionWithRowNumberAlias = new ColumnProjection(null, 
ROW_NUMBER_COLUMN_NAME, ROW_NUMBER_COLUMN_ALIAS, mock(DatabaseType.class));
+        ProjectionsContext projectionsContext = new ProjectionsContext(0, 0, 
false, Collections.singleton(projectionWithRowNumberAlias));
+        ColumnSegment left = new ColumnSegment(0, 10, new 
IdentifierValue(ROW_NUMBER_COLUMN_NAME));
+        LiteralExpressionSegment right = new LiteralExpressionSegment(0, 3, 
null);
+        BinaryOperationExpression expression = new 
BinaryOperationExpression(0, 0, left, right, "<", null);
+        PaginationContext paginationContext = 
paginationContextEngine.createPaginationContext(Collections.singletonList(expression),
 projectionsContext, Collections.emptyList());
+        assertFalse(paginationContext.getOffsetSegment().isPresent());
+        assertFalse(paginationContext.getRowCountSegment().isPresent());
+    }
+    
     private void 
assertCreatePaginationContextWhenRowNumberAliasPresentAndRowNumberPredicatedNotEmptyWithGivenOperator(final
 String operator) {
         Projection projectionWithRowNumberAlias = new ColumnProjection(null, 
ROW_NUMBER_COLUMN_NAME, ROW_NUMBER_COLUMN_ALIAS, mock(DatabaseType.class));
         ProjectionsContext projectionsContext = new ProjectionsContext(0, 0, 
false, Collections.singleton(projectionWithRowNumberAlias));
diff --git 
a/infra/binder/core/src/test/java/org/apache/shardingsphere/infra/binder/context/segment/select/pagination/engine/TopPaginationContextEngineTest.java
 
b/infra/binder/core/src/test/java/org/apache/shardingsphere/infra/binder/context/segment/select/pagination/engine/TopPaginationContextEngineTest.java
index 32c121c7305..030637c9482 100644
--- 
a/infra/binder/core/src/test/java/org/apache/shardingsphere/infra/binder/context/segment/select/pagination/engine/TopPaginationContextEngineTest.java
+++ 
b/infra/binder/core/src/test/java/org/apache/shardingsphere/infra/binder/context/segment/select/pagination/engine/TopPaginationContextEngineTest.java
@@ -51,6 +51,18 @@ class TopPaginationContextEngineTest {
         assertFalse(paginationContext.getRowCountSegment().isPresent());
     }
     
+    @Test
+    void assertCreatePaginationContextWhenRowNumberValueIsNull() {
+        String name = "rowNumberAlias";
+        ColumnSegment left = new ColumnSegment(0, 10, new 
IdentifierValue(name));
+        LiteralExpressionSegment right = new LiteralExpressionSegment(0, 3, 
null);
+        BinaryOperationExpression expression = new 
BinaryOperationExpression(0, 0, left, right, ">", null);
+        PaginationContext paginationContext = 
paginationContextEngine.createPaginationContext(
+                new TopProjectionSegment(0, 10, null, name), 
Collections.singletonList(expression), Collections.emptyList());
+        assertFalse(paginationContext.getOffsetSegment().isPresent());
+        assertFalse(paginationContext.getRowCountSegment().isPresent());
+    }
+    
     @Test
     void 
assertCreatePaginationContextWhenRowNumberPredicatePresentAndOperatorIsGreatThan()
 {
         
assertCreatePaginationContextWhenRowNumberPredicatePresentAndWithGivenOperator(">");
diff --git 
a/infra/binder/dialect/oracle/src/main/java/org/apache/shardingsphere/infra/binder/oracle/OracleProjectionIdentifierExtractor.java
 
b/infra/binder/dialect/oracle/src/main/java/org/apache/shardingsphere/infra/binder/oracle/OracleProjectionIdentifierExtractor.java
index bbad8e8f207..132863d555b 100644
--- 
a/infra/binder/dialect/oracle/src/main/java/org/apache/shardingsphere/infra/binder/oracle/OracleProjectionIdentifierExtractor.java
+++ 
b/infra/binder/dialect/oracle/src/main/java/org/apache/shardingsphere/infra/binder/oracle/OracleProjectionIdentifierExtractor.java
@@ -42,7 +42,11 @@ public final class OracleProjectionIdentifierExtractor 
implements DialectProject
     @Override
     public String getColumnNameFromExpression(final ExpressionSegment 
expressionSegment) {
         if (expressionSegment instanceof ExpressionProjectionSegment && 
((ExpressionProjectionSegment) expressionSegment).getExpr() instanceof 
LiteralExpressionSegment) {
-            Object literal = ((LiteralExpressionSegment) 
((ExpressionProjectionSegment) expressionSegment).getExpr()).getLiterals();
+            LiteralExpressionSegment literalExpression = 
(LiteralExpressionSegment) ((ExpressionProjectionSegment) 
expressionSegment).getExpr();
+            if (literalExpression.isNullLiteral()) {
+                return "NULL";
+            }
+            Object literal = literalExpression.getLiterals();
             if (literal instanceof String) {
                 return String.format("'%s'", literal.toString().replace("'", 
"''"));
             }
diff --git 
a/infra/binder/dialect/oracle/src/test/java/org/apache/shardingsphere/infra/binder/oracle/OracleProjectionIdentifierExtractorTest.java
 
b/infra/binder/dialect/oracle/src/test/java/org/apache/shardingsphere/infra/binder/oracle/OracleProjectionIdentifierExtractorTest.java
index 66c74db3559..5803efc54a0 100644
--- 
a/infra/binder/dialect/oracle/src/test/java/org/apache/shardingsphere/infra/binder/oracle/OracleProjectionIdentifierExtractorTest.java
+++ 
b/infra/binder/dialect/oracle/src/test/java/org/apache/shardingsphere/infra/binder/oracle/OracleProjectionIdentifierExtractorTest.java
@@ -64,6 +64,11 @@ class OracleProjectionIdentifierExtractorTest {
         assertThat(extractor.getColumnNameFromExpression(new 
ExpressionProjectionSegment(0, 7, "EXTMSG", new LiteralExpressionSegment(0, 7, 
"EXTMSG"))), is("'EXTMSG'"));
     }
     
+    @Test
+    void assertGetColumnNameFromNullLiteralExpression() {
+        assertThat(extractor.getColumnNameFromExpression(new 
ExpressionProjectionSegment(0, 3, "NULL", new LiteralExpressionSegment(0, 3, 
null))), is("NULL"));
+    }
+    
     @Test
     void assertGetColumnNameFromSubquery() {
         assertThat(extractor.getColumnNameFromSubquery(new 
SubqueryProjectionSegment(mock(SubquerySegment.class), "text")), is("TEXT"));
diff --git 
a/kernel/sql-federation/compiler/src/main/java/org/apache/shardingsphere/sqlfederation/compiler/sql/ast/converter/segment/expression/impl/LiteralExpressionConverter.java
 
b/kernel/sql-federation/compiler/src/main/java/org/apache/shardingsphere/sqlfederation/compiler/sql/ast/converter/segment/expression/impl/LiteralExpressionConverter.java
index c098b036014..5b4272952ab 100644
--- 
a/kernel/sql-federation/compiler/src/main/java/org/apache/shardingsphere/sqlfederation/compiler/sql/ast/converter/segment/expression/impl/LiteralExpressionConverter.java
+++ 
b/kernel/sql-federation/compiler/src/main/java/org/apache/shardingsphere/sqlfederation/compiler/sql/ast/converter/segment/expression/impl/LiteralExpressionConverter.java
@@ -57,7 +57,7 @@ public final class LiteralExpressionConverter {
      * @return sql node
      */
     public static Optional<SqlNode> convert(final LiteralExpressionSegment 
segment, final RelDataType dataType) {
-        if (null == segment.getLiterals()) {
+        if (segment.isNullLiteral()) {
             return Optional.of(SqlLiteral.createNull(SqlParserPos.ZERO));
         }
         String literalValue = String.valueOf(segment.getLiterals());
diff --git 
a/parser/sql/engine/dialect/clickhouse/src/main/java/org/apache/shardingsphere/sql/parser/engine/clickhouse/visitor/statement/type/ClickHouseDMLStatementVisitor.java
 
b/parser/sql/engine/dialect/clickhouse/src/main/java/org/apache/shardingsphere/sql/parser/engine/clickhouse/visitor/statement/type/ClickHouseDMLStatementVisitor.java
index b65e827992c..fae71689bf5 100644
--- 
a/parser/sql/engine/dialect/clickhouse/src/main/java/org/apache/shardingsphere/sql/parser/engine/clickhouse/visitor/statement/type/ClickHouseDMLStatementVisitor.java
+++ 
b/parser/sql/engine/dialect/clickhouse/src/main/java/org/apache/shardingsphere/sql/parser/engine/clickhouse/visitor/statement/type/ClickHouseDMLStatementVisitor.java
@@ -41,6 +41,7 @@ import 
org.apache.shardingsphere.sql.parser.statement.core.segment.dml.expr.Bina
 import 
org.apache.shardingsphere.sql.parser.statement.core.segment.dml.expr.ExpressionSegment;
 import 
org.apache.shardingsphere.sql.parser.statement.core.segment.dml.expr.FunctionSegment;
 import 
org.apache.shardingsphere.sql.parser.statement.core.segment.dml.expr.complex.CommonExpressionSegment;
+import 
org.apache.shardingsphere.sql.parser.statement.core.segment.dml.expr.simple.LiteralExpressionSegment;
 import 
org.apache.shardingsphere.sql.parser.statement.core.segment.dml.expr.simple.ParameterMarkerExpressionSegment;
 import 
org.apache.shardingsphere.sql.parser.statement.core.segment.dml.expr.subquery.SubqueryExpressionSegment;
 import 
org.apache.shardingsphere.sql.parser.statement.core.segment.dml.expr.subquery.SubquerySegment;
@@ -324,8 +325,13 @@ public final class ClickHouseDMLStatementVisitor extends 
ClickHouseStatementVisi
             return projection;
         }
         ExpressionSegment column = (ExpressionSegment) projection;
-        ExpressionProjectionSegment result = null == alias ? new 
ExpressionProjectionSegment(column.getStartIndex(), column.getStopIndex(), 
column.getText(), column)
-                : new ExpressionProjectionSegment(column.getStartIndex(), 
ctx.alias().stop.getStopIndex(), String.valueOf(column.getText()), column);
+        ExpressionProjectionSegment result;
+        if (null == alias) {
+            String expressionText = column instanceof LiteralExpressionSegment 
&& ((LiteralExpressionSegment) column).isNullLiteral() ? 
getOriginalText(ctx.expr()) : column.getText();
+            result = new ExpressionProjectionSegment(column.getStartIndex(), 
column.getStopIndex(), expressionText, column);
+        } else {
+            result = new ExpressionProjectionSegment(column.getStartIndex(), 
ctx.alias().stop.getStopIndex(), String.valueOf(column.getText()), column);
+        }
         result.setAlias(alias);
         return result;
     }
diff --git 
a/parser/sql/engine/dialect/opengauss/src/main/java/org/apache/shardingsphere/sql/parser/engine/opengauss/visitor/statement/OpenGaussStatementVisitor.java
 
b/parser/sql/engine/dialect/opengauss/src/main/java/org/apache/shardingsphere/sql/parser/engine/opengauss/visitor/statement/OpenGaussStatementVisitor.java
index c7958796413..6bd361e8c12 100644
--- 
a/parser/sql/engine/dialect/opengauss/src/main/java/org/apache/shardingsphere/sql/parser/engine/opengauss/visitor/statement/OpenGaussStatementVisitor.java
+++ 
b/parser/sql/engine/dialect/opengauss/src/main/java/org/apache/shardingsphere/sql/parser/engine/opengauss/visitor/statement/OpenGaussStatementVisitor.java
@@ -694,7 +694,7 @@ public abstract class OpenGaussStatementVisitor extends 
OpenGaussStatementParser
             ColumnSegment column = (ColumnSegment) expr;
             return new ColumnOrderByItemSegment(column, orderDirection, 
nullsOrderType);
         }
-        if (expr instanceof LiteralExpressionSegment) {
+        if (expr instanceof LiteralExpressionSegment && 
!((LiteralExpressionSegment) expr).isNullLiteral()) {
             LiteralExpressionSegment index = (LiteralExpressionSegment) expr;
             return new IndexOrderByItemSegment(index.getStartIndex(), 
index.getStopIndex(), Integer.parseInt(index.getLiterals().toString()), 
orderDirection, nullsOrderType);
         }
@@ -1210,7 +1210,7 @@ public abstract class OpenGaussStatementVisitor extends 
OpenGaussStatementParser
             if (astNode instanceof ColumnSegment) {
                 return new ColumnOrderByItemSegment((ColumnSegment) astNode, 
OrderDirection.ASC, null);
             }
-            if (astNode instanceof LiteralExpressionSegment) {
+            if (astNode instanceof LiteralExpressionSegment && 
!((LiteralExpressionSegment) astNode).isNullLiteral()) {
                 LiteralExpressionSegment index = (LiteralExpressionSegment) 
astNode;
                 return new IndexOrderByItemSegment(index.getStartIndex(), 
index.getStopIndex(),
                         Integer.parseInt(index.getLiterals().toString()), 
OrderDirection.ASC, null);
diff --git 
a/parser/sql/engine/dialect/opengauss/src/test/java/org/apache/shardingsphere/sql/parser/engine/opengauss/visitor/statement/OpenGaussStatementVisitorTest.java
 
b/parser/sql/engine/dialect/opengauss/src/test/java/org/apache/shardingsphere/sql/parser/engine/opengauss/visitor/statement/OpenGaussStatementVisitorTest.java
new file mode 100644
index 00000000000..1db0803d80b
--- /dev/null
+++ 
b/parser/sql/engine/dialect/opengauss/src/test/java/org/apache/shardingsphere/sql/parser/engine/opengauss/visitor/statement/OpenGaussStatementVisitorTest.java
@@ -0,0 +1,42 @@
+/*
+ * 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.shardingsphere.sql.parser.engine.opengauss.visitor.statement;
+
+import org.apache.shardingsphere.sql.parser.engine.api.CacheOption;
+import org.apache.shardingsphere.sql.parser.engine.api.SQLParserEngine;
+import 
org.apache.shardingsphere.sql.parser.engine.api.SQLStatementVisitorEngine;
+import 
org.apache.shardingsphere.sql.parser.statement.core.segment.dml.expr.simple.LiteralExpressionSegment;
+import 
org.apache.shardingsphere.sql.parser.statement.core.segment.dml.order.item.ExpressionOrderByItemSegment;
+import 
org.apache.shardingsphere.sql.parser.statement.core.statement.type.dml.SelectStatement;
+import org.junit.jupiter.api.Test;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+class OpenGaussStatementVisitorTest {
+    
+    @Test
+    void assertVisitGroupByNull() {
+        SelectStatement actual = (SelectStatement) new 
SQLStatementVisitorEngine("openGauss").visit(new SQLParserEngine("openGauss", 
new CacheOption(128, 1024L)).parse(
+                "SELECT COUNT(*) FROM t_order GROUP BY NULL", false));
+        ExpressionOrderByItemSegment actualItem = 
(ExpressionOrderByItemSegment) 
actual.getGroupBy().get().getGroupByItems().iterator().next();
+        assertThat(actualItem.getText(), is("NULL"));
+        assertTrue(((LiteralExpressionSegment) 
actualItem.getExpr()).isNullLiteral());
+    }
+}
diff --git 
a/parser/sql/engine/dialect/oracle/src/main/java/org/apache/shardingsphere/sql/parser/engine/oracle/visitor/statement/type/OracleDMLStatementVisitor.java
 
b/parser/sql/engine/dialect/oracle/src/main/java/org/apache/shardingsphere/sql/parser/engine/oracle/visitor/statement/type/OracleDMLStatementVisitor.java
index 2f6e6b0de4e..4e8f0641d91 100644
--- 
a/parser/sql/engine/dialect/oracle/src/main/java/org/apache/shardingsphere/sql/parser/engine/oracle/visitor/statement/type/OracleDMLStatementVisitor.java
+++ 
b/parser/sql/engine/dialect/oracle/src/main/java/org/apache/shardingsphere/sql/parser/engine/oracle/visitor/statement/type/OracleDMLStatementVisitor.java
@@ -1553,7 +1553,7 @@ public final class OracleDMLStatementVisitor extends 
OracleStatementVisitor impl
             ColumnSegment column = (ColumnSegment) expression;
             return new ColumnOrderByItemSegment(column, OrderDirection.ASC, 
null);
         }
-        if (expression instanceof LiteralExpressionSegment) {
+        if (expression instanceof LiteralExpressionSegment && 
!((LiteralExpressionSegment) expression).isNullLiteral()) {
             LiteralExpressionSegment literalExpression = 
(LiteralExpressionSegment) expression;
             return new 
IndexOrderByItemSegment(literalExpression.getStartIndex(), 
literalExpression.getStopIndex(),
                     
SQLUtils.getExactlyNumber(literalExpression.getLiterals().toString(), 
10).intValue(), OrderDirection.ASC, null);
diff --git 
a/parser/sql/engine/dialect/postgresql/src/main/java/org/apache/shardingsphere/sql/parser/engine/postgresql/visitor/statement/PostgreSQLStatementVisitor.java
 
b/parser/sql/engine/dialect/postgresql/src/main/java/org/apache/shardingsphere/sql/parser/engine/postgresql/visitor/statement/PostgreSQLStatementVisitor.java
index 399ffd14d31..6e2fd6c5826 100644
--- 
a/parser/sql/engine/dialect/postgresql/src/main/java/org/apache/shardingsphere/sql/parser/engine/postgresql/visitor/statement/PostgreSQLStatementVisitor.java
+++ 
b/parser/sql/engine/dialect/postgresql/src/main/java/org/apache/shardingsphere/sql/parser/engine/postgresql/visitor/statement/PostgreSQLStatementVisitor.java
@@ -705,7 +705,7 @@ public abstract class PostgreSQLStatementVisitor extends 
PostgreSQLStatementPars
             ColumnSegment column = (ColumnSegment) expr;
             return new ColumnOrderByItemSegment(column, orderDirection, 
nullsOrderType);
         }
-        if (expr instanceof LiteralExpressionSegment) {
+        if (expr instanceof LiteralExpressionSegment && 
!((LiteralExpressionSegment) expr).isNullLiteral()) {
             LiteralExpressionSegment index = (LiteralExpressionSegment) expr;
             return new IndexOrderByItemSegment(index.getStartIndex(), 
index.getStopIndex(), Integer.parseInt(index.getLiterals().toString()), 
orderDirection, nullsOrderType);
         }
@@ -1247,7 +1247,7 @@ public abstract class PostgreSQLStatementVisitor extends 
PostgreSQLStatementPars
             if (astNode instanceof ColumnSegment) {
                 return new ColumnOrderByItemSegment((ColumnSegment) astNode, 
OrderDirection.ASC, null);
             }
-            if (astNode instanceof LiteralExpressionSegment) {
+            if (astNode instanceof LiteralExpressionSegment && 
!((LiteralExpressionSegment) astNode).isNullLiteral()) {
                 LiteralExpressionSegment index = (LiteralExpressionSegment) 
astNode;
                 return new IndexOrderByItemSegment(index.getStartIndex(), 
index.getStopIndex(),
                         Integer.parseInt(index.getLiterals().toString()), 
OrderDirection.ASC, null);
diff --git 
a/parser/sql/engine/dialect/sqlserver/src/main/java/org/apache/shardingsphere/sql/parser/engine/sqlserver/visitor/statement/SQLServerStatementVisitor.java
 
b/parser/sql/engine/dialect/sqlserver/src/main/java/org/apache/shardingsphere/sql/parser/engine/sqlserver/visitor/statement/SQLServerStatementVisitor.java
index 23ecba76c85..d69018cc9b2 100644
--- 
a/parser/sql/engine/dialect/sqlserver/src/main/java/org/apache/shardingsphere/sql/parser/engine/sqlserver/visitor/statement/SQLServerStatementVisitor.java
+++ 
b/parser/sql/engine/dialect/sqlserver/src/main/java/org/apache/shardingsphere/sql/parser/engine/sqlserver/visitor/statement/SQLServerStatementVisitor.java
@@ -1419,7 +1419,7 @@ public abstract class SQLServerStatementVisitor extends 
SQLServerStatementBaseVi
             ColumnSegment column = (ColumnSegment) expression;
             return new ColumnOrderByItemSegment(column, OrderDirection.ASC, 
null);
         }
-        if (expression instanceof LiteralExpressionSegment) {
+        if (expression instanceof LiteralExpressionSegment && 
!((LiteralExpressionSegment) expression).isNullLiteral()) {
             LiteralExpressionSegment literalExpression = 
(LiteralExpressionSegment) expression;
             return new 
IndexOrderByItemSegment(literalExpression.getStartIndex(), 
literalExpression.getStopIndex(),
                     
SQLUtils.getExactlyNumber(literalExpression.getLiterals().toString(), 
10).intValue(), OrderDirection.ASC, null);
diff --git 
a/parser/sql/engine/dialect/sqlserver/src/test/java/org/apache/shardingsphere/sql/parser/engine/sqlserver/visitor/statement/SQLServerStatementVisitorTest.java
 
b/parser/sql/engine/dialect/sqlserver/src/test/java/org/apache/shardingsphere/sql/parser/engine/sqlserver/visitor/statement/SQLServerStatementVisitorTest.java
new file mode 100644
index 00000000000..a16df454187
--- /dev/null
+++ 
b/parser/sql/engine/dialect/sqlserver/src/test/java/org/apache/shardingsphere/sql/parser/engine/sqlserver/visitor/statement/SQLServerStatementVisitorTest.java
@@ -0,0 +1,42 @@
+/*
+ * 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.shardingsphere.sql.parser.engine.sqlserver.visitor.statement;
+
+import org.apache.shardingsphere.sql.parser.engine.api.CacheOption;
+import org.apache.shardingsphere.sql.parser.engine.api.SQLParserEngine;
+import 
org.apache.shardingsphere.sql.parser.engine.api.SQLStatementVisitorEngine;
+import 
org.apache.shardingsphere.sql.parser.statement.core.segment.dml.expr.simple.LiteralExpressionSegment;
+import 
org.apache.shardingsphere.sql.parser.statement.core.segment.dml.order.item.ExpressionOrderByItemSegment;
+import 
org.apache.shardingsphere.sql.parser.statement.core.statement.type.dml.SelectStatement;
+import org.junit.jupiter.api.Test;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+class SQLServerStatementVisitorTest {
+    
+    @Test
+    void assertVisitGroupByNull() {
+        SelectStatement actual = (SelectStatement) new 
SQLStatementVisitorEngine("SQLServer").visit(new SQLParserEngine("SQLServer", 
new CacheOption(128, 1024L)).parse(
+                "SELECT COUNT(*) FROM t_order GROUP BY NULL", false));
+        ExpressionOrderByItemSegment actualItem = 
(ExpressionOrderByItemSegment) 
actual.getGroupBy().get().getGroupByItems().iterator().next();
+        assertThat(actualItem.getText(), is("NULL"));
+        assertTrue(((LiteralExpressionSegment) 
actualItem.getExpr()).isNullLiteral());
+    }
+}
diff --git 
a/parser/sql/statement/core/src/main/java/org/apache/shardingsphere/sql/parser/statement/core/segment/dml/expr/simple/LiteralExpressionSegment.java
 
b/parser/sql/statement/core/src/main/java/org/apache/shardingsphere/sql/parser/statement/core/segment/dml/expr/simple/LiteralExpressionSegment.java
index 031c2970bf2..f8eefd9d7ed 100644
--- 
a/parser/sql/statement/core/src/main/java/org/apache/shardingsphere/sql/parser/statement/core/segment/dml/expr/simple/LiteralExpressionSegment.java
+++ 
b/parser/sql/statement/core/src/main/java/org/apache/shardingsphere/sql/parser/statement/core/segment/dml/expr/simple/LiteralExpressionSegment.java
@@ -33,6 +33,15 @@ public class LiteralExpressionSegment implements 
SimpleExpressionSegment {
     
     private final Object literals;
     
+    /**
+     * Judge whether this literal represents SQL NULL.
+     *
+     * @return true if this literal represents SQL NULL, otherwise false
+     */
+    public boolean isNullLiteral() {
+        return null == literals;
+    }
+    
     @Override
     public String getText() {
         return null == literals ? null : literals.toString();
diff --git 
a/parser/sql/statement/core/src/test/java/org/apache/shardingsphere/sql/parser/statement/core/segment/dml/expr/simple/LiteralExpressionSegmentTest.java
 
b/parser/sql/statement/core/src/test/java/org/apache/shardingsphere/sql/parser/statement/core/segment/dml/expr/simple/LiteralExpressionSegmentTest.java
new file mode 100644
index 00000000000..7b47733ed02
--- /dev/null
+++ 
b/parser/sql/statement/core/src/test/java/org/apache/shardingsphere/sql/parser/statement/core/segment/dml/expr/simple/LiteralExpressionSegmentTest.java
@@ -0,0 +1,54 @@
+/*
+ * 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.shardingsphere.sql.parser.statement.core.segment.dml.expr.simple;
+
+import org.junit.jupiter.api.Test;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+class LiteralExpressionSegmentTest {
+    
+    @Test
+    void assertIsNullLiteral() {
+        assertTrue(new LiteralExpressionSegment(0, 3, null).isNullLiteral());
+    }
+    
+    @Test
+    void assertIsNullLiteralWithNullString() {
+        assertFalse(new LiteralExpressionSegment(0, 5, 
"null").isNullLiteral());
+    }
+    
+    @Test
+    void assertIsNullLiteralWithEmptyString() {
+        assertFalse(new LiteralExpressionSegment(0, 1, "").isNullLiteral());
+    }
+    
+    @Test
+    void assertGetTextWithNullValue() {
+        assertNull(new LiteralExpressionSegment(0, 3, null).getText());
+    }
+    
+    @Test
+    void assertGetTextWithNullString() {
+        assertThat(new LiteralExpressionSegment(0, 5, "null").getText(), 
is("null"));
+    }
+}
diff --git a/test/it/parser/src/main/resources/case/dml/clickhouse.xml 
b/test/it/parser/src/main/resources/case/dml/clickhouse.xml
index 42871194c33..91af8a199ef 100644
--- a/test/it/parser/src/main/resources/case/dml/clickhouse.xml
+++ b/test/it/parser/src/main/resources/case/dml/clickhouse.xml
@@ -1172,4 +1172,14 @@
             <simple-table name="t_order" start-index="28" stop-index="34" />
         </from>
     </select>
+
+    <select sql-case-id="clickhouse_select_null_without_alias">
+        <projections start-index="7" stop-index="10">
+            <expression-projection text="NULL" start-index="7" stop-index="10">
+                <expr>
+                    <literal-expression value="null" start-index="7" 
stop-index="10" />
+                </expr>
+            </expression-projection>
+        </projections>
+    </select>
 </sql-parser-test-cases>
diff --git a/test/it/parser/src/main/resources/case/dml/select-group-by.xml 
b/test/it/parser/src/main/resources/case/dml/select-group-by.xml
index e68f1c81dfb..3ad9f4d03cf 100644
--- a/test/it/parser/src/main/resources/case/dml/select-group-by.xml
+++ b/test/it/parser/src/main/resources/case/dml/select-group-by.xml
@@ -1076,4 +1076,20 @@
             <column-item name="department_id" order-direction="ASC" 
start-index="62" stop-index="74" />
         </group-by>
     </select>
+
+    <select sql-case-id="select_group_by_null_expression">
+        <from>
+            <simple-table name="t_order" start-index="21" stop-index="27" />
+        </from>
+        <projections start-index="7" stop-index="14">
+            <aggregation-projection type="COUNT" expression="COUNT(*)" 
start-index="7" stop-index="14" />
+        </projections>
+        <group-by>
+            <expression-item expression="NULL" start-index="38" 
stop-index="41">
+                <expr>
+                    <literal-expression value="null" start-index="38" 
stop-index="41" />
+                </expr>
+            </expression-item>
+        </group-by>
+    </select>
 </sql-parser-test-cases>
diff --git a/test/it/parser/src/main/resources/case/dml/select-order-by.xml 
b/test/it/parser/src/main/resources/case/dml/select-order-by.xml
index a6987b0a7b3..c59feb7dcaf 100644
--- a/test/it/parser/src/main/resources/case/dml/select-order-by.xml
+++ b/test/it/parser/src/main/resources/case/dml/select-order-by.xml
@@ -426,4 +426,20 @@
             <index-item index="1" order-direction="ASC" start-index="47" 
stop-index="47" />
         </order-by>
     </select>
+
+    <select sql-case-id="select_order_by_null_expression">
+        <from>
+            <simple-table name="t_order" start-index="21" stop-index="27" />
+        </from>
+        <projections start-index="7" stop-index="14">
+            <column-projection name="order_id" start-index="7" stop-index="14" 
/>
+        </projections>
+        <order-by>
+            <expression-item expression="NULL" start-index="38" 
stop-index="41">
+                <expr>
+                    <literal-expression value="null" start-index="38" 
stop-index="41" />
+                </expr>
+            </expression-item>
+        </order-by>
+    </select>
 </sql-parser-test-cases>
diff --git a/test/it/parser/src/main/resources/sql/supported/dml/clickhouse.xml 
b/test/it/parser/src/main/resources/sql/supported/dml/clickhouse.xml
index 1831796945a..44077923988 100644
--- a/test/it/parser/src/main/resources/sql/supported/dml/clickhouse.xml
+++ b/test/it/parser/src/main/resources/sql/supported/dml/clickhouse.xml
@@ -56,4 +56,5 @@
     <sql-case id="clickhouse_select_interval_plus" value="SELECT order_time + 
INTERVAL 1 DAY AS next_day FROM t_order" db-types="ClickHouse" />
     <sql-case id="clickhouse_select_sum_distinct_expr" value="SELECT 
SUM(DISTINCT amount + 1) FROM t_order" db-types="ClickHouse" />
     <sql-case id="clickhouse_select_cast_literal" value="SELECT CAST(1 AS 
INT8) FROM t_order" db-types="ClickHouse" />
+    <sql-case id="clickhouse_select_null_without_alias" value="SELECT NULL" 
db-types="ClickHouse" />
 </sql-cases>
diff --git 
a/test/it/parser/src/main/resources/sql/supported/dml/select-group-by.xml 
b/test/it/parser/src/main/resources/sql/supported/dml/select-group-by.xml
index 5a2ff127d1c..6126520c9c1 100644
--- a/test/it/parser/src/main/resources/sql/supported/dml/select-group-by.xml
+++ b/test/it/parser/src/main/resources/sql/supported/dml/select-group-by.xml
@@ -52,4 +52,5 @@
     <sql-case id="select_group_by_top_column_value" value="SELECT TOP 10 
hash_unique_bigint_id FROM dbo.TelemetryDS WHERE Timestamp BETWEEN @StartTime 
AND @EndTime GROUP BY hash_unique_bigint_id ORDER BY 
MAX(max_elapsed_time_microsec) DESC" db-types="SQLServer" />
     <sql-case id="select_group_by_count_with_tumblingwindow_function" 
value="SELECT count(*) FROM input GROUP BY clusterid,tumblingwindow(minutes, 
5)" db-types="SQLServer" />
     <sql-case id="select_group_by_rollup_oracle" value="SELECT dept, 
SUM(salary) FROM employees GROUP BY ROLLUP(dept)" db-types="Oracle" />
+    <sql-case id="select_group_by_null_expression" value="SELECT COUNT(*) FROM 
t_order GROUP BY NULL" db-types="MySQL,PostgreSQL,Oracle" />
 </sql-cases>
diff --git 
a/test/it/parser/src/main/resources/sql/supported/dml/select-order-by.xml 
b/test/it/parser/src/main/resources/sql/supported/dml/select-order-by.xml
index 3a93841fb72..3d64efc9dfc 100644
--- a/test/it/parser/src/main/resources/sql/supported/dml/select-order-by.xml
+++ b/test/it/parser/src/main/resources/sql/supported/dml/select-order-by.xml
@@ -37,4 +37,5 @@
     <sql-case id="select_order_by_with_table_star_without_table_name" 
value="SELECT i.*, o.* FROM t_order o JOIN t_order_item i ON o.user_id = 
i.user_id AND o.order_id = i.order_id ORDER BY item_id" db-types="H2,MySQL" />
     <sql-case id="select_order_by_expression_binary_operation" value="select * 
from t_order order by 1+1" db-types="MySQL,Presto,Doris,Firebird" />
     <sql-case id="select_order_by_position_oracle" value="SELECT order_id, 
user_id FROM t_order ORDER BY 1" db-types="Oracle" />
+    <sql-case id="select_order_by_null_expression" value="SELECT order_id FROM 
t_order ORDER BY NULL" db-types="PostgreSQL,openGauss" />
 </sql-cases>

Reply via email to