1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 package org.apache.shardingsphere.infra.binder.context.segment.table;
19
20 import com.cedarsoftware.util.CaseInsensitiveMap;
21 import com.cedarsoftware.util.CaseInsensitiveSet;
22 import com.google.common.base.Preconditions;
23 import lombok.Getter;
24 import lombok.ToString;
25 import org.apache.shardingsphere.infra.binder.context.segment.select.subquery.SubqueryTableContext;
26 import org.apache.shardingsphere.infra.binder.context.segment.select.subquery.engine.SubqueryTableContextEngine;
27 import org.apache.shardingsphere.infra.binder.context.statement.dml.SelectStatementContext;
28 import org.apache.shardingsphere.infra.database.core.metadata.database.DialectDatabaseMetaData;
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.infra.metadata.database.schema.model.ShardingSphereSchema;
32 import org.apache.shardingsphere.sql.parser.sql.common.segment.dml.column.ColumnSegment;
33 import org.apache.shardingsphere.sql.parser.sql.common.segment.generic.OwnerSegment;
34 import org.apache.shardingsphere.sql.parser.sql.common.segment.generic.table.SimpleTableSegment;
35 import org.apache.shardingsphere.sql.parser.sql.common.segment.generic.table.SubqueryTableSegment;
36 import org.apache.shardingsphere.sql.parser.sql.common.segment.generic.table.TableSegment;
37 import org.apache.shardingsphere.sql.parser.sql.common.value.identifier.IdentifierValue;
38
39 import java.util.Collection;
40 import java.util.Collections;
41 import java.util.HashMap;
42 import java.util.LinkedHashMap;
43 import java.util.LinkedList;
44 import java.util.Map;
45 import java.util.Optional;
46 import java.util.TreeSet;
47
48
49
50
51 @Getter
52 @ToString
53 public final class TablesContext {
54
55 private final Collection<TableSegment> tableSegments = new LinkedList<>();
56
57 private final Collection<SimpleTableSegment> simpleTableSegments = new LinkedList<>();
58
59 private final Collection<String> tableNames = new CaseInsensitiveSet<>();
60
61 private final Collection<String> schemaNames = new CaseInsensitiveSet<>();
62
63 private final Collection<String> databaseNames = new CaseInsensitiveSet<>();
64
65 private final Map<String, Collection<SubqueryTableContext>> subqueryTables = new HashMap<>();
66
67 private final Map<String, IdentifierValue> tableNameAliasMap = new HashMap<>();
68
69 public TablesContext(final SimpleTableSegment tableSegment, final DatabaseType databaseType) {
70 this(Collections.singletonList(tableSegment), databaseType);
71 }
72
73 public TablesContext(final Collection<SimpleTableSegment> tableSegments, final DatabaseType databaseType) {
74 this(tableSegments, Collections.emptyMap(), databaseType);
75 }
76
77 public TablesContext(final Collection<? extends TableSegment> tableSegments, final Map<Integer, SelectStatementContext> subqueryContexts, final DatabaseType databaseType) {
78 if (tableSegments.isEmpty()) {
79 return;
80 }
81 this.tableSegments.addAll(tableSegments);
82 for (TableSegment each : tableSegments) {
83 if (each instanceof SimpleTableSegment) {
84 SimpleTableSegment simpleTableSegment = (SimpleTableSegment) each;
85 simpleTableSegments.add(simpleTableSegment);
86 tableNames.add(simpleTableSegment.getTableName().getIdentifier().getValue());
87 simpleTableSegment.getOwner().ifPresent(optional -> schemaNames.add(optional.getIdentifier().getValue()));
88 findDatabaseName(simpleTableSegment, databaseType).ifPresent(databaseNames::add);
89 tableNameAliasMap.put(simpleTableSegment.getTableName().getIdentifier().getValue().toLowerCase(), each.getAlias().orElse(simpleTableSegment.getTableName().getIdentifier()));
90 }
91 if (each instanceof SubqueryTableSegment) {
92 subqueryTables.putAll(createSubqueryTables(subqueryContexts, (SubqueryTableSegment) each));
93 }
94 }
95 }
96
97 private Optional<String> findDatabaseName(final SimpleTableSegment tableSegment, final DatabaseType databaseType) {
98 DialectDatabaseMetaData dialectDatabaseMetaData = new DatabaseTypeRegistry(databaseType).getDialectDatabaseMetaData();
99 Optional<OwnerSegment> owner = dialectDatabaseMetaData.getDefaultSchema().isPresent() ? tableSegment.getOwner().flatMap(OwnerSegment::getOwner) : tableSegment.getOwner();
100 return owner.map(optional -> optional.getIdentifier().getValue());
101 }
102
103 private Map<String, Collection<SubqueryTableContext>> createSubqueryTables(final Map<Integer, SelectStatementContext> subqueryContexts, final SubqueryTableSegment subqueryTable) {
104 SelectStatementContext subqueryContext = subqueryContexts.get(subqueryTable.getSubquery().getStartIndex());
105 Map<String, SubqueryTableContext> subqueryTableContexts = new SubqueryTableContextEngine().createSubqueryTableContexts(subqueryContext, subqueryTable.getAliasName().orElse(null));
106 Map<String, Collection<SubqueryTableContext>> result = new HashMap<>(subqueryTableContexts.size(), 1F);
107 for (SubqueryTableContext each : subqueryTableContexts.values()) {
108 if (null != each.getAliasName()) {
109 result.computeIfAbsent(each.getAliasName(), unused -> new LinkedList<>()).add(each);
110 }
111 }
112 return result;
113 }
114
115
116
117
118
119
120
121
122 public Map<String, String> findTableNamesByColumnSegment(final Collection<ColumnSegment> columns, final ShardingSphereSchema schema) {
123 if (1 == simpleTableSegments.size()) {
124 return findTableNameFromSingleTableByColumnSegment(columns);
125 }
126 Map<String, String> result = new CaseInsensitiveMap<>();
127 Map<String, Collection<String>> ownerColumnNames = getOwnerColumnNamesByColumnSegment(columns);
128 result.putAll(findTableNameFromSQL(ownerColumnNames));
129 Collection<String> noOwnerColumnNames = getNoOwnerColumnNamesByColumnSegment(columns);
130 result.putAll(findTableNameFromMetaData(noOwnerColumnNames, schema));
131 result.putAll(findTableNameFromSubqueryByColumnSegment(columns, result));
132 return result;
133 }
134
135 private Map<String, String> findTableNameFromSubqueryByColumnSegment(final Collection<ColumnSegment> columns, final Map<String, String> ownerTableNames) {
136 if (ownerTableNames.size() == columns.size() || subqueryTables.isEmpty()) {
137 return Collections.emptyMap();
138 }
139 Map<String, String> result = new LinkedHashMap<>(columns.size(), 1F);
140 for (ColumnSegment each : columns) {
141 if (ownerTableNames.containsKey(each.getExpression())) {
142 continue;
143 }
144 String owner = each.getOwner().map(optional -> optional.getIdentifier().getValue()).orElse("");
145 Collection<SubqueryTableContext> subqueryTableContexts = subqueryTables.getOrDefault(owner, Collections.emptyList());
146 for (SubqueryTableContext subqueryTableContext : subqueryTableContexts) {
147 if (subqueryTableContext.getColumnNames().contains(each.getIdentifier().getValue())) {
148 result.put(each.getExpression(), subqueryTableContext.getTableName());
149 }
150 }
151 }
152 return result;
153 }
154
155 private Map<String, String> findTableNameFromSingleTableByColumnSegment(final Collection<ColumnSegment> columns) {
156 String tableName = simpleTableSegments.iterator().next().getTableName().getIdentifier().getValue();
157 Map<String, String> result = new CaseInsensitiveMap<>();
158 for (ColumnSegment each : columns) {
159 result.putIfAbsent(each.getExpression(), tableName);
160 }
161 return result;
162 }
163
164 private Map<String, Collection<String>> getOwnerColumnNamesByColumnSegment(final Collection<ColumnSegment> columns) {
165 Map<String, Collection<String>> result = new CaseInsensitiveMap<>();
166 for (ColumnSegment each : columns) {
167 if (!each.getOwner().isPresent()) {
168 continue;
169 }
170 result.computeIfAbsent(each.getOwner().get().getIdentifier().getValue(), unused -> new LinkedList<>()).add(each.getExpression());
171 }
172 return result;
173 }
174
175 private Map<String, String> findTableNameFromSQL(final Map<String, Collection<String>> ownerColumnNames) {
176 if (ownerColumnNames.isEmpty()) {
177 return Collections.emptyMap();
178 }
179 Map<String, String> result = new LinkedHashMap<>(simpleTableSegments.size(), 1F);
180 for (SimpleTableSegment each : simpleTableSegments) {
181 String tableName = each.getTableName().getIdentifier().getValue();
182 if (ownerColumnNames.containsKey(tableName)) {
183 ownerColumnNames.get(tableName).forEach(column -> result.put(column, tableName));
184 }
185 Optional<String> alias = each.getAliasName();
186 if (alias.isPresent() && ownerColumnNames.containsKey(alias.get())) {
187 ownerColumnNames.get(alias.get()).forEach(column -> result.put(column, tableName));
188 }
189 }
190 return result;
191 }
192
193 private Map<String, String> findTableNameFromMetaData(final Collection<String> noOwnerColumnNames, final ShardingSphereSchema schema) {
194 if (noOwnerColumnNames.isEmpty()) {
195 return Collections.emptyMap();
196 }
197 Map<String, String> result = new LinkedHashMap<>(noOwnerColumnNames.size(), 1F);
198 for (SimpleTableSegment each : simpleTableSegments) {
199 String tableName = each.getTableName().getIdentifier().getValue();
200 for (String columnName : schema.getAllColumnNames(tableName)) {
201 if (noOwnerColumnNames.contains(columnName)) {
202 result.put(columnName, tableName);
203 }
204 }
205 }
206 return result;
207 }
208
209 private Collection<String> getNoOwnerColumnNamesByColumnSegment(final Collection<ColumnSegment> columns) {
210 Collection<String> result = new TreeSet<>(String.CASE_INSENSITIVE_ORDER);
211 for (ColumnSegment each : columns) {
212 if (!each.getOwner().isPresent()) {
213 result.add(each.getIdentifier().getValue());
214 }
215 }
216 return result;
217 }
218
219
220
221
222
223
224 public Optional<String> getDatabaseName() {
225 Preconditions.checkState(databaseNames.size() <= 1, "Can not support multiple different database.");
226 return databaseNames.isEmpty() ? Optional.empty() : Optional.of(databaseNames.iterator().next());
227 }
228
229
230
231
232
233
234 public Optional<String> getSchemaName() {
235 return schemaNames.isEmpty() ? Optional.empty() : Optional.of(schemaNames.iterator().next());
236 }
237 }