diff --git a/iotdb-core/antlr/src/main/antlr4/org/apache/iotdb/db/qp/sql/IoTDBSqlParser.g4 b/iotdb-core/antlr/src/main/antlr4/org/apache/iotdb/db/qp/sql/IoTDBSqlParser.g4 index bd49f38ec3c7..3fe4c9912da5 100644 --- a/iotdb-core/antlr/src/main/antlr4/org/apache/iotdb/db/qp/sql/IoTDBSqlParser.g4 +++ b/iotdb-core/antlr/src/main/antlr4/org/apache/iotdb/db/qp/sql/IoTDBSqlParser.g4 @@ -169,12 +169,13 @@ alterTimeseries ; alterClause - : RENAME beforeName=attributeKey TO currentName=attributeKey - | SET attributePair (COMMA attributePair)* - | DROP attributeKey (COMMA attributeKey)* - | ADD TAGS attributePair (COMMA attributePair)* - | ADD ATTRIBUTES attributePair (COMMA attributePair)* - | UPSERT aliasClause? tagClause? attributeClause? + : RENAME TO newPath=fullPath #renameTimeseriesPath + | RENAME beforeName=attributeKey TO currentName=attributeKey #renameTagOrAttribute + | SET attributePair (COMMA attributePair)* #setAlter + | DROP attributeKey (COMMA attributeKey)* #dropAlter + | ADD TAGS attributePair (COMMA attributePair)* #addTagsAlter + | ADD ATTRIBUTES attributePair (COMMA attributePair)* #addAttributesAlter + | UPSERT aliasClause? tagClause? attributeClause? #upsertAlter ; alterEncodingCompressor diff --git a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/client/async/CnToDnAsyncRequestType.java b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/client/async/CnToDnAsyncRequestType.java index 0d6d439583f9..3bd493099cf5 100644 --- a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/client/async/CnToDnAsyncRequestType.java +++ b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/client/async/CnToDnAsyncRequestType.java @@ -90,6 +90,14 @@ public enum CnToDnAsyncRequestType { DELETE_DATA_FOR_DELETE_SCHEMA, DELETE_TIMESERIES, + LOCK_ALIAS, + CREATE_ALIAS_SERIES, + MARK_SERIES_DISABLED, + UPDATE_PHYSICAL_ALIAS_REF, + DROP_ALIAS_SERIES, + ENABLE_PHYSICAL_SERIES, + UNLOCK_FOR_ALIAS, + ALTER_ENCODING_COMPRESSOR, CONSTRUCT_SCHEMA_BLACK_LIST_WITH_TEMPLATE, diff --git a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/client/async/CnToDnInternalServiceAsyncRequestManager.java b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/client/async/CnToDnInternalServiceAsyncRequestManager.java index cde9a174492d..0138f11a0065 100644 --- a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/client/async/CnToDnInternalServiceAsyncRequestManager.java +++ b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/client/async/CnToDnInternalServiceAsyncRequestManager.java @@ -33,6 +33,7 @@ import org.apache.iotdb.commons.client.request.DataNodeInternalServiceRequestManager; import org.apache.iotdb.commons.client.request.TestConnectionUtils; import org.apache.iotdb.commons.exception.UncheckedStartupException; +import org.apache.iotdb.confignode.client.async.handlers.rpc.AliasTimeSeriesRPCHandler; import org.apache.iotdb.confignode.client.async.handlers.rpc.CheckTimeSeriesExistenceRPCHandler; import org.apache.iotdb.confignode.client.async.handlers.rpc.CountPathsUsingTemplateRPCHandler; import org.apache.iotdb.confignode.client.async.handlers.rpc.DataNodeAsyncRequestRPCHandler; @@ -48,6 +49,7 @@ import org.apache.iotdb.confignode.client.async.handlers.rpc.subscription.ConsumerGroupPushMetaRPCHandler; import org.apache.iotdb.confignode.client.async.handlers.rpc.subscription.TopicPushMetaRPCHandler; import org.apache.iotdb.mpp.rpc.thrift.TActiveTriggerInstanceReq; +import org.apache.iotdb.mpp.rpc.thrift.TAliasTimeSeriesReq; import org.apache.iotdb.mpp.rpc.thrift.TAlterEncodingCompressorReq; import org.apache.iotdb.mpp.rpc.thrift.TAlterViewReq; import org.apache.iotdb.mpp.rpc.thrift.TCheckSchemaRegionUsingTemplateReq; @@ -57,6 +59,7 @@ import org.apache.iotdb.mpp.rpc.thrift.TConstructSchemaBlackListWithTemplateReq; import org.apache.iotdb.mpp.rpc.thrift.TConstructViewSchemaBlackListReq; import org.apache.iotdb.mpp.rpc.thrift.TCountPathsUsingTemplateReq; +import org.apache.iotdb.mpp.rpc.thrift.TCreateAliasSeriesReq; import org.apache.iotdb.mpp.rpc.thrift.TCreateDataRegionReq; import org.apache.iotdb.mpp.rpc.thrift.TCreateFunctionInstanceReq; import org.apache.iotdb.mpp.rpc.thrift.TCreatePipePluginInstanceReq; @@ -69,9 +72,11 @@ import org.apache.iotdb.mpp.rpc.thrift.TDeleteTimeSeriesReq; import org.apache.iotdb.mpp.rpc.thrift.TDeleteViewSchemaReq; import org.apache.iotdb.mpp.rpc.thrift.TDeviceViewReq; +import org.apache.iotdb.mpp.rpc.thrift.TDropAliasSeriesReq; import org.apache.iotdb.mpp.rpc.thrift.TDropFunctionInstanceReq; import org.apache.iotdb.mpp.rpc.thrift.TDropPipePluginInstanceReq; import org.apache.iotdb.mpp.rpc.thrift.TDropTriggerInstanceReq; +import org.apache.iotdb.mpp.rpc.thrift.TEnablePhysicalSeriesReq; import org.apache.iotdb.mpp.rpc.thrift.TFetchSchemaBlackListReq; import org.apache.iotdb.mpp.rpc.thrift.TInactiveTriggerInstanceReq; import org.apache.iotdb.mpp.rpc.thrift.TInvalidateCacheReq; @@ -79,6 +84,8 @@ import org.apache.iotdb.mpp.rpc.thrift.TInvalidateMatchedSchemaCacheReq; import org.apache.iotdb.mpp.rpc.thrift.TInvalidateTableCacheReq; import org.apache.iotdb.mpp.rpc.thrift.TKillQueryInstanceReq; +import org.apache.iotdb.mpp.rpc.thrift.TLockAndGetSchemaInfoForAliasReq; +import org.apache.iotdb.mpp.rpc.thrift.TMarkSeriesDisabledReq; import org.apache.iotdb.mpp.rpc.thrift.TNotifyRegionMigrationReq; import org.apache.iotdb.mpp.rpc.thrift.TPipeHeartbeatReq; import org.apache.iotdb.mpp.rpc.thrift.TPushConsumerGroupMetaReq; @@ -98,6 +105,7 @@ import org.apache.iotdb.mpp.rpc.thrift.TTableDeviceDeletionWithPatternAndFilterReq; import org.apache.iotdb.mpp.rpc.thrift.TTableDeviceDeletionWithPatternOrModReq; import org.apache.iotdb.mpp.rpc.thrift.TTableDeviceInvalidateCacheReq; +import org.apache.iotdb.mpp.rpc.thrift.TUpdatePhysicalAliasRefReq; import org.apache.iotdb.mpp.rpc.thrift.TUpdateTableReq; import org.apache.iotdb.mpp.rpc.thrift.TUpdateTemplateReq; import org.apache.iotdb.mpp.rpc.thrift.TUpdateTriggerLocationReq; @@ -310,6 +318,39 @@ protected void initActionMapBuilder() { CnToDnAsyncRequestType.DELETE_TIMESERIES, (req, client, handler) -> client.deleteTimeSeries((TDeleteTimeSeriesReq) req, (SchemaUpdateRPCHandler) handler)); + actionMapBuilder.put( + CnToDnAsyncRequestType.LOCK_ALIAS, + (req, client, handler) -> + client.lockAndGetSchemaInfoForAlias( + (TLockAndGetSchemaInfoForAliasReq) req, (AliasTimeSeriesRPCHandler) handler)); + actionMapBuilder.put( + CnToDnAsyncRequestType.CREATE_ALIAS_SERIES, + (req, client, handler) -> + client.createAliasSeries( + (TCreateAliasSeriesReq) req, (SchemaUpdateRPCHandler) handler)); + actionMapBuilder.put( + CnToDnAsyncRequestType.MARK_SERIES_DISABLED, + (req, client, handler) -> + client.markSeriesDisabled( + (TMarkSeriesDisabledReq) req, (SchemaUpdateRPCHandler) handler)); + actionMapBuilder.put( + CnToDnAsyncRequestType.UPDATE_PHYSICAL_ALIAS_REF, + (req, client, handler) -> + client.updatePhysicalAliasRef( + (TUpdatePhysicalAliasRefReq) req, (SchemaUpdateRPCHandler) handler)); + actionMapBuilder.put( + CnToDnAsyncRequestType.DROP_ALIAS_SERIES, + (req, client, handler) -> + client.dropAliasSeries((TDropAliasSeriesReq) req, (SchemaUpdateRPCHandler) handler)); + actionMapBuilder.put( + CnToDnAsyncRequestType.ENABLE_PHYSICAL_SERIES, + (req, client, handler) -> + client.enablePhysicalSeries( + (TEnablePhysicalSeriesReq) req, (SchemaUpdateRPCHandler) handler)); + actionMapBuilder.put( + CnToDnAsyncRequestType.UNLOCK_FOR_ALIAS, + (req, client, handler) -> + client.unlockForAlias((TAliasTimeSeriesReq) req, (SchemaUpdateRPCHandler) handler)); actionMapBuilder.put( CnToDnAsyncRequestType.ALTER_ENCODING_COMPRESSOR, (req, client, handler) -> diff --git a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/client/async/handlers/rpc/AliasTimeSeriesRPCHandler.java b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/client/async/handlers/rpc/AliasTimeSeriesRPCHandler.java new file mode 100644 index 000000000000..a5abf58087c2 --- /dev/null +++ b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/client/async/handlers/rpc/AliasTimeSeriesRPCHandler.java @@ -0,0 +1,86 @@ +/* + * 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 this 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.confignode.client.async.handlers.rpc; + +import org.apache.iotdb.common.rpc.thrift.TDataNodeLocation; +import org.apache.iotdb.common.rpc.thrift.TSStatus; +import org.apache.iotdb.confignode.client.async.CnToDnAsyncRequestType; +import org.apache.iotdb.mpp.rpc.thrift.TAliasTimeSeriesResp; +import org.apache.iotdb.rpc.RpcUtils; +import org.apache.iotdb.rpc.TSStatusCode; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Map; +import java.util.concurrent.CountDownLatch; + +public class AliasTimeSeriesRPCHandler + extends DataNodeAsyncRequestRPCHandler { + + private static final Logger LOGGER = LoggerFactory.getLogger(AliasTimeSeriesRPCHandler.class); + + public AliasTimeSeriesRPCHandler( + CnToDnAsyncRequestType requestType, + int requestId, + TDataNodeLocation targetDataNode, + Map dataNodeLocationMap, + Map responseMap, + CountDownLatch countDownLatch) { + super(requestType, requestId, targetDataNode, dataNodeLocationMap, responseMap, countDownLatch); + } + + @Override + public void onComplete(TAliasTimeSeriesResp resp) { + TSStatus tsStatus = resp.getStatus(); + responseMap.put(requestId, resp); + if (tsStatus.getCode() == TSStatusCode.SUCCESS_STATUS.getStatusCode()) { + nodeLocationMap.remove(requestId); + LOGGER.info("Successfully executed alias time series operation on DataNode: {}", targetNode); + } else if (tsStatus.getCode() == TSStatusCode.MULTIPLE_ERROR.getStatusCode()) { + nodeLocationMap.remove(requestId); + LOGGER.error( + "Failed to execute alias time series operation on DataNode {}, {}", targetNode, tsStatus); + } else { + LOGGER.error( + "Failed to execute alias time series operation on DataNode {}, {}", targetNode, tsStatus); + } + countDownLatch.countDown(); + } + + @Override + public void onError(Exception e) { + String errorMsg = + "Alias time series operation error on DataNode: {id=" + + targetNode.getDataNodeId() + + ", internalEndPoint=" + + targetNode.getInternalEndPoint() + + "}" + + e.getMessage(); + LOGGER.error(errorMsg); + + countDownLatch.countDown(); + TAliasTimeSeriesResp resp = new TAliasTimeSeriesResp(); + resp.setStatus( + new TSStatus( + RpcUtils.getStatus(TSStatusCode.EXECUTE_STATEMENT_ERROR.getStatusCode(), errorMsg))); + responseMap.put(requestId, resp); + } +} diff --git a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/client/async/handlers/rpc/DataNodeAsyncRequestRPCHandler.java b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/client/async/handlers/rpc/DataNodeAsyncRequestRPCHandler.java index 301bfc71f97d..583d0aca6a7a 100644 --- a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/client/async/handlers/rpc/DataNodeAsyncRequestRPCHandler.java +++ b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/client/async/handlers/rpc/DataNodeAsyncRequestRPCHandler.java @@ -29,6 +29,7 @@ import org.apache.iotdb.confignode.client.async.handlers.rpc.subscription.CheckSchemaRegionUsingTemplateRPCHandler; import org.apache.iotdb.confignode.client.async.handlers.rpc.subscription.ConsumerGroupPushMetaRPCHandler; import org.apache.iotdb.confignode.client.async.handlers.rpc.subscription.TopicPushMetaRPCHandler; +import org.apache.iotdb.mpp.rpc.thrift.TAliasTimeSeriesResp; import org.apache.iotdb.mpp.rpc.thrift.TCheckSchemaRegionUsingTemplateResp; import org.apache.iotdb.mpp.rpc.thrift.TCheckTimeSeriesExistenceResp; import org.apache.iotdb.mpp.rpc.thrift.TCountPathsUsingTemplateResp; @@ -184,6 +185,27 @@ public static DataNodeAsyncRequestRPCHandler buildHandler( dataNodeLocationMap, (Map) responseMap, countDownLatch); + case LOCK_ALIAS: + return new AliasTimeSeriesRPCHandler( + requestType, + requestId, + targetDataNode, + dataNodeLocationMap, + (Map) responseMap, + countDownLatch); + case CREATE_ALIAS_SERIES: + case MARK_SERIES_DISABLED: + case UPDATE_PHYSICAL_ALIAS_REF: + case DROP_ALIAS_SERIES: + case ENABLE_PHYSICAL_SERIES: + case UNLOCK_FOR_ALIAS: + return new SchemaUpdateRPCHandler( + requestType, + requestId, + targetDataNode, + dataNodeLocationMap, + (Map) responseMap, + countDownLatch); case DETECT_TREE_DEVICE_VIEW_FIELD_TYPE: return new TreeDeviceViewFieldDetectionHandler( requestType, diff --git a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/ConfigManager.java b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/ConfigManager.java index 9d7151a8d20e..8837e6b00126 100644 --- a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/ConfigManager.java +++ b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/ConfigManager.java @@ -49,6 +49,7 @@ import org.apache.iotdb.commons.exception.IllegalPathException; import org.apache.iotdb.commons.exception.MetadataException; import org.apache.iotdb.commons.path.PartialPath; +import org.apache.iotdb.commons.path.PathDeserializeUtil; import org.apache.iotdb.commons.path.PathPatternTree; import org.apache.iotdb.commons.path.PathPatternUtil; import org.apache.iotdb.commons.pipe.sink.payload.airgap.AirGapPseudoTPipeTransferRequest; @@ -134,6 +135,7 @@ import org.apache.iotdb.confignode.rpc.thrift.TAINodeRegisterReq; import org.apache.iotdb.confignode.rpc.thrift.TAINodeRestartReq; import org.apache.iotdb.confignode.rpc.thrift.TAINodeRestartResp; +import org.apache.iotdb.confignode.rpc.thrift.TAliasTimeSeriesReq; import org.apache.iotdb.confignode.rpc.thrift.TAlterEncodingCompressorReq; import org.apache.iotdb.confignode.rpc.thrift.TAlterLogicalViewReq; import org.apache.iotdb.confignode.rpc.thrift.TAlterOrDropTableReq; @@ -2218,6 +2220,22 @@ public TSStatus alterEncodingCompressor(final TAlterEncodingCompressorReq req) { } } + @Override + public TSStatus aliasTimeSeries(TAliasTimeSeriesReq req) { + TSStatus status = confirmLeader(); + if (status.getCode() == TSStatusCode.SUCCESS_STATUS.getStatusCode()) { + String queryId = req.getQueryId(); + PartialPath oldPath = + (PartialPath) PathDeserializeUtil.deserialize(ByteBuffer.wrap(req.getOldPath())); + PartialPath newPath = + (PartialPath) PathDeserializeUtil.deserialize(ByteBuffer.wrap(req.getNewPath())); + boolean isGeneratedByPipe = req.isSetIsGeneratedByPipe() && req.isIsGeneratedByPipe(); + return procedureManager.aliasTimeSeries(queryId, oldPath, newPath, isGeneratedByPipe); + } else { + return status; + } + } + @Override public TSStatus deleteTimeSeries(TDeleteTimeSeriesReq req) { TSStatus status = confirmLeader(); diff --git a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/IManager.java b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/IManager.java index dff994d70e7e..581614c45c77 100644 --- a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/IManager.java +++ b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/IManager.java @@ -62,6 +62,7 @@ import org.apache.iotdb.confignode.rpc.thrift.TAINodeRegisterReq; import org.apache.iotdb.confignode.rpc.thrift.TAINodeRestartReq; import org.apache.iotdb.confignode.rpc.thrift.TAINodeRestartResp; +import org.apache.iotdb.confignode.rpc.thrift.TAliasTimeSeriesReq; import org.apache.iotdb.confignode.rpc.thrift.TAlterEncodingCompressorReq; import org.apache.iotdb.confignode.rpc.thrift.TAlterLogicalViewReq; import org.apache.iotdb.confignode.rpc.thrift.TAlterOrDropTableReq; @@ -701,6 +702,9 @@ TDataPartitionTableResp getOrCreateDataPartition( TSStatus alterEncodingCompressor(TAlterEncodingCompressorReq req); + /** Alias timeseries. */ + TSStatus aliasTimeSeries(TAliasTimeSeriesReq req); + /** Delete timeseries. */ TSStatus deleteTimeSeries(TDeleteTimeSeriesReq req); diff --git a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/ProcedureManager.java b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/ProcedureManager.java index d67e7721eef8..92f45ed727fe 100644 --- a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/ProcedureManager.java +++ b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/ProcedureManager.java @@ -82,6 +82,7 @@ import org.apache.iotdb.confignode.procedure.impl.region.RegionMigrationPlan; import org.apache.iotdb.confignode.procedure.impl.region.RegionOperationProcedure; import org.apache.iotdb.confignode.procedure.impl.region.RemoveRegionPeerProcedure; +import org.apache.iotdb.confignode.procedure.impl.schema.AliasTimeSeriesProcedure; import org.apache.iotdb.confignode.procedure.impl.schema.AlterEncodingCompressorProcedure; import org.apache.iotdb.confignode.procedure.impl.schema.AlterLogicalViewProcedure; import org.apache.iotdb.confignode.procedure.impl.schema.DeactivateTemplateProcedure; @@ -386,6 +387,46 @@ public TSStatus deleteTimeSeries( return waitingProcedureFinished(procedure); } + public TSStatus aliasTimeSeries( + String queryId, PartialPath oldPath, PartialPath newPath, boolean isGeneratedByPipe) { + AliasTimeSeriesProcedure procedure = null; + synchronized (this) { + boolean hasOverlappedTask = false; + ProcedureType type; + AliasTimeSeriesProcedure aliasTimeSeriesProcedure; + for (Procedure runningProcedure : executor.getProcedures().values()) { + type = ProcedureFactory.getProcedureType(runningProcedure); + if (type == null || !type.equals(ProcedureType.ALIAS_TIMESERIES_PROCEDURE)) { + continue; + } + aliasTimeSeriesProcedure = ((AliasTimeSeriesProcedure) runningProcedure); + if (queryId.equals(aliasTimeSeriesProcedure.getQueryId())) { + procedure = aliasTimeSeriesProcedure; + break; + } + // Check if there's overlap with old path or new path + if (oldPath.equals(aliasTimeSeriesProcedure.getOldPath()) + || oldPath.equals(aliasTimeSeriesProcedure.getNewPath()) + || newPath.equals(aliasTimeSeriesProcedure.getOldPath()) + || newPath.equals(aliasTimeSeriesProcedure.getNewPath())) { + hasOverlappedTask = true; + break; + } + } + + if (procedure == null) { + if (hasOverlappedTask) { + return RpcUtils.getStatus( + TSStatusCode.OVERLAP_WITH_EXISTING_TASK, + "Some other task is aliasing some target timeseries."); + } + procedure = new AliasTimeSeriesProcedure(queryId, oldPath, newPath, isGeneratedByPipe); + this.executor.submitProcedure(procedure); + } + } + return waitingProcedureFinished(procedure); + } + public TSStatus deleteLogicalView(TDeleteLogicalViewReq req) { String queryId = req.getQueryId(); PathPatternTree patternTree = diff --git a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/procedure/impl/schema/AliasTimeSeriesProcedure.java b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/procedure/impl/schema/AliasTimeSeriesProcedure.java new file mode 100644 index 000000000000..33daddf191ca --- /dev/null +++ b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/procedure/impl/schema/AliasTimeSeriesProcedure.java @@ -0,0 +1,668 @@ +/* + * 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.confignode.procedure.impl.schema; + +import org.apache.iotdb.common.rpc.thrift.TConsensusGroupId; +import org.apache.iotdb.common.rpc.thrift.TDataNodeLocation; +import org.apache.iotdb.common.rpc.thrift.TRegionReplicaSet; +import org.apache.iotdb.common.rpc.thrift.TSStatus; +import org.apache.iotdb.commons.exception.MetadataException; +import org.apache.iotdb.commons.path.PartialPath; +import org.apache.iotdb.commons.path.PathDeserializeUtil; +import org.apache.iotdb.commons.path.PathPatternTree; +import org.apache.iotdb.confignode.client.async.CnToDnAsyncRequestType; +import org.apache.iotdb.confignode.consensus.request.ConfigPhysicalPlanType; +import org.apache.iotdb.confignode.consensus.request.write.database.DatabaseSchemaPlan; +import org.apache.iotdb.confignode.manager.schema.ClusterSchemaManager; +import org.apache.iotdb.confignode.procedure.env.ConfigNodeProcedureEnv; +import org.apache.iotdb.confignode.procedure.exception.ProcedureException; +import org.apache.iotdb.confignode.procedure.impl.StateMachineProcedure; +import org.apache.iotdb.confignode.procedure.state.schema.AliasTimeSeriesState; +import org.apache.iotdb.confignode.procedure.store.ProcedureType; +import org.apache.iotdb.confignode.rpc.thrift.TDatabaseSchema; +import org.apache.iotdb.confignode.rpc.thrift.TSchemaPartitionTableResp; +import org.apache.iotdb.mpp.rpc.thrift.TAliasTimeSeriesReq; +import org.apache.iotdb.mpp.rpc.thrift.TAliasTimeSeriesResp; +import org.apache.iotdb.mpp.rpc.thrift.TCreateAliasSeriesReq; +import org.apache.iotdb.mpp.rpc.thrift.TDropAliasSeriesReq; +import org.apache.iotdb.mpp.rpc.thrift.TEnablePhysicalSeriesReq; +import org.apache.iotdb.mpp.rpc.thrift.TLockAndGetSchemaInfoForAliasReq; +import org.apache.iotdb.mpp.rpc.thrift.TMarkSeriesDisabledReq; +import org.apache.iotdb.mpp.rpc.thrift.TUpdatePhysicalAliasRefReq; +import org.apache.iotdb.rpc.TSStatusCode; + +import org.apache.tsfile.utils.ReadWriteIOUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.ByteArrayOutputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.function.BiFunction; +import java.util.stream.Collectors; + +public class AliasTimeSeriesProcedure + extends StateMachineProcedure { + + private static final Logger LOGGER = LoggerFactory.getLogger(AliasTimeSeriesProcedure.class); + + private String queryId; + private PartialPath oldPath; + private PartialPath newPath; + + private transient ByteBuffer oldPathBytes; + private transient ByteBuffer newPathBytes; + private transient String requestMessage; + + // Schema info from phase 1 + private transient TAliasTimeSeriesResp schemaInfoResp; + private transient boolean isRenamed = false; + private transient PartialPath physicalPath; + + public AliasTimeSeriesProcedure(final boolean isGeneratedByPipe) { + super(isGeneratedByPipe); + } + + public AliasTimeSeriesProcedure( + final String queryId, + final PartialPath oldPath, + final PartialPath newPath, + final boolean isGeneratedByPipe) { + super(isGeneratedByPipe); + this.queryId = queryId; + this.oldPath = oldPath; + this.newPath = newPath; + generateRequestBytes(); + } + + private void generateRequestBytes() { + requestMessage = String.format("Alias %s to %s", oldPath.getFullPath(), newPath.getFullPath()); + final ByteArrayOutputStream oldPathStream = new ByteArrayOutputStream(); + final ByteArrayOutputStream newPathStream = new ByteArrayOutputStream(); + final DataOutputStream oldPathDataStream = new DataOutputStream(oldPathStream); + final DataOutputStream newPathDataStream = new DataOutputStream(newPathStream); + try { + oldPath.serialize(oldPathDataStream); + newPath.serialize(newPathDataStream); + } catch (final IOException ignored) { + // ByteArrayOutputStream won't throw IOException + } + oldPathBytes = ByteBuffer.wrap(oldPathStream.toByteArray()); + newPathBytes = ByteBuffer.wrap(newPathStream.toByteArray()); + } + + @Override + protected Flow executeFromState( + final ConfigNodeProcedureEnv env, final AliasTimeSeriesState state) + throws InterruptedException { + final long startTime = System.currentTimeMillis(); + try { + switch (state) { + case LOCK_AND_GET_SCHEMA_INFO: + LOGGER.info("Lock and get schema info for alias time series {}", requestMessage); + lockAndGetSchemaInfo(env); + setNextState(AliasTimeSeriesState.TRANSFORM_METADATA); + break; + case TRANSFORM_METADATA: + LOGGER.info("Transform metadata for alias time series {}", requestMessage); + transformMetadata(env); + setNextState(AliasTimeSeriesState.UNLOCK); + break; + case UNLOCK: + LOGGER.info("Unlock for alias time series {}", requestMessage); + unlock(env); + return Flow.NO_MORE_STATE; + default: + setFailure(new ProcedureException("Unrecognized state " + state)); + return Flow.NO_MORE_STATE; + } + return Flow.HAS_MORE_STATE; + } finally { + LOGGER.info( + "AliasTimeSeries-[{}] costs {}ms", state, (System.currentTimeMillis() - startTime)); + } + } + + private Map getRelatedSchemaRegionGroup( + final ConfigNodeProcedureEnv env, final PartialPath path) { + final PathPatternTree patternTree = new PathPatternTree(); + patternTree.appendFullPath(path); + patternTree.constructTree(); + return env.getConfigManager().getRelatedSchemaRegionGroup(patternTree, false); + } + + private Map getOrCreateRelatedSchemaRegionGroup( + final ConfigNodeProcedureEnv env, final PartialPath path) { + // Step 1: Extract and check/create database for the path + try { + final String databaseName = extractDatabaseName(path); + if (databaseName != null && !databaseName.isEmpty()) { + final List databases = + env.getConfigManager().getClusterSchemaManager().getDatabaseNames(false); + if (!databases.contains(databaseName)) { + // Database doesn't exist, create it with default settings + final TDatabaseSchema databaseSchema = new TDatabaseSchema(databaseName); + ClusterSchemaManager.enrichDatabaseSchemaWithDefaultProperties(databaseSchema); + final DatabaseSchemaPlan databaseSchemaPlan = + new DatabaseSchemaPlan(ConfigPhysicalPlanType.CreateDatabase, databaseSchema); + final TSStatus status = env.getConfigManager().setDatabase(databaseSchemaPlan); + if (status.getCode() != TSStatusCode.SUCCESS_STATUS.getStatusCode()) { + LOGGER.warn( + "Failed to create database {} for path {}: {}", + databaseName, + path.getFullPath(), + status.getMessage()); + return Collections.emptyMap(); + } + LOGGER.info("Auto-created database {} for path {}", databaseName, path.getFullPath()); + } + } + } catch (Exception e) { + LOGGER.warn( + "Failed to extract or create database for path {}: {}", + path.getFullPath(), + e.getMessage()); + // Continue to try getOrCreateSchemaPartition anyway + } + + // Step 2: Use getOrCreateSchemaPartition to automatically create SchemaRegion if it doesn't + // exist + final PathPatternTree patternTree = new PathPatternTree(); + patternTree.appendFullPath(path); + patternTree.constructTree(); + final TSchemaPartitionTableResp resp = + env.getConfigManager().getOrCreateSchemaPartition(patternTree); + if (resp.getStatus().getCode() != TSStatusCode.SUCCESS_STATUS.getStatusCode()) { + return Collections.emptyMap(); + } + // Convert TSchemaPartitionTableResp to Map + final List allRegionReplicaSets = + env.getConfigManager().getPartitionManager().getAllReplicaSets(); + final Set groupIdSet = + resp.getSchemaPartitionTable().values().stream() + .flatMap(m -> m.values().stream()) + .collect(Collectors.toSet()); + final Map filteredRegionReplicaSets = new HashMap<>(); + for (final TRegionReplicaSet regionReplicaSet : allRegionReplicaSets) { + if (groupIdSet.contains(regionReplicaSet.getRegionId())) { + filteredRegionReplicaSets.put(regionReplicaSet.getRegionId(), regionReplicaSet); + } + } + return filteredRegionReplicaSets; + } + + /** + * Extract database name from a PartialPath. For Tree Model, database is typically at level 2 + * (root.sg). We try to construct the database path from the first two nodes. + */ + private String extractDatabaseName(final PartialPath path) { + try { + // For Tree Model, database is typically root.sg (first two nodes) + // For multi-level databases, it could be root.level1.level2, but we start with root.level1 + final String[] nodes = path.getNodes(); + if (nodes.length >= 2) { + // Try root.sg as database name + final PartialPath candidateDatabase = new PartialPath(new String[] {nodes[0], nodes[1]}); + return candidateDatabase.getFullPath(); + } + } catch (Exception e) { + LOGGER.debug( + "Failed to extract database name from path {}: {}", path.getFullPath(), e.getMessage()); + } + return null; + } + + private void lockAndGetSchemaInfo(final ConfigNodeProcedureEnv env) { + final Map targetSchemaRegionGroup = + getRelatedSchemaRegionGroup(env, oldPath); + if (targetSchemaRegionGroup.isEmpty()) { + setFailure( + new ProcedureException( + new MetadataException( + String.format("Schema region not found for path: %s", oldPath.getFullPath())))); + return; + } + + final LockAndGetSchemaInfoTaskExecutor lockTask = + new LockAndGetSchemaInfoTaskExecutor( + env, + targetSchemaRegionGroup, + (dataNodeLocation, consensusGroupIdList) -> + new TLockAndGetSchemaInfoForAliasReq( + consensusGroupIdList, oldPathBytes, newPathBytes) + .setIsGeneratedByPipe(isGeneratedByPipe)); + lockTask.execute(); + + // Store schema info from response + schemaInfoResp = lockTask.getResponse(); + if (schemaInfoResp != null + && schemaInfoResp.getStatus().getCode() == TSStatusCode.SUCCESS_STATUS.getStatusCode()) { + isRenamed = schemaInfoResp.isSetIsRenamed() && schemaInfoResp.isIsRenamed(); + if (schemaInfoResp.isSetPhysicalPath() && schemaInfoResp.getPhysicalPath() != null) { + physicalPath = + (PartialPath) + PathDeserializeUtil.deserialize(ByteBuffer.wrap(schemaInfoResp.getPhysicalPath())); + } + } + } + + private void transformMetadata(final ConfigNodeProcedureEnv env) { + // Transform metadata based on schema info from phase 1 + final Map oldPathSchemaRegionGroup = + getRelatedSchemaRegionGroup(env, oldPath); + if (oldPathSchemaRegionGroup.isEmpty()) { + setFailure( + new ProcedureException( + new MetadataException( + String.format("Schema region not found for path: %s", oldPath.getFullPath())))); + return; + } + + // Determine scenario based on phase 1 response + if (!isRenamed) { + // Scenario A: OldPath is a physical series + // Step 1: Create alias series (NewPath -> OldPath) in newPath's SchemaRegion + // Use getOrCreateRelatedSchemaRegionGroup to auto-create SchemaRegion if it doesn't exist + final Map newPathSchemaRegionGroup = + getOrCreateRelatedSchemaRegionGroup(env, newPath); + if (newPathSchemaRegionGroup.isEmpty()) { + setFailure( + new ProcedureException( + new MetadataException( + String.format( + "Failed to get or create schema region for path: %s", + newPath.getFullPath())))); + return; + } + final AliasTimeSeriesRegionTaskExecutor createAliasTask = + new AliasTimeSeriesRegionTaskExecutor<>( + "create alias series", + env, + newPathSchemaRegionGroup, + CnToDnAsyncRequestType.CREATE_ALIAS_SERIES, + (dataNodeLocation, consensusGroupIdList) -> + new TCreateAliasSeriesReq( + consensusGroupIdList, + oldPathBytes, + newPathBytes, + schemaInfoResp.getTimeSeriesInfo()) + .setIsGeneratedByPipe(isGeneratedByPipe)); + createAliasTask.execute(); + + // Step 2: Mark old series as disabled and set ALIAS_PATH in oldPath's SchemaRegion + final AliasTimeSeriesRegionTaskExecutor markDisabledTask = + new AliasTimeSeriesRegionTaskExecutor<>( + "mark series disabled", + env, + oldPathSchemaRegionGroup, + CnToDnAsyncRequestType.MARK_SERIES_DISABLED, + (dataNodeLocation, consensusGroupIdList) -> + new TMarkSeriesDisabledReq(consensusGroupIdList, oldPathBytes, newPathBytes) + .setIsGeneratedByPipe(isGeneratedByPipe)); + markDisabledTask.execute(); + } else { + // OldPath is an alias series + // Get physical path's SchemaRegion + final Map physicalPathSchemaRegionGroup = + getRelatedSchemaRegionGroup(env, physicalPath); + if (physicalPathSchemaRegionGroup.isEmpty()) { + setFailure( + new ProcedureException( + new MetadataException( + String.format( + "Schema region not found for path: %s", physicalPath.getFullPath())))); + return; + } + + if (newPath.equals(physicalPath)) { + // Scenario C: NewPath == physicalPath (revert alias) + // Step 1: Enable physical series (remove DISABLED, clear ALIAS_PATH) in physicalPath's + // SchemaRegion + final AliasTimeSeriesRegionTaskExecutor enablePhysicalTask = + new AliasTimeSeriesRegionTaskExecutor<>( + "enable physical series", + env, + physicalPathSchemaRegionGroup, + CnToDnAsyncRequestType.ENABLE_PHYSICAL_SERIES, + (dataNodeLocation, consensusGroupIdList) -> { + ByteArrayOutputStream physicalPathStream = new ByteArrayOutputStream(); + DataOutputStream physicalPathDataStream = + new DataOutputStream(physicalPathStream); + try { + physicalPath.serialize(physicalPathDataStream); + } catch (IOException ignored) { + // ByteArrayOutputStream won't throw IOException + } + return new TEnablePhysicalSeriesReq( + consensusGroupIdList, ByteBuffer.wrap(physicalPathStream.toByteArray())) + .setIsGeneratedByPipe(isGeneratedByPipe); + }); + enablePhysicalTask.execute(); + + // Step 2: Drop old alias series in oldPath's SchemaRegion + final AliasTimeSeriesRegionTaskExecutor dropAliasTask = + new AliasTimeSeriesRegionTaskExecutor<>( + "drop alias series", + env, + oldPathSchemaRegionGroup, + CnToDnAsyncRequestType.DROP_ALIAS_SERIES, + (dataNodeLocation, consensusGroupIdList) -> + new TDropAliasSeriesReq(consensusGroupIdList, oldPathBytes) + .setIsGeneratedByPipe(isGeneratedByPipe)); + dropAliasTask.execute(); + } else { + // Scenario B: NewPath != physicalPath (update alias) + // Step 1: Create new alias series (NewPath -> physicalPath) in newPath's SchemaRegion + // Use getOrCreateRelatedSchemaRegionGroup to auto-create SchemaRegion if it doesn't exist + final Map newPathSchemaRegionGroup = + getOrCreateRelatedSchemaRegionGroup(env, newPath); + if (newPathSchemaRegionGroup.isEmpty()) { + setFailure( + new ProcedureException( + new MetadataException( + String.format( + "Failed to get or create schema region for path: %s", + newPath.getFullPath())))); + return; + } + final AliasTimeSeriesRegionTaskExecutor createAliasTask = + new AliasTimeSeriesRegionTaskExecutor<>( + "create alias series", + env, + newPathSchemaRegionGroup, + CnToDnAsyncRequestType.CREATE_ALIAS_SERIES, + (dataNodeLocation, consensusGroupIdList) -> { + ByteArrayOutputStream physicalPathStream = new ByteArrayOutputStream(); + DataOutputStream physicalPathDataStream = + new DataOutputStream(physicalPathStream); + try { + physicalPath.serialize(physicalPathDataStream); + } catch (IOException ignored) { + // ByteArrayOutputStream won't throw IOException + } + return new TCreateAliasSeriesReq( + consensusGroupIdList, + ByteBuffer.wrap(physicalPathStream.toByteArray()), + newPathBytes, + schemaInfoResp.getTimeSeriesInfo()) + .setIsGeneratedByPipe(isGeneratedByPipe); + }); + createAliasTask.execute(); + + // Step 2: Update physical series ALIAS_PATH to NewPath in physicalPath's SchemaRegion + final AliasTimeSeriesRegionTaskExecutor updateRefTask = + new AliasTimeSeriesRegionTaskExecutor<>( + "update physical alias ref", + env, + physicalPathSchemaRegionGroup, + CnToDnAsyncRequestType.UPDATE_PHYSICAL_ALIAS_REF, + (dataNodeLocation, consensusGroupIdList) -> { + ByteArrayOutputStream physicalPathStream = new ByteArrayOutputStream(); + DataOutputStream physicalPathDataStream = + new DataOutputStream(physicalPathStream); + try { + physicalPath.serialize(physicalPathDataStream); + } catch (IOException ignored) { + // ByteArrayOutputStream won't throw IOException + } + return new TUpdatePhysicalAliasRefReq( + consensusGroupIdList, + ByteBuffer.wrap(physicalPathStream.toByteArray()), + newPathBytes) + .setIsGeneratedByPipe(isGeneratedByPipe); + }); + updateRefTask.execute(); + + // Step 3: Drop old alias series in oldPath's SchemaRegion + final AliasTimeSeriesRegionTaskExecutor dropAliasTask = + new AliasTimeSeriesRegionTaskExecutor<>( + "drop alias series", + env, + oldPathSchemaRegionGroup, + CnToDnAsyncRequestType.DROP_ALIAS_SERIES, + (dataNodeLocation, consensusGroupIdList) -> + new TDropAliasSeriesReq(consensusGroupIdList, oldPathBytes) + .setIsGeneratedByPipe(isGeneratedByPipe)); + dropAliasTask.execute(); + } + } + } + + private void unlock(final ConfigNodeProcedureEnv env) { + // Unlock in oldPath's SchemaRegion (where the lock was acquired) + final Map oldPathSchemaRegionGroup = + getRelatedSchemaRegionGroup(env, oldPath); + if (oldPathSchemaRegionGroup.isEmpty()) { + setFailure( + new ProcedureException( + new MetadataException( + String.format("Schema region not found for path: %s", oldPath.getFullPath())))); + return; + } + + final AliasTimeSeriesRegionTaskExecutor unlockTask = + new AliasTimeSeriesRegionTaskExecutor<>( + "unlock", + env, + oldPathSchemaRegionGroup, + CnToDnAsyncRequestType.UNLOCK_FOR_ALIAS, + (dataNodeLocation, consensusGroupIdList) -> + new TAliasTimeSeriesReq(consensusGroupIdList, oldPathBytes, newPathBytes) + .setIsGeneratedByPipe(isGeneratedByPipe)); + unlockTask.execute(); + } + + @Override + protected void rollbackState( + final ConfigNodeProcedureEnv env, final AliasTimeSeriesState aliasTimeSeriesState) + throws IOException, InterruptedException, ProcedureException { + // TODO: Implement rollback logic + // Rollback should unlock and restore the original state + if (aliasTimeSeriesState == AliasTimeSeriesState.LOCK_AND_GET_SCHEMA_INFO) { + // Rollback: unlock if we locked + unlock(env); + } else if (aliasTimeSeriesState == AliasTimeSeriesState.TRANSFORM_METADATA) { + // Rollback: restore original metadata + // TODO: Implement rollback of metadata transformation + } + } + + @Override + protected boolean isRollbackSupported(final AliasTimeSeriesState aliasTimeSeriesState) { + return true; + } + + @Override + protected AliasTimeSeriesState getState(final int stateId) { + return AliasTimeSeriesState.values()[stateId]; + } + + @Override + protected int getStateId(final AliasTimeSeriesState aliasTimeSeriesState) { + return aliasTimeSeriesState.ordinal(); + } + + @Override + protected AliasTimeSeriesState getInitialState() { + return AliasTimeSeriesState.LOCK_AND_GET_SCHEMA_INFO; + } + + public String getQueryId() { + return queryId; + } + + public PartialPath getOldPath() { + return oldPath; + } + + public PartialPath getNewPath() { + return newPath; + } + + @Override + public void serialize(final DataOutputStream stream) throws IOException { + stream.writeShort(ProcedureType.ALIAS_TIMESERIES_PROCEDURE.getTypeCode()); + super.serialize(stream); + ReadWriteIOUtils.write(queryId, stream); + oldPath.serialize(stream); + newPath.serialize(stream); + } + + @Override + public void deserialize(final ByteBuffer byteBuffer) { + super.deserialize(byteBuffer); + queryId = ReadWriteIOUtils.readString(byteBuffer); + oldPath = (PartialPath) PathDeserializeUtil.deserialize(byteBuffer); + newPath = (PartialPath) PathDeserializeUtil.deserialize(byteBuffer); + generateRequestBytes(); + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + final AliasTimeSeriesProcedure that = (AliasTimeSeriesProcedure) o; + return this.getProcId() == that.getProcId() + && this.getCurrentState().equals(that.getCurrentState()) + && this.getCycles() == that.getCycles() + && this.isGeneratedByPipe == that.isGeneratedByPipe + && Objects.equals(this.oldPath, that.oldPath) + && Objects.equals(this.newPath, that.newPath); + } + + @Override + public int hashCode() { + return Objects.hash( + getProcId(), getCurrentState(), getCycles(), isGeneratedByPipe, oldPath, newPath); + } + + /** Task executor for lock and get schema info phase, which returns TAliasTimeSeriesResp. */ + private class LockAndGetSchemaInfoTaskExecutor + extends DataNodeRegionTaskExecutor { + + private TAliasTimeSeriesResp response; + + LockAndGetSchemaInfoTaskExecutor( + final ConfigNodeProcedureEnv env, + final Map targetSchemaRegionGroup, + final BiFunction< + TDataNodeLocation, List, TLockAndGetSchemaInfoForAliasReq> + dataNodeRequestGenerator) { + super( + env, + targetSchemaRegionGroup, + false, + CnToDnAsyncRequestType.LOCK_ALIAS, + dataNodeRequestGenerator); + } + + @Override + protected List processResponseOfOneDataNode( + final TDataNodeLocation dataNodeLocation, + final List consensusGroupIdList, + final TAliasTimeSeriesResp resp) { + final List failedRegionList = new ArrayList<>(); + if (resp.getStatus().getCode() == TSStatusCode.SUCCESS_STATUS.getStatusCode()) { + // Store the first successful response + if (response == null) { + response = resp; + } + return failedRegionList; + } + + // Handle errors + if (resp.getStatus().getCode() == TSStatusCode.MULTIPLE_ERROR.getStatusCode()) { + final List subStatus = resp.getStatus().getSubStatus(); + for (int i = 0; i < subStatus.size(); i++) { + if (subStatus.get(i).getCode() != TSStatusCode.SUCCESS_STATUS.getStatusCode()) { + failedRegionList.add(consensusGroupIdList.get(i)); + } + } + } else { + failedRegionList.addAll(consensusGroupIdList); + } + return failedRegionList; + } + + @Override + protected void onAllReplicasetFailure( + final TConsensusGroupId consensusGroupId, + final Set dataNodeLocationSet) { + setFailure( + new ProcedureException( + new MetadataException( + String.format( + "Alias time series %s failed when [lock and get schema info] because failed to execute in all replicaset of %s %s. Failure nodes: %s", + requestMessage, + consensusGroupId.type, + consensusGroupId.id, + dataNodeLocationSet)))); + interruptTask(); + } + + public TAliasTimeSeriesResp getResponse() { + return response; + } + } + + private class AliasTimeSeriesRegionTaskExecutor extends DataNodeTSStatusTaskExecutor { + + private final String taskName; + + AliasTimeSeriesRegionTaskExecutor( + final String taskName, + final ConfigNodeProcedureEnv env, + final Map targetSchemaRegionGroup, + final CnToDnAsyncRequestType dataNodeRequestType, + final BiFunction, Q> dataNodeRequestGenerator) { + super(env, targetSchemaRegionGroup, false, dataNodeRequestType, dataNodeRequestGenerator); + this.taskName = taskName; + } + + @Override + protected void onAllReplicasetFailure( + final TConsensusGroupId consensusGroupId, + final Set dataNodeLocationSet) { + setFailure( + new ProcedureException( + new MetadataException( + String.format( + "Alias time series %s failed when [%s] because failed to execute in all replicaset of %s %s. Failure nodes: %s", + requestMessage, + taskName, + consensusGroupId.type, + consensusGroupId.id, + dataNodeLocationSet)))); + interruptTask(); + } + } +} diff --git a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/procedure/state/schema/AliasTimeSeriesState.java b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/procedure/state/schema/AliasTimeSeriesState.java new file mode 100644 index 000000000000..03c77c2e49d9 --- /dev/null +++ b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/procedure/state/schema/AliasTimeSeriesState.java @@ -0,0 +1,26 @@ +/* + * 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.confignode.procedure.state.schema; + +public enum AliasTimeSeriesState { + LOCK_AND_GET_SCHEMA_INFO, + TRANSFORM_METADATA, + UNLOCK +} diff --git a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/procedure/store/ProcedureFactory.java b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/procedure/store/ProcedureFactory.java index f20a6999d593..93cd78a155ba 100644 --- a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/procedure/store/ProcedureFactory.java +++ b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/procedure/store/ProcedureFactory.java @@ -42,6 +42,7 @@ import org.apache.iotdb.confignode.procedure.impl.region.ReconstructRegionProcedure; import org.apache.iotdb.confignode.procedure.impl.region.RegionMigrateProcedure; import org.apache.iotdb.confignode.procedure.impl.region.RemoveRegionPeerProcedure; +import org.apache.iotdb.confignode.procedure.impl.schema.AliasTimeSeriesProcedure; import org.apache.iotdb.confignode.procedure.impl.schema.AlterEncodingCompressorProcedure; import org.apache.iotdb.confignode.procedure.impl.schema.AlterLogicalViewProcedure; import org.apache.iotdb.confignode.procedure.impl.schema.DeactivateTemplateProcedure; @@ -145,6 +146,9 @@ public Procedure create(ByteBuffer buffer) throws IOException { case DELETE_TIMESERIES_PROCEDURE: procedure = new DeleteTimeSeriesProcedure(false); break; + case ALIAS_TIMESERIES_PROCEDURE: + procedure = new AliasTimeSeriesProcedure(false); + break; case DELETE_LOGICAL_VIEW_PROCEDURE: procedure = new DeleteLogicalViewProcedure(false); break; @@ -428,6 +432,8 @@ public static ProcedureType getProcedureType(final Procedure procedure) { return ProcedureType.ALTER_ENCODING_COMPRESSOR_PROCEDURE; } else if (procedure instanceof DeleteTimeSeriesProcedure) { return ProcedureType.DELETE_TIMESERIES_PROCEDURE; + } else if (procedure instanceof AliasTimeSeriesProcedure) { + return ProcedureType.ALIAS_TIMESERIES_PROCEDURE; } else if (procedure instanceof ReconstructRegionProcedure) { return ProcedureType.RECONSTRUCT_REGION_PROCEDURE; } else if (procedure instanceof NotifyRegionMigrationProcedure) { diff --git a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/procedure/store/ProcedureType.java b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/procedure/store/ProcedureType.java index d076a7d9d926..1a90f07bc13b 100644 --- a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/procedure/store/ProcedureType.java +++ b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/procedure/store/ProcedureType.java @@ -47,6 +47,7 @@ public enum ProcedureType { /** Timeseries */ DELETE_TIMESERIES_PROCEDURE((short) 300), ALTER_ENCODING_COMPRESSOR_PROCEDURE((short) 301), + ALIAS_TIMESERIES_PROCEDURE((short) 302), /** Trigger */ CREATE_TRIGGER_PROCEDURE((short) 400), diff --git a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/service/thrift/ConfigNodeRPCServiceProcessor.java b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/service/thrift/ConfigNodeRPCServiceProcessor.java index 6582a5bfff8e..9c50add3f73b 100644 --- a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/service/thrift/ConfigNodeRPCServiceProcessor.java +++ b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/service/thrift/ConfigNodeRPCServiceProcessor.java @@ -94,6 +94,7 @@ import org.apache.iotdb.confignode.rpc.thrift.TAINodeRestartReq; import org.apache.iotdb.confignode.rpc.thrift.TAINodeRestartResp; import org.apache.iotdb.confignode.rpc.thrift.TAddConsensusGroupReq; +import org.apache.iotdb.confignode.rpc.thrift.TAliasTimeSeriesReq; import org.apache.iotdb.confignode.rpc.thrift.TAlterEncodingCompressorReq; import org.apache.iotdb.confignode.rpc.thrift.TAlterLogicalViewReq; import org.apache.iotdb.confignode.rpc.thrift.TAlterOrDropTableReq; @@ -1170,6 +1171,11 @@ public TSStatus deleteTimeSeries(TDeleteTimeSeriesReq req) { return configManager.deleteTimeSeries(req); } + @Override + public TSStatus aliasTimeSeries(TAliasTimeSeriesReq req) { + return configManager.aliasTimeSeries(req); + } + @Override public TSStatus deleteLogicalView(TDeleteLogicalViewReq req) { return configManager.deleteLogicalView(req); diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/consensus/statemachine/schemaregion/SchemaExecutionVisitor.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/consensus/statemachine/schemaregion/SchemaExecutionVisitor.java index 28ccff2f20d4..66689598825f 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/consensus/statemachine/schemaregion/SchemaExecutionVisitor.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/consensus/statemachine/schemaregion/SchemaExecutionVisitor.java @@ -38,18 +38,25 @@ import org.apache.iotdb.db.queryengine.plan.planner.plan.node.metadata.write.AlterTimeSeriesNode; import org.apache.iotdb.db.queryengine.plan.planner.plan.node.metadata.write.BatchActivateTemplateNode; import org.apache.iotdb.db.queryengine.plan.planner.plan.node.metadata.write.ConstructSchemaBlackListNode; +import org.apache.iotdb.db.queryengine.plan.planner.plan.node.metadata.write.CreateAliasSeriesNode; import org.apache.iotdb.db.queryengine.plan.planner.plan.node.metadata.write.CreateAlignedTimeSeriesNode; import org.apache.iotdb.db.queryengine.plan.planner.plan.node.metadata.write.CreateMultiTimeSeriesNode; import org.apache.iotdb.db.queryengine.plan.planner.plan.node.metadata.write.CreateTimeSeriesNode; import org.apache.iotdb.db.queryengine.plan.planner.plan.node.metadata.write.DeactivateTemplateNode; import org.apache.iotdb.db.queryengine.plan.planner.plan.node.metadata.write.DeleteTimeSeriesNode; +import org.apache.iotdb.db.queryengine.plan.planner.plan.node.metadata.write.DropAliasSeriesNode; +import org.apache.iotdb.db.queryengine.plan.planner.plan.node.metadata.write.EnablePhysicalSeriesNode; import org.apache.iotdb.db.queryengine.plan.planner.plan.node.metadata.write.InternalBatchActivateTemplateNode; import org.apache.iotdb.db.queryengine.plan.planner.plan.node.metadata.write.InternalCreateMultiTimeSeriesNode; import org.apache.iotdb.db.queryengine.plan.planner.plan.node.metadata.write.InternalCreateTimeSeriesNode; +import org.apache.iotdb.db.queryengine.plan.planner.plan.node.metadata.write.LockAliasNode; +import org.apache.iotdb.db.queryengine.plan.planner.plan.node.metadata.write.MarkSeriesDisabledNode; import org.apache.iotdb.db.queryengine.plan.planner.plan.node.metadata.write.MeasurementGroup; import org.apache.iotdb.db.queryengine.plan.planner.plan.node.metadata.write.PreDeactivateTemplateNode; import org.apache.iotdb.db.queryengine.plan.planner.plan.node.metadata.write.RollbackPreDeactivateTemplateNode; import org.apache.iotdb.db.queryengine.plan.planner.plan.node.metadata.write.RollbackSchemaBlackListNode; +import org.apache.iotdb.db.queryengine.plan.planner.plan.node.metadata.write.UnlockForAliasNode; +import org.apache.iotdb.db.queryengine.plan.planner.plan.node.metadata.write.UpdatePhysicalAliasRefNode; import org.apache.iotdb.db.queryengine.plan.planner.plan.node.metadata.write.view.AlterLogicalViewNode; import org.apache.iotdb.db.queryengine.plan.planner.plan.node.metadata.write.view.ConstructLogicalViewBlackListNode; import org.apache.iotdb.db.queryengine.plan.planner.plan.node.metadata.write.view.CreateLogicalViewNode; @@ -836,6 +843,89 @@ public TSStatus visitPipeOperateSchemaQueueNode( return new TSStatus(TSStatusCode.SUCCESS_STATUS.getStatusCode()); } + @Override + public TSStatus visitLockAlias(final LockAliasNode node, final ISchemaRegion schemaRegion) { + try { + schemaRegion.lockForAlias(node); + return RpcUtils.getStatus(TSStatusCode.SUCCESS_STATUS); + } catch (final MetadataException e) { + logger.error(e.getMessage(), e); + return RpcUtils.getStatus(e.getErrorCode(), e.getMessage()); + } + } + + @Override + public TSStatus visitCreateAliasSeries( + final CreateAliasSeriesNode node, final ISchemaRegion schemaRegion) { + try { + schemaRegion.createAliasSeries(node); + return RpcUtils.getStatus(TSStatusCode.SUCCESS_STATUS); + } catch (final MetadataException e) { + logger.error(e.getMessage(), e); + return RpcUtils.getStatus(e.getErrorCode(), e.getMessage()); + } + } + + @Override + public TSStatus visitMarkSeriesDisabled( + final MarkSeriesDisabledNode node, final ISchemaRegion schemaRegion) { + try { + schemaRegion.markSeriesDisabled(node); + return RpcUtils.getStatus(TSStatusCode.SUCCESS_STATUS); + } catch (final MetadataException e) { + logger.error(e.getMessage(), e); + return RpcUtils.getStatus(e.getErrorCode(), e.getMessage()); + } + } + + @Override + public TSStatus visitUpdatePhysicalAliasRef( + final UpdatePhysicalAliasRefNode node, final ISchemaRegion schemaRegion) { + try { + schemaRegion.updatePhysicalAliasRef(node); + return RpcUtils.getStatus(TSStatusCode.SUCCESS_STATUS); + } catch (final MetadataException e) { + logger.error(e.getMessage(), e); + return RpcUtils.getStatus(e.getErrorCode(), e.getMessage()); + } + } + + @Override + public TSStatus visitDropAliasSeries( + final DropAliasSeriesNode node, final ISchemaRegion schemaRegion) { + try { + schemaRegion.dropAliasSeries(node); + return RpcUtils.getStatus(TSStatusCode.SUCCESS_STATUS); + } catch (final MetadataException e) { + logger.error(e.getMessage(), e); + return RpcUtils.getStatus(e.getErrorCode(), e.getMessage()); + } + } + + @Override + public TSStatus visitEnablePhysicalSeries( + final EnablePhysicalSeriesNode node, final ISchemaRegion schemaRegion) { + try { + schemaRegion.enablePhysicalSeries(node); + return RpcUtils.getStatus(TSStatusCode.SUCCESS_STATUS); + } catch (final MetadataException e) { + logger.error(e.getMessage(), e); + return RpcUtils.getStatus(e.getErrorCode(), e.getMessage()); + } + } + + @Override + public TSStatus visitUnlockForAlias( + final UnlockForAliasNode node, final ISchemaRegion schemaRegion) { + try { + schemaRegion.unlockForAlias(node); + return RpcUtils.getStatus(TSStatusCode.SUCCESS_STATUS); + } catch (final MetadataException e) { + logger.error(e.getMessage(), e); + return RpcUtils.getStatus(e.getErrorCode(), e.getMessage()); + } + } + @Override public TSStatus visitPlan(PlanNode node, ISchemaRegion context) { return null; diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/protocol/client/ConfigNodeClient.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/protocol/client/ConfigNodeClient.java index df80d49b502b..028352ed1868 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/protocol/client/ConfigNodeClient.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/protocol/client/ConfigNodeClient.java @@ -52,6 +52,7 @@ import org.apache.iotdb.confignode.rpc.thrift.TAINodeRestartReq; import org.apache.iotdb.confignode.rpc.thrift.TAINodeRestartResp; import org.apache.iotdb.confignode.rpc.thrift.TAddConsensusGroupReq; +import org.apache.iotdb.confignode.rpc.thrift.TAliasTimeSeriesReq; import org.apache.iotdb.confignode.rpc.thrift.TAlterEncodingCompressorReq; import org.apache.iotdb.confignode.rpc.thrift.TAlterLogicalViewReq; import org.apache.iotdb.confignode.rpc.thrift.TAlterOrDropTableReq; @@ -1112,6 +1113,12 @@ public TSStatus deleteTimeSeries(TDeleteTimeSeriesReq req) throws TException { () -> client.deleteTimeSeries(req), status -> !updateConfigNodeLeader(status)); } + @Override + public TSStatus aliasTimeSeries(TAliasTimeSeriesReq req) throws TException { + return executeRemoteCallWithRetry( + () -> client.aliasTimeSeries(req), status -> !updateConfigNodeLeader(status)); + } + @Override public TSStatus deleteLogicalView(TDeleteLogicalViewReq req) throws TException { return executeRemoteCallWithRetry( diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/protocol/thrift/impl/DataNodeInternalRPCServiceImpl.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/protocol/thrift/impl/DataNodeInternalRPCServiceImpl.java index 8f1b97b71964..88826ab066be 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/protocol/thrift/impl/DataNodeInternalRPCServiceImpl.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/protocol/thrift/impl/DataNodeInternalRPCServiceImpl.java @@ -114,6 +114,9 @@ import org.apache.iotdb.db.protocol.thrift.OperationType; import org.apache.iotdb.db.queryengine.common.FragmentInstanceId; import org.apache.iotdb.db.queryengine.common.SessionInfo; +import org.apache.iotdb.db.queryengine.common.schematree.ClusterSchemaTree; +import org.apache.iotdb.db.queryengine.common.schematree.DeviceSchemaInfo; +import org.apache.iotdb.db.queryengine.common.schematree.IMeasurementSchemaInfo; import org.apache.iotdb.db.queryengine.execution.executor.RegionExecutionResult; import org.apache.iotdb.db.queryengine.execution.executor.RegionReadExecutor; import org.apache.iotdb.db.queryengine.execution.executor.RegionWriteExecutor; @@ -148,11 +151,18 @@ import org.apache.iotdb.db.queryengine.plan.planner.plan.node.load.LoadTsFilePieceNode; import org.apache.iotdb.db.queryengine.plan.planner.plan.node.metadata.write.AlterEncodingCompressorNode; import org.apache.iotdb.db.queryengine.plan.planner.plan.node.metadata.write.ConstructSchemaBlackListNode; +import org.apache.iotdb.db.queryengine.plan.planner.plan.node.metadata.write.CreateAliasSeriesNode; import org.apache.iotdb.db.queryengine.plan.planner.plan.node.metadata.write.DeactivateTemplateNode; import org.apache.iotdb.db.queryengine.plan.planner.plan.node.metadata.write.DeleteTimeSeriesNode; +import org.apache.iotdb.db.queryengine.plan.planner.plan.node.metadata.write.DropAliasSeriesNode; +import org.apache.iotdb.db.queryengine.plan.planner.plan.node.metadata.write.EnablePhysicalSeriesNode; +import org.apache.iotdb.db.queryengine.plan.planner.plan.node.metadata.write.LockAliasNode; +import org.apache.iotdb.db.queryengine.plan.planner.plan.node.metadata.write.MarkSeriesDisabledNode; import org.apache.iotdb.db.queryengine.plan.planner.plan.node.metadata.write.PreDeactivateTemplateNode; import org.apache.iotdb.db.queryengine.plan.planner.plan.node.metadata.write.RollbackPreDeactivateTemplateNode; import org.apache.iotdb.db.queryengine.plan.planner.plan.node.metadata.write.RollbackSchemaBlackListNode; +import org.apache.iotdb.db.queryengine.plan.planner.plan.node.metadata.write.UnlockForAliasNode; +import org.apache.iotdb.db.queryengine.plan.planner.plan.node.metadata.write.UpdatePhysicalAliasRefNode; import org.apache.iotdb.db.queryengine.plan.planner.plan.node.metadata.write.view.AlterLogicalViewNode; import org.apache.iotdb.db.queryengine.plan.planner.plan.node.metadata.write.view.ConstructLogicalViewBlackListNode; import org.apache.iotdb.db.queryengine.plan.planner.plan.node.metadata.write.view.DeleteLogicalViewNode; @@ -178,6 +188,7 @@ import org.apache.iotdb.db.queryengine.plan.udf.UDFManagementService; import org.apache.iotdb.db.schemaengine.SchemaEngine; import org.apache.iotdb.db.schemaengine.schemaregion.ISchemaRegion; +import org.apache.iotdb.db.schemaengine.schemaregion.mtree.impl.mem.mnode.info.MeasurementInfo; import org.apache.iotdb.db.schemaengine.schemaregion.read.resp.info.ITimeSeriesSchemaInfo; import org.apache.iotdb.db.schemaengine.schemaregion.read.resp.reader.ISchemaReader; import org.apache.iotdb.db.schemaengine.table.DataNodeTableCache; @@ -208,6 +219,8 @@ import org.apache.iotdb.metrics.utils.SystemMetric; import org.apache.iotdb.mpp.rpc.thrift.IDataNodeRPCService; import org.apache.iotdb.mpp.rpc.thrift.TActiveTriggerInstanceReq; +import org.apache.iotdb.mpp.rpc.thrift.TAliasTimeSeriesReq; +import org.apache.iotdb.mpp.rpc.thrift.TAliasTimeSeriesResp; import org.apache.iotdb.mpp.rpc.thrift.TAlterEncodingCompressorReq; import org.apache.iotdb.mpp.rpc.thrift.TAlterViewReq; import org.apache.iotdb.mpp.rpc.thrift.TAttributeUpdateReq; @@ -226,6 +239,7 @@ import org.apache.iotdb.mpp.rpc.thrift.TConstructViewSchemaBlackListReq; import org.apache.iotdb.mpp.rpc.thrift.TCountPathsUsingTemplateReq; import org.apache.iotdb.mpp.rpc.thrift.TCountPathsUsingTemplateResp; +import org.apache.iotdb.mpp.rpc.thrift.TCreateAliasSeriesReq; import org.apache.iotdb.mpp.rpc.thrift.TCreateDataRegionReq; import org.apache.iotdb.mpp.rpc.thrift.TCreateFunctionInstanceReq; import org.apache.iotdb.mpp.rpc.thrift.TCreatePeerReq; @@ -242,9 +256,11 @@ import org.apache.iotdb.mpp.rpc.thrift.TDeleteViewSchemaReq; import org.apache.iotdb.mpp.rpc.thrift.TDeviceViewReq; import org.apache.iotdb.mpp.rpc.thrift.TDeviceViewResp; +import org.apache.iotdb.mpp.rpc.thrift.TDropAliasSeriesReq; import org.apache.iotdb.mpp.rpc.thrift.TDropFunctionInstanceReq; import org.apache.iotdb.mpp.rpc.thrift.TDropPipePluginInstanceReq; import org.apache.iotdb.mpp.rpc.thrift.TDropTriggerInstanceReq; +import org.apache.iotdb.mpp.rpc.thrift.TEnablePhysicalSeriesReq; import org.apache.iotdb.mpp.rpc.thrift.TExecuteCQ; import org.apache.iotdb.mpp.rpc.thrift.TFetchFragmentInstanceInfoReq; import org.apache.iotdb.mpp.rpc.thrift.TFetchFragmentInstanceStatisticsReq; @@ -263,7 +279,9 @@ import org.apache.iotdb.mpp.rpc.thrift.TKillQueryInstanceReq; import org.apache.iotdb.mpp.rpc.thrift.TLoadCommandReq; import org.apache.iotdb.mpp.rpc.thrift.TLoadResp; +import org.apache.iotdb.mpp.rpc.thrift.TLockAndGetSchemaInfoForAliasReq; import org.apache.iotdb.mpp.rpc.thrift.TMaintainPeerReq; +import org.apache.iotdb.mpp.rpc.thrift.TMarkSeriesDisabledReq; import org.apache.iotdb.mpp.rpc.thrift.TNotifyRegionMigrationReq; import org.apache.iotdb.mpp.rpc.thrift.TPipeHeartbeatReq; import org.apache.iotdb.mpp.rpc.thrift.TPushConsumerGroupMetaReq; @@ -300,6 +318,7 @@ import org.apache.iotdb.mpp.rpc.thrift.TTableDeviceDeletionWithPatternOrModReq; import org.apache.iotdb.mpp.rpc.thrift.TTableDeviceInvalidateCacheReq; import org.apache.iotdb.mpp.rpc.thrift.TTsFilePieceReq; +import org.apache.iotdb.mpp.rpc.thrift.TUpdatePhysicalAliasRefReq; import org.apache.iotdb.mpp.rpc.thrift.TUpdateTableReq; import org.apache.iotdb.mpp.rpc.thrift.TUpdateTemplateReq; import org.apache.iotdb.mpp.rpc.thrift.TUpdateTriggerLocationReq; @@ -816,6 +835,445 @@ public TSStatus deleteTimeSeries(final TDeleteTimeSeriesReq req) throws TExcepti }); } + @Override + public TAliasTimeSeriesResp lockAndGetSchemaInfoForAlias( + final TLockAndGetSchemaInfoForAliasReq req) throws TException { + final PartialPath oldPath = + (PartialPath) PathDeserializeUtil.deserialize(ByteBuffer.wrap(req.getOldPath())); + final PartialPath newPath = + (PartialPath) PathDeserializeUtil.deserialize(ByteBuffer.wrap(req.getNewPath())); + + final TAliasTimeSeriesResp resp = new TAliasTimeSeriesResp(); + try { + if (req.getSchemaRegionIdList().isEmpty()) { + resp.setStatus( + RpcUtils.getStatus(TSStatusCode.ILLEGAL_PARAMETER, "SchemaRegionIdList is empty")); + return resp; + } + + final SchemaRegionId schemaRegionId = + new SchemaRegionId(req.getSchemaRegionIdList().get(0).getId()); + final ISchemaRegion schemaRegion = schemaEngine.getSchemaRegion(schemaRegionId); + + // Check if oldPath belongs to this schema region + // Note: newPath may belong to a different database, which is allowed for alias operations + String database = schemaRegion.getDatabaseFullPath(); + String oldPathFull = oldPath.getFullPath(); + if (!oldPathFull.startsWith(database)) { + resp.setStatus( + RpcUtils.getStatus( + TSStatusCode.ILLEGAL_PARAMETER, + String.format("Path %s does not belong to database %s", oldPathFull, database))); + return resp; + } + + // Step 1: Execute lock operation through RegionWriteExecutor (consensus) + // This will lock the schema region and mark oldPath as isRenaming through consensus + RegionWriteExecutor executor = new RegionWriteExecutor(); + RegionExecutionResult lockResult = + executor.execute( + schemaRegionId, + req.isSetIsGeneratedByPipe() && req.isIsGeneratedByPipe() + ? new PipeEnrichedNonWritePlanNode( + new LockAliasNode(new PlanNodeId(""), oldPath, newPath)) + : new LockAliasNode(new PlanNodeId(""), oldPath, newPath)); + if (!lockResult.isAccepted() + || lockResult.getStatus().getCode() != TSStatusCode.SUCCESS_STATUS.getStatusCode()) { + resp.setStatus(lockResult.getStatus()); + return resp; + } + + // Step 2: Fetch schema information for oldPath (including tags and attributes) + PathPatternTree patternTree = new PathPatternTree(); + patternTree.appendFullPath(oldPath); + ClusterSchemaTree schemaTree = + schemaRegion.fetchSeriesSchema(patternTree, null, true, true, false, false); + + // Extract schema info for oldPath + Pair, Integer> measurementPaths = + schemaTree.searchMeasurementPaths(oldPath); + if (measurementPaths == null || measurementPaths.left.isEmpty()) { + resp.setStatus( + RpcUtils.getStatus( + TSStatusCode.PATH_NOT_EXIST, + String.format("Timeseries [%s] does not exist", oldPathFull))); + return resp; + } + + MeasurementPath measurementPath = measurementPaths.left.get(0); + DeviceSchemaInfo deviceSchemaInfo = + schemaTree.searchDeviceSchemaInfo( + measurementPath.getDevicePath(), + Collections.singletonList(measurementPath.getMeasurement())); + if (deviceSchemaInfo == null || deviceSchemaInfo.getMeasurementSchemaInfoList().isEmpty()) { + resp.setStatus( + RpcUtils.getStatus( + TSStatusCode.INTERNAL_SERVER_ERROR, + String.format("Failed to get schema info for [%s]", oldPathFull))); + return resp; + } + + IMeasurementSchemaInfo schemaInfo = deviceSchemaInfo.getMeasurementSchemaInfoList().get(0); + IMeasurementSchema schema = schemaInfo.getSchema(); + + // Step 3: Build TTimeSeriesInfo + org.apache.iotdb.mpp.rpc.thrift.TTimeSeriesInfo timeSeriesInfo = + new org.apache.iotdb.mpp.rpc.thrift.TTimeSeriesInfo(); + + // Serialize path + ByteArrayOutputStream pathStream = new ByteArrayOutputStream(); + try { + measurementPath.serialize(pathStream); + timeSeriesInfo.setPath(ByteBuffer.wrap(pathStream.toByteArray())); + } catch (IOException e) { + resp.setStatus( + RpcUtils.getStatus( + TSStatusCode.INTERNAL_SERVER_ERROR, "Failed to serialize path: " + e.getMessage())); + return resp; + } + + // Set data type, encoding, compressor + timeSeriesInfo.setDataType(schema.getType().ordinal()); + timeSeriesInfo.setEncoding(schema.getEncodingType().ordinal()); + timeSeriesInfo.setCompressor(schema.getCompressor().ordinal()); + + // Set alias + if (schemaInfo.getAlias() != null) { + timeSeriesInfo.setMeasurementAlias(schemaInfo.getAlias()); + } + + // Set props (from schema), excluding internal alias-related keys + if (schema instanceof org.apache.tsfile.write.schema.MeasurementSchema) { + Map props = + ((org.apache.tsfile.write.schema.MeasurementSchema) schema).getProps(); + if (props != null && !props.isEmpty()) { + // Remove internal alias-related keys if they exist + Map propsCopy = new HashMap<>(props); + propsCopy.remove(MeasurementInfo.IS_RENAMED_KEY); + propsCopy.remove(MeasurementInfo.IS_RENAMING_KEY); + propsCopy.remove(MeasurementInfo.DISABLED_KEY); + propsCopy.remove(MeasurementInfo.ORIGINAL_PATH_KEY); + propsCopy.remove(MeasurementInfo.ALIAS_PATH_KEY); + if (!propsCopy.isEmpty()) { + timeSeriesInfo.setProps(propsCopy); + } + } + } + + // Set tags and attributes + Map tags = schemaInfo.getTagMap(); + if (tags != null && !tags.isEmpty()) { + timeSeriesInfo.setTags(tags); + } + Map attributes = schemaInfo.getAttributeMap(); + if (attributes != null && !attributes.isEmpty()) { + timeSeriesInfo.setAttributes(attributes); + } + + // Step 4: Determine if oldPath is an alias series and get physical path + PartialPath physicalPath = null; + boolean isRenamed = false; + if (schema instanceof org.apache.tsfile.write.schema.MeasurementSchema) { + Map props = + ((org.apache.tsfile.write.schema.MeasurementSchema) schema).getProps(); + if (props != null) { + String isRenamedValue = props.get(MeasurementInfo.IS_RENAMED_KEY); + isRenamed = Boolean.parseBoolean(isRenamedValue); + if (isRenamed) { + String originalPathStr = props.get(MeasurementInfo.ORIGINAL_PATH_KEY); + if (originalPathStr != null) { + try { + physicalPath = new PartialPath(originalPathStr); + } catch (IllegalPathException e) { + LOGGER.warn("Failed to parse original path: {}", originalPathStr, e); + } + } + } + } + } + + resp.setTimeSeriesInfo(timeSeriesInfo); + resp.setIsRenamed(isRenamed); + if (physicalPath != null) { + ByteArrayOutputStream physicalPathStream = new ByteArrayOutputStream(); + try { + physicalPath.serialize(physicalPathStream); + resp.setPhysicalPath(ByteBuffer.wrap(physicalPathStream.toByteArray())); + } catch (IOException e) { + LOGGER.warn("Failed to serialize physical path", e); + } + } + resp.setStatus(RpcUtils.getStatus(TSStatusCode.SUCCESS_STATUS)); + } catch (MetadataException e) { + LOGGER.error("Error locking and getting schema info for alias", e); + resp.setStatus(RpcUtils.getStatus(e.getErrorCode(), e.getMessage())); + } catch (Exception e) { + LOGGER.error("Error locking and getting schema info for alias", e); + resp.setStatus( + RpcUtils.getStatus( + TSStatusCode.INTERNAL_SERVER_ERROR, + "Error locking and getting schema info: " + e.getMessage())); + } + return resp; + } + + @Override + public TSStatus createAliasSeries(final TCreateAliasSeriesReq req) throws TException { + final PartialPath oldPath = + (PartialPath) PathDeserializeUtil.deserialize(ByteBuffer.wrap(req.getOldPath())); + final PartialPath newPath = + (PartialPath) PathDeserializeUtil.deserialize(ByteBuffer.wrap(req.getNewPath())); + + final org.apache.iotdb.mpp.rpc.thrift.TTimeSeriesInfo timeSeriesInfo = req.getTimeSeriesInfo(); + + try { + if (req.getSchemaRegionIdList().isEmpty()) { + return RpcUtils.getStatus(TSStatusCode.ILLEGAL_PARAMETER, "SchemaRegionIdList is empty"); + } + + final SchemaRegionId schemaRegionId = + new SchemaRegionId(req.getSchemaRegionIdList().get(0).getId()); + final ISchemaRegion schemaRegion = schemaEngine.getSchemaRegion(schemaRegionId); + + // Check if oldPath belongs to this schema region + // Note: newPath may belong to a different database, which is allowed for alias operations + String database = schemaRegion.getDatabaseFullPath(); + String newPathFull = newPath.getFullPath(); + if (!newPathFull.startsWith(database)) { + return RpcUtils.getStatus( + TSStatusCode.ILLEGAL_PARAMETER, + String.format("Path %s does not belong to database %s", newPathFull, database)); + } + + RegionWriteExecutor executor = new RegionWriteExecutor(); + RegionExecutionResult result = + executor.execute( + schemaRegionId, + req.isSetIsGeneratedByPipe() && req.isIsGeneratedByPipe() + ? new PipeEnrichedNonWritePlanNode( + new CreateAliasSeriesNode( + new PlanNodeId(""), oldPath, newPath, timeSeriesInfo)) + : new CreateAliasSeriesNode( + new PlanNodeId(""), oldPath, newPath, timeSeriesInfo)); + + return result.getStatus(); + } catch (Exception e) { + LOGGER.error("Error creating alias series", e); + return RpcUtils.getStatus( + TSStatusCode.INTERNAL_SERVER_ERROR, "Error creating alias series: " + e.getMessage()); + } + } + + @Override + public TSStatus markSeriesDisabled(final TMarkSeriesDisabledReq req) throws TException { + final PartialPath oldPath = + (PartialPath) PathDeserializeUtil.deserialize(ByteBuffer.wrap(req.getOldPath())); + final PartialPath newPath = + (PartialPath) PathDeserializeUtil.deserialize(ByteBuffer.wrap(req.getNewPath())); + + try { + if (req.getSchemaRegionIdList().isEmpty()) { + return RpcUtils.getStatus(TSStatusCode.ILLEGAL_PARAMETER, "SchemaRegionIdList is empty"); + } + + final SchemaRegionId schemaRegionId = + new SchemaRegionId(req.getSchemaRegionIdList().get(0).getId()); + final ISchemaRegion schemaRegion = schemaEngine.getSchemaRegion(schemaRegionId); + + // Check if oldPath belongs to this schema region + String database = schemaRegion.getDatabaseFullPath(); + String oldPathFull = oldPath.getFullPath(); + if (!oldPathFull.startsWith(database)) { + return RpcUtils.getStatus( + TSStatusCode.ILLEGAL_PARAMETER, + String.format("Path %s does not belong to database %s", oldPathFull, database)); + } + + RegionWriteExecutor executor = new RegionWriteExecutor(); + RegionExecutionResult result = + executor.execute( + schemaRegionId, + req.isSetIsGeneratedByPipe() && req.isIsGeneratedByPipe() + ? new PipeEnrichedNonWritePlanNode( + new MarkSeriesDisabledNode(new PlanNodeId(""), oldPath, newPath)) + : new MarkSeriesDisabledNode(new PlanNodeId(""), oldPath, newPath)); + + return result.getStatus(); + } catch (Exception e) { + LOGGER.error("Error marking series as disabled", e); + return RpcUtils.getStatus( + TSStatusCode.INTERNAL_SERVER_ERROR, + "Error marking series as disabled: " + e.getMessage()); + } + } + + @Override + public TSStatus updatePhysicalAliasRef(final TUpdatePhysicalAliasRefReq req) throws TException { + final PartialPath physicalPath = + (PartialPath) PathDeserializeUtil.deserialize(ByteBuffer.wrap(req.getPhysicalPath())); + final PartialPath newAliasPath = + (PartialPath) PathDeserializeUtil.deserialize(ByteBuffer.wrap(req.getNewAliasPath())); + + try { + if (req.getSchemaRegionIdList().isEmpty()) { + return RpcUtils.getStatus(TSStatusCode.ILLEGAL_PARAMETER, "SchemaRegionIdList is empty"); + } + + final SchemaRegionId schemaRegionId = + new SchemaRegionId(req.getSchemaRegionIdList().get(0).getId()); + final ISchemaRegion schemaRegion = schemaEngine.getSchemaRegion(schemaRegionId); + + // Check if physicalPath belongs to this schema region + String database = schemaRegion.getDatabaseFullPath(); + String physicalPathFull = physicalPath.getFullPath(); + if (!physicalPathFull.startsWith(database)) { + return RpcUtils.getStatus( + TSStatusCode.ILLEGAL_PARAMETER, + String.format("Path %s does not belong to database %s", physicalPathFull, database)); + } + + RegionWriteExecutor executor = new RegionWriteExecutor(); + RegionExecutionResult result = + executor.execute( + schemaRegionId, + req.isSetIsGeneratedByPipe() && req.isIsGeneratedByPipe() + ? new PipeEnrichedNonWritePlanNode( + new UpdatePhysicalAliasRefNode( + new PlanNodeId(""), physicalPath, newAliasPath)) + : new UpdatePhysicalAliasRefNode(new PlanNodeId(""), physicalPath, newAliasPath)); + + return result.getStatus(); + } catch (Exception e) { + LOGGER.error("Error updating physical alias reference", e); + return RpcUtils.getStatus( + TSStatusCode.INTERNAL_SERVER_ERROR, + "Error updating physical alias reference: " + e.getMessage()); + } + } + + @Override + public TSStatus dropAliasSeries(final TDropAliasSeriesReq req) throws TException { + final PartialPath aliasPath = + (PartialPath) PathDeserializeUtil.deserialize(ByteBuffer.wrap(req.getAliasPath())); + + try { + if (req.getSchemaRegionIdList().isEmpty()) { + return RpcUtils.getStatus(TSStatusCode.ILLEGAL_PARAMETER, "SchemaRegionIdList is empty"); + } + + final SchemaRegionId schemaRegionId = + new SchemaRegionId(req.getSchemaRegionIdList().get(0).getId()); + final ISchemaRegion schemaRegion = schemaEngine.getSchemaRegion(schemaRegionId); + + // Check if aliasPath belongs to this schema region + String database = schemaRegion.getDatabaseFullPath(); + String aliasPathFull = aliasPath.getFullPath(); + if (!aliasPathFull.startsWith(database)) { + return RpcUtils.getStatus( + TSStatusCode.ILLEGAL_PARAMETER, + String.format("Path %s does not belong to database %s", aliasPathFull, database)); + } + + RegionWriteExecutor executor = new RegionWriteExecutor(); + RegionExecutionResult result = + executor.execute( + schemaRegionId, + req.isSetIsGeneratedByPipe() && req.isIsGeneratedByPipe() + ? new PipeEnrichedNonWritePlanNode( + new DropAliasSeriesNode(new PlanNodeId(""), aliasPath)) + : new DropAliasSeriesNode(new PlanNodeId(""), aliasPath)); + + return result.getStatus(); + } catch (Exception e) { + LOGGER.error("Error dropping alias series", e); + return RpcUtils.getStatus( + TSStatusCode.INTERNAL_SERVER_ERROR, "Error dropping alias series: " + e.getMessage()); + } + } + + @Override + public TSStatus enablePhysicalSeries(final TEnablePhysicalSeriesReq req) throws TException { + final PartialPath physicalPath = + (PartialPath) PathDeserializeUtil.deserialize(ByteBuffer.wrap(req.getPhysicalPath())); + + try { + if (req.getSchemaRegionIdList().isEmpty()) { + return RpcUtils.getStatus(TSStatusCode.ILLEGAL_PARAMETER, "SchemaRegionIdList is empty"); + } + + final SchemaRegionId schemaRegionId = + new SchemaRegionId(req.getSchemaRegionIdList().get(0).getId()); + final ISchemaRegion schemaRegion = schemaEngine.getSchemaRegion(schemaRegionId); + + // Check if physicalPath belongs to this schema region + String database = schemaRegion.getDatabaseFullPath(); + String physicalPathFull = physicalPath.getFullPath(); + if (!physicalPathFull.startsWith(database)) { + return RpcUtils.getStatus( + TSStatusCode.ILLEGAL_PARAMETER, + String.format("Path %s does not belong to database %s", physicalPathFull, database)); + } + + RegionWriteExecutor executor = new RegionWriteExecutor(); + RegionExecutionResult result = + executor.execute( + schemaRegionId, + req.isSetIsGeneratedByPipe() && req.isIsGeneratedByPipe() + ? new PipeEnrichedNonWritePlanNode( + new EnablePhysicalSeriesNode(new PlanNodeId(""), physicalPath)) + : new EnablePhysicalSeriesNode(new PlanNodeId(""), physicalPath)); + + return result.getStatus(); + } catch (Exception e) { + LOGGER.error("Error enabling physical series", e); + return RpcUtils.getStatus( + TSStatusCode.INTERNAL_SERVER_ERROR, "Error enabling physical series: " + e.getMessage()); + } + } + + @Override + public TSStatus unlockForAlias(final TAliasTimeSeriesReq req) throws TException { + final PartialPath oldPath = + (PartialPath) PathDeserializeUtil.deserialize(ByteBuffer.wrap(req.getOldPath())); + final PartialPath newPath = + (PartialPath) PathDeserializeUtil.deserialize(ByteBuffer.wrap(req.getNewPath())); + + try { + if (req.getSchemaRegionIdList().isEmpty()) { + return RpcUtils.getStatus(TSStatusCode.ILLEGAL_PARAMETER, "SchemaRegionIdList is empty"); + } + + final SchemaRegionId schemaRegionId = + new SchemaRegionId(req.getSchemaRegionIdList().get(0).getId()); + final ISchemaRegion schemaRegion = schemaEngine.getSchemaRegion(schemaRegionId); + + // Check if oldPath belongs to this schema region + // Note: newPath may belong to a different database, which is allowed for alias operations + String database = schemaRegion.getDatabaseFullPath(); + String oldPathFull = oldPath.getFullPath(); + if (!oldPathFull.startsWith(database)) { + return RpcUtils.getStatus( + TSStatusCode.ILLEGAL_PARAMETER, + String.format("Path %s does not belong to database %s", oldPathFull, database)); + } + + RegionWriteExecutor executor = new RegionWriteExecutor(); + RegionExecutionResult result = + executor.execute( + schemaRegionId, + req.isSetIsGeneratedByPipe() && req.isIsGeneratedByPipe() + ? new PipeEnrichedNonWritePlanNode( + new UnlockForAliasNode(new PlanNodeId(""), oldPath, newPath)) + : new UnlockForAliasNode(new PlanNodeId(""), oldPath, newPath)); + + return result.getStatus(); + } catch (Exception e) { + LOGGER.error("Error unlocking for alias", e); + return RpcUtils.getStatus( + TSStatusCode.INTERNAL_SERVER_ERROR, "Error unlocking for alias: " + e.getMessage()); + } + } + @Override public TSStatus alterEncodingCompressor(final TAlterEncodingCompressorReq req) throws TException { final PathPatternTree patternTree = diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/analyze/AnalyzeVisitor.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/analyze/AnalyzeVisitor.java index dc56fe118b7b..2d70064b61c9 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/analyze/AnalyzeVisitor.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/analyze/AnalyzeVisitor.java @@ -108,6 +108,7 @@ import org.apache.iotdb.db.queryengine.plan.statement.internal.InternalCreateMultiTimeSeriesStatement; import org.apache.iotdb.db.queryengine.plan.statement.internal.InternalCreateTimeSeriesStatement; import org.apache.iotdb.db.queryengine.plan.statement.internal.SeriesSchemaFetchStatement; +import org.apache.iotdb.db.queryengine.plan.statement.metadata.AliasTimeSeriesStatement; import org.apache.iotdb.db.queryengine.plan.statement.metadata.AlterTimeSeriesStatement; import org.apache.iotdb.db.queryengine.plan.statement.metadata.CountDatabaseStatement; import org.apache.iotdb.db.queryengine.plan.statement.metadata.CountDevicesStatement; @@ -2747,6 +2748,48 @@ public Analysis visitAlterTimeSeries( return analysis; } + @Override + public Analysis visitAliasTimeSeriesStatement( + AliasTimeSeriesStatement aliasTimeSeriesStatement, MPPQueryContext context) { + context.setQueryType(QueryType.WRITE); + Analysis analysis = new Analysis(); + analysis.setRealStatement(aliasTimeSeriesStatement); + + // Verify old path exists using fetchSchema + PathPatternTree oldPathPatternTree = new PathPatternTree(); + oldPathPatternTree.appendFullPath(aliasTimeSeriesStatement.getPath()); + ISchemaTree oldPathSchemaTree = + schemaFetcher.fetchSchema(oldPathPatternTree, false, context, false); + if (oldPathSchemaTree.isEmpty()) { + throw new RuntimeException( + new SemanticException( + String.format( + "Timeseries [%s] does not exist.", + aliasTimeSeriesStatement.getPath().getFullPath()))); + } + + // Check if old path and new path are the same + if (aliasTimeSeriesStatement.getPath().equals(aliasTimeSeriesStatement.getNewPath())) { + // Old path and new path are the same, no need to rename + analysis.setFinishQueryAfterAnalyze(true); + return analysis; + } + + // Verify new path does not exist using fetchSchema + PathPatternTree newPathPatternTree = new PathPatternTree(); + newPathPatternTree.appendFullPath(aliasTimeSeriesStatement.getNewPath()); + ISchemaTree newPathSchemaTree = + schemaFetcher.fetchSchema(newPathPatternTree, false, context, false); + if (!newPathSchemaTree.isEmpty()) { + throw new RuntimeException( + new SemanticException( + String.format( + "Timeseries [%s] already exists.", + aliasTimeSeriesStatement.getNewPath().getFullPath()))); + } + return analysis; + } + @Override public Analysis visitInsertTablet( InsertTabletStatement insertTabletStatement, MPPQueryContext context) { diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/execution/config/TreeConfigTaskVisitor.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/execution/config/TreeConfigTaskVisitor.java index ed5e2e434f48..ea475d56dbc1 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/execution/config/TreeConfigTaskVisitor.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/execution/config/TreeConfigTaskVisitor.java @@ -30,6 +30,7 @@ import org.apache.iotdb.db.auth.AuthorityChecker; import org.apache.iotdb.db.exception.sql.SemanticException; import org.apache.iotdb.db.queryengine.common.MPPQueryContext; +import org.apache.iotdb.db.queryengine.plan.execution.config.metadata.AliasTimeSeriesTask; import org.apache.iotdb.db.queryengine.plan.execution.config.metadata.AlterEncodingCompressorTask; import org.apache.iotdb.db.queryengine.plan.execution.config.metadata.CountDatabaseTask; import org.apache.iotdb.db.queryengine.plan.execution.config.metadata.CountTimeSlotListTask; @@ -125,6 +126,7 @@ import org.apache.iotdb.db.queryengine.plan.statement.Statement; import org.apache.iotdb.db.queryengine.plan.statement.StatementNode; import org.apache.iotdb.db.queryengine.plan.statement.StatementVisitor; +import org.apache.iotdb.db.queryengine.plan.statement.metadata.AliasTimeSeriesStatement; import org.apache.iotdb.db.queryengine.plan.statement.metadata.AlterEncodingCompressorStatement; import org.apache.iotdb.db.queryengine.plan.statement.metadata.CountDatabaseStatement; import org.apache.iotdb.db.queryengine.plan.statement.metadata.CountTimeSlotListStatement; @@ -742,6 +744,12 @@ public IConfigTask visitAlterLogicalView( return new AlterLogicalViewTask(alterLogicalViewStatement, context); } + @Override + public IConfigTask visitAliasTimeSeriesStatement( + AliasTimeSeriesStatement aliasTimeSeriesStatement, MPPQueryContext context) { + return new AliasTimeSeriesTask(aliasTimeSeriesStatement, context); + } + @Override public IConfigTask visitGetRegionId( GetRegionIdStatement getRegionIdStatement, MPPQueryContext context) { diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/execution/config/executor/ClusterConfigTaskExecutor.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/execution/config/executor/ClusterConfigTaskExecutor.java index e33cc6154078..d91a9757ceee 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/execution/config/executor/ClusterConfigTaskExecutor.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/execution/config/executor/ClusterConfigTaskExecutor.java @@ -89,6 +89,7 @@ import org.apache.iotdb.commons.utils.SerializeUtils; import org.apache.iotdb.commons.utils.TimePartitionUtils; import org.apache.iotdb.confignode.rpc.thrift.TAINodeRemoveReq; +import org.apache.iotdb.confignode.rpc.thrift.TAliasTimeSeriesReq; import org.apache.iotdb.confignode.rpc.thrift.TAlterEncodingCompressorReq; import org.apache.iotdb.confignode.rpc.thrift.TAlterLogicalViewReq; import org.apache.iotdb.confignode.rpc.thrift.TAlterOrDropTableReq; @@ -248,6 +249,7 @@ import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.ShowCluster; import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.ShowDB; import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.Use; +import org.apache.iotdb.db.queryengine.plan.statement.metadata.AliasTimeSeriesStatement; import org.apache.iotdb.db.queryengine.plan.statement.metadata.AlterEncodingCompressorStatement; import org.apache.iotdb.db.queryengine.plan.statement.metadata.CountDatabaseStatement; import org.apache.iotdb.db.queryengine.plan.statement.metadata.CountTimeSlotListStatement; @@ -3128,6 +3130,65 @@ public SettableFuture alterLogicalView( } } + @Override + public SettableFuture aliasTimeSeries( + final AliasTimeSeriesStatement aliasTimeSeriesStatement, final MPPQueryContext context) { + final SettableFuture future = SettableFuture.create(); + + // Serialize the old path and new path + final ByteArrayOutputStream oldPathStream = new ByteArrayOutputStream(); + final ByteArrayOutputStream newPathStream = new ByteArrayOutputStream(); + try { + // Write old path + aliasTimeSeriesStatement.getPath().serialize(oldPathStream); + // Write new path + aliasTimeSeriesStatement.getNewPath().serialize(newPathStream); + } catch (final IOException e) { + future.setException(new RuntimeException("Failed to serialize paths", e)); + return future; + } + + final TAliasTimeSeriesReq req = + new TAliasTimeSeriesReq( + context.getQueryId().getId(), + ByteBuffer.wrap(oldPathStream.toByteArray()), + ByteBuffer.wrap(newPathStream.toByteArray())); + req.setIsGeneratedByPipe(aliasTimeSeriesStatement.isGeneratedByPipe()); + + try (final ConfigNodeClient client = + CLUSTER_DELETION_CONFIG_NODE_CLIENT_MANAGER.borrowClient(ConfigNodeInfo.CONFIG_REGION_ID)) { + TSStatus tsStatus; + do { + try { + tsStatus = client.aliasTimeSeries(req); + } catch (final TTransportException e) { + if (e.getType() == TTransportException.TIMED_OUT + || e.getCause() instanceof SocketTimeoutException) { + // time out mainly caused by slow execution, wait until + tsStatus = RpcUtils.getStatus(TSStatusCode.OVERLAP_WITH_EXISTING_TASK); + } else { + throw e; + } + } + // keep waiting until task ends + } while (TSStatusCode.OVERLAP_WITH_EXISTING_TASK.getStatusCode() == tsStatus.getCode()); + + if (TSStatusCode.SUCCESS_STATUS.getStatusCode() != tsStatus.getCode()) { + if (tsStatus.getCode() == TSStatusCode.MULTIPLE_ERROR.getStatusCode()) { + future.setException( + new BatchProcessException(tsStatus.subStatus.toArray(new TSStatus[0]))); + } else { + future.setException(new IoTDBException(tsStatus)); + } + } else { + future.set(new ConfigTaskResult(TSStatusCode.SUCCESS_STATUS)); + } + } catch (final ClientManagerException | TException e) { + future.setException(e); + } + return future; + } + @Override public TSStatus alterLogicalViewByPipe( final AlterLogicalViewNode alterLogicalViewNode, final boolean shouldMarkAsPipeRequest) { diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/execution/config/executor/IConfigTaskExecutor.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/execution/config/executor/IConfigTaskExecutor.java index 2d7c0f6d1f3a..f33671ae222b 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/execution/config/executor/IConfigTaskExecutor.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/execution/config/executor/IConfigTaskExecutor.java @@ -45,6 +45,7 @@ import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.ShowCluster; import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.ShowDB; import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.Use; +import org.apache.iotdb.db.queryengine.plan.statement.metadata.AliasTimeSeriesStatement; import org.apache.iotdb.db.queryengine.plan.statement.metadata.AlterEncodingCompressorStatement; import org.apache.iotdb.db.queryengine.plan.statement.metadata.CountDatabaseStatement; import org.apache.iotdb.db.queryengine.plan.statement.metadata.CountTimeSlotListStatement; @@ -259,6 +260,9 @@ SettableFuture alterLogicalView( TSStatus alterLogicalViewByPipe( AlterLogicalViewNode alterLogicalViewNode, boolean shouldMarkAsPipeRequest); + SettableFuture aliasTimeSeries( + AliasTimeSeriesStatement aliasTimeSeriesStatement, MPPQueryContext context); + SettableFuture getRegionId(GetRegionIdStatement getRegionIdStatement); SettableFuture getSeriesSlotList( diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/execution/config/metadata/AliasTimeSeriesTask.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/execution/config/metadata/AliasTimeSeriesTask.java new file mode 100644 index 000000000000..a2e03a341f63 --- /dev/null +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/execution/config/metadata/AliasTimeSeriesTask.java @@ -0,0 +1,46 @@ +/* + * 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.queryengine.plan.execution.config.metadata; + +import org.apache.iotdb.db.queryengine.common.MPPQueryContext; +import org.apache.iotdb.db.queryengine.plan.execution.config.ConfigTaskResult; +import org.apache.iotdb.db.queryengine.plan.execution.config.IConfigTask; +import org.apache.iotdb.db.queryengine.plan.execution.config.executor.IConfigTaskExecutor; +import org.apache.iotdb.db.queryengine.plan.statement.metadata.AliasTimeSeriesStatement; + +import com.google.common.util.concurrent.ListenableFuture; + +public class AliasTimeSeriesTask implements IConfigTask { + + private final MPPQueryContext context; + private final AliasTimeSeriesStatement aliasTimeSeriesStatement; + + public AliasTimeSeriesTask( + AliasTimeSeriesStatement aliasTimeSeriesStatement, MPPQueryContext context) { + this.aliasTimeSeriesStatement = aliasTimeSeriesStatement; + this.context = context; + } + + @Override + public ListenableFuture execute(IConfigTaskExecutor configTaskExecutor) + throws InterruptedException { + return configTaskExecutor.aliasTimeSeries(aliasTimeSeriesStatement, context); + } +} diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/parser/ASTVisitor.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/parser/ASTVisitor.java index fafa0995db17..7749d1d346d1 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/parser/ASTVisitor.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/parser/ASTVisitor.java @@ -144,6 +144,7 @@ import org.apache.iotdb.db.queryengine.plan.statement.literal.Literal; import org.apache.iotdb.db.queryengine.plan.statement.literal.LongLiteral; import org.apache.iotdb.db.queryengine.plan.statement.literal.StringLiteral; +import org.apache.iotdb.db.queryengine.plan.statement.metadata.AliasTimeSeriesStatement; import org.apache.iotdb.db.queryengine.plan.statement.metadata.AlterEncodingCompressorStatement; import org.apache.iotdb.db.queryengine.plan.statement.metadata.AlterTimeSeriesStatement; import org.apache.iotdb.db.queryengine.plan.statement.metadata.CountDatabaseStatement; @@ -613,8 +614,21 @@ public void parseAttributeClauseForTimeSeries( @Override public Statement visitAlterTimeseries(IoTDBSqlParser.AlterTimeseriesContext ctx) { + MeasurementPath path = parseFullPath(ctx.fullPath()); + + // Check if it's a timeseries path rename (RENAME TO fullPath) - return AliasTimeSeriesStatement + if (ctx.alterClause() instanceof IoTDBSqlParser.RenameTimeseriesPathContext) { + IoTDBSqlParser.RenameTimeseriesPathContext renameCtx = + (IoTDBSqlParser.RenameTimeseriesPathContext) ctx.alterClause(); + AliasTimeSeriesStatement aliasStatement = new AliasTimeSeriesStatement(); + aliasStatement.setPath(path); + aliasStatement.setNewPath(parseFullPath(renameCtx.newPath)); + return aliasStatement; + } + + // For other alter operations, use AlterTimeSeriesStatement AlterTimeSeriesStatement alterTimeSeriesStatement = new AlterTimeSeriesStatement(); - alterTimeSeriesStatement.setPath(parseFullPath(ctx.fullPath())); + alterTimeSeriesStatement.setPath(path); parseAlterClause(ctx.alterClause(), alterTimeSeriesStatement); return alterTimeSeriesStatement; } @@ -622,39 +636,54 @@ public Statement visitAlterTimeseries(IoTDBSqlParser.AlterTimeseriesContext ctx) private void parseAlterClause( IoTDBSqlParser.AlterClauseContext ctx, AlterTimeSeriesStatement alterTimeSeriesStatement) { Map alterMap = new HashMap<>(); - // Rename - if (ctx.RENAME() != null) { + // Check if it's a timeseries path rename (should be handled in visitAlterTimeseries) + // This case should not happen here as it's already handled in visitAlterTimeseries + if (ctx instanceof IoTDBSqlParser.RenameTimeseriesPathContext) { + // This should not happen - RENAME TO is handled in visitAlterTimeseries + throw new IllegalStateException( + "RenameTimeseriesPathContext should be handled in visitAlterTimeseries, not parseAlterClause"); + } else if (ctx instanceof IoTDBSqlParser.RenameTagOrAttributeContext) { + // Rename tag/attribute key (RENAME attributeKey TO attributeKey) + IoTDBSqlParser.RenameTagOrAttributeContext renameCtx = + (IoTDBSqlParser.RenameTagOrAttributeContext) ctx; alterTimeSeriesStatement.setAlterType(AlterTimeSeriesStatement.AlterType.RENAME); - alterMap.put(parseAttributeKey(ctx.beforeName), parseAttributeKey(ctx.currentName)); - } else if (ctx.SET() != null) { + alterMap.put( + parseAttributeKey(renameCtx.beforeName), parseAttributeKey(renameCtx.currentName)); + } else if (ctx instanceof IoTDBSqlParser.SetAlterContext) { // Set + IoTDBSqlParser.SetAlterContext setCtx = (IoTDBSqlParser.SetAlterContext) ctx; alterTimeSeriesStatement.setAlterType(AlterTimeSeriesStatement.AlterType.SET); - setMap(ctx, alterMap); - } else if (ctx.DROP() != null) { + setMap(setCtx, alterMap); + } else if (ctx instanceof IoTDBSqlParser.DropAlterContext) { // Drop + IoTDBSqlParser.DropAlterContext dropCtx = (IoTDBSqlParser.DropAlterContext) ctx; alterTimeSeriesStatement.setAlterType(AlterTimeSeriesStatement.AlterType.DROP); - for (int i = 0; i < ctx.attributeKey().size(); i++) { - alterMap.put(parseAttributeKey(ctx.attributeKey().get(i)), null); + for (int i = 0; i < dropCtx.attributeKey().size(); i++) { + alterMap.put(parseAttributeKey(dropCtx.attributeKey().get(i)), null); } - } else if (ctx.TAGS() != null) { + } else if (ctx instanceof IoTDBSqlParser.AddTagsAlterContext) { // Add tag + IoTDBSqlParser.AddTagsAlterContext addTagsCtx = (IoTDBSqlParser.AddTagsAlterContext) ctx; alterTimeSeriesStatement.setAlterType((AlterTimeSeriesStatement.AlterType.ADD_TAGS)); - setMap(ctx, alterMap); - } else if (ctx.ATTRIBUTES() != null) { + setMap(addTagsCtx, alterMap); + } else if (ctx instanceof IoTDBSqlParser.AddAttributesAlterContext) { // Add attribute + IoTDBSqlParser.AddAttributesAlterContext addAttrsCtx = + (IoTDBSqlParser.AddAttributesAlterContext) ctx; alterTimeSeriesStatement.setAlterType(AlterTimeSeriesStatement.AlterType.ADD_ATTRIBUTES); - setMap(ctx, alterMap); - } else { + setMap(addAttrsCtx, alterMap); + } else if (ctx instanceof IoTDBSqlParser.UpsertAlterContext) { // Upsert + IoTDBSqlParser.UpsertAlterContext upsertCtx = (IoTDBSqlParser.UpsertAlterContext) ctx; alterTimeSeriesStatement.setAlterType(AlterTimeSeriesStatement.AlterType.UPSERT); - if (ctx.aliasClause() != null) { - parseAliasClause(ctx.aliasClause(), alterTimeSeriesStatement); + if (upsertCtx.aliasClause() != null) { + parseAliasClause(upsertCtx.aliasClause(), alterTimeSeriesStatement); } - if (ctx.tagClause() != null) { - parseTagClause(ctx.tagClause(), alterTimeSeriesStatement); + if (upsertCtx.tagClause() != null) { + parseTagClause(upsertCtx.tagClause(), alterTimeSeriesStatement); } - if (ctx.attributeClause() != null) { - parseAttributeClauseForTimeSeries(ctx.attributeClause(), alterTimeSeriesStatement); + if (upsertCtx.attributeClause() != null) { + parseAttributeClauseForTimeSeries(upsertCtx.attributeClause(), alterTimeSeriesStatement); } } alterTimeSeriesStatement.setAlterMap(alterMap); @@ -3539,11 +3568,23 @@ private long parseTimeValue(IoTDBSqlParser.TimeValueContext ctx, long currentTim } /** Utils. */ - private void setMap(IoTDBSqlParser.AlterClauseContext ctx, Map alterMap) { - List tagsList = ctx.attributePair(); + private void setMap(IoTDBSqlParser.SetAlterContext ctx, Map alterMap) { + setMapFromAttributePairs(ctx.attributePair(), alterMap); + } + + private void setMap(IoTDBSqlParser.AddTagsAlterContext ctx, Map alterMap) { + setMapFromAttributePairs(ctx.attributePair(), alterMap); + } + + private void setMap(IoTDBSqlParser.AddAttributesAlterContext ctx, Map alterMap) { + setMapFromAttributePairs(ctx.attributePair(), alterMap); + } + + private void setMapFromAttributePairs( + List attributePairs, Map alterMap) { String key; - if (ctx.attributePair(0) != null) { - for (IoTDBSqlParser.AttributePairContext attributePair : tagsList) { + if (attributePairs != null && !attributePairs.isEmpty()) { + for (IoTDBSqlParser.AttributePairContext attributePair : attributePairs) { key = parseAttributeKey(attributePair.attributeKey()); alterMap.computeIfPresent( key, diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/planner/plan/node/PlanNodeType.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/planner/plan/node/PlanNodeType.java index 6ebfa6dcfdc4..8ff238d8e4da 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/planner/plan/node/PlanNodeType.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/planner/plan/node/PlanNodeType.java @@ -43,17 +43,24 @@ import org.apache.iotdb.db.queryengine.plan.planner.plan.node.metadata.write.AlterTimeSeriesNode; import org.apache.iotdb.db.queryengine.plan.planner.plan.node.metadata.write.BatchActivateTemplateNode; import org.apache.iotdb.db.queryengine.plan.planner.plan.node.metadata.write.ConstructSchemaBlackListNode; +import org.apache.iotdb.db.queryengine.plan.planner.plan.node.metadata.write.CreateAliasSeriesNode; import org.apache.iotdb.db.queryengine.plan.planner.plan.node.metadata.write.CreateAlignedTimeSeriesNode; import org.apache.iotdb.db.queryengine.plan.planner.plan.node.metadata.write.CreateMultiTimeSeriesNode; import org.apache.iotdb.db.queryengine.plan.planner.plan.node.metadata.write.CreateTimeSeriesNode; import org.apache.iotdb.db.queryengine.plan.planner.plan.node.metadata.write.DeactivateTemplateNode; import org.apache.iotdb.db.queryengine.plan.planner.plan.node.metadata.write.DeleteTimeSeriesNode; +import org.apache.iotdb.db.queryengine.plan.planner.plan.node.metadata.write.DropAliasSeriesNode; +import org.apache.iotdb.db.queryengine.plan.planner.plan.node.metadata.write.EnablePhysicalSeriesNode; import org.apache.iotdb.db.queryengine.plan.planner.plan.node.metadata.write.InternalBatchActivateTemplateNode; import org.apache.iotdb.db.queryengine.plan.planner.plan.node.metadata.write.InternalCreateMultiTimeSeriesNode; import org.apache.iotdb.db.queryengine.plan.planner.plan.node.metadata.write.InternalCreateTimeSeriesNode; +import org.apache.iotdb.db.queryengine.plan.planner.plan.node.metadata.write.LockAliasNode; +import org.apache.iotdb.db.queryengine.plan.planner.plan.node.metadata.write.MarkSeriesDisabledNode; import org.apache.iotdb.db.queryengine.plan.planner.plan.node.metadata.write.PreDeactivateTemplateNode; import org.apache.iotdb.db.queryengine.plan.planner.plan.node.metadata.write.RollbackPreDeactivateTemplateNode; import org.apache.iotdb.db.queryengine.plan.planner.plan.node.metadata.write.RollbackSchemaBlackListNode; +import org.apache.iotdb.db.queryengine.plan.planner.plan.node.metadata.write.UnlockForAliasNode; +import org.apache.iotdb.db.queryengine.plan.planner.plan.node.metadata.write.UpdatePhysicalAliasRefNode; import org.apache.iotdb.db.queryengine.plan.planner.plan.node.metadata.write.view.AlterLogicalViewNode; import org.apache.iotdb.db.queryengine.plan.planner.plan.node.metadata.write.view.ConstructLogicalViewBlackListNode; import org.apache.iotdb.db.queryengine.plan.planner.plan.node.metadata.write.view.CreateLogicalViewNode; @@ -266,6 +273,13 @@ public enum PlanNodeType { LAST_QUERY_SCAN((short) 98), ALTER_ENCODING_COMPRESSOR((short) 99), + LOCK_ALIAS((short) 100), + CREATE_ALIAS_SERIES((short) 101), + MARK_SERIES_DISABLED((short) 102), + UPDATE_PHYSICAL_ALIAS_REF((short) 103), + DROP_ALIAS_SERIES((short) 104), + ENABLE_PHYSICAL_SERIES((short) 105), + UNLOCK_FOR_ALIAS((short) 106), CREATE_OR_UPDATE_TABLE_DEVICE((short) 902), TABLE_DEVICE_QUERY_SCAN((short) 903), @@ -601,6 +615,20 @@ public static PlanNode deserialize(ByteBuffer buffer, short nodeType) { return LastQueryScanNode.deserialize(buffer); case 99: return AlterEncodingCompressorNode.deserialize(buffer); + case 100: + return LockAliasNode.deserialize(buffer); + case 101: + return CreateAliasSeriesNode.deserialize(buffer); + case 102: + return MarkSeriesDisabledNode.deserialize(buffer); + case 103: + return UpdatePhysicalAliasRefNode.deserialize(buffer); + case 104: + return DropAliasSeriesNode.deserialize(buffer); + case 105: + return EnablePhysicalSeriesNode.deserialize(buffer); + case 106: + return UnlockForAliasNode.deserialize(buffer); case 902: return CreateOrUpdateTableDeviceNode.deserialize(buffer); case 903: diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/planner/plan/node/PlanVisitor.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/planner/plan/node/PlanVisitor.java index 4bebb692254a..08054b6cdeb8 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/planner/plan/node/PlanVisitor.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/planner/plan/node/PlanVisitor.java @@ -40,17 +40,24 @@ import org.apache.iotdb.db.queryengine.plan.planner.plan.node.metadata.write.AlterTimeSeriesNode; import org.apache.iotdb.db.queryengine.plan.planner.plan.node.metadata.write.BatchActivateTemplateNode; import org.apache.iotdb.db.queryengine.plan.planner.plan.node.metadata.write.ConstructSchemaBlackListNode; +import org.apache.iotdb.db.queryengine.plan.planner.plan.node.metadata.write.CreateAliasSeriesNode; import org.apache.iotdb.db.queryengine.plan.planner.plan.node.metadata.write.CreateAlignedTimeSeriesNode; import org.apache.iotdb.db.queryengine.plan.planner.plan.node.metadata.write.CreateMultiTimeSeriesNode; import org.apache.iotdb.db.queryengine.plan.planner.plan.node.metadata.write.CreateTimeSeriesNode; import org.apache.iotdb.db.queryengine.plan.planner.plan.node.metadata.write.DeactivateTemplateNode; import org.apache.iotdb.db.queryengine.plan.planner.plan.node.metadata.write.DeleteTimeSeriesNode; +import org.apache.iotdb.db.queryengine.plan.planner.plan.node.metadata.write.DropAliasSeriesNode; +import org.apache.iotdb.db.queryengine.plan.planner.plan.node.metadata.write.EnablePhysicalSeriesNode; import org.apache.iotdb.db.queryengine.plan.planner.plan.node.metadata.write.InternalBatchActivateTemplateNode; import org.apache.iotdb.db.queryengine.plan.planner.plan.node.metadata.write.InternalCreateMultiTimeSeriesNode; import org.apache.iotdb.db.queryengine.plan.planner.plan.node.metadata.write.InternalCreateTimeSeriesNode; +import org.apache.iotdb.db.queryengine.plan.planner.plan.node.metadata.write.LockAliasNode; +import org.apache.iotdb.db.queryengine.plan.planner.plan.node.metadata.write.MarkSeriesDisabledNode; import org.apache.iotdb.db.queryengine.plan.planner.plan.node.metadata.write.PreDeactivateTemplateNode; import org.apache.iotdb.db.queryengine.plan.planner.plan.node.metadata.write.RollbackPreDeactivateTemplateNode; import org.apache.iotdb.db.queryengine.plan.planner.plan.node.metadata.write.RollbackSchemaBlackListNode; +import org.apache.iotdb.db.queryengine.plan.planner.plan.node.metadata.write.UnlockForAliasNode; +import org.apache.iotdb.db.queryengine.plan.planner.plan.node.metadata.write.UpdatePhysicalAliasRefNode; import org.apache.iotdb.db.queryengine.plan.planner.plan.node.metadata.write.view.AlterLogicalViewNode; import org.apache.iotdb.db.queryengine.plan.planner.plan.node.metadata.write.view.ConstructLogicalViewBlackListNode; import org.apache.iotdb.db.queryengine.plan.planner.plan.node.metadata.write.view.CreateLogicalViewNode; @@ -504,6 +511,34 @@ public R visitAlterEncodingCompressor(AlterEncodingCompressorNode node, C contex return visitPlan(node, context); } + public R visitCreateAliasSeries(CreateAliasSeriesNode node, C context) { + return visitPlan(node, context); + } + + public R visitMarkSeriesDisabled(MarkSeriesDisabledNode node, C context) { + return visitPlan(node, context); + } + + public R visitUpdatePhysicalAliasRef(UpdatePhysicalAliasRefNode node, C context) { + return visitPlan(node, context); + } + + public R visitDropAliasSeries(DropAliasSeriesNode node, C context) { + return visitPlan(node, context); + } + + public R visitEnablePhysicalSeries(EnablePhysicalSeriesNode node, C context) { + return visitPlan(node, context); + } + + public R visitUnlockForAlias(UnlockForAliasNode node, C context) { + return visitPlan(node, context); + } + + public R visitLockAlias(LockAliasNode node, C context) { + return visitPlan(node, context); + } + public R visitConstructSchemaBlackList(ConstructSchemaBlackListNode node, C context) { return visitPlan(node, context); } diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/planner/plan/node/metadata/write/CreateAliasSeriesNode.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/planner/plan/node/metadata/write/CreateAliasSeriesNode.java new file mode 100644 index 000000000000..a361e5c62c50 --- /dev/null +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/planner/plan/node/metadata/write/CreateAliasSeriesNode.java @@ -0,0 +1,209 @@ +/* + * 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.queryengine.plan.planner.plan.node.metadata.write; + +import org.apache.iotdb.commons.path.PartialPath; +import org.apache.iotdb.db.queryengine.plan.planner.plan.node.PlanNode; +import org.apache.iotdb.db.queryengine.plan.planner.plan.node.PlanNodeId; +import org.apache.iotdb.db.queryengine.plan.planner.plan.node.PlanNodeType; +import org.apache.iotdb.db.queryengine.plan.planner.plan.node.PlanVisitor; +import org.apache.iotdb.db.schemaengine.schemaregion.ISchemaRegionPlan; +import org.apache.iotdb.db.schemaengine.schemaregion.SchemaRegionPlanType; +import org.apache.iotdb.db.schemaengine.schemaregion.SchemaRegionPlanVisitor; +import org.apache.iotdb.mpp.rpc.thrift.TTimeSeriesInfo; + +import org.apache.thrift.protocol.TBinaryProtocol; +import org.apache.thrift.transport.TIOStreamTransport; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.List; +import java.util.Objects; + +public class CreateAliasSeriesNode extends PlanNode implements ISchemaRegionPlan { + + private final PartialPath oldPath; // Physical path + private final PartialPath newPath; // Alias path + private final TTimeSeriesInfo timeSeriesInfo; // Schema info for creating alias series + + public CreateAliasSeriesNode(PlanNodeId id, PartialPath oldPath, PartialPath newPath) { + super(id); + this.oldPath = oldPath; + this.newPath = newPath; + this.timeSeriesInfo = null; + } + + public CreateAliasSeriesNode( + PlanNodeId id, PartialPath oldPath, PartialPath newPath, TTimeSeriesInfo timeSeriesInfo) { + super(id); + this.oldPath = oldPath; + this.newPath = newPath; + this.timeSeriesInfo = timeSeriesInfo; + } + + public PartialPath getOldPath() { + return oldPath; + } + + public PartialPath getNewPath() { + return newPath; + } + + public TTimeSeriesInfo getTimeSeriesInfo() { + return timeSeriesInfo; + } + + @Override + public List getChildren() { + return null; + } + + @Override + public void addChild(PlanNode child) {} + + @Override + public PlanNode clone() { + return new CreateAliasSeriesNode(getPlanNodeId(), oldPath, newPath, timeSeriesInfo); + } + + @Override + public int allowedChildCount() { + return 0; + } + + @Override + public List getOutputColumnNames() { + return null; + } + + @Override + public PlanNodeType getType() { + return PlanNodeType.CREATE_ALIAS_SERIES; + } + + @Override + public R accept(PlanVisitor visitor, C context) { + return visitor.visitCreateAliasSeries(this, context); + } + + @Override + protected void serializeAttributes(ByteBuffer byteBuffer) { + PlanNodeType.CREATE_ALIAS_SERIES.serialize(byteBuffer); + oldPath.serialize(byteBuffer); + newPath.serialize(byteBuffer); + // Serialize timeSeriesInfo + if (timeSeriesInfo != null) { + org.apache.tsfile.utils.ReadWriteIOUtils.write((byte) 1, byteBuffer); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + try { + timeSeriesInfo.write(new TBinaryProtocol(new TIOStreamTransport(baos))); + byte[] bytes = baos.toByteArray(); + org.apache.tsfile.utils.ReadWriteIOUtils.write(bytes.length, byteBuffer); + byteBuffer.put(bytes); + } catch (Exception e) { + throw new RuntimeException("Failed to serialize TTimeSeriesInfo", e); + } + } else { + org.apache.tsfile.utils.ReadWriteIOUtils.write((byte) 0, byteBuffer); + } + } + + @Override + protected void serializeAttributes(DataOutputStream stream) throws IOException { + PlanNodeType.CREATE_ALIAS_SERIES.serialize(stream); + oldPath.serialize(stream); + newPath.serialize(stream); + // Serialize timeSeriesInfo + if (timeSeriesInfo != null) { + org.apache.tsfile.utils.ReadWriteIOUtils.write((byte) 1, stream); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + try { + timeSeriesInfo.write(new TBinaryProtocol(new TIOStreamTransport(baos))); + byte[] bytes = baos.toByteArray(); + org.apache.tsfile.utils.ReadWriteIOUtils.write(bytes.length, stream); + stream.write(bytes); + } catch (Exception e) { + throw new IOException("Failed to serialize TTimeSeriesInfo", e); + } + } else { + org.apache.tsfile.utils.ReadWriteIOUtils.write((byte) 0, stream); + } + } + + public static CreateAliasSeriesNode deserialize(ByteBuffer byteBuffer) { + PartialPath oldPath = + (PartialPath) org.apache.iotdb.commons.path.PathDeserializeUtil.deserialize(byteBuffer); + PartialPath newPath = + (PartialPath) org.apache.iotdb.commons.path.PathDeserializeUtil.deserialize(byteBuffer); + // Deserialize timeSeriesInfo + TTimeSeriesInfo timeSeriesInfo = null; + byte hasTimeSeriesInfo = org.apache.tsfile.utils.ReadWriteIOUtils.readByte(byteBuffer); + if (hasTimeSeriesInfo == 1) { + int length = org.apache.tsfile.utils.ReadWriteIOUtils.readInt(byteBuffer); + byte[] bytes = new byte[length]; + byteBuffer.get(bytes); + try { + timeSeriesInfo = new TTimeSeriesInfo(); + timeSeriesInfo.read( + new TBinaryProtocol(new TIOStreamTransport(new ByteArrayInputStream(bytes)))); + } catch (Exception e) { + throw new RuntimeException("Failed to deserialize TTimeSeriesInfo", e); + } + } + PlanNodeId planNodeId = PlanNodeId.deserialize(byteBuffer); + return new CreateAliasSeriesNode(planNodeId, oldPath, newPath, timeSeriesInfo); + } + + @Override + public SchemaRegionPlanType getPlanType() { + return SchemaRegionPlanType.CREATE_ALIAS_SERIES; + } + + @Override + public R accept(SchemaRegionPlanVisitor visitor, C context) { + return visitor.visitCreateAliasSeries(this, context); + } + + @Override + public String toString() { + return String.format( + "CreateAliasSeriesNode-%s: %s -> %s", + getPlanNodeId(), oldPath.getFullPath(), newPath.getFullPath()); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + if (!super.equals(o)) return false; + CreateAliasSeriesNode that = (CreateAliasSeriesNode) o; + return Objects.equals(oldPath, that.oldPath) + && Objects.equals(newPath, that.newPath) + && Objects.equals(timeSeriesInfo, that.timeSeriesInfo); + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), oldPath, newPath, timeSeriesInfo); + } +} diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/planner/plan/node/metadata/write/DropAliasSeriesNode.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/planner/plan/node/metadata/write/DropAliasSeriesNode.java new file mode 100644 index 000000000000..5f28a5dad8a9 --- /dev/null +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/planner/plan/node/metadata/write/DropAliasSeriesNode.java @@ -0,0 +1,131 @@ +/* + * 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.queryengine.plan.planner.plan.node.metadata.write; + +import org.apache.iotdb.commons.path.PartialPath; +import org.apache.iotdb.db.queryengine.plan.planner.plan.node.PlanNode; +import org.apache.iotdb.db.queryengine.plan.planner.plan.node.PlanNodeId; +import org.apache.iotdb.db.queryengine.plan.planner.plan.node.PlanNodeType; +import org.apache.iotdb.db.queryengine.plan.planner.plan.node.PlanVisitor; +import org.apache.iotdb.db.schemaengine.schemaregion.ISchemaRegionPlan; +import org.apache.iotdb.db.schemaengine.schemaregion.SchemaRegionPlanType; +import org.apache.iotdb.db.schemaengine.schemaregion.SchemaRegionPlanVisitor; + +import java.io.DataOutputStream; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.List; +import java.util.Objects; + +public class DropAliasSeriesNode extends PlanNode implements ISchemaRegionPlan { + + private final PartialPath aliasPath; // Alias path to drop + + public DropAliasSeriesNode(PlanNodeId id, PartialPath aliasPath) { + super(id); + this.aliasPath = aliasPath; + } + + public PartialPath getAliasPath() { + return aliasPath; + } + + @Override + public List getChildren() { + return null; + } + + @Override + public void addChild(PlanNode child) {} + + @Override + public PlanNode clone() { + return new DropAliasSeriesNode(getPlanNodeId(), aliasPath); + } + + @Override + public int allowedChildCount() { + return 0; + } + + @Override + public List getOutputColumnNames() { + return null; + } + + @Override + public PlanNodeType getType() { + return PlanNodeType.DROP_ALIAS_SERIES; + } + + @Override + public R accept(PlanVisitor visitor, C context) { + return visitor.visitDropAliasSeries(this, context); + } + + @Override + protected void serializeAttributes(ByteBuffer byteBuffer) { + PlanNodeType.DROP_ALIAS_SERIES.serialize(byteBuffer); + aliasPath.serialize(byteBuffer); + } + + @Override + protected void serializeAttributes(DataOutputStream stream) throws IOException { + PlanNodeType.DROP_ALIAS_SERIES.serialize(stream); + aliasPath.serialize(stream); + } + + public static DropAliasSeriesNode deserialize(ByteBuffer byteBuffer) { + PartialPath aliasPath = + (PartialPath) org.apache.iotdb.commons.path.PathDeserializeUtil.deserialize(byteBuffer); + PlanNodeId planNodeId = PlanNodeId.deserialize(byteBuffer); + return new DropAliasSeriesNode(planNodeId, aliasPath); + } + + @Override + public SchemaRegionPlanType getPlanType() { + return SchemaRegionPlanType.DROP_ALIAS_SERIES; + } + + @Override + public R accept(SchemaRegionPlanVisitor visitor, C context) { + return visitor.visitDropAliasSeries(this, context); + } + + @Override + public String toString() { + return String.format( + "DropAliasSeriesNode-%s: drop alias series %s", getPlanNodeId(), aliasPath.getFullPath()); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + if (!super.equals(o)) return false; + DropAliasSeriesNode that = (DropAliasSeriesNode) o; + return Objects.equals(aliasPath, that.aliasPath); + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), aliasPath); + } +} diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/planner/plan/node/metadata/write/EnablePhysicalSeriesNode.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/planner/plan/node/metadata/write/EnablePhysicalSeriesNode.java new file mode 100644 index 000000000000..28c925c91f45 --- /dev/null +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/planner/plan/node/metadata/write/EnablePhysicalSeriesNode.java @@ -0,0 +1,132 @@ +/* + * 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.queryengine.plan.planner.plan.node.metadata.write; + +import org.apache.iotdb.commons.path.PartialPath; +import org.apache.iotdb.db.queryengine.plan.planner.plan.node.PlanNode; +import org.apache.iotdb.db.queryengine.plan.planner.plan.node.PlanNodeId; +import org.apache.iotdb.db.queryengine.plan.planner.plan.node.PlanNodeType; +import org.apache.iotdb.db.queryengine.plan.planner.plan.node.PlanVisitor; +import org.apache.iotdb.db.schemaengine.schemaregion.ISchemaRegionPlan; +import org.apache.iotdb.db.schemaengine.schemaregion.SchemaRegionPlanType; +import org.apache.iotdb.db.schemaengine.schemaregion.SchemaRegionPlanVisitor; + +import java.io.DataOutputStream; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.List; +import java.util.Objects; + +public class EnablePhysicalSeriesNode extends PlanNode implements ISchemaRegionPlan { + + private final PartialPath physicalPath; // Physical path to enable + + public EnablePhysicalSeriesNode(PlanNodeId id, PartialPath physicalPath) { + super(id); + this.physicalPath = physicalPath; + } + + public PartialPath getPhysicalPath() { + return physicalPath; + } + + @Override + public List getChildren() { + return null; + } + + @Override + public void addChild(PlanNode child) {} + + @Override + public PlanNode clone() { + return new EnablePhysicalSeriesNode(getPlanNodeId(), physicalPath); + } + + @Override + public int allowedChildCount() { + return 0; + } + + @Override + public List getOutputColumnNames() { + return null; + } + + @Override + public PlanNodeType getType() { + return PlanNodeType.ENABLE_PHYSICAL_SERIES; + } + + @Override + public R accept(PlanVisitor visitor, C context) { + return visitor.visitEnablePhysicalSeries(this, context); + } + + @Override + protected void serializeAttributes(ByteBuffer byteBuffer) { + PlanNodeType.ENABLE_PHYSICAL_SERIES.serialize(byteBuffer); + physicalPath.serialize(byteBuffer); + } + + @Override + protected void serializeAttributes(DataOutputStream stream) throws IOException { + PlanNodeType.ENABLE_PHYSICAL_SERIES.serialize(stream); + physicalPath.serialize(stream); + } + + public static EnablePhysicalSeriesNode deserialize(ByteBuffer byteBuffer) { + PartialPath physicalPath = + (PartialPath) org.apache.iotdb.commons.path.PathDeserializeUtil.deserialize(byteBuffer); + PlanNodeId planNodeId = PlanNodeId.deserialize(byteBuffer); + return new EnablePhysicalSeriesNode(planNodeId, physicalPath); + } + + @Override + public SchemaRegionPlanType getPlanType() { + return SchemaRegionPlanType.ENABLE_PHYSICAL_SERIES; + } + + @Override + public R accept(SchemaRegionPlanVisitor visitor, C context) { + return visitor.visitEnablePhysicalSeries(this, context); + } + + @Override + public String toString() { + return String.format( + "EnablePhysicalSeriesNode-%s: enable physical series %s (remove DISABLED, clear ALIAS_PATH)", + getPlanNodeId(), physicalPath.getFullPath()); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + if (!super.equals(o)) return false; + EnablePhysicalSeriesNode that = (EnablePhysicalSeriesNode) o; + return Objects.equals(physicalPath, that.physicalPath); + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), physicalPath); + } +} diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/planner/plan/node/metadata/write/LockAliasNode.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/planner/plan/node/metadata/write/LockAliasNode.java new file mode 100644 index 000000000000..87159fbb2359 --- /dev/null +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/planner/plan/node/metadata/write/LockAliasNode.java @@ -0,0 +1,142 @@ +/* + * 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.queryengine.plan.planner.plan.node.metadata.write; + +import org.apache.iotdb.commons.path.PartialPath; +import org.apache.iotdb.db.queryengine.plan.planner.plan.node.PlanNode; +import org.apache.iotdb.db.queryengine.plan.planner.plan.node.PlanNodeId; +import org.apache.iotdb.db.queryengine.plan.planner.plan.node.PlanNodeType; +import org.apache.iotdb.db.queryengine.plan.planner.plan.node.PlanVisitor; +import org.apache.iotdb.db.schemaengine.schemaregion.ISchemaRegionPlan; +import org.apache.iotdb.db.schemaengine.schemaregion.SchemaRegionPlanType; +import org.apache.iotdb.db.schemaengine.schemaregion.SchemaRegionPlanVisitor; + +import java.io.DataOutputStream; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.List; +import java.util.Objects; + +public class LockAliasNode extends PlanNode implements ISchemaRegionPlan { + + private final PartialPath oldPath; + private final PartialPath newPath; + + public LockAliasNode(PlanNodeId id, PartialPath oldPath, PartialPath newPath) { + super(id); + this.oldPath = oldPath; + this.newPath = newPath; + } + + public PartialPath getOldPath() { + return oldPath; + } + + public PartialPath getNewPath() { + return newPath; + } + + @Override + public List getChildren() { + return null; + } + + @Override + public void addChild(PlanNode child) {} + + @Override + public PlanNode clone() { + return new LockAliasNode(getPlanNodeId(), oldPath, newPath); + } + + @Override + public int allowedChildCount() { + return 0; + } + + @Override + public List getOutputColumnNames() { + return null; + } + + @Override + public PlanNodeType getType() { + return PlanNodeType.LOCK_ALIAS; + } + + @Override + public R accept(PlanVisitor visitor, C context) { + return visitor.visitLockAlias(this, context); + } + + @Override + protected void serializeAttributes(ByteBuffer byteBuffer) { + PlanNodeType.LOCK_ALIAS.serialize(byteBuffer); + oldPath.serialize(byteBuffer); + newPath.serialize(byteBuffer); + } + + @Override + protected void serializeAttributes(DataOutputStream stream) throws IOException { + PlanNodeType.LOCK_ALIAS.serialize(stream); + oldPath.serialize(stream); + newPath.serialize(stream); + } + + public static LockAliasNode deserialize(ByteBuffer byteBuffer) { + PartialPath oldPath = + (PartialPath) org.apache.iotdb.commons.path.PathDeserializeUtil.deserialize(byteBuffer); + PartialPath newPath = + (PartialPath) org.apache.iotdb.commons.path.PathDeserializeUtil.deserialize(byteBuffer); + PlanNodeId planNodeId = PlanNodeId.deserialize(byteBuffer); + return new LockAliasNode(planNodeId, oldPath, newPath); + } + + @Override + public SchemaRegionPlanType getPlanType() { + return SchemaRegionPlanType.LOCK_ALIAS; + } + + @Override + public R accept(SchemaRegionPlanVisitor visitor, C context) { + return visitor.visitLockAlias(this, context); + } + + @Override + public String toString() { + return String.format( + "LockAliasNode-%s: lock alias %s -> %s", + getPlanNodeId(), oldPath.getFullPath(), newPath.getFullPath()); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + if (!super.equals(o)) return false; + LockAliasNode that = (LockAliasNode) o; + return Objects.equals(oldPath, that.oldPath) && Objects.equals(newPath, that.newPath); + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), oldPath, newPath); + } +} diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/planner/plan/node/metadata/write/MarkSeriesDisabledNode.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/planner/plan/node/metadata/write/MarkSeriesDisabledNode.java new file mode 100644 index 000000000000..7c4aa8e3ba20 --- /dev/null +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/planner/plan/node/metadata/write/MarkSeriesDisabledNode.java @@ -0,0 +1,142 @@ +/* + * 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.queryengine.plan.planner.plan.node.metadata.write; + +import org.apache.iotdb.commons.path.PartialPath; +import org.apache.iotdb.db.queryengine.plan.planner.plan.node.PlanNode; +import org.apache.iotdb.db.queryengine.plan.planner.plan.node.PlanNodeId; +import org.apache.iotdb.db.queryengine.plan.planner.plan.node.PlanNodeType; +import org.apache.iotdb.db.queryengine.plan.planner.plan.node.PlanVisitor; +import org.apache.iotdb.db.schemaengine.schemaregion.ISchemaRegionPlan; +import org.apache.iotdb.db.schemaengine.schemaregion.SchemaRegionPlanType; +import org.apache.iotdb.db.schemaengine.schemaregion.SchemaRegionPlanVisitor; + +import java.io.DataOutputStream; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.List; +import java.util.Objects; + +public class MarkSeriesDisabledNode extends PlanNode implements ISchemaRegionPlan { + + private final PartialPath oldPath; // Physical path to mark as disabled + private final PartialPath newPath; // Alias path to set in ALIAS_PATH + + public MarkSeriesDisabledNode(PlanNodeId id, PartialPath oldPath, PartialPath newPath) { + super(id); + this.oldPath = oldPath; + this.newPath = newPath; + } + + public PartialPath getOldPath() { + return oldPath; + } + + public PartialPath getNewPath() { + return newPath; + } + + @Override + public List getChildren() { + return null; + } + + @Override + public void addChild(PlanNode child) {} + + @Override + public PlanNode clone() { + return new MarkSeriesDisabledNode(getPlanNodeId(), oldPath, newPath); + } + + @Override + public int allowedChildCount() { + return 0; + } + + @Override + public List getOutputColumnNames() { + return null; + } + + @Override + public PlanNodeType getType() { + return PlanNodeType.MARK_SERIES_DISABLED; + } + + @Override + public R accept(PlanVisitor visitor, C context) { + return visitor.visitMarkSeriesDisabled(this, context); + } + + @Override + protected void serializeAttributes(ByteBuffer byteBuffer) { + PlanNodeType.MARK_SERIES_DISABLED.serialize(byteBuffer); + oldPath.serialize(byteBuffer); + newPath.serialize(byteBuffer); + } + + @Override + protected void serializeAttributes(DataOutputStream stream) throws IOException { + PlanNodeType.MARK_SERIES_DISABLED.serialize(stream); + oldPath.serialize(stream); + newPath.serialize(stream); + } + + public static MarkSeriesDisabledNode deserialize(ByteBuffer byteBuffer) { + PartialPath oldPath = + (PartialPath) org.apache.iotdb.commons.path.PathDeserializeUtil.deserialize(byteBuffer); + PartialPath newPath = + (PartialPath) org.apache.iotdb.commons.path.PathDeserializeUtil.deserialize(byteBuffer); + PlanNodeId planNodeId = PlanNodeId.deserialize(byteBuffer); + return new MarkSeriesDisabledNode(planNodeId, oldPath, newPath); + } + + @Override + public SchemaRegionPlanType getPlanType() { + return SchemaRegionPlanType.MARK_SERIES_DISABLED; + } + + @Override + public R accept(SchemaRegionPlanVisitor visitor, C context) { + return visitor.visitMarkSeriesDisabled(this, context); + } + + @Override + public String toString() { + return String.format( + "MarkSeriesDisabledNode-%s: mark %s as disabled, set ALIAS_PATH to %s", + getPlanNodeId(), oldPath.getFullPath(), newPath.getFullPath()); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + if (!super.equals(o)) return false; + MarkSeriesDisabledNode that = (MarkSeriesDisabledNode) o; + return Objects.equals(oldPath, that.oldPath) && Objects.equals(newPath, that.newPath); + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), oldPath, newPath); + } +} diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/planner/plan/node/metadata/write/UnlockForAliasNode.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/planner/plan/node/metadata/write/UnlockForAliasNode.java new file mode 100644 index 000000000000..384192243da2 --- /dev/null +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/planner/plan/node/metadata/write/UnlockForAliasNode.java @@ -0,0 +1,142 @@ +/* + * 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.queryengine.plan.planner.plan.node.metadata.write; + +import org.apache.iotdb.commons.path.PartialPath; +import org.apache.iotdb.db.queryengine.plan.planner.plan.node.PlanNode; +import org.apache.iotdb.db.queryengine.plan.planner.plan.node.PlanNodeId; +import org.apache.iotdb.db.queryengine.plan.planner.plan.node.PlanNodeType; +import org.apache.iotdb.db.queryengine.plan.planner.plan.node.PlanVisitor; +import org.apache.iotdb.db.schemaengine.schemaregion.ISchemaRegionPlan; +import org.apache.iotdb.db.schemaengine.schemaregion.SchemaRegionPlanType; +import org.apache.iotdb.db.schemaengine.schemaregion.SchemaRegionPlanVisitor; + +import java.io.DataOutputStream; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.List; +import java.util.Objects; + +public class UnlockForAliasNode extends PlanNode implements ISchemaRegionPlan { + + private final PartialPath oldPath; + private final PartialPath newPath; + + public UnlockForAliasNode(PlanNodeId id, PartialPath oldPath, PartialPath newPath) { + super(id); + this.oldPath = oldPath; + this.newPath = newPath; + } + + public PartialPath getOldPath() { + return oldPath; + } + + public PartialPath getNewPath() { + return newPath; + } + + @Override + public List getChildren() { + return null; + } + + @Override + public void addChild(PlanNode child) {} + + @Override + public PlanNode clone() { + return new UnlockForAliasNode(getPlanNodeId(), oldPath, newPath); + } + + @Override + public int allowedChildCount() { + return 0; + } + + @Override + public List getOutputColumnNames() { + return null; + } + + @Override + public PlanNodeType getType() { + return PlanNodeType.UNLOCK_FOR_ALIAS; + } + + @Override + public R accept(PlanVisitor visitor, C context) { + return visitor.visitUnlockForAlias(this, context); + } + + @Override + protected void serializeAttributes(ByteBuffer byteBuffer) { + PlanNodeType.UNLOCK_FOR_ALIAS.serialize(byteBuffer); + oldPath.serialize(byteBuffer); + newPath.serialize(byteBuffer); + } + + @Override + protected void serializeAttributes(DataOutputStream stream) throws IOException { + PlanNodeType.UNLOCK_FOR_ALIAS.serialize(stream); + oldPath.serialize(stream); + newPath.serialize(stream); + } + + public static UnlockForAliasNode deserialize(ByteBuffer byteBuffer) { + PartialPath oldPath = + (PartialPath) org.apache.iotdb.commons.path.PathDeserializeUtil.deserialize(byteBuffer); + PartialPath newPath = + (PartialPath) org.apache.iotdb.commons.path.PathDeserializeUtil.deserialize(byteBuffer); + PlanNodeId planNodeId = PlanNodeId.deserialize(byteBuffer); + return new UnlockForAliasNode(planNodeId, oldPath, newPath); + } + + @Override + public SchemaRegionPlanType getPlanType() { + return SchemaRegionPlanType.UNLOCK_FOR_ALIAS; + } + + @Override + public R accept(SchemaRegionPlanVisitor visitor, C context) { + return visitor.visitUnlockForAlias(this, context); + } + + @Override + public String toString() { + return String.format( + "UnlockForAliasNode-%s: unlock for alias %s -> %s", + getPlanNodeId(), oldPath.getFullPath(), newPath.getFullPath()); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + if (!super.equals(o)) return false; + UnlockForAliasNode that = (UnlockForAliasNode) o; + return Objects.equals(oldPath, that.oldPath) && Objects.equals(newPath, that.newPath); + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), oldPath, newPath); + } +} diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/planner/plan/node/metadata/write/UpdatePhysicalAliasRefNode.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/planner/plan/node/metadata/write/UpdatePhysicalAliasRefNode.java new file mode 100644 index 000000000000..d082fe0d9929 --- /dev/null +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/planner/plan/node/metadata/write/UpdatePhysicalAliasRefNode.java @@ -0,0 +1,144 @@ +/* + * 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.queryengine.plan.planner.plan.node.metadata.write; + +import org.apache.iotdb.commons.path.PartialPath; +import org.apache.iotdb.db.queryengine.plan.planner.plan.node.PlanNode; +import org.apache.iotdb.db.queryengine.plan.planner.plan.node.PlanNodeId; +import org.apache.iotdb.db.queryengine.plan.planner.plan.node.PlanNodeType; +import org.apache.iotdb.db.queryengine.plan.planner.plan.node.PlanVisitor; +import org.apache.iotdb.db.schemaengine.schemaregion.ISchemaRegionPlan; +import org.apache.iotdb.db.schemaengine.schemaregion.SchemaRegionPlanType; +import org.apache.iotdb.db.schemaengine.schemaregion.SchemaRegionPlanVisitor; + +import java.io.DataOutputStream; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.List; +import java.util.Objects; + +public class UpdatePhysicalAliasRefNode extends PlanNode implements ISchemaRegionPlan { + + private final PartialPath physicalPath; // Physical path to update + private final PartialPath newAliasPath; // New alias path to set in ALIAS_PATH + + public UpdatePhysicalAliasRefNode( + PlanNodeId id, PartialPath physicalPath, PartialPath newAliasPath) { + super(id); + this.physicalPath = physicalPath; + this.newAliasPath = newAliasPath; + } + + public PartialPath getPhysicalPath() { + return physicalPath; + } + + public PartialPath getNewAliasPath() { + return newAliasPath; + } + + @Override + public List getChildren() { + return null; + } + + @Override + public void addChild(PlanNode child) {} + + @Override + public PlanNode clone() { + return new UpdatePhysicalAliasRefNode(getPlanNodeId(), physicalPath, newAliasPath); + } + + @Override + public int allowedChildCount() { + return 0; + } + + @Override + public List getOutputColumnNames() { + return null; + } + + @Override + public PlanNodeType getType() { + return PlanNodeType.UPDATE_PHYSICAL_ALIAS_REF; + } + + @Override + public R accept(PlanVisitor visitor, C context) { + return visitor.visitUpdatePhysicalAliasRef(this, context); + } + + @Override + protected void serializeAttributes(ByteBuffer byteBuffer) { + PlanNodeType.UPDATE_PHYSICAL_ALIAS_REF.serialize(byteBuffer); + physicalPath.serialize(byteBuffer); + newAliasPath.serialize(byteBuffer); + } + + @Override + protected void serializeAttributes(DataOutputStream stream) throws IOException { + PlanNodeType.UPDATE_PHYSICAL_ALIAS_REF.serialize(stream); + physicalPath.serialize(stream); + newAliasPath.serialize(stream); + } + + public static UpdatePhysicalAliasRefNode deserialize(ByteBuffer byteBuffer) { + PartialPath physicalPath = + (PartialPath) org.apache.iotdb.commons.path.PathDeserializeUtil.deserialize(byteBuffer); + PartialPath newAliasPath = + (PartialPath) org.apache.iotdb.commons.path.PathDeserializeUtil.deserialize(byteBuffer); + PlanNodeId planNodeId = PlanNodeId.deserialize(byteBuffer); + return new UpdatePhysicalAliasRefNode(planNodeId, physicalPath, newAliasPath); + } + + @Override + public SchemaRegionPlanType getPlanType() { + return SchemaRegionPlanType.UPDATE_PHYSICAL_ALIAS_REF; + } + + @Override + public R accept(SchemaRegionPlanVisitor visitor, C context) { + return visitor.visitUpdatePhysicalAliasRef(this, context); + } + + @Override + public String toString() { + return String.format( + "UpdatePhysicalAliasRefNode-%s: update %s ALIAS_PATH to %s", + getPlanNodeId(), physicalPath.getFullPath(), newAliasPath.getFullPath()); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + if (!super.equals(o)) return false; + UpdatePhysicalAliasRefNode that = (UpdatePhysicalAliasRefNode) o; + return Objects.equals(physicalPath, that.physicalPath) + && Objects.equals(newAliasPath, that.newAliasPath); + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), physicalPath, newAliasPath); + } +} diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/security/TreeAccessCheckVisitor.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/security/TreeAccessCheckVisitor.java index 86f4dce2b84a..6d52e2dcf4a8 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/security/TreeAccessCheckVisitor.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/security/TreeAccessCheckVisitor.java @@ -45,6 +45,7 @@ import org.apache.iotdb.db.queryengine.plan.statement.internal.InternalBatchActivateTemplateStatement; import org.apache.iotdb.db.queryengine.plan.statement.internal.InternalCreateMultiTimeSeriesStatement; import org.apache.iotdb.db.queryengine.plan.statement.internal.InternalCreateTimeSeriesStatement; +import org.apache.iotdb.db.queryengine.plan.statement.metadata.AliasTimeSeriesStatement; import org.apache.iotdb.db.queryengine.plan.statement.metadata.AlterEncodingCompressorStatement; import org.apache.iotdb.db.queryengine.plan.statement.metadata.AlterTimeSeriesStatement; import org.apache.iotdb.db.queryengine.plan.statement.metadata.CountDatabaseStatement; @@ -1421,6 +1422,52 @@ public TSStatus visitAlterTimeSeries( return checkTimeSeriesPermission(context, statement::getPaths, PrivilegeType.WRITE_SCHEMA); } + @Override + public TSStatus visitAliasTimeSeriesStatement( + AliasTimeSeriesStatement statement, TreeAccessCheckContext context) { + context.setAuditLogOperation(AuditLogOperation.DDL); + + // audit db is read-only - check old path + if (includeByAuditTreeDB(statement.getPath()) + && !context.getUsername().equals(AuthorityChecker.INTERNAL_AUDIT_USER)) { + recordObjectAuthenticationAuditLog( + context.setResult(false), + () -> + Arrays.asList(statement.getPath(), statement.getNewPath()).stream() + .distinct() + .collect(Collectors.toList()) + .toString()); + return new TSStatus(TSStatusCode.NO_PERMISSION.getStatusCode()) + .setMessage(String.format(READ_ONLY_DB_ERROR_MSG, TREE_MODEL_AUDIT_DATABASE)); + } + + // audit db is read-only - check new path + if (includeByAuditTreeDB(statement.getNewPath()) + && !context.getUsername().equals(AuthorityChecker.INTERNAL_AUDIT_USER)) { + recordObjectAuthenticationAuditLog( + context.setResult(false), + () -> + Arrays.asList(statement.getPath(), statement.getNewPath()).stream() + .distinct() + .collect(Collectors.toList()) + .toString()); + return new TSStatus(TSStatusCode.NO_PERMISSION.getStatusCode()) + .setMessage(String.format(READ_ONLY_DB_ERROR_MSG, TREE_MODEL_AUDIT_DATABASE)); + } + + // Check permission for old path (WRITE_SCHEMA) + TSStatus oldPathStatus = + checkTimeSeriesPermission( + context, () -> Arrays.asList(statement.getPath()), PrivilegeType.WRITE_SCHEMA); + if (oldPathStatus.getCode() != TSStatusCode.SUCCESS_STATUS.getStatusCode()) { + return oldPathStatus; + } + + // Check permission for new path (WRITE_SCHEMA) + return checkTimeSeriesPermission( + context, () -> Arrays.asList(statement.getNewPath()), PrivilegeType.WRITE_SCHEMA); + } + @Override public TSStatus visitAlterEncodingCompressor( final AlterEncodingCompressorStatement alterEncodingCompressorStatement, diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/statement/StatementVisitor.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/statement/StatementVisitor.java index eb38d9ba3902..4a827524b111 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/statement/StatementVisitor.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/statement/StatementVisitor.java @@ -34,6 +34,7 @@ import org.apache.iotdb.db.queryengine.plan.statement.internal.InternalCreateMultiTimeSeriesStatement; import org.apache.iotdb.db.queryengine.plan.statement.internal.InternalCreateTimeSeriesStatement; import org.apache.iotdb.db.queryengine.plan.statement.internal.SeriesSchemaFetchStatement; +import org.apache.iotdb.db.queryengine.plan.statement.metadata.AliasTimeSeriesStatement; import org.apache.iotdb.db.queryengine.plan.statement.metadata.AlterEncodingCompressorStatement; import org.apache.iotdb.db.queryengine.plan.statement.metadata.AlterTimeSeriesStatement; import org.apache.iotdb.db.queryengine.plan.statement.metadata.CountDatabaseStatement; @@ -198,6 +199,11 @@ public R visitAlterTimeSeries(AlterTimeSeriesStatement alterTimeSeriesStatement, return visitStatement(alterTimeSeriesStatement, context); } + public R visitAliasTimeSeriesStatement( + AliasTimeSeriesStatement aliasTimeSeriesStatement, C context) { + return visitStatement(aliasTimeSeriesStatement, context); + } + public R visitAlterEncodingCompressor( AlterEncodingCompressorStatement alterEncodingCompressorStatement, C context) { return visitStatement(alterEncodingCompressorStatement, context); diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/statement/metadata/AliasTimeSeriesStatement.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/statement/metadata/AliasTimeSeriesStatement.java new file mode 100644 index 000000000000..9ae918fc2da5 --- /dev/null +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/statement/metadata/AliasTimeSeriesStatement.java @@ -0,0 +1,119 @@ +/* + * 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.queryengine.plan.statement.metadata; + +import org.apache.iotdb.commons.path.MeasurementPath; +import org.apache.iotdb.commons.path.PartialPath; +import org.apache.iotdb.db.queryengine.plan.analyze.QueryType; +import org.apache.iotdb.db.queryengine.plan.statement.IConfigStatement; +import org.apache.iotdb.db.queryengine.plan.statement.StatementType; +import org.apache.iotdb.db.queryengine.plan.statement.StatementVisitor; + +import java.util.Arrays; +import java.util.List; +import java.util.Objects; + +/** + * ALIAS TIMESERIES statement for renaming timeseries path (alias series). + * + *

