This is an automated email from the ASF dual-hosted git repository.

JackieTien97 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/iotdb.git


The following commit(s) were added to refs/heads/master by this push:
     new 6b77a8dc8f0 Isolate writes to the internal system database 
root.__system (#19017)
6b77a8dc8f0 is described below

commit 6b77a8dc8f000b9117f2c7b250b5ed97a680e89b
Author: alpass163 <[email protected]>
AuthorDate: Mon Oct 26 17:41:42 2026 +0800

    Isolate writes to the internal system database root.__system (#19017)
---
 .../iotdb/trigger/StatisticsUpdaterTrigger.java    |   2 +-
 .../org/apache/iotdb/db/auth/AuthorityChecker.java |   1 +
 .../impl/DataNodeInternalRPCServiceImpl.java       |  13 +-
 .../db/queryengine/common/MPPQueryContext.java     |   5 +
 .../db/queryengine/plan/analyze/AnalyzeUtils.java  |  21 +++-
 .../queryengine/plan/analyze/AnalyzeVisitor.java   |  20 ++-
 .../analyze/schema/AutoCreateSchemaExecutor.java   |   6 +-
 .../relational/security/AccessControlImpl.java     |  20 +++
 .../security/TreeAccessCheckContext.java           |  11 ++
 .../security/TreeAccessCheckVisitor.java           | 138 +++++++++++++++++++++
 .../metrics/IoTDBInternalLocalReporter.java        |  13 +-
 .../org/apache/iotdb/db/auth/TreeAccessTest.java   | 102 +++++++++++++++
 .../conf/iotdb-system.properties.template          |   4 +
 .../apache/iotdb/commons/audit/IAuditEntity.java   |   5 +
 .../commons/queryengine/common/SessionInfo.java    |  13 ++
 .../commons/schema/InternalDatabaseUtils.java      |  52 ++++++++
 .../apache/iotdb/commons/schema/table/Audit.java   |   1 +
 .../iotdb/commons/schema/table/AuditTest.java      |   1 +
 18 files changed, 404 insertions(+), 24 deletions(-)

diff --git 
a/example/trigger/src/main/java/org/apache/iotdb/trigger/StatisticsUpdaterTrigger.java
 
b/example/trigger/src/main/java/org/apache/iotdb/trigger/StatisticsUpdaterTrigger.java
index 4f1217b6f66..c8fed4130c2 100644
--- 
a/example/trigger/src/main/java/org/apache/iotdb/trigger/StatisticsUpdaterTrigger.java
+++ 
b/example/trigger/src/main/java/org/apache/iotdb/trigger/StatisticsUpdaterTrigger.java
@@ -45,7 +45,7 @@ public class StatisticsUpdaterTrigger implements Trigger {
 
   private static final Logger LOGGER = 
LoggerFactory.getLogger(StatisticsUpdaterTrigger.class);
 
-  private static final String TARGET_DEVICE = "root.__system.statistics";
+  private static final String TARGET_DEVICE = "root.statistics";
 
   private static final String TARGET_SERIES = "total_count";
   private String ip;
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 53c5da0ddc4..ccf79c78240 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
@@ -226,6 +226,7 @@ public class AuthorityChecker {
                             auditEntity.getUserId(),
                             auditEntity.getUsername(),
                             auditEntity.getCliHostname())
+                        .setInternal(auditEntity.isInternal())
                         .setSqlString(auditEntity.getSqlString()));
       }
       return status;
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/protocol/thrift/impl/DataNodeInternalRPCServiceImpl.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/protocol/thrift/impl/DataNodeInternalRPCServiceImpl.java
index 5d3bce4e4c3..1f5f95b4460 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/protocol/thrift/impl/DataNodeInternalRPCServiceImpl.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/protocol/thrift/impl/DataNodeInternalRPCServiceImpl.java
@@ -3557,12 +3557,13 @@ public class DataNodeInternalRPCServiceImpl implements 
IDataNodeRPCService.Iface
       InsertRowStatement statement = StatementGenerator.createStatement(req);
       SessionInfo sessionInfo =
           new SessionInfo(
-              0,
-              new UserEntity(
-                  AuthorityChecker.SUPER_USER_ID,
-                  AuthorityChecker.SUPER_USER,
-                  
IoTDBDescriptor.getInstance().getConfig().getInternalAddress()),
-              ZoneId.systemDefault());
+                  0,
+                  new UserEntity(
+                      AuthorityChecker.SUPER_USER_ID,
+                      AuthorityChecker.SUPER_USER,
+                      
IoTDBDescriptor.getInstance().getConfig().getInternalAddress()),
+                  ZoneId.systemDefault())
+              .setInternal(true);
 
       long queryId = SESSION_MANAGER.requestQueryId();
       ExecutionResult result =
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/common/MPPQueryContext.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/common/MPPQueryContext.java
index 311bf04322a..d609c5c8e02 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/common/MPPQueryContext.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/common/MPPQueryContext.java
@@ -1086,6 +1086,11 @@ public class MPPQueryContext implements IAuditEntity {
     return session.getUserName();
   }
 
