This is an automated email from the ASF dual-hosted git repository.
jt2594838 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 7558aa7ef80 Fix metadata cache and pipe edge cases (#18147)
7558aa7ef80 is described below
commit 7558aa7ef8012f94a082dbb51b55ef2906f60b75
Author: Caideyipi <[email protected]>
AuthorDate: Wed Jul 8 17:15:04 2026 +0800
Fix metadata cache and pipe edge cases (#18147)
---
.../agent/task/connection/PipeEventCollector.java | 15 +++--
.../realtime/PipeRealtimeDataRegionSource.java | 6 +-
.../schemaregion/IoTDBSchemaRegionSource.java | 4 +-
.../PipePlanTablePrivilegeParseVisitor.java | 17 +++++
.../db/schemaengine/table/DataNodeTableCache.java | 2 +-
.../PipePlanTablePatternParseVisitorTest.java | 49 +++++++++++++++
.../db/pipe/source/PipeRealtimeExtractTest.java | 73 ++++++++++++++++++++++
.../schemaengine/table/DataNodeTableCacheTest.java | 20 ++++++
8 files changed, 177 insertions(+), 9 deletions(-)
diff --git
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/agent/task/connection/PipeEventCollector.java
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/agent/task/connection/PipeEventCollector.java
index 2324a3d2707..5ecd81d92b4 100644
---
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/agent/task/connection/PipeEventCollector.java
+++
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/agent/task/connection/PipeEventCollector.java
@@ -35,6 +35,7 @@ import
org.apache.iotdb.db.pipe.event.common.tablet.PipeRawTabletInsertionEvent;
import org.apache.iotdb.db.pipe.event.common.terminate.PipeTerminateEvent;
import org.apache.iotdb.db.pipe.event.common.tsfile.PipeTsFileInsertionEvent;
import org.apache.iotdb.db.pipe.source.schemaregion.IoTDBSchemaRegionSource;
+import
org.apache.iotdb.db.pipe.source.schemaregion.PipePlanTablePrivilegeParseVisitor;
import
org.apache.iotdb.db.pipe.source.schemaregion.PipePlanTreePrivilegeParseVisitor;
import
org.apache.iotdb.db.queryengine.plan.planner.plan.node.write.AbstractDeleteDataNode;
import
org.apache.iotdb.db.queryengine.plan.planner.plan.node.write.DeleteDataNode;
@@ -202,12 +203,14 @@ public class PipeEventCollector implements EventCollector
{
.process(deleteDataEvent.getDeleteDataNode(),
deleteDataEvent.getTablePattern())
.flatMap(
planNode ->
-
IoTDBSchemaRegionSource.TABLE_PRIVILEGE_PARSE_VISITOR.process(
- planNode,
- new UserEntity(
- Long.parseLong(deleteDataEvent.getUserId()),
- deleteDataEvent.getUserName(),
- deleteDataEvent.getCliHostname()))))
+ new PipePlanTablePrivilegeParseVisitor(
+ deleteDataEvent.isSkipIfNoPrivileges())
+ .process(
+ planNode,
+ new UserEntity(
+
Long.parseLong(deleteDataEvent.getUserId()),
+ deleteDataEvent.getUserName(),
+ deleteDataEvent.getCliHostname()))))
.map(
planNode ->
new PipeDeleteDataNodeEvent(
diff --git
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/source/dataregion/realtime/PipeRealtimeDataRegionSource.java
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/source/dataregion/realtime/PipeRealtimeDataRegionSource.java
index 62136877fc1..0d03df454ed 100644
---
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/source/dataregion/realtime/PipeRealtimeDataRegionSource.java
+++
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/source/dataregion/realtime/PipeRealtimeDataRegionSource.java
@@ -448,7 +448,10 @@ public abstract class PipeRealtimeDataRegionSource
implements PipeExtractor {
if (lastEvent == null || !(lastEvent.getEvent() instanceof
PipeHeartbeatEvent)) {
break;
}
- pendingQueue.pollLast();
+ final PipeRealtimeEvent droppedEvent = (PipeRealtimeEvent)
pendingQueue.pollLast();
+ if (droppedEvent != null) {
+
droppedEvent.decreaseReferenceCount(PipeRealtimeDataRegionSource.class.getName(),
false);
+ }
}
final Event last = pendingQueue.peekLast();
if (last instanceof PipeRealtimeEvent
@@ -459,6 +462,7 @@ public abstract class PipeRealtimeDataRegionSource
implements PipeExtractor {
oldEvent
.getProgressIndex()
.updateToMinimumEqualOrIsAfterProgressIndex(event.getProgressIndex()));
+
event.decreaseReferenceCount(PipeRealtimeDataRegionSource.class.getName(),
false);
return;
}
pendingQueue.offer(event);
diff --git
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/source/schemaregion/IoTDBSchemaRegionSource.java
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/source/schemaregion/IoTDBSchemaRegionSource.java
index ecb27f1b266..8e33be13bdb 100644
---
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/source/schemaregion/IoTDBSchemaRegionSource.java
+++
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/source/schemaregion/IoTDBSchemaRegionSource.java
@@ -97,6 +97,7 @@ public class IoTDBSchemaRegionSource extends
IoTDBNonDataRegionSource {
// Local for exception
private PipePlanTreePrivilegeParseVisitor treePrivilegeParseVisitor;
+ private PipePlanTablePrivilegeParseVisitor tablePrivilegeParseVisitor;
private SchemaRegionId schemaRegionId;
private Set<PlanNodeType> listenedTypeSet = new HashSet<>();
@@ -123,6 +124,7 @@ public class IoTDBSchemaRegionSource extends
IoTDBNonDataRegionSource {
schemaRegionId = new SchemaRegionId(regionId);
listenedTypeSet =
SchemaRegionListeningFilter.parseListeningPlanTypeSet(parameters);
treePrivilegeParseVisitor = new
PipePlanTreePrivilegeParseVisitor(skipIfNoPrivileges);
+ tablePrivilegeParseVisitor = new
PipePlanTablePrivilegeParseVisitor(skipIfNoPrivileges);
PipeSchemaRegionSourceMetrics.getInstance().register(this);
if (regionId >= 0) {
@@ -294,7 +296,7 @@ public class IoTDBSchemaRegionSource extends
IoTDBNonDataRegionSource {
final Optional<PlanNode> result =
treePrivilegeParseVisitor
.process(((PipeSchemaRegionWritePlanEvent) event).getPlanNode(),
userEntity)
- .flatMap(planNode ->
TABLE_PRIVILEGE_PARSE_VISITOR.process(planNode, userEntity));
+ .flatMap(planNode -> tablePrivilegeParseVisitor.process(planNode,
userEntity));
if (result.isPresent()) {
return Optional.of(
new PipeSchemaRegionWritePlanEvent(result.get(),
event.isGeneratedByPipe()));
diff --git
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/source/schemaregion/PipePlanTablePrivilegeParseVisitor.java
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/source/schemaregion/PipePlanTablePrivilegeParseVisitor.java
index 4737b18cc76..f6c8b17bf67 100644
---
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/source/schemaregion/PipePlanTablePrivilegeParseVisitor.java
+++
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/pipe/source/schemaregion/PipePlanTablePrivilegeParseVisitor.java
@@ -20,9 +20,11 @@
package org.apache.iotdb.db.pipe.source.schemaregion;
import org.apache.iotdb.commons.audit.IAuditEntity;
+import org.apache.iotdb.commons.exception.auth.AccessDeniedException;
import org.apache.iotdb.commons.queryengine.plan.planner.plan.node.PlanNode;
import
org.apache.iotdb.commons.queryengine.plan.relational.metadata.QualifiedObjectName;
import org.apache.iotdb.db.auth.AuthorityChecker;
+import org.apache.iotdb.db.i18n.DataNodePipeMessages;
import org.apache.iotdb.db.queryengine.plan.planner.plan.node.PlanVisitor;
import
org.apache.iotdb.db.queryengine.plan.planner.plan.node.write.RelationalDeleteDataNode;
import
org.apache.iotdb.db.queryengine.plan.relational.planner.node.schema.CreateOrUpdateTableDeviceNode;
@@ -35,6 +37,17 @@ import java.util.stream.Collectors;
public class PipePlanTablePrivilegeParseVisitor
implements PlanVisitor<Optional<PlanNode>, IAuditEntity> {
+
+ private final boolean skip;
+
+ public PipePlanTablePrivilegeParseVisitor() {
+ this(true);
+ }
+
+ public PipePlanTablePrivilegeParseVisitor(final boolean skip) {
+ this.skip = skip;
+ }
+
@Override
public Optional<PlanNode> visitPlan(final PlanNode node, final IAuditEntity
auditEntity) {
return Optional.of(node);
@@ -77,6 +90,10 @@ public class PipePlanTablePrivilegeParseVisitor
new QualifiedObjectName(node.getDatabaseName(),
entry.getTableName()),
auditEntity))
.collect(Collectors.toList());
+ if (!skip && modEntries.size() != node.getModEntries().size()) {
+ throw new AccessDeniedException(
+ DataNodePipeMessages.NOT_HAS_PRIVILEGE_TO_TRANSFER_EVENT + node);
+ }
return !modEntries.isEmpty()
? Optional.of(
new RelationalDeleteDataNode(node.getPlanNodeId(), modEntries,
node.getDatabaseName()))
diff --git
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/schemaengine/table/DataNodeTableCache.java
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/schemaengine/table/DataNodeTableCache.java
index 142e3553715..d8a7e434a9f 100644
---
a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/schemaengine/table/DataNodeTableCache.java
+++
b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/schemaengine/table/DataNodeTableCache.java
@@ -198,7 +198,7 @@ public class DataNodeTableCache implements ITableCache {
}
databaseTableMap
.computeIfAbsent(database, k -> new ConcurrentHashMap<>())
- .put(tableName, oldTable);
+ .put(oldName, oldTable);
LOGGER.info(DataNodeSchemaMessages.ROLLBACK_RENAME_OLD_TABLE_SUCCESS,
database, oldName);
removeTableFromPreUpdateMap(database, oldName);
}
diff --git
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/pipe/source/PipePlanTablePatternParseVisitorTest.java
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/pipe/source/PipePlanTablePatternParseVisitorTest.java
index a4da1fc2fdf..283ca6e4090 100644
---
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/pipe/source/PipePlanTablePatternParseVisitorTest.java
+++
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/pipe/source/PipePlanTablePatternParseVisitorTest.java
@@ -19,14 +19,22 @@
package org.apache.iotdb.db.pipe.source;
+import org.apache.iotdb.commons.audit.IAuditEntity;
+import org.apache.iotdb.commons.audit.UserEntity;
+import org.apache.iotdb.commons.exception.auth.AccessDeniedException;
import org.apache.iotdb.commons.path.PartialPath;
import org.apache.iotdb.commons.pipe.datastructure.pattern.TablePattern;
import org.apache.iotdb.commons.queryengine.plan.planner.plan.node.PlanNode;
import org.apache.iotdb.commons.queryengine.plan.planner.plan.node.PlanNodeId;
+import
org.apache.iotdb.commons.queryengine.plan.relational.metadata.QualifiedObjectName;
+import org.apache.iotdb.db.auth.AuthorityChecker;
import org.apache.iotdb.db.pipe.source.schemaregion.IoTDBSchemaRegionSource;
+import
org.apache.iotdb.db.pipe.source.schemaregion.PipePlanTablePrivilegeParseVisitor;
import
org.apache.iotdb.db.queryengine.plan.planner.plan.node.write.RelationalDeleteDataNode;
import
org.apache.iotdb.db.queryengine.plan.relational.planner.node.schema.CreateOrUpdateTableDeviceNode;
import
org.apache.iotdb.db.queryengine.plan.relational.planner.node.schema.TableDeviceAttributeUpdateNode;
+import org.apache.iotdb.db.queryengine.plan.relational.security.AccessControl;
+import
org.apache.iotdb.db.queryengine.plan.relational.security.AllowAllAccessControl;
import org.apache.iotdb.db.storageengine.dataregion.memtable.DeviceIDFactory;
import
org.apache.iotdb.db.storageengine.dataregion.modification.DeletionPredicate;
import
org.apache.iotdb.db.storageengine.dataregion.modification.TableDeletionEntry;
@@ -126,4 +134,45 @@ public class PipePlanTablePatternParseVisitorTest {
tablePattern)
.orElse(null));
}
+
+ @Test
+ public void testDeleteDataPrivilegeRespectsSkipOption() {
+ final AccessControl oldAccessControl = AuthorityChecker.getAccessControl();
+ try {
+ AuthorityChecker.setAccessControl(
+ new AllowAllAccessControl() {
+ @Override
+ public boolean checkCanSelectFromTable4Pipe(
+ final String userName,
+ final QualifiedObjectName tableName,
+ final IAuditEntity auditEntity) {
+ return "ab".equals(tableName.getObjectName());
+ }
+ });
+
+ final RelationalDeleteDataNode input =
+ new RelationalDeleteDataNode(
+ new PlanNodeId(""),
+ Arrays.asList(
+ new TableDeletionEntry(
+ new DeletionPredicate("ab"), new
TimeRange(Long.MIN_VALUE, Long.MAX_VALUE)),
+ new TableDeletionEntry(
+ new DeletionPredicate("ac"), new
TimeRange(Long.MIN_VALUE, Long.MAX_VALUE))),
+ "db1");
+ final UserEntity userEntity = new UserEntity(1, "test", "127.0.0.1");
+
+ Assert.assertEquals(
+ new RelationalDeleteDataNode(
+ new PlanNodeId(""),
+ new TableDeletionEntry(
+ new DeletionPredicate("ab"), new TimeRange(Long.MIN_VALUE,
Long.MAX_VALUE)),
+ "db1"),
+ new PipePlanTablePrivilegeParseVisitor(true).process(input,
userEntity).orElse(null));
+ Assert.assertThrows(
+ AccessDeniedException.class,
+ () -> new PipePlanTablePrivilegeParseVisitor(false).process(input,
userEntity));
+ } finally {
+ AuthorityChecker.setAccessControl(oldAccessControl);
+ }
+ }
}
diff --git
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/pipe/source/PipeRealtimeExtractTest.java
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/pipe/source/PipeRealtimeExtractTest.java
index 1b714a80009..fc4a15a885b 100644
---
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/pipe/source/PipeRealtimeExtractTest.java
+++
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/pipe/source/PipeRealtimeExtractTest.java
@@ -33,6 +33,7 @@ import
org.apache.iotdb.commons.pipe.config.constant.PipeSourceConstant;
import
org.apache.iotdb.commons.pipe.config.plugin.configuraion.PipeTaskRuntimeConfiguration;
import
org.apache.iotdb.commons.pipe.config.plugin.env.PipeTaskSourceRuntimeEnvironment;
import org.apache.iotdb.commons.pipe.event.EnrichedEvent;
+import org.apache.iotdb.commons.pipe.event.ProgressReportEvent;
import org.apache.iotdb.commons.queryengine.plan.planner.plan.node.PlanNodeId;
import org.apache.iotdb.commons.utils.FileUtils;
import org.apache.iotdb.db.conf.IoTDBDescriptor;
@@ -517,6 +518,37 @@ public class PipeRealtimeExtractTest {
}
}
+ @Test
+ public void testProgressReportExtractionReleasesDroppedEvents() {
+ final TestRealtimeDataRegionSource source = new
TestRealtimeDataRegionSource();
+
+ final PipeRealtimeEvent heartbeatEvent =
+ PipeRealtimeEventFactory.createRealtimeEvent(dataRegion1, false);
+ heartbeatEvent.increaseReferenceCount(TEST_REFERENCE_HOLDER);
+ source.extractHeartbeatForTest(heartbeatEvent);
+ Assert.assertEquals(1, heartbeatEvent.getEvent().getReferenceCount());
+
+ final PipeRealtimeEvent progressEvent =
createProgressReportRealtimeEvent();
+ progressEvent.increaseReferenceCount(TEST_REFERENCE_HOLDER);
+ source.extractProgressReportEventForTest(progressEvent);
+
+ Assert.assertEquals(0, heartbeatEvent.getEvent().getReferenceCount());
+ Assert.assertEquals(1, progressEvent.getEvent().getReferenceCount());
+ Assert.assertEquals(1, source.getEventCount());
+
+ final PipeRealtimeEvent mergedProgressEvent =
createProgressReportRealtimeEvent();
+ mergedProgressEvent.increaseReferenceCount(TEST_REFERENCE_HOLDER);
+ source.extractProgressReportEventForTest(mergedProgressEvent);
+
+ Assert.assertEquals(0, mergedProgressEvent.getEvent().getReferenceCount());
+ Assert.assertEquals(1, progressEvent.getEvent().getReferenceCount());
+ Assert.assertEquals(1, source.getEventCount());
+
+ final Event queuedEvent = source.pollForTest();
+ Assert.assertSame(progressEvent, queuedEvent);
+ ((EnrichedEvent) queuedEvent).clearReferenceCount(TEST_REFERENCE_HOLDER);
+ }
+
private void removeTestPipeMeta() throws Exception {
final PipeMetaKeeper pipeMetaKeeper = getPipeMetaKeeper();
pipeMetaKeeper.acquireWriteLock();
@@ -545,6 +577,47 @@ public class PipeRealtimeExtractTest {
}
}
+ private PipeRealtimeEvent createProgressReportRealtimeEvent() {
+ final ProgressReportEvent progressReportEvent = new
ProgressReportEvent(null, 0, null);
+ progressReportEvent.bindProgressIndex(MinimumProgressIndex.INSTANCE);
+ return PipeRealtimeEventFactory.createRealtimeEvent(progressReportEvent);
+ }
+
+ private static class TestRealtimeDataRegionSource extends
PipeRealtimeDataRegionSource {
+
+ private void extractHeartbeatForTest(final PipeRealtimeEvent event) {
+ extractHeartbeat(event);
+ }
+
+ private void extractProgressReportEventForTest(final PipeRealtimeEvent
event) {
+ extractProgressReportEvent(event);
+ }
+
+ private Event pollForTest() {
+ return pendingQueue.directPoll();
+ }
+
+ @Override
+ protected void doExtract(final PipeRealtimeEvent event) {
+ // Not needed in this reference-counting unit test.
+ }
+
+ @Override
+ public Event supply() {
+ return pendingQueue.directPoll();
+ }
+
+ @Override
+ public boolean isNeedListenToTsFile() {
+ return false;
+ }
+
+ @Override
+ public boolean isNeedListenToInsertNode() {
+ return false;
+ }
+ }
+
private static class NoTsFileRealtimeDataRegionSource extends
PipeRealtimeDataRegionSource {
private final AtomicInteger observedTsFileEventCount = new
AtomicInteger(0);
diff --git
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/schemaengine/table/DataNodeTableCacheTest.java
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/schemaengine/table/DataNodeTableCacheTest.java
index 4b33991e3d7..2e3717cf233 100644
---
a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/schemaengine/table/DataNodeTableCacheTest.java
+++
b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/schemaengine/table/DataNodeTableCacheTest.java
@@ -91,6 +91,26 @@ public class DataNodeTableCacheTest {
}
}
+ @Test
+ public void rollbackRenameTableRestoresOldName() {
+ final DataNodeTableCache cache = DataNodeTableCache.getInstance();
+ final String newTableName = "table2";
+ cache.invalid(TABLE_CACHE_TEST_DATABASE);
+ try {
+ cache.preUpdateTable(TABLE_CACHE_TEST_DATABASE, createTable(TABLE_NAME),
null);
+ cache.commitUpdateTable(TABLE_CACHE_TEST_DATABASE, TABLE_NAME, null);
+
+ cache.preUpdateTable(TABLE_CACHE_TEST_DATABASE,
createTable(newTableName), TABLE_NAME);
+ cache.rollbackUpdateTable(TABLE_CACHE_TEST_DATABASE, newTableName,
TABLE_NAME);
+
+ Assert.assertEquals(
+ TABLE_NAME, cache.getTable(TABLE_CACHE_TEST_DATABASE,
TABLE_NAME).getTableName());
+ Assert.assertNull(cache.getTable(TABLE_CACHE_TEST_DATABASE,
newTableName, false));
+ } finally {
+ cache.invalid(TABLE_CACHE_TEST_DATABASE);
+ }
+ }
+
private Semaphore getFetchTableSemaphore(final DataNodeTableCache cache)
throws Exception {
final Field field =
DataNodeTableCache.class.getDeclaredField("fetchTableSemaphore");
field.setAccessible(true);