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.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   * Tables context.
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      * Find expression table name map by column segment.
117      *
118      * @param columns column segment collection
119      * @param schema schema meta data
120      * @return expression table name map
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      * Get database name.
221      *
222      * @return database name
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      * Get schema name.
231      *
232      * @return schema name
233      */
234     public Optional<String> getSchemaName() {
235         return schemaNames.isEmpty() ? Optional.empty() : Optional.of(schemaNames.iterator().next());
236     }
237 }