diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index ee11ef4c411c1..b880b94df863e 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -28,6 +28,7 @@ 1. Proxy Native: Change the Base Docker Image of ShardingSphere Proxy Native - [#33263](https://github.com/apache/shardingsphere/issues/33263) 1. Proxy Native: Support connecting to HiveServer2 with ZooKeeper Service Discovery enabled in GraalVM Native Image - [#33768](https://github.com/apache/shardingsphere/pull/33768) 1. Proxy Native: Support local transactions of ClickHouse under GraalVM Native Image - [#33801](https://github.com/apache/shardingsphere/pull/33801) +1. Sharding: Support MYSQL GroupConcat function for aggregating multiple shards - [#33808](https://github.com/apache/shardingsphere/pull/33808) 1. Proxy Native: Support Seata AT integration under Proxy Native in GraalVM Native Image - [#33889](https://github.com/apache/shardingsphere/pull/33889) 1. Agent: Simplify the use of Agent's Docker Image - [#33356](https://github.com/apache/shardingsphere/pull/33356) 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 56505af0f4454..a0a2871e0695f 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 @@ -81,7 +81,8 @@ private void initForFirstGroupByValue(final SelectStatementContext selectStateme dataMap.put(groupByValue, new MemoryQueryResultRow(queryResult)); } aggregationMap.computeIfAbsent(groupByValue, unused -> selectStatementContext.getProjectionsContext().getAggregationProjections().stream() - .collect(Collectors.toMap(Function.identity(), input -> AggregationUnitFactory.create(input.getType(), input instanceof AggregationDistinctProjection)))); + .collect(Collectors.toMap(Function.identity(), + input -> AggregationUnitFactory.create(input.getType(), input instanceof AggregationDistinctProjection, input.getSeparator().orElse(null))))); } private void aggregate(final SelectStatementContext selectStatementContext, final QueryResult queryResult, diff --git a/features/sharding/core/src/main/java/org/apache/shardingsphere/sharding/merge/dql/groupby/GroupByStreamMergedResult.java b/features/sharding/core/src/main/java/org/apache/shardingsphere/sharding/merge/dql/groupby/GroupByStreamMergedResult.java index 550c982af4ba1..a26782b3be72c 100644 --- a/features/sharding/core/src/main/java/org/apache/shardingsphere/sharding/merge/dql/groupby/GroupByStreamMergedResult.java +++ b/features/sharding/core/src/main/java/org/apache/shardingsphere/sharding/merge/dql/groupby/GroupByStreamMergedResult.java @@ -77,7 +77,8 @@ private boolean aggregateCurrentGroupByRowAndNext() throws SQLException { boolean result = false; boolean cachedRow = false; Map aggregationUnitMap = Maps.toMap( - selectStatementContext.getProjectionsContext().getAggregationProjections(), input -> AggregationUnitFactory.create(input.getType(), input instanceof AggregationDistinctProjection)); + selectStatementContext.getProjectionsContext().getAggregationProjections(), + input -> AggregationUnitFactory.create(input.getType(), input instanceof AggregationDistinctProjection, input.getSeparator().orElse(null))); while (currentGroupByValues.equals(new GroupByValue(getCurrentQueryResult(), selectStatementContext.getGroupByContext().getItems()).getGroupValues())) { aggregate(aggregationUnitMap); if (!cachedRow) { diff --git a/features/sharding/core/src/main/java/org/apache/shardingsphere/sharding/merge/dql/groupby/aggregation/AggregationUnitFactory.java b/features/sharding/core/src/main/java/org/apache/shardingsphere/sharding/merge/dql/groupby/aggregation/AggregationUnitFactory.java index c6709cbb4ed57..02caeecf6c103 100644 --- a/features/sharding/core/src/main/java/org/apache/shardingsphere/sharding/merge/dql/groupby/aggregation/AggregationUnitFactory.java +++ b/features/sharding/core/src/main/java/org/apache/shardingsphere/sharding/merge/dql/groupby/aggregation/AggregationUnitFactory.java @@ -33,10 +33,11 @@ public final class AggregationUnitFactory { * * @param type aggregation function type * @param isDistinct is distinct + * @param separator is separator for group_concat * @return aggregation unit instance * @throws UnsupportedSQLOperationException unsupported SQL operation exception */ - public static AggregationUnit create(final AggregationType type, final boolean isDistinct) { + public static AggregationUnit create(final AggregationType type, final boolean isDistinct, final String separator) { switch (type) { case MAX: return new ComparableAggregationUnit(false); @@ -50,6 +51,8 @@ public static AggregationUnit create(final AggregationType type, final boolean i return isDistinct ? new DistinctAverageAggregationUnit() : new AverageAggregationUnit(); case BIT_XOR: return new BitXorAggregationUnit(); + case GROUP_CONCAT: + return isDistinct ? new DistinctGroupConcatAggregationUnit(separator) : new GroupConcatAggregationUnit(separator); default: throw new UnsupportedSQLOperationException(type.name()); } diff --git a/features/sharding/core/src/main/java/org/apache/shardingsphere/sharding/merge/dql/groupby/aggregation/DistinctGroupConcatAggregationUnit.java b/features/sharding/core/src/main/java/org/apache/shardingsphere/sharding/merge/dql/groupby/aggregation/DistinctGroupConcatAggregationUnit.java new file mode 100644 index 0000000000000..93a19c21d6999 --- /dev/null +++ b/features/sharding/core/src/main/java/org/apache/shardingsphere/sharding/merge/dql/groupby/aggregation/DistinctGroupConcatAggregationUnit.java @@ -0,0 +1,51 @@ +/* + * 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.sharding.merge.dql.groupby.aggregation; + +import java.util.Collection; +import java.util.LinkedHashSet; +import java.util.List; + +/** + * Distinct group concat aggregation unit. + */ +public final class DistinctGroupConcatAggregationUnit implements AggregationUnit { + + private static final String DEFAULT_SEPARATOR = ","; + + private final Collection values = new LinkedHashSet<>(); + + private final String separator; + + public DistinctGroupConcatAggregationUnit(final String separator) { + this.separator = null == separator ? DEFAULT_SEPARATOR : separator; + } + + @Override + public void merge(final List> values) { + if (null == values || null == values.get(0)) { + return; + } + this.values.add(String.valueOf(values.get(0))); + } + + @Override + public Comparable getResult() { + return String.join(separator, values); + } +} diff --git a/features/sharding/core/src/main/java/org/apache/shardingsphere/sharding/merge/dql/groupby/aggregation/GroupConcatAggregationUnit.java b/features/sharding/core/src/main/java/org/apache/shardingsphere/sharding/merge/dql/groupby/aggregation/GroupConcatAggregationUnit.java new file mode 100644 index 0000000000000..c3e1796106d15 --- /dev/null +++ b/features/sharding/core/src/main/java/org/apache/shardingsphere/sharding/merge/dql/groupby/aggregation/GroupConcatAggregationUnit.java @@ -0,0 +1,51 @@ +/* + * 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.sharding.merge.dql.groupby.aggregation; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +/** + * Group concat aggregation unit. + */ +public final class GroupConcatAggregationUnit implements AggregationUnit { + + private static final String DEFAULT_SEPARATOR = ","; + + private final Collection values = new ArrayList<>(); + + private final String separator; + + public GroupConcatAggregationUnit(final String separator) { + this.separator = null == separator ? DEFAULT_SEPARATOR : separator; + } + + @Override + public void merge(final List> values) { + if (null == values || null == values.get(0)) { + return; + } + this.values.add(String.valueOf(values.get(0))); + } + + @Override + public Comparable getResult() { + return String.join(separator, values); + } +} diff --git a/features/sharding/core/src/test/java/org/apache/shardingsphere/sharding/merge/dql/groupby/aggregation/AggregationUnitFactoryTest.java b/features/sharding/core/src/test/java/org/apache/shardingsphere/sharding/merge/dql/groupby/aggregation/AggregationUnitFactoryTest.java index 6e14d251409de..92bc71a5fa377 100644 --- a/features/sharding/core/src/test/java/org/apache/shardingsphere/sharding/merge/dql/groupby/aggregation/AggregationUnitFactoryTest.java +++ b/features/sharding/core/src/test/java/org/apache/shardingsphere/sharding/merge/dql/groupby/aggregation/AggregationUnitFactoryTest.java @@ -27,38 +27,50 @@ class AggregationUnitFactoryTest { @Test void assertCreateComparableAggregationUnit() { - assertThat(AggregationUnitFactory.create(AggregationType.MIN, false), instanceOf(ComparableAggregationUnit.class)); - assertThat(AggregationUnitFactory.create(AggregationType.MAX, false), instanceOf(ComparableAggregationUnit.class)); + assertThat(AggregationUnitFactory.create(AggregationType.MIN, false, null), instanceOf(ComparableAggregationUnit.class)); + assertThat(AggregationUnitFactory.create(AggregationType.MAX, false, null), instanceOf(ComparableAggregationUnit.class)); } @Test void assertCreateAccumulationAggregationUnit() { - assertThat(AggregationUnitFactory.create(AggregationType.SUM, false), instanceOf(AccumulationAggregationUnit.class)); - assertThat(AggregationUnitFactory.create(AggregationType.COUNT, false), instanceOf(AccumulationAggregationUnit.class)); + assertThat(AggregationUnitFactory.create(AggregationType.SUM, false, null), instanceOf(AccumulationAggregationUnit.class)); + assertThat(AggregationUnitFactory.create(AggregationType.COUNT, false, null), instanceOf(AccumulationAggregationUnit.class)); } @Test void assertCreateAverageAggregationUnit() { - assertThat(AggregationUnitFactory.create(AggregationType.AVG, false), instanceOf(AverageAggregationUnit.class)); + assertThat(AggregationUnitFactory.create(AggregationType.AVG, false, null), instanceOf(AverageAggregationUnit.class)); } @Test void assertCreateDistinctSumAggregationUnit() { - assertThat(AggregationUnitFactory.create(AggregationType.SUM, true), instanceOf(DistinctSumAggregationUnit.class)); + assertThat(AggregationUnitFactory.create(AggregationType.SUM, true, null), instanceOf(DistinctSumAggregationUnit.class)); } @Test void assertCreateDistinctCountAggregationUnit() { - assertThat(AggregationUnitFactory.create(AggregationType.COUNT, true), instanceOf(DistinctCountAggregationUnit.class)); + assertThat(AggregationUnitFactory.create(AggregationType.COUNT, true, null), instanceOf(DistinctCountAggregationUnit.class)); } @Test void assertCreateDistinctAverageAggregationUnit() { - assertThat(AggregationUnitFactory.create(AggregationType.AVG, true), instanceOf(DistinctAverageAggregationUnit.class)); + assertThat(AggregationUnitFactory.create(AggregationType.AVG, true, null), instanceOf(DistinctAverageAggregationUnit.class)); } @Test void assertCreateBitXorAggregationUnit() { - assertThat(AggregationUnitFactory.create(AggregationType.BIT_XOR, false), instanceOf(BitXorAggregationUnit.class)); + assertThat(AggregationUnitFactory.create(AggregationType.BIT_XOR, false, null), instanceOf(BitXorAggregationUnit.class)); + } + + @Test + void assertGroupConcatAggregationUnit() { + assertThat(AggregationUnitFactory.create(AggregationType.GROUP_CONCAT, false, null), instanceOf(GroupConcatAggregationUnit.class)); + assertThat(AggregationUnitFactory.create(AggregationType.GROUP_CONCAT, false, " "), instanceOf(GroupConcatAggregationUnit.class)); + } + + @Test + void assertDistinctGroupConcatAggregationUnit() { + assertThat(AggregationUnitFactory.create(AggregationType.GROUP_CONCAT, true, null), instanceOf(DistinctGroupConcatAggregationUnit.class)); + assertThat(AggregationUnitFactory.create(AggregationType.GROUP_CONCAT, true, " "), instanceOf(DistinctGroupConcatAggregationUnit.class)); } } diff --git a/features/sharding/core/src/test/java/org/apache/shardingsphere/sharding/merge/dql/groupby/aggregation/GroupConcatAggregationUnitTest.java b/features/sharding/core/src/test/java/org/apache/shardingsphere/sharding/merge/dql/groupby/aggregation/GroupConcatAggregationUnitTest.java new file mode 100644 index 0000000000000..24b6e08550011 --- /dev/null +++ b/features/sharding/core/src/test/java/org/apache/shardingsphere/sharding/merge/dql/groupby/aggregation/GroupConcatAggregationUnitTest.java @@ -0,0 +1,51 @@ +/* + * 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.sharding.merge.dql.groupby.aggregation; + +import org.junit.jupiter.api.Test; + +import java.util.Collections; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; + +class GroupConcatAggregationUnitTest { + + @Test + void assertGroupConcatAggregation() { + GroupConcatAggregationUnit groupConcatAggregationUnit = new GroupConcatAggregationUnit(" "); + groupConcatAggregationUnit.merge(null); + groupConcatAggregationUnit.merge(Collections.singletonList(null)); + groupConcatAggregationUnit.merge(Collections.singletonList("001")); + groupConcatAggregationUnit.merge(Collections.singletonList("002")); + groupConcatAggregationUnit.merge(Collections.singletonList("002 003")); + assertThat(groupConcatAggregationUnit.getResult(), is("001 002 002 003")); + } + + @Test + void assertDistinctGroupConcatAggregation() { + DistinctGroupConcatAggregationUnit distinctGroupConcatAggregationUnit = new DistinctGroupConcatAggregationUnit(" "); + distinctGroupConcatAggregationUnit.merge(null); + distinctGroupConcatAggregationUnit.merge(Collections.singletonList(null)); + distinctGroupConcatAggregationUnit.merge(Collections.singletonList("")); + distinctGroupConcatAggregationUnit.merge(Collections.singletonList("001")); + distinctGroupConcatAggregationUnit.merge(Collections.singletonList("001")); + distinctGroupConcatAggregationUnit.merge(Collections.singletonList("003")); + assertThat(distinctGroupConcatAggregationUnit.getResult(), is(" 001 003")); + } +} diff --git a/infra/binder/src/main/java/org/apache/shardingsphere/infra/binder/context/segment/select/projection/engine/ProjectionEngine.java b/infra/binder/src/main/java/org/apache/shardingsphere/infra/binder/context/segment/select/projection/engine/ProjectionEngine.java index 7edd06747079c..3fd824f99a485 100644 --- a/infra/binder/src/main/java/org/apache/shardingsphere/infra/binder/context/segment/select/projection/engine/ProjectionEngine.java +++ b/infra/binder/src/main/java/org/apache/shardingsphere/infra/binder/context/segment/select/projection/engine/ProjectionEngine.java @@ -121,7 +121,7 @@ private AggregationDistinctProjection createProjection(final AggregationDistinct projectionSegment.getAlias().orElseGet(() -> new IdentifierValue(DerivedColumn.AGGREGATION_DISTINCT_DERIVED.getDerivedColumnAlias(aggregationDistinctDerivedColumnCount++))); AggregationDistinctProjection result = new AggregationDistinctProjection( projectionSegment.getStartIndex(), projectionSegment.getStopIndex(), projectionSegment.getType(), projectionSegment.getExpression(), alias, - projectionSegment.getDistinctInnerExpression(), databaseType); + projectionSegment.getDistinctInnerExpression(), databaseType, projectionSegment.getSeparator().orElse(null)); if (AggregationType.AVG == result.getType()) { appendAverageDistinctDerivedProjection(result); } @@ -129,7 +129,9 @@ private AggregationDistinctProjection createProjection(final AggregationDistinct } private AggregationProjection createProjection(final AggregationProjectionSegment projectionSegment) { - AggregationProjection result = new AggregationProjection(projectionSegment.getType(), projectionSegment.getExpression(), projectionSegment.getAlias().orElse(null), databaseType); + AggregationProjection result = + new AggregationProjection(projectionSegment.getType(), projectionSegment.getExpression(), projectionSegment.getAlias().orElse(null), databaseType, + projectionSegment.getSeparator().orElse(null)); if (AggregationType.AVG == result.getType()) { appendAverageDerivedProjection(result); // TODO replace avg to constant, avoid calculate useless avg diff --git a/infra/binder/src/main/java/org/apache/shardingsphere/infra/binder/context/segment/select/projection/impl/AggregationDistinctProjection.java b/infra/binder/src/main/java/org/apache/shardingsphere/infra/binder/context/segment/select/projection/impl/AggregationDistinctProjection.java index 6bb6241b00b58..e047645fa1e0a 100644 --- a/infra/binder/src/main/java/org/apache/shardingsphere/infra/binder/context/segment/select/projection/impl/AggregationDistinctProjection.java +++ b/infra/binder/src/main/java/org/apache/shardingsphere/infra/binder/context/segment/select/projection/impl/AggregationDistinctProjection.java @@ -43,4 +43,12 @@ public AggregationDistinctProjection(final int startIndex, final int stopIndex, this.stopIndex = stopIndex; this.distinctInnerExpression = distinctInnerExpression; } + + public AggregationDistinctProjection(final int startIndex, final int stopIndex, final AggregationType type, final String expression, + final IdentifierValue alias, final String distinctInnerExpression, final DatabaseType databaseType, final String separator) { + super(type, expression, alias, databaseType, separator); + this.startIndex = startIndex; + this.stopIndex = stopIndex; + this.distinctInnerExpression = distinctInnerExpression; + } } diff --git a/infra/binder/src/main/java/org/apache/shardingsphere/infra/binder/context/segment/select/projection/impl/AggregationProjection.java b/infra/binder/src/main/java/org/apache/shardingsphere/infra/binder/context/segment/select/projection/impl/AggregationProjection.java index 3034779f82978..0493904fd8b4e 100644 --- a/infra/binder/src/main/java/org/apache/shardingsphere/infra/binder/context/segment/select/projection/impl/AggregationProjection.java +++ b/infra/binder/src/main/java/org/apache/shardingsphere/infra/binder/context/segment/select/projection/impl/AggregationProjection.java @@ -50,11 +50,21 @@ public class AggregationProjection implements Projection { private final DatabaseType databaseType; + private final String separator; + private final List derivedAggregationProjections = new ArrayList<>(2); @Setter private int index = -1; + public AggregationProjection(final AggregationType type, final String expression, final IdentifierValue alias, final DatabaseType databaseType) { + this.type = type; + this.expression = expression; + this.alias = alias; + this.databaseType = databaseType; + this.separator = null; + } + @Override public String getColumnName() { return getColumnLabel(); @@ -72,4 +82,8 @@ public String getColumnLabel() { public final Optional getAlias() { return Optional.ofNullable(alias); } + + public Optional getSeparator() { + return Optional.ofNullable(separator); + } } diff --git a/infra/binder/src/main/java/org/apache/shardingsphere/infra/binder/engine/segment/projection/ProjectionsSegmentBinder.java b/infra/binder/src/main/java/org/apache/shardingsphere/infra/binder/engine/segment/projection/ProjectionsSegmentBinder.java index a520cc4142fc0..83557beb1f937 100644 --- a/infra/binder/src/main/java/org/apache/shardingsphere/infra/binder/engine/segment/projection/ProjectionsSegmentBinder.java +++ b/infra/binder/src/main/java/org/apache/shardingsphere/infra/binder/engine/segment/projection/ProjectionsSegmentBinder.java @@ -106,7 +106,8 @@ private static AggregationDistinctProjectionSegment bindAggregationDistinctProje final Multimap tableBinderContexts, final Multimap outerTableBinderContexts) { AggregationDistinctProjectionSegment result = new AggregationDistinctProjectionSegment(aggregationDistinctSegment.getStartIndex(), aggregationDistinctSegment.getStopIndex(), - aggregationDistinctSegment.getType(), aggregationDistinctSegment.getExpression(), aggregationDistinctSegment.getDistinctInnerExpression()); + aggregationDistinctSegment.getType(), aggregationDistinctSegment.getExpression(), aggregationDistinctSegment.getDistinctInnerExpression(), + aggregationDistinctSegment.getSeparator().orElse(null)); aggregationDistinctSegment.getParameters() .forEach(each -> result.getParameters().add(ExpressionSegmentBinder.bind(each, SegmentType.PROJECTION, binderContext, tableBinderContexts, outerTableBinderContexts))); aggregationDistinctSegment.getAliasSegment().ifPresent(result::setAlias); @@ -117,7 +118,8 @@ private static AggregationProjectionSegment bindAggregationProjection(final Aggr final Multimap tableBinderContexts, final Multimap outerTableBinderContexts) { AggregationProjectionSegment result = - new AggregationProjectionSegment(aggregationSegment.getStartIndex(), aggregationSegment.getStopIndex(), aggregationSegment.getType(), aggregationSegment.getExpression()); + new AggregationProjectionSegment(aggregationSegment.getStartIndex(), aggregationSegment.getStopIndex(), aggregationSegment.getType(), aggregationSegment.getExpression(), + aggregationSegment.getSeparator().orElse(null)); aggregationSegment.getParameters() .forEach(each -> result.getParameters().add(ExpressionSegmentBinder.bind(each, SegmentType.PROJECTION, binderContext, tableBinderContexts, outerTableBinderContexts))); aggregationSegment.getAliasSegment().ifPresent(result::setAlias); diff --git a/kernel/sql-federation/optimizer/src/main/java/org/apache/shardingsphere/sqlfederation/optimizer/converter/segment/projection/impl/AggregationProjectionConverter.java b/kernel/sql-federation/optimizer/src/main/java/org/apache/shardingsphere/sqlfederation/optimizer/converter/segment/projection/impl/AggregationProjectionConverter.java index dd62b33e6b2c1..8cb1e65af3574 100644 --- a/kernel/sql-federation/optimizer/src/main/java/org/apache/shardingsphere/sqlfederation/optimizer/converter/segment/projection/impl/AggregationProjectionConverter.java +++ b/kernel/sql-federation/optimizer/src/main/java/org/apache/shardingsphere/sqlfederation/optimizer/converter/segment/projection/impl/AggregationProjectionConverter.java @@ -57,12 +57,17 @@ public final class AggregationProjectionConverter { register(SqlStdOperatorTable.COUNT); register(SqlStdOperatorTable.AVG); register(SqlStdOperatorTable.BIT_XOR); + register(SqlStdOperatorTable.LISTAGG, "GROUP_CONCAT"); } private static void register(final SqlAggFunction sqlAggFunction) { REGISTRY.put(sqlAggFunction.getName(), sqlAggFunction); } + private static void register(final SqlAggFunction sqlAggFunction, final String alias) { + REGISTRY.put(alias, sqlAggFunction); + } + /** * Convert aggregation projection segment to sql node. * @@ -75,7 +80,7 @@ public static Optional convert(final AggregationProjectionSegment segme } SqlLiteral functionQuantifier = segment instanceof AggregationDistinctProjectionSegment ? SqlLiteral.createSymbol(SqlSelectKeyword.DISTINCT, SqlParserPos.ZERO) : null; SqlAggFunction operator = convertOperator(segment.getType().name()); - List params = convertParameters(segment.getParameters(), segment.getExpression()); + List params = convertParameters(segment.getParameters(), segment.getExpression(), segment.getSeparator().orElse(null)); SqlBasicCall sqlBasicCall = new SqlBasicCall(operator, params, SqlParserPos.ZERO, functionQuantifier); if (segment.getAliasName().isPresent()) { return Optional.of(new SqlBasicCall(SqlStdOperatorTable.AS, Arrays.asList(sqlBasicCall, @@ -89,7 +94,7 @@ private static SqlAggFunction convertOperator(final String operator) { return REGISTRY.get(operator); } - private static List convertParameters(final Collection params, final String expression) { + private static List convertParameters(final Collection params, final String expression, final String separator) { if (expression.contains("*")) { return Collections.singletonList(SqlIdentifier.star(SqlParserPos.ZERO)); } @@ -97,6 +102,9 @@ private static List convertParameters(final Collection getExpressions(final List e } private String getDistinctExpression(final AggregationFunctionContext ctx) { - StringBuilder result = new StringBuilder(); - for (int i = 3; i < ctx.getChildCount() - 1; i++) { - result.append(ctx.getChild(i).getText()); - } - return result.toString(); + return ctx.aggregationExpression().getText(); } @Override diff --git a/parser/sql/statement/core/src/main/java/org/apache/shardingsphere/sql/parser/statement/core/segment/dml/item/AggregationDistinctProjectionSegment.java b/parser/sql/statement/core/src/main/java/org/apache/shardingsphere/sql/parser/statement/core/segment/dml/item/AggregationDistinctProjectionSegment.java index e01efd80f4a42..2b82bd1209693 100644 --- a/parser/sql/statement/core/src/main/java/org/apache/shardingsphere/sql/parser/statement/core/segment/dml/item/AggregationDistinctProjectionSegment.java +++ b/parser/sql/statement/core/src/main/java/org/apache/shardingsphere/sql/parser/statement/core/segment/dml/item/AggregationDistinctProjectionSegment.java @@ -33,4 +33,10 @@ public AggregationDistinctProjectionSegment(final int startIndex, final int stop super(startIndex, stopIndex, type, expression); distinctInnerExpression = SQLUtils.getExpressionWithoutOutsideParentheses(distinctExpression); } + + public AggregationDistinctProjectionSegment(final int startIndex, final int stopIndex, final AggregationType type, final String expression, final String distinctExpression, + final String separator) { + super(startIndex, stopIndex, type, expression, separator); + distinctInnerExpression = SQLUtils.getExpressionWithoutOutsideParentheses(distinctExpression); + } } diff --git a/parser/sql/statement/core/src/main/java/org/apache/shardingsphere/sql/parser/statement/core/segment/dml/item/AggregationProjectionSegment.java b/parser/sql/statement/core/src/main/java/org/apache/shardingsphere/sql/parser/statement/core/segment/dml/item/AggregationProjectionSegment.java index ab8eed3a2dc67..667a5ef89dbb4 100644 --- a/parser/sql/statement/core/src/main/java/org/apache/shardingsphere/sql/parser/statement/core/segment/dml/item/AggregationProjectionSegment.java +++ b/parser/sql/statement/core/src/main/java/org/apache/shardingsphere/sql/parser/statement/core/segment/dml/item/AggregationProjectionSegment.java @@ -43,6 +43,8 @@ public class AggregationProjectionSegment implements ProjectionSegment, AliasAva private final String expression; + private final String separator; + private final Collection parameters = new LinkedList<>(); @Setter @@ -53,6 +55,15 @@ public AggregationProjectionSegment(final int startIndex, final int stopIndex, f this.stopIndex = stopIndex; this.type = type; this.expression = expression; + this.separator = null; + } + + public AggregationProjectionSegment(final int startIndex, final int stopIndex, final AggregationType type, final String expression, final String separator) { + this.startIndex = startIndex; + this.stopIndex = stopIndex; + this.type = type; + this.expression = expression; + this.separator = separator; } @Override @@ -83,4 +94,8 @@ public Optional getAliasSegment() { public String getText() { return expression; } + + public Optional getSeparator() { + return Optional.ofNullable(separator); + } } diff --git a/test/e2e/sql/src/test/resources/cases/dql/e2e-dql-select-aggregate.xml b/test/e2e/sql/src/test/resources/cases/dql/e2e-dql-select-aggregate.xml index f8c39535f1f10..f4bd5b4809205 100644 --- a/test/e2e/sql/src/test/resources/cases/dql/e2e-dql-select-aggregate.xml +++ b/test/e2e/sql/src/test/resources/cases/dql/e2e-dql-select-aggregate.xml @@ -20,123 +20,123 @@ - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + @@ -144,4 +144,12 @@ + + + + + + + + diff --git a/test/it/parser/src/main/java/org/apache/shardingsphere/test/it/sql/parser/internal/asserts/segment/projection/ProjectionAssert.java b/test/it/parser/src/main/java/org/apache/shardingsphere/test/it/sql/parser/internal/asserts/segment/projection/ProjectionAssert.java index 7a8fbe2a079d5..ecaa805fd2c94 100644 --- a/test/it/parser/src/main/java/org/apache/shardingsphere/test/it/sql/parser/internal/asserts/segment/projection/ProjectionAssert.java +++ b/test/it/parser/src/main/java/org/apache/shardingsphere/test/it/sql/parser/internal/asserts/segment/projection/ProjectionAssert.java @@ -169,6 +169,7 @@ private static void assertAggregationProjection(final SQLCaseAssertContext asser assertThat(assertContext.getText("Aggregation projection type assertion error: "), actual.getType().name(), is(expected.getType())); assertThat(assertContext.getText("Aggregation projection inner expression assertion error: "), actual.getExpression(), is(expected.getExpression())); assertThat(assertContext.getText("Aggregation projection alias assertion error: "), actual.getAliasName().orElse(null), is(expected.getAlias())); + assertThat(assertContext.getText("Aggregation projection separator assertion error: "), actual.getSeparator().orElse(null), is(expected.getSeparator())); if (actual instanceof AggregationDistinctProjectionSegment) { assertThat(assertContext.getText("Projection type assertion error: "), expected, instanceOf(ExpectedAggregationDistinctProjection.class)); assertThat(assertContext.getText("Aggregation projection distinct inner expression assertion error: "), diff --git a/test/it/parser/src/main/java/org/apache/shardingsphere/test/it/sql/parser/internal/cases/parser/jaxb/segment/impl/projection/impl/aggregation/ExpectedAggregationProjection.java b/test/it/parser/src/main/java/org/apache/shardingsphere/test/it/sql/parser/internal/cases/parser/jaxb/segment/impl/projection/impl/aggregation/ExpectedAggregationProjection.java index 2cf8da6f4229d..384048fa74cca 100644 --- a/test/it/parser/src/main/java/org/apache/shardingsphere/test/it/sql/parser/internal/cases/parser/jaxb/segment/impl/projection/impl/aggregation/ExpectedAggregationProjection.java +++ b/test/it/parser/src/main/java/org/apache/shardingsphere/test/it/sql/parser/internal/cases/parser/jaxb/segment/impl/projection/impl/aggregation/ExpectedAggregationProjection.java @@ -44,6 +44,9 @@ public class ExpectedAggregationProjection extends AbstractExpectedSQLSegment im @XmlAttribute private String alias; + @XmlAttribute + private String separator; + @XmlElement(name = "parameters") private final List parameters = new LinkedList<>(); } diff --git a/test/it/parser/src/main/resources/case/dml/select-aggregate.xml b/test/it/parser/src/main/resources/case/dml/select-aggregate.xml index 50d7b5bd00fb5..08dd5ab86853d 100644 --- a/test/it/parser/src/main/resources/case/dml/select-aggregate.xml +++ b/test/it/parser/src/main/resources/case/dml/select-aggregate.xml @@ -518,4 +518,29 @@ + + diff --git a/test/it/parser/src/main/resources/case/dml/select-special-function.xml b/test/it/parser/src/main/resources/case/dml/select-special-function.xml index ac2e2441214b6..102a9540647e6 100644 --- a/test/it/parser/src/main/resources/case/dml/select-special-function.xml +++ b/test/it/parser/src/main/resources/case/dml/select-special-function.xml @@ -17,20 +17,20 @@ --> - - + - - + + - + - +