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.sharding.route.engine.type.unicast;
19  
20  import com.google.common.collect.Sets;
21  import lombok.RequiredArgsConstructor;
22  import org.apache.shardingsphere.infra.binder.context.statement.SQLStatementContext;
23  import org.apache.shardingsphere.infra.binder.context.statement.ddl.AlterViewStatementContext;
24  import org.apache.shardingsphere.infra.binder.context.statement.ddl.CreateViewStatementContext;
25  import org.apache.shardingsphere.infra.binder.context.statement.ddl.DropViewStatementContext;
26  import org.apache.shardingsphere.infra.binder.context.type.CursorAvailable;
27  import org.apache.shardingsphere.infra.session.connection.ConnectionContext;
28  import org.apache.shardingsphere.infra.datanode.DataNode;
29  import org.apache.shardingsphere.infra.route.context.RouteContext;
30  import org.apache.shardingsphere.infra.route.context.RouteMapper;
31  import org.apache.shardingsphere.infra.route.context.RouteUnit;
32  import org.apache.shardingsphere.sharding.exception.syntax.DataSourceIntersectionNotFoundException;
33  import org.apache.shardingsphere.sharding.route.engine.type.ShardingRouteEngine;
34  import org.apache.shardingsphere.sharding.rule.ShardingRule;
35  import org.apache.shardingsphere.sharding.rule.ShardingTable;
36  
37  import java.util.ArrayList;
38  import java.util.Collection;
39  import java.util.Collections;
40  import java.util.LinkedHashSet;
41  import java.util.List;
42  import java.util.Set;
43  import java.util.concurrent.ThreadLocalRandom;
44  import java.util.stream.Collectors;
45  
46  /**
47   * Sharding unicast routing engine.
48   */
49  @RequiredArgsConstructor
50  public final class ShardingUnicastRoutingEngine implements ShardingRouteEngine {
51      
52      private final SQLStatementContext sqlStatementContext;
53      
54      private final Collection<String> logicTables;
55      
56      private final ConnectionContext connectionContext;
57      
58      @Override
59      public RouteContext route(final ShardingRule shardingRule) {
60          RouteContext result = new RouteContext();
61          String dataSourceName = getDataSourceName(shardingRule.getDataSourceNames());
62          RouteMapper dataSourceMapper = new RouteMapper(dataSourceName, dataSourceName);
63          if (logicTables.isEmpty()) {
64              result.getRouteUnits().add(new RouteUnit(dataSourceMapper, Collections.emptyList()));
65          } else if (1 == logicTables.size()) {
66              String logicTableName = logicTables.iterator().next();
67              if (!shardingRule.findShardingTable(logicTableName).isPresent()) {
68                  result.getRouteUnits().add(new RouteUnit(dataSourceMapper, Collections.emptyList()));
69                  return result;
70              }
71              DataNode dataNode = shardingRule.getDataNode(logicTableName);
72              result.getRouteUnits().add(new RouteUnit(new RouteMapper(dataNode.getDataSourceName(), dataNode.getDataSourceName()),
73                      Collections.singletonList(new RouteMapper(logicTableName, dataNode.getTableName()))));
74          } else {
75              routeWithMultipleTables(result, shardingRule);
76          }
77          return result;
78      }
79      
80      private String getDataSourceName(final Collection<String> dataSourceNames) {
81          return sqlStatementContext instanceof CursorAvailable || isViewStatementContext(sqlStatementContext) ? dataSourceNames.iterator().next() : getRandomDataSourceName(dataSourceNames);
82      }
83      
84      private boolean isViewStatementContext(final SQLStatementContext sqlStatementContext) {
85          return sqlStatementContext instanceof CreateViewStatementContext || sqlStatementContext instanceof AlterViewStatementContext || sqlStatementContext instanceof DropViewStatementContext;
86      }
87      
88      private void routeWithMultipleTables(final RouteContext routeContext, final ShardingRule shardingRule) {
89          List<RouteMapper> tableMappers = new ArrayList<>(logicTables.size());
90          Set<String> availableDataSourceNames = Collections.emptySet();
91          boolean first = true;
92          for (String each : logicTables) {
93              ShardingTable shardingTable = shardingRule.getShardingTable(each);
94              DataNode dataNode = shardingTable.getActualDataNodes().get(0);
95              tableMappers.add(new RouteMapper(each, dataNode.getTableName()));
96              Set<String> currentDataSourceNames = shardingTable.getActualDataNodes().stream().map(DataNode::getDataSourceName).collect(
97                      Collectors.toCollection(() -> new LinkedHashSet<>(shardingTable.getActualDataSourceNames().size(), 1F)));
98              if (first) {
99                  availableDataSourceNames = currentDataSourceNames;
100                 first = false;
101             } else {
102                 availableDataSourceNames = Sets.intersection(availableDataSourceNames, currentDataSourceNames);
103             }
104         }
105         if (availableDataSourceNames.isEmpty()) {
106             throw new DataSourceIntersectionNotFoundException(logicTables);
107         }
108         String dataSourceName = getDataSourceName(availableDataSourceNames);
109         routeContext.getRouteUnits().add(new RouteUnit(new RouteMapper(dataSourceName, dataSourceName), tableMappers));
110     }
111     
112     private String getRandomDataSourceName(final Collection<String> dataSourceNames) {
113         Collection<String> usedDataSourceNames = connectionContext.getUsedDataSourceNames();
114         List<String> availableDataSourceNames = new ArrayList<>(usedDataSourceNames.isEmpty() ? dataSourceNames : usedDataSourceNames);
115         return availableDataSourceNames.get(ThreadLocalRandom.current().nextInt(availableDataSourceNames.size()));
116     }
117 }