View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *     http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  
18  package org.apache.shardingsphere.infra.binder.context.segment.select.projection.engine;
19  
20  import lombok.RequiredArgsConstructor;
21  import org.apache.shardingsphere.infra.binder.context.segment.select.projection.DerivedColumn;
22  import org.apache.shardingsphere.infra.binder.context.segment.select.projection.Projection;
23  import org.apache.shardingsphere.infra.binder.context.segment.select.projection.impl.AggregationDistinctProjection;
24  import org.apache.shardingsphere.infra.binder.context.segment.select.projection.impl.AggregationProjection;
25  import org.apache.shardingsphere.infra.binder.context.segment.select.projection.impl.ColumnProjection;
26  import org.apache.shardingsphere.infra.binder.context.segment.select.projection.impl.ExpressionProjection;
27  import org.apache.shardingsphere.infra.binder.context.segment.select.projection.impl.ParameterMarkerProjection;
28  import org.apache.shardingsphere.infra.binder.context.segment.select.projection.impl.ShorthandProjection;
29  import org.apache.shardingsphere.infra.binder.context.segment.select.projection.impl.SubqueryProjection;
30  import org.apache.shardingsphere.infra.database.core.type.DatabaseType;
31  import org.apache.shardingsphere.sql.parser.sql.common.enums.AggregationType;
32  import org.apache.shardingsphere.sql.parser.sql.common.enums.Paren;
33  import org.apache.shardingsphere.sql.parser.sql.common.segment.dml.expr.simple.ParameterMarkerExpressionSegment;
34  import org.apache.shardingsphere.sql.parser.sql.common.segment.dml.item.AggregationDistinctProjectionSegment;
35  import org.apache.shardingsphere.sql.parser.sql.common.segment.dml.item.AggregationProjectionSegment;
36  import org.apache.shardingsphere.sql.parser.sql.common.segment.dml.item.ColumnProjectionSegment;
37  import org.apache.shardingsphere.sql.parser.sql.common.segment.dml.item.ExpressionProjectionSegment;
38  import org.apache.shardingsphere.sql.parser.sql.common.segment.dml.item.ProjectionSegment;
39  import org.apache.shardingsphere.sql.parser.sql.common.segment.dml.item.ShorthandProjectionSegment;
40  import org.apache.shardingsphere.sql.parser.sql.common.segment.dml.item.SubqueryProjectionSegment;
41  import org.apache.shardingsphere.sql.parser.sql.common.segment.generic.OwnerSegment;
42  import org.apache.shardingsphere.sql.parser.sql.common.value.identifier.IdentifierValue;
43  
44  import java.util.Collection;
45  import java.util.LinkedHashSet;
46  import java.util.Optional;
47  
48  /**
49   * Projection engine.
50   */
51  @RequiredArgsConstructor
52  public final class ProjectionEngine {
53      
54      private final DatabaseType databaseType;
55      
56      private int aggregationAverageDerivedColumnCount;
57      
58      private int aggregationDistinctDerivedColumnCount;
59      
60      /**
61       * Create projection.
62       *
63       * @param projectionSegment projection segment
64       * @return projection
65       */
66      public Optional<Projection> createProjection(final ProjectionSegment projectionSegment) {
67          if (projectionSegment instanceof ShorthandProjectionSegment) {
68              return Optional.of(createProjection((ShorthandProjectionSegment) projectionSegment));
69          }
70          if (projectionSegment instanceof ColumnProjectionSegment) {
71              return Optional.of(createProjection((ColumnProjectionSegment) projectionSegment));
72          }
73          if (projectionSegment instanceof ExpressionProjectionSegment) {
74              return Optional.of(createProjection((ExpressionProjectionSegment) projectionSegment));
75          }
76          if (projectionSegment instanceof AggregationDistinctProjectionSegment) {
77              return Optional.of(createProjection((AggregationDistinctProjectionSegment) projectionSegment));
78          }
79          if (projectionSegment instanceof AggregationProjectionSegment) {
80              return Optional.of(createProjection((AggregationProjectionSegment) projectionSegment));
81          }
82          if (projectionSegment instanceof SubqueryProjectionSegment) {
83              return Optional.of(createProjection((SubqueryProjectionSegment) projectionSegment));
84          }
85          if (projectionSegment instanceof ParameterMarkerExpressionSegment) {
86              return Optional.of(createProjection((ParameterMarkerExpressionSegment) projectionSegment));
87          }
88          return Optional.empty();
89      }
90      
91      private ParameterMarkerProjection createProjection(final ParameterMarkerExpressionSegment projectionSegment) {
92          return new ParameterMarkerProjection(projectionSegment.getParameterMarkerIndex(), projectionSegment.getParameterMarkerType(), projectionSegment.getAlias().orElse(null));
93      }
94      
95      private SubqueryProjection createProjection(final SubqueryProjectionSegment projectionSegment) {
96          Projection subqueryProjection = createProjection(projectionSegment.getSubquery().getSelect().getProjections().getProjections().iterator().next())
97                  .orElseThrow(() -> new IllegalArgumentException("Subquery projection must have at least one projection column."));
98          return new SubqueryProjection(projectionSegment, subqueryProjection, projectionSegment.getAlias().orElse(null), databaseType);
99      }
100     
101     private ShorthandProjection createProjection(final ShorthandProjectionSegment projectionSegment) {
102         IdentifierValue owner = projectionSegment.getOwner().map(OwnerSegment::getIdentifier).orElse(null);
103         Collection<Projection> projections = new LinkedHashSet<>();
104         projectionSegment.getActualProjectionSegments().forEach(each -> createProjection(each).ifPresent(projections::add));
105         return new ShorthandProjection(owner, projections);
106     }
107     
108     private ColumnProjection createProjection(final ColumnProjectionSegment projectionSegment) {
109         IdentifierValue owner = projectionSegment.getColumn().getOwner().isPresent() ? projectionSegment.getColumn().getOwner().get().getIdentifier() : null;
110         IdentifierValue alias = projectionSegment.getAliasName().isPresent() ? projectionSegment.getAlias().orElse(null) : null;
111         ColumnProjection result = new ColumnProjection(owner, projectionSegment.getColumn().getIdentifier(), alias, databaseType);
112         result.setOriginalColumn(projectionSegment.getColumn().getColumnBoundedInfo().getOriginalColumn());
113         result.setOriginalTable(projectionSegment.getColumn().getColumnBoundedInfo().getOriginalTable());
114         return result;
115     }
116     
117     private ExpressionProjection createProjection(final ExpressionProjectionSegment projectionSegment) {
118         return new ExpressionProjection(projectionSegment, projectionSegment.getAlias().orElse(null), databaseType);
119     }
120     
121     private AggregationDistinctProjection createProjection(final AggregationDistinctProjectionSegment projectionSegment) {
122         IdentifierValue alias =
123                 projectionSegment.getAlias().orElseGet(() -> new IdentifierValue(DerivedColumn.AGGREGATION_DISTINCT_DERIVED.getDerivedColumnAlias(aggregationDistinctDerivedColumnCount++)));
124         AggregationDistinctProjection result = new AggregationDistinctProjection(
125                 projectionSegment.getStartIndex(), projectionSegment.getStopIndex(), projectionSegment.getType(), projectionSegment.getExpression(), alias,
126                 projectionSegment.getDistinctInnerExpression(), databaseType);
127         if (AggregationType.AVG == result.getType()) {
128             appendAverageDistinctDerivedProjection(result);
129         }
130         return result;
131     }
132     
133     private AggregationProjection createProjection(final AggregationProjectionSegment projectionSegment) {
134         AggregationProjection result = new AggregationProjection(projectionSegment.getType(), projectionSegment.getExpression(), projectionSegment.getAlias().orElse(null), databaseType);
135         if (AggregationType.AVG == result.getType()) {
136             appendAverageDerivedProjection(result);
137             // TODO replace avg to constant, avoid calculate useless avg
138         }
139         return result;
140     }
141     
142     private void appendAverageDistinctDerivedProjection(final AggregationDistinctProjection averageDistinctProjection) {
143         String distinctInnerExpression = averageDistinctProjection.getDistinctInnerExpression();
144         String countAlias = DerivedColumn.AVG_COUNT_ALIAS.getDerivedColumnAlias(aggregationAverageDerivedColumnCount);
145         String innerExpression = averageDistinctProjection.getExpression().substring(averageDistinctProjection.getExpression().indexOf(Paren.PARENTHESES.getLeftParen()));
146         AggregationDistinctProjection countDistinctProjection =
147                 new AggregationDistinctProjection(0, 0, AggregationType.COUNT, AggregationType.COUNT.name() + innerExpression, new IdentifierValue(countAlias), distinctInnerExpression, databaseType);
148         String sumAlias = DerivedColumn.AVG_SUM_ALIAS.getDerivedColumnAlias(aggregationAverageDerivedColumnCount);
149         AggregationDistinctProjection sumDistinctProjection =
150                 new AggregationDistinctProjection(0, 0, AggregationType.SUM, AggregationType.SUM.name() + innerExpression, new IdentifierValue(sumAlias), distinctInnerExpression, databaseType);
151         averageDistinctProjection.getDerivedAggregationProjections().add(countDistinctProjection);
152         averageDistinctProjection.getDerivedAggregationProjections().add(sumDistinctProjection);
153         aggregationAverageDerivedColumnCount++;
154     }
155     
156     private void appendAverageDerivedProjection(final AggregationProjection averageProjection) {
157         String countAlias = DerivedColumn.AVG_COUNT_ALIAS.getDerivedColumnAlias(aggregationAverageDerivedColumnCount);
158         String innerExpression = averageProjection.getExpression().substring(averageProjection.getExpression().indexOf(Paren.PARENTHESES.getLeftParen()));
159         AggregationProjection countProjection = new AggregationProjection(AggregationType.COUNT, AggregationType.COUNT.name() + innerExpression, new IdentifierValue(countAlias), databaseType);
160         String sumAlias = DerivedColumn.AVG_SUM_ALIAS.getDerivedColumnAlias(aggregationAverageDerivedColumnCount);
161         AggregationProjection sumProjection = new AggregationProjection(AggregationType.SUM, AggregationType.SUM.name() + innerExpression, new IdentifierValue(sumAlias), databaseType);
162         averageProjection.getDerivedAggregationProjections().add(countProjection);
163         averageProjection.getDerivedAggregationProjections().add(sumProjection);
164         aggregationAverageDerivedColumnCount++;
165     }
166 }