1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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
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
80
81
82
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 }