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.condition;
19  
20  import lombok.Getter;
21  import lombok.ToString;
22  import org.apache.shardingsphere.infra.binder.context.statement.SQLStatementContext;
23  import org.apache.shardingsphere.infra.binder.context.statement.dml.InsertStatementContext;
24  import org.apache.shardingsphere.infra.binder.context.statement.dml.SelectStatementContext;
25  import org.apache.shardingsphere.sharding.api.config.strategy.sharding.HintShardingStrategyConfiguration;
26  import org.apache.shardingsphere.sharding.api.config.strategy.sharding.NoneShardingStrategyConfiguration;
27  import org.apache.shardingsphere.sharding.api.config.strategy.sharding.ShardingStrategyConfiguration;
28  import org.apache.shardingsphere.sharding.route.engine.condition.value.ListShardingConditionValue;
29  import org.apache.shardingsphere.sharding.route.engine.condition.value.RangeShardingConditionValue;
30  import org.apache.shardingsphere.sharding.route.engine.condition.value.ShardingConditionValue;
31  import org.apache.shardingsphere.sharding.rule.BindingTableRule;
32  import org.apache.shardingsphere.sharding.rule.ShardingRule;
33  import org.apache.shardingsphere.sharding.rule.ShardingTable;
34  import org.apache.shardingsphere.sql.parser.sql.common.segment.generic.table.SubqueryTableSegment;
35  import org.apache.shardingsphere.sql.parser.sql.common.statement.dml.SelectStatement;
36  import org.apache.shardingsphere.sql.parser.sql.common.util.SafeNumberOperationUtils;
37  
38  import java.util.Collection;
39  import java.util.HashMap;
40  import java.util.HashSet;
41  import java.util.LinkedList;
42  import java.util.List;
43  import java.util.Map;
44  import java.util.Optional;
45  
46  /**
47   * Sharding conditions.
48   */
49  @Getter
50  @ToString
51  public final class ShardingConditions {
52      
53      private final List<ShardingCondition> conditions;
54      
55      private final SQLStatementContext sqlStatementContext;
56      
57      private final ShardingRule rule;
58      
59      private final boolean subqueryContainsShardingCondition;
60      
61      public ShardingConditions(final List<ShardingCondition> conditions, final SQLStatementContext sqlStatementContext, final ShardingRule rule) {
62          this.conditions = conditions;
63          this.sqlStatementContext = sqlStatementContext;
64          this.rule = rule;
65          subqueryContainsShardingCondition = isSubqueryContainsShardingCondition(conditions, sqlStatementContext);
66      }
67      
68      /**
69       * Judge sharding conditions is always false or not.
70       *
71       * @return sharding conditions is always false or not
72       */
73      public boolean isAlwaysFalse() {
74          if (conditions.isEmpty()) {
75              return false;
76          }
77          for (ShardingCondition each : conditions) {
78              if (!(each instanceof AlwaysFalseShardingCondition)) {
79                  return false;
80              }
81          }
82          return true;
83      }
84      
85      /**
86       * Merge sharding conditions.
87       */
88      public void merge() {
89          if (conditions.size() > 1) {
90              Collection<ShardingCondition> result = new LinkedList<>();
91              result.add(conditions.remove(conditions.size() - 1));
92              while (!conditions.isEmpty()) {
93                  findUniqueShardingCondition(result, conditions.remove(conditions.size() - 1)).ifPresent(result::add);
94              }
95              conditions.addAll(result);
96          }
97      }
98      
99      private Optional<ShardingCondition> findUniqueShardingCondition(final Collection<ShardingCondition> conditions, final ShardingCondition condition) {
100         for (ShardingCondition each : conditions) {
101             if (isSameShardingCondition(rule, condition, each)) {
102                 return Optional.empty();
103             }
104         }
105         return Optional.of(condition);
106     }
107     
108     /**
109      * Judge whether sharding condition need merge or not.
110      *
111      * @return whether sharding condition need merge or not
112      */
113     public boolean isNeedMerge() {
114         boolean selectContainsSubquery = sqlStatementContext instanceof SelectStatementContext && ((SelectStatementContext) sqlStatementContext).isContainsSubquery();
115         boolean insertSelectContainsSubquery = sqlStatementContext instanceof InsertStatementContext && null != ((InsertStatementContext) sqlStatementContext).getInsertSelectContext()
116                 && ((InsertStatementContext) sqlStatementContext).getInsertSelectContext().getSelectStatementContext().isContainsSubquery();
117         return (selectContainsSubquery || insertSelectContainsSubquery) && !rule.getShardingLogicTableNames(sqlStatementContext.getTablesContext().getTableNames()).isEmpty();
118     }
119     
120     private boolean isSubqueryContainsShardingCondition(final List<ShardingCondition> conditions, final SQLStatementContext sqlStatementContext) {
121         Collection<SelectStatement> selectStatements = getSelectStatements(sqlStatementContext);
122         if (selectStatements.size() > 1) {
123             Map<Integer, List<ShardingCondition>> startIndexShardingConditions = new HashMap<>(conditions.size(), 1F);
124             for (ShardingCondition each : conditions) {
125                 startIndexShardingConditions.computeIfAbsent(each.getStartIndex(), unused -> new LinkedList<>()).add(each);
126             }
127             for (SelectStatement each : selectStatements) {
128                 if (each.getFrom().isPresent() && each.getFrom().get() instanceof SubqueryTableSegment) {
129                     continue;
130                 }
131                 if (!each.getWhere().isPresent() || !startIndexShardingConditions.containsKey(each.getWhere().get().getExpr().getStartIndex())) {
132                     return false;
133                 }
134             }
135         }
136         return true;
137     }
138     
139     private Collection<SelectStatement> getSelectStatements(final SQLStatementContext sqlStatementContext) {
140         Collection<SelectStatement> result = new LinkedList<>();
141         if (sqlStatementContext instanceof SelectStatementContext) {
142             result.add(((SelectStatementContext) sqlStatementContext).getSqlStatement());
143             for (SelectStatementContext each : ((SelectStatementContext) sqlStatementContext).getSubqueryContexts().values()) {
144                 result.add(each.getSqlStatement());
145             }
146         }
147         if (sqlStatementContext instanceof InsertStatementContext && null != ((InsertStatementContext) sqlStatementContext).getInsertSelectContext()) {
148             SelectStatementContext selectStatementContext = ((InsertStatementContext) sqlStatementContext).getInsertSelectContext().getSelectStatementContext();
149             result.add(selectStatementContext.getSqlStatement());
150             for (SelectStatementContext each : selectStatementContext.getSubqueryContexts().values()) {
151                 result.add(each.getSqlStatement());
152             }
153         }
154         return result;
155     }
156     
157     /**
158      * Judge whether all sharding conditions are same or not.
159      *
160      * @return whether all sharding conditions are same or not
161      */
162     public boolean isSameShardingCondition() {
163         Collection<String> hintStrategyTables = findHintStrategyTables(sqlStatementContext);
164         return 1 == hintStrategyTables.size() || subqueryContainsShardingCondition && 1 == conditions.size();
165     }
166     
167     private boolean isSameShardingCondition(final ShardingRule shardingRule, final ShardingCondition shardingCondition1, final ShardingCondition shardingCondition2) {
168         if (shardingCondition1.getValues().size() != shardingCondition2.getValues().size()) {
169             return false;
170         }
171         for (int i = 0; i < shardingCondition1.getValues().size(); i++) {
172             ShardingConditionValue shardingValue1 = shardingCondition1.getValues().get(i);
173             ShardingConditionValue shardingValue2 = shardingCondition2.getValues().get(i);
174             if (!isSameShardingConditionValue(shardingRule, shardingValue1, shardingValue2)) {
175                 return false;
176             }
177         }
178         return true;
179     }
180     
181     private boolean isSameShardingCondition(final ShardingConditionValue shardingValue1, final ShardingConditionValue shardingValue2) {
182         return shardingValue1.getTableName().equals(shardingValue2.getTableName()) && shardingValue1.getColumnName().equals(shardingValue2.getColumnName());
183     }
184     
185     private Collection<String> findHintStrategyTables(final SQLStatementContext sqlStatementContext) {
186         Collection<String> result = new HashSet<>(sqlStatementContext.getTablesContext().getTableNames().size(), 1F);
187         for (String each : sqlStatementContext.getTablesContext().getTableNames()) {
188             Optional<ShardingTable> shardingTable = rule.findShardingTable(each);
189             if (!shardingTable.isPresent()) {
190                 continue;
191             }
192             ShardingStrategyConfiguration databaseHintStrategy = rule.getDatabaseShardingStrategyConfiguration(shardingTable.get());
193             ShardingStrategyConfiguration tableHintStrategy = rule.getTableShardingStrategyConfiguration(shardingTable.get());
194             boolean isDatabaseTableHintStrategy = databaseHintStrategy instanceof HintShardingStrategyConfiguration && tableHintStrategy instanceof HintShardingStrategyConfiguration;
195             boolean isDatabaseHintStrategy = databaseHintStrategy instanceof HintShardingStrategyConfiguration && tableHintStrategy instanceof NoneShardingStrategyConfiguration;
196             boolean isTableHintStrategy = databaseHintStrategy instanceof NoneShardingStrategyConfiguration && tableHintStrategy instanceof HintShardingStrategyConfiguration;
197             if (isDatabaseTableHintStrategy || isDatabaseHintStrategy || isTableHintStrategy) {
198                 result.add(each);
199             }
200         }
201         return result;
202     }
203     
204     private boolean isBindingTable(final ShardingRule shardingRule, final ShardingConditionValue shardingValue1, final ShardingConditionValue shardingValue2) {
205         Optional<BindingTableRule> bindingRule = shardingRule.findBindingTableRule(shardingValue1.getTableName());
206         return bindingRule.isPresent() && bindingRule.get().hasLogicTable(shardingValue2.getTableName());
207     }
208     
209     private boolean isSameShardingConditionValue(final ShardingRule shardingRule, final ShardingConditionValue shardingValue1, final ShardingConditionValue shardingValue2) {
210         if (isBindingTable(shardingRule, shardingValue1, shardingValue2)) {
211             return true;
212         }
213         return isSameShardingCondition(shardingValue1, shardingValue2) && isSameShardingValue(shardingValue1, shardingValue2);
214     }
215     
216     @SuppressWarnings({"rawtypes", "unchecked"})
217     private boolean isSameShardingValue(final ShardingConditionValue shardingConditionValue1, final ShardingConditionValue shardingConditionValue2) {
218         if (shardingConditionValue1 instanceof ListShardingConditionValue && shardingConditionValue2 instanceof ListShardingConditionValue) {
219             return SafeNumberOperationUtils.safeCollectionEquals(
220                     ((ListShardingConditionValue) shardingConditionValue1).getValues(), ((ListShardingConditionValue) shardingConditionValue2).getValues());
221         }
222         if (shardingConditionValue1 instanceof RangeShardingConditionValue && shardingConditionValue2 instanceof RangeShardingConditionValue) {
223             return SafeNumberOperationUtils.safeRangeEquals(
224                     ((RangeShardingConditionValue) shardingConditionValue1).getValueRange(), ((RangeShardingConditionValue) shardingConditionValue2).getValueRange());
225         }
226         return false;
227     }
228 }