| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF 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. |
| */ |
| package org.apache.iotdb.db.schemaengine.schemaregion.utils; |
| |
| import org.apache.iotdb.commons.conf.IoTDBConstant; |
| import org.apache.iotdb.commons.exception.IllegalPathException; |
| import org.apache.iotdb.commons.exception.MetadataException; |
| import org.apache.iotdb.commons.path.AlignedPath; |
| import org.apache.iotdb.commons.path.MeasurementPath; |
| import org.apache.iotdb.commons.path.PartialPath; |
| import org.apache.iotdb.commons.schema.node.IMNode; |
| import org.apache.iotdb.commons.utils.TestOnly; |
| import org.apache.iotdb.db.queryengine.plan.planner.plan.parameter.AggregationDescriptor; |
| import org.apache.iotdb.db.queryengine.plan.statement.component.Ordering; |
| |
| import org.apache.tsfile.read.common.Path; |
| import org.apache.tsfile.utils.Binary; |
| import org.apache.tsfile.utils.Pair; |
| |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.Comparator; |
| import java.util.HashMap; |
| import java.util.LinkedList; |
| import java.util.List; |
| import java.util.Locale; |
| import java.util.Map; |
| import java.util.Map.Entry; |
| |
| import static org.apache.iotdb.commons.conf.IoTDBConstant.LOSS; |
| import static org.apache.iotdb.commons.conf.IoTDBConstant.SDT_PARAMETERS; |
| |
| public class MetaUtils { |
| |
| private MetaUtils() {} |
| |
| /** |
| * Get database path when creating schema automatically is enable |
| * |
| * <p>e.g., path = root.a.b.c and level = 1, return root.a |
| * |
| * @param path path |
| * @param level level |
| */ |
| public static PartialPath getStorageGroupPathByLevel(PartialPath path, int level) |
| throws MetadataException { |
| String[] nodeNames = path.getNodes(); |
| if (nodeNames.length <= level || !nodeNames[0].equals(IoTDBConstant.PATH_ROOT)) { |
| throw new IllegalPathException(path.getFullPath()); |
| } |
| String[] storageGroupNodes = new String[level + 1]; |
| System.arraycopy(nodeNames, 0, storageGroupNodes, 0, level + 1); |
| return new PartialPath(storageGroupNodes); |
| } |
| |
| /** |
| * PartialPath of aligned time series will be organized to one AlignedPath. BEFORE this method, |
| * all the aligned time series is NOT united. For example, given root.sg.d1.vector1[s1] and |
| * root.sg.d1.vector1[s2], they will be organized to root.sg.d1.vector1 [s1,s2] |
| * |
| * @param fullPaths full path list without uniting the sub measurement under the same aligned time |
| * series. The list has been sorted by the alphabetical order, so all the aligned time series |
| * of one device has already been placed contiguously. |
| * @return Size of partial path list could NOT equal to the input list size. For example, the |
| * vector1 (s1,s2) would be returned once. |
| * @deprecated |
| */ |
| @Deprecated |
| public static List<PartialPath> groupAlignedPaths(List<PartialPath> fullPaths) { |
| List<PartialPath> result = new LinkedList<>(); |
| AlignedPath alignedPath = null; |
| for (PartialPath path : fullPaths) { |
| MeasurementPath measurementPath = (MeasurementPath) path; |
| if (!measurementPath.isUnderAlignedEntity()) { |
| result.add(measurementPath); |
| alignedPath = null; |
| } else { |
| if (alignedPath == null || !alignedPath.equals(measurementPath.getDevice())) { |
| alignedPath = new AlignedPath(measurementPath); |
| result.add(alignedPath); |
| } else { |
| alignedPath.addMeasurement(measurementPath); |
| } |
| } |
| } |
| return result; |
| } |
| |
| /** |
| * PartialPath of aligned time series will be organized to one AlignedPath. BEFORE this method, |
| * all the aligned time series is NOT united. For example, given root.sg.d1.vector1[s1] and |
| * root.sg.d1.vector1[s2], they will be organized to root.sg.d1.vector1 [s1,s2] |
| * |
| * @param fullPaths full path list without uniting the sub measurement under the same aligned time |
| * series. The list has been sorted by the alphabetical order, so all the aligned time series |
| * of one device has already been placed contiguously. |
| * @return Size of partial path list could NOT equal to the input list size. For example, the |
| * vector1 (s1,s2) would be returned once. |
| */ |
| public static List<PartialPath> groupAlignedSeries(List<PartialPath> fullPaths) { |
| return groupAlignedSeries(fullPaths, new HashMap<>()); |
| } |
| |
| public static List<PartialPath> groupAlignedSeriesWithOrder( |
| List<PartialPath> fullPaths, Ordering timeseriesOrdering) { |
| Map<String, AlignedPath> deviceToAlignedPathMap = new HashMap<>(); |
| List<PartialPath> res = groupAlignedSeries(fullPaths, deviceToAlignedPathMap); |
| res.sort( |
| timeseriesOrdering == Ordering.ASC ? Comparator.naturalOrder() : Comparator.reverseOrder()); |
| // sort the measurements of AlignedPath |
| Comparator<Binary> comparator = |
| timeseriesOrdering == Ordering.ASC ? Comparator.naturalOrder() : Comparator.reverseOrder(); |
| for (AlignedPath alignedPath : deviceToAlignedPathMap.values()) { |
| alignedPath.sortMeasurement(comparator); |
| } |
| return res; |
| } |
| |
| private static List<PartialPath> groupAlignedSeries( |
| List<PartialPath> fullPaths, Map<String, AlignedPath> deviceToAlignedPathMap) { |
| List<PartialPath> result = new ArrayList<>(); |
| for (PartialPath path : fullPaths) { |
| MeasurementPath measurementPath = (MeasurementPath) path; |
| if (!measurementPath.isUnderAlignedEntity()) { |
| result.add(measurementPath); |
| } else { |
| String deviceName = measurementPath.getDevice(); |
| if (!deviceToAlignedPathMap.containsKey(deviceName)) { |
| AlignedPath alignedPath = new AlignedPath(measurementPath); |
| deviceToAlignedPathMap.put(deviceName, alignedPath); |
| } else { |
| AlignedPath alignedPath = deviceToAlignedPathMap.get(deviceName); |
| alignedPath.addMeasurement(measurementPath); |
| } |
| } |
| } |
| result.addAll(deviceToAlignedPathMap.values()); |
| return result; |
| } |
| |
| @TestOnly |
| public static List<String> getMultiFullPaths(IMNode node) { |
| if (node == null) { |
| return Collections.emptyList(); |
| } |
| |
| List<IMNode> lastNodeList = new ArrayList<>(); |
| collectLastNode(node, lastNodeList); |
| |
| List<String> result = new ArrayList<>(); |
| for (IMNode lastNode : lastNodeList) { |
| result.add(lastNode.getFullPath()); |
| } |
| |
| return result; |
| } |
| |
| @TestOnly |
| public static void collectLastNode(IMNode node, List<IMNode> lastNodeList) { |
| if (node != null) { |
| Map<String, IMNode> children = node.getChildren(); |
| if (children.isEmpty()) { |
| lastNodeList.add(node); |
| } |
| |
| for (Entry<String, IMNode> entry : children.entrySet()) { |
| IMNode childNode = entry.getValue(); |
| collectLastNode(childNode, lastNodeList); |
| } |
| } |
| } |
| |
| /** |
| * Merge same series and convert to series map. For example: Given: paths: s1, s2, s3, s1 and |
| * aggregations: count, sum, count, sum. Then: pathToAggrIndexesMap: s1 -> 0, 3; s2 -> 1; s3 -> 2 |
| * |
| * @param selectedPaths selected series |
| * @return path to aggregation indexes map |
| */ |
| public static Map<PartialPath, List<Integer>> groupAggregationsBySeries( |
| List<? extends Path> selectedPaths) { |
| Map<PartialPath, List<Integer>> pathToAggrIndexesMap = new HashMap<>(); |
| for (int i = 0; i < selectedPaths.size(); i++) { |
| PartialPath series = (PartialPath) selectedPaths.get(i); |
| pathToAggrIndexesMap.computeIfAbsent(series, key -> new ArrayList<>()).add(i); |
| } |
| return pathToAggrIndexesMap; |
| } |
| |
| /** |
| * Group all the series under an aligned entity into one AlignedPath and remove these series from |
| * pathToAggrIndexesMap. For example, input map: d1[s1] -> [1, 3], d1[s2] -> [2,4], will return |
| * d1[s1,s2], [[1,3], [2,4]] |
| */ |
| public static Map<AlignedPath, List<List<Integer>>> groupAlignedSeriesWithAggregations( |
| Map<PartialPath, List<Integer>> pathToAggrIndexesMap) { |
| Map<AlignedPath, List<List<Integer>>> alignedPathToAggrIndexesMap = new HashMap<>(); |
| Map<String, AlignedPath> temp = new HashMap<>(); |
| List<PartialPath> seriesPaths = new ArrayList<>(pathToAggrIndexesMap.keySet()); |
| for (PartialPath seriesPath : seriesPaths) { |
| // for with value filter |
| if (seriesPath instanceof AlignedPath) { |
| List<Integer> indexes = pathToAggrIndexesMap.remove(seriesPath); |
| AlignedPath groupPath = temp.get(seriesPath.getFullPath()); |
| if (groupPath == null) { |
| groupPath = (AlignedPath) seriesPath.copy(); |
| temp.put(groupPath.getFullPath(), groupPath); |
| alignedPathToAggrIndexesMap |
| .computeIfAbsent(groupPath, key -> new ArrayList<>()) |
| .add(indexes); |
| } else { |
| // groupPath is changed here so we update it |
| List<List<Integer>> subIndexes = alignedPathToAggrIndexesMap.remove(groupPath); |
| subIndexes.add(indexes); |
| groupPath.addMeasurements(((AlignedPath) seriesPath).getMeasurementList()); |
| groupPath.addSchemas(((AlignedPath) seriesPath).getSchemaList()); |
| alignedPathToAggrIndexesMap.put(groupPath, subIndexes); |
| } |
| } else if (((MeasurementPath) seriesPath).isUnderAlignedEntity()) { |
| // for without value filter |
| List<Integer> indexes = pathToAggrIndexesMap.remove(seriesPath); |
| AlignedPath groupPath = temp.get(seriesPath.getDevice()); |
| if (groupPath == null) { |
| groupPath = new AlignedPath((MeasurementPath) seriesPath); |
| temp.put(seriesPath.getDevice(), groupPath); |
| alignedPathToAggrIndexesMap |
| .computeIfAbsent(groupPath, key -> new ArrayList<>()) |
| .add(indexes); |
| } else { |
| // groupPath is changed here so we update it |
| List<List<Integer>> subIndexes = alignedPathToAggrIndexesMap.remove(groupPath); |
| subIndexes.add(indexes); |
| groupPath.addMeasurement((MeasurementPath) seriesPath); |
| alignedPathToAggrIndexesMap.put(groupPath, subIndexes); |
| } |
| } |
| } |
| return alignedPathToAggrIndexesMap; |
| } |
| |
| public static Map<PartialPath, List<AggregationDescriptor>> groupAlignedAggregations( |
| Map<PartialPath, List<AggregationDescriptor>> pathToAggregations) { |
| Map<PartialPath, List<AggregationDescriptor>> result = new HashMap<>(); |
| Map<String, List<MeasurementPath>> deviceToAlignedPathsMap = new HashMap<>(); |
| for (PartialPath path : pathToAggregations.keySet()) { |
| MeasurementPath measurementPath = (MeasurementPath) path; |
| if (!measurementPath.isUnderAlignedEntity()) { |
| result |
| .computeIfAbsent(measurementPath, key -> new ArrayList<>()) |
| .addAll(pathToAggregations.get(path)); |
| } else { |
| deviceToAlignedPathsMap |
| .computeIfAbsent(path.getDevice(), key -> new ArrayList<>()) |
| .add(measurementPath); |
| } |
| } |
| for (Map.Entry<String, List<MeasurementPath>> alignedPathEntry : |
| deviceToAlignedPathsMap.entrySet()) { |
| List<MeasurementPath> measurementPathList = alignedPathEntry.getValue(); |
| AlignedPath alignedPath = null; |
| List<AggregationDescriptor> aggregationDescriptorList = new ArrayList<>(); |
| for (int i = 0; i < measurementPathList.size(); i++) { |
| MeasurementPath measurementPath = measurementPathList.get(i); |
| if (i == 0) { |
| alignedPath = new AlignedPath(measurementPath); |
| } else { |
| alignedPath.addMeasurement(measurementPath); |
| } |
| aggregationDescriptorList.addAll(pathToAggregations.get(measurementPath)); |
| } |
| result.put(alignedPath, aggregationDescriptorList); |
| } |
| return result; |
| } |
| |
| public static Pair<String, String> parseDeadbandInfo(Map<String, String> props) { |
| if (props == null) { |
| return new Pair<>(null, null); |
| } |
| String deadband = props.get(LOSS); |
| deadband = deadband == null ? null : deadband.toUpperCase(Locale.ROOT); |
| Map<String, String> deadbandParameters = new HashMap<>(); |
| for (String k : SDT_PARAMETERS) { |
| if (props.containsKey(k)) { |
| deadbandParameters.put(k, props.get(k)); |
| } |
| } |
| |
| return new Pair<>( |
| deadband, deadbandParameters.isEmpty() ? null : String.format("%s", deadbandParameters)); |
| } |
| } |