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>