+  @Override
+  public boolean isInternal() {
+    return session != null && session.isInternal();
+  }
+
   @Override
   public String getCliHostname() {
     if (session == null || session.getCliHostname() == null) {
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/analyze/AnalyzeUtils.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/analyze/AnalyzeUtils.java
index a1d5dc7a27f..95bf1570350 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/analyze/AnalyzeUtils.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/analyze/AnalyzeUtils.java
@@ -20,7 +20,9 @@
 package org.apache.iotdb.db.queryengine.plan.analyze;
 
 import org.apache.iotdb.common.rpc.thrift.TRegionReplicaSet;
+import org.apache.iotdb.common.rpc.thrift.TSStatus;
 import org.apache.iotdb.common.rpc.thrift.TTimePartitionSlot;
+import org.apache.iotdb.commons.audit.IAuditEntity;
 import org.apache.iotdb.commons.exception.IoTDBException;
 import org.apache.iotdb.commons.exception.IoTDBRuntimeException;
 import org.apache.iotdb.commons.exception.SemanticException;
@@ -56,6 +58,7 @@ import 
org.apache.iotdb.db.queryengine.plan.relational.metadata.DeviceEntry;
 import 
org.apache.iotdb.db.queryengine.plan.relational.metadata.fetcher.TableDeviceSchemaFetcher;
 import 
org.apache.iotdb.db.queryengine.plan.relational.planner.ir.ExpressionRewriter;
 import 
org.apache.iotdb.db.queryengine.plan.relational.planner.ir.ExpressionTreeRewriter;
+import 
org.apache.iotdb.db.queryengine.plan.relational.security.TreeAccessCheckVisitor;
 import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.Delete;
 import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.TimeRange;
 import org.apache.iotdb.db.queryengine.plan.statement.crud.InsertBaseStatement;
@@ -168,7 +171,9 @@ public class AnalyzeUtils {
     validateSchema(analysis, insertBaseStatement, schemaValidation);
 
     InsertBaseStatement realStatement =
-        removeLogicalView ? removeLogicalView(analysis, insertBaseStatement) : 
insertBaseStatement;
+        removeLogicalView
+            ? removeLogicalView(analysis, insertBaseStatement, context)
+            : insertBaseStatement;
     if (analysis.isFinishQueryAfterAnalyze()) {
       return realStatement;
     }
@@ -343,9 +348,19 @@ public class AnalyzeUtils {
   }
 
   public static InsertBaseStatement removeLogicalView(
-      IAnalysis analysis, InsertBaseStatement insertBaseStatement) {
+      IAnalysis analysis, InsertBaseStatement insertBaseStatement, 
IAuditEntity context) {
     try {
-      return insertBaseStatement.removeLogicalView();
+      final InsertBaseStatement realStatement = 
insertBaseStatement.removeLogicalView();
+      // Inserts into a logical view are rewritten to the view's source 
series. Check the real
+      // target paths so a view created before this restriction cannot write 
root.__system.
+      final TSStatus systemDatabaseStatus =
+          TreeAccessCheckVisitor.checkSystemDatabaseWrite(
+              context, realStatement.getDevicePathsStream().toList());
+      if (systemDatabaseStatus != null) {
+        analysis.setFinishQueryAfterAnalyze(true);
+        analysis.setFailStatus(systemDatabaseStatus);
+      }
+      return realStatement;
     } catch (SemanticException e) {
       analysis.setFinishQueryAfterAnalyze(true);
       if (e.getCause() instanceof IoTDBException) {
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/analyze/AnalyzeVisitor.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/analyze/AnalyzeVisitor.java
index c9a422dec98..5d42cde5859 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/analyze/AnalyzeVisitor.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/analyze/AnalyzeVisitor.java
@@ -87,6 +87,7 @@ import 
org.apache.iotdb.db.queryengine.plan.planner.plan.parameter.GroupByTimePa
 import 
org.apache.iotdb.db.queryengine.plan.planner.plan.parameter.GroupByVariationParameter;
 import 
org.apache.iotdb.db.queryengine.plan.planner.plan.parameter.IntoPathDescriptor;
 import 
org.apache.iotdb.db.queryengine.plan.planner.plan.parameter.OrderByParameter;
+import 
org.apache.iotdb.db.queryengine.plan.relational.security.TreeAccessCheckVisitor;
 import org.apache.iotdb.db.queryengine.plan.statement.StatementNode;
 import org.apache.iotdb.db.queryengine.plan.statement.StatementVisitor;
 import org.apache.iotdb.db.queryengine.plan.statement.component.FillComponent;
@@ -2903,7 +2904,8 @@ public class AnalyzeVisitor extends 
StatementVisitor<Analysis, MPPQueryContext>
         analysis,
         insertRowStatement,
         () -> SchemaValidator.validate(schemaFetcher, insertRowStatement, 
context));
-    InsertBaseStatement realInsertStatement = removeLogicalView(analysis, 
insertRowStatement);
+    InsertBaseStatement realInsertStatement =
+        removeLogicalView(analysis, insertRowStatement, context);
     if (analysis.isFinishQueryAfterAnalyze()) {
       return analysis;
     }
@@ -2943,7 +2945,7 @@ public class AnalyzeVisitor extends 
StatementVisitor<Analysis, MPPQueryContext>
         insertRowsStatement,
         () -> SchemaValidator.validate(schemaFetcher, insertRowsStatement, 
context));
     InsertRowsStatement realInsertRowsStatement =
-        (InsertRowsStatement) removeLogicalView(analysis, insertRowsStatement);
+        (InsertRowsStatement) removeLogicalView(analysis, insertRowsStatement, 
context);
     if (analysis.isFinishQueryAfterAnalyze()) {
       return analysis;
     }
@@ -2968,7 +2970,8 @@ public class AnalyzeVisitor extends 
StatementVisitor<Analysis, MPPQueryContext>
         insertMultiTabletsStatement,
         () -> SchemaValidator.validate(schemaFetcher, 
insertMultiTabletsStatement, context));
     InsertMultiTabletsStatement realStatement =
-        (InsertMultiTabletsStatement) removeLogicalView(analysis, 
insertMultiTabletsStatement);
+        (InsertMultiTabletsStatement)
+            removeLogicalView(analysis, insertMultiTabletsStatement, context);
     if (analysis.isFinishQueryAfterAnalyze()) {
       return analysis;
     }
@@ -2993,7 +2996,7 @@ public class AnalyzeVisitor extends 
StatementVisitor<Analysis, MPPQueryContext>
         insertRowsOfOneDeviceStatement,
         () -> SchemaValidator.validate(schemaFetcher, 
insertRowsOfOneDeviceStatement, context));
     InsertBaseStatement realInsertStatement =
-        removeLogicalView(analysis, insertRowsOfOneDeviceStatement);
+        removeLogicalView(analysis, insertRowsOfOneDeviceStatement, context);
     if (analysis.isFinishQueryAfterAnalyze()) {
       return analysis;
     }
@@ -3725,6 +3728,15 @@ public class AnalyzeVisitor extends 
StatementVisitor<Analysis, MPPQueryContext>
         }
       }
       deleteDataStatement.setPathList(new ArrayList<>(deletePatternSet));
