From dd5dd976980941fe86753c3e78c7f451b01cc4cb Mon Sep 17 00:00:00 2001 From: shuwenwei Date: Thu, 17 Sep 2026 14:22:38 +0800 Subject: [PATCH 01/16] [Information Schema] Add SPI extension points for information schema --- ...formationSchemaContentSupplierFactory.java | 36 +++++++---- .../AdditionalInformationSchemaProvider.java | 59 +++++++++++++++++++ ...onalInformationSchemaProviderRegistry.java | 43 ++++++++++++++ .../DataNodeLocationSupplierFactory.java | 9 +++ .../schema/table/InformationSchema.java | 38 ++++++++++++ 5 files changed, 175 insertions(+), 10 deletions(-) create mode 100644 iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/information/AdditionalInformationSchemaProvider.java create mode 100644 iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/information/AdditionalInformationSchemaProviderRegistry.java diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/execution/operator/source/relational/InformationSchemaContentSupplierFactory.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/execution/operator/source/relational/InformationSchemaContentSupplierFactory.java index d39f9f396a8db..f4fb59f7ca7a5 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/execution/operator/source/relational/InformationSchemaContentSupplierFactory.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/execution/operator/source/relational/InformationSchemaContentSupplierFactory.java @@ -89,6 +89,8 @@ import org.apache.iotdb.db.queryengine.plan.execution.config.metadata.relational.ShowCreateViewTask; import org.apache.iotdb.db.queryengine.plan.planner.plan.node.PlanGraphPrinter; import org.apache.iotdb.db.queryengine.plan.relational.function.DataNodeTableBuiltinTableFunction; +import org.apache.iotdb.db.queryengine.plan.relational.information.AdditionalInformationSchemaProvider; +import org.apache.iotdb.db.queryengine.plan.relational.information.AdditionalInformationSchemaProviderRegistry; import org.apache.iotdb.db.queryengine.plan.relational.planner.node.InformationSchemaTableScanNode; import org.apache.iotdb.db.queryengine.plan.relational.planner.node.TableDiskUsageInformationSchemaTableScanNode; import org.apache.iotdb.db.queryengine.plan.relational.security.AccessControl; @@ -173,8 +175,17 @@ public static IInformationSchemaContentSupplier getSupplier( final List dataTypes, final UserEntity userEntity, final InformationSchemaTableScanNode node) { - String tableName = node.getQualifiedObjectName().getObjectName(); + final String tableName = node.getQualifiedObjectName().getObjectName(); try { + // Try additional providers first + for (final AdditionalInformationSchemaProvider provider : + AdditionalInformationSchemaProviderRegistry.getProviders()) { + final IInformationSchemaContentSupplier supplier = + provider.getContentSupplier(tableName, context, dataTypes, userEntity, node); + if (supplier != null) { + return supplier; + } + } switch (tableName) { case InformationSchema.QUERIES: return new QueriesSupplier(dataTypes, userEntity); @@ -384,14 +395,14 @@ public boolean hasNext() { } } - private static class TableSupplier extends TsBlockSupplier { + public static class TableSupplier extends TsBlockSupplier { private final Iterator>> dbIterator; private Iterator tableInfoIterator = null; - private TTableInfo currentTable; - private String dbName; + protected TTableInfo currentTable; + protected String dbName; private final UserEntity userEntity; - private TableSupplier(final List dataTypes, final UserEntity userEntity) + protected TableSupplier(final List dataTypes, final UserEntity userEntity) throws Exception { super(dataTypes); this.userEntity = userEntity; @@ -475,7 +486,7 @@ public boolean hasNext() { } } - private static class ColumnSupplier extends TsBlockSupplier { + public static class ColumnSupplier extends TsBlockSupplier { private final Iterator>> dbIterator; private Iterator> tableInfoIterator; private Iterator columnSchemaIterator; @@ -483,9 +494,10 @@ private static class ColumnSupplier extends TsBlockSupplier { private String tableName; private Set preDeletedColumns; private Map preAlteredColumns; + protected TsTableColumnSchema schema; private final UserEntity userEntity; - private ColumnSupplier(final List dataTypes, final UserEntity userEntity) + protected ColumnSupplier(final List dataTypes, final UserEntity userEntity) throws Exception { super(dataTypes); this.userEntity = userEntity; @@ -522,7 +534,6 @@ private ColumnSupplier(final List dataTypes, final UserEntity userEn @Override protected void constructLine() { - final TsTableColumnSchema schema = columnSchemaIterator.next(); columnBuilders[0].writeBinary(new Binary(dbName, TSFileConfig.STRING_CHARSET)); columnBuilders[1].writeBinary(new Binary(tableName, TSFileConfig.STRING_CHARSET)); columnBuilders[2].writeBinary( @@ -546,10 +557,14 @@ protected void constructLine() { columnBuilders[6].appendNull(); } resultBuilder.declarePosition(); + schema = null; } @Override public boolean hasNext() { + if (Objects.nonNull(schema)) { + return true; + } while (Objects.isNull(columnSchemaIterator) || !columnSchemaIterator.hasNext()) { while (Objects.isNull(tableInfoIterator) || !tableInfoIterator.hasNext()) { if (!dbIterator.hasNext()) { @@ -576,6 +591,7 @@ public boolean hasNext() { } } } + schema = columnSchemaIterator.next(); return true; } } @@ -1660,13 +1676,13 @@ private void closeDataRegionReader() { } } - private abstract static class TsBlockSupplier implements IInformationSchemaContentSupplier { + public abstract static class TsBlockSupplier implements IInformationSchemaContentSupplier { protected final TsBlockBuilder resultBuilder; protected final ColumnBuilder[] columnBuilders; protected final AccessControl accessControl = AuthorityChecker.getAccessControl(); - private TsBlockSupplier(final List dataTypes) { + protected TsBlockSupplier(final List dataTypes) { this.resultBuilder = new TsBlockBuilder(dataTypes); this.columnBuilders = resultBuilder.getValueColumnBuilders(); } diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/information/AdditionalInformationSchemaProvider.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/information/AdditionalInformationSchemaProvider.java new file mode 100644 index 0000000000000..d0874676ca307 --- /dev/null +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/information/AdditionalInformationSchemaProvider.java @@ -0,0 +1,59 @@ +/* + * 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.relational.information; + +import org.apache.iotdb.common.rpc.thrift.TDataNodeLocation; +import org.apache.iotdb.commons.audit.UserEntity; +import org.apache.iotdb.db.queryengine.execution.operator.OperatorContext; +import org.apache.iotdb.db.queryengine.execution.operator.source.relational.InformationSchemaContentSupplierFactory.IInformationSchemaContentSupplier; +import org.apache.iotdb.db.queryengine.plan.relational.planner.node.InformationSchemaTableScanNode; + +import org.apache.tsfile.enums.TSDataType; + +import java.util.List; + +/** Provides additional content and execution locations for information schema tables. */ +public interface AdditionalInformationSchemaProvider { + + /** + * Returns the content supplier for the table, or {@code null} if the table is not handled. + * + * @throws Exception if the content supplier cannot be created. + */ + default IInformationSchemaContentSupplier getContentSupplier( + final String tableName, + final OperatorContext context, + final List dataTypes, + final UserEntity userEntity, + final InformationSchemaTableScanNode node) + throws Exception { + return null; + } + + /** Returns the execution location for the table, or {@code null} if the table is not handled. */ + default List getTableLocation(final String tableName) { + return null; + } + + enum InformationSchemaTableLocation { + LOCAL_DATA_NODE, + ALL_READABLE_DATA_NODES + } +} diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/information/AdditionalInformationSchemaProviderRegistry.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/information/AdditionalInformationSchemaProviderRegistry.java new file mode 100644 index 0000000000000..0f3f22136c3ec --- /dev/null +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/information/AdditionalInformationSchemaProviderRegistry.java @@ -0,0 +1,43 @@ +/* + * 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.relational.information; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.ServiceLoader; + +public class AdditionalInformationSchemaProviderRegistry { + + private static final List PROVIDERS = new ArrayList<>(); + + static { + for (final AdditionalInformationSchemaProvider provider : + ServiceLoader.load(AdditionalInformationSchemaProvider.class)) { + PROVIDERS.add(provider); + } + } + + private AdditionalInformationSchemaProviderRegistry() {} + + public static List getProviders() { + return Collections.unmodifiableList(PROVIDERS); + } +} diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/optimizations/DataNodeLocationSupplierFactory.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/optimizations/DataNodeLocationSupplierFactory.java index 629518313e2e7..b4d8e8d7a4371 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/optimizations/DataNodeLocationSupplierFactory.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/planner/optimizations/DataNodeLocationSupplierFactory.java @@ -33,6 +33,8 @@ import org.apache.iotdb.db.protocol.client.ConfigNodeClientManager; import org.apache.iotdb.db.protocol.client.ConfigNodeInfo; import org.apache.iotdb.db.queryengine.common.DataNodeEndPoints; +import org.apache.iotdb.db.queryengine.plan.relational.information.AdditionalInformationSchemaProvider; +import org.apache.iotdb.db.queryengine.plan.relational.information.AdditionalInformationSchemaProviderRegistry; import org.apache.iotdb.rpc.TSStatusCode; import org.apache.thrift.TException; @@ -127,6 +129,13 @@ private static InformationSchemaTableDataNodeLocationSupplier getInstance() { @Override public List getDataNodeLocations(final String tableName) { + for (final AdditionalInformationSchemaProvider provider : + AdditionalInformationSchemaProviderRegistry.getProviders()) { + final List location = provider.getTableLocation(tableName); + if (location != null) { + return location; + } + } switch (tableName) { case InformationSchema.QUERIES: case InformationSchema.TABLE_DISK_USAGE: diff --git a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/schema/table/InformationSchema.java b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/schema/table/InformationSchema.java index 462d9da8eabe3..92f8950a386c6 100644 --- a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/schema/table/InformationSchema.java +++ b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/schema/table/InformationSchema.java @@ -32,7 +32,10 @@ import java.util.HashSet; import java.util.Locale; import java.util.Map; +import java.util.ServiceLoader; import java.util.Set; +import java.util.function.BiConsumer; +import java.util.function.Function; public class InformationSchema { public static final String INFORMATION_DATABASE = "information_schema"; @@ -494,6 +497,41 @@ public class InformationSchema { tablesThatSupportPushDownLimitOffset.add(TABLE_DISK_USAGE); } + // ==================== SPI extension point ==================== + + static { + for (final InformationSchemaExtension extension : + ServiceLoader.load(InformationSchemaExtension.class)) { + extension.registerAdditionalInformationSchemaTables(schemaTables::put); + extension.enhanceInformationSchemaTables(schemaTables::get); + } + } + + /** Extends the information schema by adding new tables or enhancing existing tables. */ + public interface InformationSchemaExtension { + + /** + * Registers extension-specific information schema tables that do not exist in the built-in + * schema. + * + * @param registerFunc accepts the table name and its schema definition + */ + default void registerAdditionalInformationSchemaTables( + final BiConsumer registerFunc) { + // Do nothing by default + } + + /** + * Enhances built-in information schema tables, for example by adding extension-specific + * columns. The provider returns the mutable schema definition of the requested table. + * + * @param tableProvider returns the schema definition for a built-in table name + */ + default void enhanceInformationSchemaTables(final Function tableProvider) { + // Do nothing by default + } + } + public static Map getSchemaTables() { return schemaTables; } From a9567a078876eae9005ca246e8b3a69598e18e67 Mon Sep 17 00:00:00 2001 From: shuwenwei Date: Thu, 17 Sep 2026 14:39:12 +0800 Subject: [PATCH 02/16] [ConfigNode] Inject additional persistence and snapshot processors via ConfigManagerContext --- .../confignode/manager/ConfigManager.java | 105 +++++++++++------- .../executor/ConfigPlanExecutor.java | 75 ++++++------- 2 files changed, 94 insertions(+), 86 deletions(-) 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 f86681f2bbec2..1c41618d90aff 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 @@ -67,6 +67,7 @@ import org.apache.iotdb.commons.schema.tree.AlterTimeSeriesOperationType; import org.apache.iotdb.commons.schema.ttl.TTLCache; import org.apache.iotdb.commons.service.metric.MetricService; +import org.apache.iotdb.commons.snapshot.SnapshotProcessor; import org.apache.iotdb.commons.subscription.meta.consumer.CommitProgressKeeper; import org.apache.iotdb.commons.subscription.meta.consumer.SubscriptionProgressSnapshot; import org.apache.iotdb.commons.utils.AuthUtils; @@ -377,58 +378,44 @@ public class ConfigManager implements IManager { public ConfigManager() throws IOException { // Build the persistence module - ClusterInfo clusterInfo = new ClusterInfo(); - NodeInfo nodeInfo = new NodeInfo(); - ClusterSchemaInfo clusterSchemaInfo = new ClusterSchemaInfo(); - PartitionInfo partitionInfo = new PartitionInfo(); - AuthorInfo authorInfo = createAuthorInfo(); - ProcedureInfo procedureInfo = new ProcedureInfo(this); - UDFInfo udfInfo = new UDFInfo(); - TriggerInfo triggerInfo = new TriggerInfo(); - CQInfo cqInfo = new CQInfo(); - ExternalServiceInfo externalServiceInfo = new ExternalServiceInfo(); - this.permissionManager = createPermissionManager(authorInfo); - PipeInfo pipeInfo = new PipeInfo(userName -> this.permissionManager.login4Pipe(userName, null)); - QuotaInfo quotaInfo = new QuotaInfo(); - TTLInfo ttlInfo = new TTLInfo(); - SubscriptionInfo subscriptionInfo = new SubscriptionInfo(); - + ConfigManagerContext context = createConfigManagerContext(); + context.clusterInfo = new ClusterInfo(); + context.nodeInfo = new NodeInfo(); + context.clusterSchemaInfo = new ClusterSchemaInfo(); + context.partitionInfo = new PartitionInfo(); + context.authorInfo = createAuthorInfo(); + context.procedureInfo = new ProcedureInfo(this); + context.udfInfo = new UDFInfo(); + context.triggerInfo = new TriggerInfo(); + context.cqInfo = new CQInfo(); + context.externalServiceInfo = new ExternalServiceInfo(); + this.permissionManager = createPermissionManager(context.authorInfo); + context.pipeInfo = new PipeInfo(userName -> this.permissionManager.login4Pipe(userName, null)); + context.quotaInfo = new QuotaInfo(); + context.ttlInfo = new TTLInfo(); + context.subscriptionInfo = new SubscriptionInfo(); + initAdditionalInfos(context); // Build state machine and executor - ConfigPlanExecutor executor = - new ConfigPlanExecutor( - clusterInfo, - nodeInfo, - clusterSchemaInfo, - partitionInfo, - authorInfo, - procedureInfo, - udfInfo, - triggerInfo, - cqInfo, - externalServiceInfo, - pipeInfo, - subscriptionInfo, - quotaInfo, - ttlInfo); + ConfigPlanExecutor executor = createConfigPlanExecutor(context); this.stateMachine = new ConfigRegionStateMachine(this, executor); // Build the manager module - this.clusterManager = new ClusterManager(this, clusterInfo); - setNodeManager(nodeInfo); + this.clusterManager = new ClusterManager(this, context.clusterInfo); + setNodeManager(context.nodeInfo); this.clusterSchemaManager = new ClusterSchemaManager( this, - clusterSchemaInfo, + context.clusterSchemaInfo, new ClusterSchemaQuotaStatistics( COMMON_CONF.getSeriesLimitThreshold(), COMMON_CONF.getDeviceLimitThreshold())); - this.partitionManager = new PartitionManager(this, partitionInfo); - this.procedureManager = createProcedureManager(procedureInfo); + this.partitionManager = new PartitionManager(this, context.partitionInfo); + this.procedureManager = createProcedureManager(context.procedureInfo); this.externalServiceManager = new ExternalServiceManager(this); - this.udfManager = new UDFManager(this, udfInfo); - this.triggerManager = new TriggerManager(this, triggerInfo); + this.udfManager = new UDFManager(this, context.udfInfo); + this.triggerManager = new TriggerManager(this, context.triggerInfo); this.cqManager = new CQManager(this); - this.pipeManager = new PipeManager(this, pipeInfo); - this.subscriptionManager = new SubscriptionManager(this, subscriptionInfo); + this.pipeManager = new PipeManager(this, context.pipeInfo); + this.subscriptionManager = new SubscriptionManager(this, context.subscriptionInfo); this.auditLogger = new CNAuditLogger(this); // 1. keep PipeManager initialization before LoadManager initialization, because @@ -438,8 +425,8 @@ public ConfigManager() throws IOException { setLoadManager(); this.retryFailedTasksThread = new RetryFailedTasksThread(this); - this.clusterQuotaManager = new ClusterQuotaManager(this, quotaInfo); - this.ttlManager = new TTLManager(this, ttlInfo); + this.clusterQuotaManager = new ClusterQuotaManager(this, context.quotaInfo); + this.ttlManager = new TTLManager(this, context.ttlInfo); } public void initConsensusManager() throws IOException { @@ -459,6 +446,16 @@ protected AuthorInfo createAuthorInfo() { return new AuthorInfo(); } + protected ConfigManagerContext createConfigManagerContext() { + return new ConfigManagerContext(); + } + + protected void initAdditionalInfos(final ConfigManagerContext context) {} + + protected ConfigPlanExecutor createConfigPlanExecutor(final ConfigManagerContext context) { + return new ConfigPlanExecutor(context); + } + protected void setNodeManager(NodeInfo nodeInfo) { this.nodeManager = new NodeManager(this, nodeInfo); } @@ -3468,4 +3465,26 @@ public DataSet registerAINode(TAINodeRegisterReq req) { public void setPermissionManager(final PermissionManager permissionManager) { this.permissionManager = permissionManager; } + + public static class ConfigManagerContext { + + public ClusterInfo clusterInfo; + public NodeInfo nodeInfo; + public ClusterSchemaInfo clusterSchemaInfo; + public PartitionInfo partitionInfo; + public AuthorInfo authorInfo; + public ProcedureInfo procedureInfo; + public UDFInfo udfInfo; + public TriggerInfo triggerInfo; + public CQInfo cqInfo; + public ExternalServiceInfo externalServiceInfo; + public PipeInfo pipeInfo; + public SubscriptionInfo subscriptionInfo; + public QuotaInfo quotaInfo; + public TTLInfo ttlInfo; + + public List getAdditionalInfoList() { + return Collections.emptyList(); + } + } } diff --git a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/persistence/executor/ConfigPlanExecutor.java b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/persistence/executor/ConfigPlanExecutor.java index 772f46baa3162..58bf86d6858e3 100644 --- a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/persistence/executor/ConfigPlanExecutor.java +++ b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/persistence/executor/ConfigPlanExecutor.java @@ -156,6 +156,7 @@ import org.apache.iotdb.confignode.consensus.response.partition.SchemaNodeManagementResp; import org.apache.iotdb.confignode.exception.physical.UnknownPhysicalPlanTypeException; import org.apache.iotdb.confignode.i18n.ConfigNodeMessages; +import org.apache.iotdb.confignode.manager.ConfigManager; import org.apache.iotdb.confignode.manager.externalservice.ExternalServiceInfo; import org.apache.iotdb.confignode.manager.pipe.agent.PipeConfigNodeAgent; import org.apache.iotdb.confignode.persistence.ClusterInfo; @@ -228,65 +229,53 @@ public class ConfigPlanExecutor { private final TTLInfo ttlInfo; - public ConfigPlanExecutor( - ClusterInfo clusterInfo, - NodeInfo nodeInfo, - ClusterSchemaInfo clusterSchemaInfo, - PartitionInfo partitionInfo, - AuthorInfo authorInfo, - ProcedureInfo procedureInfo, - UDFInfo udfInfo, - TriggerInfo triggerInfo, - CQInfo cqInfo, - ExternalServiceInfo externalServiceInfo, - PipeInfo pipeInfo, - SubscriptionInfo subscriptionInfo, - QuotaInfo quotaInfo, - TTLInfo ttlInfo) { + public ConfigPlanExecutor(final ConfigManager.ConfigManagerContext context) { this.snapshotProcessorList = new ArrayList<>(); - this.clusterInfo = clusterInfo; - this.snapshotProcessorList.add(clusterInfo); + this.clusterInfo = context.clusterInfo; + this.snapshotProcessorList.add(context.clusterInfo); - this.nodeInfo = nodeInfo; - this.snapshotProcessorList.add(nodeInfo); + this.nodeInfo = context.nodeInfo; + this.snapshotProcessorList.add(context.nodeInfo); - this.clusterSchemaInfo = clusterSchemaInfo; - this.snapshotProcessorList.add(clusterSchemaInfo); + this.clusterSchemaInfo = context.clusterSchemaInfo; + this.snapshotProcessorList.add(context.clusterSchemaInfo); - this.partitionInfo = partitionInfo; - this.snapshotProcessorList.add(partitionInfo); + this.partitionInfo = context.partitionInfo; + this.snapshotProcessorList.add(context.partitionInfo); - this.authorInfo = authorInfo; - this.snapshotProcessorList.add(authorInfo); + this.authorInfo = context.authorInfo; + this.snapshotProcessorList.add(context.authorInfo); - this.triggerInfo = triggerInfo; - this.snapshotProcessorList.add(triggerInfo); + this.triggerInfo = context.triggerInfo; + this.snapshotProcessorList.add(context.triggerInfo); - this.udfInfo = udfInfo; - this.snapshotProcessorList.add(udfInfo); + this.udfInfo = context.udfInfo; + this.snapshotProcessorList.add(context.udfInfo); - this.cqInfo = cqInfo; - this.snapshotProcessorList.add(cqInfo); + this.cqInfo = context.cqInfo; + this.snapshotProcessorList.add(context.cqInfo); - this.externalServiceInfo = externalServiceInfo; - this.snapshotProcessorList.add(externalServiceInfo); + this.externalServiceInfo = context.externalServiceInfo; + this.snapshotProcessorList.add(context.externalServiceInfo); - this.pipeInfo = pipeInfo; - this.snapshotProcessorList.add(pipeInfo); + this.pipeInfo = context.pipeInfo; + this.snapshotProcessorList.add(context.pipeInfo); - this.subscriptionInfo = subscriptionInfo; - this.snapshotProcessorList.add(subscriptionInfo); + this.subscriptionInfo = context.subscriptionInfo; + this.snapshotProcessorList.add(context.subscriptionInfo); - this.procedureInfo = procedureInfo; - this.snapshotProcessorList.add(procedureInfo); + this.procedureInfo = context.procedureInfo; + this.snapshotProcessorList.add(context.procedureInfo); - this.quotaInfo = quotaInfo; - this.snapshotProcessorList.add(quotaInfo); + this.quotaInfo = context.quotaInfo; + this.snapshotProcessorList.add(context.quotaInfo); - this.ttlInfo = ttlInfo; - this.snapshotProcessorList.add(ttlInfo); + this.ttlInfo = context.ttlInfo; + this.snapshotProcessorList.add(context.ttlInfo); + + this.snapshotProcessorList.addAll(context.getAdditionalInfoList()); this.snapshotProcessorList.add(PipeConfigNodeAgent.runtime().listener()); } From a35888795719f79e6566b1f1216f9fec8766297d Mon Sep 17 00:00:00 2001 From: shuwenwei Date: Thu, 17 Sep 2026 14:39:16 +0800 Subject: [PATCH 03/16] [RPC] Reserve LBAC status codes --- .../org/apache/iotdb/rpc/TSStatusCode.java | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/iotdb-client/service-rpc/src/main/java/org/apache/iotdb/rpc/TSStatusCode.java b/iotdb-client/service-rpc/src/main/java/org/apache/iotdb/rpc/TSStatusCode.java index 61431249eb371..e4c42b91c05d8 100644 --- a/iotdb-client/service-rpc/src/main/java/org/apache/iotdb/rpc/TSStatusCode.java +++ b/iotdb-client/service-rpc/src/main/java/org/apache/iotdb/rpc/TSStatusCode.java @@ -366,6 +366,28 @@ public enum TSStatusCode { EXTERNAL_SERVICE_INSTANCE_CREATE_ERROR(2303), CANNOT_DROP_BUILTIN_EXTERNAL_SERVICE(2304), CANNOT_DROP_RUNNING_EXTERNAL_SERVICE(2305), + + // LBAC + LBAC_ACCESS_DENIED(2400), + COMPONENT_NOT_EXISTS(2401), + COMPONENT_ALREADY_EXISTS(2402), + POLICY_NOT_EXISTS(2403), + POLICY_ALREADY_EXISTS(2404), + LABEL_NOT_EXISTS(2405), + LABEL_ALREADY_EXISTS(2406), + ELEMENT_NOT_EXISTS(2407), + ELEMENT_ALREADY_EXISTS(2408), + INVALID_COMPONENT_TYPE(2409), + INVALID_ELEMENT_CLAUSE(2410), + INVALID_COMMENT_TARGET(2411), + COMPONENT_IS_REFERENCED(2412), + POLICY_IS_REFERENCED(2413), + LABEL_IS_REFERENCED(2414), + POLICY_NOT_SET(2415), + POLICY_ALREADY_SET(2416), + POLICY_CONFLICT(2417), + LABEL_NOT_SET(2418), + INVALID_LABEL_VALUE(2419), ; private final int statusCode; From 29c06fb929ca6de4fdf22d07bb49638938ddaf1d Mon Sep 17 00:00:00 2001 From: shuwenwei Date: Thu, 17 Sep 2026 15:04:37 +0800 Subject: [PATCH 04/16] [LBAC] Add LBAC access control abstraction with allow-all default implementation --- .../iotdb/db/auth/AuthorityChecker.java | 14 ++++ .../db/lbac/AllowAllLBACAccessControl.java | 47 ++++++++++++ .../iotdb/db/lbac/ILBACAccessControl.java | 74 +++++++++++++++++++ .../iotdb/commons/i18n/LBACMessages.java | 28 +++++++ .../iotdb/commons/i18n/LBACMessages.java | 28 +++++++ .../lbac/LBACAccessDeniedException.java | 56 ++++++++++++++ .../commons/lbac/LBACRuntimeException.java | 33 +++++++++ .../iotdb/commons/lbac/RequiredLabels.java | 64 ++++++++++++++++ .../lbac/operation/LabelAccessType.java | 26 +++++++ 9 files changed, 370 insertions(+) create mode 100644 iotdb-core/datanode/src/main/java/org/apache/iotdb/db/lbac/AllowAllLBACAccessControl.java create mode 100644 iotdb-core/datanode/src/main/java/org/apache/iotdb/db/lbac/ILBACAccessControl.java create mode 100644 iotdb-core/node-commons/src/main/i18n/en/org/apache/iotdb/commons/i18n/LBACMessages.java create mode 100644 iotdb-core/node-commons/src/main/i18n/zh/org/apache/iotdb/commons/i18n/LBACMessages.java create mode 100644 iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/lbac/LBACAccessDeniedException.java create mode 100644 iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/lbac/LBACRuntimeException.java create mode 100644 iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/lbac/RequiredLabels.java create mode 100644 iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/lbac/operation/LabelAccessType.java diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/auth/AuthorityChecker.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/auth/AuthorityChecker.java index 0593db10840ba..9634992f0af5c 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/auth/AuthorityChecker.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/auth/AuthorityChecker.java @@ -41,6 +41,8 @@ import org.apache.iotdb.confignode.rpc.thrift.TUserResp; import org.apache.iotdb.db.audit.DNAuditLogger; import org.apache.iotdb.db.i18n.DataNodeMiscMessages; +import org.apache.iotdb.db.lbac.AllowAllLBACAccessControl; +import org.apache.iotdb.db.lbac.ILBACAccessControl; import org.apache.iotdb.db.pipe.source.dataregion.realtime.listener.PipeInsertionDataNodeListener; import org.apache.iotdb.db.protocol.session.IClientSession; import org.apache.iotdb.db.queryengine.common.header.DatasetHeader; @@ -109,6 +111,8 @@ public class AuthorityChecker { private static volatile AccessControl accessControl = new AccessControlImpl(new ITableAuthCheckerImpl(), new TreeAccessCheckVisitor()); + private static volatile ILBACAccessControl lbacAccessControl = new AllowAllLBACAccessControl(); + private AuthorityChecker() { // empty constructor } @@ -122,6 +126,16 @@ public static void setAccessControl(AccessControl accessControl) { AuthorityChecker.accessControl = accessControl; } + @SuppressWarnings("java:S100") + public static ILBACAccessControl getLBACAccessControl() { + return lbacAccessControl; + } + + @SuppressWarnings("java:S100") + public static void setLBACAccessControl(final ILBACAccessControl lbacAccessControl) { + AuthorityChecker.lbacAccessControl = lbacAccessControl; + } + public static void setSuperUser(String superUser) { SUPER_USER = superUser; } diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/lbac/AllowAllLBACAccessControl.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/lbac/AllowAllLBACAccessControl.java new file mode 100644 index 0000000000000..49e7c0d30ae58 --- /dev/null +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/lbac/AllowAllLBACAccessControl.java @@ -0,0 +1,47 @@ +/* + * 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.lbac; + +import org.apache.iotdb.commons.audit.IAuditEntity; +import org.apache.iotdb.commons.lbac.RequiredLabels; +import org.apache.iotdb.commons.queryengine.plan.relational.sql.ast.Statement; +import org.apache.iotdb.db.protocol.session.IClientSession; +import org.apache.iotdb.db.queryengine.plan.relational.metadata.Metadata; + +import java.util.Map; + +/** + * Open-source default implementation of {@link ILBACAccessControl} that allows all access. The + * commercial TimechoDB DataNode replaces this with its real LBAC implementation during startup. + */ +@SuppressWarnings("java:S100") +public class AllowAllLBACAccessControl implements ILBACAccessControl { + + @Override + public void checkCanAccess( + final Statement statement, + final Metadata metadata, + final IClientSession clientSession, + final IAuditEntity auditEntity) {} + + @Override + public void checkCanAccess( + final Map requiredLabelsByPolicy, final IAuditEntity auditEntity) {} +} diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/lbac/ILBACAccessControl.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/lbac/ILBACAccessControl.java new file mode 100644 index 0000000000000..070118a1ab0db --- /dev/null +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/lbac/ILBACAccessControl.java @@ -0,0 +1,74 @@ +/* + * 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.lbac; + +import org.apache.iotdb.commons.audit.IAuditEntity; +import org.apache.iotdb.commons.lbac.LBACAccessDeniedException; +import org.apache.iotdb.commons.lbac.RequiredLabels; +import org.apache.iotdb.commons.queryengine.plan.relational.sql.ast.Statement; +import org.apache.iotdb.db.protocol.session.IClientSession; +import org.apache.iotdb.db.queryengine.plan.relational.metadata.Metadata; + +import java.util.Map; + +/** + * LBAC (Label-Based Access Control) entry point, at the same level as RBAC's {@code AccessControl}. + * Performs label-level access checks on protected columns. + */ +@SuppressWarnings("java:S100") +public interface ILBACAccessControl { + + /** + * Performs a full LBAC access check for the given DDL statement. + * + *

Internally extracts the required column labels from the statement, loads user/role grants + * and LBAC metadata from cache, then compares subject labels against required object labels. + * + * @param statement the DDL statement to check + * @param metadata metadata handle for table schema lookups + * @param clientSession the client session for database resolution + * @param auditEntity the audit entity carrying the user identity + * @throws LBACAccessDeniedException if any required label comparison fails + */ + void checkCanAccess( + Statement statement, + Metadata metadata, + IClientSession clientSession, + IAuditEntity auditEntity) + throws LBACAccessDeniedException; + + /** + * Performs a full LBAC access check for explicitly-declared column label requirements, without + * going through the relational StatementAnalyzer (e.g. the tree-model Load TsFile path). + * + * @param requiredLabelsByPolicy required object labels grouped by policy name + * @param auditEntity the audit entity carrying the user identity + * @throws LBACAccessDeniedException + */ + void checkCanAccess(Map requiredLabelsByPolicy, IAuditEntity auditEntity) + throws LBACAccessDeniedException; + + /** + * Clears any LBAC-local caches. Invoked when the metadata lease is fenced and the DataNode drops + * its caches, so a recovery forces a fresh re-fetch from the ConfigNode. Defaults to a no-op for + * implementations without a local cache. + */ + default void clearCache() {} +} diff --git a/iotdb-core/node-commons/src/main/i18n/en/org/apache/iotdb/commons/i18n/LBACMessages.java b/iotdb-core/node-commons/src/main/i18n/en/org/apache/iotdb/commons/i18n/LBACMessages.java new file mode 100644 index 0000000000000..85453a946781c --- /dev/null +++ b/iotdb-core/node-commons/src/main/i18n/en/org/apache/iotdb/commons/i18n/LBACMessages.java @@ -0,0 +1,28 @@ +/* + * 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.commons.i18n; + +public final class LBACMessages { + + private LBACMessages() {} + + public static final String EXCEPTION_LBAC_CHECK_FAILED_FOR_USER_ARG_ARG_TO_LABEL_ARG_VALUE_ARG_UNDER_POLICY_ARG_USER_S_MERGED_GRANT_ARG_DOES_NOT_DOMINATE_REQUIRED_ARG_C18D4EA7 = "LBAC check failed for user '%s' %s to label '%s' (value: %s) under policy '%s': user's merged grant %s does not dominate required %s"; + public static final String EXCEPTION_LABELNAME_IS_NULL_856ABAE4 = "labelName is null"; +} diff --git a/iotdb-core/node-commons/src/main/i18n/zh/org/apache/iotdb/commons/i18n/LBACMessages.java b/iotdb-core/node-commons/src/main/i18n/zh/org/apache/iotdb/commons/i18n/LBACMessages.java new file mode 100644 index 0000000000000..ce7d66b9f1710 --- /dev/null +++ b/iotdb-core/node-commons/src/main/i18n/zh/org/apache/iotdb/commons/i18n/LBACMessages.java @@ -0,0 +1,28 @@ +/* + * 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.commons.i18n; + +public final class LBACMessages { + + private LBACMessages() {} + + public static final String EXCEPTION_LBAC_CHECK_FAILED_FOR_USER_ARG_ARG_TO_LABEL_ARG_VALUE_ARG_UNDER_POLICY_ARG_USER_S_MERGED_GRANT_ARG_DOES_NOT_DOMINATE_REQUIRED_ARG_C18D4EA7 = "用户 '%s' 的 %s 访问失败:标签 '%s'(值:%s)位于策略 '%s' 下,用户合并后的授权 %s 不支配所需值 %s"; + public static final String EXCEPTION_LABELNAME_IS_NULL_856ABAE4 = "labelName 为 null"; +} diff --git a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/lbac/LBACAccessDeniedException.java b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/lbac/LBACAccessDeniedException.java new file mode 100644 index 0000000000000..c025e4333fad1 --- /dev/null +++ b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/lbac/LBACAccessDeniedException.java @@ -0,0 +1,56 @@ +/* + * 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.commons.lbac; + +import org.apache.iotdb.commons.audit.IAuditEntity; +import org.apache.iotdb.commons.i18n.LBACMessages; +import org.apache.iotdb.commons.lbac.operation.LabelAccessType; +import org.apache.iotdb.rpc.TSStatusCode; + +import java.io.Serial; +import java.util.Set; + +/** Thrown when an LBAC label comparison denies access to a protected object. */ +@SuppressWarnings("java:S100") +public class LBACAccessDeniedException extends LBACRuntimeException { + + @Serial private static final long serialVersionUID = 1L; + + public LBACAccessDeniedException( + final IAuditEntity auditEntity, + final String policyName, + final String subjectValue, + final Set objectComponentValues, + final String objectLabelName, + final LabelAccessType accessType) { + super( + String.format( + LBACMessages + .EXCEPTION_LBAC_CHECK_FAILED_FOR_USER_ARG_ARG_TO_LABEL_ARG_VALUE_ARG_UNDER_POLICY_ARG_USER_S_MERGED_GRANT_ARG_DOES_NOT_DOMINATE_REQUIRED_ARG_C18D4EA7, + auditEntity.getUsername(), + accessType, + objectLabelName, + objectComponentValues, + policyName, + subjectValue, + objectComponentValues), + TSStatusCode.LBAC_ACCESS_DENIED); + } +} diff --git a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/lbac/LBACRuntimeException.java b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/lbac/LBACRuntimeException.java new file mode 100644 index 0000000000000..04235886f7d09 --- /dev/null +++ b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/lbac/LBACRuntimeException.java @@ -0,0 +1,33 @@ +/* + * 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.commons.lbac; + +import org.apache.iotdb.commons.exception.IoTDBRuntimeException; +import org.apache.iotdb.rpc.TSStatusCode; + +@SuppressWarnings("java:S100") +public class LBACRuntimeException extends IoTDBRuntimeException { + + private static final long serialVersionUID = 1L; + + public LBACRuntimeException(final String message, final TSStatusCode statusCode) { + super(message, statusCode.getStatusCode()); + } +} diff --git a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/lbac/RequiredLabels.java b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/lbac/RequiredLabels.java new file mode 100644 index 0000000000000..13d635597659e --- /dev/null +++ b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/lbac/RequiredLabels.java @@ -0,0 +1,64 @@ +/* + * 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.commons.lbac; + +import org.apache.iotdb.commons.i18n.LBACMessages; + +import java.util.HashSet; +import java.util.Objects; +import java.util.Set; + +/** The object-side LBAC requirement: protected column labels grouped by READ and WRITE. */ +public final class RequiredLabels { + + private final Set readLabels = new HashSet<>(); + private final Set writeLabels = new HashSet<>(); + + public RequiredLabels requireLabelWithReadAccess(final String labelName) { + Objects.requireNonNull(labelName, LBACMessages.EXCEPTION_LABELNAME_IS_NULL_856ABAE4); + readLabels.add(labelName); + return this; + } + + public RequiredLabels requireLabelWithWriteAccess(final String labelName) { + Objects.requireNonNull(labelName, LBACMessages.EXCEPTION_LABELNAME_IS_NULL_856ABAE4); + writeLabels.add(labelName); + return this; + } + + public RequiredLabels requireLabelWithAllAccess(final String labelName) { + Objects.requireNonNull(labelName, LBACMessages.EXCEPTION_LABELNAME_IS_NULL_856ABAE4); + readLabels.add(labelName); + writeLabels.add(labelName); + return this; + } + + public Set getReadLabels() { + return readLabels; + } + + public Set getWriteLabels() { + return writeLabels; + } + + public boolean isEmpty() { + return readLabels.isEmpty() && writeLabels.isEmpty(); + } +} diff --git a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/lbac/operation/LabelAccessType.java b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/lbac/operation/LabelAccessType.java new file mode 100644 index 0000000000000..fd8ce0fdbf68d --- /dev/null +++ b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/lbac/operation/LabelAccessType.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.commons.lbac.operation; + +public enum LabelAccessType { + READ, + WRITE, + ALL +} From b59462d40e0cbf67d84e60e9f284847eab096c47 Mon Sep 17 00:00:00 2001 From: shuwenwei Date: Thu, 17 Sep 2026 15:26:49 +0800 Subject: [PATCH 05/16] [Auth] Persist user/role profiles with a versioned extra segment region --- .../schema/CNPhysicalPlanGenerator.java | 33 ++++++++- .../auth/role/LocalFileRoleAccessor.java | 69 ++++++++++++++++++- .../auth/user/LocalFileUserAccessor.java | 6 +- 3 files changed, 103 insertions(+), 5 deletions(-) diff --git a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/persistence/schema/CNPhysicalPlanGenerator.java b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/persistence/schema/CNPhysicalPlanGenerator.java index e4366c45fc429..c2e0e08fe210d 100644 --- a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/persistence/schema/CNPhysicalPlanGenerator.java +++ b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/persistence/schema/CNPhysicalPlanGenerator.java @@ -21,6 +21,7 @@ import org.apache.iotdb.commons.auth.entity.PrivilegeModelType; import org.apache.iotdb.commons.auth.entity.PrivilegeType; +import org.apache.iotdb.commons.auth.role.LocalFileRoleAccessor.ExtraSegmentType; import org.apache.iotdb.commons.exception.IllegalPathException; import org.apache.iotdb.commons.path.PartialPath; import org.apache.iotdb.commons.schema.SchemaConstant; @@ -224,7 +225,7 @@ private void generateUserRolePhysicalPlan(final boolean isUser) { createUser.setPermissions(new HashSet<>()); createUser.setNodeNameList(new ArrayList<>()); planDeque.add(createUser); - if (tag == 2) { + if (tag >= 2) { final AuthorTreePlan updateUserMaxSession = new AuthorTreePlan(ConfigPhysicalPlanType.UpdateUserMaxSession); updateUserMaxSession.setMaxSessionPerUser(dataInputStream.readInt()); @@ -296,6 +297,10 @@ private void generateUserRolePhysicalPlan(final boolean isUser) { } } } + + if (tag >= 3) { + generateExtraSegmentPhysicalPlans(dataInputStream, user, isUser); + } } catch (IOException ioException) { logger.error( ManagerMessages.LOG_GOT_IOEXCEPTION_DESERIALIZE_USE_ROLE_FILE_TYPE_ARG_1B548759, @@ -307,6 +312,32 @@ private void generateUserRolePhysicalPlan(final boolean isUser) { } } + /** + * Reads the extra segment region appended after the RBAC privileges of a user/role profile file. + * Each segment is encoded as {@code [type: int32][length: int32][payload: bytes]} and dispatched + * by its type. No extra segment types are handled in this branch, so every payload is ignored, + * while segments of unknown types written by newer versions stay forward compatible. + */ + private void generateExtraSegmentPhysicalPlans( + final DataInputStream dataInputStream, final String granteeName, final boolean isUser) + throws IOException { + final int extraSegmentCount = dataInputStream.readInt(); + for (int i = 0; i < extraSegmentCount; i++) { + final ExtraSegmentType segmentType = ExtraSegmentType.fromType(dataInputStream.readInt()); + final int length = dataInputStream.readInt(); + final byte[] segmentData = new byte[length]; + dataInputStream.readFully(segmentData); + if (segmentType == null) { + continue; + } + switch (segmentType) { + default: + // No extra segment types are handled in this branch. + break; + } + } + } + private void generateGrantRolePhysicalPlan() { try (final DataInputStream roleInputStream = new DataInputStream(new BufferedInputStream((inputStream)))) { diff --git a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/auth/role/LocalFileRoleAccessor.java b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/auth/role/LocalFileRoleAccessor.java index df7d7f284271c..4f57760183da1 100644 --- a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/auth/role/LocalFileRoleAccessor.java +++ b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/auth/role/LocalFileRoleAccessor.java @@ -85,8 +85,34 @@ public class LocalFileRoleAccessor implements IEntityAccessor { protected final String entityDirPath; // It might be a good idea to use a Version number to control upgrade compatibility. - // Now it's version 1 - protected static final int VERSION = 2; + // Version 3 appends an extra segment region after the RBAC privileges. + protected static final int VERSION = 3; + + /** Types of the extra segments appended after the RBAC privileges in a profile file. */ + public enum ExtraSegmentType { + /** The subject's LBAC label grants and rule exemptions. */ + LBAC_GRANT_INFO(1); + + private final int type; + + ExtraSegmentType(final int type) { + this.type = type; + } + + public int getType() { + return type; + } + + /** Returns the segment type matching the given value, or {@code null} if unknown. */ + public static ExtraSegmentType fromType(final int type) { + for (final ExtraSegmentType segmentType : values()) { + if (segmentType.type == type) { + return segmentType; + } + } + return null; + } + } /** * Reused buffer for primitive types encoding/decoding, which aim to reduce memory fragments. Use @@ -151,6 +177,38 @@ protected void loadPrivileges(DataInputStream dataInputStream, Role role) role.setObjectPrivilegeMap(objectPrivilegeMap); } + /** + * Writes the extra segment region appended after the RBAC privileges. The region starts with the + * segment count. No extra segments are written by this branch, so the region is empty. + */ + protected void saveExtraSegments(BufferedOutputStream outputStream, Role role) + throws IOException { + IOUtils.writeInt(outputStream, 0, encodingBufferLocal); + } + + /** + * Reads the extra segment region appended after the RBAC privileges. Each segment is encoded as + * [type: int32][length: int32][payload: bytes]. This branch handles no segment type, so every + * payload is ignored while profile files written by newer versions can still be loaded. + */ + protected void loadExtraSegments(DataInputStream dataInputStream, Role role) throws IOException { + final int extraSegmentCount = dataInputStream.readInt(); + for (int i = 0; i < extraSegmentCount; i++) { + final ExtraSegmentType segmentType = ExtraSegmentType.fromType(dataInputStream.readInt()); + final int length = dataInputStream.readInt(); + final byte[] segmentData = new byte[length]; + dataInputStream.readFully(segmentData); + if (segmentType == null) { + continue; + } + switch (segmentType) { + default: + // No extra segment types are handled in this branch. + break; + } + } + } + protected void saveSessionPerUser(BufferedOutputStream outputStream, Role role) throws IOException { // Just used in LocalFileUserAccessor.java. @@ -218,10 +276,14 @@ public Role loadEntity(String entityName) throws IOException { loadPrivileges(dataInputStream, role); return role; } else { - assert tag == VERSION; + // tag >= 2: version 2 and version 3 share the same leading layout; version 3 additionally + // appends the extra segment region. entityName = IOUtils.readString(dataInputStream, STRING_ENCODING, strBufferLocal); Role role = new Role(entityName); loadPrivileges(dataInputStream, role); + if (tag >= 3) { + loadExtraSegments(dataInputStream, role); + } return role; } @@ -276,6 +338,7 @@ public void saveEntity(Role entity) throws IOException { saveEntityName(outputStream, entity); saveSessionPerUser(outputStream, entity); savePrivileges(outputStream, entity); + saveExtraSegments(outputStream, entity); outputStream.flush(); fileOutputStream.getFD().sync(); } catch (Exception e) { diff --git a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/auth/user/LocalFileUserAccessor.java b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/auth/user/LocalFileUserAccessor.java index 5ad0d08fc4257..bac866a5291b2 100644 --- a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/auth/user/LocalFileUserAccessor.java +++ b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/auth/user/LocalFileUserAccessor.java @@ -172,13 +172,17 @@ public User loadEntity(String entityName) throws IOException { user.setPassword(IOUtils.readString(dataInputStream, STRING_ENCODING, strBufferLocal)); loadPrivileges(dataInputStream, user); } else { - assert (tag == VERSION); + // tag >= 2: version 2 and version 3 share the same leading layout; version 3 additionally + // appends the extra segment region. user.setUserId(dataInputStream.readLong()); user.setName(IOUtils.readString(dataInputStream, STRING_ENCODING, strBufferLocal)); user.setPassword(IOUtils.readString(dataInputStream, STRING_ENCODING, strBufferLocal)); user.setMaxSessionPerUser(dataInputStream.readInt()); user.setMinSessionPerUser(dataInputStream.readInt()); loadPrivileges(dataInputStream, user); + if (tag >= 3) { + loadExtraSegments(dataInputStream, user); + } } File roleOfUser = checkFileAvailable(entityName, "_role"); From 2ee6391c382d658123e695642ed8ac4e0d18d673 Mon Sep 17 00:00:00 2001 From: shuwenwei Date: Thu, 17 Sep 2026 15:26:49 +0800 Subject: [PATCH 06/16] [LBAC] Clarify AllowAllLBACAccessControl javadoc --- .../org/apache/iotdb/db/lbac/AllowAllLBACAccessControl.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/lbac/AllowAllLBACAccessControl.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/lbac/AllowAllLBACAccessControl.java index 49e7c0d30ae58..77cbb11b96b1b 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/lbac/AllowAllLBACAccessControl.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/lbac/AllowAllLBACAccessControl.java @@ -28,8 +28,8 @@ import java.util.Map; /** - * Open-source default implementation of {@link ILBACAccessControl} that allows all access. The - * commercial TimechoDB DataNode replaces this with its real LBAC implementation during startup. + * Default implementation of {@link ILBACAccessControl} that allows all access. A DataNode may + * replace it with a real LBAC implementation during startup. */ @SuppressWarnings("java:S100") public class AllowAllLBACAccessControl implements ILBACAccessControl { From 0789228bff2796719a198541dbab78976a42c1da Mon Sep 17 00:00:00 2001 From: shuwenwei Date: Thu, 17 Sep 2026 16:03:14 +0800 Subject: [PATCH 07/16] [Auth] Remove unused Role/User serialize and deserialize --- .../apache/iotdb/db/auth/entity/RoleTest.java | 84 ------------------- .../apache/iotdb/db/auth/entity/UserTest.java | 53 ------------ .../iotdb/commons/auth/entity/Role.java | 64 -------------- .../iotdb/commons/auth/entity/User.java | 64 -------------- 4 files changed, 265 deletions(-) delete mode 100644 iotdb-core/datanode/src/test/java/org/apache/iotdb/db/auth/entity/RoleTest.java delete mode 100644 iotdb-core/datanode/src/test/java/org/apache/iotdb/db/auth/entity/UserTest.java diff --git a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/auth/entity/RoleTest.java b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/auth/entity/RoleTest.java deleted file mode 100644 index 270dd8af6895f..0000000000000 --- a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/auth/entity/RoleTest.java +++ /dev/null @@ -1,84 +0,0 @@ -/* - * 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.auth.entity; - -import org.apache.iotdb.commons.auth.entity.DatabasePrivilege; -import org.apache.iotdb.commons.auth.entity.PathPrivilege; -import org.apache.iotdb.commons.auth.entity.PrivilegeType; -import org.apache.iotdb.commons.auth.entity.Role; -import org.apache.iotdb.commons.auth.entity.TablePrivilege; -import org.apache.iotdb.commons.conf.IoTDBConstant; -import org.apache.iotdb.commons.exception.IllegalPathException; -import org.apache.iotdb.commons.path.PartialPath; - -import org.junit.Assert; -import org.junit.Test; - -import java.util.Collections; - -public class RoleTest { - - @Test - public void testRole_InitAndSerialize() throws IllegalPathException { - Role role = new Role("role"); - PathPrivilege pathPrivilege = new PathPrivilege(new PartialPath("root.ln")); - role.setPrivilegeList(Collections.singletonList(pathPrivilege)); - role.grantPathPrivilege(new PartialPath("root.ln"), PrivilegeType.READ_SCHEMA, true); - role.grantPathPrivilege(new PartialPath("root.ln"), PrivilegeType.READ_DATA, false); - - Assert.assertEquals( - "Role{name='role', pathPrivilegeList=[root.ln : " - + "READ_DATA READ_SCHEMA_with_grant_option], systemPrivilegeSet=[], " - + "AnyScopePrivilegeMap=[], objectPrivilegeSet={}}", - role.toString()); - Role role1 = new Role("role1"); - role1.deserialize(role.serialize()); - Assert.assertEquals( - "Role{name='role', pathPrivilegeList=[root.ln : " - + "READ_DATA READ_SCHEMA_with_grant_option], systemPrivilegeSet=[], " - + "AnyScopePrivilegeMap=[], objectPrivilegeSet={}}", - role1.toString()); - - Role admin = new Role("root"); - PartialPath rootPath = new PartialPath(IoTDBConstant.PATH_ROOT + ".**"); - PathPrivilege pathPri = new PathPrivilege(rootPath); - DatabasePrivilege databasePrivilege = new DatabasePrivilege("testDB"); - TablePrivilege tablePrivilege = new TablePrivilege("testTable"); - databasePrivilege.getTablePrivilegeMap().put("testTable", tablePrivilege); - for (PrivilegeType item : PrivilegeType.values()) { - if (item.isSystemPrivilege()) { - admin.getSysPrivilege().add(item); - admin.getSysPriGrantOpt().add(item); - } else if (item.isPathPrivilege()) { - pathPri.grantPrivilege(item, true); - } else if (item.isRelationalPrivilege()) { - databasePrivilege.grantDBPrivilege(item); - databasePrivilege.grantDBGrantOption(item); - databasePrivilege.grantTablePrivilege("testTable", item); - databasePrivilege.grantTableGrantOption("testTable", item); - admin.grantAnyScopePrivilege(item, true); - } - } - admin.getDBScopePrivilegeMap().put("testDB", databasePrivilege); - admin.getPathPrivilegeList().add(pathPri); - Assert.assertEquals( - "Role{name='root', pathPrivilegeList=[root.** : READ_DATA_with_grant_option WRITE_DATA_with_grant_option READ_SCHEMA_with_grant_option WRITE_SCHEMA_with_grant_option], systemPrivilegeSet=[USE_MODEL_with_grant_option, MAINTAIN_with_grant_option, EXTEND_TEMPLATE_with_grant_option, SYSTEM_with_grant_option, MANAGE_DATABASE_with_grant_option, MANAGE_USER_with_grant_option, USE_TRIGGER_with_grant_option, USE_CQ_with_grant_option, SECURITY_with_grant_option, USE_PIPE_with_grant_option, USE_UDF_with_grant_option, MANAGE_ROLE_with_grant_option, AUDIT_with_grant_option], AnyScopePrivilegeMap=[DELETE_with_grant_option, DROP_with_grant_option, ALTER_with_grant_option, CREATE_with_grant_option, SELECT_with_grant_option, INSERT_with_grant_option], objectPrivilegeSet={testDB=Database(testDB):{CREATE_with_grant_option,DROP_with_grant_option,ALTER_with_grant_option,SELECT_with_grant_option,INSERT_with_grant_option,DELETE_with_grant_option,; Tables: [ testTable(CREATE_with_grant_option,DROP_with_grant_option,ALTER_with_grant_option,SELECT_with_grant_option,INSERT_with_grant_option,DELETE_with_grant_option,)]}}}", - admin.toString()); - } -} diff --git a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/auth/entity/UserTest.java b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/auth/entity/UserTest.java deleted file mode 100644 index 82e7d8f15b181..0000000000000 --- a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/auth/entity/UserTest.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * 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.auth.entity; - -import org.apache.iotdb.commons.auth.entity.PathPrivilege; -import org.apache.iotdb.commons.auth.entity.PrivilegeType; -import org.apache.iotdb.commons.auth.entity.User; -import org.apache.iotdb.commons.exception.IllegalPathException; -import org.apache.iotdb.commons.path.PartialPath; - -import org.junit.Assert; -import org.junit.Test; - -import java.util.Collections; - -public class UserTest { - - @Test - public void testUser() throws IllegalPathException { - User user = new User("user", "password123456"); - PathPrivilege pathPrivilege = new PathPrivilege(new PartialPath("root.ln")); - user.setPrivilegeList(Collections.singletonList(pathPrivilege)); - user.setPathPrivileges( - new PartialPath("root.ln"), Collections.singleton(PrivilegeType.WRITE_DATA)); - Assert.assertEquals( - "User{id=-1, name='user', pathPrivilegeList=[root.ln : WRITE_DATA], " - + "sysPrivilegeSet=[], AnyScopePrivilegeMap=[], objectPrivilegeMap={}, roleList=[], isOpenIdUser=false}", - user.toString()); - User user1 = new User("user1", "password1"); - user1.deserialize(user.serialize()); - Assert.assertEquals( - "User{id=-1, name='user', pathPrivilegeList=[root.ln : WRITE_DATA], " - + "sysPrivilegeSet=[], AnyScopePrivilegeMap=[], objectPrivilegeMap={}, roleList=[], isOpenIdUser=false}", - user1.toString()); - Assert.assertEquals(user1, user); - } -} diff --git a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/auth/entity/Role.java b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/auth/entity/Role.java index e5296dca60cad..4c1a4baa78fe0 100644 --- a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/auth/entity/Role.java +++ b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/auth/entity/Role.java @@ -21,17 +21,12 @@ import org.apache.iotdb.commons.exception.MetadataException; import org.apache.iotdb.commons.path.PartialPath; import org.apache.iotdb.commons.utils.AuthUtils; -import org.apache.iotdb.commons.utils.SerializeUtils; import org.apache.iotdb.commons.utils.TestOnly; import org.apache.iotdb.confignode.rpc.thrift.TDBPrivilege; import org.apache.iotdb.confignode.rpc.thrift.TPathPrivilege; import org.apache.iotdb.confignode.rpc.thrift.TRoleResp; import org.apache.iotdb.confignode.rpc.thrift.TTablePrivilege; -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; @@ -621,65 +616,6 @@ public int hashCode() { objectPrivilegeMap); } - public ByteBuffer serialize() { - ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); - DataOutputStream dataOutputStream = new DataOutputStream(byteArrayOutputStream); - - SerializeUtils.serialize(name, dataOutputStream); - - try { - SerializeUtils.serializePrivilegeTypeSet(sysPrivilegeSet, dataOutputStream); - SerializeUtils.serializePrivilegeTypeSet(sysPriGrantOpt, dataOutputStream); - dataOutputStream.writeInt(pathPrivilegeList.size()); - for (PathPrivilege pathPrivilege : pathPrivilegeList) { - dataOutputStream.write(pathPrivilege.serialize().array()); - } - SerializeUtils.serializePrivilegeTypeSet(anyScopePrivilegeSet, dataOutputStream); - SerializeUtils.serializePrivilegeTypeSet(anyScopePrivilegeGrantOptSet, dataOutputStream); - dataOutputStream.writeInt(objectPrivilegeMap.size()); - for (Map.Entry item : objectPrivilegeMap.entrySet()) { - SerializeUtils.serialize(item.getKey(), dataOutputStream); - dataOutputStream.write(item.getValue().serialize().array()); - } - } catch (IOException e) { - // unreachable - } - - return ByteBuffer.wrap(byteArrayOutputStream.toByteArray()); - } - - public void deserialize(ByteBuffer buffer) { - name = SerializeUtils.deserializeString(buffer); - int sysPrivilegeSize = buffer.getInt(); - sysPrivilegeSet = new HashSet<>(); - for (int i = 0; i < sysPrivilegeSize; i++) { - sysPrivilegeSet.add(PrivilegeType.values()[buffer.getInt()]); - } - int sysPriGrantOptSize = buffer.getInt(); - sysPriGrantOpt = new HashSet<>(); - for (int i = 0; i < sysPriGrantOptSize; i++) { - sysPriGrantOpt.add(PrivilegeType.values()[buffer.getInt()]); - } - int privilegeListSize = buffer.getInt(); - pathPrivilegeList = new ArrayList<>(privilegeListSize); - for (int i = 0; i < privilegeListSize; i++) { - PathPrivilege pathPrivilege = new PathPrivilege(); - pathPrivilege.deserialize(buffer); - pathPrivilegeList.add(pathPrivilege); - } - - SerializeUtils.deserializePrivilegeTypeSet(anyScopePrivilegeSet, buffer); - SerializeUtils.deserializePrivilegeTypeSet(anyScopePrivilegeGrantOptSet, buffer); - - int objectPrivilegesSize = buffer.getInt(); - for (int i = 0; i < objectPrivilegesSize; i++) { - DatabasePrivilege databasePrivilege = new DatabasePrivilege(); - String objectName = SerializeUtils.deserializeString(buffer); - databasePrivilege.deserialize(buffer); - this.objectPrivilegeMap.put(objectName, databasePrivilege); - } - } - @Override public String toString() { return "Role{" diff --git a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/auth/entity/User.java b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/auth/entity/User.java index 0bebeaf8e0652..16575e505eb2d 100644 --- a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/auth/entity/User.java +++ b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/auth/entity/User.java @@ -18,18 +18,11 @@ */ package org.apache.iotdb.commons.auth.entity; -import org.apache.iotdb.commons.utils.SerializeUtils; import org.apache.iotdb.commons.utils.TestOnly; import org.apache.iotdb.confignode.rpc.thrift.TListUserInfo; import org.apache.iotdb.confignode.rpc.thrift.TUserResp; -import java.io.ByteArrayOutputStream; -import java.io.DataOutputStream; -import java.io.IOException; -import java.nio.ByteBuffer; -import java.util.ArrayList; import java.util.HashSet; -import java.util.List; import java.util.Objects; import java.util.Set; @@ -187,63 +180,6 @@ public int hashCode() { isOpenIdUser); } - @Override - public ByteBuffer serialize() { - ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); - DataOutputStream dataOutputStream = new DataOutputStream(byteArrayOutputStream); - - SerializeUtils.serialize(super.getName(), dataOutputStream); - SerializeUtils.serialize(password, dataOutputStream); - - try { - dataOutputStream.writeInt(super.getSysPrivilege().size()); - for (PrivilegeType item : super.getSysPrivilege()) { - dataOutputStream.writeInt(item.ordinal()); - } - dataOutputStream.writeInt(super.getSysPriGrantOpt().size()); - for (PrivilegeType item : super.getSysPriGrantOpt()) { - dataOutputStream.writeInt(item.ordinal()); - } - dataOutputStream.writeInt(super.getPathPrivilegeList().size()); - for (PathPrivilege pathPrivilege : super.getPathPrivilegeList()) { - dataOutputStream.write(pathPrivilege.serialize().array()); - } - } catch (IOException e) { - // unreachable - } - SerializeUtils.serializeStringList(new ArrayList<>(roleSet), dataOutputStream); - - return ByteBuffer.wrap(byteArrayOutputStream.toByteArray()); - } - - @Override - public void deserialize(ByteBuffer buffer) { - super.setName(SerializeUtils.deserializeString(buffer)); - password = SerializeUtils.deserializeString(buffer); - int systemPriSize = buffer.getInt(); - Set sysPri = new HashSet<>(); - for (int i = 0; i < systemPriSize; i++) { - sysPri.add(PrivilegeType.values()[buffer.getInt()]); - } - super.setSysPrivilegeSet(sysPri); - int sysPriGrantOptSize = buffer.getInt(); - Set grantOpt = new HashSet<>(); - for (int i = 0; i < sysPriGrantOptSize; i++) { - grantOpt.add(PrivilegeType.values()[buffer.getInt()]); - } - super.setSysPriGrantOpt(grantOpt); - - int privilegeListSize = buffer.getInt(); - List privilegeList = new ArrayList<>(privilegeListSize); - for (int i = 0; i < privilegeListSize; i++) { - PathPrivilege pathPrivilege = new PathPrivilege(); - pathPrivilege.deserialize(buffer); - privilegeList.add(pathPrivilege); - } - super.setPrivilegeList(privilegeList); - roleSet = new HashSet<>(SerializeUtils.deserializeStringList(buffer)); - } - /** * TestOnly, get the string representation of the user. * From 2d75ef3747fbed5f62fb23ff819076d9f1ae64df Mon Sep 17 00:00:00 2001 From: shuwenwei Date: Thu, 17 Sep 2026 16:03:18 +0800 Subject: [PATCH 08/16] [Query] Log LBAC access errors at info level --- .../main/java/org/apache/iotdb/db/utils/ErrorHandlingUtils.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/utils/ErrorHandlingUtils.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/utils/ErrorHandlingUtils.java index b6154b059d5e6..cff45b29b9a73 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/utils/ErrorHandlingUtils.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/utils/ErrorHandlingUtils.java @@ -116,6 +116,8 @@ public static TSStatus onQueryException(Exception e, String operation, TSStatusC if (status.getCode() == TSStatusCode.SQL_PARSE_ERROR.getStatusCode() || status.getCode() == TSStatusCode.SEMANTIC_ERROR.getStatusCode() || status.getCode() == TSStatusCode.NO_PERMISSION.getStatusCode() + || (status.getCode() >= TSStatusCode.LBAC_ACCESS_DENIED.getStatusCode() + && status.getCode() <= TSStatusCode.INVALID_LABEL_VALUE.getStatusCode()) || status.getCode() == TSStatusCode.ILLEGAL_PATH.getStatusCode() || status.getCode() == TSStatusCode.NUMERIC_VALUE_OUT_OF_RANGE.getStatusCode() || status.getCode() == TSStatusCode.DIVISION_BY_ZERO.getStatusCode() From 3f075d1f81a6eeccab6c12cd899c5b3c2d972ffb Mon Sep 17 00:00:00 2001 From: shuwenwei Date: Thu, 17 Sep 2026 16:09:06 +0800 Subject: [PATCH 09/16] [ConfigNode] Reserve snapshot file types and column-properties procedure ids --- .../confignode/persistence/schema/CNSnapshotFileType.java | 4 +++- .../iotdb/confignode/procedure/store/ProcedureType.java | 2 ++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/persistence/schema/CNSnapshotFileType.java b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/persistence/schema/CNSnapshotFileType.java index ddace413b6c61..3fbfde5e33744 100644 --- a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/persistence/schema/CNSnapshotFileType.java +++ b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/persistence/schema/CNSnapshotFileType.java @@ -28,7 +28,9 @@ public enum CNSnapshotFileType { ROLE((byte) 2), USER_ROLE((byte) 3), SCHEMA((byte) 4), - TTL((byte) 5); + TTL((byte) 5), + LBAC_COMPONENT((byte) 6), + LBAC_POLICY((byte) 7); private static final Map TYPE_SNAPSHOT_MAP = new HashMap<>(); 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 75299638b6027..7514d06bce48e 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 @@ -86,6 +86,7 @@ public enum ProcedureType { RENAME_VIEW_PROCEDURE((short) 764), ALTER_TABLE_COLUMN_DATATYPE_PROCEDURE((short) 765), + SET_TABLE_COLUMN_PROPERTIES_PROCEDURE((short) 767), /** AI Model */ @Deprecated // Since 2.0.6, all models are managed by AINode @@ -148,6 +149,7 @@ public enum ProcedureType { PIPE_ENRICHED_CREATE_TABLE_VIEW_PROCEDURE((short) 1420), PIPE_ENRICHED_ADD_VIEW_COLUMN_PROCEDURE((short) 1421), PIPE_ENRICHED_ALTER_COLUMN_DATATYPE_PROCEDURE((short) 1422), + PIPE_ENRICHED_SET_TABLE_COLUMN_PROPERTIES_PROCEDURE((short) 1424), PIPE_ENRICHED_DROP_VIEW_COLUMN_PROCEDURE((short) 143), PIPE_ENRICHED_DROP_VIEW_PROCEDURE((short) 144), PIPE_ENRICHED_SET_VIEW_PROPERTIES_PROCEDURE((short) 145), From 0bcc2135bb3715e509db431eb05c852f6f4417f8 Mon Sep 17 00:00:00 2001 From: shuwenwei Date: Fri, 18 Sep 2026 08:38:08 +0800 Subject: [PATCH 10/16] set table column properties --- .../consensus/request/ConfigPhysicalPlan.java | 4 + .../request/ConfigPhysicalPlanType.java | 1 + .../request/ConfigPhysicalPlanVisitor.java | 8 + .../table/SetTableColumnPropertiesPlan.java | 80 ++++++ .../confignode/manager/ConfigManager.java | 2 + .../confignode/manager/ProcedureManager.java | 21 ++ .../protocol/IoTDBConfigNodeReceiver.java | 19 ++ ...PipeConfigPhysicalPlanTSStatusVisitor.java | 7 + .../source/ConfigRegionListeningFilter.java | 2 + .../manager/schema/ClusterSchemaManager.java | 55 ++++ .../executor/ConfigPlanExecutor.java | 4 + .../persistence/schema/ClusterSchemaInfo.java | 11 + .../persistence/schema/ConfigMTree.java | 22 ++ .../table/AbstractSetPropertiesProcedure.java | 249 ++++++++++++++++++ .../SetTableColumnPropertiesProcedure.java | 107 ++++++++ .../table/SetTablePropertiesProcedure.java | 218 ++------------- .../procedure/store/ProcedureFactory.java | 9 + .../table/AlterOrDropTableOperationType.java | 5 +- 18 files changed, 624 insertions(+), 200 deletions(-) create mode 100644 iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/consensus/request/write/table/SetTableColumnPropertiesPlan.java create mode 100644 iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/procedure/impl/schema/table/AbstractSetPropertiesProcedure.java create mode 100644 iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/procedure/impl/schema/table/SetTableColumnPropertiesProcedure.java diff --git a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/consensus/request/ConfigPhysicalPlan.java b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/consensus/request/ConfigPhysicalPlan.java index 0d9ca912571a4..c70b8c12fcf93 100644 --- a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/consensus/request/ConfigPhysicalPlan.java +++ b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/consensus/request/ConfigPhysicalPlan.java @@ -118,6 +118,7 @@ import org.apache.iotdb.confignode.consensus.request.write.table.RollbackCreateTablePlan; import org.apache.iotdb.confignode.consensus.request.write.table.RollbackPreDeleteTablePlan; import org.apache.iotdb.confignode.consensus.request.write.table.SetTableColumnCommentPlan; +import org.apache.iotdb.confignode.consensus.request.write.table.SetTableColumnPropertiesPlan; import org.apache.iotdb.confignode.consensus.request.write.table.SetTableCommentPlan; import org.apache.iotdb.confignode.consensus.request.write.table.SetTablePropertiesPlan; import org.apache.iotdb.confignode.consensus.request.write.table.view.AddTableViewColumnPlan; @@ -430,6 +431,9 @@ public static ConfigPhysicalPlan create(final ByteBuffer buffer) throws IOExcept case SetTableProperties: plan = new SetTablePropertiesPlan(configPhysicalPlanType); break; + case SetTableColumnProperties: + plan = new SetTableColumnPropertiesPlan(configPhysicalPlanType); + break; case SetViewProperties: plan = new SetViewPropertiesPlan(); break; diff --git a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/consensus/request/ConfigPhysicalPlanType.java b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/consensus/request/ConfigPhysicalPlanType.java index 1be9518141483..dbc6988ba8782 100644 --- a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/consensus/request/ConfigPhysicalPlanType.java +++ b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/consensus/request/ConfigPhysicalPlanType.java @@ -233,6 +233,7 @@ public enum ConfigPhysicalPlanType { AlterColumnDataType((short) 878), PreAlterColumnDataType((short) 879), RollbackPreDeleteTable((short) 880), + SetTableColumnProperties((short) 881), /** Deprecated types for sync, restored them for upgrade. */ @Deprecated diff --git a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/consensus/request/ConfigPhysicalPlanVisitor.java b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/consensus/request/ConfigPhysicalPlanVisitor.java index 53e3c4cd37dfc..283aee3ae58d4 100644 --- a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/consensus/request/ConfigPhysicalPlanVisitor.java +++ b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/consensus/request/ConfigPhysicalPlanVisitor.java @@ -39,6 +39,7 @@ import org.apache.iotdb.confignode.consensus.request.write.table.RenameTableColumnPlan; import org.apache.iotdb.confignode.consensus.request.write.table.RenameTablePlan; import org.apache.iotdb.confignode.consensus.request.write.table.SetTableColumnCommentPlan; +import org.apache.iotdb.confignode.consensus.request.write.table.SetTableColumnPropertiesPlan; import org.apache.iotdb.confignode.consensus.request.write.table.SetTableCommentPlan; import org.apache.iotdb.confignode.consensus.request.write.table.SetTablePropertiesPlan; import org.apache.iotdb.confignode.consensus.request.write.table.view.AddTableViewColumnPlan; @@ -174,6 +175,8 @@ public R process(final ConfigPhysicalPlan plan, final C context) { return visitAddTableViewColumn((AddTableViewColumnPlan) plan, context); case SetTableProperties: return visitSetTableProperties((SetTablePropertiesPlan) plan, context); + case SetTableColumnProperties: + return visitSetTableColumnProperties((SetTableColumnPropertiesPlan) plan, context); case SetViewProperties: return visitSetViewProperties((SetViewPropertiesPlan) plan, context); case RenameTableColumn: @@ -462,6 +465,11 @@ public R visitSetTableProperties( return visitPlan(setTablePropertiesPlan, context); } + public R visitSetTableColumnProperties( + final SetTableColumnPropertiesPlan setTableColumnPropertiesPlan, final C context) { + return visitPlan(setTableColumnPropertiesPlan, context); + } + // Use set table properties by default public R visitSetViewProperties( final SetViewPropertiesPlan setViewPropertiesPlan, final C context) { diff --git a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/consensus/request/write/table/SetTableColumnPropertiesPlan.java b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/consensus/request/write/table/SetTableColumnPropertiesPlan.java new file mode 100644 index 0000000000000..89b18cafc842e --- /dev/null +++ b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/consensus/request/write/table/SetTableColumnPropertiesPlan.java @@ -0,0 +1,80 @@ +/* + * 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.consensus.request.write.table; + +import org.apache.iotdb.confignode.consensus.request.ConfigPhysicalPlanType; + +import org.apache.tsfile.utils.ReadWriteIOUtils; + +import java.io.DataOutputStream; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.Map; + +public class SetTableColumnPropertiesPlan extends AbstractTablePlan { + + private String columnName; + private Map properties; + private boolean isRollback; + + public SetTableColumnPropertiesPlan(final ConfigPhysicalPlanType type) { + super(type); + } + + public SetTableColumnPropertiesPlan( + final String database, + final String tableName, + final String columnName, + final Map properties, + final boolean isRollback) { + super(ConfigPhysicalPlanType.SetTableColumnProperties, database, tableName); + this.columnName = columnName; + this.properties = properties; + this.isRollback = isRollback; + } + + public String getColumnName() { + return columnName; + } + + public Map getProperties() { + return properties; + } + + public boolean isRollback() { + return isRollback; + } + + @Override + protected void serializeImpl(final DataOutputStream stream) throws IOException { + super.serializeImpl(stream); + ReadWriteIOUtils.write(columnName, stream); + ReadWriteIOUtils.write(properties, stream); + ReadWriteIOUtils.write(isRollback, stream); + } + + @Override + protected void deserializeImpl(final ByteBuffer buffer) throws IOException { + super.deserializeImpl(buffer); + this.columnName = ReadWriteIOUtils.readString(buffer); + this.properties = ReadWriteIOUtils.readMap(buffer); + this.isRollback = ReadWriteIOUtils.readBool(buffer); + } +} 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 1c41618d90aff..9b5e80a3e86ee 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 @@ -3314,6 +3314,8 @@ public TSStatus alterOrDropTable(final TAlterOrDropTableReq req) { return procedureManager.alterTableAddColumn(req); case SET_PROPERTIES: return procedureManager.alterTableSetProperties(req); + case SET_COLUMN_PROPERTIES: + return procedureManager.alterTableSetColumnProperties(req); case RENAME_COLUMN: return procedureManager.alterTableRenameColumn(req); case DROP_COLUMN: 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 50b003d0494d4..056c756f68349 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 @@ -113,6 +113,7 @@ import org.apache.iotdb.confignode.procedure.impl.schema.table.DropTableProcedure; import org.apache.iotdb.confignode.procedure.impl.schema.table.RenameTableColumnProcedure; import org.apache.iotdb.confignode.procedure.impl.schema.table.RenameTableProcedure; +import org.apache.iotdb.confignode.procedure.impl.schema.table.SetTableColumnPropertiesProcedure; import org.apache.iotdb.confignode.procedure.impl.schema.table.SetTablePropertiesProcedure; import org.apache.iotdb.confignode.procedure.impl.schema.table.view.AddViewColumnProcedure; import org.apache.iotdb.confignode.procedure.impl.schema.table.view.CreateTableViewProcedure; @@ -2401,6 +2402,25 @@ public TSStatus alterTableSetProperties(final TAlterOrDropTableReq req) { false)); } + public TSStatus alterTableSetColumnProperties(final TAlterOrDropTableReq req) { + final String columnName = ReadWriteIOUtils.readString(req.updateInfo); + final SetTableColumnPropertiesProcedure procedure = + new SetTableColumnPropertiesProcedure( + req.database, + req.tableName, + columnName, + req.queryId, + ReadWriteIOUtils.readMap(req.updateInfo), + false); + return executeWithoutDuplicate( + req.database, + null, + req.tableName, + req.queryId, + ProcedureType.SET_TABLE_COLUMN_PROPERTIES_PROCEDURE, + procedure); + } + public TSStatus alterTableRenameColumn(final TAlterOrDropTableReq req) { final boolean isView = req.isSetIsView() && req.isIsView(); return executeWithoutDuplicate( @@ -2652,6 +2672,7 @@ public Pair checkDuplicateTableTask( case ADD_VIEW_COLUMN_PROCEDURE: case SET_TABLE_PROPERTIES_PROCEDURE: case SET_VIEW_PROPERTIES_PROCEDURE: + case SET_TABLE_COLUMN_PROPERTIES_PROCEDURE: case RENAME_TABLE_COLUMN_PROCEDURE: case RENAME_VIEW_COLUMN_PROCEDURE: case DROP_TABLE_COLUMN_PROCEDURE: diff --git a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/pipe/receiver/protocol/IoTDBConfigNodeReceiver.java b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/pipe/receiver/protocol/IoTDBConfigNodeReceiver.java index 86f2320b344df..936e54e00dbd9 100644 --- a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/pipe/receiver/protocol/IoTDBConfigNodeReceiver.java +++ b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/pipe/receiver/protocol/IoTDBConfigNodeReceiver.java @@ -79,6 +79,7 @@ import org.apache.iotdb.confignode.consensus.request.write.table.RenameTableColumnPlan; import org.apache.iotdb.confignode.consensus.request.write.table.RenameTablePlan; import org.apache.iotdb.confignode.consensus.request.write.table.SetTableColumnCommentPlan; +import org.apache.iotdb.confignode.consensus.request.write.table.SetTableColumnPropertiesPlan; import org.apache.iotdb.confignode.consensus.request.write.table.SetTableCommentPlan; import org.apache.iotdb.confignode.consensus.request.write.table.SetTablePropertiesPlan; import org.apache.iotdb.confignode.consensus.request.write.table.view.AddTableViewColumnPlan; @@ -116,6 +117,7 @@ import org.apache.iotdb.confignode.procedure.impl.schema.table.DropTableProcedure; import org.apache.iotdb.confignode.procedure.impl.schema.table.RenameTableColumnProcedure; import org.apache.iotdb.confignode.procedure.impl.schema.table.RenameTableProcedure; +import org.apache.iotdb.confignode.procedure.impl.schema.table.SetTableColumnPropertiesProcedure; import org.apache.iotdb.confignode.procedure.impl.schema.table.SetTablePropertiesProcedure; import org.apache.iotdb.confignode.procedure.impl.schema.table.view.AddViewColumnProcedure; import org.apache.iotdb.confignode.procedure.impl.schema.table.view.CreateTableViewProcedure; @@ -572,6 +574,7 @@ private TSStatus checkPermission(final ConfigPhysicalPlan plan) throws IOExcepti case AddTableColumn: case AddViewColumn: case SetTableProperties: + case SetTableColumnProperties: case SetViewProperties: case CommitDeleteColumn: case CommitDeleteViewColumn: @@ -1034,6 +1037,22 @@ private TSStatus executePlan(final ConfigPhysicalPlan plan) throws ConsensusExce queryId, ((SetTablePropertiesPlan) plan).getProperties(), shouldMarkAsPipeRequest.get())); + case SetTableColumnProperties: + return configManager + .getProcedureManager() + .executeWithoutDuplicate( + ((SetTableColumnPropertiesPlan) plan).getDatabase(), + null, + ((SetTableColumnPropertiesPlan) plan).getTableName(), + queryId, + ProcedureType.SET_TABLE_COLUMN_PROPERTIES_PROCEDURE, + new SetTableColumnPropertiesProcedure( + ((SetTableColumnPropertiesPlan) plan).getDatabase(), + ((SetTableColumnPropertiesPlan) plan).getTableName(), + ((SetTableColumnPropertiesPlan) plan).getColumnName(), + queryId, + ((SetTableColumnPropertiesPlan) plan).getProperties(), + shouldMarkAsPipeRequest.get())); case SetViewProperties: return configManager .getProcedureManager() diff --git a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/pipe/receiver/visitor/PipeConfigPhysicalPlanTSStatusVisitor.java b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/pipe/receiver/visitor/PipeConfigPhysicalPlanTSStatusVisitor.java index b8607126e99f0..3b315b7ba3e51 100644 --- a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/pipe/receiver/visitor/PipeConfigPhysicalPlanTSStatusVisitor.java +++ b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/pipe/receiver/visitor/PipeConfigPhysicalPlanTSStatusVisitor.java @@ -42,6 +42,7 @@ import org.apache.iotdb.confignode.consensus.request.write.table.RenameTableColumnPlan; import org.apache.iotdb.confignode.consensus.request.write.table.RenameTablePlan; import org.apache.iotdb.confignode.consensus.request.write.table.SetTableColumnCommentPlan; +import org.apache.iotdb.confignode.consensus.request.write.table.SetTableColumnPropertiesPlan; import org.apache.iotdb.confignode.consensus.request.write.table.SetTableCommentPlan; import org.apache.iotdb.confignode.consensus.request.write.table.SetTablePropertiesPlan; import org.apache.iotdb.confignode.consensus.request.write.template.CommitSetSchemaTemplatePlan; @@ -536,6 +537,12 @@ public TSStatus visitSetTableProperties( return visitCommonTablePlan(setTablePropertiesPlan, context); } + @Override + public TSStatus visitSetTableColumnProperties( + final SetTableColumnPropertiesPlan setTableColumnPropertiesPlan, final TSStatus context) { + return visitCommonTablePlan(setTableColumnPropertiesPlan, context); + } + @Override public TSStatus visitCommitDeleteColumn( final CommitDeleteColumnPlan commitDeleteColumnPlan, final TSStatus context) { diff --git a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/pipe/source/ConfigRegionListeningFilter.java b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/pipe/source/ConfigRegionListeningFilter.java index ff2cdd5649f5b..30daf6c9a8854 100644 --- a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/pipe/source/ConfigRegionListeningFilter.java +++ b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/pipe/source/ConfigRegionListeningFilter.java @@ -124,6 +124,7 @@ public class ConfigRegionListeningFilter { Collections.unmodifiableList( Arrays.asList( ConfigPhysicalPlanType.SetTableProperties, + ConfigPhysicalPlanType.SetTableColumnProperties, ConfigPhysicalPlanType.SetViewProperties, ConfigPhysicalPlanType.SetTableComment, ConfigPhysicalPlanType.SetViewComment, @@ -287,6 +288,7 @@ static boolean shouldPlanBeListened(final ConfigPhysicalPlan plan) { case AddTableColumn: case AddViewColumn: case SetTableProperties: + case SetTableColumnProperties: case SetViewProperties: case SetTableComment: case SetViewComment: diff --git a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/schema/ClusterSchemaManager.java b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/schema/ClusterSchemaManager.java index 840be42d48c17..0e88a2712b997 100644 --- a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/schema/ClusterSchemaManager.java +++ b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/schema/ClusterSchemaManager.java @@ -1837,6 +1837,61 @@ public synchronized Pair updateTableProperties( return new Pair<>(RpcUtils.SUCCESS_STATUS, updatedTable); } + public synchronized Pair updateTableColumnProperties( + final String database, + final String tableName, + final String columnName, + final Map originalProperties, + final Map updatedProperties) + throws MetadataException { + final TsTable originalTable = getTableIfExists(database, tableName).orElse(null); + + if (Objects.isNull(originalTable)) { + return new Pair<>( + RpcUtils.getStatus( + TSStatusCode.TABLE_NOT_EXISTS, + String.format("Table '%s.%s' does not exist", database, tableName)), + null); + } + + final Optional> result = + checkTable4View(database, originalTable, false); + if (result.isPresent()) { + return result.get(); + } + + if (Objects.isNull(originalTable.getColumnSchema(columnName))) { + return new Pair<>( + RpcUtils.getStatus( + TSStatusCode.COLUMN_NOT_EXISTS, + String.format("Column '%s' does not exist", columnName)), + null); + } + + final TsTable updatedTable = new TsTable(originalTable); + final TsTableColumnSchema updatedColumn = updatedTable.getColumnSchema(columnName); + updatedProperties + .keySet() + .removeIf( + key -> Objects.equals(updatedProperties.get(key), updatedColumn.getProps().get(key))); + if (updatedProperties.isEmpty()) { + return new Pair<>(RpcUtils.SUCCESS_STATUS, null); + } + + for (final Map.Entry entry : updatedProperties.entrySet()) { + final String key = entry.getKey(); + final String value = entry.getValue(); + originalProperties.put(key, updatedColumn.getProps().get(key)); + if (Objects.nonNull(value)) { + updatedColumn.getProps().put(key, value); + } else { + updatedColumn.getProps().remove(key); + } + } + + return new Pair<>(RpcUtils.SUCCESS_STATUS, updatedTable); + } + private void invalidateLastCache(final String database) { final Map dataNodeLocationMap = getNodeManager().getRegisteredDataNodeLocations(); diff --git a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/persistence/executor/ConfigPlanExecutor.java b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/persistence/executor/ConfigPlanExecutor.java index 58bf86d6858e3..a09c283a48d35 100644 --- a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/persistence/executor/ConfigPlanExecutor.java +++ b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/persistence/executor/ConfigPlanExecutor.java @@ -136,6 +136,7 @@ import org.apache.iotdb.confignode.consensus.request.write.table.RollbackCreateTablePlan; import org.apache.iotdb.confignode.consensus.request.write.table.RollbackPreDeleteTablePlan; import org.apache.iotdb.confignode.consensus.request.write.table.SetTableColumnCommentPlan; +import org.apache.iotdb.confignode.consensus.request.write.table.SetTableColumnPropertiesPlan; import org.apache.iotdb.confignode.consensus.request.write.table.SetTableCommentPlan; import org.apache.iotdb.confignode.consensus.request.write.table.SetTablePropertiesPlan; import org.apache.iotdb.confignode.consensus.request.write.table.view.PreCreateTableViewPlan; @@ -588,6 +589,9 @@ public TSStatus executeNonQueryPlan(ConfigPhysicalPlan physicalPlan) case SetTableProperties: case SetViewProperties: return clusterSchemaInfo.setTableProperties((SetTablePropertiesPlan) physicalPlan); + case SetTableColumnProperties: + return clusterSchemaInfo.setTableColumnProperties( + (SetTableColumnPropertiesPlan) physicalPlan); case PreDeleteColumn: case PreDeleteViewColumn: return clusterSchemaInfo.preDeleteColumn((PreDeleteColumnPlan) physicalPlan); diff --git a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/persistence/schema/ClusterSchemaInfo.java b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/persistence/schema/ClusterSchemaInfo.java index 1b9c880cd6a08..3e900eca55c18 100644 --- a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/persistence/schema/ClusterSchemaInfo.java +++ b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/persistence/schema/ClusterSchemaInfo.java @@ -69,6 +69,7 @@ import org.apache.iotdb.confignode.consensus.request.write.table.RollbackCreateTablePlan; import org.apache.iotdb.confignode.consensus.request.write.table.RollbackPreDeleteTablePlan; import org.apache.iotdb.confignode.consensus.request.write.table.SetTableColumnCommentPlan; +import org.apache.iotdb.confignode.consensus.request.write.table.SetTableColumnPropertiesPlan; import org.apache.iotdb.confignode.consensus.request.write.table.SetTableCommentPlan; import org.apache.iotdb.confignode.consensus.request.write.table.SetTablePropertiesPlan; import org.apache.iotdb.confignode.consensus.request.write.table.view.PreCreateTableViewPlan; @@ -1593,6 +1594,16 @@ public TSStatus setTableProperties(final SetTablePropertiesPlan plan) { plan.getProperties())); } + public TSStatus setTableColumnProperties(final SetTableColumnPropertiesPlan plan) { + return executeWithLock( + () -> + tableModelMTree.setTableColumnProperties( + getQualifiedDatabasePartialPath(plan.getDatabase()), + plan.getTableName(), + plan.getColumnName(), + plan.getProperties())); + } + public TSStatus preDeleteColumn(final PreDeleteColumnPlan plan) { databaseReadWriteLock.writeLock().lock(); try { diff --git a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/persistence/schema/ConfigMTree.java b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/persistence/schema/ConfigMTree.java index 12f6a2baaa186..53536aa361cfd 100644 --- a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/persistence/schema/ConfigMTree.java +++ b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/persistence/schema/ConfigMTree.java @@ -980,6 +980,28 @@ public void setTableProperties( }); } + public void setTableColumnProperties( + final PartialPath database, + final String tableName, + final String columnName, + final Map properties) + throws MetadataException { + final TsTable table = getTableNode(database, tableName).getTable(); + final TsTableColumnSchema columnSchema = table.getColumnSchema(columnName); + if (Objects.isNull(columnSchema)) { + throw new ColumnNotExistsException( + PathUtils.unQualifyDatabaseName(database.getFullPath()), tableName, columnName); + } + properties.forEach( + (key, value) -> { + if (Objects.nonNull(value)) { + columnSchema.getProps().put(key, value); + } else { + columnSchema.getProps().remove(key); + } + }); + } + // Return true if removed column is an attribute column // false if measurement column public boolean preDeleteColumn( diff --git a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/procedure/impl/schema/table/AbstractSetPropertiesProcedure.java b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/procedure/impl/schema/table/AbstractSetPropertiesProcedure.java new file mode 100644 index 0000000000000..be6f67007a3db --- /dev/null +++ b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/procedure/impl/schema/table/AbstractSetPropertiesProcedure.java @@ -0,0 +1,249 @@ +/* + * 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.table; + +import org.apache.iotdb.common.rpc.thrift.TSStatus; +import org.apache.iotdb.commons.exception.IoTDBException; +import org.apache.iotdb.commons.exception.MetadataException; +import org.apache.iotdb.commons.schema.table.TsTable; +import org.apache.iotdb.confignode.consensus.request.ConfigPhysicalPlan; +import org.apache.iotdb.confignode.i18n.ProcedureMessages; +import org.apache.iotdb.confignode.procedure.env.ConfigNodeProcedureEnv; +import org.apache.iotdb.confignode.procedure.exception.ProcedureException; +import org.apache.iotdb.confignode.procedure.state.schema.SetTablePropertiesState; +import org.apache.iotdb.rpc.TSStatusCode; + +import org.apache.tsfile.utils.Pair; +import org.apache.tsfile.utils.ReadWriteIOUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.DataOutputStream; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; + +import static org.apache.iotdb.confignode.procedure.state.schema.SetTablePropertiesState.COMMIT_RELEASE; +import static org.apache.iotdb.confignode.procedure.state.schema.SetTablePropertiesState.PRE_RELEASE; +import static org.apache.iotdb.confignode.procedure.state.schema.SetTablePropertiesState.SET_PROPERTIES; +import static org.apache.iotdb.confignode.procedure.state.schema.SetTablePropertiesState.VALIDATE_TABLE; + +public abstract class AbstractSetPropertiesProcedure + extends AbstractAlterOrDropTableProcedure { + + private static final Logger LOGGER = + LoggerFactory.getLogger(AbstractSetPropertiesProcedure.class); + + protected Map originalProperties = new HashMap<>(); + protected Map updatedProperties; + + protected AbstractSetPropertiesProcedure(final boolean isGeneratedByPipe) { + super(isGeneratedByPipe); + } + + protected AbstractSetPropertiesProcedure( + final String database, + final String tableName, + final String queryId, + final Map properties, + final boolean isGeneratedByPipe) { + super(database, tableName, queryId, isGeneratedByPipe); + this.updatedProperties = properties; + } + + @Override + protected Flow executeFromState( + final ConfigNodeProcedureEnv env, final SetTablePropertiesState state) + throws InterruptedException { + final long startTime = System.currentTimeMillis(); + try { + switch (state) { + case VALIDATE_TABLE: + validateTable(env); + LOGGER.info( + ProcedureMessages.VALIDATE_TABLE_FOR_TABLE_WHEN_SETTING_PROPERTIES, + database, + tableName); + if (!isFailed() && Objects.isNull(table)) { + LOGGER.info( + ProcedureMessages.THE_UPDATED_TABLE_HAS_THE_SAME_PROPERTIES_WITH_THE_ORIGINAL); + return Flow.NO_MORE_STATE; + } + break; + case PRE_RELEASE: + preRelease(env); + LOGGER.info( + ProcedureMessages.PRE_RELEASE_INFO_FOR_TABLE_WHEN_SETTING_PROPERTIES, + database, + tableName); + break; + case SET_PROPERTIES: + setProperties(env); + LOGGER.info(ProcedureMessages.SET_PROPERTIES_TO_TABLE, database, tableName); + break; + case COMMIT_RELEASE: + commitRelease(env); + LOGGER.info( + ProcedureMessages.COMMIT_RELEASE_INFO_OF_TABLE_WHEN_SETTING_PROPERTIES, + database, + tableName); + return Flow.NO_MORE_STATE; + default: + setFailure( + new ProcedureException(ProcedureMessages.UNRECOGNIZED_ADDTABLECOLUMNSTATE + state)); + return Flow.NO_MORE_STATE; + } + return Flow.HAS_MORE_STATE; + } finally { + LOGGER.info( + ProcedureMessages.SETTABLEPROPERTIES_COSTS_MS, + database, + tableName, + state, + (System.currentTimeMillis() - startTime)); + } + } + + public void validateTable(final ConfigNodeProcedureEnv env) { + try { + final Pair result = updateProperties(env); + final TSStatus status = result.getLeft(); + if (status.getCode() != TSStatusCode.SUCCESS_STATUS.getStatusCode()) { + setFailure(new ProcedureException(new IoTDBException(status))); + return; + } + table = result.getRight(); + setNextState(PRE_RELEASE); + } catch (final MetadataException e) { + setFailure(new ProcedureException(e)); + } + } + + protected abstract Pair updateProperties(ConfigNodeProcedureEnv env) + throws MetadataException; + + @Override + protected void preRelease(final ConfigNodeProcedureEnv env) { + super.preRelease(env); + setNextState(SET_PROPERTIES); + } + + private void setProperties(final ConfigNodeProcedureEnv env) { + final TSStatus status = + env.getConfigManager() + .getClusterSchemaManager() + .executePlan(createSetPropertiesPlan(updatedProperties, false), isGeneratedByPipe); + if (status.getCode() != TSStatusCode.SUCCESS_STATUS.getStatusCode()) { + setFailure(new ProcedureException(new IoTDBException(status))); + } else { + setNextState(COMMIT_RELEASE); + } + } + + protected abstract ConfigPhysicalPlan createSetPropertiesPlan( + Map properties, boolean isRollback); + + @Override + protected void rollbackState( + final ConfigNodeProcedureEnv env, final SetTablePropertiesState state) + throws IOException, InterruptedException, ProcedureException { + final long startTime = System.currentTimeMillis(); + try { + switch (state) { + case PRE_RELEASE: + LOGGER.info( + ProcedureMessages.START_ROLLBACK_PRE_RELEASE_INFO_FOR_TABLE_WHEN_SETTING_PROPERTIES, + database, + table.getTableName()); + rollbackPreRelease(env); + break; + case SET_PROPERTIES: + LOGGER.info( + ProcedureMessages.START_ROLLBACK_SET_PROPERTIES_TO_TABLE, + database, + table.getTableName()); + rollbackSetProperties(env); + break; + } + } finally { + LOGGER.info( + ProcedureMessages.ROLLBACK_SETTABLEPROPERTIES_COSTS_MS, + state, + (System.currentTimeMillis() - startTime)); + } + } + + private void rollbackSetProperties(final ConfigNodeProcedureEnv env) { + if (table == null) { + return; + } + final TSStatus status = + env.getConfigManager() + .getClusterSchemaManager() + .executePlan(createSetPropertiesPlan(originalProperties, true), isGeneratedByPipe); + if (status.getCode() != TSStatusCode.SUCCESS_STATUS.getStatusCode()) { + setFailure(new ProcedureException(new IoTDBException(status))); + } + } + + @Override + protected SetTablePropertiesState getState(final int stateId) { + return SetTablePropertiesState.values()[stateId]; + } + + @Override + protected int getStateId(final SetTablePropertiesState state) { + return state.ordinal(); + } + + @Override + protected SetTablePropertiesState getInitialState() { + return VALIDATE_TABLE; + } + + protected void innerSerialize(final DataOutputStream stream) throws IOException { + super.serialize(stream); + serializeProperties(stream); + } + + protected void serializeProperties(final DataOutputStream stream) throws IOException { + ReadWriteIOUtils.write(originalProperties, stream); + ReadWriteIOUtils.write(updatedProperties, stream); + } + + protected void deserializeProperties(final ByteBuffer byteBuffer) { + this.originalProperties = ReadWriteIOUtils.readMap(byteBuffer); + this.updatedProperties = ReadWriteIOUtils.readMap(byteBuffer); + } + + @Override + public boolean equals(final Object o) { + return super.equals(o) + && Objects.equals( + updatedProperties, ((AbstractSetPropertiesProcedure) o).updatedProperties); + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), updatedProperties); + } +} diff --git a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/procedure/impl/schema/table/SetTableColumnPropertiesProcedure.java b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/procedure/impl/schema/table/SetTableColumnPropertiesProcedure.java new file mode 100644 index 0000000000000..996b192e34311 --- /dev/null +++ b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/procedure/impl/schema/table/SetTableColumnPropertiesProcedure.java @@ -0,0 +1,107 @@ +/* + * 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.table; + +import org.apache.iotdb.common.rpc.thrift.TSStatus; +import org.apache.iotdb.commons.exception.MetadataException; +import org.apache.iotdb.commons.schema.table.TsTable; +import org.apache.iotdb.confignode.consensus.request.ConfigPhysicalPlan; +import org.apache.iotdb.confignode.consensus.request.write.table.SetTableColumnPropertiesPlan; +import org.apache.iotdb.confignode.procedure.env.ConfigNodeProcedureEnv; +import org.apache.iotdb.confignode.procedure.store.ProcedureType; + +import org.apache.tsfile.utils.Pair; +import org.apache.tsfile.utils.ReadWriteIOUtils; + +import java.io.DataOutputStream; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.Map; +import java.util.Objects; + +public class SetTableColumnPropertiesProcedure extends AbstractSetPropertiesProcedure { + + private String columnName; + + public SetTableColumnPropertiesProcedure(final boolean isGeneratedByPipe) { + super(isGeneratedByPipe); + } + + public SetTableColumnPropertiesProcedure( + final String database, + final String tableName, + final String columnName, + final String queryId, + final Map properties, + final boolean isGeneratedByPipe) { + super(database, tableName, queryId, properties, isGeneratedByPipe); + this.columnName = columnName; + } + + @Override + protected Pair updateProperties(final ConfigNodeProcedureEnv env) + throws MetadataException { + return env.getConfigManager() + .getClusterSchemaManager() + .updateTableColumnProperties( + database, tableName, columnName, originalProperties, updatedProperties); + } + + @Override + protected ConfigPhysicalPlan createSetPropertiesPlan( + final Map properties, final boolean isRollback) { + return new SetTableColumnPropertiesPlan( + database, tableName, columnName, properties, isRollback); + } + + @Override + protected String getActionMessage() { + return "set table column properties"; + } + + @Override + public void serialize(final DataOutputStream stream) throws IOException { + stream.writeShort( + isGeneratedByPipe + ? ProcedureType.PIPE_ENRICHED_SET_TABLE_COLUMN_PROPERTIES_PROCEDURE.getTypeCode() + : ProcedureType.SET_TABLE_COLUMN_PROPERTIES_PROCEDURE.getTypeCode()); + super.serialize(stream); + ReadWriteIOUtils.write(columnName, stream); + serializeProperties(stream); + } + + @Override + public void deserialize(final ByteBuffer byteBuffer) { + super.deserialize(byteBuffer); + this.columnName = ReadWriteIOUtils.readString(byteBuffer); + deserializeProperties(byteBuffer); + } + + @Override + public boolean equals(final Object o) { + return super.equals(o) + && Objects.equals(columnName, ((SetTableColumnPropertiesProcedure) o).columnName); + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), columnName); + } +} diff --git a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/procedure/impl/schema/table/SetTablePropertiesProcedure.java b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/procedure/impl/schema/table/SetTablePropertiesProcedure.java index 27e75e6c28c26..8cf3026d04356 100644 --- a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/procedure/impl/schema/table/SetTablePropertiesProcedure.java +++ b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/procedure/impl/schema/table/SetTablePropertiesProcedure.java @@ -20,43 +20,23 @@ package org.apache.iotdb.confignode.procedure.impl.schema.table; import org.apache.iotdb.common.rpc.thrift.TSStatus; -import org.apache.iotdb.commons.exception.IoTDBException; import org.apache.iotdb.commons.exception.MetadataException; import org.apache.iotdb.commons.schema.table.TsTable; +import org.apache.iotdb.confignode.consensus.request.ConfigPhysicalPlan; import org.apache.iotdb.confignode.consensus.request.write.table.SetTablePropertiesPlan; import org.apache.iotdb.confignode.consensus.request.write.table.view.SetViewPropertiesPlan; -import org.apache.iotdb.confignode.i18n.ProcedureMessages; import org.apache.iotdb.confignode.procedure.env.ConfigNodeProcedureEnv; -import org.apache.iotdb.confignode.procedure.exception.ProcedureException; import org.apache.iotdb.confignode.procedure.impl.schema.table.view.SetViewPropertiesProcedure; -import org.apache.iotdb.confignode.procedure.state.schema.SetTablePropertiesState; import org.apache.iotdb.confignode.procedure.store.ProcedureType; -import org.apache.iotdb.rpc.TSStatusCode; import org.apache.tsfile.utils.Pair; -import org.apache.tsfile.utils.ReadWriteIOUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import java.io.DataOutputStream; import java.io.IOException; import java.nio.ByteBuffer; -import java.util.HashMap; import java.util.Map; -import java.util.Objects; -import static org.apache.iotdb.confignode.procedure.state.schema.SetTablePropertiesState.COMMIT_RELEASE; -import static org.apache.iotdb.confignode.procedure.state.schema.SetTablePropertiesState.PRE_RELEASE; -import static org.apache.iotdb.confignode.procedure.state.schema.SetTablePropertiesState.SET_PROPERTIES; -import static org.apache.iotdb.confignode.procedure.state.schema.SetTablePropertiesState.VALIDATE_TABLE; - -public class SetTablePropertiesProcedure - extends AbstractAlterOrDropTableProcedure { - - private static final Logger LOGGER = LoggerFactory.getLogger(SetTablePropertiesProcedure.class); - - private Map originalProperties = new HashMap<>(); - private Map updatedProperties; +public class SetTablePropertiesProcedure extends AbstractSetPropertiesProcedure { public SetTablePropertiesProcedure(final boolean isGeneratedByPipe) { super(isGeneratedByPipe); @@ -68,106 +48,28 @@ public SetTablePropertiesProcedure( final String queryId, final Map properties, final boolean isGeneratedByPipe) { - super(database, tableName, queryId, isGeneratedByPipe); - this.updatedProperties = properties; + super(database, tableName, queryId, properties, isGeneratedByPipe); } @Override - protected Flow executeFromState( - final ConfigNodeProcedureEnv env, final SetTablePropertiesState state) - throws InterruptedException { - final long startTime = System.currentTimeMillis(); - try { - switch (state) { - case VALIDATE_TABLE: - validateTable(env); - LOGGER.info( - ProcedureMessages.VALIDATE_TABLE_FOR_TABLE_WHEN_SETTING_PROPERTIES, - database, - tableName); - if (!isFailed() && Objects.isNull(table)) { - LOGGER.info( - ProcedureMessages.THE_UPDATED_TABLE_HAS_THE_SAME_PROPERTIES_WITH_THE_ORIGINAL); - return Flow.NO_MORE_STATE; - } - break; - case PRE_RELEASE: - preRelease(env); - LOGGER.info( - ProcedureMessages.PRE_RELEASE_INFO_FOR_TABLE_WHEN_SETTING_PROPERTIES, - database, - tableName); - break; - case SET_PROPERTIES: - setProperties(env); - LOGGER.info(ProcedureMessages.SET_PROPERTIES_TO_TABLE, database, tableName); - break; - case COMMIT_RELEASE: - commitRelease(env); - LOGGER.info( - ProcedureMessages.COMMIT_RELEASE_INFO_OF_TABLE_WHEN_SETTING_PROPERTIES, - database, - tableName); - return Flow.NO_MORE_STATE; - default: - setFailure( - new ProcedureException(ProcedureMessages.UNRECOGNIZED_ADDTABLECOLUMNSTATE + state)); - return Flow.NO_MORE_STATE; - } - return Flow.HAS_MORE_STATE; - } finally { - LOGGER.info( - ProcedureMessages.SETTABLEPROPERTIES_COSTS_MS, - database, - tableName, - state, - (System.currentTimeMillis() - startTime)); - } - } - - private void validateTable(final ConfigNodeProcedureEnv env) { - try { - final Pair result = - env.getConfigManager() - .getClusterSchemaManager() - .updateTableProperties( - database, - tableName, - originalProperties, - updatedProperties, - this instanceof SetViewPropertiesProcedure); - final TSStatus status = result.getLeft(); - if (status.getCode() != TSStatusCode.SUCCESS_STATUS.getStatusCode()) { - setFailure(new ProcedureException(new IoTDBException(status))); - return; - } - table = result.getRight(); - setNextState(PRE_RELEASE); - } catch (final MetadataException e) { - setFailure(new ProcedureException(e)); - } + protected Pair updateProperties(final ConfigNodeProcedureEnv env) + throws MetadataException { + return env.getConfigManager() + .getClusterSchemaManager() + .updateTableProperties( + database, + tableName, + originalProperties, + updatedProperties, + this instanceof SetViewPropertiesProcedure); } @Override - protected void preRelease(final ConfigNodeProcedureEnv env) { - super.preRelease(env); - setNextState(SET_PROPERTIES); - } - - private void setProperties(final ConfigNodeProcedureEnv env) { - final TSStatus status = - env.getConfigManager() - .getClusterSchemaManager() - .executePlan( - this instanceof SetViewPropertiesProcedure - ? new SetViewPropertiesPlan(database, tableName, updatedProperties) - : new SetTablePropertiesPlan(database, tableName, updatedProperties), - isGeneratedByPipe); - if (status.getCode() != TSStatusCode.SUCCESS_STATUS.getStatusCode()) { - setFailure(new ProcedureException(new IoTDBException(status))); - } else { - setNextState(COMMIT_RELEASE); - } + protected ConfigPhysicalPlan createSetPropertiesPlan( + final Map properties, final boolean isRollback) { + return this instanceof SetViewPropertiesProcedure + ? new SetViewPropertiesPlan(database, tableName, properties) + : new SetTablePropertiesPlan(database, tableName, properties); } @Override @@ -175,68 +77,6 @@ protected String getActionMessage() { return "set table properties"; } - @Override - protected void rollbackState( - final ConfigNodeProcedureEnv env, final SetTablePropertiesState state) - throws IOException, InterruptedException, ProcedureException { - final long startTime = System.currentTimeMillis(); - try { - switch (state) { - case PRE_RELEASE: - LOGGER.info( - ProcedureMessages.START_ROLLBACK_PRE_RELEASE_INFO_FOR_TABLE_WHEN_SETTING_PROPERTIES, - database, - table.getTableName()); - rollbackPreRelease(env); - break; - case SET_PROPERTIES: - LOGGER.info( - ProcedureMessages.START_ROLLBACK_SET_PROPERTIES_TO_TABLE, - database, - table.getTableName()); - rollbackSetProperties(env); - break; - } - } finally { - LOGGER.info( - ProcedureMessages.ROLLBACK_SETTABLEPROPERTIES_COSTS_MS, - state, - (System.currentTimeMillis() - startTime)); - } - } - - private void rollbackSetProperties(final ConfigNodeProcedureEnv env) { - if (table == null) { - return; - } - final TSStatus status = - env.getConfigManager() - .getClusterSchemaManager() - .executePlan( - this instanceof SetViewPropertiesProcedure - ? new SetViewPropertiesPlan(database, tableName, originalProperties) - : new SetTablePropertiesPlan(database, tableName, originalProperties), - isGeneratedByPipe); - if (status.getCode() != TSStatusCode.SUCCESS_STATUS.getStatusCode()) { - setFailure(new ProcedureException(new IoTDBException(status))); - } - } - - @Override - protected SetTablePropertiesState getState(final int stateId) { - return SetTablePropertiesState.values()[stateId]; - } - - @Override - protected int getStateId(final SetTablePropertiesState state) { - return state.ordinal(); - } - - @Override - protected SetTablePropertiesState getInitialState() { - return VALIDATE_TABLE; - } - @Override public void serialize(final DataOutputStream stream) throws IOException { stream.writeShort( @@ -246,29 +86,9 @@ public void serialize(final DataOutputStream stream) throws IOException { innerSerialize(stream); } - protected void innerSerialize(final DataOutputStream stream) throws IOException { - super.serialize(stream); - - ReadWriteIOUtils.write(originalProperties, stream); - ReadWriteIOUtils.write(updatedProperties, stream); - } - @Override public void deserialize(final ByteBuffer byteBuffer) { super.deserialize(byteBuffer); - - this.originalProperties = ReadWriteIOUtils.readMap(byteBuffer); - this.updatedProperties = ReadWriteIOUtils.readMap(byteBuffer); - } - - @Override - public boolean equals(final Object o) { - return super.equals(o) - && Objects.equals(updatedProperties, ((SetTablePropertiesProcedure) o).updatedProperties); - } - - @Override - public int hashCode() { - return Objects.hash(super.hashCode(), updatedProperties); + deserializeProperties(byteBuffer); } } 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 165b048b6363e..0f6b2fa75e1af 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 @@ -62,6 +62,7 @@ import org.apache.iotdb.confignode.procedure.impl.schema.table.DropTableProcedure; import org.apache.iotdb.confignode.procedure.impl.schema.table.RenameTableColumnProcedure; import org.apache.iotdb.confignode.procedure.impl.schema.table.RenameTableProcedure; +import org.apache.iotdb.confignode.procedure.impl.schema.table.SetTableColumnPropertiesProcedure; import org.apache.iotdb.confignode.procedure.impl.schema.table.SetTablePropertiesProcedure; import org.apache.iotdb.confignode.procedure.impl.schema.table.view.AddViewColumnProcedure; import org.apache.iotdb.confignode.procedure.impl.schema.table.view.CreateTableViewProcedure; @@ -237,6 +238,9 @@ public Procedure create(ByteBuffer buffer) throws IOException { case SET_VIEW_PROPERTIES_PROCEDURE: procedure = new SetViewPropertiesProcedure(false); break; + case SET_TABLE_COLUMN_PROPERTIES_PROCEDURE: + procedure = new SetTableColumnPropertiesProcedure(false); + break; case RENAME_TABLE_COLUMN_PROCEDURE: procedure = new RenameTableColumnProcedure(false); break; @@ -318,6 +322,9 @@ public Procedure create(ByteBuffer buffer) throws IOException { case PIPE_ENRICHED_SET_TABLE_PROPERTIES_PROCEDURE: procedure = new SetTablePropertiesProcedure(true); break; + case PIPE_ENRICHED_SET_TABLE_COLUMN_PROPERTIES_PROCEDURE: + procedure = new SetTableColumnPropertiesProcedure(true); + break; case PIPE_ENRICHED_RENAME_TABLE_COLUMN_PROCEDURE: procedure = new RenameTableColumnProcedure(true); break; @@ -498,6 +505,8 @@ public static ProcedureType getProcedureType(final Procedure procedure) { return ProcedureType.SET_VIEW_PROPERTIES_PROCEDURE; } else if (procedure instanceof SetTablePropertiesProcedure) { return ProcedureType.SET_TABLE_PROPERTIES_PROCEDURE; + } else if (procedure instanceof SetTableColumnPropertiesProcedure) { + return ProcedureType.SET_TABLE_COLUMN_PROPERTIES_PROCEDURE; } else if (procedure instanceof RenameViewColumnProcedure) { return ProcedureType.RENAME_VIEW_COLUMN_PROCEDURE; } else if (procedure instanceof RenameTableColumnProcedure) { diff --git a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/schema/table/AlterOrDropTableOperationType.java b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/schema/table/AlterOrDropTableOperationType.java index 46a8d5ddf3e3d..13f9d51798da8 100644 --- a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/schema/table/AlterOrDropTableOperationType.java +++ b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/schema/table/AlterOrDropTableOperationType.java @@ -28,7 +28,8 @@ public enum AlterOrDropTableOperationType { DROP_TABLE((byte) 5), COMMENT_TABLE((byte) 6), COMMENT_COLUMN((byte) 7), - ALTER_COLUMN_DATA_TYPE((byte) 8); + ALTER_COLUMN_DATA_TYPE((byte) 8), + SET_COLUMN_PROPERTIES((byte) 9); private final byte type; @@ -60,6 +61,8 @@ public static AlterOrDropTableOperationType getType(final byte value) { return COMMENT_COLUMN; case 8: return ALTER_COLUMN_DATA_TYPE; + case 9: + return SET_COLUMN_PROPERTIES; default: throw new IllegalArgumentException(); } From a2a27495db8e4cdf995e64865e3e104dfc488dea Mon Sep 17 00:00:00 2001 From: shuwenwei Date: Mon, 21 Sep 2026 10:58:58 +0800 Subject: [PATCH 11/16] [Query] Add the SetColumnProperties execution flow --- .../iotdb/db/i18n/DataNodeQueryMessages.java | 4 + .../iotdb/db/i18n/DataNodeQueryMessages.java | 4 + .../db/queryengine/plan/Coordinator.java | 2 + .../config/TableConfigTaskVisitor.java | 69 +++++++++ .../executor/ClusterConfigTaskExecutor.java | 48 +++++- .../config/executor/IConfigTaskExecutor.java | 9 ++ .../AlterTableSetColumnPropertiesTask.java | 55 +++++++ .../plan/relational/sql/ast/AstVisitor.java | 4 + .../sql/ast/DefaultTraversalVisitor.java | 9 ++ .../sql/ast/SetColumnProperties.java | 140 ++++++++++++++++++ .../sql/util/DataNodeSqlFormatter.java | 25 ++++ .../iotdb/commons/i18n/LBACMessages.java | 4 + .../iotdb/commons/i18n/LBACMessages.java | 4 + .../iotdb/commons/schema/table/TsTable.java | 2 + 14 files changed, 378 insertions(+), 1 deletion(-) create mode 100644 iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/execution/config/metadata/relational/AlterTableSetColumnPropertiesTask.java create mode 100644 iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/sql/ast/SetColumnProperties.java diff --git a/iotdb-core/datanode/src/main/i18n/en/org/apache/iotdb/db/i18n/DataNodeQueryMessages.java b/iotdb-core/datanode/src/main/i18n/en/org/apache/iotdb/db/i18n/DataNodeQueryMessages.java index bf7a36010030a..14fe3672d9858 100644 --- a/iotdb-core/datanode/src/main/i18n/en/org/apache/iotdb/db/i18n/DataNodeQueryMessages.java +++ b/iotdb-core/datanode/src/main/i18n/en/org/apache/iotdb/db/i18n/DataNodeQueryMessages.java @@ -267,6 +267,10 @@ public final class DataNodeQueryMessages { "getPageReader() shouldn't be called here"; public static final String UNSUPPORTED_COLUMN_TYPE = "Unsupported column type: "; + public static final String UNSUPPORTED_COLUMN_PROPERTY = + "Unsupported column property: "; + public static final String EXCEPTION_THE_COLUMN_PROPERTY_VALUE_MUST_BE_A_STRING_LITERAL_D6FA0250 = + "The column property value must be a string literal."; public static final String FAIL_TO_CLOSE_CTEDATAREADER = "Fail to close CteDataReader"; public static final String UNKNOWN_TABLE = diff --git a/iotdb-core/datanode/src/main/i18n/zh/org/apache/iotdb/db/i18n/DataNodeQueryMessages.java b/iotdb-core/datanode/src/main/i18n/zh/org/apache/iotdb/db/i18n/DataNodeQueryMessages.java index f26f2306be2fa..24caaa389dc4a 100644 --- a/iotdb-core/datanode/src/main/i18n/zh/org/apache/iotdb/db/i18n/DataNodeQueryMessages.java +++ b/iotdb-core/datanode/src/main/i18n/zh/org/apache/iotdb/db/i18n/DataNodeQueryMessages.java @@ -253,6 +253,10 @@ public final class DataNodeQueryMessages { "此处不应调用 getPageReader()"; public static final String UNSUPPORTED_COLUMN_TYPE = "不支持的列类型:"; + public static final String UNSUPPORTED_COLUMN_PROPERTY = + "不支持的列属性:"; + public static final String EXCEPTION_THE_COLUMN_PROPERTY_VALUE_MUST_BE_A_STRING_LITERAL_D6FA0250 = + "列属性值必须是字符串字面量。"; public static final String FAIL_TO_CLOSE_CTEDATAREADER = "关闭 CteDataReader 失败"; public static final String UNKNOWN_TABLE = diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/Coordinator.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/Coordinator.java index fad2ea1354d60..bdc738ddc4bb1 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/Coordinator.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/Coordinator.java @@ -117,6 +117,7 @@ import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.RenameColumn; import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.RenameTable; import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.SetColumnComment; +import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.SetColumnProperties; import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.SetConfiguration; import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.SetProperties; import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.SetSqlDialect; @@ -696,6 +697,7 @@ private IQueryExecution createQueryExecutionForTableModel( || statement instanceof AddColumn || statement instanceof AlterColumnDataType || statement instanceof SetProperties + || statement instanceof SetColumnProperties || statement instanceof DropColumn || statement instanceof DropTable || statement instanceof SetTableComment diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/execution/config/TableConfigTaskVisitor.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/execution/config/TableConfigTaskVisitor.java index d2f81429d4578..2af8eccc17388 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/execution/config/TableConfigTaskVisitor.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/execution/config/TableConfigTaskVisitor.java @@ -101,6 +101,7 @@ import org.apache.iotdb.db.queryengine.plan.execution.config.metadata.relational.AlterTableDropColumnTask; import org.apache.iotdb.db.queryengine.plan.execution.config.metadata.relational.AlterTableRenameColumnTask; import org.apache.iotdb.db.queryengine.plan.execution.config.metadata.relational.AlterTableRenameTableTask; +import org.apache.iotdb.db.queryengine.plan.execution.config.metadata.relational.AlterTableSetColumnPropertiesTask; import org.apache.iotdb.db.queryengine.plan.execution.config.metadata.relational.AlterTableSetPropertiesTask; import org.apache.iotdb.db.queryengine.plan.execution.config.metadata.relational.ClearCacheTask; import org.apache.iotdb.db.queryengine.plan.execution.config.metadata.relational.CountDBTask; @@ -208,6 +209,7 @@ import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.RenameColumn; import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.RenameTable; import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.SetColumnComment; +import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.SetColumnProperties; import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.SetConfiguration; import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.SetProperties; import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.SetSqlDialect; @@ -264,6 +266,7 @@ import org.apache.iotdb.db.queryengine.plan.statement.sys.ShowConfigurationStatement; import org.apache.iotdb.db.queryengine.plan.statement.sys.StartRepairDataStatement; import org.apache.iotdb.db.queryengine.plan.statement.sys.StopRepairDataStatement; +import org.apache.iotdb.db.schemaengine.table.DataNodeTableCache; import org.apache.iotdb.db.subscription.columnfilter.ColumnFilterParser; import org.apache.iotdb.pipe.api.customizer.parameter.PipeParameters; import org.apache.iotdb.rpc.TSStatusCode; @@ -289,6 +292,7 @@ import java.util.Set; import java.util.function.Predicate; +import static com.google.common.util.concurrent.Futures.immediateFuture; import static org.apache.iotdb.commons.conf.IoTDBConstant.MAX_DATABASE_NAME_LENGTH; import static org.apache.iotdb.commons.conf.IoTDBConstant.TTL_INFINITE; import static org.apache.iotdb.commons.executable.ExecutableManager.getUnTrustedUriErrorMsg; @@ -845,6 +849,40 @@ public IConfigTask visitDropColumn(final DropColumn node, final MPPQueryContext node.isView()); } + @Override + public IConfigTask visitSetColumnProperties( + final SetColumnProperties node, final MPPQueryContext context) { + context.setQueryType(QueryType.OTHER); + final Pair databaseTablePair = splitQualifiedName(node.getTableName()); + final String database = databaseTablePair.getLeft(); + final String tableName = databaseTablePair.getRight(); + final QualifiedObjectName table = new QualifiedObjectName(database, tableName); + + accessControl.checkCanAlterTable(context.getSession().getUserName(), table, context); + + if (!metadata.tableExists(table)) { + if (node.tableIfExists()) { + return configTaskExecutor -> + immediateFuture(new ConfigTaskResult(TSStatusCode.SUCCESS_STATUS)); + } + if (!DataNodeTableCache.getInstance().isDatabaseExist(database)) { + throw new SemanticException( + new org.apache.iotdb.commons.exception.IoTDBException( + String.format(DataNodeQueryMessages.UNKNOWN_DATABASE, database), + TSStatusCode.DATABASE_NOT_EXIST.getStatusCode())); + } + } + + return new AlterTableSetColumnPropertiesTask( + database, + tableName, + node.getColumnName().getValue(), + convertColumnPropertiesToMap(node.getProperties(), true), + context.getQueryId().getId(), + node.tableIfExists(), + node.columnIfExists()); + } + @Override public IConfigTask visitSetProperties(final SetProperties node, final MPPQueryContext context) { context.setQueryType(QueryType.OTHER); @@ -991,6 +1029,37 @@ private Map convertPropertiesToMap( return map; } + private Map convertColumnPropertiesToMap( + final List propertyList, final boolean serializeDefault) { + final Map map = new HashMap<>(); + final Set deduplicate = new HashSet<>(); + for (final Property property : propertyList) { + final String key = property.getName().getValue().toLowerCase(Locale.ENGLISH); + if (!deduplicate.add(key)) { + throw new SemanticException(DataNodeQueryMessages.DUPLICATED_PROPERTY + key); + } + if (!TsTable.COLUMN_ALLOWED_PROPERTIES.contains(key)) { + throw new SemanticException( + DataNodeQueryMessages.TABLE_PROPERTY + + key + + DataNodeQueryMessages.IS_CURRENTLY_NOT_ALLOWED); + } + if (!property.isSetToDefault()) { + map.put( + key, + parseStringFromLiteralIfBinary(property.getNonDefaultValue()) + .orElseThrow( + () -> + new SemanticException( + DataNodeQueryMessages + .EXCEPTION_THE_COLUMN_PROPERTY_VALUE_MUST_BE_A_STRING_LITERAL_D6FA0250))); + } else if (serializeDefault) { + map.put(key, null); + } + } + return map; + } + private TSDataType getDataType(final DataType dataType) { try { return getTSDataType(metadata.getType(toTypeSignature(dataType))); 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 7a3580fe66bf3..e239deb1a1477 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 @@ -5198,7 +5198,53 @@ public SettableFuture alterTableCommentTable( client); if (TSStatusCode.SUCCESS_STATUS.getStatusCode() == tsStatus.getCode() - || TSStatusCode.TABLE_NOT_EXISTS.getStatusCode() == tsStatus.getCode() && ifExists) { + || (TSStatusCode.TABLE_NOT_EXISTS.getStatusCode() == tsStatus.getCode() && ifExists)) { + future.set(new ConfigTaskResult(TSStatusCode.SUCCESS_STATUS)); + } else { + future.setException( + new IoTDBException(getTableErrorMessage(tsStatus, database), tsStatus.getCode())); + } + } catch (final ClientManagerException | TException e) { + future.setException(e); + } + return future; + } + + @Override + public SettableFuture alterTableSetColumnProperties( + final String database, + final String tableName, + final String columnName, + final Map properties, + final String queryId, + final boolean tableIfExists, + final boolean columnIfExists) { + final SettableFuture future = SettableFuture.create(); + try (final ConfigNodeClient client = + CONFIG_NODE_CLIENT_MANAGER.borrowClient(ConfigNodeInfo.CONFIG_REGION_ID)) { + + final ByteArrayOutputStream stream = new ByteArrayOutputStream(); + try { + ReadWriteIOUtils.write(columnName, stream); + ReadWriteIOUtils.write(properties, stream); + } catch (final IOException ignored) { + // ByteArrayOutputStream won't throw IOException + } + + final TSStatus tsStatus = + sendAlterReq2ConfigNode( + database, + tableName, + queryId, + AlterOrDropTableOperationType.SET_COLUMN_PROPERTIES, + stream.toByteArray(), + false, + client); + + if (TSStatusCode.SUCCESS_STATUS.getStatusCode() == tsStatus.getCode() + || (TSStatusCode.TABLE_NOT_EXISTS.getStatusCode() == tsStatus.getCode() && tableIfExists) + || (TSStatusCode.COLUMN_NOT_EXISTS.getStatusCode() == tsStatus.getCode() + && columnIfExists)) { future.set(new ConfigTaskResult(TSStatusCode.SUCCESS_STATUS)); } else { future.setException( 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 964b1c527792b..9fd3f7e9d5df0 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 @@ -430,6 +430,15 @@ SettableFuture alterTableSetProperties( final boolean ifExists, final boolean isView); + SettableFuture alterTableSetColumnProperties( + final String database, + final String tableName, + final String columnName, + final Map properties, + final String queryId, + final boolean tableIfExists, + final boolean columnIfExists); + SettableFuture alterTableCommentTable( final String database, final String tableName, diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/execution/config/metadata/relational/AlterTableSetColumnPropertiesTask.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/execution/config/metadata/relational/AlterTableSetColumnPropertiesTask.java new file mode 100644 index 0000000000000..d1265603954a6 --- /dev/null +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/execution/config/metadata/relational/AlterTableSetColumnPropertiesTask.java @@ -0,0 +1,55 @@ +/* + * 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.relational; + +import org.apache.iotdb.db.queryengine.plan.execution.config.ConfigTaskResult; +import org.apache.iotdb.db.queryengine.plan.execution.config.executor.IConfigTaskExecutor; + +import com.google.common.util.concurrent.ListenableFuture; + +import java.util.Map; + +public class AlterTableSetColumnPropertiesTask extends AbstractAlterOrDropTableTask { + + private final String columnName; + private final Map properties; + private final boolean columnIfExists; + + public AlterTableSetColumnPropertiesTask( + final String database, + final String tableName, + final String columnName, + final Map properties, + final String queryId, + final boolean tableIfExists, + final boolean columnIfExists) { + super(database, tableName, queryId, tableIfExists, false); + this.columnName = columnName; + this.properties = properties; + this.columnIfExists = columnIfExists; + } + + @Override + public ListenableFuture execute(final IConfigTaskExecutor configTaskExecutor) + throws InterruptedException { + return configTaskExecutor.alterTableSetColumnProperties( + database, tableName, columnName, properties, queryId, tableIfExists, columnIfExists); + } +} diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/sql/ast/AstVisitor.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/sql/ast/AstVisitor.java index 86084ff2c6a1d..9e62a311713ea 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/sql/ast/AstVisitor.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/sql/ast/AstVisitor.java @@ -163,6 +163,10 @@ default R visitSetProperties(SetProperties node, C context) { return visitStatement(node, context); } + default R visitSetColumnProperties(SetColumnProperties node, C context) { + return visitStatement(node, context); + } + default R visitRenameColumn(RenameColumn node, C context) { return visitStatement(node, context); } diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/sql/ast/DefaultTraversalVisitor.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/sql/ast/DefaultTraversalVisitor.java index 43cee9d88e723..9b1c48f37994a 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/sql/ast/DefaultTraversalVisitor.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/sql/ast/DefaultTraversalVisitor.java @@ -515,6 +515,15 @@ public Void visitSetProperties(final SetProperties node, final C context) { return null; } + @Override + public Void visitSetColumnProperties(final SetColumnProperties node, final C context) { + for (final Property property : node.getProperties()) { + process(property, context); + } + + return null; + } + @Override public Void visitAddColumn(final AddColumn node, final C context) { process(node.getColumn(), context); diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/sql/ast/SetColumnProperties.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/sql/ast/SetColumnProperties.java new file mode 100644 index 0000000000000..c8391efd591f8 --- /dev/null +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/sql/ast/SetColumnProperties.java @@ -0,0 +1,140 @@ +/* + * 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.relational.sql.ast; + +import org.apache.iotdb.commons.i18n.LBACMessages; +import org.apache.iotdb.commons.queryengine.plan.relational.sql.ast.AstMemoryEstimationHelper; +import org.apache.iotdb.commons.queryengine.plan.relational.sql.ast.IAstVisitor; +import org.apache.iotdb.commons.queryengine.plan.relational.sql.ast.Identifier; +import org.apache.iotdb.commons.queryengine.plan.relational.sql.ast.Node; +import org.apache.iotdb.commons.queryengine.plan.relational.sql.ast.NodeLocation; +import org.apache.iotdb.commons.queryengine.plan.relational.sql.ast.QualifiedName; +import org.apache.iotdb.commons.queryengine.plan.relational.sql.ast.Statement; + +import com.google.common.collect.ImmutableList; +import org.apache.tsfile.utils.RamUsageEstimator; + +import java.util.List; +import java.util.Objects; + +import static com.google.common.base.MoreObjects.toStringHelper; +import static java.util.Objects.requireNonNull; + +public class SetColumnProperties extends Statement { + + private static final long INSTANCE_SIZE = + RamUsageEstimator.shallowSizeOfInstance(SetColumnProperties.class); + + private final QualifiedName tableName; + private final Identifier columnName; + private final List properties; + private final boolean tableIfExists; + private final boolean columnIfExists; + + public SetColumnProperties( + final NodeLocation location, + final QualifiedName tableName, + final Identifier columnName, + final List properties, + final boolean tableIfExists, + final boolean columnIfExists) { + super(requireNonNull(location, LBACMessages.EXCEPTION_LOCATION_IS_NULL_399F8D73)); + this.tableName = requireNonNull(tableName, LBACMessages.EXCEPTION_TABLENAME_IS_NULL_6B6687B9); + this.columnName = + requireNonNull(columnName, LBACMessages.EXCEPTION_COLUMNNAME_IS_NULL_46BD2848); + this.properties = + ImmutableList.copyOf( + requireNonNull(properties, LBACMessages.EXCEPTION_PROPERTIES_IS_NULL_08E70FBB)); + this.tableIfExists = tableIfExists; + this.columnIfExists = columnIfExists; + } + + public QualifiedName getTableName() { + return tableName; + } + + public Identifier getColumnName() { + return columnName; + } + + public List getProperties() { + return properties; + } + + public boolean tableIfExists() { + return tableIfExists; + } + + public boolean columnIfExists() { + return columnIfExists; + } + + @Override + public R accept(final IAstVisitor visitor, final C context) { + return ((AstVisitor) visitor).visitSetColumnProperties(this, context); + } + + @Override + public List getChildren() { + return ImmutableList.of(); + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + final SetColumnProperties that = (SetColumnProperties) o; + return tableIfExists == that.tableIfExists + && columnIfExists == that.columnIfExists + && Objects.equals(tableName, that.tableName) + && Objects.equals(columnName, that.columnName) + && Objects.equals(properties, that.properties); + } + + @Override + public int hashCode() { + return Objects.hash(tableName, columnName, properties, tableIfExists, columnIfExists); + } + + @Override + public String toString() { + return toStringHelper(this) + .add("tableName", tableName) + .add("columnName", columnName) + .add("properties", properties) + .add("tableIfExists", tableIfExists) + .add("columnIfExists", columnIfExists) + .toString(); + } + + @Override + public long ramBytesUsed() { + long size = INSTANCE_SIZE; + size += AstMemoryEstimationHelper.getEstimatedSizeOfNodeLocation(getLocationInternal()); + size += tableName.ramBytesUsed(); + size += AstMemoryEstimationHelper.getEstimatedSizeOfAccountableObject(columnName); + size += AstMemoryEstimationHelper.getEstimatedSizeOfNodeList(properties); + return size; + } +} diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/sql/util/DataNodeSqlFormatter.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/sql/util/DataNodeSqlFormatter.java index dec3cc58b96c6..63e94c23238a0 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/sql/util/DataNodeSqlFormatter.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/sql/util/DataNodeSqlFormatter.java @@ -60,6 +60,7 @@ import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.RenameColumn; import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.RenameTable; import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.SetColumnComment; +import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.SetColumnProperties; import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.SetProperties; import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.SetTableComment; import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.ShowClusterId; @@ -389,6 +390,30 @@ public Void visitSetProperties(SetProperties node, Integer context) { return null; } + @Override + public Void visitSetColumnProperties(SetColumnProperties node, Integer context) { + builder.append("ALTER TABLE "); + if (node.tableIfExists()) { + builder.append("IF EXISTS "); + } + builder + .append(CommonQuerySqlFormatter.formatName(node.getTableName())) + .append(" ALTER COLUMN "); + if (node.columnIfExists()) { + builder.append("IF EXISTS "); + } + builder.append(CommonQuerySqlFormatter.formatName(node.getColumnName())); + for (final Property property : node.getProperties()) { + switch (property.getName().getValue()) { + // No column property key is supported in this edition. + default: + throw new UnsupportedOperationException( + DataNodeQueryMessages.UNSUPPORTED_COLUMN_PROPERTY + property.getName().getValue()); + } + } + return null; + } + @Override public Void visitRenameColumn(RenameColumn node, Integer indent) { builder.append("ALTER"); diff --git a/iotdb-core/node-commons/src/main/i18n/en/org/apache/iotdb/commons/i18n/LBACMessages.java b/iotdb-core/node-commons/src/main/i18n/en/org/apache/iotdb/commons/i18n/LBACMessages.java index 85453a946781c..3a5fb22c67714 100644 --- a/iotdb-core/node-commons/src/main/i18n/en/org/apache/iotdb/commons/i18n/LBACMessages.java +++ b/iotdb-core/node-commons/src/main/i18n/en/org/apache/iotdb/commons/i18n/LBACMessages.java @@ -25,4 +25,8 @@ private LBACMessages() {} public static final String EXCEPTION_LBAC_CHECK_FAILED_FOR_USER_ARG_ARG_TO_LABEL_ARG_VALUE_ARG_UNDER_POLICY_ARG_USER_S_MERGED_GRANT_ARG_DOES_NOT_DOMINATE_REQUIRED_ARG_C18D4EA7 = "LBAC check failed for user '%s' %s to label '%s' (value: %s) under policy '%s': user's merged grant %s does not dominate required %s"; public static final String EXCEPTION_LABELNAME_IS_NULL_856ABAE4 = "labelName is null"; + public static final String EXCEPTION_LOCATION_IS_NULL_399F8D73 = "location is null"; + public static final String EXCEPTION_TABLENAME_IS_NULL_6B6687B9 = "tableName is null"; + public static final String EXCEPTION_COLUMNNAME_IS_NULL_46BD2848 = "columnName is null"; + public static final String EXCEPTION_PROPERTIES_IS_NULL_08E70FBB = "properties is null"; } diff --git a/iotdb-core/node-commons/src/main/i18n/zh/org/apache/iotdb/commons/i18n/LBACMessages.java b/iotdb-core/node-commons/src/main/i18n/zh/org/apache/iotdb/commons/i18n/LBACMessages.java index ce7d66b9f1710..935a98d52267e 100644 --- a/iotdb-core/node-commons/src/main/i18n/zh/org/apache/iotdb/commons/i18n/LBACMessages.java +++ b/iotdb-core/node-commons/src/main/i18n/zh/org/apache/iotdb/commons/i18n/LBACMessages.java @@ -25,4 +25,8 @@ private LBACMessages() {} public static final String EXCEPTION_LBAC_CHECK_FAILED_FOR_USER_ARG_ARG_TO_LABEL_ARG_VALUE_ARG_UNDER_POLICY_ARG_USER_S_MERGED_GRANT_ARG_DOES_NOT_DOMINATE_REQUIRED_ARG_C18D4EA7 = "用户 '%s' 的 %s 访问失败:标签 '%s'(值:%s)位于策略 '%s' 下,用户合并后的授权 %s 不支配所需值 %s"; public static final String EXCEPTION_LABELNAME_IS_NULL_856ABAE4 = "labelName 为 null"; + public static final String EXCEPTION_LOCATION_IS_NULL_399F8D73 = "location 为 null"; + public static final String EXCEPTION_TABLENAME_IS_NULL_6B6687B9 = "tableName 为 null"; + public static final String EXCEPTION_COLUMNNAME_IS_NULL_46BD2848 = "columnName 为 null"; + public static final String EXCEPTION_PROPERTIES_IS_NULL_08E70FBB = "properties 为 null"; } diff --git a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/schema/table/TsTable.java b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/schema/table/TsTable.java index 14481bfa9bcaa..681c638bc563e 100644 --- a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/schema/table/TsTable.java +++ b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/schema/table/TsTable.java @@ -65,6 +65,8 @@ public class TsTable { public static final String TIME_COLUMN_NAME = "time"; public static final String COMMENT_KEY = "__comment"; public static final String TTL_PROPERTY = "ttl"; + // No column property key is supported yet. + public static final Set COLUMN_ALLOWED_PROPERTIES = Collections.emptySet(); public static final String NEED_LAST_CACHE_PROPERTY = "need_last_cache"; public static final Set TABLE_ALLOWED_PROPERTIES = Collections.unmodifiableSet( From b3f50e5dcac1a3c7be786bfb6e0256dcb661f622 Mon Sep 17 00:00:00 2001 From: shuwenwei Date: Mon, 21 Sep 2026 15:28:28 +0800 Subject: [PATCH 12/16] [Query] Resolve insert target tables per row only for pipe batches --- .../request/PipeTransferTabletBatchReqV2.java | 4 ++ .../plan/analyze/schema/SchemaValidator.java | 56 +++++++++++++------ .../statement/crud/InsertRowsStatement.java | 17 ++++++ 3 files changed, 61 insertions(+), 16 deletions(-) diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/payload/evolvable/request/PipeTransferTabletBatchReqV2.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/payload/evolvable/request/PipeTransferTabletBatchReqV2.java index 2cec219fa7524..52c80bb515ae6 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/payload/evolvable/request/PipeTransferTabletBatchReqV2.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/sink/payload/evolvable/request/PipeTransferTabletBatchReqV2.java @@ -148,6 +148,10 @@ public List constructStatements() { statement.setWriteToTable(true); statement.setDatabaseName(insertRows.getKey()); statement.setInsertRowStatementList(tableInsertRows.getValue()); + // Rows merged into this batch may come from different source inserts and therefore target + // different tables; mark the source so analysis traverses each row instead of assuming the + // first row is representative. + statement.setFromPipeBatch(true); statements.add(statement); } } diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/analyze/schema/SchemaValidator.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/analyze/schema/SchemaValidator.java index 06905feead4e3..4a55a8d28597b 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/analyze/schema/SchemaValidator.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/analyze/schema/SchemaValidator.java @@ -42,6 +42,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.util.Collections; import java.util.LinkedHashSet; import java.util.List; import java.util.Set; @@ -77,7 +78,12 @@ public static void validate( final MPPQueryContext context, AccessControl accessControl) { try { - for (final QualifiedObjectName targetTable : getTargetTables(insertStatement, context)) { + final InsertBaseStatement innerInsertStatement = insertStatement.getInnerTreeStatement(); + final boolean fromPipeBatch = + innerInsertStatement instanceof InsertRowsStatement + && ((InsertRowsStatement) innerInsertStatement).isFromPipeBatch(); + for (final QualifiedObjectName targetTable : + resolveTargetTables(insertStatement, context, fromPipeBatch)) { accessControl.checkCanInsertIntoTable( context.getSession().getUserName(), targetTable, context); } @@ -90,28 +96,46 @@ public static void validate( } } - private static Set getTargetTables( - final WrappedInsertStatement insertStatement, final MPPQueryContext context) { - final Set targetTables = new LinkedHashSet<>(); - if (insertStatement instanceof InsertRows) { - for (final InsertRowStatement rowStatement : - ((InsertRows) insertStatement).getInnerTreeStatement().getInsertRowStatementList()) { - final String database = AnalyzeUtils.getDatabaseName(rowStatement, context); - if (database == null) { - throw new SemanticException(DATABASE_NOT_SPECIFIED); - } - targetTables.add( - new QualifiedObjectName(unQualifyDatabaseName(database), rowStatement.getTableName())); - } - } else { - targetTables.add( + /** + * Resolves the target tables to check. Every row of a pipe batch is considered, since its rows + * may target different tables; rows of any other insert share the same table, so the first row is + * representative. + */ + private static Set resolveTargetTables( + final WrappedInsertStatement insertStatement, + final MPPQueryContext context, + final boolean fromPipeBatch) { + if (!(insertStatement instanceof InsertRows)) { + return Collections.singleton( new QualifiedObjectName( unQualifyDatabaseName(insertStatement.getDatabase()), insertStatement.getTableName())); } + + final List rowStatements = + ((InsertRows) insertStatement).getInnerTreeStatement().getInsertRowStatementList(); + if (!fromPipeBatch) { + return rowStatements.isEmpty() + ? Collections.emptySet() + : Collections.singleton(resolveTargetTable(rowStatements.get(0), context)); + } + + final Set targetTables = new LinkedHashSet<>(); + for (final InsertRowStatement rowStatement : rowStatements) { + targetTables.add(resolveTargetTable(rowStatement, context)); + } return targetTables; } + private static QualifiedObjectName resolveTargetTable( + final InsertRowStatement rowStatement, final MPPQueryContext context) { + final String database = AnalyzeUtils.getDatabaseName(rowStatement, context); + if (database == null) { + throw new SemanticException(DATABASE_NOT_SPECIFIED); + } + return new QualifiedObjectName(unQualifyDatabaseName(database), rowStatement.getTableName()); + } + public static ISchemaTree validate( ISchemaFetcher schemaFetcher, List devicePaths, diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/statement/crud/InsertRowsStatement.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/statement/crud/InsertRowsStatement.java index cf4c3f2882d74..9b302b4107cf9 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/statement/crud/InsertRowsStatement.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/statement/crud/InsertRowsStatement.java @@ -53,6 +53,15 @@ public class InsertRowsStatement extends InsertBaseStatement { /** the InsertRowsStatement list */ private List insertRowStatementList; + /** + * Whether this statement is assembled by pipe batching ({@code + * PipeTransferTabletBatchReqV2#constructStatements()}). A pipe batch merges rows that originate + * from different source insert statements, so the rows of such a statement may target different + * tables and must be traversed row by row. Rows of any other insert share the same target table, + * so the first row is representative. + */ + private boolean fromPipeBatch; + public InsertRowsStatement() { super(); statementType = StatementType.BATCH_INSERT_ROWS; @@ -99,6 +108,14 @@ public void setInsertRowStatementList(List insertRowStatemen this.insertRowStatementList = insertRowStatementList; } + public boolean isFromPipeBatch() { + return fromPipeBatch; + } + + public void setFromPipeBatch(final boolean fromPipeBatch) { + this.fromPipeBatch = fromPipeBatch; + } + @Override public boolean isEmpty() { return insertRowStatementList.isEmpty(); From b34a6472f1f171dfd791ede3dade2382d627a2d8 Mon Sep 17 00:00:00 2001 From: shuwenwei Date: Mon, 21 Sep 2026 15:28:28 +0800 Subject: [PATCH 13/16] [LBAC] Lazily allocate RequiredLabels sets --- .../iotdb/commons/lbac/RequiredLabels.java | 25 +++++++++++++++---- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/lbac/RequiredLabels.java b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/lbac/RequiredLabels.java index 13d635597659e..4ad27125f8f8d 100644 --- a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/lbac/RequiredLabels.java +++ b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/lbac/RequiredLabels.java @@ -21,6 +21,7 @@ import org.apache.iotdb.commons.i18n.LBACMessages; +import java.util.Collections; import java.util.HashSet; import java.util.Objects; import java.util.Set; @@ -28,37 +29,51 @@ /** The object-side LBAC requirement: protected column labels grouped by READ and WRITE. */ public final class RequiredLabels { - private final Set readLabels = new HashSet<>(); - private final Set writeLabels = new HashSet<>(); + // Lazily created on first use: a requirement usually carries only READ or only WRITE labels. + private Set readLabels; + private Set writeLabels; public RequiredLabels requireLabelWithReadAccess(final String labelName) { Objects.requireNonNull(labelName, LBACMessages.EXCEPTION_LABELNAME_IS_NULL_856ABAE4); + if (readLabels == null) { + readLabels = new HashSet<>(); + } readLabels.add(labelName); return this; } public RequiredLabels requireLabelWithWriteAccess(final String labelName) { Objects.requireNonNull(labelName, LBACMessages.EXCEPTION_LABELNAME_IS_NULL_856ABAE4); + if (writeLabels == null) { + writeLabels = new HashSet<>(); + } writeLabels.add(labelName); return this; } public RequiredLabels requireLabelWithAllAccess(final String labelName) { Objects.requireNonNull(labelName, LBACMessages.EXCEPTION_LABELNAME_IS_NULL_856ABAE4); + if (readLabels == null) { + readLabels = new HashSet<>(); + } + if (writeLabels == null) { + writeLabels = new HashSet<>(); + } readLabels.add(labelName); writeLabels.add(labelName); return this; } public Set getReadLabels() { - return readLabels; + return readLabels == null ? Collections.emptySet() : readLabels; } public Set getWriteLabels() { - return writeLabels; + return writeLabels == null ? Collections.emptySet() : writeLabels; } public boolean isEmpty() { - return readLabels.isEmpty() && writeLabels.isEmpty(); + return (readLabels == null || readLabels.isEmpty()) + && (writeLabels == null || writeLabels.isEmpty()); } } From 536315aff8a3c623166f5f544a97ca696c95a0e9 Mon Sep 17 00:00:00 2001 From: shuwenwei Date: Mon, 21 Sep 2026 15:38:08 +0800 Subject: [PATCH 14/16] [Query] Propagate table and column properties into relational schemas --- .../plan/relational/metadata/TableMetadataImpl.java | 11 +++++++---- .../plan/relational/metadata/ColumnSchema.java | 13 ++++++++----- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/metadata/TableMetadataImpl.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/metadata/TableMetadataImpl.java index d94a3a3707450..04dcc93b7312c 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/metadata/TableMetadataImpl.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/metadata/TableMetadataImpl.java @@ -142,10 +142,13 @@ public Optional getTableSchema( return schema; }) .collect(Collectors.toList()); - return Optional.of( - TreeViewSchema.isTreeViewTable(table) - ? new TreeDeviceViewSchema(table.getTableName(), columnSchemaList, table.getProps()) - : new TableSchema(table.getTableName(), columnSchemaList)); + if (TreeViewSchema.isTreeViewTable(table)) { + return Optional.of( + new TreeDeviceViewSchema(table.getTableName(), columnSchemaList, table.getProps())); + } + final TableSchema tableSchema = new TableSchema(table.getTableName(), columnSchemaList); + tableSchema.setProps(table.getProps()); + return Optional.of(tableSchema); } @Override diff --git a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/queryengine/plan/relational/metadata/ColumnSchema.java b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/queryengine/plan/relational/metadata/ColumnSchema.java index 0a9fd902e094f..1721b771f3b3d 100644 --- a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/queryengine/plan/relational/metadata/ColumnSchema.java +++ b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/queryengine/plan/relational/metadata/ColumnSchema.java @@ -133,11 +133,14 @@ public static ColumnSchema deserialize(ByteBuffer byteBuffer) { } public static ColumnSchema ofTsColumnSchema(TsTableColumnSchema schema) { - return new ColumnSchema( - schema.getColumnName(), - TypeFactory.getType(schema.getDataType()), - false, - schema.getColumnCategory()); + final ColumnSchema columnSchema = + new ColumnSchema( + schema.getColumnName(), + TypeFactory.getType(schema.getDataType()), + false, + schema.getColumnCategory()); + columnSchema.setProps(schema.getProps()); + return columnSchema; } public static Builder builder() { From ec5b274fdbdf9dfe86db552021a1e7a15b87dd28 Mon Sep 17 00:00:00 2001 From: shuwenwei Date: Mon, 21 Sep 2026 15:38:08 +0800 Subject: [PATCH 15/16] [Query] Add long-list memory estimation helper --- .../execution/MemoryEstimationHelper.java | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/queryengine/execution/MemoryEstimationHelper.java b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/queryengine/execution/MemoryEstimationHelper.java index 6e047eee3b934..f2744c765d02b 100644 --- a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/queryengine/execution/MemoryEstimationHelper.java +++ b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/queryengine/execution/MemoryEstimationHelper.java @@ -48,6 +48,7 @@ public class MemoryEstimationHelper { RamUsageEstimator.shallowSizeOfInstance(ArrayList.class); public static final long INTEGER_INSTANCE_SIZE = RamUsageEstimator.shallowSizeOfInstance(Integer.class); + public static final long LONG_INSTANCE_SIZE = RamUsageEstimator.shallowSizeOfInstance(Long.class); public static final long TIME_RANGE_INSTANCE_SIZE = RamUsageEstimator.shallowSizeOfInstance(TimeRange.class); @@ -135,4 +136,16 @@ public static long getEstimatedSizeOfIntegerArrayList(List integerArray size += INTEGER_INSTANCE_SIZE * integerArrayList.size(); return RamUsageEstimator.alignObjectSize(size); } + + public static long getEstimatedSizeOfLongArrayList(List longArrayList) { + if (longArrayList == null) { + return 0L; + } + long size = ARRAY_LIST_INSTANCE_SIZE; + size += + (long) RamUsageEstimator.NUM_BYTES_ARRAY_HEADER + + (long) longArrayList.size() * (long) RamUsageEstimator.NUM_BYTES_OBJECT_REF; + size += LONG_INSTANCE_SIZE * longArrayList.size(); + return RamUsageEstimator.alignObjectSize(size); + } } From 4739e3531d0338d1b0aa52eb7ca00d9e5b6b427f Mon Sep 17 00:00:00 2001 From: shuwenwei Date: Mon, 21 Sep 2026 18:01:41 +0800 Subject: [PATCH 16/16] [Test] Mark multi-table insert rows as pipe batch in SchemaValidatorTest --- .../db/queryengine/plan/analyze/schema/SchemaValidatorTest.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/queryengine/plan/analyze/schema/SchemaValidatorTest.java b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/queryengine/plan/analyze/schema/SchemaValidatorTest.java index 8cb3cfb1ee1e5..88ecff414580d 100644 --- a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/queryengine/plan/analyze/schema/SchemaValidatorTest.java +++ b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/queryengine/plan/analyze/schema/SchemaValidatorTest.java @@ -62,6 +62,8 @@ public void testAllInsertRowsTablesCheckedBeforeSchemaValidation() { null); final InsertRowsStatement statement = new InsertRowsStatement(); statement.setWriteToTable(true); + // rows spanning multiple tables can only be assembled by pipe batching + statement.setFromPipeBatch(true); statement.setInsertRowStatementList( Arrays.asList( createInsertRowStatement("db1", "table1"),