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.encrypt.rewrite.token.generator.projection;
19  
20  import lombok.RequiredArgsConstructor;
21  import org.apache.shardingsphere.encrypt.enums.EncryptDerivedColumnSuffix;
22  import org.apache.shardingsphere.encrypt.rule.EncryptRule;
23  import org.apache.shardingsphere.encrypt.rule.column.EncryptColumn;
24  import org.apache.shardingsphere.encrypt.rule.column.item.AssistedQueryColumnItem;
25  import org.apache.shardingsphere.encrypt.rule.column.item.LikeQueryColumnItem;
26  import org.apache.shardingsphere.encrypt.rule.table.EncryptTable;
27  import org.apache.shardingsphere.infra.annotation.HighFrequencyInvocation;
28  import org.apache.shardingsphere.infra.binder.context.segment.select.projection.DerivedColumn;
29  import org.apache.shardingsphere.infra.binder.context.segment.select.projection.Projection;
30  import org.apache.shardingsphere.infra.binder.context.segment.select.projection.ProjectionsContext;
31  import org.apache.shardingsphere.infra.binder.context.segment.select.projection.impl.ColumnProjection;
32  import org.apache.shardingsphere.infra.binder.context.segment.select.projection.impl.ShorthandProjection;
33  import org.apache.shardingsphere.infra.binder.context.statement.type.dml.SelectStatementContext;
34  import org.apache.shardingsphere.database.connector.core.metadata.database.enums.QuoteCharacter;
35  import org.apache.shardingsphere.database.connector.core.metadata.database.metadata.DialectDatabaseMetaData;
36  import org.apache.shardingsphere.database.connector.core.type.DatabaseType;
37  import org.apache.shardingsphere.database.connector.core.type.DatabaseTypeRegistry;
38  import org.apache.shardingsphere.infra.exception.generic.UnsupportedSQLOperationException;
39  import org.apache.shardingsphere.infra.rewrite.sql.token.common.pojo.SQLToken;
40  import org.apache.shardingsphere.infra.rewrite.sql.token.common.pojo.generic.SubstitutableColumnNameToken;
41  import org.apache.shardingsphere.sql.parser.statement.core.enums.SubqueryType;
42  import org.apache.shardingsphere.sql.parser.statement.core.enums.TableSourceType;
43  import org.apache.shardingsphere.sql.parser.statement.core.segment.dml.item.ColumnProjectionSegment;
44  import org.apache.shardingsphere.sql.parser.statement.core.segment.dml.item.ProjectionSegment;
45  import org.apache.shardingsphere.sql.parser.statement.core.segment.dml.item.ShorthandProjectionSegment;
46  import org.apache.shardingsphere.sql.parser.statement.core.segment.generic.OwnerSegment;
47  import org.apache.shardingsphere.sql.parser.statement.core.segment.generic.ParenthesesSegment;
48  import org.apache.shardingsphere.sql.parser.statement.core.value.identifier.IdentifierValue;
49  
50  import java.util.Collection;
51  import java.util.Collections;
52  import java.util.LinkedList;
53  import java.util.List;
54  import java.util.Optional;
55  
56  /**
57   * Projection token generator for encrypt.
58   */
59  @HighFrequencyInvocation
60  @RequiredArgsConstructor
61  public final class EncryptProjectionTokenGenerator {
62      
63      private final List<SQLToken> previousSQLTokens;
64      
65      private final DatabaseType databaseType;
66      
67      private final EncryptRule rule;
68      
69      private final DialectDatabaseMetaData dialectDatabaseMetaData;
70      
71      public EncryptProjectionTokenGenerator(final List<SQLToken> previousSQLTokens, final DatabaseType databaseType, final EncryptRule rule) {
72          this.previousSQLTokens = previousSQLTokens;
73          this.databaseType = databaseType;
74          this.rule = rule;
75          dialectDatabaseMetaData = new DatabaseTypeRegistry(databaseType).getDialectDatabaseMetaData();
76      }
77      
78      /**
79       * Generate SQL tokens.
80       *
81       * @param selectStatementContext select statement context
82       * @return generated SQL tokens
83       */
84      public Collection<SQLToken> generateSQLTokens(final SelectStatementContext selectStatementContext) {
85          Collection<SQLToken> result = new LinkedList<>();
86          selectStatementContext.getSubqueryContexts().values().forEach(each -> result.addAll(generateSQLTokens(each)));
87          result.addAll(generateSelectSQLTokens(selectStatementContext));
88          return result;
89      }
90      
91      private Collection<SQLToken> generateSelectSQLTokens(final SelectStatementContext selectStatementContext) {
92          Collection<SQLToken> result = new LinkedList<>();
93          for (ProjectionSegment each : selectStatementContext.getSqlStatement().getProjections().getProjections()) {
94              if (each instanceof ColumnProjectionSegment) {
95                  generateSQLToken(selectStatementContext, (ColumnProjectionSegment) each).ifPresent(result::add);
96              }
97              if (each instanceof ShorthandProjectionSegment) {
98                  ShorthandProjectionSegment shorthandSegment = (ShorthandProjectionSegment) each;
99                  Collection<Projection> actualColumns = getShorthandProjection(shorthandSegment, selectStatementContext.getProjectionsContext()).getActualColumns();
100                 if (!actualColumns.isEmpty()) {
101                     result.add(generateSQLToken(shorthandSegment, actualColumns, selectStatementContext.getSqlStatement().getDatabaseType(), selectStatementContext.getSubqueryType()));
102                 }
103             }
104         }
105         return result;
106     }
107     
108     private Optional<SubstitutableColumnNameToken> generateSQLToken(final SelectStatementContext selectStatementContext, final ColumnProjectionSegment columnSegment) {
109         ColumnProjection columnProjection = buildColumnProjection(columnSegment);
110         String columnName = columnProjection.getOriginalColumn().getValue();
111         Optional<EncryptTable> encryptTable = rule.findEncryptTable(columnProjection.getOriginalTable().getValue());
112         if (encryptTable.isPresent() && encryptTable.get().isEncryptColumn(columnName)) {
113             EncryptColumn encryptColumn = encryptTable.get().getEncryptColumn(columnName);
114             Collection<Projection> projections = generateProjections(encryptColumn, columnProjection, selectStatementContext.getSubqueryType());
115             int startIndex = getStartIndex(columnSegment);
116             int stopIndex = getStopIndex(columnSegment);
117             previousSQLTokens.removeIf(each -> each.getStartIndex() == startIndex);
118             return Optional.of(new SubstitutableColumnNameToken(startIndex, stopIndex, projections, databaseType));
119         }
120         return Optional.empty();
121     }
122     
123     private SubstitutableColumnNameToken generateSQLToken(final ShorthandProjectionSegment segment, final Collection<Projection> actualColumns, final DatabaseType databaseType,
124                                                           final SubqueryType subqueryType) {
125         Collection<Projection> projections = new LinkedList<>();
126         for (Projection each : actualColumns) {
127             if (each instanceof ColumnProjection) {
128                 ColumnProjection columnProjection = (ColumnProjection) each;
129                 Optional<EncryptTable> encryptTable = rule.findEncryptTable(columnProjection.getOriginalTable().getValue());
130                 if (encryptTable.isPresent() && encryptTable.get().isEncryptColumn(columnProjection.getOriginalColumn().getValue())) {
131                     EncryptColumn encryptColumn = encryptTable.get().getEncryptColumn(columnProjection.getOriginalColumn().getValue());
132                     projections.addAll(generateProjections(encryptColumn, columnProjection, subqueryType));
133                     continue;
134                 }
135             }
136             projections.add(each.getAlias().filter(alias -> !DerivedColumn.isDerivedColumnName(alias.getValue()))
137                     .map(optional -> (Projection) new ColumnProjection(null, optional, null, databaseType)).orElse(each));
138         }
139         int startIndex = segment.getOwner().isPresent() ? segment.getOwner().get().getStartIndex() : segment.getStartIndex();
140         previousSQLTokens.removeIf(each -> each.getStartIndex() == startIndex);
141         return new SubstitutableColumnNameToken(startIndex, segment.getStopIndex(), projections, databaseType);
142     }
143     
144     private int getStartIndex(final ColumnProjectionSegment columnSegment) {
145         if (columnSegment.getColumn().getLeftParentheses().isPresent()) {
146             return columnSegment.getColumn().getLeftParentheses().get().getStartIndex();
147         }
148         return columnSegment.getColumn().getOwner().isPresent() ? columnSegment.getColumn().getOwner().get().getStartIndex() : columnSegment.getColumn().getStartIndex();
149     }
150     
151     private int getStopIndex(final ColumnProjectionSegment columnSegment) {
152         if (columnSegment.getAliasSegment().isPresent()) {
153             return columnSegment.getAliasSegment().get().getStopIndex();
154         }
155         return columnSegment.getColumn().getRightParentheses().isPresent() ? columnSegment.getColumn().getRightParentheses().get().getStopIndex() : columnSegment.getColumn().getStopIndex();
156     }
157     
158     private ColumnProjection buildColumnProjection(final ColumnProjectionSegment segment) {
159         IdentifierValue owner = segment.getColumn().getOwner().map(OwnerSegment::getIdentifier).orElse(null);
160         return new ColumnProjection(owner, segment.getColumn().getIdentifier(), segment.getAliasName().isPresent() ? segment.getAlias().orElse(null) : null, databaseType,
161                 segment.getColumn().getLeftParentheses().orElse(null), segment.getColumn().getRightParentheses().orElse(null), segment.getColumn().getColumnBoundInfo());
162     }
163     
164     private Collection<Projection> generateProjections(final EncryptColumn encryptColumn, final ColumnProjection columnProjection, final SubqueryType subqueryType) {
165         if (null == subqueryType || SubqueryType.PROJECTION == subqueryType) {
166             return Collections.singleton(generateProjection(encryptColumn, columnProjection));
167         }
168         if (SubqueryType.TABLE == subqueryType || SubqueryType.JOIN == subqueryType || SubqueryType.WITH == subqueryType) {
169             return generateProjectionsInTableSegmentSubquery(encryptColumn, columnProjection);
170         }
171         if (SubqueryType.PREDICATE == subqueryType) {
172             return Collections.singleton(generateProjectionInPredicateSubquery(encryptColumn, columnProjection));
173         }
174         if (SubqueryType.INSERT_SELECT == subqueryType || SubqueryType.VIEW_DEFINITION == subqueryType) {
175             return generateProjectionsInInsertSelectSubquery(encryptColumn, columnProjection);
176         }
177         throw new UnsupportedSQLOperationException(
178                 "Projections not in simple select, table subquery, join subquery, predicate subquery and insert select subquery are not supported in encrypt feature.");
179     }
180     
181     private ColumnProjection generateProjection(final EncryptColumn encryptColumn, final ColumnProjection columnProjection) {
182         String encryptColumnName = getEncryptColumnName(columnProjection, encryptColumn);
183         QuoteCharacter quoteCharacter = getQuoteCharacter(columnProjection);
184         IdentifierValue cipherColumnName = new IdentifierValue(encryptColumnName, quoteCharacter);
185         IdentifierValue cipherColumnAlias = columnProjection.getAlias().orElse(columnProjection.getName());
186         return new ColumnProjection(columnProjection.getOwner().orElse(null), cipherColumnName, cipherColumnAlias,
187                 databaseType, columnProjection.getLeftParentheses().orElse(null), columnProjection.getRightParentheses().orElse(null));
188     }
189     
190     private QuoteCharacter getQuoteCharacter(final ColumnProjection columnProjection) {
191         return TableSourceType.PHYSICAL_TABLE == columnProjection.getColumnBoundInfo().getTableSourceType()
192                 ? dialectDatabaseMetaData.getQuoteCharacter()
193                 : columnProjection.getName().getQuoteCharacter();
194     }
195     
196     private String getEncryptColumnName(final ColumnProjection columnProjection, final EncryptColumn encryptColumn) {
197         IdentifierValue columnName = columnProjection.getName();
198         return TableSourceType.TEMPORARY_TABLE == columnProjection.getColumnBoundInfo().getTableSourceType()
199                 ? EncryptDerivedColumnSuffix.CIPHER.getDerivedColumnName(columnName.getValue(), databaseType)
200                 : encryptColumn.getCipher().getName();
201     }
202     
203     private Collection<Projection> generateProjectionsInTableSegmentSubquery(final EncryptColumn encryptColumn, final ColumnProjection columnProjection) {
204         return generateCipherProjectionsInTableSegmentSubquery(encryptColumn, columnProjection);
205     }
206     
207     private Collection<Projection> generateCipherProjectionsInTableSegmentSubquery(final EncryptColumn encryptColumn, final ColumnProjection columnProjection) {
208         Collection<Projection> result = new LinkedList<>();
209         IdentifierValue cipherColumnName = TableSourceType.TEMPORARY_TABLE == columnProjection.getColumnBoundInfo().getTableSourceType()
210                 ? new IdentifierValue(EncryptDerivedColumnSuffix.CIPHER.getDerivedColumnName(columnProjection.getName().getValue(), databaseType),
211                         columnProjection.getName().getQuoteCharacter())
212                 : new IdentifierValue(encryptColumn.getCipher().getName(), columnProjection.getName().getQuoteCharacter());
213         IdentifierValue columnAlias = columnProjection.getAlias().orElse(columnProjection.getName());
214         IdentifierValue cipherColumnAlias = getEncryptColumnAliasInTableSegmentSubquery(columnProjection, columnAlias, EncryptDerivedColumnSuffix.CIPHER);
215         result.add(new ColumnProjection(columnProjection.getOwner().orElse(null), cipherColumnName, cipherColumnAlias, databaseType));
216         encryptColumn.getAssistedQuery().ifPresent(optional -> addAssistedQueryColumn(columnProjection, optional, columnAlias, result));
217         encryptColumn.getLikeQuery().ifPresent(optional -> addLikeQueryColumn(columnProjection, optional, columnAlias, result));
218         return result;
219     }
220     
221     private IdentifierValue getEncryptColumnAliasInTableSegmentSubquery(final ColumnProjection columnProjection, final IdentifierValue columnAlias, final EncryptDerivedColumnSuffix suffix) {
222         if (TableSourceType.TEMPORARY_TABLE == columnProjection.getColumnBoundInfo().getTableSourceType()) {
223             return columnProjection.getAlias().map(optional -> new IdentifierValue(suffix.getDerivedColumnName(optional.getValue(), databaseType), optional.getQuoteCharacter())).orElse(null);
224         }
225         return new IdentifierValue(suffix.getDerivedColumnName(columnAlias.getValue(), databaseType), columnAlias.getQuoteCharacter());
226     }
227     
228     private void addAssistedQueryColumn(final ColumnProjection columnProjection, final AssistedQueryColumnItem assistedQueryColumnItem, final IdentifierValue columnAlias,
229                                         final Collection<Projection> result) {
230         IdentifierValue assistedQueryName = TableSourceType.TEMPORARY_TABLE == columnProjection.getColumnBoundInfo().getTableSourceType()
231                 ? new IdentifierValue(EncryptDerivedColumnSuffix.ASSISTED_QUERY.getDerivedColumnName(columnProjection.getName().getValue(), databaseType),
232                         columnProjection.getName().getQuoteCharacter())
233                 : new IdentifierValue(assistedQueryColumnItem.getName(), columnProjection.getName().getQuoteCharacter());
234         IdentifierValue assistedQueryAlias = getEncryptColumnAliasInTableSegmentSubquery(columnProjection, columnAlias, EncryptDerivedColumnSuffix.ASSISTED_QUERY);
235         result.add(new ColumnProjection(columnProjection.getOwner().orElse(null), assistedQueryName, assistedQueryAlias, databaseType, columnProjection.getLeftParentheses().orElse(null),
236                 columnProjection.getRightParentheses().orElse(null)));
237     }
238     
239     private void addLikeQueryColumn(final ColumnProjection columnProjection, final LikeQueryColumnItem likeQueryColumnItem, final IdentifierValue columnAlias, final Collection<Projection> result) {
240         IdentifierValue likeQueryName = TableSourceType.TEMPORARY_TABLE == columnProjection.getColumnBoundInfo().getTableSourceType()
241                 ? new IdentifierValue(EncryptDerivedColumnSuffix.LIKE_QUERY.getDerivedColumnName(columnProjection.getName().getValue(), databaseType),
242                         columnProjection.getName().getQuoteCharacter())
243                 : new IdentifierValue(likeQueryColumnItem.getName(), columnProjection.getName().getQuoteCharacter());
244         IdentifierValue likeQueryAlias = getEncryptColumnAliasInTableSegmentSubquery(columnProjection, columnAlias, EncryptDerivedColumnSuffix.LIKE_QUERY);
245         result.add(new ColumnProjection(columnProjection.getOwner().orElse(null), likeQueryName, likeQueryAlias, databaseType, columnProjection.getLeftParentheses().orElse(null),
246                 columnProjection.getRightParentheses().orElse(null)));
247     }
248     
249     private ColumnProjection generateProjectionInPredicateSubquery(final EncryptColumn encryptColumn, final ColumnProjection columnProjection) {
250         QuoteCharacter quoteCharacter = columnProjection.getName().getQuoteCharacter();
251         ParenthesesSegment leftParentheses = columnProjection.getLeftParentheses().orElse(null);
252         ParenthesesSegment rightParentheses = columnProjection.getRightParentheses().orElse(null);
253         IdentifierValue owner = columnProjection.getOwner().orElse(null);
254         return encryptColumn.getAssistedQuery()
255                 .map(optional -> new ColumnProjection(owner, new IdentifierValue(optional.getName(), quoteCharacter), null, databaseType, leftParentheses, rightParentheses))
256                 .orElseGet(() -> new ColumnProjection(owner, new IdentifierValue(encryptColumn.getCipher().getName(), quoteCharacter), columnProjection.getAlias().orElse(columnProjection.getName()),
257                         databaseType, leftParentheses, rightParentheses));
258     }
259     
260     private Collection<Projection> generateProjectionsInInsertSelectSubquery(final EncryptColumn encryptColumn, final ColumnProjection columnProjection) {
261         QuoteCharacter quoteCharacter = columnProjection.getName().getQuoteCharacter();
262         IdentifierValue columnName = new IdentifierValue(encryptColumn.getCipher().getName(), quoteCharacter);
263         Collection<Projection> result = new LinkedList<>();
264         ParenthesesSegment leftParentheses = columnProjection.getLeftParentheses().orElse(null);
265         ParenthesesSegment rightParentheses = columnProjection.getRightParentheses().orElse(null);
266         result.add(new ColumnProjection(columnProjection.getOwner().orElse(null), columnName, null, databaseType, leftParentheses, rightParentheses));
267         IdentifierValue columOwner = columnProjection.getOwner().orElse(null);
268         encryptColumn.getAssistedQuery()
269                 .ifPresent(optional -> result.add(new ColumnProjection(columOwner, new IdentifierValue(optional.getName(), quoteCharacter), null, databaseType, leftParentheses, rightParentheses)));
270         encryptColumn.getLikeQuery()
271                 .ifPresent(optional -> result.add(new ColumnProjection(columOwner, new IdentifierValue(optional.getName(), quoteCharacter), null, databaseType, leftParentheses, rightParentheses)));
272         return result;
273     }
274     
275     private ShorthandProjection getShorthandProjection(final ShorthandProjectionSegment segment, final ProjectionsContext projectionsContext) {
276         Optional<String> owner = segment.getOwner().isPresent() ? Optional.of(segment.getOwner().get().getIdentifier().getValue()) : Optional.empty();
277         for (Projection each : projectionsContext.getProjections()) {
278             if (each instanceof ShorthandProjection) {
279                 if (!owner.isPresent() && !((ShorthandProjection) each).getOwner().isPresent()) {
280                     return (ShorthandProjection) each;
281                 }
282                 if (owner.isPresent() && owner.get().equals(((ShorthandProjection) each).getOwner().map(IdentifierValue::getValue).orElse(null))) {
283                     return (ShorthandProjection) each;
284                 }
285             }
286         }
287         throw new IllegalStateException(String.format("Can not find shorthand projection segment, owner is `%s`", owner.orElse(null)));
288     }
289 }