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.rewrite.sql.token.pojo.generic;
19  
20  import com.cedarsoftware.util.CaseInsensitiveMap;
21  import lombok.EqualsAndHashCode;
22  import lombok.Getter;
23  import org.apache.shardingsphere.infra.binder.context.segment.select.projection.Projection;
24  import org.apache.shardingsphere.infra.binder.context.segment.select.projection.impl.ColumnProjection;
25  import org.apache.shardingsphere.infra.database.core.metadata.database.enums.QuoteCharacter;
26  import org.apache.shardingsphere.infra.database.core.type.DatabaseType;
27  import org.apache.shardingsphere.infra.database.core.type.DatabaseTypeRegistry;
28  import org.apache.shardingsphere.infra.rewrite.sql.token.pojo.RouteUnitAware;
29  import org.apache.shardingsphere.infra.rewrite.sql.token.pojo.SQLToken;
30  import org.apache.shardingsphere.infra.rewrite.sql.token.pojo.Substitutable;
31  import org.apache.shardingsphere.infra.route.context.RouteMapper;
32  import org.apache.shardingsphere.infra.route.context.RouteUnit;
33  import org.apache.shardingsphere.sql.parser.sql.common.value.identifier.IdentifierValue;
34  
35  import java.util.Collection;
36  import java.util.Collections;
37  import java.util.HashMap;
38  import java.util.Map;
39  
40  /**
41   * Substitutable column name token.
42   */
43  @EqualsAndHashCode(callSuper = false)
44  public final class SubstitutableColumnNameToken extends SQLToken implements Substitutable, RouteUnitAware {
45      
46      private static final String COLUMN_NAME_SPLITTER = ", ";
47      
48      @Getter
49      private final int stopIndex;
50      
51      private final Collection<Projection> projections;
52      
53      private final boolean lastColumn;
54      
55      private final QuoteCharacter quoteCharacter;
56      
57      public SubstitutableColumnNameToken(final int startIndex, final int stopIndex, final Collection<Projection> projections, final DatabaseType databaseType) {
58          super(startIndex);
59          this.stopIndex = stopIndex;
60          this.lastColumn = false;
61          this.quoteCharacter = new DatabaseTypeRegistry(databaseType).getDialectDatabaseMetaData().getQuoteCharacter();
62          this.projections = projections;
63      }
64      
65      @Override
66      public String toString(final RouteUnit routeUnit) {
67          Map<String, String> logicAndActualTables = new HashMap<>();
68          if (null != routeUnit) {
69              logicAndActualTables.putAll(getLogicAndActualTables(routeUnit));
70          }
71          StringBuilder result = new StringBuilder();
72          int count = 0;
73          for (Projection each : projections) {
74              if (0 == count && !lastColumn) {
75                  result.append(getColumnExpression(each, logicAndActualTables));
76              } else {
77                  result.append(COLUMN_NAME_SPLITTER).append(getColumnExpression(each, logicAndActualTables));
78              }
79              count++;
80          }
81          return result.toString();
82      }
83      
84      private Map<String, String> getLogicAndActualTables(final RouteUnit routeUnit) {
85          if (null == routeUnit) {
86              return Collections.emptyMap();
87          }
88          Map<String, String> result = new CaseInsensitiveMap<>();
89          for (RouteMapper each : routeUnit.getTableMappers()) {
90              result.put(each.getLogicName().toLowerCase(), each.getActualName());
91          }
92          return result;
93      }
94      
95      private String getColumnExpression(final Projection projection, final Map<String, String> logicActualTableNames) {
96          StringBuilder builder = new StringBuilder();
97          if (projection instanceof ColumnProjection) {
98              appendColumnProjection((ColumnProjection) projection, logicActualTableNames, builder);
99          } else {
100             builder.append(quoteCharacter.wrap(projection.getColumnLabel()));
101         }
102         return builder.toString();
103     }
104     
105     private void appendColumnProjection(final ColumnProjection columnProjection, final Map<String, String> logicActualTableNames, final StringBuilder builder) {
106         if (columnProjection.getOwner().isPresent()) {
107             IdentifierValue owner = columnProjection.getOwner().get();
108             String actualTableOwner = logicActualTableNames.getOrDefault(owner.getValue(), owner.getValue());
109             builder.append(getValueWithQuoteCharacters(new IdentifierValue(actualTableOwner, owner.getQuoteCharacter()))).append('.');
110         }
111         builder.append(getValueWithQuoteCharacters(columnProjection.getName()));
112         if (columnProjection.getAlias().isPresent()) {
113             builder.append(" AS ").append(getValueWithQuoteCharacters(columnProjection.getAlias().get()));
114         }
115     }
116     
117     private String getValueWithQuoteCharacters(final IdentifierValue identifierValue) {
118         return QuoteCharacter.NONE == identifierValue.getQuoteCharacter() ? identifierValue.getValue() : quoteCharacter.wrap(identifierValue.getValue());
119     }
120 }