This statement is used to rename a timeseries path, which will create an alias series pointing + * to the original physical series. The syntax is: + * + *

ALTER TIMESERIES RENAME TO + */ +public class AliasTimeSeriesStatement extends AlterTimeSeriesStatement implements IConfigStatement { + + /** Used when renaming the timeseries path itself (RENAME_TO). */ + private PartialPath newPath; + + private boolean isGeneratedByPipe = false; + + public AliasTimeSeriesStatement() { + super(); + this.statementType = StatementType.ALTER_TIME_SERIES; + // Set alterType to RENAME_TO for alias series renaming + setAlterType(AlterType.RENAME_TO); + } + + public AliasTimeSeriesStatement(MeasurementPath oldPath, PartialPath newPath) { + this(); + setPath(oldPath); + this.newPath = newPath; + } + + public PartialPath getNewPath() { + return newPath; + } + + public void setNewPath(PartialPath newPath) { + this.newPath = newPath; + } + + @Override + public List getPaths() { + PartialPath oldPath = getPath(); + PartialPath newPath = getNewPath(); + if (oldPath != null && newPath != null) { + return Arrays.asList(oldPath, newPath); + } else if (oldPath != null) { + return Arrays.asList(oldPath); + } else { + return super.getPaths(); + } + } + + @Override + public R accept(StatementVisitor visitor, C context) { + return visitor.visitAliasTimeSeriesStatement(this, context); + } + + @Override + public QueryType getQueryType() { + return QueryType.WRITE; + } + + public void markIsGeneratedByPipe() { + isGeneratedByPipe = true; + } + + public boolean isGeneratedByPipe() { + return isGeneratedByPipe; + } + + @Override + public boolean equals(final Object obj) { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } + if (!super.equals(obj)) { + return false; + } + final AliasTimeSeriesStatement that = (AliasTimeSeriesStatement) obj; + return Objects.equals(this.newPath, that.newPath); + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), newPath); + } +} diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/statement/metadata/AlterTimeSeriesStatement.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/statement/metadata/AlterTimeSeriesStatement.java index 5913fb9c2fd9..9acb9b119c0f 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/statement/metadata/AlterTimeSeriesStatement.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/statement/metadata/AlterTimeSeriesStatement.java @@ -159,7 +159,8 @@ public int hashCode() { } public enum AlterType { - RENAME, + RENAME, // Rename tag/attribute key + RENAME_TO, // Rename the timeseries path itself SET, DROP, ADD_TAGS, diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/schemaengine/schemaregion/ISchemaRegion.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/schemaengine/schemaregion/ISchemaRegion.java index c087d638d4a2..1728cad97bdc 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/schemaengine/schemaregion/ISchemaRegion.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/schemaengine/schemaregion/ISchemaRegion.java @@ -28,6 +28,13 @@ import org.apache.iotdb.db.exception.metadata.SchemaQuotaExceededException; import org.apache.iotdb.db.queryengine.common.schematree.ClusterSchemaTree; import org.apache.iotdb.db.queryengine.plan.planner.plan.node.metadata.write.AlterEncodingCompressorNode; +import org.apache.iotdb.db.queryengine.plan.planner.plan.node.metadata.write.CreateAliasSeriesNode; +import org.apache.iotdb.db.queryengine.plan.planner.plan.node.metadata.write.DropAliasSeriesNode; +import org.apache.iotdb.db.queryengine.plan.planner.plan.node.metadata.write.EnablePhysicalSeriesNode; +import org.apache.iotdb.db.queryengine.plan.planner.plan.node.metadata.write.LockAliasNode; +import org.apache.iotdb.db.queryengine.plan.planner.plan.node.metadata.write.MarkSeriesDisabledNode; +import org.apache.iotdb.db.queryengine.plan.planner.plan.node.metadata.write.UnlockForAliasNode; +import org.apache.iotdb.db.queryengine.plan.planner.plan.node.metadata.write.UpdatePhysicalAliasRefNode; import org.apache.iotdb.db.queryengine.plan.relational.metadata.fetcher.cache.TableId; import org.apache.iotdb.db.queryengine.plan.relational.planner.node.schema.ConstructTableDevicesBlackListNode; import org.apache.iotdb.db.queryengine.plan.relational.planner.node.schema.CreateOrUpdateTableDeviceNode; @@ -214,6 +221,21 @@ Pair constructSchemaBlackList(final PathPatternTree patternTree) void alterEncodingCompressor(final AlterEncodingCompressorNode node) throws MetadataException; + // Alias Series Operations + void lockForAlias(final LockAliasNode node) throws MetadataException; + + void createAliasSeries(final CreateAliasSeriesNode node) throws MetadataException; + + void markSeriesDisabled(final MarkSeriesDisabledNode node) throws MetadataException; + + void updatePhysicalAliasRef(final UpdatePhysicalAliasRefNode node) throws MetadataException; + + void dropAliasSeries(final DropAliasSeriesNode node) throws MetadataException; + + void enablePhysicalSeries(final EnablePhysicalSeriesNode node) throws MetadataException; + + void unlockForAlias(final UnlockForAliasNode node) throws MetadataException; + // endregion // region Interfaces for Logical View diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/schemaengine/schemaregion/SchemaRegionPlanType.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/schemaengine/schemaregion/SchemaRegionPlanType.java index b0d41c725eeb..4df639a2fc24 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/schemaengine/schemaregion/SchemaRegionPlanType.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/schemaengine/schemaregion/SchemaRegionPlanType.java @@ -40,6 +40,13 @@ public enum SchemaRegionPlanType { PRE_DELETE_TIMESERIES_IN_CLUSTER((byte) 64), ROLLBACK_PRE_DELETE_TIMESERIES((byte) 65), ALTER_ENCODING_COMPRESSOR((byte) 66), + LOCK_ALIAS((byte) 71), + CREATE_ALIAS_SERIES((byte) 72), + MARK_SERIES_DISABLED((byte) 73), + UPDATE_PHYSICAL_ALIAS_REF((byte) 74), + DROP_ALIAS_SERIES((byte) 75), + ENABLE_PHYSICAL_SERIES((byte) 76), + UNLOCK_FOR_ALIAS((byte) 77), // endregion diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/schemaengine/schemaregion/SchemaRegionPlanVisitor.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/schemaengine/schemaregion/SchemaRegionPlanVisitor.java index 0cf087a40e11..36ff42d5ff17 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/schemaengine/schemaregion/SchemaRegionPlanVisitor.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/schemaengine/schemaregion/SchemaRegionPlanVisitor.java @@ -20,6 +20,13 @@ package org.apache.iotdb.db.schemaengine.schemaregion; import org.apache.iotdb.db.queryengine.plan.planner.plan.node.metadata.write.AlterEncodingCompressorNode; +import org.apache.iotdb.db.queryengine.plan.planner.plan.node.metadata.write.CreateAliasSeriesNode; +import org.apache.iotdb.db.queryengine.plan.planner.plan.node.metadata.write.DropAliasSeriesNode; +import org.apache.iotdb.db.queryengine.plan.planner.plan.node.metadata.write.EnablePhysicalSeriesNode; +import org.apache.iotdb.db.queryengine.plan.planner.plan.node.metadata.write.LockAliasNode; +import org.apache.iotdb.db.queryengine.plan.planner.plan.node.metadata.write.MarkSeriesDisabledNode; +import org.apache.iotdb.db.queryengine.plan.planner.plan.node.metadata.write.UnlockForAliasNode; +import org.apache.iotdb.db.queryengine.plan.planner.plan.node.metadata.write.UpdatePhysicalAliasRefNode; import org.apache.iotdb.db.queryengine.plan.relational.planner.node.schema.ConstructTableDevicesBlackListNode; import org.apache.iotdb.db.queryengine.plan.relational.planner.node.schema.CreateOrUpdateTableDeviceNode; import org.apache.iotdb.db.queryengine.plan.relational.planner.node.schema.DeleteTableDeviceNode; @@ -185,4 +192,36 @@ public R visitAlterEncodingCompressor( final AlterEncodingCompressorNode alterEncodingCompressorNode, final C context) { return visitSchemaRegionPlan(alterEncodingCompressorNode, context); } + + public R visitCreateAliasSeries( + final CreateAliasSeriesNode createAliasSeriesNode, final C context) { + return visitSchemaRegionPlan(createAliasSeriesNode, context); + } + + public R visitMarkSeriesDisabled( + final MarkSeriesDisabledNode markSeriesDisabledNode, final C context) { + return visitSchemaRegionPlan(markSeriesDisabledNode, context); + } + + public R visitUpdatePhysicalAliasRef( + final UpdatePhysicalAliasRefNode updatePhysicalAliasRefNode, final C context) { + return visitSchemaRegionPlan(updatePhysicalAliasRefNode, context); + } + + public R visitDropAliasSeries(final DropAliasSeriesNode dropAliasSeriesNode, final C context) { + return visitSchemaRegionPlan(dropAliasSeriesNode, context); + } + + public R visitEnablePhysicalSeries( + final EnablePhysicalSeriesNode enablePhysicalSeriesNode, final C context) { + return visitSchemaRegionPlan(enablePhysicalSeriesNode, context); + } + + public R visitUnlockForAlias(final UnlockForAliasNode unlockForAliasNode, final C context) { + return visitSchemaRegionPlan(unlockForAliasNode, context); + } + + public R visitLockAlias(final LockAliasNode lockAliasNode, final C context) { + return visitSchemaRegionPlan(lockAliasNode, context); + } } diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/schemaengine/schemaregion/impl/SchemaRegionMemoryImpl.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/schemaengine/schemaregion/impl/SchemaRegionMemoryImpl.java index 5f6486f8be4f..bf4c3c20c640 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/schemaengine/schemaregion/impl/SchemaRegionMemoryImpl.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/schemaengine/schemaregion/impl/SchemaRegionMemoryImpl.java @@ -38,6 +38,8 @@ import org.apache.iotdb.consensus.ConsensusFactory; import org.apache.iotdb.db.conf.IoTDBConfig; import org.apache.iotdb.db.conf.IoTDBDescriptor; +import org.apache.iotdb.db.exception.metadata.MeasurementInBlackListException; +import org.apache.iotdb.db.exception.metadata.PathAlreadyExistException; import org.apache.iotdb.db.exception.metadata.PathNotExistException; import org.apache.iotdb.db.exception.metadata.SchemaDirCreationFailureException; import org.apache.iotdb.db.exception.metadata.SchemaQuotaExceededException; @@ -51,7 +53,14 @@ import org.apache.iotdb.db.queryengine.plan.analyze.TypeProvider; import org.apache.iotdb.db.queryengine.plan.planner.LocalExecutionPlanner; import org.apache.iotdb.db.queryengine.plan.planner.plan.node.metadata.write.AlterEncodingCompressorNode; +import org.apache.iotdb.db.queryengine.plan.planner.plan.node.metadata.write.CreateAliasSeriesNode; import org.apache.iotdb.db.queryengine.plan.planner.plan.node.metadata.write.CreateTimeSeriesNode; +import org.apache.iotdb.db.queryengine.plan.planner.plan.node.metadata.write.DropAliasSeriesNode; +import org.apache.iotdb.db.queryengine.plan.planner.plan.node.metadata.write.EnablePhysicalSeriesNode; +import org.apache.iotdb.db.queryengine.plan.planner.plan.node.metadata.write.LockAliasNode; +import org.apache.iotdb.db.queryengine.plan.planner.plan.node.metadata.write.MarkSeriesDisabledNode; +import org.apache.iotdb.db.queryengine.plan.planner.plan.node.metadata.write.UnlockForAliasNode; +import org.apache.iotdb.db.queryengine.plan.planner.plan.node.metadata.write.UpdatePhysicalAliasRefNode; import org.apache.iotdb.db.queryengine.plan.planner.plan.parameter.InputLocation; import org.apache.iotdb.db.queryengine.plan.relational.metadata.Metadata; import org.apache.iotdb.db.queryengine.plan.relational.metadata.fetcher.cache.TableDeviceSchemaCache; @@ -93,6 +102,7 @@ import org.apache.iotdb.db.schemaengine.schemaregion.logfile.visitor.SchemaRegionPlanSerializer; import org.apache.iotdb.db.schemaengine.schemaregion.mtree.impl.mem.MTreeBelowSGMemoryImpl; import org.apache.iotdb.db.schemaengine.schemaregion.mtree.impl.mem.mnode.IMemMNode; +import org.apache.iotdb.db.schemaengine.schemaregion.mtree.impl.mem.mnode.info.MeasurementInfo; import org.apache.iotdb.db.schemaengine.schemaregion.mtree.impl.mem.mnode.info.TableDeviceInfo; import org.apache.iotdb.db.schemaengine.schemaregion.read.req.IShowDevicesPlan; import org.apache.iotdb.db.schemaengine.schemaregion.read.req.IShowNodesPlan; @@ -152,6 +162,7 @@ import java.util.Map; import java.util.Objects; import java.util.Set; +import java.util.TreeMap; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import java.util.stream.Collectors; @@ -983,6 +994,268 @@ public void alterEncodingCompressor(final AlterEncodingCompressorNode node) writeToMLog(node); } + @Override + public void lockForAlias(final LockAliasNode node) throws MetadataException { + final PartialPath oldPath = node.getOldPath(); + final PartialPath newPath = node.getNewPath(); + + // Get oldPath node and validate it exists + final IMeasurementMNode oldPathNode = mTree.getMeasurementMNode(oldPath); + + // Check if oldPath is already being renamed (prevent concurrent rename operations) + if (oldPathNode.isRenaming()) { + throw new MetadataException( + String.format( + "Timeseries [%s] is already being renamed, cannot perform another rename operation", + oldPath.getFullPath())); + } + + // Check if oldPath is pre-deleted (in deletion blacklist) + if (oldPathNode.isPreDeleted()) { + throw new MeasurementInBlackListException(oldPath); + } + + // Check if oldPath is a logical view (logical views may not support alias series) + // Note: This check depends on the design decision. If logical views can have alias series, + // remove this check. Otherwise, keep it. + if (oldPathNode.isLogicalView()) { + throw new MetadataException( + String.format("Cannot create alias series for logical view [%s]", oldPath.getFullPath())); + } + + // Check if oldPath is disabled (disabled series cannot be renamed/aliased) + if (oldPathNode.isDisabled()) { + throw new MetadataException( + String.format( + "Cannot create alias series for disabled timeseries [%s]", oldPath.getFullPath())); + } + + // Mark oldPath as isRenaming to prevent concurrent operations + oldPathNode.setIsRenaming(true); + + // Write log + writeToMLog(node); + } + + @Override + public void createAliasSeries(final CreateAliasSeriesNode node) throws MetadataException { + if (!regionStatistics.isAllowToCreateNewSeries()) { + throw new SeriesOverflowException( + regionStatistics.getGlobalMemoryUsage(), regionStatistics.getGlobalSeriesNumber()); + } + + final PartialPath oldPath = node.getOldPath(); // Physical path to be aliased + final PartialPath newPath = node.getNewPath(); // New alias path + final org.apache.iotdb.mpp.rpc.thrift.TTimeSeriesInfo timeSeriesInfo = node.getTimeSeriesInfo(); + + if (timeSeriesInfo == null) { + throw new MetadataException("TTimeSeriesInfo is required for createAliasSeries but is null"); + } + + // Check if newPath already exists + try { + mTree.getMeasurementMNode(newPath); + throw new PathAlreadyExistException(newPath.getFullPath()); + } catch (PathNotExistException e) { + // Path doesn't exist, which is expected + } + + // Extract schema information from TTimeSeriesInfo + final TSDataType dataType = TSDataType.values()[timeSeriesInfo.getDataType()]; + final TSEncoding encoding = TSEncoding.values()[timeSeriesInfo.getEncoding()]; + final CompressionType compressor = CompressionType.values()[timeSeriesInfo.getCompressor()]; + final String alias = timeSeriesInfo.getMeasurementAlias(); + + // Build props map with alias series properties + Map props = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); + // Copy original props if any + if (timeSeriesInfo.getProps() != null && !timeSeriesInfo.getProps().isEmpty()) { + props.putAll(timeSeriesInfo.getProps()); + } + // Add alias series properties + props.put(MeasurementInfo.IS_RENAMED_KEY, "true"); + props.put(MeasurementInfo.ORIGINAL_PATH_KEY, oldPath.getFullPath()); + + try { + // Check data type and encoding compatibility + SchemaUtils.checkDataTypeWithEncoding(dataType, encoding); + + // Create alias measurement node at newPath using mTree.createTimeSeries + final IMeasurementMNode aliasMNode = + mTree.createTimeSeries( + newPath, dataType, encoding, compressor, props, alias, false, null); + + // Should not merge (aliasMNode should not be null) + if (Objects.isNull(aliasMNode)) { + throw new MetadataException( + "Failed to create alias series at " + newPath.getFullPath() + ": merge detected"); + } + + // Update statistics + regionStatistics.addMeasurement(1L); + + // Update tag index + // Note: For alias series creation during recovery, tags/attributes should be handled via + // offset recovery if available. Since alias series is created via Procedure, recovery + // scenarios may be limited, but we follow the same pattern as createTimeSeries for + // consistency. + long offset = -1; + if (timeSeriesInfo.getTags() != null && !timeSeriesInfo.getTags().isEmpty()) { + // Only add index if not recovering (during recovery, tags are recovered from tag file) + if (!isRecovering) { + tagManager.addIndex(timeSeriesInfo.getTags(), aliasMNode); + } + } + + // Write log + if (!isRecovering) { + // Write tags and attributes to tag file if needed + if ((timeSeriesInfo.getTags() != null && !timeSeriesInfo.getTags().isEmpty()) + || (timeSeriesInfo.getAttributes() != null + && !timeSeriesInfo.getAttributes().isEmpty())) { + offset = + tagManager.writeTagFile(timeSeriesInfo.getTags(), timeSeriesInfo.getAttributes()); + } + writeToMLog(node); + } + if (offset != -1) { + aliasMNode.setOffset(offset); + } + + // Note: Tags and attributes are already handled above via tagManager + // The alias node points to the same physical data as oldPath logically + // (handled at query/write time through ORIGINAL_PATH property) + + } catch (IOException e) { + throw new MetadataException(e); + } + } + + @Override + public void markSeriesDisabled(final MarkSeriesDisabledNode node) throws MetadataException { + final PartialPath oldPath = node.getOldPath(); + final PartialPath newPath = node.getNewPath(); + + // Get the physical measurement node (verify it exists) + final IMeasurementMNode physicalNode = mTree.getMeasurementMNode(oldPath); + + // Validate that the physical node is not a logical view + if (physicalNode.isLogicalView()) { + throw new MetadataException( + String.format("Cannot mark logical view [%s] as disabled", oldPath.getFullPath())); + } + + // Mark physical node as disabled (DISABLED=true) + physicalNode.setDisabled(true); + + // Set ALIAS_PATH to point to the alias series (newPath) + physicalNode.setAliasPath(newPath); + + // Write log + writeToMLog(node); + } + + @Override + public void updatePhysicalAliasRef(final UpdatePhysicalAliasRefNode node) + throws MetadataException { + final PartialPath physicalPath = node.getPhysicalPath(); + final PartialPath newAliasPath = node.getNewAliasPath(); + + // Get the physical measurement node (verify it exists) + final IMeasurementMNode physicalNode = mTree.getMeasurementMNode(physicalPath); + + // Validate that the physical node is not a logical view + if (physicalNode.isLogicalView()) { + throw new MetadataException( + String.format( + "Cannot update alias reference for logical view [%s]", physicalPath.getFullPath())); + } + + // Update ALIAS_PATH to newAliasPath in the physical node + physicalNode.setAliasPath(newAliasPath); + + // Write log + writeToMLog(node); + } + + @Override + public void dropAliasSeries(final DropAliasSeriesNode node) throws MetadataException { + final PartialPath aliasPath = node.getAliasPath(); + + // Get the alias measurement node (verify it exists) + final IMeasurementMNode aliasNode = mTree.getMeasurementMNode(aliasPath); + + // Verify it's an alias series (IS_RENAMED=true) + if (!aliasNode.isRenamed()) { + throw new MetadataException( + String.format( + "Path [%s] is not an alias series (IS_RENAMED=false), cannot drop", + aliasPath.getFullPath())); + } + + // Check if it's a logical view (logical views cannot be alias series, but double check) + if (aliasNode.isLogicalView()) { + throw new MetadataException( + String.format("Cannot drop logical view [%s] as alias series", aliasPath.getFullPath())); + } + + // Physically delete the alias node + // Note: deleteTimeSeries will handle statistics update internally + mTree.deleteTimeSeries(aliasPath); + + // Write log + writeToMLog(node); + } + + @Override + public void enablePhysicalSeries(final EnablePhysicalSeriesNode node) throws MetadataException { + final PartialPath physicalPath = node.getPhysicalPath(); + + // Get the physical measurement node (verify it exists) + final IMeasurementMNode physicalNode = mTree.getMeasurementMNode(physicalPath); + + // Validate that the physical node is not a logical view + if (physicalNode.isLogicalView()) { + throw new MetadataException( + String.format("Cannot enable logical view [%s]", physicalPath.getFullPath())); + } + + // Remove DISABLED flag (set DISABLED=false) + physicalNode.setDisabled(false); + + // Clear ALIAS_PATH reference pointer (set to null) + physicalNode.setAliasPath(null); + + // Write log + writeToMLog(node); + } + + @Override + public void unlockForAlias(final UnlockForAliasNode node) throws MetadataException { + final PartialPath oldPath = node.getOldPath(); + + // Get the oldPath node (verify it exists) + // Note: The node might have been deleted in some scenarios (e.g., when oldPath is an alias + // series that gets dropped), so we need to handle the case where it might not exist. + IMeasurementMNode oldPathNode; + try { + oldPathNode = mTree.getMeasurementMNode(oldPath); + } catch (PathNotExistException e) { + // If the node doesn't exist (e.g., it was deleted as part of the alias operation), + // we just log and continue - the unlock operation should be idempotent. + // Write log and return early + writeToMLog(node); + return; + } + + // Clear the isRenaming flag that was set in lockForAlias + // This unlocks the node and allows other operations on it + oldPathNode.setIsRenaming(false); + + // Write log + writeToMLog(node); + } + @Override public void createLogicalView(final ICreateLogicalViewPlan plan) throws MetadataException { if (!regionStatistics.isAllowToCreateNewSeries()) { @@ -2099,5 +2372,73 @@ public RecoverOperationResult visitAlterEncodingCompressor( return new RecoverOperationResult(e); } } + + @Override + public RecoverOperationResult visitCreateAliasSeries( + final CreateAliasSeriesNode createAliasSeriesNode, final SchemaRegionMemoryImpl context) { + try { + createAliasSeries(createAliasSeriesNode); + return RecoverOperationResult.SUCCESS; + } catch (final MetadataException e) { + return new RecoverOperationResult(e); + } + } + + @Override + public RecoverOperationResult visitMarkSeriesDisabled( + final MarkSeriesDisabledNode markSeriesDisabledNode, final SchemaRegionMemoryImpl context) { + try { + markSeriesDisabled(markSeriesDisabledNode); + return RecoverOperationResult.SUCCESS; + } catch (final MetadataException e) { + return new RecoverOperationResult(e); + } + } + + @Override + public RecoverOperationResult visitUpdatePhysicalAliasRef( + final UpdatePhysicalAliasRefNode updatePhysicalAliasRefNode, + final SchemaRegionMemoryImpl context) { + try { + updatePhysicalAliasRef(updatePhysicalAliasRefNode); + return RecoverOperationResult.SUCCESS; + } catch (final MetadataException e) { + return new RecoverOperationResult(e); + } + } + + @Override + public RecoverOperationResult visitDropAliasSeries( + final DropAliasSeriesNode dropAliasSeriesNode, final SchemaRegionMemoryImpl context) { + try { + dropAliasSeries(dropAliasSeriesNode); + return RecoverOperationResult.SUCCESS; + } catch (final MetadataException e) { + return new RecoverOperationResult(e); + } + } + + @Override + public RecoverOperationResult visitEnablePhysicalSeries( + final EnablePhysicalSeriesNode enablePhysicalSeriesNode, + final SchemaRegionMemoryImpl context) { + try { + enablePhysicalSeries(enablePhysicalSeriesNode); + return RecoverOperationResult.SUCCESS; + } catch (final MetadataException e) { + return new RecoverOperationResult(e); + } + } + + @Override + public RecoverOperationResult visitUnlockForAlias( + final UnlockForAliasNode unlockForAliasNode, final SchemaRegionMemoryImpl context) { + try { + unlockForAlias(unlockForAliasNode); + return RecoverOperationResult.SUCCESS; + } catch (final MetadataException e) { + return new RecoverOperationResult(e); + } + } } } diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/schemaengine/schemaregion/impl/SchemaRegionPBTreeImpl.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/schemaengine/schemaregion/impl/SchemaRegionPBTreeImpl.java index 5a105cf7dd7c..93b68588b158 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/schemaengine/schemaregion/impl/SchemaRegionPBTreeImpl.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/schemaengine/schemaregion/impl/SchemaRegionPBTreeImpl.java @@ -35,11 +35,20 @@ import org.apache.iotdb.db.conf.IoTDBConfig; import org.apache.iotdb.db.conf.IoTDBDescriptor; import org.apache.iotdb.db.exception.metadata.AliasAlreadyExistException; +import org.apache.iotdb.db.exception.metadata.MeasurementInBlackListException; import org.apache.iotdb.db.exception.metadata.PathAlreadyExistException; +import org.apache.iotdb.db.exception.metadata.PathNotExistException; import org.apache.iotdb.db.exception.metadata.SchemaDirCreationFailureException; import org.apache.iotdb.db.exception.metadata.SchemaQuotaExceededException; import org.apache.iotdb.db.queryengine.common.schematree.ClusterSchemaTree; import org.apache.iotdb.db.queryengine.plan.planner.plan.node.metadata.write.AlterEncodingCompressorNode; +import org.apache.iotdb.db.queryengine.plan.planner.plan.node.metadata.write.CreateAliasSeriesNode; +import org.apache.iotdb.db.queryengine.plan.planner.plan.node.metadata.write.DropAliasSeriesNode; +import org.apache.iotdb.db.queryengine.plan.planner.plan.node.metadata.write.EnablePhysicalSeriesNode; +import org.apache.iotdb.db.queryengine.plan.planner.plan.node.metadata.write.LockAliasNode; +import org.apache.iotdb.db.queryengine.plan.planner.plan.node.metadata.write.MarkSeriesDisabledNode; +import org.apache.iotdb.db.queryengine.plan.planner.plan.node.metadata.write.UnlockForAliasNode; +import org.apache.iotdb.db.queryengine.plan.planner.plan.node.metadata.write.UpdatePhysicalAliasRefNode; import org.apache.iotdb.db.queryengine.plan.relational.metadata.fetcher.cache.TableId; import org.apache.iotdb.db.queryengine.plan.relational.planner.node.schema.ConstructTableDevicesBlackListNode; import org.apache.iotdb.db.queryengine.plan.relational.planner.node.schema.CreateOrUpdateTableDeviceNode; @@ -69,6 +78,7 @@ import org.apache.iotdb.db.schemaengine.schemaregion.logfile.SchemaLogWriter; import org.apache.iotdb.db.schemaengine.schemaregion.logfile.visitor.SchemaRegionPlanDeserializer; import org.apache.iotdb.db.schemaengine.schemaregion.logfile.visitor.SchemaRegionPlanSerializer; +import org.apache.iotdb.db.schemaengine.schemaregion.mtree.impl.mem.mnode.info.MeasurementInfo; import org.apache.iotdb.db.schemaengine.schemaregion.mtree.impl.pbtree.MTreeBelowSGCachedImpl; import org.apache.iotdb.db.schemaengine.schemaregion.mtree.impl.pbtree.memory.ReleaseFlushMonitor; import org.apache.iotdb.db.schemaengine.schemaregion.mtree.impl.pbtree.mnode.ICachedMNode; @@ -115,6 +125,7 @@ import java.io.IOException; import java.util.ArrayList; import java.util.Collections; +import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; @@ -958,6 +969,331 @@ public void alterEncodingCompressor(final AlterEncodingCompressorNode node) "PBTree does not support altering encoding and compressor yet."); } + @Override + public void lockForAlias(final LockAliasNode node) throws MetadataException { + final PartialPath oldPath = node.getOldPath(); + final IMeasurementMNode oldPathNode = mtree.getMeasurementMNode(oldPath); + try { + // Check if oldPath is already being renamed (prevent concurrent rename operations) + if (oldPathNode.isRenaming()) { + throw new MetadataException( + String.format( + "Timeseries [%s] is already being renamed, cannot perform another rename operation", + oldPath.getFullPath())); + } + + // Check if oldPath is pre-deleted (in deletion blacklist) + if (oldPathNode.isPreDeleted()) { + throw new MeasurementInBlackListException(oldPath); + } + + // Check if oldPath is a logical view (logical views may not support alias series) + if (oldPathNode.isLogicalView()) { + throw new MetadataException( + String.format( + "Cannot create alias series for logical view [%s]", oldPath.getFullPath())); + } + + // Check if oldPath is disabled (disabled series cannot be renamed/aliased) + if (oldPathNode.isDisabled()) { + throw new MetadataException( + String.format( + "Cannot create alias series for disabled timeseries [%s]", oldPath.getFullPath())); + } + + // Mark oldPath as isRenaming to prevent concurrent operations + mtree.updateMNode( + oldPathNode.getAsMNode(), o -> o.getAsMeasurementMNode().setIsRenaming(true)); + + // Write log + if (!isRecovering) { + try { + writeToMLog(node); + } catch (IOException e) { + throw new MetadataException(e); + } + } + } finally { + mtree.unPinMNode(oldPathNode.getAsMNode()); + } + } + + @Override + public void createAliasSeries(final CreateAliasSeriesNode node) throws MetadataException { + while (!regionStatistics.isAllowToCreateNewSeries()) { + ReleaseFlushMonitor.getInstance().waitIfReleasing(); + } + + final PartialPath oldPath = node.getOldPath(); // Physical path to be aliased + final PartialPath newPath = node.getNewPath(); // New alias path + final org.apache.iotdb.mpp.rpc.thrift.TTimeSeriesInfo timeSeriesInfo = node.getTimeSeriesInfo(); + + if (timeSeriesInfo == null) { + throw new MetadataException("TTimeSeriesInfo is required for createAliasSeries but is null"); + } + + // Extract schema information from TTimeSeriesInfo + final TSDataType dataType = TSDataType.values()[timeSeriesInfo.getDataType()]; + final TSEncoding encoding = TSEncoding.values()[timeSeriesInfo.getEncoding()]; + final CompressionType compressor = CompressionType.values()[timeSeriesInfo.getCompressor()]; + final String alias = timeSeriesInfo.getMeasurementAlias(); + + // Build props map with alias series properties + Map props = new HashMap<>(); + // Copy original props if any + if (timeSeriesInfo.getProps() != null && !timeSeriesInfo.getProps().isEmpty()) { + props.putAll(timeSeriesInfo.getProps()); + } + // Add alias series properties + props.put(MeasurementInfo.IS_RENAMED_KEY, "true"); + props.put(MeasurementInfo.ORIGINAL_PATH_KEY, oldPath.getFullPath()); + + final IMeasurementMNode aliasMNode; + long offset = -1; + try { + SchemaUtils.checkDataTypeWithEncoding(dataType, encoding); + + // Create alias measurement node at newPath using mtree.createTimeSeriesWithPinnedReturn + aliasMNode = + mtree.createTimeSeriesWithPinnedReturn( + newPath, dataType, encoding, compressor, props, alias, false, null); + + try { + // Should merge + if (Objects.isNull(aliasMNode)) { + // Write an upsert plan directly + // Note that the "pin" and "unpin" is reentrant + upsertAliasAndTagsAndAttributes( + alias, + timeSeriesInfo.getTags() != null ? timeSeriesInfo.getTags() : null, + timeSeriesInfo.getAttributes() != null ? timeSeriesInfo.getAttributes() : null, + newPath); + return; + } + + // Update statistics and schemaDataTypeNumMap + regionStatistics.addMeasurement(1L); + + // Update tag index + if (offset != -1 && isRecovering) { + // The time series has already been created and now system is recovering, using the tag + // info in tagFile to recover index directly + tagManager.recoverIndex(offset, aliasMNode); + mtree.pinMNode(aliasMNode.getAsMNode()); + } else if (timeSeriesInfo.getTags() != null) { + // Tag key, tag value + tagManager.addIndex(timeSeriesInfo.getTags(), aliasMNode); + mtree.pinMNode(aliasMNode.getAsMNode()); + } + + // write log + if (!isRecovering) { + // either tags or attributes is not empty + if ((timeSeriesInfo.getTags() != null && !timeSeriesInfo.getTags().isEmpty()) + || (timeSeriesInfo.getAttributes() != null + && !timeSeriesInfo.getAttributes().isEmpty())) { + offset = + tagManager.writeTagFile(timeSeriesInfo.getTags(), timeSeriesInfo.getAttributes()); + } + writeToMLog(node); + } + if (offset != -1) { + long finalOffset = offset; + mtree.updateMNode( + aliasMNode.getAsMNode(), o -> o.getAsMeasurementMNode().setOffset(finalOffset)); + } + } finally { + if (Objects.nonNull(aliasMNode)) { + mtree.unPinMNode(aliasMNode.getAsMNode()); + } + } + } catch (IOException e) { + throw new MetadataException(e); + } + } + + @Override + public void markSeriesDisabled(final MarkSeriesDisabledNode node) throws MetadataException { + final PartialPath oldPath = node.getOldPath(); + final PartialPath newPath = node.getNewPath(); + + // Get the physical measurement node (verify it exists) + final IMeasurementMNode physicalNode = mtree.getMeasurementMNode(oldPath); + try { + // Validate that the physical node is not a logical view + if (physicalNode.isLogicalView()) { + throw new MetadataException( + String.format("Cannot mark logical view [%s] as disabled", oldPath.getFullPath())); + } + + // Mark physical node as disabled (DISABLED=true) and set ALIAS_PATH + mtree.updateMNode( + physicalNode.getAsMNode(), + o -> { + o.getAsMeasurementMNode().setDisabled(true); + o.getAsMeasurementMNode().setAliasPath(newPath); + }); + + // Write log + if (!isRecovering) { + try { + writeToMLog(node); + } catch (IOException e) { + throw new MetadataException(e); + } + } + } finally { + mtree.unPinMNode(physicalNode.getAsMNode()); + } + } + + @Override + public void updatePhysicalAliasRef(final UpdatePhysicalAliasRefNode node) + throws MetadataException { + final PartialPath physicalPath = node.getPhysicalPath(); + final PartialPath newAliasPath = node.getNewAliasPath(); + + // Get the physical measurement node (verify it exists) + final IMeasurementMNode physicalNode = mtree.getMeasurementMNode(physicalPath); + try { + // Validate that the physical node is not a logical view + if (physicalNode.isLogicalView()) { + throw new MetadataException( + String.format( + "Cannot update alias reference for logical view [%s]", physicalPath.getFullPath())); + } + + // Update ALIAS_PATH to newAliasPath + mtree.updateMNode( + physicalNode.getAsMNode(), o -> o.getAsMeasurementMNode().setAliasPath(newAliasPath)); + + // Write log + if (!isRecovering) { + try { + writeToMLog(node); + } catch (IOException e) { + throw new MetadataException(e); + } + } + } finally { + mtree.unPinMNode(physicalNode.getAsMNode()); + } + } + + @Override + public void dropAliasSeries(final DropAliasSeriesNode node) throws MetadataException { + final PartialPath aliasPath = node.getAliasPath(); + + // Get the alias measurement node (verify it exists) + final IMeasurementMNode aliasNode = mtree.getMeasurementMNode(aliasPath); + try { + // Verify it's an alias series (IS_RENAMED=true) + if (!aliasNode.isRenamed()) { + throw new MetadataException( + String.format( + "Timeseries [%s] is not an alias series, cannot drop as alias", + aliasPath.getFullPath())); + } + + // Validate that the alias node is not a logical view (logical views are not alias series) + if (aliasNode.isLogicalView()) { + throw new MetadataException( + String.format( + "Logical view [%s] cannot be dropped as an alias series", aliasPath.getFullPath())); + } + + // Physically delete the alias node + // Note: deleteTimeseries will return the deleted node and handle unpin internally + IMeasurementMNode deletedNode = mtree.deleteTimeseries(aliasPath); + removeFromTagInvertedIndex(deletedNode); + regionStatistics.deleteMeasurement(1L); + + // Write log + if (!isRecovering) { + writeToMLog(node); + } + } catch (IOException e) { + throw new MetadataException(e); + } + } + + @Override + public void enablePhysicalSeries(final EnablePhysicalSeriesNode node) throws MetadataException { + final PartialPath physicalPath = node.getPhysicalPath(); + + // Get the physical measurement node (verify it exists) + final IMeasurementMNode physicalNode = mtree.getMeasurementMNode(physicalPath); + try { + // Validate that the physical node is not a logical view + if (physicalNode.isLogicalView()) { + throw new MetadataException( + String.format("Cannot enable logical view [%s]", physicalPath.getFullPath())); + } + + // Remove DISABLED flag (set DISABLED=false) and clear ALIAS_PATH reference pointer + mtree.updateMNode( + physicalNode.getAsMNode(), + o -> { + o.getAsMeasurementMNode().setDisabled(false); + o.getAsMeasurementMNode().setAliasPath(null); + }); + + // Write log + if (!isRecovering) { + try { + writeToMLog(node); + } catch (IOException e) { + throw new MetadataException(e); + } + } + } finally { + mtree.unPinMNode(physicalNode.getAsMNode()); + } + } + + @Override + public void unlockForAlias(final UnlockForAliasNode node) throws MetadataException { + final PartialPath oldPath = node.getOldPath(); + + IMeasurementMNode oldPathNode; + try { + // Get oldPath node + oldPathNode = mtree.getMeasurementMNode(oldPath); + } catch (PathNotExistException e) { + // If the node no longer exists (e.g., due to a rollback or concurrent deletion), + // we can consider the unlock operation idempotent and log a warning. + logger.warn( + "Attempted to unlock non-existent path [{}]. It might have been deleted.", + oldPath.getFullPath()); + // Still write to MLog if not recovering, to ensure the procedure state is consistent. + if (!isRecovering) { + try { + writeToMLog(node); + } catch (IOException ioException) { + throw new MetadataException(ioException); + } + } + return; + } + + try { + // Clear isRenaming flag + mtree.updateMNode( + oldPathNode.getAsMNode(), o -> o.getAsMeasurementMNode().setIsRenaming(false)); + + // Write log + if (!isRecovering) { + try { + writeToMLog(node); + } catch (IOException ioException) { + throw new MetadataException(ioException); + } + } + } finally { + mtree.unPinMNode(oldPathNode.getAsMNode()); + } + } + @Override public void createLogicalView(ICreateLogicalViewPlan plan) throws MetadataException { while (!regionStatistics.isAllowToCreateNewSeries()) { @@ -1736,5 +2072,84 @@ public RecoverOperationResult visitDeactivateTemplate( return new RecoverOperationResult(e); } } + + @Override + public RecoverOperationResult visitCreateAliasSeries( + final CreateAliasSeriesNode createAliasSeriesNode, final SchemaRegionPBTreeImpl context) { + try { + createAliasSeries(createAliasSeriesNode); + return RecoverOperationResult.SUCCESS; + } catch (final MetadataException e) { + return new RecoverOperationResult(e); + } + } + + @Override + public RecoverOperationResult visitLockAlias( + final LockAliasNode lockAliasNode, final SchemaRegionPBTreeImpl context) { + try { + lockForAlias(lockAliasNode); + return RecoverOperationResult.SUCCESS; + } catch (final MetadataException e) { + return new RecoverOperationResult(e); + } + } + + @Override + public RecoverOperationResult visitMarkSeriesDisabled( + final MarkSeriesDisabledNode markSeriesDisabledNode, final SchemaRegionPBTreeImpl context) { + try { + markSeriesDisabled(markSeriesDisabledNode); + return RecoverOperationResult.SUCCESS; + } catch (final MetadataException e) { + return new RecoverOperationResult(e); + } + } + + @Override + public RecoverOperationResult visitUpdatePhysicalAliasRef( + final UpdatePhysicalAliasRefNode updatePhysicalAliasRefNode, + final SchemaRegionPBTreeImpl context) { + try { + updatePhysicalAliasRef(updatePhysicalAliasRefNode); + return RecoverOperationResult.SUCCESS; + } catch (final MetadataException e) { + return new RecoverOperationResult(e); + } + } + + @Override + public RecoverOperationResult visitDropAliasSeries( + final DropAliasSeriesNode dropAliasSeriesNode, final SchemaRegionPBTreeImpl context) { + try { + dropAliasSeries(dropAliasSeriesNode); + return RecoverOperationResult.SUCCESS; + } catch (final MetadataException e) { + return new RecoverOperationResult(e); + } + } + + @Override + public RecoverOperationResult visitEnablePhysicalSeries( + final EnablePhysicalSeriesNode enablePhysicalSeriesNode, + final SchemaRegionPBTreeImpl context) { + try { + enablePhysicalSeries(enablePhysicalSeriesNode); + return RecoverOperationResult.SUCCESS; + } catch (final MetadataException e) { + return new RecoverOperationResult(e); + } + } + + @Override + public RecoverOperationResult visitUnlockForAlias( + final UnlockForAliasNode unlockForAliasNode, final SchemaRegionPBTreeImpl context) { + try { + unlockForAlias(unlockForAliasNode); + return RecoverOperationResult.SUCCESS; + } catch (final MetadataException e) { + return new RecoverOperationResult(e); + } + } } } diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/schemaengine/schemaregion/logfile/visitor/SchemaRegionPlanDeserializer.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/schemaengine/schemaregion/logfile/visitor/SchemaRegionPlanDeserializer.java index 163ccb4e59de..3cfb7d90eb1f 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/schemaengine/schemaregion/logfile/visitor/SchemaRegionPlanDeserializer.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/schemaengine/schemaregion/logfile/visitor/SchemaRegionPlanDeserializer.java @@ -26,6 +26,13 @@ import org.apache.iotdb.commons.schema.view.viewExpression.ViewExpression; import org.apache.iotdb.db.queryengine.plan.planner.plan.node.PlanNodeType; import org.apache.iotdb.db.queryengine.plan.planner.plan.node.metadata.write.AlterEncodingCompressorNode; +import org.apache.iotdb.db.queryengine.plan.planner.plan.node.metadata.write.CreateAliasSeriesNode; +import org.apache.iotdb.db.queryengine.plan.planner.plan.node.metadata.write.DropAliasSeriesNode; +import org.apache.iotdb.db.queryengine.plan.planner.plan.node.metadata.write.EnablePhysicalSeriesNode; +import org.apache.iotdb.db.queryengine.plan.planner.plan.node.metadata.write.LockAliasNode; +import org.apache.iotdb.db.queryengine.plan.planner.plan.node.metadata.write.MarkSeriesDisabledNode; +import org.apache.iotdb.db.queryengine.plan.planner.plan.node.metadata.write.UnlockForAliasNode; +import org.apache.iotdb.db.queryengine.plan.planner.plan.node.metadata.write.UpdatePhysicalAliasRefNode; import org.apache.iotdb.db.queryengine.plan.relational.planner.node.schema.ConstructTableDevicesBlackListNode; import org.apache.iotdb.db.queryengine.plan.relational.planner.node.schema.CreateOrUpdateTableDeviceNode; import org.apache.iotdb.db.queryengine.plan.relational.planner.node.schema.DeleteTableDeviceNode; @@ -482,5 +489,47 @@ public ISchemaRegionPlan visitAlterEncodingCompressor( final AlterEncodingCompressorNode alterEncodingCompressorNode, final ByteBuffer buffer) { return (AlterEncodingCompressorNode) PlanNodeType.deserialize(buffer); } + + @Override + public ISchemaRegionPlan visitCreateAliasSeries( + final CreateAliasSeriesNode createAliasSeriesNode, final ByteBuffer buffer) { + return CreateAliasSeriesNode.deserialize(buffer); + } + + @Override + public ISchemaRegionPlan visitMarkSeriesDisabled( + final MarkSeriesDisabledNode markSeriesDisabledNode, final ByteBuffer buffer) { + return MarkSeriesDisabledNode.deserialize(buffer); + } + + @Override + public ISchemaRegionPlan visitUpdatePhysicalAliasRef( + final UpdatePhysicalAliasRefNode updatePhysicalAliasRefNode, final ByteBuffer buffer) { + return UpdatePhysicalAliasRefNode.deserialize(buffer); + } + + @Override + public ISchemaRegionPlan visitDropAliasSeries( + final DropAliasSeriesNode dropAliasSeriesNode, final ByteBuffer buffer) { + return DropAliasSeriesNode.deserialize(buffer); + } + + @Override + public ISchemaRegionPlan visitEnablePhysicalSeries( + final EnablePhysicalSeriesNode enablePhysicalSeriesNode, final ByteBuffer buffer) { + return EnablePhysicalSeriesNode.deserialize(buffer); + } + + @Override + public ISchemaRegionPlan visitUnlockForAlias( + final UnlockForAliasNode unlockForAliasNode, final ByteBuffer buffer) { + return UnlockForAliasNode.deserialize(buffer); + } + + @Override + public ISchemaRegionPlan visitLockAlias( + final LockAliasNode lockAliasNode, final ByteBuffer buffer) { + return LockAliasNode.deserialize(buffer); + } } } diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/schemaengine/schemaregion/logfile/visitor/SchemaRegionPlanSerializer.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/schemaengine/schemaregion/logfile/visitor/SchemaRegionPlanSerializer.java index b7b7d9758ca4..7eaaf452b4ee 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/schemaengine/schemaregion/logfile/visitor/SchemaRegionPlanSerializer.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/schemaengine/schemaregion/logfile/visitor/SchemaRegionPlanSerializer.java @@ -23,6 +23,13 @@ import org.apache.iotdb.commons.schema.view.viewExpression.ViewExpression; import org.apache.iotdb.db.queryengine.plan.planner.plan.node.PlanNode; import org.apache.iotdb.db.queryengine.plan.planner.plan.node.metadata.write.AlterEncodingCompressorNode; +import org.apache.iotdb.db.queryengine.plan.planner.plan.node.metadata.write.CreateAliasSeriesNode; +import org.apache.iotdb.db.queryengine.plan.planner.plan.node.metadata.write.DropAliasSeriesNode; +import org.apache.iotdb.db.queryengine.plan.planner.plan.node.metadata.write.EnablePhysicalSeriesNode; +import org.apache.iotdb.db.queryengine.plan.planner.plan.node.metadata.write.LockAliasNode; +import org.apache.iotdb.db.queryengine.plan.planner.plan.node.metadata.write.MarkSeriesDisabledNode; +import org.apache.iotdb.db.queryengine.plan.planner.plan.node.metadata.write.UnlockForAliasNode; +import org.apache.iotdb.db.queryengine.plan.planner.plan.node.metadata.write.UpdatePhysicalAliasRefNode; import org.apache.iotdb.db.queryengine.plan.relational.planner.node.schema.ConstructTableDevicesBlackListNode; import org.apache.iotdb.db.queryengine.plan.relational.planner.node.schema.CreateOrUpdateTableDeviceNode; import org.apache.iotdb.db.queryengine.plan.relational.planner.node.schema.DeleteTableDeviceNode; @@ -553,6 +560,50 @@ public SchemaRegionPlanSerializationResult visitAlterEncodingCompressor( return visitPlanNode(alterEncodingCompressorNode, outputStream); } + @Override + public SchemaRegionPlanSerializationResult visitCreateAliasSeries( + final CreateAliasSeriesNode createAliasSeriesNode, final DataOutputStream outputStream) { + return visitPlanNode(createAliasSeriesNode, outputStream); + } + + @Override + public SchemaRegionPlanSerializationResult visitMarkSeriesDisabled( + final MarkSeriesDisabledNode markSeriesDisabledNode, final DataOutputStream outputStream) { + return visitPlanNode(markSeriesDisabledNode, outputStream); + } + + @Override + public SchemaRegionPlanSerializationResult visitUpdatePhysicalAliasRef( + final UpdatePhysicalAliasRefNode updatePhysicalAliasRefNode, + final DataOutputStream outputStream) { + return visitPlanNode(updatePhysicalAliasRefNode, outputStream); + } + + @Override + public SchemaRegionPlanSerializationResult visitDropAliasSeries( + final DropAliasSeriesNode dropAliasSeriesNode, final DataOutputStream outputStream) { + return visitPlanNode(dropAliasSeriesNode, outputStream); + } + + @Override + public SchemaRegionPlanSerializationResult visitEnablePhysicalSeries( + final EnablePhysicalSeriesNode enablePhysicalSeriesNode, + final DataOutputStream outputStream) { + return visitPlanNode(enablePhysicalSeriesNode, outputStream); + } + + @Override + public SchemaRegionPlanSerializationResult visitUnlockForAlias( + final UnlockForAliasNode unlockForAliasNode, final DataOutputStream outputStream) { + return visitPlanNode(unlockForAliasNode, outputStream); + } + + @Override + public SchemaRegionPlanSerializationResult visitLockAlias( + final LockAliasNode lockAliasNode, final DataOutputStream outputStream) { + return visitPlanNode(lockAliasNode, outputStream); + } + private SchemaRegionPlanSerializationResult visitPlanNode( final PlanNode planNode, final DataOutputStream outputStream) { try { diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/schemaengine/schemaregion/mtree/impl/mem/mnode/info/LogicalViewInfo.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/schemaengine/schemaregion/mtree/impl/mem/mnode/info/LogicalViewInfo.java index a5ab55796e43..f18474cae484 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/schemaengine/schemaregion/mtree/impl/mem/mnode/info/LogicalViewInfo.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/schemaengine/schemaregion/mtree/impl/mem/mnode/info/LogicalViewInfo.java @@ -160,11 +160,74 @@ public void moveDataToNewMNode(IMeasurementMNode newMNode) { if (newMNode.isLogicalView()) { newMNode.setSchema(this.schema); newMNode.setPreDeleted(preDeleted); + // Logical views don't support alias series properties + newMNode.setIsRenamed(false); + newMNode.setIsRenaming(false); + newMNode.setDisabled(false); + newMNode.setOriginalPath(null); + newMNode.setAliasPath(null); + return; } throw new SchemaExecutionException( new IllegalArgumentException( "Type of newMNode is not LogicalViewMNode! It's " + newMNode.getMNodeType().toString())); } + + // Alias series properties - Logical views don't support alias series + @Override + public boolean isRenamed() { + return false; + } + + @Override + public void setIsRenamed(boolean isRenamed) { + // Logical views don't support alias series + throw new UnsupportedOperationException("Logical views don't support alias series"); + } + + @Override + public boolean isRenaming() { + return false; + } + + @Override + public void setIsRenaming(boolean isRenaming) { + // Logical views don't support alias series + throw new UnsupportedOperationException("Logical views don't support alias series"); + } + + @Override + public boolean isDisabled() { + return false; + } + + @Override + public void setDisabled(boolean isDisabled) { + // Logical views don't support alias series + throw new UnsupportedOperationException("Logical views don't support alias series"); + } + + @Override + public PartialPath getOriginalPath() { + return null; + } + + @Override + public void setOriginalPath(PartialPath originalPath) { + // Logical views don't support alias series + throw new UnsupportedOperationException("Logical views don't support alias series"); + } + + @Override + public PartialPath getAliasPath() { + return null; + } + + @Override + public void setAliasPath(PartialPath aliasPath) { + // Logical views don't support alias series + throw new UnsupportedOperationException("Logical views don't support alias series"); + } // endregion } diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/schemaengine/schemaregion/mtree/impl/mem/mnode/info/MeasurementInfo.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/schemaengine/schemaregion/mtree/impl/mem/mnode/info/MeasurementInfo.java index 19d1855db0cb..e47bfc678e11 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/schemaengine/schemaregion/mtree/impl/mem/mnode/info/MeasurementInfo.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/schemaengine/schemaregion/mtree/impl/mem/mnode/info/MeasurementInfo.java @@ -18,14 +18,46 @@ */ package org.apache.iotdb.db.schemaengine.schemaregion.mtree.impl.mem.mnode.info; +import org.apache.iotdb.commons.path.PartialPath; import org.apache.iotdb.commons.schema.node.info.IMeasurementInfo; import org.apache.iotdb.commons.schema.node.role.IMeasurementMNode; import org.apache.tsfile.enums.TSDataType; import org.apache.tsfile.write.schema.IMeasurementSchema; +import org.apache.tsfile.write.schema.MeasurementSchema; + +import java.util.Map; public class MeasurementInfo implements IMeasurementInfo { + // Props keys for alias series feature + /** + * Key for IS_RENAMED property in schema props (value: "true" or "false") Indicates this is an + * alias series (renaming completed) + */ + public static final String IS_RENAMED_KEY = "IS_RENAMED"; + + /** + * Key for IS_RENAMING property in schema props (value: "true" or "false") Indicates this + * measurement is currently being renamed (for non-procedure alter operations) + */ + public static final String IS_RENAMING_KEY = "IS_RENAMING"; + + /** Key for DISABLED property in schema props (value: "true" or "false") */ + public static final String DISABLED_KEY = "DISABLED"; + + /** + * Key for ORIGINAL_PATH property in schema props (value: physical path string) If this key + * exists, it indicates this is an alias series pointing to the physical path + */ + public static final String ORIGINAL_PATH_KEY = "ORIGINAL_PATH"; + + /** + * Key for ALIAS_PATH property in schema props (value: alias path string) If this key exists, it + * indicates this physical series has an alias pointing to it + */ + public static final String ALIAS_PATH_KEY = "ALIAS_PATH"; + /** alias name of this measurement */ protected String alias; @@ -52,6 +84,12 @@ public void moveDataToNewMNode(IMeasurementMNode newMNode) { newMNode.setAlias(alias); newMNode.setOffset(offset); newMNode.setPreDeleted(preDeleted); + // Copy alias series properties + newMNode.setIsRenamed(this.isRenamed()); + newMNode.setIsRenaming(this.isRenaming()); + newMNode.setDisabled(this.isDisabled()); + newMNode.setOriginalPath(this.getOriginalPath()); + newMNode.setAliasPath(this.getAliasPath()); } @Override @@ -99,6 +137,137 @@ public void setPreDeleted(boolean preDeleted) { this.preDeleted = preDeleted; } + /** Get props from MeasurementSchema. Returns null if schema is not MeasurementSchema. */ + private Map getProps() { + if (schema instanceof MeasurementSchema) { + return ((MeasurementSchema) schema).getProps(); + } + return null; + } + + /** Get or create props map from MeasurementSchema. */ + private Map getOrCreateProps() { + if (schema instanceof MeasurementSchema) { + Map props = ((MeasurementSchema) schema).getProps(); + if (props == null) { + props = new java.util.HashMap<>(); + ((MeasurementSchema) schema).setProps(props); + } + return props; + } + throw new IllegalStateException("Schema is not MeasurementSchema, cannot access props"); + } + + @Override + public boolean isRenamed() { + Map props = getProps(); + if (props == null) { + return false; + } + String value = props.get(IS_RENAMED_KEY); + return Boolean.parseBoolean(value); + } + + @Override + public void setIsRenamed(boolean isRenamed) { + Map props = getOrCreateProps(); + if (isRenamed) { + props.put(IS_RENAMED_KEY, "true"); + } else { + props.remove(IS_RENAMED_KEY); + } + } + + @Override + public boolean isRenaming() { + Map props = getProps(); + if (props == null) { + return false; + } + String value = props.get(IS_RENAMING_KEY); + return Boolean.parseBoolean(value); + } + + @Override + public void setIsRenaming(boolean isRenaming) { + Map props = getOrCreateProps(); + if (isRenaming) { + props.put(IS_RENAMING_KEY, "true"); + } else { + props.remove(IS_RENAMING_KEY); + } + } + + @Override + public boolean isDisabled() { + Map props = getProps(); + if (props == null) { + return false; + } + String value = props.get(DISABLED_KEY); + return Boolean.parseBoolean(value); + } + + @Override + public void setDisabled(boolean isDisabled) { + Map props = getOrCreateProps(); + props.put(DISABLED_KEY, String.valueOf(isDisabled)); + } + + @Override + public PartialPath getOriginalPath() { + Map props = getProps(); + if (props == null) { + return null; + } + String pathString = props.get(ORIGINAL_PATH_KEY); + if (pathString == null) { + return null; + } + try { + return new PartialPath(pathString); + } catch (Exception e) { + return null; + } + } + + @Override + public void setOriginalPath(PartialPath originalPath) { + Map props = getOrCreateProps(); + if (originalPath == null) { + props.remove(ORIGINAL_PATH_KEY); + } else { + props.put(ORIGINAL_PATH_KEY, originalPath.getFullPath()); + } + } + + @Override + public PartialPath getAliasPath() { + Map props = getProps(); + if (props == null) { + return null; + } + String pathString = props.get(ALIAS_PATH_KEY); + if (pathString == null) { + return null; + } + try { + return new PartialPath(pathString); + } catch (Exception e) { + return null; + } + } + + @Override + public void setAliasPath(PartialPath aliasPath) { + Map props = getOrCreateProps(); + if (aliasPath == null) { + props.remove(ALIAS_PATH_KEY); + } else { + props.put(ALIAS_PATH_KEY, aliasPath.getFullPath()); + } + } + /** * The memory occupied by an MeasurementInfo based occupation * diff --git a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/schema/node/common/AbstractMeasurementMNode.java b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/schema/node/common/AbstractMeasurementMNode.java index 01cd77714acb..dcbcbedbfdb7 100644 --- a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/schema/node/common/AbstractMeasurementMNode.java +++ b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/schema/node/common/AbstractMeasurementMNode.java @@ -132,6 +132,57 @@ public void setPreDeleted(boolean preDeleted) { measurementInfo.setPreDeleted(preDeleted); } + // Alias series properties methods + @Override + public boolean isRenamed() { + return measurementInfo.isRenamed(); + } + + @Override + public void setIsRenamed(boolean isRenamed) { + measurementInfo.setIsRenamed(isRenamed); + } + + @Override + public boolean isRenaming() { + return measurementInfo.isRenaming(); + } + + @Override + public void setIsRenaming(boolean isRenaming) { + measurementInfo.setIsRenaming(isRenaming); + } + + @Override + public boolean isDisabled() { + return measurementInfo.isDisabled(); + } + + @Override + public void setDisabled(boolean isDisabled) { + measurementInfo.setDisabled(isDisabled); + } + + @Override + public PartialPath getOriginalPath() { + return measurementInfo.getOriginalPath(); + } + + @Override + public void setOriginalPath(PartialPath originalPath) { + measurementInfo.setOriginalPath(originalPath); + } + + @Override + public PartialPath getAliasPath() { + return measurementInfo.getAliasPath(); + } + + @Override + public void setAliasPath(PartialPath aliasPath) { + measurementInfo.setAliasPath(aliasPath); + } + @Override public R accept(MNodeVisitor visitor, C context) { return visitor.visitMeasurementMNode(this, context); diff --git a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/schema/node/info/IMeasurementInfo.java b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/schema/node/info/IMeasurementInfo.java index 1449edc40522..c836076498a3 100644 --- a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/schema/node/info/IMeasurementInfo.java +++ b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/schema/node/info/IMeasurementInfo.java @@ -18,6 +18,7 @@ */ package org.apache.iotdb.commons.schema.node.info; +import org.apache.iotdb.commons.path.PartialPath; import org.apache.iotdb.commons.schema.node.role.IMeasurementMNode; import org.apache.tsfile.enums.TSDataType; @@ -46,4 +47,75 @@ public interface IMeasurementInfo { int estimateSize(); void moveDataToNewMNode(IMeasurementMNode newMNode); + + // Alias series properties methods + /** + * Check if this measurement is renamed (is an alias series). + * + * @return true if this is an alias series, false otherwise + */ + boolean isRenamed(); + + /** + * Set the IS_RENAMED flag for this measurement. + * + * @param isRenamed true if this is an alias series, false otherwise + */ + void setIsRenamed(boolean isRenamed); + + /** + * Check if this measurement is currently being renamed (for non-procedure alter operations). + * + * @return true if this measurement is being renamed, false otherwise + */ + boolean isRenaming(); + + /** + * Set the IS_RENAMING flag for this measurement. + * + * @param isRenaming true if this measurement is being renamed, false otherwise + */ + void setIsRenaming(boolean isRenaming); + + /** + * Check if this measurement is disabled (original physical path after renaming). + * + * @return true if this measurement is disabled, false otherwise + */ + boolean isDisabled(); + + /** + * Set the DISABLED flag for this measurement. + * + * @param isDisabled true if this measurement is disabled, false otherwise + */ + void setDisabled(boolean isDisabled); + + /** + * Get the original physical path if this is an alias series. + * + * @return the original physical path, or null if not an alias series + */ + PartialPath getOriginalPath(); + + /** + * Set the original physical path for this alias series. + * + * @param originalPath the original physical path + */ + void setOriginalPath(PartialPath originalPath); + + /** + * Get the alias path pointing to this physical series. + * + * @return the alias path, or null if not set + */ + PartialPath getAliasPath(); + + /** + * Set the alias path pointing to this physical series. + * + * @param aliasPath the alias path + */ + void setAliasPath(PartialPath aliasPath); } diff --git a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/schema/node/role/IMeasurementMNode.java b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/schema/node/role/IMeasurementMNode.java index 84e7e157fb89..5a1a1973aa54 100644 --- a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/schema/node/role/IMeasurementMNode.java +++ b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/schema/node/role/IMeasurementMNode.java @@ -19,6 +19,7 @@ package org.apache.iotdb.commons.schema.node.role; 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.tsfile.enums.TSDataType; @@ -48,4 +49,25 @@ public interface IMeasurementMNode> extends IMNode { MeasurementPath getMeasurementPath(); boolean isLogicalView(); + + // Alias series properties methods + boolean isRenamed(); + + void setIsRenamed(boolean isRenamed); + + boolean isRenaming(); + + void setIsRenaming(boolean isRenaming); + + boolean isDisabled(); + + void setDisabled(boolean isDisabled); + + PartialPath getOriginalPath(); + + void setOriginalPath(PartialPath originalPath); + + PartialPath getAliasPath(); + + void setAliasPath(PartialPath aliasPath); } diff --git a/iotdb-protocol/thrift-confignode/src/main/thrift/confignode.thrift b/iotdb-protocol/thrift-confignode/src/main/thrift/confignode.thrift index 01bde7c378dc..9ecb885c8611 100644 --- a/iotdb-protocol/thrift-confignode/src/main/thrift/confignode.thrift +++ b/iotdb-protocol/thrift-confignode/src/main/thrift/confignode.thrift @@ -944,6 +944,13 @@ struct TDeleteTimeSeriesReq { 4: optional bool mayDeleteAudit } +struct TAliasTimeSeriesReq { + 1: required string queryId + 2: required binary oldPath + 3: required binary newPath + 4: optional bool isGeneratedByPipe +} + struct TDeleteLogicalViewReq { 1: required string queryId 2: required binary pathPatternTree @@ -1842,6 +1849,8 @@ service IConfigNodeRPCService { */ common.TSStatus deleteTimeSeries(TDeleteTimeSeriesReq req) + common.TSStatus aliasTimeSeries(TAliasTimeSeriesReq req) + common.TSStatus deleteLogicalView(TDeleteLogicalViewReq req) common.TSStatus alterLogicalView(TAlterLogicalViewReq req) diff --git a/iotdb-protocol/thrift-datanode/src/main/thrift/datanode.thrift b/iotdb-protocol/thrift-datanode/src/main/thrift/datanode.thrift index 469d2bb006ae..5af180d0c1dd 100644 --- a/iotdb-protocol/thrift-datanode/src/main/thrift/datanode.thrift +++ b/iotdb-protocol/thrift-datanode/src/main/thrift/datanode.thrift @@ -465,6 +465,78 @@ struct TDeleteTimeSeriesReq { 3: optional bool isGeneratedByPipe } +struct TTimeSeriesInfo { + 1: required binary path // Serialized MeasurementPath + 2: required i32 dataType // TSDataType ordinal value + 3: required i32 encoding // TSEncoding ordinal value + 4: required i32 compressor // CompressionType ordinal value + 5: optional string measurementAlias + 6: optional map props + 7: optional map tags + 8: optional map attributes +} + +struct TAliasTimeSeriesReq { + 1: required list schemaRegionIdList + 2: required binary oldPath + 3: required binary newPath + 4: optional bool isGeneratedByPipe +} + +struct TAliasTimeSeriesResp { + 1: required common.TSStatus status + 2: optional TTimeSeriesInfo timeSeriesInfo // Schema info from phase 1 + 3: optional bool isRenamed // Whether oldPath is an alias series + 4: optional binary physicalPath // Physical path if oldPath is alias +} + +// Phase 1: Lock and get schema info +struct TLockAndGetSchemaInfoForAliasReq { + 1: required list schemaRegionIdList + 2: required binary oldPath + 3: required binary newPath + 4: optional bool isGeneratedByPipe +} + +// Phase 2: Transform metadata - Scenario A: Create alias series +struct TCreateAliasSeriesReq { + 1: required list schemaRegionIdList + 2: required binary oldPath // Physical path + 3: required binary newPath // Alias path + 4: required TTimeSeriesInfo timeSeriesInfo + 5: optional bool isGeneratedByPipe +} + +// Phase 2: Transform metadata - Scenario A: Mark old series as disabled +struct TMarkSeriesDisabledReq { + 1: required list schemaRegionIdList + 2: required binary oldPath // Physical path to mark as disabled + 3: required binary newPath // Alias path to set in ALIAS_PATH + 4: optional bool isGeneratedByPipe +} + +// Phase 2: Transform metadata - Scenario B: Create new alias and update physical reference +struct TUpdatePhysicalAliasRefReq { + 1: required list schemaRegionIdList + 2: required binary physicalPath // Physical path to update + 3: required binary newAliasPath // New alias path to set in ALIAS_PATH + 4: optional bool isGeneratedByPipe +} + +// Phase 2: Transform metadata - Scenario B/C: Drop old alias series +struct TDropAliasSeriesReq { + 1: required list schemaRegionIdList + 2: required binary aliasPath // Alias path to drop + 3: optional bool isGeneratedByPipe +} + +// Phase 2: Transform metadata - Scenario C: Enable physical series +struct TEnablePhysicalSeriesReq { + 1: required list schemaRegionIdList + 2: required binary physicalPath // Physical path to enable + 3: optional bool isGeneratedByPipe +} + struct TAlterEncodingCompressorReq { 1: required list schemaRegionIdList 2: required binary pathPatternTree @@ -1087,6 +1159,41 @@ service IDataNodeRPCService { */ common.TSStatus deleteTimeSeries(TDeleteTimeSeriesReq req) + /** + * Alias time series: lock and get schema info (phase 1) + */ + TAliasTimeSeriesResp lockAndGetSchemaInfoForAlias(TLockAndGetSchemaInfoForAliasReq req) + + /** + * Alias time series: transform metadata - Scenario A: Create alias series + */ + common.TSStatus createAliasSeries(TCreateAliasSeriesReq req) + + /** + * Alias time series: transform metadata - Scenario A: Mark old series as disabled + */ + common.TSStatus markSeriesDisabled(TMarkSeriesDisabledReq req) + + /** + * Alias time series: transform metadata - Scenario B: Update physical alias reference + */ + common.TSStatus updatePhysicalAliasRef(TUpdatePhysicalAliasRefReq req) + + /** + * Alias time series: transform metadata - Scenario B/C: Drop alias series + */ + common.TSStatus dropAliasSeries(TDropAliasSeriesReq req) + + /** + * Alias time series: transform metadata - Scenario C: Enable physical series + */ + common.TSStatus enablePhysicalSeries(TEnablePhysicalSeriesReq req) + + /** + * Alias time series: unlock (phase 3) + */ + common.TSStatus unlockForAlias(TAliasTimeSeriesReq req) + /** * Alter matched timeseries to specific encoding and compressor in target schemaRegions */