+      // Deletes through a writable view are rewritten to the view's source 
series as well.
+      final TSStatus systemDatabaseStatus =
+          TreeAccessCheckVisitor.checkSystemDatabaseWrite(
+              context, deleteDataStatement.getPathList());
+      if (systemDatabaseStatus != null) {
+        analysis.setFinishQueryAfterAnalyze(true);
+        analysis.setFailStatus(systemDatabaseStatus);
+        return analysis;
+      }
     } else {
       for (PartialPath devicePattern : patternTree.getAllDevicePaths()) {
         schemaTree
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/analyze/schema/AutoCreateSchemaExecutor.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/analyze/schema/AutoCreateSchemaExecutor.java
index fe8695c859d..2c5836c96c8 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/analyze/schema/AutoCreateSchemaExecutor.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/analyze/schema/AutoCreateSchemaExecutor.java
@@ -567,8 +567,7 @@ class AutoCreateSchemaExecutor {
 
   private void internalActivateTemplate(PartialPath devicePath, 
MPPQueryContext context) {
     ActivateTemplateStatement statement = new 
ActivateTemplateStatement(devicePath);
-    TSStatus status =
-        AuthorityChecker.checkAuthority(statement, 
context.getSession().getUserEntity());
+    TSStatus status = AuthorityChecker.checkAuthority(statement, context);
     if (status.getCode() != TSStatusCode.SUCCESS_STATUS.getStatusCode()) {
       throw new IoTDBRuntimeException(status.getMessage(), status.getCode());
     }
@@ -585,8 +584,7 @@ class AutoCreateSchemaExecutor {
       MPPQueryContext context) {
     InternalBatchActivateTemplateStatement statement =
         new 
InternalBatchActivateTemplateStatement(devicesNeedActivateTemplate);
-    TSStatus status =
-        AuthorityChecker.checkAuthority(statement, 
context.getSession().getUserEntity());
+    TSStatus status = AuthorityChecker.checkAuthority(statement, context);
     if (status.getCode() != TSStatusCode.SUCCESS_STATUS.getStatusCode()) {
       throw new IoTDBRuntimeException(status.getMessage(), status.getCode());
     }
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/security/AccessControlImpl.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/security/AccessControlImpl.java
index 0db343b44d9..d0f847878a2 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/security/AccessControlImpl.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/security/AccessControlImpl.java
@@ -606,6 +606,12 @@ public class AccessControlImpl implements AccessControl {
       IAuditEntity auditEntity, IDeviceID device, String measurementId) {
     try {
       PartialPath path = new MeasurementPath(device, measurementId);
+      final TSStatus systemDatabaseStatus =
+          TreeAccessCheckVisitor.checkSystemDatabaseWrite(
+              auditEntity, Collections.singletonList(path));
+      if (systemDatabaseStatus != null) {
+        return systemDatabaseStatus;
+      }
       // Apache IoTDB does not support external writes to the audit database.
       if (includeByAuditTreeDB(path)
           && 
!auditEntity.getUsername().equals(AuthorityChecker.INTERNAL_AUDIT_USER)) {
@@ -633,6 +639,20 @@ public class AccessControlImpl implements AccessControl {
   @Override
   public TSStatus checkCanAlterView(
       IAuditEntity entity, List<PartialPath> sourcePaths, List<PartialPath> 
targetPaths) {
+    if (sourcePaths != null) {
+      final TSStatus sourceSystemDatabaseStatus =
+          TreeAccessCheckVisitor.checkSystemDatabaseWrite(entity, sourcePaths);
+      if (sourceSystemDatabaseStatus != null) {
+        return sourceSystemDatabaseStatus;
+      }
+    }
+    if (targetPaths != null) {
+      final TSStatus targetSystemDatabaseStatus =
+          TreeAccessCheckVisitor.checkSystemDatabaseWrite(entity, targetPaths);
+      if (targetSystemDatabaseStatus != null) {
+        return targetSystemDatabaseStatus;
+      }
+    }
     if (AuthorityChecker.SUPER_USER_ID == entity.getUserId()) {
       DNAuditLogger.getInstance()
           .recordObjectAuthenticationAuditLog(
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/security/TreeAccessCheckContext.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/security/TreeAccessCheckContext.java
index 5dbb91cdbe6..ac09431fc2c 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/security/TreeAccessCheckContext.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/security/TreeAccessCheckContext.java
@@ -32,6 +32,7 @@ public class TreeAccessCheckContext implements IAuditEntity {
   private final long userId;
   private final String username;
   private final String cliHostname;
+  private boolean internal;
 
   public TreeAccessCheckContext(long userId, String username, String 
cliHostname) {
     this.userId = userId;
@@ -54,6 +55,16 @@ public class TreeAccessCheckContext implements IAuditEntity {
     return cliHostname;
   }
 
+  @Override
+  public boolean isInternal() {
+    return internal;
+  }
+
+  public TreeAccessCheckContext setInternal(final boolean internal) {
+    this.internal = internal;
+    return this;
+  }
+
   private AuditEventType auditEventType;
   private AuditLogOperation auditLogOperation;
   private List<PrivilegeType> privilegeTypeList;
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/security/TreeAccessCheckVisitor.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/security/TreeAccessCheckVisitor.java
index ae1ba598379..c1945f9d728 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/security/TreeAccessCheckVisitor.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/security/TreeAccessCheckVisitor.java
@@ -30,6 +30,7 @@ import org.apache.iotdb.commons.path.MeasurementPath;
 import org.apache.iotdb.commons.path.PartialPath;
 import org.apache.iotdb.commons.path.PathPatternTree;
 import org.apache.iotdb.commons.path.PathPatternTreeUtils;
+import org.apache.iotdb.commons.schema.InternalDatabaseUtils;
 import org.apache.iotdb.commons.schema.SchemaConstant;
 import org.apache.iotdb.commons.schema.table.Audit;
 import org.apache.iotdb.commons.utils.StatusUtils;
@@ -180,6 +181,7 @@ import com.google.common.collect.ImmutableList;
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
 import java.util.Objects;
@@ -278,6 +280,12 @@ public class TreeAccessCheckVisitor extends 
StatementVisitor<TSStatus, TreeAcces
   public TSStatus visitSetSchemaTemplate(
       SetSchemaTemplateStatement setSchemaTemplateStatement, 
TreeAccessCheckContext context) {
     context.setAuditLogOperation(AuditLogOperation.DDL);
+    final TSStatus systemDatabaseStatus =
+        checkSystemDatabaseWrite(
+            context, 
Collections.singletonList(setSchemaTemplateStatement.getPath()));
+    if (systemDatabaseStatus != null) {
+      return systemDatabaseStatus;
+    }
     // root.__audit can never be set template
     TSStatus status =
         checkUnsupportedAuditDatabaseWrite(
@@ -379,6 +387,11 @@ public class TreeAccessCheckVisitor extends 
StatementVisitor<TSStatus, TreeAcces
   @Override
   public TSStatus visitUnsetSchemaTemplate(
       UnsetSchemaTemplateStatement unsetSchemaTemplateStatement, 
TreeAccessCheckContext context) {
+    final TSStatus systemDatabaseStatus =
+        checkSystemDatabaseWrite(context, 
unsetSchemaTemplateStatement.getPaths());
+    if (systemDatabaseStatus != null) {
+      return systemDatabaseStatus;
+    }
     return checkSystemAuth(
         context.setAuditLogOperation(AuditLogOperation.DDL),
         () -> unsetSchemaTemplateStatement.getPaths().toString());
@@ -437,6 +450,34 @@ public class TreeAccessCheckVisitor extends 
StatementVisitor<TSStatus, TreeAcces
             
.setMessage(getUnsupportedAuditDatabaseOperationMessage(TREE_MODEL_AUDIT_DATABASE));
       }
     }
+    final TSStatus systemDatabaseStatus = checkSystemDatabaseWrite(context, 
paths);
+    if (systemDatabaseStatus != null) {
+      return systemDatabaseStatus;
+    }
+    // A logical view redirects inserts and deletes to its source series 
during analysis, so the
+    // source paths must be checked here as well.
+    if (statement.getSourcePaths().fullPathList != null) {
+      final TSStatus sourceSystemDatabaseStatus =
+          checkSystemDatabaseWrite(context, 
statement.getSourcePaths().fullPathList);
+      if (sourceSystemDatabaseStatus != null) {
+        return sourceSystemDatabaseStatus;
+      }
+    }
+    if (statement.getSourcePaths().prefixOfPathsGroup != null) {
+      final TSStatus sourceSystemDatabaseStatus =
+          checkSystemDatabaseWrite(
+              context, 
Collections.singletonList(statement.getSourcePaths().prefixOfPathsGroup));
+      if (sourceSystemDatabaseStatus != null) {
+        return sourceSystemDatabaseStatus;
+      }
+    }
+    if (statement.getQueryStatement() != null) {
+      final TSStatus sourceSystemDatabaseStatus =
+          checkSystemDatabaseWrite(context, 
statement.getQueryStatement().getPaths());
+      if (sourceSystemDatabaseStatus != null) {
+        return sourceSystemDatabaseStatus;
+      }
+    }
 
     if (AuthorityChecker.SUPER_USER.equals(context.getUsername())) {
       statement.setCanSeeAuditDB(true);
@@ -496,6 +537,35 @@ public class TreeAccessCheckVisitor extends 
StatementVisitor<TSStatus, TreeAcces
   public TSStatus visitAlterLogicalView(
       AlterLogicalViewStatement statement, TreeAccessCheckContext context) {
     context.setAuditLogOperation(AuditLogOperation.DDL);
+    final List<PartialPath> targetPathList = statement.getTargetPathList();
+    if (targetPathList != null) {
+      final TSStatus systemDatabaseStatus = checkSystemDatabaseWrite(context, 
targetPathList);
+      if (systemDatabaseStatus != null) {
+        return systemDatabaseStatus;
+      }
+    }
+    if (statement.getSourcePaths().fullPathList != null) {
+      final TSStatus sourceSystemDatabaseStatus =
+          checkSystemDatabaseWrite(context, 
statement.getSourcePaths().fullPathList);
+      if (sourceSystemDatabaseStatus != null) {
+        return sourceSystemDatabaseStatus;
+      }
+    }
+    if (statement.getSourcePaths().prefixOfPathsGroup != null) {
+      final TSStatus sourceSystemDatabaseStatus =
+          checkSystemDatabaseWrite(
+              context, 
Collections.singletonList(statement.getSourcePaths().prefixOfPathsGroup));
+      if (sourceSystemDatabaseStatus != null) {
+        return sourceSystemDatabaseStatus;
+      }
+    }
+    if (statement.getQueryStatement() != null) {
+      final TSStatus sourceSystemDatabaseStatus =
+          checkSystemDatabaseWrite(context, 
statement.getQueryStatement().getPaths());
+      if (sourceSystemDatabaseStatus != null) {
+        return sourceSystemDatabaseStatus;
+      }
+    }
     if (AuthorityChecker.SUPER_USER.equals(context.getUsername())) {
       statement.setCanSeeAuditDB(true);
       if (statement.getQueryStatement() != null) {
@@ -1060,6 +1130,18 @@ public class TreeAccessCheckVisitor extends 
StatementVisitor<TSStatus, TreeAcces
       return new TSStatus(TSStatusCode.NO_PERMISSION.getStatusCode())
           
.setMessage(getReservedDatabaseNameErrorMsg(TREE_MODEL_AUDIT_DATABASE));
     }
+    // The system database is created by the database itself, not through SQL.
+    if (!context.isInternal()
+        && 
InternalDatabaseUtils.isSystemDatabasePath(statement.getDatabasePath())) {
+      context
+          .setDatabase(statement.getDatabasePath().getFullPath())
+          .setPrivilegeType(PrivilegeType.MANAGE_DATABASE)
+          .setAuditLogOperation(AuditLogOperation.DDL);
+      AUDIT_LOGGER.recordObjectAuthenticationAuditLog(
+          context.setResult(false), statement.getDatabasePath()::getFullPath);
+      return new TSStatus(TSStatusCode.NO_PERMISSION.getStatusCode())
+          
.setMessage(getReservedDatabaseNameErrorMsg(SchemaConstant.SYSTEM_DATABASE));
+    }
     return checkCreateOrAlterDatabasePermission(
         context.setAuditLogOperation(AuditLogOperation.DDL), 
statement.getDatabasePath());
   }
@@ -1067,6 +1149,12 @@ public class TreeAccessCheckVisitor extends 
StatementVisitor<TSStatus, TreeAcces
   @Override
   public TSStatus visitAlterDatabase(
       DatabaseSchemaStatement databaseSchemaStatement, TreeAccessCheckContext 
context) {
+    final TSStatus systemDatabaseStatus =
+        checkSystemDatabaseWrite(
+            context, 
Collections.singletonList(databaseSchemaStatement.getDatabasePath()));
+    if (systemDatabaseStatus != null) {
+      return systemDatabaseStatus;
+    }
     return checkCreateOrAlterDatabasePermission(
         context.setAuditLogOperation(AuditLogOperation.DDL),
         databaseSchemaStatement.getDatabasePath());
@@ -1116,6 +1204,14 @@ public class TreeAccessCheckVisitor extends 
StatementVisitor<TSStatus, TreeAcces
     context
         .setAuditLogOperation(AuditLogOperation.DDL)
         .setDatabase(statement.getPrefixPath().toString());
+    if (!context.isInternal()
+        && SchemaConstant.SYSTEM_DATABASE.equals(String.join(".", 
statement.getPrefixPath()))) {
+      AUDIT_LOGGER.recordObjectAuthenticationAuditLog(
+          
context.setPrivilegeType(PrivilegeType.MANAGE_DATABASE).setResult(false),
+          () -> String.join(".", statement.getPrefixPath()));
+      return new TSStatus(TSStatusCode.NO_PERMISSION.getStatusCode())
+          
.setMessage(getUnsupportedAuditDatabaseOperationMessage(SchemaConstant.SYSTEM_DATABASE));
+    }
     for (String prefixPath : statement.getPrefixPath()) {
       // root.__audit can never be deleted
       if (TREE_MODEL_AUDIT_DATABASE.equals(prefixPath)) {
@@ -1183,6 +1279,27 @@ public class TreeAccessCheckVisitor extends 
StatementVisitor<TSStatus, TreeAcces
   }
 
   // ==================================== data related 
========================================
+  /**
+   * Rejects writes to {@code root.__system} from sessions that are not 
dedicated internal writers.
+   * Callers must pass the paths that will actually be written, including the 
source paths of a
+   * writable view, because analysis rewrites inserts and deletes to those 
source series.
+   */
+  public static TSStatus checkSystemDatabaseWrite(
+      final IAuditEntity context, final Collection<? extends PartialPath> 
checkedPaths) {
+    if (context.isInternal()) {
+      return null;
+    }
+    for (PartialPath path : checkedPaths) {
+      if (InternalDatabaseUtils.isSystemDatabasePath(path)) {
+        
AUDIT_LOGGER.recordObjectAuthenticationAuditLog(context.setResult(false), 
path::toString);
+        return new TSStatus(TSStatusCode.NO_PERMISSION.getStatusCode())
+            .setMessage(
+                
getUnsupportedAuditDatabaseOperationMessage(SchemaConstant.SYSTEM_DATABASE));
+      }
+    }
+    return null;
+  }
+
   @Override
   public TSStatus visitInsertBase(InsertBaseStatement statement, 
TreeAccessCheckContext context) {
     
context.setAuditLogOperation(AuditLogOperation.DML).setPrivilegeType(PrivilegeType.WRITE_DATA);
@@ -1201,6 +1318,11 @@ public class TreeAccessCheckVisitor extends 
StatementVisitor<TSStatus, TreeAcces
       return new TSStatus(TSStatusCode.NO_PERMISSION.getStatusCode())
           
.setMessage(getUnsupportedAuditDatabaseOperationMessage(TREE_MODEL_AUDIT_DATABASE));
     }
+    final TSStatus unsupportedSystemStatus =
+        checkSystemDatabaseWrite(context, 
statement.getDevicePathsStream().toList());
+    if (unsupportedSystemStatus != null) {
+      return unsupportedSystemStatus;
+    }
 
     if (AuthorityChecker.SUPER_USER.equals(context.getUsername())) {
       AUDIT_LOGGER.recordObjectAuthenticationAuditLog(
@@ -1301,6 +1423,13 @@ public class TreeAccessCheckVisitor extends 
StatementVisitor<TSStatus, TreeAcces
       PrivilegeType permission,
       Function<List<? extends PartialPath>, String> auditObjectFormatter) {
     context.setPrivilegeType(permission);
+    if (permission == PrivilegeType.WRITE_SCHEMA || permission == 
PrivilegeType.WRITE_DATA) {
+      final TSStatus systemDatabaseStatus =
+          checkSystemDatabaseWrite(context, checkedPathsSupplier.get());
+      if (systemDatabaseStatus != null) {
+        return systemDatabaseStatus;
+      }
+    }
     if (AuthorityChecker.SUPER_USER.equals(context.getUsername())) {
       AUDIT_LOGGER.recordObjectAuthenticationAuditLog(
           context.setResult(true), () -> 
auditObjectFormatter.apply(checkedPathsSupplier.get()));
@@ -1760,6 +1889,11 @@ public class TreeAccessCheckVisitor extends 
StatementVisitor<TSStatus, TreeAcces
             
.setMessage(getUnsupportedAuditDatabaseOperationMessage(TREE_MODEL_AUDIT_DATABASE));
       }
     }
+    final TSStatus systemDatabaseStatus =
+        checkSystemDatabaseWrite(context, statement.getPathPatternList());
+    if (systemDatabaseStatus != null) {
+      return systemDatabaseStatus;
+    }
     return checkTimeSeriesPermission(context, statement::getPaths, 
PrivilegeType.WRITE_SCHEMA);
   }
 
@@ -2068,6 +2202,10 @@ public class TreeAccessCheckVisitor extends 
StatementVisitor<TSStatus, TreeAcces
   public TSStatus visitSetTTL(SetTTLStatement statement, 
TreeAccessCheckContext context) {
     
context.setPrivilegeType(PrivilegeType.SYSTEM).setAuditLogOperation(AuditLogOperation.DDL);
     List<PartialPath> checkedPaths = statement.getPaths();
+    final TSStatus systemDatabaseStatus = checkSystemDatabaseWrite(context, 
checkedPaths);
+    if (systemDatabaseStatus != null) {
+      return systemDatabaseStatus;
+    }
     boolean[] pathsNotEndWithMultiLevelWildcard = null;
     for (int i = 0; i < checkedPaths.size(); i++) {
       PartialPath checkedPath = checkedPaths.get(i);
diff --git 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/service/metrics/IoTDBInternalLocalReporter.java
 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/service/metrics/IoTDBInternalLocalReporter.java
index ad13233dbac..5749d99b66f 100644
--- 
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/service/metrics/IoTDBInternalLocalReporter.java
+++ 
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/service/metrics/IoTDBInternalLocalReporter.java
@@ -97,12 +97,13 @@ public class IoTDBInternalLocalReporter extends 
IoTDBInternalReporter {
         Executors.newSingleThreadScheduledExecutor(),
         Coordinator.getInstance(),
         new SessionInfo(
-            0,
-            new UserEntity(
-                AuthorityChecker.SUPER_USER_ID,
-                AuthorityChecker.SUPER_USER,
-                
IoTDBDescriptor.getInstance().getConfig().getInternalAddress()),
-            ZoneId.systemDefault()),
+                0,
+                new UserEntity(
+                    AuthorityChecker.SUPER_USER_ID,
+                    AuthorityChecker.SUPER_USER,
+                    
IoTDBDescriptor.getInstance().getConfig().getInternalAddress()),
+                ZoneId.systemDefault())
+            .setInternal(true),
         ClusterPartitionFetcher.getInstance(),
         ClusterSchemaFetcher.getInstance());
 
diff --git 
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/auth/TreeAccessTest.java
 
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/auth/TreeAccessTest.java
index 04fdde70613..1bd9ffb2f7a 100644
--- 
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/auth/TreeAccessTest.java
+++ 
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/auth/TreeAccessTest.java
@@ -23,12 +23,21 @@ import org.apache.iotdb.commons.auth.entity.PrivilegeType;
 import org.apache.iotdb.commons.auth.entity.User;
 import org.apache.iotdb.commons.conf.CommonConfig;
 import org.apache.iotdb.commons.conf.CommonDescriptor;
+import org.apache.iotdb.commons.path.MeasurementPath;
 import org.apache.iotdb.commons.path.PartialPath;
 import 
org.apache.iotdb.db.queryengine.plan.relational.security.TreeAccessCheckContext;
 import 
org.apache.iotdb.db.queryengine.plan.relational.security.TreeAccessCheckVisitor;
 import org.apache.iotdb.db.queryengine.plan.statement.AuthorType;
+import org.apache.iotdb.db.queryengine.plan.statement.crud.InsertStatement;
+import 
org.apache.iotdb.db.queryengine.plan.statement.metadata.AlterTimeSeriesStatement;
 import 
org.apache.iotdb.db.queryengine.plan.statement.metadata.CountLevelTimeSeriesStatement;
 import 
org.apache.iotdb.db.queryengine.plan.statement.metadata.CountTimeSeriesStatement;
+import 
org.apache.iotdb.db.queryengine.plan.statement.metadata.CreateTimeSeriesStatement;
+import 
org.apache.iotdb.db.queryengine.plan.statement.metadata.DatabaseSchemaStatement;
+import 
org.apache.iotdb.db.queryengine.plan.statement.metadata.DeleteTimeSeriesStatement;
+import 
org.apache.iotdb.db.queryengine.plan.statement.metadata.template.ActivateTemplateStatement;
+import 
org.apache.iotdb.db.queryengine.plan.statement.metadata.template.SetSchemaTemplateStatement;
+import 
org.apache.iotdb.db.queryengine.plan.statement.metadata.view.CreateLogicalViewStatement;
 import org.apache.iotdb.db.queryengine.plan.statement.sys.AuthorStatement;
 import org.apache.iotdb.rpc.TSStatusCode;
 
@@ -238,6 +247,99 @@ public class TreeAccessTest {
             new TreeAccessCheckContext(10000L, "user1", ""), new 
PartialPath("root.sg")));
   }
 
+  @Test
+  public void testSystemDatabaseWriteRequiresInternalCaller() throws Exception 
{
+    final TreeAccessCheckVisitor visitor = new TreeAccessCheckVisitor();
+    final TreeAccessCheckContext externalCaller =
+        new TreeAccessCheckContext(AuthorityChecker.SUPER_USER_ID, 
AuthorityChecker.SUPER_USER, "");
+    final TreeAccessCheckContext internalCaller =
+        new TreeAccessCheckContext(AuthorityChecker.SUPER_USER_ID, 
AuthorityChecker.SUPER_USER, "")
+            .setInternal(true);
+
+    final InsertStatement systemInsert = new InsertStatement();
+    systemInsert.setDevice(new PartialPath("root.__system.d"));
+    systemInsert.setMeasurementList(new String[] {"s"});
+    Assert.assertEquals(
+        TSStatusCode.NO_PERMISSION.getStatusCode(),
+        visitor.visitInsert(systemInsert, externalCaller).getCode());
+    Assert.assertEquals(
+        TSStatusCode.SUCCESS_STATUS.getStatusCode(),
+        visitor.visitInsert(systemInsert, internalCaller).getCode());
+
+    final CreateTimeSeriesStatement systemCreate = new 
CreateTimeSeriesStatement();
+    systemCreate.setPath(new MeasurementPath("root.__system.d.s"));
+    Assert.assertEquals(
+        TSStatusCode.NO_PERMISSION.getStatusCode(),
+        visitor.visitCreateTimeseries(systemCreate, externalCaller).getCode());
+    Assert.assertEquals(
+        TSStatusCode.SUCCESS_STATUS.getStatusCode(),
+        visitor.visitCreateTimeseries(systemCreate, internalCaller).getCode());
+
+    final DeleteTimeSeriesStatement deleteTimeSeries =
+        new DeleteTimeSeriesStatement(
+            Collections.singletonList(new PartialPath("root.__system.**")));
+    Assert.assertEquals(
+        TSStatusCode.NO_PERMISSION.getStatusCode(),
+        visitor.visitDeleteTimeSeries(deleteTimeSeries, 
externalCaller).getCode());
+    Assert.assertEquals(
+        TSStatusCode.SUCCESS_STATUS.getStatusCode(),
+        visitor.visitDeleteTimeSeries(deleteTimeSeries, 
internalCaller).getCode());
+
+    final AlterTimeSeriesStatement alterTimeSeries = new 
AlterTimeSeriesStatement();
+    alterTimeSeries.setPath(new MeasurementPath("root.__system.d.s"));
+    Assert.assertEquals(
+        TSStatusCode.NO_PERMISSION.getStatusCode(),
+        visitor.visitAlterTimeSeries(alterTimeSeries, 
externalCaller).getCode());
+    Assert.assertEquals(
+        TSStatusCode.SUCCESS_STATUS.getStatusCode(),
+        visitor.visitAlterTimeSeries(alterTimeSeries, 
internalCaller).getCode());
+
+    final ActivateTemplateStatement activateTemplate =
+        new ActivateTemplateStatement(new PartialPath("root.__system.d"));
+    Assert.assertEquals(
+        TSStatusCode.SUCCESS_STATUS.getStatusCode(),
+        visitor.visitActivateTemplate(activateTemplate, 
internalCaller).getCode());
+
+    final SetSchemaTemplateStatement setSchemaTemplate =
+        new SetSchemaTemplateStatement("t", new PartialPath("root.__system"));
+    Assert.assertEquals(
+        TSStatusCode.NO_PERMISSION.getStatusCode(),
+        visitor.visitSetSchemaTemplate(setSchemaTemplate, 
externalCaller).getCode());
+    Assert.assertEquals(
+        TSStatusCode.SUCCESS_STATUS.getStatusCode(),
+        visitor.visitSetSchemaTemplate(setSchemaTemplate, 
internalCaller).getCode());
+
+    final CreateLogicalViewStatement systemView = new 
CreateLogicalViewStatement();
+    systemView.setTargetFullPaths(Collections.singletonList(new 
PartialPath("root.sg.v.s")));
+    systemView.setSourceFullPaths(Collections.singletonList(new 
PartialPath("root.__system.d.s")));
+    Assert.assertEquals(
+        TSStatusCode.NO_PERMISSION.getStatusCode(),
+        visitor.visitCreateLogicalView(systemView, externalCaller).getCode());
+
+    // The system database cannot be created through SQL.
+    final DatabaseSchemaStatement systemDatabaseCreate =
+        new 
DatabaseSchemaStatement(DatabaseSchemaStatement.DatabaseSchemaStatementType.CREATE);
+    systemDatabaseCreate.setDatabasePath(new PartialPath("root.__system"));
+    Assert.assertEquals(
+        TSStatusCode.NO_PERMISSION.getStatusCode(),
+        visitor.visitSetDatabase(systemDatabaseCreate, 
externalCaller).getCode());
+
+    final DatabaseSchemaStatement systemCaseVariantCreate =
+        new 
DatabaseSchemaStatement(DatabaseSchemaStatement.DatabaseSchemaStatementType.CREATE);
+    systemCaseVariantCreate.setDatabasePath(new PartialPath("root.__System"));
+    Assert.assertEquals(
+        TSStatusCode.NO_PERMISSION.getStatusCode(),
+        visitor.visitSetDatabase(systemCaseVariantCreate, 
externalCaller).getCode());
+
+    // Writes to a normal database are unaffected.
+    final InsertStatement normalInsert = new InsertStatement();
+    normalInsert.setDevice(new PartialPath("root.sg.d"));
+    normalInsert.setMeasurementList(new String[] {"s"});
+    Assert.assertEquals(
+        TSStatusCode.SUCCESS_STATUS.getStatusCode(),
+        visitor.visitInsert(normalInsert, externalCaller).getCode());
+  }
+
   @Test
   public void testPathLogLimitDoesNotLimitPermissionCheck() throws Exception {
     final CommonConfig config = CommonDescriptor.getInstance().getConfig();
diff --git 
a/iotdb-core/node-commons/src/assembly/resources/conf/iotdb-system.properties.template
 
b/iotdb-core/node-commons/src/assembly/resources/conf/iotdb-system.properties.template
index d952bce23ca..4db46c0cdbf 100644
--- 
a/iotdb-core/node-commons/src/assembly/resources/conf/iotdb-system.properties.template
+++ 
b/iotdb-core/node-commons/src/assembly/resources/conf/iotdb-system.properties.template
@@ -391,6 +391,8 @@ prometheus_reporter_async_update=true
 # The reporters of metric module to report metrics
 # If there are more than one reporter, please separate them by commas ",".
 # Options: [JMX, PROMETHEUS]
+# The IOTDB session reporter is not supported: it writes to root.__system, 
which rejects writes
+# from external client sessions.
 # effectiveMode: restart
 # Datatype: String
 cn_metric_reporter_list=
@@ -414,6 +416,8 @@ cn_metric_prometheus_reporter_port=9091
 # The reporters of metric module to report metrics
 # If there are more than one reporter, please separate them by commas ",".
 # Options: [JMX, PROMETHEUS]
+# The IOTDB session reporter is not supported: it writes to root.__system, 
which rejects writes
+# from external client sessions.
 # effectiveMode: restart
 # Datatype: String
 dn_metric_reporter_list=
diff --git 
a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/audit/IAuditEntity.java
 
b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/audit/IAuditEntity.java
index 0b46b0fd286..ad4c85f18d3 100644
--- 
a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/audit/IAuditEntity.java
+++ 
b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/audit/IAuditEntity.java
@@ -58,4 +58,9 @@ public interface IAuditEntity {
   String getSqlString();
 
   IAuditEntity setSqlString(String sqlString);
+
+  /** Whether the caller is a dedicated internal writer of the database 
itself. */
+  default boolean isInternal() {
+    return false;
+  }
 }
diff --git 
a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/queryengine/common/SessionInfo.java
 
b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/queryengine/common/SessionInfo.java
index de681067c93..d6a0f4120ab 100644
--- 
a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/queryengine/common/SessionInfo.java
+++ 
b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/queryengine/common/SessionInfo.java
@@ -50,6 +50,10 @@ public class SessionInfo {
   // SessionInfo is sent to execution nodes, so this field does not need 
serialization.
   private final transient boolean enableAutoCreateSchema;
 
+  // Whether the session is opened by the database itself. Like 
enableAutoCreateSchema, this is
+  // only consulted during analysis and therefore does not need serialization.
+  private transient boolean internal;
+
   public SessionInfo(long sessionId, UserEntity userEntity, ZoneId zoneId) {
     this.sessionId = sessionId;
     this.userEntity = userEntity;
@@ -165,6 +169,15 @@ public class SessionInfo {
     return enableAutoCreateSchema;
   }
 
+  public boolean isInternal() {
+    return internal;
+  }
+
+  public SessionInfo setInternal(final boolean internal) {
+    this.internal = internal;
+    return this;
+  }
+
   public static SessionInfo deserializeFrom(final ByteBuffer buffer) {
     final long sessionId = ReadWriteIOUtils.readLong(buffer);
     final long userId = ReadWriteIOUtils.readLong(buffer);
diff --git 
a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/schema/InternalDatabaseUtils.java
 
b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/schema/InternalDatabaseUtils.java
new file mode 100644
index 00000000000..3ed5ba24fd0
--- /dev/null
+++ 
b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/schema/InternalDatabaseUtils.java
@@ -0,0 +1,52 @@
+/*
+ * 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.schema;
+
+import org.apache.iotdb.commons.path.PartialPath;
+import org.apache.iotdb.commons.schema.table.Audit;
+
+public final class InternalDatabaseUtils {
+
+  private InternalDatabaseUtils() {}
+
+  /** Whether the given database (or one of its descendants) is an internal 
database. */
+  public static boolean isInternalDatabase(final String databaseName) {
+    return isSystemDatabase(databaseName) || 
Audit.isAuditDatabase(databaseName);
+  }
+
+  /** Whether the given database (or one of its descendants) is {@code 
root.__system}. */
+  public static boolean isSystemDatabase(final String databaseName) {
+    return isDatabaseOrDescendant(databaseName, 
SchemaConstant.SYSTEM_DATABASE);
+  }
+
+  /** Whether the device or measurement path belongs to {@code root.__system}. 
*/
+  public static boolean isSystemDatabasePath(final PartialPath path) {
+    return path != null && isSystemDatabase(path.getFullPath());
+  }
+
+  // Internal database names are matched case-insensitively: root.__System is 
also internal.
+  private static boolean isDatabaseOrDescendant(
+      final String databaseName, final String internalDatabase) {
+    return databaseName != null
+        && databaseName.regionMatches(true, 0, internalDatabase, 0, 
internalDatabase.length())
+        && (databaseName.length() == internalDatabase.length()
+            || databaseName.charAt(internalDatabase.length()) == '.');
+  }
+}
diff --git 
a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/schema/table/Audit.java
 
b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/schema/table/Audit.java
index a33cda1b5dc..fa672344081 100644
--- 
a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/schema/table/Audit.java
+++ 
b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/schema/table/Audit.java
@@ -57,6 +57,7 @@ public class Audit {
         || isDatabaseOrDescendant(databaseName, TREE_MODEL_AUDIT_DATABASE);
   }
 
+  // Audit database names are matched case-insensitively: root.__Audit is also 
an audit database.
   private static boolean isDatabaseOrDescendant(
       final String databaseName, final String auditDatabaseName) {
     return databaseName != null
diff --git 
a/iotdb-core/node-commons/src/test/java/org/apache/iotdb/commons/schema/table/AuditTest.java
 
b/iotdb-core/node-commons/src/test/java/org/apache/iotdb/commons/schema/table/AuditTest.java
index ad66cd0f284..ddb01615798 100644
--- 
a/iotdb-core/node-commons/src/test/java/org/apache/iotdb/commons/schema/table/AuditTest.java
+++ 
b/iotdb-core/node-commons/src/test/java/org/apache/iotdb/commons/schema/table/AuditTest.java
@@ -34,6 +34,7 @@ public class AuditTest {
     assertTrue(Audit.isAuditDatabase("ROOT.__AUDIT"));
     assertTrue(Audit.isAuditDatabase("__audit.data"));
     assertTrue(Audit.isAuditDatabase("root.__audit.data"));
+    assertTrue(Audit.isAuditDatabase("root.__Audit.data"));
     assertFalse(Audit.isAuditDatabase("__audit_data"));
     assertFalse(Audit.isAuditDatabase("root.__audit_data"));
     assertFalse(Audit.isAuditDatabase(null));

Reply via email to