| /* |
| * 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.metadata; |
| |
| import org.apache.iotdb.db.conf.IoTDBConfig; |
| import org.apache.iotdb.db.conf.IoTDBConstant; |
| import org.apache.iotdb.db.conf.IoTDBDescriptor; |
| import org.apache.iotdb.db.engine.fileSystem.SystemFileFactory; |
| import org.apache.iotdb.db.engine.querycontext.QueryDataSource; |
| import org.apache.iotdb.db.exception.metadata.AliasAlreadyExistException; |
| import org.apache.iotdb.db.exception.metadata.IllegalParameterOfPathException; |
| import org.apache.iotdb.db.exception.metadata.IllegalPathException; |
| import org.apache.iotdb.db.exception.metadata.MetadataException; |
| import org.apache.iotdb.db.exception.metadata.PathAlreadyExistException; |
| import org.apache.iotdb.db.exception.metadata.PathNotExistException; |
| import org.apache.iotdb.db.exception.metadata.StorageGroupAlreadySetException; |
| import org.apache.iotdb.db.exception.metadata.StorageGroupNotSetException; |
| import org.apache.iotdb.db.metadata.MManager.StorageGroupFilter; |
| import org.apache.iotdb.db.metadata.logfile.MLogReader; |
| import org.apache.iotdb.db.metadata.logfile.MLogWriter; |
| import org.apache.iotdb.db.metadata.mnode.IEntityMNode; |
| import org.apache.iotdb.db.metadata.mnode.IMNode; |
| import org.apache.iotdb.db.metadata.mnode.IMeasurementMNode; |
| import org.apache.iotdb.db.metadata.mnode.IStorageGroupMNode; |
| import org.apache.iotdb.db.metadata.mnode.InternalMNode; |
| import org.apache.iotdb.db.metadata.mnode.MeasurementMNode; |
| import org.apache.iotdb.db.metadata.mnode.StorageGroupMNode; |
| import org.apache.iotdb.db.metadata.template.Template; |
| import org.apache.iotdb.db.qp.physical.PhysicalPlan; |
| import org.apache.iotdb.db.qp.physical.sys.MNodePlan; |
| import org.apache.iotdb.db.qp.physical.sys.MeasurementMNodePlan; |
| import org.apache.iotdb.db.qp.physical.sys.ShowDevicesPlan; |
| import org.apache.iotdb.db.qp.physical.sys.ShowTimeSeriesPlan; |
| import org.apache.iotdb.db.qp.physical.sys.StorageGroupMNodePlan; |
| import org.apache.iotdb.db.query.context.QueryContext; |
| import org.apache.iotdb.db.query.control.QueryResourceManager; |
| import org.apache.iotdb.db.query.dataset.ShowDevicesResult; |
| import org.apache.iotdb.db.query.executor.fill.LastPointReader; |
| import org.apache.iotdb.db.utils.TestOnly; |
| import org.apache.iotdb.tsfile.file.metadata.enums.CompressionType; |
| import org.apache.iotdb.tsfile.file.metadata.enums.TSDataType; |
| import org.apache.iotdb.tsfile.file.metadata.enums.TSEncoding; |
| import org.apache.iotdb.tsfile.read.TimeValuePair; |
| import org.apache.iotdb.tsfile.utils.Pair; |
| import org.apache.iotdb.tsfile.write.schema.IMeasurementSchema; |
| import org.apache.iotdb.tsfile.write.schema.MeasurementSchema; |
| import org.apache.iotdb.tsfile.write.schema.VectorMeasurementSchema; |
| |
| import com.google.gson.Gson; |
| import com.google.gson.GsonBuilder; |
| import com.google.gson.JsonElement; |
| import com.google.gson.JsonObject; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| import java.io.File; |
| import java.io.IOException; |
| import java.io.Serializable; |
| import java.nio.file.Files; |
| import java.util.ArrayDeque; |
| import java.util.ArrayList; |
| import java.util.Comparator; |
| import java.util.Deque; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.LinkedList; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Map.Entry; |
| import java.util.Queue; |
| import java.util.Set; |
| import java.util.TreeSet; |
| import java.util.concurrent.ConcurrentHashMap; |
| import java.util.regex.Pattern; |
| import java.util.stream.Stream; |
| |
| import static java.util.stream.Collectors.toList; |
| import static org.apache.iotdb.db.conf.IoTDBConstant.LOSS; |
| import static org.apache.iotdb.db.conf.IoTDBConstant.PATH_SEPARATOR; |
| import static org.apache.iotdb.db.conf.IoTDBConstant.PATH_WILDCARD; |
| import static org.apache.iotdb.db.conf.IoTDBConstant.SDT; |
| import static org.apache.iotdb.db.conf.IoTDBConstant.SDT_COMP_DEV; |
| import static org.apache.iotdb.db.conf.IoTDBConstant.SDT_COMP_MAX_TIME; |
| import static org.apache.iotdb.db.conf.IoTDBConstant.SDT_COMP_MIN_TIME; |
| |
| /** The hierarchical struct of the Metadata Tree is implemented in this class. */ |
| public class MTree implements Serializable { |
| |
| public static final Gson GSON = new GsonBuilder().setPrettyPrinting().create(); |
| private static final long serialVersionUID = -4200394435237291964L; |
| private static final Logger logger = LoggerFactory.getLogger(MTree.class); |
| private static final String NO_CHILDNODE_MSG = " does not have the child node "; |
| private static transient ThreadLocal<Integer> limit = new ThreadLocal<>(); |
| private static transient ThreadLocal<Integer> offset = new ThreadLocal<>(); |
| private static transient ThreadLocal<Integer> count = new ThreadLocal<>(); |
| private static transient ThreadLocal<Integer> curOffset = new ThreadLocal<>(); |
| private IMNode root; |
| |
| private String mtreeSnapshotPath; |
| private String mtreeSnapshotTmpPath; |
| |
| MTree() { |
| this.root = new InternalMNode(null, IoTDBConstant.PATH_ROOT); |
| } |
| |
| private MTree(InternalMNode root) { |
| this.root = root; |
| } |
| |
| public void init() throws IOException { |
| mtreeSnapshotPath = |
| IoTDBDescriptor.getInstance().getConfig().getSchemaDir() |
| + File.separator |
| + MetadataConstant.MTREE_SNAPSHOT; |
| mtreeSnapshotTmpPath = |
| IoTDBDescriptor.getInstance().getConfig().getSchemaDir() |
| + File.separator |
| + MetadataConstant.MTREE_SNAPSHOT_TMP; |
| |
| File tmpFile = SystemFileFactory.INSTANCE.getFile(mtreeSnapshotTmpPath); |
| if (tmpFile.exists()) { |
| logger.warn("Creating MTree snapshot not successful before crashing..."); |
| Files.delete(tmpFile.toPath()); |
| } |
| |
| File mtreeSnapshot = SystemFileFactory.INSTANCE.getFile(mtreeSnapshotPath); |
| long time = System.currentTimeMillis(); |
| if (mtreeSnapshot.exists()) { |
| this.root = deserializeFrom(mtreeSnapshot).root; |
| logger.debug( |
| "spend {} ms to deserialize mtree from snapshot", System.currentTimeMillis() - time); |
| } |
| } |
| |
| public void clear() { |
| root = new InternalMNode(null, IoTDBConstant.PATH_ROOT); |
| } |
| |
| public void createSnapshot() throws IOException { |
| long time = System.currentTimeMillis(); |
| logger.info("Start creating MTree snapshot to {}", mtreeSnapshotPath); |
| try { |
| serializeTo(mtreeSnapshotTmpPath); |
| File tmpFile = SystemFileFactory.INSTANCE.getFile(mtreeSnapshotTmpPath); |
| File snapshotFile = SystemFileFactory.INSTANCE.getFile(mtreeSnapshotPath); |
| if (snapshotFile.exists()) { |
| Files.delete(snapshotFile.toPath()); |
| } |
| if (tmpFile.renameTo(snapshotFile)) { |
| logger.info( |
| "Finish creating MTree snapshot to {}, spend {} ms.", |
| mtreeSnapshotPath, |
| System.currentTimeMillis() - time); |
| } |
| } catch (IOException e) { |
| logger.warn("Failed to create MTree snapshot to {}", mtreeSnapshotPath, e); |
| if (SystemFileFactory.INSTANCE.getFile(mtreeSnapshotTmpPath).exists()) { |
| try { |
| Files.delete(SystemFileFactory.INSTANCE.getFile(mtreeSnapshotTmpPath).toPath()); |
| } catch (IOException e1) { |
| logger.warn("delete file {} failed: {}", mtreeSnapshotTmpPath, e1.getMessage()); |
| } |
| } |
| throw e; |
| } |
| } |
| |
| public static long getLastTimeStamp(IMeasurementMNode node, QueryContext queryContext) { |
| TimeValuePair last = node.getCachedLast(); |
| if (last != null) { |
| return node.getCachedLast().getTimestamp(); |
| } else { |
| try { |
| QueryDataSource dataSource = |
| QueryResourceManager.getInstance() |
| .getQueryDataSource(node.getPartialPath(), queryContext, null); |
| Set<String> measurementSet = new HashSet<>(); |
| measurementSet.add(node.getPartialPath().getFullPath()); |
| LastPointReader lastReader = |
| new LastPointReader( |
| node.getPartialPath(), |
| node.getSchema().getType(), |
| measurementSet, |
| queryContext, |
| dataSource, |
| Long.MAX_VALUE, |
| null); |
| last = lastReader.readLastPoint(); |
| return (last != null ? last.getTimestamp() : Long.MIN_VALUE); |
| } catch (Exception e) { |
| logger.error( |
| "Something wrong happened while trying to get last time value pair of {}", |
| node.getFullPath(), |
| e); |
| return Long.MIN_VALUE; |
| } |
| } |
| } |
| |
| private static String jsonToString(JsonObject jsonObject) { |
| return GSON.toJson(jsonObject); |
| } |
| |
| /** combine multiple metadata in string format */ |
| @TestOnly |
| static JsonObject combineMetadataInStrings(String[] metadataStrs) { |
| JsonObject[] jsonObjects = new JsonObject[metadataStrs.length]; |
| for (int i = 0; i < jsonObjects.length; i++) { |
| jsonObjects[i] = GSON.fromJson(metadataStrs[i], JsonObject.class); |
| } |
| |
| JsonObject root = jsonObjects[0]; |
| for (int i = 1; i < jsonObjects.length; i++) { |
| root = combineJsonObjects(root, jsonObjects[i]); |
| } |
| |
| return root; |
| } |
| |
| private static JsonObject combineJsonObjects(JsonObject a, JsonObject b) { |
| JsonObject res = new JsonObject(); |
| |
| Set<String> retainSet = new HashSet<>(a.keySet()); |
| retainSet.retainAll(b.keySet()); |
| Set<String> aCha = new HashSet<>(a.keySet()); |
| Set<String> bCha = new HashSet<>(b.keySet()); |
| aCha.removeAll(retainSet); |
| bCha.removeAll(retainSet); |
| |
| for (String key : aCha) { |
| res.add(key, a.get(key)); |
| } |
| |
| for (String key : bCha) { |
| res.add(key, b.get(key)); |
| } |
| for (String key : retainSet) { |
| JsonElement v1 = a.get(key); |
| JsonElement v2 = b.get(key); |
| if (v1 instanceof JsonObject && v2 instanceof JsonObject) { |
| res.add(key, combineJsonObjects((JsonObject) v1, (JsonObject) v2)); |
| } else { |
| res.add(v1.getAsString(), v2); |
| } |
| } |
| return res; |
| } |
| |
| /** |
| * Create a timeseries with a full path from root to leaf node. Before creating a timeseries, the |
| * storage group should be set first, throw exception otherwise |
| * |
| * @param path timeseries path |
| * @param dataType data type |
| * @param encoding encoding |
| * @param compressor compressor |
| * @param props props |
| * @param alias alias of measurement |
| */ |
| IMeasurementMNode createTimeseries( |
| PartialPath path, |
| TSDataType dataType, |
| TSEncoding encoding, |
| CompressionType compressor, |
| Map<String, String> props, |
| String alias) |
| throws MetadataException { |
| String[] nodeNames = path.getNodes(); |
| if (nodeNames.length <= 2 || !nodeNames[0].equals(root.getName())) { |
| throw new IllegalPathException(path.getFullPath()); |
| } |
| checkTimeseries(path.getFullPath()); |
| IMNode cur = root; |
| boolean hasSetStorageGroup = false; |
| Template upperTemplate = cur.getSchemaTemplate(); |
| // e.g, path = root.sg.d1.s1, create internal nodes and set cur to d1 node |
| for (int i = 1; i < nodeNames.length - 1; i++) { |
| if (cur.isMeasurement()) { |
| throw new PathAlreadyExistException(cur.getFullPath()); |
| } |
| if (cur.isStorageGroup()) { |
| hasSetStorageGroup = true; |
| } |
| String childName = nodeNames[i]; |
| if (!cur.hasChild(childName)) { |
| if (!hasSetStorageGroup) { |
| throw new StorageGroupNotSetException("Storage group should be created first"); |
| } |
| if (cur.isUseTemplate() && upperTemplate.hasSchema(childName)) { |
| throw new PathAlreadyExistException( |
| cur.getPartialPath().concatNode(childName).getFullPath()); |
| } |
| cur.addChild(childName, new InternalMNode(cur, childName)); |
| } |
| cur = cur.getChild(childName); |
| |
| if (cur.getSchemaTemplate() != null) { |
| upperTemplate = cur.getSchemaTemplate(); |
| } |
| } |
| |
| if (cur.isMeasurement()) { |
| throw new PathAlreadyExistException(cur.getFullPath()); |
| } |
| |
| if (upperTemplate != null && !upperTemplate.isCompatible(path)) { |
| throw new PathAlreadyExistException( |
| path.getFullPath() + " ( which is incompatible with template )"); |
| } |
| |
| if (props != null && props.containsKey(LOSS) && props.get(LOSS).equals(SDT)) { |
| checkSDTFormat(path.getFullPath(), props); |
| } |
| |
| String leafName = nodeNames[nodeNames.length - 1]; |
| |
| // synchronize check and add, we need addChild and add Alias become atomic operation |
| // only write on mtree will be synchronized |
| synchronized (this) { |
| if (cur.hasChild(leafName)) { |
| throw new PathAlreadyExistException(path.getFullPath()); |
| } |
| |
| if (alias != null && cur.hasChild(alias)) { |
| throw new AliasAlreadyExistException(path.getFullPath(), alias); |
| } |
| |
| IEntityMNode entityMNode = IEntityMNode.setToEntity(cur); |
| |
| IMeasurementMNode measurementMNode = |
| new MeasurementMNode(entityMNode, leafName, alias, dataType, encoding, compressor, props); |
| entityMNode.addChild(leafName, measurementMNode); |
| // link alias to LeafMNode |
| if (alias != null) { |
| entityMNode.addAlias(alias, measurementMNode); |
| } |
| return measurementMNode; |
| } |
| } |
| |
| /** |
| * Create aligned timeseries with full paths from root to one leaf node. Before creating |
| * timeseries, the * storage group should be set first, throw exception otherwise |
| * |
| * @param devicePath device path |
| * @param measurements measurements list |
| * @param dataTypes data types list |
| * @param encodings encodings list |
| * @param compressor compressor |
| */ |
| void createAlignedTimeseries( |
| PartialPath devicePath, |
| List<String> measurements, |
| List<TSDataType> dataTypes, |
| List<TSEncoding> encodings, |
| CompressionType compressor) |
| throws MetadataException { |
| String[] deviceNodeNames = devicePath.getNodes(); |
| if (deviceNodeNames.length <= 1 || !deviceNodeNames[0].equals(root.getName())) { |
| throw new IllegalPathException(devicePath.getFullPath()); |
| } |
| checkTimeseries(devicePath.getFullPath()); |
| for (String measurement : measurements) { |
| checkTimeseries(measurement); |
| } |
| IMNode cur = root; |
| boolean hasSetStorageGroup = false; |
| // e.g, devicePath = root.sg.d1, create internal nodes and set cur to d1 node |
| for (int i = 1; i < deviceNodeNames.length - 1; i++) { |
| if (cur.isMeasurement()) { |
| throw new PathAlreadyExistException(cur.getFullPath()); |
| } |
| if (cur.isStorageGroup()) { |
| hasSetStorageGroup = true; |
| } |
| String nodeName = deviceNodeNames[i]; |
| if (!cur.hasChild(nodeName)) { |
| if (!hasSetStorageGroup) { |
| throw new StorageGroupNotSetException("Storage group should be created first"); |
| } |
| cur.addChild(nodeName, new InternalMNode(cur, nodeName)); |
| } |
| cur = cur.getChild(nodeName); |
| } |
| |
| if (cur.isMeasurement()) { |
| throw new PathAlreadyExistException(cur.getFullPath()); |
| } |
| |
| String leafName = deviceNodeNames[deviceNodeNames.length - 1]; |
| |
| // synchronize check and add, we need addChild and add Alias become atomic operation |
| // only write on mtree will be synchronized |
| synchronized (this) { |
| if (cur.hasChild(leafName)) { |
| throw new PathAlreadyExistException(devicePath.getFullPath() + "." + leafName); |
| } |
| |
| IEntityMNode entityMNode = IEntityMNode.setToEntity(cur); |
| |
| int measurementsSize = measurements.size(); |
| |
| // this measurementMNode could be a leaf or not. |
| IMeasurementMNode measurementMNode = |
| new MeasurementMNode( |
| entityMNode, |
| leafName, |
| new VectorMeasurementSchema( |
| leafName, |
| measurements.toArray(new String[measurementsSize]), |
| dataTypes.toArray(new TSDataType[measurementsSize]), |
| encodings.toArray(new TSEncoding[measurementsSize]), |
| compressor), |
| null); |
| entityMNode.addChild(leafName, measurementMNode); |
| } |
| } |
| |
| private void checkTimeseries(String timeseries) throws IllegalPathException { |
| if (!IoTDBConfig.NODE_PATTERN.matcher(timeseries).matches()) { |
| throw new IllegalPathException( |
| String.format("The timeseries name contains unsupported character. %s", timeseries)); |
| } |
| } |
| |
| // check if sdt parameters are valid |
| private void checkSDTFormat(String path, Map<String, String> props) |
| throws IllegalParameterOfPathException { |
| if (!props.containsKey(SDT_COMP_DEV)) { |
| throw new IllegalParameterOfPathException("SDT compression deviation is required", path); |
| } |
| |
| try { |
| double d = Double.parseDouble(props.get(SDT_COMP_DEV)); |
| if (d < 0) { |
| throw new IllegalParameterOfPathException( |
| "SDT compression deviation cannot be negative", path); |
| } |
| } catch (NumberFormatException e) { |
| throw new IllegalParameterOfPathException("SDT compression deviation formatting error", path); |
| } |
| |
| long compMinTime = sdtCompressionTimeFormat(SDT_COMP_MIN_TIME, props, path); |
| long compMaxTime = sdtCompressionTimeFormat(SDT_COMP_MAX_TIME, props, path); |
| |
| if (compMaxTime <= compMinTime) { |
| throw new IllegalParameterOfPathException( |
| "SDT compression maximum time needs to be greater than compression minimum time", path); |
| } |
| } |
| |
| private long sdtCompressionTimeFormat(String compTime, Map<String, String> props, String path) |
| throws IllegalParameterOfPathException { |
| boolean isCompMaxTime = compTime.equals(SDT_COMP_MAX_TIME); |
| long time = isCompMaxTime ? Long.MAX_VALUE : 0; |
| String s = isCompMaxTime ? "maximum" : "minimum"; |
| if (props.containsKey(compTime)) { |
| try { |
| time = Long.parseLong(props.get(compTime)); |
| if (time < 0) { |
| throw new IllegalParameterOfPathException( |
| String.format("SDT compression %s time cannot be negative", s), path); |
| } |
| } catch (IllegalParameterOfPathException e) { |
| throw new IllegalParameterOfPathException( |
| String.format("SDT compression %s time formatting error", s), path); |
| } |
| } else { |
| logger.info("{} enabled SDT but did not set compression {} time", path, s); |
| } |
| return time; |
| } |
| |
| /** |
| * Add an interval path to MTree. This is only used for automatically creating schema |
| * |
| * <p>e.g., get root.sg.d1, get or create all internal nodes and return the node of d1 |
| */ |
| Pair<IMNode, Template> getDeviceNodeWithAutoCreating(PartialPath deviceId, int sgLevel) |
| throws MetadataException { |
| String[] nodeNames = deviceId.getNodes(); |
| if (nodeNames.length <= 1 || !nodeNames[0].equals(root.getName())) { |
| throw new IllegalPathException(deviceId.getFullPath()); |
| } |
| IMNode cur = root; |
| Template upperTemplate = cur.getSchemaTemplate(); |
| for (int i = 1; i < nodeNames.length; i++) { |
| if (!cur.hasChild(nodeNames[i])) { |
| if (cur.isUseTemplate() && upperTemplate.hasSchema(nodeNames[i])) { |
| throw new PathAlreadyExistException( |
| cur.getPartialPath().concatNode(nodeNames[i]).getFullPath()); |
| } |
| if (i == sgLevel) { |
| cur.addChild( |
| nodeNames[i], |
| new StorageGroupMNode( |
| cur, nodeNames[i], IoTDBDescriptor.getInstance().getConfig().getDefaultTTL())); |
| } else { |
| cur.addChild(nodeNames[i], new InternalMNode(cur, nodeNames[i])); |
| } |
| } |
| cur = cur.getChild(nodeNames[i]); |
| // update upper template |
| upperTemplate = cur.getSchemaTemplate() == null ? upperTemplate : cur.getSchemaTemplate(); |
| } |
| |
| return new Pair<>(cur, upperTemplate); |
| } |
| |
| /** |
| * Check whether the given path exists. |
| * |
| * @param path a full path or a prefix path |
| */ |
| boolean isPathExist(PartialPath path) { |
| String[] nodeNames = path.getNodes(); |
| IMNode cur = root; |
| if (!nodeNames[0].equals(root.getName())) { |
| return false; |
| } |
| Template upperTemplate = cur.getSchemaTemplate(); |
| for (int i = 1; i < nodeNames.length; i++) { |
| if (!cur.hasChild(nodeNames[i])) { |
| return cur.isUseTemplate() && upperTemplate.hasSchema(nodeNames[i]); |
| } |
| cur = cur.getChild(nodeNames[i]); |
| if (cur.isMeasurement()) { |
| if (i == nodeNames.length - 1) { |
| return true; |
| } |
| if (((IMeasurementMNode) cur).getSchema() instanceof VectorMeasurementSchema) { |
| return i == nodeNames.length - 2 |
| && ((IMeasurementMNode) cur).getSchema().isCompatible(nodeNames[i + 1]); |
| } else { |
| return false; |
| } |
| } |
| upperTemplate = cur.getSchemaTemplate() == null ? upperTemplate : cur.getSchemaTemplate(); |
| } |
| return true; |
| } |
| |
| /** |
| * Set storage group. Make sure check seriesPath before setting storage group |
| * |
| * @param path path |
| */ |
| void setStorageGroup(PartialPath path) throws MetadataException { |
| String[] nodeNames = path.getNodes(); |
| checkStorageGroup(path.getFullPath()); |
| if (nodeNames.length <= 1 || !nodeNames[0].equals(root.getName())) { |
| throw new IllegalPathException(path.getFullPath()); |
| } |
| IMNode cur = root; |
| Template upperTemplate = cur.getSchemaTemplate(); |
| int i = 1; |
| // e.g., path = root.a.b.sg, create internal nodes for a, b |
| while (i < nodeNames.length - 1) { |
| IMNode temp = cur.getChild(nodeNames[i]); |
| if (temp == null) { |
| if (cur.isUseTemplate() && upperTemplate.hasSchema(nodeNames[i])) { |
| throw new PathAlreadyExistException( |
| cur.getPartialPath().concatNode(nodeNames[i]).getFullPath()); |
| } |
| cur.addChild(nodeNames[i], new InternalMNode(cur, nodeNames[i])); |
| } else if (temp.isStorageGroup()) { |
| // before set storage group, check whether the exists or not |
| throw new StorageGroupAlreadySetException(temp.getFullPath()); |
| } |
| cur = cur.getChild(nodeNames[i]); |
| upperTemplate = cur.getSchemaTemplate() == null ? upperTemplate : cur.getSchemaTemplate(); |
| i++; |
| } |
| |
| // synchronize check and add, we need addChild become atomic operation |
| // only write on mtree will be synchronized |
| synchronized (this) { |
| if (cur.hasChild(nodeNames[i])) { |
| // node b has child sg |
| if (cur.getChild(nodeNames[i]).isStorageGroup()) { |
| throw new StorageGroupAlreadySetException(path.getFullPath()); |
| } else { |
| throw new StorageGroupAlreadySetException(path.getFullPath(), true); |
| } |
| } else { |
| if (cur.isUseTemplate() && upperTemplate.hasSchema(nodeNames[i])) { |
| throw new PathAlreadyExistException( |
| cur.getPartialPath().concatNode(nodeNames[i]).getFullPath()); |
| } |
| IStorageGroupMNode storageGroupMNode = |
| new StorageGroupMNode( |
| cur, nodeNames[i], IoTDBDescriptor.getInstance().getConfig().getDefaultTTL()); |
| cur.addChild(nodeNames[i], storageGroupMNode); |
| } |
| } |
| } |
| |
| private void checkStorageGroup(String storageGroup) throws IllegalPathException { |
| if (!IoTDBConfig.STORAGE_GROUP_PATTERN.matcher(storageGroup).matches()) { |
| throw new IllegalPathException( |
| String.format( |
| "The storage group name can only be characters, numbers and underscores. %s", |
| storageGroup)); |
| } |
| } |
| |
| /** Delete a storage group */ |
| List<IMeasurementMNode> deleteStorageGroup(PartialPath path) throws MetadataException { |
| IMNode cur = getNodeByPath(path); |
| if (!(cur.isStorageGroup())) { |
| throw new StorageGroupNotSetException(path.getFullPath()); |
| } |
| // Suppose current system has root.a.b.sg1, root.a.sg2, and delete root.a.b.sg1 |
| // delete the storage group node sg1 |
| cur.getParent().deleteChild(cur.getName()); |
| |
| // collect all the LeafMNode in this storage group |
| List<IMeasurementMNode> leafMNodes = new LinkedList<>(); |
| Queue<IMNode> queue = new LinkedList<>(); |
| queue.add(cur); |
| while (!queue.isEmpty()) { |
| IMNode node = queue.poll(); |
| for (IMNode child : node.getChildren().values()) { |
| if (child.isMeasurement()) { |
| leafMNodes.add((IMeasurementMNode) child); |
| } else { |
| queue.add(child); |
| } |
| } |
| } |
| |
| cur = cur.getParent(); |
| // delete node b while retain root.a.sg2 |
| while (!IoTDBConstant.PATH_ROOT.equals(cur.getName()) && cur.getChildren().size() == 0) { |
| cur.getParent().deleteChild(cur.getName()); |
| cur = cur.getParent(); |
| } |
| return leafMNodes; |
| } |
| |
| /** |
| * Check whether path is storage group or not |
| * |
| * <p>e.g., path = root.a.b.sg. if nor a and b is StorageGroupMNode and sg is a StorageGroupMNode |
| * path is a storage group |
| * |
| * @param path path |
| * @apiNote :for cluster |
| */ |
| boolean isStorageGroup(PartialPath path) { |
| String[] nodeNames = path.getNodes(); |
| if (nodeNames.length <= 1 || !nodeNames[0].equals(IoTDBConstant.PATH_ROOT)) { |
| return false; |
| } |
| IMNode cur = root; |
| int i = 1; |
| while (i < nodeNames.length - 1) { |
| cur = cur.getChild(nodeNames[i]); |
| if (cur == null || cur.isStorageGroup()) { |
| return false; |
| } |
| i++; |
| } |
| cur = cur.getChild(nodeNames[i]); |
| return cur != null && cur.isStorageGroup(); |
| } |
| |
| /** |
| * Delete path. The path should be a full path from root to leaf node |
| * |
| * @param path Format: root.node(.node)+ |
| */ |
| Pair<PartialPath, IMeasurementMNode> deleteTimeseriesAndReturnEmptyStorageGroup(PartialPath path) |
| throws MetadataException { |
| IMNode curNode = getNodeByPath(path); |
| if (!(curNode.isMeasurement())) { |
| throw new PathNotExistException(path.getFullPath()); |
| } |
| String[] nodes = path.getNodes(); |
| if (nodes.length == 0 || !IoTDBConstant.PATH_ROOT.equals(nodes[0])) { |
| throw new IllegalPathException(path.getFullPath()); |
| } |
| |
| IMeasurementMNode deletedNode = (IMeasurementMNode) curNode; |
| |
| // delete the last node of path |
| curNode.getParent().deleteChild(path.getMeasurement()); |
| if (deletedNode.getAlias() != null) { |
| deletedNode.getParent().deleteAliasChild(((IMeasurementMNode) curNode).getAlias()); |
| } |
| curNode = curNode.getParent(); |
| // delete all empty ancestors except storage group and MeasurementMNode |
| while (!IoTDBConstant.PATH_ROOT.equals(curNode.getName()) |
| && !(curNode.isMeasurement()) |
| && curNode.getChildren().size() == 0) { |
| // if current storage group has no time series, return the storage group name |
| if (curNode.isStorageGroup()) { |
| return new Pair<>(curNode.getPartialPath(), deletedNode); |
| } |
| curNode.getParent().deleteChild(curNode.getName()); |
| curNode = curNode.getParent(); |
| } |
| return new Pair<>(null, deletedNode); |
| } |
| |
| /** |
| * Get measurement schema for a given path. Path must be a complete Path from root to leaf node. |
| */ |
| IMeasurementSchema getSchema(PartialPath path) throws MetadataException { |
| IMeasurementMNode node = (IMeasurementMNode) getNodeByPath(path); |
| return node.getSchema(); |
| } |
| |
| /** |
| * Get node by path with storage group check If storage group is not set, |
| * StorageGroupNotSetException will be thrown |
| */ |
| Pair<IMNode, Template> getNodeByPathWithStorageGroupCheck(PartialPath path) |
| throws MetadataException { |
| boolean storageGroupChecked = false; |
| String[] nodes = path.getNodes(); |
| if (nodes.length == 0 || !nodes[0].equals(root.getName())) { |
| throw new IllegalPathException(path.getFullPath()); |
| } |
| |
| IMNode cur = root; |
| Template upperTemplate = null; |
| |
| for (int i = 1; i < nodes.length; i++) { |
| upperTemplate = cur.getSchemaTemplate() == null ? upperTemplate : cur.getSchemaTemplate(); |
| cur = cur.getChild(nodes[i]); |
| if (cur == null) { |
| // not find |
| if (!storageGroupChecked) { |
| throw new StorageGroupNotSetException(path.getFullPath()); |
| } |
| throw new PathNotExistException(path.getFullPath()); |
| } |
| |
| if (cur.isStorageGroup()) { |
| storageGroupChecked = true; |
| } |
| } |
| |
| if (!storageGroupChecked) { |
| throw new StorageGroupNotSetException(path.getFullPath()); |
| } |
| return new Pair<>(cur, upperTemplate); |
| } |
| |
| /** |
| * E.g., root.sg is storage group given [root, sg], return the MNode of root.sg given [root, sg, |
| * device], throw exception Get storage group node, if the give path is not a storage group, throw |
| * exception |
| */ |
| IStorageGroupMNode getStorageGroupNodeByStorageGroupPath(PartialPath path) |
| throws MetadataException { |
| IMNode node = getNodeByPath(path); |
| if (node.isStorageGroup()) { |
| return (IStorageGroupMNode) node; |
| } else { |
| throw new StorageGroupNotSetException(path.getFullPath(), true); |
| } |
| } |
| |
| /** |
| * E.g., root.sg is storage group given [root, sg], return the MNode of root.sg given [root, sg, |
| * device], return the MNode of root.sg Get storage group node, the give path don't need to be |
| * storage group path. |
| */ |
| IStorageGroupMNode getStorageGroupNodeByPath(PartialPath path) throws MetadataException { |
| String[] nodes = path.getNodes(); |
| if (nodes.length == 0 || !nodes[0].equals(root.getName())) { |
| throw new IllegalPathException(path.getFullPath()); |
| } |
| IMNode cur = root; |
| for (int i = 1; i < nodes.length; i++) { |
| cur = cur.getChild(nodes[i]); |
| if (cur == null) { |
| break; |
| } |
| if (cur.isStorageGroup()) { |
| return (IStorageGroupMNode) cur; |
| } |
| } |
| throw new StorageGroupNotSetException(path.getFullPath()); |
| } |
| |
| /** |
| * Get node by the path |
| * |
| * @return last node in given seriesPath |
| */ |
| IMNode getNodeByPath(PartialPath path) throws MetadataException { |
| String[] nodes = path.getNodes(); |
| if (nodes.length == 0 || !nodes[0].equals(root.getName())) { |
| throw new IllegalPathException(path.getFullPath()); |
| } |
| IMNode cur = root; |
| Template upperTemplate = cur.getSchemaTemplate(); |
| |
| for (int i = 1; i < nodes.length; i++) { |
| if (cur.isMeasurement()) { |
| if (i == nodes.length - 1 |
| || ((IMeasurementMNode) cur).getSchema() instanceof VectorMeasurementSchema) { |
| return cur; |
| } else { |
| throw new PathNotExistException(path.getFullPath(), true); |
| } |
| } |
| if (cur.getSchemaTemplate() != null) { |
| upperTemplate = cur.getSchemaTemplate(); |
| } |
| IMNode next = cur.getChild(nodes[i]); |
| if (next == null) { |
| if (upperTemplate == null) { |
| throw new PathNotExistException(path.getFullPath(), true); |
| } |
| |
| String realName = nodes[i]; |
| IMeasurementSchema schema = upperTemplate.getSchemaMap().get(realName); |
| if (schema == null) { |
| throw new PathNotExistException(path.getFullPath(), true); |
| } |
| return new MeasurementMNode(cur, schema.getMeasurementId(), schema, null); |
| } |
| cur = next; |
| } |
| return cur; |
| } |
| |
| /** |
| * Get all storage groups under the given path |
| * |
| * @return storage group list |
| * @apiNote :for cluster |
| */ |
| List<String> getStorageGroupByPath(PartialPath path) throws MetadataException { |
| List<String> storageGroups = new ArrayList<>(); |
| String[] nodes = path.getNodes(); |
| if (nodes.length == 0 || !nodes[0].equals(root.getName())) { |
| throw new IllegalPathException(path.getFullPath()); |
| } |
| findStorageGroup(root, nodes, 1, "", storageGroups); |
| return storageGroups; |
| } |
| |
| /** |
| * Recursively find all storage group according to a specific path |
| * |
| * @apiNote :for cluster |
| */ |
| private void findStorageGroup( |
| IMNode node, String[] nodes, int idx, String parent, List<String> storageGroupNames) { |
| if (node.isStorageGroup()) { |
| storageGroupNames.add(node.getFullPath()); |
| return; |
| } |
| String nodeReg = MetaUtils.getNodeRegByIdx(idx, nodes); |
| if (!(PATH_WILDCARD).equals(nodeReg)) { |
| IMNode next = node.getChild(nodeReg); |
| if (next != null) { |
| findStorageGroup( |
| next, nodes, idx + 1, parent + node.getName() + PATH_SEPARATOR, storageGroupNames); |
| } |
| } else { |
| for (IMNode child : node.getChildren().values()) { |
| findStorageGroup( |
| child, nodes, idx + 1, parent + node.getName() + PATH_SEPARATOR, storageGroupNames); |
| } |
| } |
| } |
| |
| /** |
| * Get all storage group names |
| * |
| * @return a list contains all distinct storage groups |
| */ |
| List<PartialPath> getAllStorageGroupPaths() { |
| List<PartialPath> res = new ArrayList<>(); |
| Deque<IMNode> nodeStack = new ArrayDeque<>(); |
| nodeStack.add(root); |
| while (!nodeStack.isEmpty()) { |
| IMNode current = nodeStack.pop(); |
| if (current.isStorageGroup()) { |
| res.add(current.getPartialPath()); |
| } else { |
| nodeStack.addAll(current.getChildren().values()); |
| } |
| } |
| return res; |
| } |
| |
| /** |
| * Get the storage group that given path belonged to or under given path All related storage |
| * groups refer two cases: 1. Storage groups with a prefix that is identical to path, e.g. given |
| * path "root.sg1", storage group "root.sg1.sg2" and "root.sg1.sg3" will be added into result |
| * list. 2. Storage group that this path belongs to, e.g. given path "root.sg1.d1", and it is in |
| * storage group "root.sg1". Then we adds "root.sg1" into result list. |
| * |
| * @return a list contains all storage groups related to given path |
| */ |
| List<PartialPath> searchAllRelatedStorageGroups(PartialPath path) throws MetadataException { |
| String[] nodes = path.getNodes(); |
| if (nodes.length == 0 || !nodes[0].equals(root.getName())) { |
| throw new IllegalPathException(path.getFullPath()); |
| } |
| List<PartialPath> storageGroupPaths = new ArrayList<>(); |
| findStorageGroupPaths(root, nodes, 1, "", storageGroupPaths, false); |
| return storageGroupPaths; |
| } |
| |
| /** |
| * Get all storage group under given path |
| * |
| * @return a list contains all storage group names under give path |
| */ |
| List<PartialPath> getStorageGroupPaths(PartialPath prefixPath) throws MetadataException { |
| String[] nodes = prefixPath.getNodes(); |
| if (nodes.length == 0 || !nodes[0].equals(root.getName())) { |
| throw new IllegalPathException(prefixPath.getFullPath()); |
| } |
| List<PartialPath> storageGroupPaths = new ArrayList<>(); |
| findStorageGroupPaths(root, nodes, 1, "", storageGroupPaths, true); |
| return storageGroupPaths; |
| } |
| |
| /** |
| * Traverse the MTree to match all storage group with prefix path. When trying to find storage |
| * groups via a path, we divide into two cases: 1. This path is only regarded as a prefix, in |
| * other words, this path is part of the result storage groups. 2. This path is a full path and we |
| * use this method to find its belonged storage group. When prefixOnly is set to true, storage |
| * group paths in 1 is only added into result, otherwise, both 1 and 2 are returned. |
| * |
| * @param node the current traversing node |
| * @param nodes split the prefix path with '.' |
| * @param idx the current index of array nodes |
| * @param parent current parent path |
| * @param storageGroupPaths store all matched storage group names |
| * @param prefixOnly only return storage groups that start with this prefix path |
| */ |
| private void findStorageGroupPaths( |
| IMNode node, |
| String[] nodes, |
| int idx, |
| String parent, |
| List<PartialPath> storageGroupPaths, |
| boolean prefixOnly) { |
| if (node.isStorageGroup() && (!prefixOnly || idx >= nodes.length)) { |
| storageGroupPaths.add(node.getPartialPath()); |
| return; |
| } |
| String nodeReg = MetaUtils.getNodeRegByIdx(idx, nodes); |
| if (!(PATH_WILDCARD).equals(nodeReg)) { |
| IMNode next = node.getChild(nodeReg); |
| if (next != null) { |
| findStorageGroupPaths( |
| node.getChild(nodeReg), |
| nodes, |
| idx + 1, |
| parent + node.getName() + PATH_SEPARATOR, |
| storageGroupPaths, |
| prefixOnly); |
| } |
| } else { |
| for (IMNode child : node.getChildren().values()) { |
| findStorageGroupPaths( |
| child, |
| nodes, |
| idx + 1, |
| parent + node.getName() + PATH_SEPARATOR, |
| storageGroupPaths, |
| prefixOnly); |
| } |
| } |
| } |
| |
| /** Get all storage group MNodes */ |
| List<IStorageGroupMNode> getAllStorageGroupNodes() { |
| List<IStorageGroupMNode> ret = new ArrayList<>(); |
| Deque<IMNode> nodeStack = new ArrayDeque<>(); |
| nodeStack.add(root); |
| while (!nodeStack.isEmpty()) { |
| IMNode current = nodeStack.pop(); |
| if (current.isStorageGroup()) { |
| ret.add((IStorageGroupMNode) current); |
| } else { |
| nodeStack.addAll(current.getChildren().values()); |
| } |
| } |
| return ret; |
| } |
| |
| /** |
| * Get storage group path by path |
| * |
| * <p>e.g., root.sg1 is storage group, path is root.sg1.d1, return root.sg1 |
| * |
| * @return storage group in the given path |
| */ |
| PartialPath getStorageGroupPath(PartialPath path) throws StorageGroupNotSetException { |
| String[] nodes = path.getNodes(); |
| IMNode cur = root; |
| for (int i = 1; i < nodes.length; i++) { |
| cur = cur.getChild(nodes[i]); |
| if (cur == null) { |
| throw new StorageGroupNotSetException(path.getFullPath()); |
| } else if (cur.isStorageGroup()) { |
| return cur.getPartialPath(); |
| } |
| } |
| throw new StorageGroupNotSetException(path.getFullPath()); |
| } |
| |
| /** Check whether the given path contains a storage group */ |
| boolean checkStorageGroupByPath(PartialPath path) { |
| String[] nodes = path.getNodes(); |
| IMNode cur = root; |
| for (int i = 1; i < nodes.length; i++) { |
| cur = cur.getChild(nodes[i]); |
| if (cur == null) { |
| return false; |
| } else if (cur.isStorageGroup()) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * Get all timeseries under the given path |
| * |
| * @param prefixPath a prefix path or a full path, may contain '*'. |
| */ |
| List<PartialPath> getAllTimeseriesPath(PartialPath prefixPath) throws MetadataException { |
| ShowTimeSeriesPlan plan = new ShowTimeSeriesPlan(prefixPath); |
| List<Pair<PartialPath, String[]>> res = getAllMeasurementSchema(plan); |
| List<PartialPath> paths = new ArrayList<>(); |
| for (Pair<PartialPath, String[]> p : res) { |
| paths.add(p.left); |
| } |
| return paths; |
| } |
| |
| /** |
| * Get all timeseries paths under the given path |
| * |
| * @param prefixPath a prefix path or a full path, may contain '*'. |
| * @return Pair.left contains all the satisfied paths Pair.right means the current offset or zero |
| * if we don't set offset. |
| */ |
| Pair<List<PartialPath>, Integer> getAllTimeseriesPathWithAlias( |
| PartialPath prefixPath, int limit, int offset) throws MetadataException { |
| PartialPath prePath = new PartialPath(prefixPath.getNodes()); |
| ShowTimeSeriesPlan plan = new ShowTimeSeriesPlan(prefixPath); |
| plan.setLimit(limit); |
| plan.setOffset(offset); |
| List<Pair<PartialPath, String[]>> res = getAllMeasurementSchema(plan, false); |
| List<PartialPath> paths = new ArrayList<>(); |
| for (Pair<PartialPath, String[]> p : res) { |
| if (prePath.getMeasurement().equals(p.right[0])) { |
| p.left.setMeasurementAlias(p.right[0]); |
| } |
| paths.add(p.left); |
| } |
| if (curOffset.get() == null) { |
| offset = 0; |
| } else { |
| offset = curOffset.get() + 1; |
| } |
| curOffset.remove(); |
| return new Pair<>(paths, offset); |
| } |
| |
| /** |
| * Get the count of timeseries under the given prefix path. if prefixPath contains '*', then not |
| * throw PathNotExistException() |
| * |
| * @param prefixPath a prefix path or a full path, may contain '*'. |
| */ |
| int getAllTimeseriesCount(PartialPath prefixPath) throws MetadataException { |
| String[] nodes = prefixPath.getNodes(); |
| if (nodes.length == 0 || !nodes[0].equals(root.getName())) { |
| throw new IllegalPathException(prefixPath.getFullPath()); |
| } |
| try { |
| return getCount(root, nodes, 1, false); |
| } catch (PathNotExistException e) { |
| throw new PathNotExistException(prefixPath.getFullPath()); |
| } |
| } |
| |
| /** Traverse the MTree to get the count of timeseries. */ |
| private int getCount(IMNode node, String[] nodes, int idx, boolean wildcard) |
| throws PathNotExistException { |
| if (node.isMeasurement()) { |
| if (idx < nodes.length) { |
| if (((IMeasurementMNode) node).getSchema().isCompatible(nodes[idx])) { |
| return 1; |
| } else { |
| if (!wildcard) { |
| throw new PathNotExistException(node.getName() + NO_CHILDNODE_MSG + nodes[idx]); |
| } else { |
| return 0; |
| } |
| } |
| } else { |
| return ((IMeasurementMNode) node).getMeasurementCount(); |
| } |
| } |
| if (idx < nodes.length) { |
| if (PATH_WILDCARD.equals(nodes[idx])) { |
| int sum = 0; |
| for (IMNode child : node.getChildren().values()) { |
| sum += getCount(child, nodes, idx + 1, true); |
| } |
| return sum; |
| } else { |
| IMNode child = node.getChild(nodes[idx]); |
| if (child == null) { |
| if (node.isUseTemplate() |
| && node.getUpperTemplate().getSchemaMap().containsKey(nodes[idx])) { |
| return 1; |
| } |
| if (!wildcard) { |
| throw new PathNotExistException(node.getName() + NO_CHILDNODE_MSG + nodes[idx]); |
| } else { |
| return 0; |
| } |
| } |
| return getCount(child, nodes, idx + 1, wildcard); |
| } |
| } else { |
| int sum = 0; |
| if (node.isUseTemplate()) { |
| sum += node.getUpperTemplate().getSchemaMap().size(); |
| } |
| for (IMNode child : node.getChildren().values()) { |
| sum += getCount(child, nodes, idx + 1, wildcard); |
| } |
| return sum; |
| } |
| } |
| |
| /** |
| * Get the count of devices under the given prefix path. |
| * |
| * @param prefixPath a prefix path or a full path, may contain '*'. |
| */ |
| int getDevicesNum(PartialPath prefixPath) throws MetadataException { |
| String[] nodes = prefixPath.getNodes(); |
| if (nodes.length == 0 || !nodes[0].equals(root.getName())) { |
| throw new IllegalPathException(prefixPath.getFullPath()); |
| } |
| return getDevicesCount(root, nodes, 1); |
| } |
| |
| /** |
| * Get the count of storage group under the given prefix path. |
| * |
| * @param prefixPath a prefix path or a full path, may contain '*'. |
| */ |
| int getStorageGroupNum(PartialPath prefixPath) throws MetadataException { |
| String[] nodes = prefixPath.getNodes(); |
| if (nodes.length == 0 || !nodes[0].equals(root.getName())) { |
| throw new IllegalPathException(prefixPath.getFullPath()); |
| } |
| return getStorageGroupCount(root, nodes, 1, ""); |
| } |
| |
| /** Get the count of nodes in the given level under the given prefix path. */ |
| int getNodesCountInGivenLevel(PartialPath prefixPath, int level) throws MetadataException { |
| String[] nodes = prefixPath.getNodes(); |
| if (nodes.length == 0 || !nodes[0].equals(root.getName())) { |
| throw new IllegalPathException(prefixPath.getFullPath()); |
| } |
| IMNode node = root; |
| int i; |
| for (i = 1; i < nodes.length; i++) { |
| if (nodes[i].equals("*")) { |
| break; |
| } |
| if (node.getChild(nodes[i]) != null) { |
| node = node.getChild(nodes[i]); |
| } else { |
| throw new MetadataException(nodes[i - 1] + NO_CHILDNODE_MSG + nodes[i]); |
| } |
| } |
| return getCountInGivenLevel(node, level - (i - 1)); |
| } |
| |
| /** Traverse the MTree to get the count of devices. */ |
| private int getDevicesCount(IMNode node, String[] nodes, int idx) { |
| String nodeReg = MetaUtils.getNodeRegByIdx(idx, nodes); |
| boolean curIsDevice = node.isUseTemplate(); |
| int cnt = curIsDevice ? 1 : 0; |
| if (!(PATH_WILDCARD).equals(nodeReg)) { |
| IMNode next = node.getChild(nodeReg); |
| if (next != null) { |
| if (next.isMeasurement() && idx >= nodes.length && !curIsDevice) { |
| cnt++; |
| } else { |
| cnt += getDevicesCount(node.getChild(nodeReg), nodes, idx + 1); |
| } |
| } |
| } else { |
| for (IMNode child : node.getChildren().values()) { |
| if (child.isMeasurement() && !curIsDevice && idx >= nodes.length) { |
| cnt++; |
| curIsDevice = true; |
| } |
| cnt += getDevicesCount(child, nodes, idx + 1); |
| } |
| } |
| return cnt; |
| } |
| |
| /** Traverse the MTree to get the count of storage group. */ |
| private int getStorageGroupCount(IMNode node, String[] nodes, int idx, String parent) { |
| int cnt = 0; |
| if (node.isStorageGroup() && idx >= nodes.length) { |
| cnt++; |
| return cnt; |
| } |
| String nodeReg = MetaUtils.getNodeRegByIdx(idx, nodes); |
| if (!(PATH_WILDCARD).equals(nodeReg)) { |
| IMNode next = node.getChild(nodeReg); |
| if (next != null) { |
| cnt += getStorageGroupCount(next, nodes, idx + 1, parent + node.getName() + PATH_SEPARATOR); |
| } |
| } else { |
| for (IMNode child : node.getChildren().values()) { |
| cnt += |
| getStorageGroupCount(child, nodes, idx + 1, parent + node.getName() + PATH_SEPARATOR); |
| } |
| } |
| return cnt; |
| } |
| |
| /** |
| * Traverse the MTree to get the count of timeseries in the given level. |
| * |
| * @param targetLevel Record the distance to the target level, 0 means the target level. |
| */ |
| private int getCountInGivenLevel(IMNode node, int targetLevel) { |
| if (targetLevel == 0) { |
| return 1; |
| } |
| int cnt = 0; |
| for (IMNode child : node.getChildren().values()) { |
| cnt += getCountInGivenLevel(child, targetLevel - 1); |
| } |
| return cnt; |
| } |
| |
| /** |
| * Get all time series schema under the given path order by insert frequency |
| * |
| * <p>result: [name, alias, storage group, dataType, encoding, compression, offset] |
| */ |
| List<Pair<PartialPath, String[]>> getAllMeasurementSchemaByHeatOrder( |
| ShowTimeSeriesPlan plan, QueryContext queryContext) throws MetadataException { |
| String[] nodes = plan.getPath().getNodes(); |
| if (nodes.length == 0 || !nodes[0].equals(root.getName())) { |
| throw new IllegalPathException(plan.getPath().getFullPath()); |
| } |
| List<Pair<PartialPath, String[]>> allMatchedNodes = new ArrayList<>(); |
| |
| findPath(root, nodes, 1, allMatchedNodes, false, true, queryContext, null); |
| |
| Stream<Pair<PartialPath, String[]>> sortedStream = |
| allMatchedNodes.stream() |
| .sorted( |
| Comparator.comparingLong( |
| (Pair<PartialPath, String[]> p) -> Long.parseLong(p.right[6])) |
| .reversed() |
| .thenComparing((Pair<PartialPath, String[]> p) -> p.left)); |
| |
| // no limit |
| if (plan.getLimit() == 0) { |
| return sortedStream.collect(toList()); |
| } else { |
| return sortedStream.skip(plan.getOffset()).limit(plan.getLimit()).collect(toList()); |
| } |
| } |
| |
| /** |
| * Get all time series schema under the given path |
| * |
| * <p>result: [name, alias, storage group, dataType, encoding, compression, offset] |
| */ |
| List<Pair<PartialPath, String[]>> getAllMeasurementSchema(ShowTimeSeriesPlan plan) |
| throws MetadataException { |
| return getAllMeasurementSchema(plan, true); |
| } |
| |
| List<Pair<PartialPath, String[]>> getAllMeasurementSchema( |
| ShowTimeSeriesPlan plan, boolean removeCurrentOffset) throws MetadataException { |
| List<Pair<PartialPath, String[]>> res = new LinkedList<>(); |
| String[] nodes = plan.getPath().getNodes(); |
| if (nodes.length == 0 || !nodes[0].equals(root.getName())) { |
| throw new IllegalPathException(plan.getPath().getFullPath()); |
| } |
| limit.set(plan.getLimit()); |
| offset.set(plan.getOffset()); |
| curOffset.set(-1); |
| count.set(0); |
| findPath(root, nodes, 1, res, offset.get() != 0 || limit.get() != 0, false, null, null); |
| // avoid memory leaks |
| limit.remove(); |
| offset.remove(); |
| if (removeCurrentOffset) { |
| curOffset.remove(); |
| } |
| count.remove(); |
| return res; |
| } |
| |
| /** |
| * Iterate through MTree to fetch metadata info of all leaf nodes under the given seriesPath |
| * |
| * @param needLast if false, lastTimeStamp in timeseriesSchemaList will be null |
| * @param timeseriesSchemaList List<timeseriesSchema> result: [name, alias, storage group, |
| * dataType, encoding, compression, offset, lastTimeStamp] |
| */ |
| @SuppressWarnings("squid:S3776") // Suppress high Cognitive Complexity warning |
| private void findPath( |
| IMNode node, |
| String[] nodes, |
| int idx, |
| List<Pair<PartialPath, String[]>> timeseriesSchemaList, |
| boolean hasLimit, |
| boolean needLast, |
| QueryContext queryContext, |
| Template upperTemplate) |
| throws MetadataException { |
| if (node.isMeasurement()) { |
| if ((nodes.length <= idx |
| || ((IMeasurementMNode) node).getSchema() instanceof VectorMeasurementSchema)) { |
| if (hasLimit) { |
| curOffset.set(curOffset.get() + 1); |
| if (curOffset.get() < offset.get() || count.get().intValue() == limit.get().intValue()) { |
| return; |
| } |
| } |
| IMeasurementSchema measurementSchema = ((IMeasurementMNode) node).getSchema(); |
| if (measurementSchema instanceof MeasurementSchema) { |
| addMeasurementSchema( |
| node, timeseriesSchemaList, needLast, queryContext, measurementSchema, "*"); |
| } else if (measurementSchema instanceof VectorMeasurementSchema) { |
| addVectorMeasurementSchema( |
| node, |
| timeseriesSchemaList, |
| needLast, |
| queryContext, |
| measurementSchema, |
| idx < nodes.length ? nodes[idx] : "*"); |
| } |
| if (hasLimit) { |
| count.set(count.get() + 1); |
| } |
| } |
| return; |
| } |
| |
| String nodeReg = MetaUtils.getNodeRegByIdx(idx, nodes); |
| if (node.getSchemaTemplate() != null) { |
| upperTemplate = node.getSchemaTemplate(); |
| } |
| |
| // we should use template when all child is measurement or this node has no child |
| if (!nodeReg.contains(PATH_WILDCARD)) { |
| IMNode next = node.getChild(nodeReg); |
| if (next != null) { |
| findPath( |
| next, |
| nodes, |
| idx + 1, |
| timeseriesSchemaList, |
| hasLimit, |
| needLast, |
| queryContext, |
| upperTemplate); |
| } |
| } else { |
| for (IMNode child : node.getChildren().values()) { |
| if (!Pattern.matches(nodeReg.replace("*", ".*"), child.getName())) { |
| continue; |
| } |
| findPath( |
| child, |
| nodes, |
| idx + 1, |
| timeseriesSchemaList, |
| hasLimit, |
| needLast, |
| queryContext, |
| upperTemplate); |
| if (hasLimit && count.get().intValue() == limit.get().intValue()) { |
| return; |
| } |
| } |
| } |
| |
| // template part |
| if (node.isUseTemplate()) { |
| if (upperTemplate != null) { |
| HashSet<IMeasurementSchema> set = new HashSet<>(); |
| for (IMeasurementSchema schema : upperTemplate.getSchemaMap().values()) { |
| if (set.add(schema)) { |
| if (schema instanceof MeasurementSchema) { |
| addMeasurementSchema( |
| new MeasurementMNode(node, schema.getMeasurementId(), schema, null), |
| timeseriesSchemaList, |
| needLast, |
| queryContext, |
| schema, |
| nodeReg); |
| } else if (schema instanceof VectorMeasurementSchema) { |
| VectorMeasurementSchema vectorMeasurementSchema = (VectorMeasurementSchema) schema; |
| if (Pattern.matches( |
| nodeReg.replace("*", ".*"), vectorMeasurementSchema.getMeasurementId())) { |
| String firstNode = schema.getValueMeasurementIdList().get(0); |
| addVectorMeasurementSchemaForTemplate( |
| new MeasurementMNode(node, firstNode, schema, null), |
| timeseriesSchemaList, |
| needLast, |
| queryContext, |
| schema, |
| MetaUtils.getNodeRegByIdx(idx + 1, nodes), |
| vectorMeasurementSchema.getMeasurementId()); |
| } |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| private void addMeasurementSchema( |
| IMNode node, |
| List<Pair<PartialPath, String[]>> timeseriesSchemaList, |
| boolean needLast, |
| QueryContext queryContext, |
| IMeasurementSchema measurementSchema, |
| String reg) |
| throws StorageGroupNotSetException { |
| if (Pattern.matches(reg.replace("*", ".*"), measurementSchema.getMeasurementId())) { |
| PartialPath nodePath = node.getPartialPath(); |
| String[] tsRow = new String[7]; |
| tsRow[0] = ((IMeasurementMNode) node).getAlias(); |
| tsRow[1] = getStorageGroupPath(nodePath).getFullPath(); |
| tsRow[2] = measurementSchema.getType().toString(); |
| tsRow[3] = measurementSchema.getEncodingType().toString(); |
| tsRow[4] = measurementSchema.getCompressor().toString(); |
| tsRow[5] = String.valueOf(((IMeasurementMNode) node).getOffset()); |
| tsRow[6] = |
| needLast |
| ? String.valueOf(getLastTimeStamp((IMeasurementMNode) node, queryContext)) |
| : null; |
| Pair<PartialPath, String[]> temp = new Pair<>(nodePath, tsRow); |
| timeseriesSchemaList.add(temp); |
| } |
| } |
| |
| private void addVectorMeasurementSchema( |
| IMNode node, |
| List<Pair<PartialPath, String[]>> timeseriesSchemaList, |
| boolean needLast, |
| QueryContext queryContext, |
| IMeasurementSchema schema, |
| String reg) |
| throws StorageGroupNotSetException, IllegalPathException { |
| List<String> measurements = schema.getValueMeasurementIdList(); |
| for (int i = 0; i < measurements.size(); i++) { |
| if (!Pattern.matches(reg.replace("*", ".*"), measurements.get(i))) { |
| continue; |
| } |
| PartialPath devicePath = node.getPartialPath(); |
| String[] tsRow = new String[7]; |
| tsRow[0] = null; |
| tsRow[1] = getStorageGroupPath(devicePath).getFullPath(); |
| tsRow[2] = schema.getValueTSDataTypeList().get(i).toString(); |
| tsRow[3] = schema.getValueTSEncodingList().get(i).toString(); |
| tsRow[4] = schema.getCompressor().toString(); |
| tsRow[5] = "-1"; |
| tsRow[6] = |
| needLast |
| ? String.valueOf(getLastTimeStamp((IMeasurementMNode) node, queryContext)) |
| : null; |
| Pair<PartialPath, String[]> temp = |
| new Pair<>(new PartialPath(devicePath.getFullPath(), measurements.get(i)), tsRow); |
| timeseriesSchemaList.add(temp); |
| } |
| } |
| |
| private void addVectorMeasurementSchemaForTemplate( |
| IMNode node, |
| List<Pair<PartialPath, String[]>> timeseriesSchemaList, |
| boolean needLast, |
| QueryContext queryContext, |
| IMeasurementSchema schema, |
| String reg, |
| String vectorId) |
| throws StorageGroupNotSetException, IllegalPathException { |
| List<String> measurements = schema.getValueMeasurementIdList(); |
| for (int i = 0; i < measurements.size(); i++) { |
| if (!Pattern.matches(reg.replace("*", ".*"), measurements.get(i))) { |
| continue; |
| } |
| PartialPath devicePath = |
| new PartialPath(node.getPartialPath().getDevicePath().getFullPath(), vectorId); |
| String[] tsRow = new String[7]; |
| tsRow[0] = null; |
| tsRow[1] = getStorageGroupPath(devicePath).getFullPath(); |
| tsRow[2] = schema.getValueTSDataTypeList().get(i).toString(); |
| tsRow[3] = schema.getValueTSEncodingList().get(i).toString(); |
| tsRow[4] = schema.getCompressor().toString(); |
| tsRow[5] = "-1"; |
| tsRow[6] = |
| needLast |
| ? String.valueOf(getLastTimeStamp((IMeasurementMNode) node, queryContext)) |
| : null; |
| Pair<PartialPath, String[]> temp = |
| new Pair<>(new PartialPath(devicePath.getFullPath(), measurements.get(i)), tsRow); |
| timeseriesSchemaList.add(temp); |
| } |
| } |
| |
| /** |
| * Get child node path in the next level of the given path. |
| * |
| * <p>e.g., MTree has [root.sg1.d1.s1, root.sg1.d1.s2, root.sg1.d2.s1] given path = root.sg1, |
| * return [root.sg1.d1, root.sg1.d2] |
| * |
| * @param path The given path |
| * @return All child nodes' seriesPath(s) of given seriesPath. |
| */ |
| Set<String> getChildNodePathInNextLevel(PartialPath path) throws MetadataException { |
| String[] nodes = path.getNodes(); |
| if (nodes.length == 0 || !nodes[0].equals(root.getName())) { |
| throw new IllegalPathException(path.getFullPath()); |
| } |
| Set<String> childNodePaths = new TreeSet<>(); |
| findChildNodePathInNextLevel(root, nodes, 1, "", childNodePaths, nodes.length + 1); |
| return childNodePaths; |
| } |
| |
| /** |
| * Traverse the MTree to match all child node path in next level |
| * |
| * @param node the current traversing node |
| * @param nodes split the prefix path with '.' |
| * @param idx the current index of array nodes |
| * @param parent store the node string having traversed |
| * @param res store all matched device names |
| * @param length expected length of path |
| */ |
| @SuppressWarnings("squid:S3776") // Suppress high Cognitive Complexity warning |
| private void findChildNodePathInNextLevel( |
| IMNode node, String[] nodes, int idx, String parent, Set<String> res, int length) { |
| if (node == null) { |
| return; |
| } |
| String nodeReg = MetaUtils.getNodeRegByIdx(idx, nodes); |
| if (!nodeReg.contains(PATH_WILDCARD)) { |
| if (idx == length) { |
| res.add(parent + node.getName()); |
| } else { |
| findChildNodePathInNextLevel( |
| node.getChild(nodeReg), |
| nodes, |
| idx + 1, |
| parent + node.getName() + PATH_SEPARATOR, |
| res, |
| length); |
| } |
| } else { |
| if (node.getChildren().size() > 0) { |
| for (IMNode child : node.getChildren().values()) { |
| if (!Pattern.matches(nodeReg.replace("*", ".*"), child.getName())) { |
| continue; |
| } |
| if (idx == length) { |
| res.add(parent + node.getName()); |
| } else { |
| findChildNodePathInNextLevel( |
| child, nodes, idx + 1, parent + node.getName() + PATH_SEPARATOR, res, length); |
| } |
| } |
| } else if (idx == length) { |
| String nodeName = node.getName(); |
| res.add(parent + nodeName); |
| } |
| } |
| } |
| |
| /** |
| * Get child node in the next level of the given path. |
| * |
| * <p>e.g., MTree has [root.sg1.d1.s1, root.sg1.d1.s2, root.sg1.d2.s1] given path = root.sg1, |
| * return [d1, d2] |
| * |
| * <p>e.g., MTree has [root.sg1.d1.s1, root.sg1.d1.s2, root.sg1.d2.s1] given path = root.sg1.d1 |
| * return [s1, s2] |
| * |
| * @param path Path |
| * @return All child nodes' seriesPath(s) of given seriesPath. |
| */ |
| Set<String> getChildNodeInNextLevel(PartialPath path) throws MetadataException { |
| String[] nodes = path.getNodes(); |
| if (nodes.length == 0 || !nodes[0].equals(root.getName())) { |
| throw new IllegalPathException(path.getFullPath()); |
| } |
| Set<String> childNodes = new TreeSet<>(); |
| findChildNodeInNextLevel(root, nodes, 1, "", childNodes, nodes.length + 1); |
| return childNodes; |
| } |
| |
| /** |
| * Traverse the MTree to match all child node path in next level |
| * |
| * @param node the current traversing node |
| * @param nodes split the prefix path with '.' |
| * @param idx the current index of array nodes |
| * @param parent store the node string having traversed |
| * @param res store all matched device names |
| * @param length expected length of path |
| */ |
| @SuppressWarnings("squid:S3776") // Suppress high Cognitive Complexity warning |
| private void findChildNodeInNextLevel( |
| IMNode node, String[] nodes, int idx, String parent, Set<String> res, int length) { |
| if (node == null) { |
| return; |
| } |
| String nodeReg = MetaUtils.getNodeRegByIdx(idx, nodes); |
| if (!nodeReg.contains(PATH_WILDCARD)) { |
| if (idx == length) { |
| res.add(node.getName()); |
| } else { |
| findChildNodeInNextLevel( |
| node.getChild(nodeReg), |
| nodes, |
| idx + 1, |
| parent + node.getName() + PATH_SEPARATOR, |
| res, |
| length); |
| } |
| } else { |
| if (node.getChildren().size() > 0) { |
| for (IMNode child : node.getChildren().values()) { |
| if (!Pattern.matches(nodeReg.replace("*", ".*"), child.getName())) { |
| continue; |
| } |
| if (idx == length) { |
| res.add(node.getName()); |
| } else { |
| findChildNodeInNextLevel( |
| child, nodes, idx + 1, parent + node.getName() + PATH_SEPARATOR, res, length); |
| } |
| } |
| } else if (idx == length) { |
| String nodeName = node.getName(); |
| res.add(nodeName); |
| } |
| } |
| } |
| |
| /** |
| * Get all devices under give path |
| * |
| * @return a list contains all distinct devices names |
| */ |
| Set<PartialPath> getDevices(PartialPath prefixPath) throws MetadataException { |
| String[] nodes = prefixPath.getNodes(); |
| if (nodes.length == 0 || !nodes[0].equals(root.getName())) { |
| throw new IllegalPathException(prefixPath.getFullPath()); |
| } |
| Set<PartialPath> devices = new TreeSet<>(); |
| findDevices(root, nodes, 1, devices, false, null); |
| return devices; |
| } |
| |
| List<ShowDevicesResult> getDevices(ShowDevicesPlan plan) throws MetadataException { |
| String[] nodes = plan.getPath().getNodes(); |
| if (nodes.length == 0 || !nodes[0].equals(root.getName())) { |
| throw new IllegalPathException(plan.getPath().getFullPath()); |
| } |
| Set<PartialPath> devices = new TreeSet<>(); |
| limit.set(plan.getLimit()); |
| offset.set(plan.getOffset()); |
| curOffset.set(-1); |
| count.set(0); |
| findDevices(root, nodes, 1, devices, offset.get() != 0 || limit.get() != 0, null); |
| // avoid memory leaks |
| limit.remove(); |
| offset.remove(); |
| curOffset.remove(); |
| count.remove(); |
| List<ShowDevicesResult> res = new ArrayList<>(); |
| for (PartialPath device : devices) { |
| if (plan.hasSgCol()) { |
| res.add( |
| new ShowDevicesResult(device.getFullPath(), getStorageGroupPath(device).getFullPath())); |
| } else { |
| res.add(new ShowDevicesResult(device.getFullPath())); |
| } |
| } |
| return res; |
| } |
| |
| /** |
| * Traverse the MTree to match all devices with prefix path. |
| * |
| * @param node the current traversing node |
| * @param nodes split the prefix path with '.' |
| * @param idx the current index of array nodes |
| * @param res store all matched device names |
| */ |
| @SuppressWarnings("squid:S3776") |
| private void findDevices( |
| IMNode node, |
| String[] nodes, |
| int idx, |
| Set<PartialPath> res, |
| boolean hasLimit, |
| Template upperTemplate) { |
| upperTemplate = node.getSchemaTemplate() == null ? upperTemplate : node.getSchemaTemplate(); |
| String nodeReg = MetaUtils.getNodeRegByIdx(idx, nodes); |
| // the node path doesn't contains '*' |
| if (!nodeReg.contains(PATH_WILDCARD)) { |
| IMNode next = node.getChild(nodeReg); |
| if (next != null) { |
| if (next.isMeasurement()) { |
| if (idx >= nodes.length) { |
| if (hasLimit) { |
| curOffset.set(curOffset.get() + 1); |
| if (curOffset.get() < offset.get() |
| || count.get().intValue() == limit.get().intValue()) { |
| return; |
| } |
| count.set(count.get() + 1); |
| } |
| res.add(node.getPartialPath()); |
| } |
| } else { |
| findDevices(next, nodes, idx + 1, res, hasLimit, upperTemplate); |
| } |
| } |
| } else { // the node path contains '*' |
| boolean deviceAdded = false; |
| List<IMNode> children = new ArrayList<>(node.getChildren().values()); |
| // template part |
| if (node.isUseTemplate() && upperTemplate != null) { |
| children.addAll(upperTemplate.getMeasurementMNode()); |
| } |
| |
| for (IMNode child : children) { |
| // use '.*' to replace '*' to form a regex to match |
| // if the match failed, skip it. |
| if (!Pattern.matches(nodeReg.replace("*", ".*"), child.getName())) { |
| continue; |
| } |
| if (child.isMeasurement()) { |
| if (!deviceAdded && idx >= nodes.length) { |
| if (hasLimit) { |
| curOffset.set(curOffset.get() + 1); |
| if (curOffset.get() < offset.get() |
| || count.get().intValue() == limit.get().intValue()) { |
| return; |
| } |
| count.set(count.get() + 1); |
| } |
| res.add(node.getPartialPath()); |
| deviceAdded = true; |
| } |
| } else { |
| findDevices(child, nodes, idx + 1, res, hasLimit, upperTemplate); |
| } |
| } |
| } |
| } |
| |
| /** Get all paths from root to the given level. */ |
| List<PartialPath> getNodesList(PartialPath path, int nodeLevel) throws MetadataException { |
| return getNodesList(path, nodeLevel, null); |
| } |
| |
| /** Get all paths from root to the given level */ |
| List<PartialPath> getNodesList(PartialPath path, int nodeLevel, StorageGroupFilter filter) |
| throws MetadataException { |
| String[] nodes = path.getNodes(); |
| if (!nodes[0].equals(root.getName())) { |
| throw new IllegalPathException(path.getFullPath()); |
| } |
| List<PartialPath> res = new ArrayList<>(); |
| IMNode node = root; |
| for (int i = 1; i < nodes.length; i++) { |
| if (node.getChild(nodes[i]) != null) { |
| node = node.getChild(nodes[i]); |
| if (node.isStorageGroup() && filter != null && !filter.satisfy(node.getFullPath())) { |
| return res; |
| } |
| } else { |
| throw new MetadataException(nodes[i - 1] + NO_CHILDNODE_MSG + nodes[i]); |
| } |
| } |
| findNodes(node, path, res, nodeLevel - (nodes.length - 1), filter); |
| return res; |
| } |
| |
| /** |
| * Get all paths under the given level. |
| * |
| * @param targetLevel Record the distance to the target level, 0 means the target level. |
| */ |
| private void findNodes( |
| IMNode node, |
| PartialPath path, |
| List<PartialPath> res, |
| int targetLevel, |
| StorageGroupFilter filter) { |
| if (node == null |
| || node.isStorageGroup() && filter != null && !filter.satisfy(node.getFullPath())) { |
| return; |
| } |
| if (targetLevel == 0) { |
| res.add(path); |
| return; |
| } |
| for (IMNode child : node.getChildren().values()) { |
| findNodes(child, path.concatNode(child.toString()), res, targetLevel - 1, filter); |
| } |
| } |
| |
| public void serializeTo(String snapshotPath) throws IOException { |
| try (MLogWriter mLogWriter = new MLogWriter(snapshotPath)) { |
| root.serializeTo(mLogWriter); |
| } |
| } |
| |
| public static MTree deserializeFrom(File mtreeSnapshot) { |
| try (MLogReader mLogReader = new MLogReader(mtreeSnapshot)) { |
| return new MTree(deserializeFromReader(mLogReader)); |
| } catch (IOException e) { |
| logger.warn("Failed to deserialize from {}. Use a new MTree.", mtreeSnapshot.getPath()); |
| return new MTree(); |
| } finally { |
| limit = new ThreadLocal<>(); |
| offset = new ThreadLocal<>(); |
| count = new ThreadLocal<>(); |
| curOffset = new ThreadLocal<>(); |
| } |
| } |
| |
| @SuppressWarnings("squid:S3776") // Suppress high Cognitive Complexity warning |
| private static InternalMNode deserializeFromReader(MLogReader mLogReader) { |
| Deque<IMNode> nodeStack = new ArrayDeque<>(); |
| IMNode node = null; |
| while (mLogReader.hasNext()) { |
| PhysicalPlan plan = null; |
| try { |
| plan = mLogReader.next(); |
| if (plan == null) { |
| continue; |
| } |
| int childrenSize = 0; |
| if (plan instanceof StorageGroupMNodePlan) { |
| node = StorageGroupMNode.deserializeFrom((StorageGroupMNodePlan) plan); |
| childrenSize = ((StorageGroupMNodePlan) plan).getChildSize(); |
| } else if (plan instanceof MeasurementMNodePlan) { |
| node = MeasurementMNode.deserializeFrom((MeasurementMNodePlan) plan); |
| childrenSize = ((MeasurementMNodePlan) plan).getChildSize(); |
| } else if (plan instanceof MNodePlan) { |
| node = InternalMNode.deserializeFrom((MNodePlan) plan); |
| childrenSize = ((MNodePlan) plan).getChildSize(); |
| } |
| |
| if (childrenSize != 0) { |
| ConcurrentHashMap<String, IMNode> childrenMap = new ConcurrentHashMap<>(); |
| for (int i = 0; i < childrenSize; i++) { |
| IMNode child = nodeStack.removeFirst(); |
| childrenMap.put(child.getName(), child); |
| if (child.isMeasurement()) { |
| if (!node.isEntity()) { |
| node = IEntityMNode.setToEntity(node); |
| } |
| String alias = ((IMeasurementMNode) child).getAlias(); |
| if (alias != null) { |
| ((IEntityMNode) node).addAlias(alias, (IMeasurementMNode) child); |
| } |
| } |
| child.setParent(node); |
| } |
| node.setChildren(childrenMap); |
| } |
| nodeStack.push(node); |
| } catch (Exception e) { |
| logger.error( |
| "Can not operate cmd {} for err:", plan == null ? "" : plan.getOperatorType(), e); |
| } |
| } |
| if (!IoTDBConstant.PATH_ROOT.equals(node.getName())) { |
| logger.error("Snapshot file corrupted!"); |
| // throw new MetadataException("Snapshot file corrupted!"); |
| } |
| |
| return (InternalMNode) node; |
| } |
| |
| @Override |
| public String toString() { |
| JsonObject jsonObject = new JsonObject(); |
| jsonObject.add(root.getName(), mNodeToJSON(root, null)); |
| return jsonToString(jsonObject); |
| } |
| |
| private JsonObject mNodeToJSON(IMNode node, String storageGroupName) { |
| JsonObject jsonObject = new JsonObject(); |
| if (node.getChildren().size() > 0) { |
| if (node.isStorageGroup()) { |
| storageGroupName = node.getFullPath(); |
| } |
| for (IMNode child : node.getChildren().values()) { |
| jsonObject.add(child.getName(), mNodeToJSON(child, storageGroupName)); |
| } |
| } else if (node.isMeasurement()) { |
| IMeasurementMNode leafMNode = (IMeasurementMNode) node; |
| jsonObject.add("DataType", GSON.toJsonTree(leafMNode.getSchema().getType())); |
| jsonObject.add("Encoding", GSON.toJsonTree(leafMNode.getSchema().getEncodingType())); |
| jsonObject.add("Compressor", GSON.toJsonTree(leafMNode.getSchema().getCompressor())); |
| if (leafMNode.getSchema().getProps() != null) { |
| jsonObject.addProperty("args", leafMNode.getSchema().getProps().toString()); |
| } |
| jsonObject.addProperty("StorageGroup", storageGroupName); |
| } |
| return jsonObject; |
| } |
| |
| Map<String, String> determineStorageGroup(PartialPath path) throws IllegalPathException { |
| Map<String, String> paths = new HashMap<>(); |
| String[] nodes = path.getNodes(); |
| if (nodes.length == 0 || !nodes[0].equals(root.getName())) { |
| throw new IllegalPathException(path.getFullPath()); |
| } |
| |
| Deque<IMNode> nodeStack = new ArrayDeque<>(); |
| Deque<Integer> depthStack = new ArrayDeque<>(); |
| if (!root.getChildren().isEmpty()) { |
| nodeStack.push(root); |
| depthStack.push(0); |
| } |
| |
| while (!nodeStack.isEmpty()) { |
| IMNode IMNode = nodeStack.removeFirst(); |
| int depth = depthStack.removeFirst(); |
| |
| determineStorageGroup(depth + 1, nodes, IMNode, paths, nodeStack, depthStack); |
| } |
| return paths; |
| } |
| |
| /** |
| * Try determining the storage group using the children of a mNode. If one child is a storage |
| * group node, put a storageGroupName-fullPath pair into paths. Otherwise put the children that |
| * match the path into the queue and discard other children. |
| */ |
| private void determineStorageGroup( |
| int depth, |
| String[] nodes, |
| IMNode IMNode, |
| Map<String, String> paths, |
| Deque<IMNode> nodeStack, |
| Deque<Integer> depthStack) { |
| String currNode = depth >= nodes.length ? PATH_WILDCARD : nodes[depth]; |
| for (Entry<String, IMNode> entry : IMNode.getChildren().entrySet()) { |
| if (!currNode.equals(PATH_WILDCARD) && !currNode.equals(entry.getKey())) { |
| continue; |
| } |
| // this child is desired |
| IMNode child = entry.getValue(); |
| if (child.isStorageGroup()) { |
| // we have found one storage group, record it |
| String sgName = child.getFullPath(); |
| // concat the remaining path with the storage group name |
| StringBuilder pathWithKnownSG = new StringBuilder(sgName); |
| for (int i = depth + 1; i < nodes.length; i++) { |
| pathWithKnownSG.append(IoTDBConstant.PATH_SEPARATOR).append(nodes[i]); |
| } |
| if (depth >= nodes.length - 1 && currNode.equals(PATH_WILDCARD)) { |
| // the we find the sg at the last node and the last node is a wildcard (find "root |
| // .group1", for "root.*"), also append the wildcard (to make "root.group1.*") |
| pathWithKnownSG.append(IoTDBConstant.PATH_SEPARATOR).append(PATH_WILDCARD); |
| } |
| paths.put(sgName, pathWithKnownSG.toString()); |
| } else if (!child.getChildren().isEmpty()) { |
| // push it back so we can traver its children later |
| nodeStack.push(child); |
| depthStack.push(depth); |
| } |
| } |
| } |
| |
| IEntityMNode setToEntity(IMNode node) { |
| // synchronize check and replace, we need replaceChild become atomic operation |
| // only write on mtree will be synchronized |
| synchronized (this) { |
| return IEntityMNode.setToEntity(node); |
| } |
| } |
| } |