This is an automated email from the ASF dual-hosted git repository. duanzhengqiang 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 f7a126a6cf6 fix: SQL COUNT with GROUP BY to prevent incorrect row returns (#33380) f7a126a6cf6 is described below commit f7a126a6cf65bd4489a1f573c28dd83b337bfdd3 Author: Malay Dewangan <66718045+malaydewanga...@users.noreply.github.com> AuthorDate: Thu Oct 31 07:11:08 2024 +0530 fix: SQL COUNT with GROUP BY to prevent incorrect row returns (#33380) * fix: SQL COUNT with GROUP BY to prevent incorrect row returns * test: Add test cases for empty result with GROUP BY and ORDER BY * fix: update db types and scenario type for e2e test case * fix: update column names for e2e test * fix: fix unit tests for empty result set * test: add e2e tests for issue #4680 * fix: fix e2e tests for issue #4680 * update e2e tests for isssue #4680 * fix: fix failing checks * fix: update conditions for group by and aggregate functions --- .../dql/groupby/GroupByMemoryMergedResult.java | 13 ++++- .../dql/groupby/GroupByMemoryMergedResultTest.java | 59 ++++++++++++++++++++-- .../cases/dql/e2e-dql-select-group-by.xml | 7 ++- 3 files changed, 71 insertions(+), 8 deletions(-) diff --git a/features/sharding/core/src/main/java/org/apache/shardingsphere/sharding/merge/dql/groupby/GroupByMemoryMergedResult.java b/features/sharding/core/src/main/java/org/apache/shardingsphere/sharding/merge/dql/groupby/GroupByMemoryMergedResult.java index 226f48fab8f..e1cf5eccca4 100644 --- a/features/sharding/core/src/main/java/org/apache/shardingsphere/sharding/merge/dql/groupby/GroupByMemoryMergedResult.java +++ b/features/sharding/core/src/main/java/org/apache/shardingsphere/sharding/merge/dql/groupby/GroupByMemoryMergedResult.java @@ -18,6 +18,7 @@ package org.apache.shardingsphere.sharding.merge.dql.groupby; import org.apache.shardingsphere.infra.binder.context.segment.select.projection.Projection; + import org.apache.shardingsphere.infra.binder.context.segment.select.projection.impl.AggregationDistinctProjection; import org.apache.shardingsphere.infra.binder.context.segment.select.projection.impl.AggregationProjection; import org.apache.shardingsphere.infra.binder.context.statement.SQLStatementContext; @@ -140,10 +141,18 @@ public final class GroupByMemoryMergedResult extends MemoryMergedResult<Sharding private List<MemoryQueryResultRow> getMemoryResultSetRows(final SelectStatementContext selectStatementContext, final Map<GroupByValue, MemoryQueryResultRow> dataMap, final List<Boolean> valueCaseSensitive) { + Object[] data = generateReturnData(selectStatementContext); + if (dataMap.isEmpty()) { - Object[] data = generateReturnData(selectStatementContext); - return selectStatementContext.getProjectionsContext().getAggregationProjections().isEmpty() ? Collections.emptyList() : Collections.singletonList(new MemoryQueryResultRow(data)); + boolean hasGroupBy = !selectStatementContext.getGroupByContext().getItems().isEmpty(); + boolean hasAggregations = !selectStatementContext.getProjectionsContext().getAggregationProjections().isEmpty(); + + if (hasGroupBy || !hasAggregations) { + return Collections.emptyList(); + } + return Collections.singletonList(new MemoryQueryResultRow(data)); } + List<MemoryQueryResultRow> result = new ArrayList<>(dataMap.values()); result.sort(new GroupByRowComparator(selectStatementContext, valueCaseSensitive)); return result; diff --git a/features/sharding/core/src/test/java/org/apache/shardingsphere/sharding/merge/dql/groupby/GroupByMemoryMergedResultTest.java b/features/sharding/core/src/test/java/org/apache/shardingsphere/sharding/merge/dql/groupby/GroupByMemoryMergedResultTest.java index 0692069ad9a..06dccc263e4 100644 --- a/features/sharding/core/src/test/java/org/apache/shardingsphere/sharding/merge/dql/groupby/GroupByMemoryMergedResultTest.java +++ b/features/sharding/core/src/test/java/org/apache/shardingsphere/sharding/merge/dql/groupby/GroupByMemoryMergedResultTest.java @@ -20,6 +20,7 @@ package org.apache.shardingsphere.sharding.merge.dql.groupby; import org.apache.shardingsphere.infra.binder.context.statement.dml.SelectStatementContext; import org.apache.shardingsphere.infra.config.props.ConfigurationProperties; import org.apache.shardingsphere.infra.database.core.DefaultDatabase; +import org.apache.shardingsphere.infra.database.core.metadata.database.enums.NullsOrderType; import org.apache.shardingsphere.infra.database.core.type.DatabaseType; import org.apache.shardingsphere.infra.executor.sql.execute.result.query.QueryResult; import org.apache.shardingsphere.infra.merge.result.MergedResult; @@ -33,7 +34,6 @@ import org.apache.shardingsphere.infra.session.connection.ConnectionContext; import org.apache.shardingsphere.infra.spi.type.typed.TypedSPILoader; import org.apache.shardingsphere.sharding.merge.dql.ShardingDQLResultMerger; import org.apache.shardingsphere.sql.parser.statement.core.enums.AggregationType; -import org.apache.shardingsphere.infra.database.core.metadata.database.enums.NullsOrderType; import org.apache.shardingsphere.sql.parser.statement.core.enums.OrderDirection; import org.apache.shardingsphere.sql.parser.statement.core.segment.dml.item.AggregationProjectionSegment; import org.apache.shardingsphere.sql.parser.statement.core.segment.dml.item.ProjectionsSegment; @@ -62,7 +62,6 @@ import java.util.Collections; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.Mockito.RETURNS_DEEP_STUBS; import static org.mockito.Mockito.mock; @@ -80,9 +79,6 @@ class GroupByMemoryMergedResultTest { when(database.getName()).thenReturn("db_schema"); ShardingDQLResultMerger resultMerger = new ShardingDQLResultMerger(TypedSPILoader.getService(DatabaseType.class, "MySQL")); MergedResult actual = resultMerger.merge(Arrays.asList(createQueryResult(), createQueryResult(), createQueryResult()), createSelectStatementContext(), database, mock(ConnectionContext.class)); - assertTrue(actual.next()); - assertThat(actual.getValue(1, Object.class), is(0)); - assertNull(actual.getValue(2, Object.class)); assertFalse(actual.next()); } @@ -217,4 +213,57 @@ class GroupByMemoryMergedResultTest { MergedResult actual = merger.merge(Arrays.asList(queryResult, queryResult, queryResult), createSelectStatementContext(database), database, mock(ConnectionContext.class)); assertFalse(actual.next()); } + + @Test + void assertNextForEmptyResultWithCountAndGroupBy() throws SQLException { + when(database.getName()).thenReturn("db_schema"); + QueryResult queryResult1 = createEmptyQueryResultWithCountGroupBy(); + QueryResult queryResult2 = createEmptyQueryResultWithCountGroupBy(); + ShardingDQLResultMerger resultMerger = new ShardingDQLResultMerger(TypedSPILoader.getService(DatabaseType.class, "MySQL")); + MergedResult actual = resultMerger.merge(Arrays.asList(queryResult1, queryResult2), createSelectStatementContextForCountGroupBy(), database, mock(ConnectionContext.class)); + assertFalse(actual.next()); + } + + @Test + void assertNextForEmptyResultWithCountGroupByDifferentOrderBy() throws SQLException { + when(database.getName()).thenReturn("db_schema"); + QueryResult queryResult = createEmptyQueryResultWithCountGroupBy(); + ShardingDQLResultMerger resultMerger = new ShardingDQLResultMerger(TypedSPILoader.getService(DatabaseType.class, "MySQL")); + MergedResult actual = resultMerger.merge(Collections.singletonList(queryResult), createSelectStatementContextForCountGroupByDifferentOrderBy(), database, mock(ConnectionContext.class)); + assertFalse(actual.next()); + } + + private QueryResult createEmptyQueryResultWithCountGroupBy() throws SQLException { + QueryResult result = mock(QueryResult.class, RETURNS_DEEP_STUBS); + when(result.getMetaData().getColumnCount()).thenReturn(3); + when(result.getMetaData().getColumnLabel(1)).thenReturn("COUNT(*)"); + when(result.getMetaData().getColumnLabel(2)).thenReturn("user_id"); + when(result.getMetaData().getColumnLabel(3)).thenReturn("order_id"); + when(result.next()).thenReturn(false); + return result; + } + + private SelectStatementContext createSelectStatementContextForCountGroupBy() { + SelectStatement selectStatement = new MySQLSelectStatement(); + ProjectionsSegment projectionsSegment = new ProjectionsSegment(0, 0); + projectionsSegment.getProjections().add(new AggregationProjectionSegment(0, 0, AggregationType.COUNT, "COUNT(*)")); + selectStatement.setGroupBy(new GroupBySegment(0, 0, Collections.singletonList(new IndexOrderByItemSegment(0, 0, 2, OrderDirection.ASC, NullsOrderType.FIRST)))); + selectStatement.setOrderBy(new OrderBySegment(0, 0, Collections.singletonList(new IndexOrderByItemSegment(0, 0, 2, OrderDirection.ASC, NullsOrderType.FIRST)))); + selectStatement.setProjections(projectionsSegment); + ShardingSphereDatabase database = mock(ShardingSphereDatabase.class, RETURNS_DEEP_STUBS); + when(database.getSchema(DefaultDatabase.LOGIC_NAME)).thenReturn(mock(ShardingSphereSchema.class)); + return new SelectStatementContext(createShardingSphereMetaData(database), Collections.emptyList(), selectStatement, DefaultDatabase.LOGIC_NAME, Collections.emptyList()); + } + + private SelectStatementContext createSelectStatementContextForCountGroupByDifferentOrderBy() { + SelectStatement selectStatement = new MySQLSelectStatement(); + ProjectionsSegment projectionsSegment = new ProjectionsSegment(0, 0); + projectionsSegment.getProjections().add(new AggregationProjectionSegment(0, 0, AggregationType.COUNT, "COUNT(*)")); + selectStatement.setGroupBy(new GroupBySegment(0, 0, Collections.singletonList(new IndexOrderByItemSegment(0, 0, 2, OrderDirection.ASC, NullsOrderType.FIRST)))); + selectStatement.setOrderBy(new OrderBySegment(0, 0, Collections.singletonList(new IndexOrderByItemSegment(0, 0, 3, OrderDirection.ASC, NullsOrderType.FIRST)))); + selectStatement.setProjections(projectionsSegment); + ShardingSphereDatabase database = mock(ShardingSphereDatabase.class, RETURNS_DEEP_STUBS); + when(database.getSchema(DefaultDatabase.LOGIC_NAME)).thenReturn(mock(ShardingSphereSchema.class)); + return new SelectStatementContext(createShardingSphereMetaData(database), Collections.emptyList(), selectStatement, DefaultDatabase.LOGIC_NAME, Collections.emptyList()); + } } diff --git a/test/e2e/sql/src/test/resources/cases/dql/e2e-dql-select-group-by.xml b/test/e2e/sql/src/test/resources/cases/dql/e2e-dql-select-group-by.xml index 0a12d650c70..73eb9839572 100644 --- a/test/e2e/sql/src/test/resources/cases/dql/e2e-dql-select-group-by.xml +++ b/test/e2e/sql/src/test/resources/cases/dql/e2e-dql-select-group-by.xml @@ -53,6 +53,11 @@ <assertion expected-data-source-name="read_dataset" /> </test-case> + <test-case sql="SELECT COUNT(1) FROM t_order WHERE 1 = 2 GROUP BY order_id,user_id ORDER BY user_id" db-types="MySQL,PostgreSQL,openGauss" scenario-types="db,tbl" + scenario-comments="Test GROUP BY with ORDER BY different fields returns no rows when WHERE condition matches no data"> + <assertion expected-data-source-name="read_dataset" /> + </test-case> + <test-case sql="SELECT AVG(DISTINCT order_id) AS avg_id FROM t_order WHERE 1 = 2" db-types="MySQL,PostgreSQL,openGauss" scenario-types="db,tbl" scenario-comments="Test AVG DISTINCT returns NULL when no data matches"> <assertion expected-data-source-name="read_dataset" /> @@ -72,7 +77,7 @@ scenario-comments="Test MIN DISTINCT returns NULL when no data matches"> <assertion expected-data-source-name="read_dataset" /> </test-case> - + <test-case sql="SELECT MAX(DISTINCT order_id) AS max_id FROM t_order WHERE 1 = 2" db-types="MySQL,PostgreSQL,openGauss" scenario-types="db,tbl" scenario-comments="Test MAX DISTINCT returns NULL when no data matches"> <assertion expected-data-source-name="read_dataset" />