/* * Licensed to Crate under one or more contributor license agreements. * See the NOTICE file distributed with this work for additional * information regarding copyright ownership. Crate licenses this file * to you under the Apache License, Version 2.0 (the "License"); you may * not use this file except in compliance with the License. You may * obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or * implied. See the License for the specific language governing * permissions and limitations under the License. * * However, if you have executed another commercial license agreement * with Crate these terms will supersede the license and you may use the * software solely pursuant to the terms of the relevant commercial * agreement. */ package io.crate.planner; import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.HashMultimap; import com.google.common.collect.Multimap; import io.crate.analyze.WhereClause; import io.crate.metadata.Routing; import io.crate.metadata.TableIdent; import io.crate.metadata.table.TableInfo; import io.crate.planner.fetch.IndexBaseBuilder; import javax.annotation.Nullable; import java.util.*; final class RoutingBuilder { final Map<TableIdent, List<TableRouting>> routingListByTable = new HashMap<>(); private ReaderAllocations readerAllocations; @VisibleForTesting final static class TableRouting { final WhereClause where; final String preference; final Routing routing; TableRouting(WhereClause where, String preference, Routing routing) { this.where = where; this.preference = preference; this.routing = routing; } } Routing allocateRouting(TableInfo tableInfo, WhereClause where, @Nullable String preference) { List<TableRouting> existingRoutings = routingListByTable.get(tableInfo.ident()); if (existingRoutings == null) { return allocateNewRouting(tableInfo, where, preference); } Routing existing = tryFindMatchInExisting(where, preference, existingRoutings); if (existing != null) return existing; Routing routing = tableInfo.getRouting(where, preference); existingRoutings.add(new TableRouting(where, preference, routing)); // ensure all routings of this table are allocated // and update new routing by merging with existing ones for (TableRouting existingRouting : existingRoutings) { // Merge locations with existing routing routing.mergeLocations(existingRouting.routing.locations()); } return routing; } private static Routing tryFindMatchInExisting(WhereClause where, @Nullable String preference, Iterable<TableRouting> existingRoutings) { for (TableRouting existing : existingRoutings) { assert preference == null || preference.equals(existing.preference) : "preference must not be null or equals existing preference"; if (Objects.equals(existing.where, where)) { return existing.routing; } } return null; } private Routing allocateNewRouting(TableInfo tableInfo, WhereClause where, @Nullable String preference) { List<TableRouting> existingRoutings = new ArrayList<>(); routingListByTable.put(tableInfo.ident(), existingRoutings); Routing routing = tableInfo.getRouting(where, preference); existingRoutings.add(new TableRouting(where, preference, routing)); return routing; } ReaderAllocations buildReaderAllocations() { if (readerAllocations != null) { return readerAllocations; } Multimap<TableIdent, String> indicesByTable = HashMultimap.create(); IndexBaseBuilder indexBaseBuilder = new IndexBaseBuilder(); Map<String, Map<Integer, String>> shardNodes = new HashMap<>(); for (final Map.Entry<TableIdent, List<TableRouting>> tableRoutingEntry : routingListByTable.entrySet()) { TableIdent table = tableRoutingEntry.getKey(); List<TableRouting> routingList = tableRoutingEntry.getValue(); for (TableRouting tr : routingList) { allocateRoutingNodes(shardNodes, tr.routing.locations()); for (Map.Entry<String, Map<String, List<Integer>>> entry : tr.routing.locations().entrySet()) { Map<String, List<Integer>> shardsByIndex = entry.getValue(); indicesByTable.putAll(table, shardsByIndex.keySet()); for (Map.Entry<String, List<Integer>> shardsByIndexEntry : shardsByIndex.entrySet()) { indexBaseBuilder.allocate(shardsByIndexEntry.getKey(), shardsByIndexEntry.getValue()); } } } } readerAllocations = new ReaderAllocations(indexBaseBuilder.build(), shardNodes, indicesByTable); return readerAllocations; } private static void allocateRoutingNodes(Map<String, Map<Integer, String>> shardNodes, Map<String, Map<String, List<Integer>>> locations) { for (Map.Entry<String, Map<String, List<Integer>>> indicesByNodeId : locations.entrySet()) { String nodeId = indicesByNodeId.getKey(); for (Map.Entry<String, List<Integer>> shardsByIndexEntry : indicesByNodeId.getValue().entrySet()) { String index = shardsByIndexEntry.getKey(); List<Integer> shards = shardsByIndexEntry.getValue(); Map<Integer, String> shardsOnIndex = shardNodes.get(index); if (shardsOnIndex == null) { shardsOnIndex = new HashMap<>(shards.size()); shardNodes.put(index, shardsOnIndex); for (Integer id : shards) { shardsOnIndex.put(id, nodeId); } } else { for (Integer id : shards) { String allocatedNodeId = shardsOnIndex.get(id); assert allocatedNodeId == null || allocatedNodeId.equals(nodeId) : "allocatedNodeId must match nodeId"; shardsOnIndex.put(id, nodeId); } } } } } }