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.segment.from.impl;
19  
20  import com.google.common.base.Strings;
21  import lombok.AccessLevel;
22  import lombok.NoArgsConstructor;
23  import org.apache.shardingsphere.infra.binder.context.segment.select.projection.util.ProjectionUtils;
24  import org.apache.shardingsphere.infra.binder.segment.from.SimpleTableSegmentBinderContext;
25  import org.apache.shardingsphere.infra.binder.segment.from.TableSegmentBinderContext;
26  import org.apache.shardingsphere.infra.binder.segment.parameter.impl.ParameterMarkerExpressionSegmentBinder;
27  import org.apache.shardingsphere.infra.binder.statement.SQLStatementBinderContext;
28  import org.apache.shardingsphere.infra.binder.statement.dml.SelectStatementBinder;
29  import org.apache.shardingsphere.infra.database.core.type.DatabaseType;
30  import org.apache.shardingsphere.infra.database.core.type.DatabaseTypeRegistry;
31  import org.apache.shardingsphere.sql.parser.sql.common.segment.dml.column.ColumnSegment;
32  import org.apache.shardingsphere.sql.parser.sql.common.segment.dml.expr.ExpressionSegment;
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.expr.subquery.SubquerySegment;
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.generic.AliasAvailable;
41  import org.apache.shardingsphere.sql.parser.sql.common.segment.generic.AliasSegment;
42  import org.apache.shardingsphere.sql.parser.sql.common.segment.generic.OwnerSegment;
43  import org.apache.shardingsphere.sql.parser.sql.common.segment.generic.bounded.ColumnSegmentBoundedInfo;
44  import org.apache.shardingsphere.sql.parser.sql.common.segment.generic.table.SubqueryTableSegment;
45  import org.apache.shardingsphere.sql.parser.sql.common.statement.dml.SelectStatement;
46  import org.apache.shardingsphere.sql.parser.sql.common.value.identifier.IdentifierValue;
47  
48  import java.util.Collection;
49  import java.util.Collections;
50  import java.util.LinkedList;
51  import java.util.Map;
52  
53  /**
54   * Subquery table segment binder.
55   */
56  @NoArgsConstructor(access = AccessLevel.PRIVATE)
57  public final class SubqueryTableSegmentBinder {
58      
59      /**
60       * Bind subquery table segment with metadata.
61       *
62       * @param segment join table segment
63       * @param statementBinderContext statement binder context
64       * @param tableBinderContexts table binder contexts
65       * @param outerTableBinderContexts outer table binder contexts
66       * @return bounded subquery table segment
67       */
68      public static SubqueryTableSegment bind(final SubqueryTableSegment segment, final SQLStatementBinderContext statementBinderContext,
69                                              final Map<String, TableSegmentBinderContext> tableBinderContexts, final Map<String, TableSegmentBinderContext> outerTableBinderContexts) {
70          fillPivotColumnNamesInBinderContext(segment, statementBinderContext);
71          SelectStatement boundedSelect = new SelectStatementBinder().bindCorrelateSubquery(segment.getSubquery().getSelect(), statementBinderContext.getMetaData(),
72                  statementBinderContext.getDefaultDatabaseName(), outerTableBinderContexts, statementBinderContext.getExternalTableBinderContexts());
73          SubquerySegment boundedSubquerySegment = new SubquerySegment(segment.getSubquery().getStartIndex(), segment.getSubquery().getStopIndex(), boundedSelect, segment.getSubquery().getText());
74          boundedSubquerySegment.setSubqueryType(segment.getSubquery().getSubqueryType());
75          IdentifierValue subqueryTableName = segment.getAliasSegment().map(AliasSegment::getIdentifier).orElseGet(() -> new IdentifierValue(""));
76          bindParameterMarkerProjection(boundedSubquerySegment, subqueryTableName);
77          SubqueryTableSegment result = new SubqueryTableSegment(segment.getStartIndex(), segment.getStopIndex(), boundedSubquerySegment);
78          segment.getAliasSegment().ifPresent(result::setAlias);
79          tableBinderContexts.put(subqueryTableName.getValue().toLowerCase(),
80                  new SimpleTableSegmentBinderContext(createSubqueryProjections(boundedSelect.getProjections().getProjections(), subqueryTableName, statementBinderContext.getDatabaseType())));
81          return result;
82      }
83      
84      private static void bindParameterMarkerProjection(final SubquerySegment boundedSubquerySegment, final IdentifierValue subqueryTableName) {
85          SelectStatement boundedSelect = boundedSubquerySegment.getSelect();
86          Collection<ProjectionSegment> projections = new LinkedList<>(boundedSelect.getProjections().getProjections());
87          boundedSelect.getProjections().getProjections().clear();
88          for (ProjectionSegment each : projections) {
89              if (!(each instanceof ParameterMarkerExpressionSegment)) {
90                  boundedSelect.getProjections().getProjections().add(each);
91                  continue;
92              }
93              ParameterMarkerExpressionSegment parameterMarkerProjection = (ParameterMarkerExpressionSegment) each;
94              // TODO add database and schema in ColumnSegmentBoundedInfo
95              boundedSelect.getProjections().getProjections().add(ParameterMarkerExpressionSegmentBinder.bind(parameterMarkerProjection, Collections.singletonMap(parameterMarkerProjection,
96                      new ColumnSegmentBoundedInfo(new IdentifierValue(""), new IdentifierValue(""), subqueryTableName, parameterMarkerProjection.getAlias().orElseGet(() -> new IdentifierValue(""))))));
97          }
98      }
99      
100     private static void fillPivotColumnNamesInBinderContext(final SubqueryTableSegment segment, final SQLStatementBinderContext statementBinderContext) {
101         segment.getPivot().ifPresent(optional -> optional.getPivotColumns().forEach(each -> statementBinderContext.getPivotColumnNames().add(each.getIdentifier().getValue().toLowerCase())));
102     }
103     
104     private static Collection<ProjectionSegment> createSubqueryProjections(final Collection<ProjectionSegment> projections, final IdentifierValue subqueryTableName, final DatabaseType databaseType) {
105         Collection<ProjectionSegment> result = new LinkedList<>();
106         for (ProjectionSegment each : projections) {
107             if (each instanceof ColumnProjectionSegment) {
108                 result.add(createColumnProjection((ColumnProjectionSegment) each, subqueryTableName));
109             } else if (each instanceof ShorthandProjectionSegment) {
110                 result.addAll(createSubqueryProjections(((ShorthandProjectionSegment) each).getActualProjectionSegments(), subqueryTableName, databaseType));
111             } else if (each instanceof ExpressionProjectionSegment) {
112                 result.add(createColumnProjection((ExpressionProjectionSegment) each, subqueryTableName, databaseType));
113             } else if (each instanceof AggregationProjectionSegment) {
114                 result.add(createColumnProjection((AggregationProjectionSegment) each, subqueryTableName, databaseType));
115             } else {
116                 result.add(each);
117             }
118         }
119         return result;
120     }
121     
122     private static ColumnProjectionSegment createColumnProjection(final ColumnProjectionSegment originalColumn, final IdentifierValue subqueryTableName) {
123         ColumnSegment newColumnSegment = new ColumnSegment(0, 0, originalColumn.getAlias().orElseGet(() -> originalColumn.getColumn().getIdentifier()));
124         if (!Strings.isNullOrEmpty(subqueryTableName.getValue())) {
125             newColumnSegment.setOwner(new OwnerSegment(0, 0, subqueryTableName));
126         }
127         newColumnSegment.setColumnBoundedInfo(
128                 new ColumnSegmentBoundedInfo(originalColumn.getColumn().getColumnBoundedInfo().getOriginalDatabase(), originalColumn.getColumn().getColumnBoundedInfo().getOriginalSchema(),
129                         originalColumn.getColumn().getColumnBoundedInfo().getOriginalTable(), originalColumn.getColumn().getColumnBoundedInfo().getOriginalColumn()));
130         ColumnProjectionSegment result = new ColumnProjectionSegment(newColumnSegment);
131         result.setVisible(originalColumn.isVisible());
132         return result;
133     }
134     
135     private static ColumnProjectionSegment createColumnProjection(final ExpressionSegment expressionSegment, final IdentifierValue subqueryTableName, final DatabaseType databaseType) {
136         ColumnSegment newColumnSegment = new ColumnSegment(0, 0,
137                 new IdentifierValue(getColumnNameFromExpression(expressionSegment, databaseType), new DatabaseTypeRegistry(databaseType).getDialectDatabaseMetaData().getQuoteCharacter()));
138         if (!Strings.isNullOrEmpty(subqueryTableName.getValue())) {
139             newColumnSegment.setOwner(new OwnerSegment(0, 0, subqueryTableName));
140         }
141         ColumnProjectionSegment result = new ColumnProjectionSegment(newColumnSegment);
142         result.setVisible(true);
143         return result;
144     }
145     
146     private static String getColumnNameFromExpression(final ExpressionSegment expressionSegment, final DatabaseType databaseType) {
147         String result;
148         if (expressionSegment instanceof AliasAvailable && ((AliasAvailable) expressionSegment).getAlias().isPresent()) {
149             result = ProjectionUtils.getColumnLabelFromAlias(((AliasAvailable) expressionSegment).getAlias().get(), databaseType);
150         } else {
151             result = ProjectionUtils.getColumnNameFromExpression(expressionSegment.getText(), databaseType);
152         }
153         return result;
154     }
155 }