This is an automated email from the ASF dual-hosted git repository.
terrymanu 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 e81c7c9aa5b Fix nullable SQL normalization in projection matching
(#40226)
e81c7c9aa5b is described below
commit e81c7c9aa5b49f6c1939eb62ec5c9f6c70e2491d
Author: Liang Zhang <[email protected]>
AuthorDate: Sun Oct 18 23:22:30 2026 +0800
Fix nullable SQL normalization in projection matching (#40226)
---
.../engine/ProjectionsContextEngine.java | 24 ++++---
.../engine/ProjectionsContextEngineTest.java | 84 ++++++++++++++++++++++
.../sql/parser/statement/core/util/SQLUtils.java | 15 ++--
.../parser/statement/core/util/SQLUtilsTest.java | 5 ++
4 files changed, 111 insertions(+), 17 deletions(-)
diff --git
a/infra/binder/core/src/main/java/org/apache/shardingsphere/infra/binder/context/segment/select/projection/engine/ProjectionsContextEngine.java
b/infra/binder/core/src/main/java/org/apache/shardingsphere/infra/binder/context/segment/select/projection/engine/ProjectionsContextEngine.java
index 7b1029036ae..8e84a039726 100644
---
a/infra/binder/core/src/main/java/org/apache/shardingsphere/infra/binder/context/segment/select/projection/engine/ProjectionsContextEngine.java
+++
b/infra/binder/core/src/main/java/org/apache/shardingsphere/infra/binder/context/segment/select/projection/engine/ProjectionsContextEngine.java
@@ -27,6 +27,7 @@ import
org.apache.shardingsphere.infra.binder.context.segment.select.projection.
import
org.apache.shardingsphere.infra.binder.context.segment.select.projection.impl.ColumnProjection;
import
org.apache.shardingsphere.infra.binder.context.segment.select.projection.impl.DerivedProjection;
import
org.apache.shardingsphere.infra.binder.context.segment.select.projection.impl.ShorthandProjection;
+import org.apache.shardingsphere.infra.exception.ShardingSpherePreconditions;
import
org.apache.shardingsphere.sql.parser.statement.core.segment.dml.column.ColumnSegment;
import
org.apache.shardingsphere.sql.parser.statement.core.segment.dml.item.ProjectionSegment;
import
org.apache.shardingsphere.sql.parser.statement.core.segment.dml.item.ProjectionsSegment;
@@ -99,26 +100,27 @@ public final class ProjectionsContextEngine {
if (orderByItem instanceof IndexOrderByItemSegment) {
return true;
}
+ String normalizedText =
SQLUtils.getExactlyValue(((TextOrderByItemSegment) orderByItem).getText());
+ ShardingSpherePreconditions.checkNotNull(normalizedText, () -> new
IllegalStateException("Order item text must not be null."));
for (Projection each : projections) {
- if (orderByItem instanceof ColumnOrderByItemSegment &&
isSameColumn(each, ((ColumnOrderByItemSegment) orderByItem).getColumn())) {
+ if (orderByItem instanceof ColumnOrderByItemSegment &&
isSameColumn(each, ((ColumnOrderByItemSegment) orderByItem).getColumn(),
normalizedText)) {
return true;
}
- String text = ((TextOrderByItemSegment) orderByItem).getText();
- if (isSameAlias(each, text) || isSameQualifiedName(each, text)) {
+ if (isSameAlias(each, normalizedText) || isSameQualifiedName(each,
normalizedText)) {
return true;
}
}
return false;
}
- private boolean isSameColumn(final Projection projection, final
ColumnSegment columnSegment) {
+ private boolean isSameColumn(final Projection projection, final
ColumnSegment columnSegment, final String normalizedText) {
Collection<ColumnProjection> columns =
getColumnProjections(projection);
if (columns.isEmpty()) {
return false;
}
boolean columnSegmentPresent = columnSegment.getOwner().isPresent();
for (ColumnProjection each : columns) {
- if (columnSegmentPresent ? isSameQualifiedName(each,
columnSegment.getQualifiedName()) : isSameName(each,
columnSegment.getQualifiedName())) {
+ if (columnSegmentPresent ? isSameQualifiedName(each,
normalizedText) : isSameName(each, normalizedText)) {
return true;
}
}
@@ -136,15 +138,15 @@ public final class ProjectionsContextEngine {
return result;
}
- private boolean isSameName(final ColumnProjection projection, final String
text) {
- return
SQLUtils.getExactlyValue(text).equalsIgnoreCase(projection.getName().getValue());
+ private boolean isSameName(final ColumnProjection projection, final String
normalizedText) {
+ return
normalizedText.equalsIgnoreCase(projection.getName().getValue());
}
- private boolean isSameAlias(final Projection projection, final String
text) {
- return projection.getAlias().isPresent() &&
SQLUtils.getExactlyValue(text).equalsIgnoreCase(SQLUtils.getExactlyValue(projection.getAlias().get().getValue()));
+ private boolean isSameAlias(final Projection projection, final String
normalizedText) {
+ return projection.getAlias().isPresent() &&
normalizedText.equalsIgnoreCase(SQLUtils.getExactlyValue(projection.getAlias().get().getValue()));
}
- private boolean isSameQualifiedName(final Projection projection, final
String text) {
- return
SQLUtils.getExactlyValue(text).equalsIgnoreCase(SQLUtils.getExactlyValue(projection.getExpression()));
+ private boolean isSameQualifiedName(final Projection projection, final
String normalizedText) {
+ return
normalizedText.equalsIgnoreCase(SQLUtils.getExactlyValue(projection.getExpression()));
}
}
diff --git
a/infra/binder/core/src/test/java/org/apache/shardingsphere/infra/binder/context/segment/select/projection/engine/ProjectionsContextEngineTest.java
b/infra/binder/core/src/test/java/org/apache/shardingsphere/infra/binder/context/segment/select/projection/engine/ProjectionsContextEngineTest.java
index 9f79ffd4789..dffafabc4df 100644
---
a/infra/binder/core/src/test/java/org/apache/shardingsphere/infra/binder/context/segment/select/projection/engine/ProjectionsContextEngineTest.java
+++
b/infra/binder/core/src/test/java/org/apache/shardingsphere/infra/binder/context/segment/select/projection/engine/ProjectionsContextEngineTest.java
@@ -27,11 +27,13 @@ import
org.apache.shardingsphere.infra.spi.type.typed.TypedSPILoader;
import
org.apache.shardingsphere.sql.parser.statement.core.enums.OrderDirection;
import
org.apache.shardingsphere.sql.parser.statement.core.segment.dml.column.ColumnSegment;
import
org.apache.shardingsphere.sql.parser.statement.core.segment.dml.item.ColumnProjectionSegment;
+import
org.apache.shardingsphere.sql.parser.statement.core.segment.dml.item.ExpressionProjectionSegment;
import
org.apache.shardingsphere.sql.parser.statement.core.segment.dml.item.ProjectionsSegment;
import
org.apache.shardingsphere.sql.parser.statement.core.segment.dml.item.ShorthandProjectionSegment;
import
org.apache.shardingsphere.sql.parser.statement.core.segment.dml.order.item.ColumnOrderByItemSegment;
import
org.apache.shardingsphere.sql.parser.statement.core.segment.dml.order.item.ExpressionOrderByItemSegment;
import
org.apache.shardingsphere.sql.parser.statement.core.segment.dml.order.item.IndexOrderByItemSegment;
+import
org.apache.shardingsphere.sql.parser.statement.core.segment.generic.AliasSegment;
import
org.apache.shardingsphere.sql.parser.statement.core.segment.generic.OwnerSegment;
import
org.apache.shardingsphere.sql.parser.statement.core.segment.generic.bound.TableSegmentBoundInfo;
import
org.apache.shardingsphere.sql.parser.statement.core.value.identifier.IdentifierValue;
@@ -43,6 +45,7 @@ import java.util.Collections;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
class ProjectionsContextEngineTest {
@@ -174,4 +177,85 @@ class ProjectionsContextEngineTest {
ProjectionsContext actual =
engine.createProjectionsContext(projectionsSegment, new
GroupByContext(Collections.emptyList()), orderByContext);
assertThat(actual.getProjections().size(), is(2));
}
+
+ @Test
+ void assertCreateProjectionsContextWithNullOrderByText() {
+ ProjectionsSegment projectionsSegment = new ProjectionsSegment(0, 0);
+ projectionsSegment.getProjections().add(new
ShorthandProjectionSegment(0, 0));
+ OrderByItem orderByItem = new OrderByItem(new
ExpressionOrderByItemSegment(0, 0, null, OrderDirection.ASC,
NullsOrderType.FIRST));
+ ProjectionsContextEngine engine = new
ProjectionsContextEngine(databaseType);
+ IllegalStateException actual =
assertThrows(IllegalStateException.class, () ->
engine.createProjectionsContext(projectionsSegment, new
GroupByContext(Collections.emptyList()),
+ new OrderByContext(Collections.singleton(orderByItem),
false)));
+ assertThat(actual.getMessage(), is("Order item text must not be
null."));
+ }
+
+ @Test
+ void assertCreateProjectionsContextWithNullGroupByText() {
+ ProjectionsSegment projectionsSegment = new ProjectionsSegment(0, 0);
+ projectionsSegment.getProjections().add(new
ShorthandProjectionSegment(0, 0));
+ OrderByItem groupByItem = new OrderByItem(new
ExpressionOrderByItemSegment(0, 0, null, OrderDirection.ASC,
NullsOrderType.FIRST));
+ ProjectionsContextEngine engine = new
ProjectionsContextEngine(databaseType);
+ IllegalStateException actual =
assertThrows(IllegalStateException.class, () ->
engine.createProjectionsContext(projectionsSegment, new
GroupByContext(Collections.singleton(groupByItem)),
+ new OrderByContext(Collections.emptyList(), false)));
+ assertThat(actual.getMessage(), is("Order item text must not be
null."));
+ }
+
+ @Test
+ void
assertCreateProjectionsContextWithNullOrderByTextAndEmptyProjections() {
+ ProjectionsSegment projectionsSegment = new ProjectionsSegment(0, 0);
+ OrderByItem orderByItem = new OrderByItem(new
ExpressionOrderByItemSegment(0, 0, null, OrderDirection.ASC,
NullsOrderType.FIRST));
+ ProjectionsContextEngine engine = new
ProjectionsContextEngine(databaseType);
+ IllegalStateException actual =
assertThrows(IllegalStateException.class, () ->
engine.createProjectionsContext(projectionsSegment, new
GroupByContext(Collections.emptyList()),
+ new OrderByContext(Collections.singleton(orderByItem),
false)));
+ assertThat(actual.getMessage(), is("Order item text must not be
null."));
+ }
+
+ @Test
+ void assertCreateProjectionsContextWithMatchingColumnName() {
+ ProjectionsSegment projectionsSegment = new ProjectionsSegment(0, 0);
+ ColumnSegment columnSegment = new ColumnSegment(0, 0, new
IdentifierValue("foo_column"));
+ columnSegment.setOwner(new OwnerSegment(0, 0, new
IdentifierValue("foo_table")));
+ projectionsSegment.getProjections().add(new
ColumnProjectionSegment(columnSegment));
+ ColumnSegment orderColumn = new ColumnSegment(0, 0, new
IdentifierValue("`FOO_COLUMN`"));
+ OrderByItem orderByItem = new OrderByItem(new
ColumnOrderByItemSegment(orderColumn, OrderDirection.ASC,
NullsOrderType.FIRST));
+ ProjectionsContextEngine engine = new
ProjectionsContextEngine(databaseType);
+ ProjectionsContext actual =
engine.createProjectionsContext(projectionsSegment, new
GroupByContext(Collections.emptyList()), new
OrderByContext(Collections.singleton(orderByItem), false));
+ assertThat(actual.getProjections().size(), is(1));
+ }
+
+ @Test
+ void assertCreateProjectionsContextWithMatchingQualifiedColumnName() {
+ ProjectionsSegment projectionsSegment = new ProjectionsSegment(0, 0);
+ ColumnSegment columnSegment = new ColumnSegment(0, 0, new
IdentifierValue("foo_column"));
+ columnSegment.setOwner(new OwnerSegment(0, 0, new
IdentifierValue("foo_table")));
+ projectionsSegment.getProjections().add(new
ColumnProjectionSegment(columnSegment));
+ ColumnSegment orderColumn = new ColumnSegment(0, 0, new
IdentifierValue("\"FOO_COLUMN\""));
+ orderColumn.setOwner(new OwnerSegment(0, 0, new
IdentifierValue("\"FOO_TABLE\"")));
+ OrderByItem orderByItem = new OrderByItem(new
ColumnOrderByItemSegment(orderColumn, OrderDirection.ASC,
NullsOrderType.FIRST));
+ ProjectionsContextEngine engine = new
ProjectionsContextEngine(databaseType);
+ ProjectionsContext actual =
engine.createProjectionsContext(projectionsSegment, new
GroupByContext(Collections.emptyList()), new
OrderByContext(Collections.singleton(orderByItem), false));
+ assertThat(actual.getProjections().size(), is(1));
+ }
+
+ @Test
+ void assertCreateProjectionsContextWithMatchingAlias() {
+ ProjectionsSegment projectionsSegment = new ProjectionsSegment(0, 0);
+ ExpressionProjectionSegment projectionSegment = new
ExpressionProjectionSegment(0, 0, "foo_column + 1");
+ projectionSegment.setAlias(new AliasSegment(0, 0, new
IdentifierValue("`foo_alias`")));
+ projectionsSegment.getProjections().add(projectionSegment);
+ OrderByItem orderByItem = new OrderByItem(new
ExpressionOrderByItemSegment(0, 0, "`FOO_ALIAS`", OrderDirection.ASC,
NullsOrderType.FIRST));
+ ProjectionsContextEngine engine = new
ProjectionsContextEngine(databaseType);
+ ProjectionsContext actual =
engine.createProjectionsContext(projectionsSegment, new
GroupByContext(Collections.emptyList()), new
OrderByContext(Collections.singleton(orderByItem), false));
+ assertThat(actual.getProjections().size(), is(1));
+ }
+
+ @Test
+ void assertCreateProjectionsContextWithMatchingExpression() {
+ ProjectionsSegment projectionsSegment = new ProjectionsSegment(0, 0);
+ projectionsSegment.getProjections().add(new
ExpressionProjectionSegment(0, 0, "`foo_column` + 1"));
+ OrderByItem orderByItem = new OrderByItem(new
ExpressionOrderByItemSegment(0, 0, "`FOO_COLUMN` + 1", OrderDirection.ASC,
NullsOrderType.FIRST));
+ ProjectionsContextEngine engine = new
ProjectionsContextEngine(databaseType);
+ ProjectionsContext actual =
engine.createProjectionsContext(projectionsSegment, new
GroupByContext(Collections.emptyList()), new
OrderByContext(Collections.singleton(orderByItem), false));
+ assertThat(actual.getProjections().size(), is(1));
+ }
}
diff --git
a/parser/sql/statement/core/src/main/java/org/apache/shardingsphere/sql/parser/statement/core/util/SQLUtils.java
b/parser/sql/statement/core/src/main/java/org/apache/shardingsphere/sql/parser/statement/core/util/SQLUtils.java
index cb54c623138..c6805bf12c5 100644
---
a/parser/sql/statement/core/src/main/java/org/apache/shardingsphere/sql/parser/statement/core/util/SQLUtils.java
+++
b/parser/sql/statement/core/src/main/java/org/apache/shardingsphere/sql/parser/statement/core/util/SQLUtils.java
@@ -38,6 +38,7 @@ import
org.apache.shardingsphere.sql.parser.statement.core.value.literal.impl.Ot
import
org.apache.shardingsphere.sql.parser.statement.core.value.literal.impl.StringLiteralValue;
import
org.apache.shardingsphere.sql.parser.statement.core.value.literal.impl.TemporalLiteralValue;
+import javax.annotation.Nullable;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.Collections;
@@ -104,10 +105,11 @@ public final class SQLUtils {
*
* <p>remove special char for SQL expression</p>
*
- * @param value SQL expression
- * @return exactly SQL expression
+ * @param value SQL expression, or {@code null}
+ * @return exactly SQL expression, or {@code null} if {@code value} is
{@code null}
*/
- public static String getExactlyValue(final String value) {
+ @Nullable
+ public static String getExactlyValue(@Nullable final String value) {
return null == value ? null :
tryGetRealContentInBackticks(CharMatcher.anyOf(EXCLUDED_CHARACTERS).removeFrom(value));
}
@@ -116,11 +118,12 @@ public final class SQLUtils {
*
* <p>remove special char for SQL expression</p>
*
- * @param value SQL expression
+ * @param value SQL expression, or {@code null}
* @param reservedCharacters characters to be reserved
- * @return exactly SQL expression
+ * @return exactly SQL expression, or {@code null} if {@code value} is
{@code null}
*/
- public static String getExactlyValue(final String value, final String
reservedCharacters) {
+ @Nullable
+ public static String getExactlyValue(@Nullable final String value, final
String reservedCharacters) {
if (null == value) {
return null;
}
diff --git
a/parser/sql/statement/core/src/test/java/org/apache/shardingsphere/sql/parser/statement/core/util/SQLUtilsTest.java
b/parser/sql/statement/core/src/test/java/org/apache/shardingsphere/sql/parser/statement/core/util/SQLUtilsTest.java
index 70cb777848b..96962c212b3 100644
---
a/parser/sql/statement/core/src/test/java/org/apache/shardingsphere/sql/parser/statement/core/util/SQLUtilsTest.java
+++
b/parser/sql/statement/core/src/test/java/org/apache/shardingsphere/sql/parser/statement/core/util/SQLUtilsTest.java
@@ -83,6 +83,11 @@ class SQLUtilsTest {
assertNull(SQLUtils.getExactlyValue(null));
}
+ @Test
+ void assertGetExactlyValueWithReservedCharactersUsingNull() {
+ assertNull(SQLUtils.getExactlyValue(null, "`"));
+ }
+
@Test
void assertGetExactlyExpressionUsingAndReturningNull() {
assertNull(SQLUtils.getExactlyExpression(null));