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 f17f89ea1de Support GroupConcat sql for aggregating multiple 
shards(#33797) (#33808)
f17f89ea1de is described below

commit f17f89ea1de8d03bb2adeb6db23d2d2fd98b8e96
Author: YaoFly <[email protected]>
AuthorDate: Fri Dec 6 09:17:54 2024 +0800

    Support GroupConcat sql for aggregating multiple shards(#33797) (#33808)
    
    * Support GroupConcat sql for aggregating multiple shards(#33797)
    
    * Check Style fix(#33797)
    
    * Check Style fix(#33797)
    
    * spotless fix (#33797)
    
    * unit test fix  (#33797)
    
    * spotless fix (#33797)
    
    * group_concat distinct compatible  (#33797)
    
    * group_concat distinct compatible  (#33797)
    
    * unit test fix for distinct group_concat  (#33797)
    
    * e2e test for group_concat  (#33797)
    
    * e2e test for group_concat  (#33797)
    
    * code format  (#33797)
    
    * e2e test  (#33797)
    
    * e2e test  (#33797)
    
    * e2e test  (#33797)
    
    * remove useless code(#33797)
    
    * code optimization (#33797)
    
    * sql parse unit test (#33797)
    
    * RELEASE-NOTES.md updated(#33797)
    
    * Code Optimization (#33797)
    
    ---------
    
    Co-authored-by: yaofly <[email protected]>
---
 RELEASE-NOTES.md                                   |  1 +
 .../dql/groupby/GroupByMemoryMergedResult.java     |  3 +-
 .../dql/groupby/GroupByStreamMergedResult.java     |  3 +-
 .../aggregation/AggregationUnitFactory.java        |  5 +-
 .../DistinctGroupConcatAggregationUnit.java        | 51 +++++++++++++++++
 .../aggregation/GroupConcatAggregationUnit.java    | 51 +++++++++++++++++
 .../aggregation/AggregationUnitFactoryTest.java    | 30 +++++++---
 .../GroupConcatAggregationUnitTest.java            | 51 +++++++++++++++++
 .../select/projection/engine/ProjectionEngine.java |  6 +-
 .../impl/AggregationDistinctProjection.java        |  8 +++
 .../projection/impl/AggregationProjection.java     | 14 +++++
 .../projection/ProjectionsSegmentBinder.java       |  6 +-
 .../impl/AggregationProjectionConverter.java       | 12 +++-
 .../src/main/antlr4/imports/mysql/BaseRule.g4      | 10 +++-
 .../visitor/statement/MySQLStatementVisitor.java   | 24 +++++---
 .../item/AggregationDistinctProjectionSegment.java |  6 ++
 .../dml/item/AggregationProjectionSegment.java     | 15 +++++
 .../cases/dql/e2e-dql-select-aggregate.xml         | 66 ++++++++++++----------
 .../segment/projection/ProjectionAssert.java       |  1 +
 .../aggregation/ExpectedAggregationProjection.java |  3 +
 .../main/resources/case/dml/select-aggregate.xml   | 25 ++++++++
 .../resources/case/dml/select-special-function.xml | 12 ++--
 .../sql/supported/dml/select-aggregate.xml         |  2 +
 .../sql/supported/dml/select-special-function.xml  |  2 +-
 24 files changed, 343 insertions(+), 64 deletions(-)

diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md
index ee11ef4c411..b880b94df86 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 56505af0f44..a0a2871e069 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 @@ public final class GroupByMemoryMergedResult extends 
MemoryMergedResult<Sharding
             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 550c982af4b..a26782b3be7 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 @@ public final class GroupByStreamMergedResult extends 
OrderByStreamMergedResult {
         boolean result = false;
         boolean cachedRow = false;
         Map<AggregationProjection, AggregationUnit> 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 c6709cbb4ed..02caeecf6c1 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 final class AggregationUnitFactory {
                 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 00000000000..93a19c21d69
--- /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<String> 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<Comparable<?>> 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 00000000000..c3e1796106d
--- /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<String> 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<Comparable<?>> 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 6e14d251409..92bc71a5fa3 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 00000000000..24b6e085500
--- /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 7edd0674707..3fd824f99a4 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 @@ public final class ProjectionEngine {
                 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 @@ public final class ProjectionEngine {
     }
     
     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 6bb6241b00b..e047645fa1e 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 final class AggregationDistinctProjection extends 
AggregationProjection {
         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 3034779f829..0493904fd8b 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<AggregationProjection> 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 class AggregationProjection implements Projection {
     public final Optional<IdentifierValue> getAlias() {
         return Optional.ofNullable(alias);
     }
+    
+    public Optional<String> 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 a520cc4142f..83557beb1f9 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 @@ public final class ProjectionsSegmentBinder {
                                                                                
           final Multimap<CaseInsensitiveString, TableSegmentBinderContext> 
tableBinderContexts,
                                                                                
           final Multimap<CaseInsensitiveString, TableSegmentBinderContext> 
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 @@ public final class ProjectionsSegmentBinder {
                                                                           
final Multimap<CaseInsensitiveString, TableSegmentBinderContext> 
tableBinderContexts,
                                                                           
final Multimap<CaseInsensitiveString, TableSegmentBinderContext> 
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 dd62b33e6b2..8cb1e65af35 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 final class AggregationProjectionConverter {
         }
         SqlLiteral functionQuantifier = segment instanceof 
AggregationDistinctProjectionSegment ? 
SqlLiteral.createSymbol(SqlSelectKeyword.DISTINCT, SqlParserPos.ZERO) : null;
         SqlAggFunction operator = convertOperator(segment.getType().name());
-        List<SqlNode> params = convertParameters(segment.getParameters(), 
segment.getExpression());
+        List<SqlNode> 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 @@ public final class AggregationProjectionConverter {
         return REGISTRY.get(operator);
     }
     
-    private static List<SqlNode> convertParameters(final 
Collection<ExpressionSegment> params, final String expression) {
+    private static List<SqlNode> convertParameters(final 
Collection<ExpressionSegment> params, final String expression, final String 
separator) {
         if (expression.contains("*")) {
             return 
Collections.singletonList(SqlIdentifier.star(SqlParserPos.ZERO));
         }
@@ -97,6 +102,9 @@ public final class AggregationProjectionConverter {
         for (ExpressionSegment each : params) {
             ExpressionConverter.convert(each).ifPresent(result::add);
         }
+        if (null != separator) {
+            result.add(SqlLiteral.createCharString(separator, 
SqlParserPos.ZERO));
+        }
         return result;
     }
 }
diff --git a/parser/sql/dialect/mysql/src/main/antlr4/imports/mysql/BaseRule.g4 
b/parser/sql/dialect/mysql/src/main/antlr4/imports/mysql/BaseRule.g4
index 77d80dbd1d6..ae356733ce3 100644
--- a/parser/sql/dialect/mysql/src/main/antlr4/imports/mysql/BaseRule.g4
+++ b/parser/sql/dialect/mysql/src/main/antlr4/imports/mysql/BaseRule.g4
@@ -986,8 +986,16 @@ udfFunction
     : functionName LP_ (expr? | expr (COMMA_ expr)*) RP_
     ;
 
+separatorName
+    : SEPARATOR string_
+    ;
+
+aggregationExpression
+    : expr (COMMA_ expr)* | ASTERISK_
+    ;
+
 aggregationFunction
-    : aggregationFunctionName LP_ distinct? (expr (COMMA_ expr)* | ASTERISK_)? 
collateClause? RP_ overClause?
+    : aggregationFunctionName LP_ distinct? aggregationExpression? 
collateClause? separatorName? RP_ overClause?
     ;
 
 jsonFunction
diff --git 
a/parser/sql/dialect/mysql/src/main/java/org/apache/shardingsphere/sql/parser/mysql/visitor/statement/MySQLStatementVisitor.java
 
b/parser/sql/dialect/mysql/src/main/java/org/apache/shardingsphere/sql/parser/mysql/visitor/statement/MySQLStatementVisitor.java
index d058633429a..6ab433e59a1 100644
--- 
a/parser/sql/dialect/mysql/src/main/java/org/apache/shardingsphere/sql/parser/mysql/visitor/statement/MySQLStatementVisitor.java
+++ 
b/parser/sql/dialect/mysql/src/main/java/org/apache/shardingsphere/sql/parser/mysql/visitor/statement/MySQLStatementVisitor.java
@@ -25,6 +25,7 @@ import org.antlr.v4.runtime.misc.Interval;
 import org.antlr.v4.runtime.tree.TerminalNode;
 import org.apache.shardingsphere.sql.parser.api.ASTNode;
 import org.apache.shardingsphere.sql.parser.autogen.MySQLStatementBaseVisitor;
+import 
org.apache.shardingsphere.sql.parser.autogen.MySQLStatementParser.SeparatorNameContext;
 import 
org.apache.shardingsphere.sql.parser.autogen.MySQLStatementParser.AggregationFunctionContext;
 import 
org.apache.shardingsphere.sql.parser.autogen.MySQLStatementParser.AliasContext;
 import 
org.apache.shardingsphere.sql.parser.autogen.MySQLStatementParser.AssignmentContext;
@@ -916,6 +917,11 @@ public abstract class MySQLStatementVisitor extends 
MySQLStatementBaseVisitor<AS
                 : new 
ExpressionProjectionSegment(ctx.getStart().getStartIndex(), 
ctx.getStop().getStopIndex(), getOriginalText(ctx));
     }
     
+    @Override
+    public ASTNode visitSeparatorName(final SeparatorNameContext ctx) {
+        return new StringLiteralValue(ctx.string_().getText());
+    }
+    
     @Override
     public final ASTNode visitJsonFunction(final JsonFunctionContext ctx) {
         JsonFunctionNameContext functionNameContext = ctx.jsonFunctionName();
@@ -957,14 +963,18 @@ public abstract class MySQLStatementVisitor extends 
MySQLStatementBaseVisitor<AS
     
     private ASTNode createAggregationSegment(final AggregationFunctionContext 
ctx, final String aggregationType) {
         AggregationType type = 
AggregationType.valueOf(aggregationType.toUpperCase());
+        String separator = null;
+        if (null != ctx.separatorName()) {
+            separator = new 
StringLiteralValue(ctx.separatorName().string_().getText()).getValue();
+        }
         if (null != ctx.distinct()) {
             AggregationDistinctProjectionSegment result =
-                    new 
AggregationDistinctProjectionSegment(ctx.getStart().getStartIndex(), 
ctx.getStop().getStopIndex(), type, getOriginalText(ctx), 
getDistinctExpression(ctx));
-            result.getParameters().addAll(getExpressions(ctx.expr()));
+                    new 
AggregationDistinctProjectionSegment(ctx.getStart().getStartIndex(), 
ctx.getStop().getStopIndex(), type, getOriginalText(ctx), 
getDistinctExpression(ctx), separator);
+            
result.getParameters().addAll(getExpressions(ctx.aggregationExpression().expr()));
             return result;
         }
-        AggregationProjectionSegment result = new 
AggregationProjectionSegment(ctx.getStart().getStartIndex(), 
ctx.getStop().getStopIndex(), type, getOriginalText(ctx));
-        result.getParameters().addAll(getExpressions(ctx.expr()));
+        AggregationProjectionSegment result = new 
AggregationProjectionSegment(ctx.getStart().getStartIndex(), 
ctx.getStop().getStopIndex(), type, getOriginalText(ctx), separator);
+        
result.getParameters().addAll(getExpressions(ctx.aggregationExpression().expr()));
         return result;
     }
     
@@ -980,11 +990,7 @@ public abstract class MySQLStatementVisitor extends 
MySQLStatementBaseVisitor<AS
     }
     
     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 e01efd80f4a..2b82bd12096 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 final class AggregationDistinctProjectionSegment 
extends AggregationProje
         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 ab8eed3a2dc..667a5ef89db 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<ExpressionSegment> parameters = new 
LinkedList<>();
     
     @Setter
@@ -53,6 +55,15 @@ public class AggregationProjectionSegment implements 
ProjectionSegment, AliasAva
         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 class AggregationProjectionSegment implements 
ProjectionSegment, AliasAva
     public String getText() {
         return expression;
     }
+    
+    public Optional<String> 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 f8c39535f1f..f4bd5b48092 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 @@
     <test-case sql="SELECT count(0) as orders_count FROM t_order o WHERE 
o.status LIKE CONCAT('%%', ?, '%%') AND o.user_id IN (?, ?) AND o.order_id 
BETWEEN ? AND ?" 
scenario-types="db,tbl,dbtbl_with_readwrite_splitting,readwrite_splitting">
         <assertion parameters="init:String, 10:int, 11:int, 1000:int, 
2901:int" expected-data-source-name="read_dataset" />
     </test-case>
-    
+
     <test-case sql="SELECT count(0) as orders_count FROM t_order o WHERE 
o.status ~~ CONCAT('%%', ?, '%%') AND o.user_id IN (?, ?) AND o.order_id 
BETWEEN ? AND ?" db-types="PostgreSQL" 
scenario-types="db,tbl,dbtbl_with_readwrite_splitting,readwrite_splitting">
         <assertion parameters="init:String, 10:int, 11:int, 1000:int, 
2901:int" expected-data-source-name="read_dataset" />
     </test-case>
-    
+
     <test-case sql="SELECT SUM(user_id) AS user_id_sum FROM t_order" 
scenario-types="db,tbl,dbtbl_with_readwrite_splitting,readwrite_splitting">
         <assertion expected-data-source-name="read_dataset" />
     </test-case>
-    
+
     <test-case sql="SELECT COUNT(*) AS orders_count FROM t_order" 
scenario-types="db,tbl,dbtbl_with_readwrite_splitting,readwrite_splitting">
         <assertion expected-data-source-name="read_dataset" />
     </test-case>
-    
+
     <test-case sql="SELECT COUNT(*) AS orders_count FROM t_order WHERE 
order_id > 1-1" 
scenario-types="db,tbl,dbtbl_with_readwrite_splitting,readwrite_splitting">
         <assertion expected-data-source-name="read_dataset" />
     </test-case>
-    
+
     <test-case sql="SELECT COUNT(*) AS orders_count FROM t_order WHERE 
order_id > 1 - 1" 
scenario-types="db,tbl,dbtbl_with_readwrite_splitting,readwrite_splitting">
         <assertion expected-data-source-name="read_dataset" />
     </test-case>
-    
+
     <test-case sql="SELECT MAX(user_id) AS max_user_id FROM t_order" 
scenario-types="db,tbl,dbtbl_with_readwrite_splitting,readwrite_splitting">
         <assertion expected-data-source-name="read_dataset" />
     </test-case>
-    
+
     <test-case sql="SELECT MIN(user_id) AS min_user_id FROM t_order" 
scenario-types="db,tbl,dbtbl_with_readwrite_splitting,readwrite_splitting">
         <assertion expected-data-source-name="read_dataset" />
     </test-case>
-    
+
     <!-- FIXME #15593 Expected: is "19", but: was "19.5000" in db scenario -->
     <test-case sql="SELECT AVG(user_id) AS user_id_avg FROM t_order" 
scenario-types="db,tbl,dbtbl_with_readwrite_splitting,readwrite_splitting">
         <assertion expected-data-source-name="read_dataset" />
     </test-case>
-    
+
     <test-case sql="SELECT COUNT(*) AS items_count FROM t_order o, 
t_order_item i WHERE o.user_id = i.user_id AND o.order_id = i.order_id AND 
o.user_id IN (?, ?) AND o.order_id BETWEEN ? AND ?" 
scenario-types="db,tbl,dbtbl_with_readwrite_splitting,readwrite_splitting">
         <assertion parameters="10:int, 11:int, 1000:int, 1909:int" 
expected-data-source-name="read_dataset" />
     </test-case>
-    
+
     <test-case sql="SELECT COUNT(*) AS items_count FROM t_order o JOIN 
t_order_item i ON o.user_id = i.user_id AND o.order_id = i.order_id WHERE 
o.user_id IN (?, ?) AND o.order_id BETWEEN ? AND ?" 
scenario-types="db,tbl,dbtbl_with_readwrite_splitting,readwrite_splitting">
         <assertion parameters="10:int, 11:int, 1000:int, 1909:int"  
expected-data-source-name="read_dataset" />
     </test-case>
-    
+
     <test-case sql="SELECT COUNT(`order_id`) AS orders_count FROM t_order" 
db-types="MySQL" 
scenario-types="db,tbl,dbtbl_with_readwrite_splitting,readwrite_splitting">
         <assertion expected-data-source-name="read_dataset" />
     </test-case>
-    
+
     <test-case sql="SELECT SUM(order_id) AS orders_sum, user_id FROM t_order 
GROUP BY user_id ORDER BY user_id" 
scenario-types="db,tbl,dbtbl_with_readwrite_splitting,readwrite_splitting">
         <assertion expected-data-source-name="read_dataset" />
     </test-case>
-    
+
     <test-case sql="SELECT COUNT(order_id) AS orders_count, user_id FROM 
t_order GROUP BY user_id ORDER BY user_id" 
scenario-types="db,tbl,dbtbl_with_readwrite_splitting,readwrite_splitting">
         <assertion expected-data-source-name="read_dataset" />
     </test-case>
-    
+
     <test-case sql="SELECT MAX(order_id) AS max_order_id, user_id FROM t_order 
GROUP BY user_id ORDER BY user_id" 
scenario-types="db,tbl,dbtbl_with_readwrite_splitting,readwrite_splitting">
         <assertion expected-data-source-name="read_dataset" />
     </test-case>
-    
+
     <test-case sql="SELECT MIN(order_id) AS min_order_id, user_id FROM t_order 
GROUP BY user_id ORDER BY user_id" 
scenario-types="db,tbl,dbtbl_with_readwrite_splitting,readwrite_splitting">
         <assertion expected-data-source-name="read_dataset" />
     </test-case>
-    
+
     <test-case sql="SELECT AVG(order_id) AS orders_avg, user_id FROM t_order 
GROUP BY user_id ORDER BY user_id" 
scenario-types="db,tbl,dbtbl_with_readwrite_splitting,readwrite_splitting">
         <assertion expected-data-source-name="read_dataset" />
     </test-case>
-    
+
     <test-case sql="SELECT SUM(order_id) AS orders_sum, user_id FROM t_order 
GROUP BY user_id ORDER BY orders_sum DESC" 
scenario-types="db,tbl,dbtbl_with_readwrite_splitting,readwrite_splitting">
         <assertion expected-data-source-name="read_dataset" />
     </test-case>
-    
+
     <test-case sql="SELECT count(*) as items_count FROM t_order o JOIN 
t_order_item i ON o.user_id = i.user_id AND o.order_id = i.order_id WHERE 
o.user_id IN (?, ?) AND o.order_id BETWEEN ? AND ? GROUP BY o.user_id" 
scenario-types="db,tbl,dbtbl_with_readwrite_splitting,readwrite_splitting">
         <assertion parameters="10:int, 11:int, 1000:int, 1109:int" 
expected-data-source-name="read_dataset" />
     </test-case>
-    
+
     <test-case sql="SELECT sum(if(status=0, 1, 0)) func_status FROM t_order 
WHERE user_id = ? AND order_id = ?" db-types="MySQL" 
scenario-types="db,tbl,dbtbl_with_readwrite_splitting,readwrite_splitting">
         <assertion parameters="12:int, 1000:int" 
expected-data-source-name="read_dataset" />
     </test-case>
-    
+
     <!-- TODO Replace with standard table structure -->
     <!--<test-case sql="SELECT (SELECT MAX(user_id) FROM 
t_order_federate_sharding) max_user_id, order_id_sharding, status FROM 
t_order_federate_sharding WHERE order_id_sharding > ?" 
db-types="MySQL,PostgreSQL" scenario-types="tbl">
         <assertion parameters="1100:int" />
     </test-case>-->
-    
+
     <!-- TODO Replace with standard table structure -->
     <!--<test-case sql="SELECT user_id, SUM(order_id_sharding) FROM 
t_order_federate_sharding GROUP BY user_id HAVING SUM(order_id_sharding) > ? 
ORDER BY user_id" db-types="MySQL,PostgreSQL" scenario-types="tbl">
         <assertion parameters="1000:int" />
     </test-case>-->
-    
+
     <test-case sql="SELECT COUNT(1) FROM t_order WHERE order_id &lt; ?" 
db-types="PostgreSQL" scenario-types="db,tbl">
         <assertion parameters="2000:int" />
     </test-case>
-    
+
     <test-case sql="SELECT SUM(CRC32(`order_id`)) FROM t_order WHERE order_id 
= ?" db-types="MySQL" scenario-types="db,tbl">
         <assertion parameters="1000:int" />
     </test-case>
-    
+
     <!-- TODO Replace with standard table structure -->
     <!--<test-case sql="SELECT AVG(order_id_sharding) AS order_id_sharding_avg 
FROM (SELECT order_id_sharding, user_id FROM t_order_federate_sharding WHERE 
order_id_sharding = 1010) AS TEMP" 
scenario-types="db,dbtbl_with_readwrite_splitting_and_encrypt,sharding_and_encrypt,encrypt_and_readwrite_splitting"
 db-types="MySQL">
         <assertion expected-data-source-name="read_dataset" />
     </test-case>-->
-    
+
     <test-case sql="SELECT MIN(o.order_id), MIN(o.merchant_id), i.product_id 
FROM t_order o INNER JOIN t_order_item i ON o.order_id = i.order_id WHERE 
o.user_id = ? GROUP BY i.product_id ORDER BY i.product_id" 
db-types="MySQL,PostgreSQL,openGauss" scenario-types="db_tbl_sql_federation">
         <assertion parameters="10:int" 
expected-data-source-name="read_dataset" />
     </test-case>
-    
+
     <test-case sql="SELECT MIN(o.order_id), MIN(o.merchant_id), 
MIN(m.merchant_name) FROM t_order o INNER JOIN t_merchant m ON o.merchant_id = 
m.merchant_id WHERE o.user_id = ? GROUP BY m.merchant_id ORDER BY 
m.merchant_id" db-types="MySQL,PostgreSQL,openGauss" 
scenario-types="db_tbl_sql_federation">
         <assertion parameters="10:int" 
expected-data-source-name="read_dataset" />
     </test-case>
-    
+
     <test-case sql="SELECT MIN(d.detail_id), MIN(p.category_id), p.product_id 
FROM t_product p INNER JOIN t_product_detail d ON p.product_id = d.product_id 
WHERE p.product_id = ? GROUP BY p.product_id" 
db-types="MySQL,PostgreSQL,openGauss" scenario-types="db_tbl_sql_federation">
         <assertion parameters="10:int" 
expected-data-source-name="read_dataset" />
     </test-case>
-    
+
     <test-case sql="SELECT MAX(p.price) AS max_price, MIN(p.price) AS 
min_price, SUM(p.price) AS sum_price, AVG(p.price) AS avg_price, COUNT(1) AS 
count FROM t_order o INNER JOIN t_order_item i ON o.order_id = i.order_id INNER 
JOIN t_product p ON i.product_id = p.product_id GROUP BY o.order_id HAVING 
SUM(p.price) > ? ORDER BY max_price" db-types="MySQL,PostgreSQL,openGauss" 
scenario-types="db_tbl_sql_federation">
         <assertion parameters="10000:int" 
expected-data-source-name="read_dataset" />
     </test-case>
-    
+
     <test-case sql="SELECT * FROM t_merchant WHERE business_code LIKE 
CONCAT('%', ?, '%')" db-types="MySQL,PostgreSQL,openGauss" 
scenario-types="encrypt">
         <assertion parameters="abc:String" 
expected-data-source-name="read_dataset" />
     </test-case>
@@ -144,4 +144,12 @@
     <test-case sql="SELECT * FROM t_merchant WHERE business_code LIKE 
CONCAT('%', CONCAT(?, '%'))" db-types="MySQL,PostgreSQL,openGauss" 
scenario-types="encrypt">
         <assertion parameters="abc:String" 
expected-data-source-name="read_dataset" />
     </test-case>
+
+    <test-case sql="SELECT GROUP_CONCAT(o.remark) as order_id_group_concat 
FROM t_order o where o.order_id > 1 - 1" db-types="MySQL" 
scenario-types="db,tbl,dbtbl_with_readwrite_splitting,readwrite_splitting,db_tbl_sql_federation">
+        <assertion expected-data-source-name="read_dataset" />
+    </test-case>
+
+    <test-case sql="SELECT GROUP_CONCAT(distinct o.remark SEPARATOR ' ') as 
order_id_group_concat FROM t_order o where o.order_id > 1 - 1" db-types="MySQL" 
scenario-types="db,tbl,dbtbl_with_readwrite_splitting,readwrite_splitting">
+        <assertion expected-data-source-name="read_dataset" />
+    </test-case>
 </e2e-test-cases>
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 7a8fbe2a079..ecaa805fd2c 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 @@ public final class ProjectionAssert {
         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 2cf8da6f422..384048fa74c 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<ExpectedExpression> 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 50d7b5bd00f..08dd5ab8685 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 @@
             <index-item index="1" start-index="199" stop-index="199"/>
         </order-by>
     </select>
+    <select sql-case-id="select_group_concat">
+        <projections start-index="7" stop-index="51">
+            <aggregation-projection type="GROUP_CONCAT" 
expression="GROUP_CONCAT(user_id)" alias="user_id_group_concat" start-index="7" 
stop-index="27">
+                <parameter>
+                    <column name="user_id" start-index="20" stop-index="26" />
+                </parameter>
+            </aggregation-projection>
+        </projections>
+        <from>
+            <simple-table name="t_order" start-index="58" stop-index="64"/>
+        </from>
+    </select>
+    <select sql-case-id="select_group_concat_with_distinct_with_separator">
+        <projections start-index="7" stop-index="74">
+            <aggregation-distinct-projection type="GROUP_CONCAT" 
expression="GROUP_CONCAT(distinct user_id SEPARATOR ' ')" separator=" "
+                                    distinct-inner-expression="user_id" 
alias="user_id_group_concat" start-index="7" stop-index="50">
+                <parameter>
+                    <column name="user_id" start-index="29" stop-index="35" />
+                </parameter>
+            </aggregation-distinct-projection>
+        </projections>
+        <from>
+            <simple-table name="t_order" start-index="81" stop-index="87"/>
+        </from>
+    </select>
 </sql-parser-test-cases>
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 ac2e2441214..102a9540647 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 @@
   -->
 
 <sql-parser-test-cases>
-    <select sql-case-id="select_group_concat">
+    <select sql-case-id="select_group_concat_with_order_by">
         <from>
-            <simple-table name="t_order" start-index="33" stop-index="39" />
+            <simple-table name="t_order" start-index="49" stop-index="55" />
         </from>
-        <projections start-index="7" stop-index="26">
-            <aggregation-projection type="GROUP_CONCAT" 
expression="GROUP_CONCAT(status)" text="GROUP_CONCAT(status)" start-index="7" 
stop-index="26">
+        <projections start-index="7" stop-index="42">
+            <expression-projection text="GROUP_CONCAT(status ORDER BY status)" 
start-index="7" stop-index="42">
                 <expr>
-                    <function function-name="GROUP_CONCAT" start-index="7" 
stop-index="26" text="GROUP_CONCAT(status)">
+                    <function function-name="GROUP_CONCAT" start-index="7" 
stop-index="42" text="GROUP_CONCAT(status ORDER BY status)">
                         <parameter>
                             <column name="status" start-index="20" 
stop-index="25" />
                         </parameter>
                     </function>
                 </expr>
-            </aggregation-projection>
+            </expression-projection>
         </projections>
     </select>
     <select sql-case-id="select_window_function">
diff --git 
a/test/it/parser/src/main/resources/sql/supported/dml/select-aggregate.xml 
b/test/it/parser/src/main/resources/sql/supported/dml/select-aggregate.xml
index 583ad048847..a4196127606 100644
--- a/test/it/parser/src/main/resources/sql/supported/dml/select-aggregate.xml
+++ b/test/it/parser/src/main/resources/sql/supported/dml/select-aggregate.xml
@@ -38,4 +38,6 @@
     <sql-case id="select_approx_count"
               value="select owner, approx_count(*) , approx_rank(partition by 
owner order by approx_count(*) desc) from t group by owner having 
approx_rank(partition by owner order by approx_count(*) desc) &lt;= 1 order by 
1"
               db-types="Oracle"/>
+    <sql-case id="select_group_concat" value="SELECT GROUP_CONCAT(user_id) AS 
user_id_group_concat FROM t_order" db-types="MySQL"/>
+    <sql-case id="select_group_concat_with_distinct_with_separator" 
value="SELECT GROUP_CONCAT(distinct user_id SEPARATOR ' ') AS 
user_id_group_concat FROM t_order" db-types="MySQL"/>
 </sql-cases>
diff --git 
a/test/it/parser/src/main/resources/sql/supported/dml/select-special-function.xml
 
b/test/it/parser/src/main/resources/sql/supported/dml/select-special-function.xml
index 8d8dcd4a0e8..99727b077bc 100644
--- 
a/test/it/parser/src/main/resources/sql/supported/dml/select-special-function.xml
+++ 
b/test/it/parser/src/main/resources/sql/supported/dml/select-special-function.xml
@@ -17,7 +17,7 @@
   -->
 
 <sql-cases>
-    <sql-case id="select_group_concat" value="SELECT GROUP_CONCAT(status) FROM 
t_order" db-types="MySQL" />
+    <sql-case id="select_group_concat_with_order_by" value="SELECT 
GROUP_CONCAT(status ORDER BY status) FROM t_order" db-types="MySQL" />
     <sql-case id="select_window_function" value="SELECT order_id, ROW_NUMBER() 
OVER() FROM t_order" db-types="MySQL" />
     <sql-case id="select_cast_function" value="SELECT CAST('1' AS UNSIGNED)" 
db-types="MySQL" />
     <sql-case id="select_cast" value="SELECT CAST(c AT TIME ZONE 'UTC' AS 
DATETIME)" db-types="MySQL" />

Reply via email to