From 270f859473f2ed88a402cc78f05dcf17c805bb5e Mon Sep 17 00:00:00 2001 From: Yaobin Chen Date: Fri, 23 Oct 2026 15:49:33 +0800 Subject: [PATCH] Ignore non-field columns when collecting tree-view source names --- .../IoTDBTreeToTableViewAutoSyncIT.java | 26 +++++++++++++++ .../manager/ClusterSchemaManagerTest.java | 33 +++++++++++++++++++ .../commons/schema/table/TreeViewSchema.java | 14 ++++---- 3 files changed, 66 insertions(+), 7 deletions(-) diff --git a/integration-test/src/test/java/org/apache/iotdb/relational/it/schema/IoTDBTreeToTableViewAutoSyncIT.java b/integration-test/src/test/java/org/apache/iotdb/relational/it/schema/IoTDBTreeToTableViewAutoSyncIT.java index 17dfee8483602..7a0569f4a5f7f 100644 --- a/integration-test/src/test/java/org/apache/iotdb/relational/it/schema/IoTDBTreeToTableViewAutoSyncIT.java +++ b/integration-test/src/test/java/org/apache/iotdb/relational/it/schema/IoTDBTreeToTableViewAutoSyncIT.java @@ -600,6 +600,32 @@ public void testAlterTemplateOnAncestorPathAddsColumns() throws Exception { } } + /** + * A TAG column name is not a source measurement. It must not prevent an explicit FROM column from + * referencing a tree measurement with the same name. + */ + @Test + public void testTagColumnNameDoesNotBlockFromSource() throws Exception { + final String treeDb = "root.as21"; + final String viewDb = "as21"; + try (Connection tree = treeConnection(); + Connection table = tableConnection()) { + execute(tree, "CREATE DATABASE " + treeDb); + execute(tree, "CREATE TIMESERIES " + treeDb + ".d1.s1 DOUBLE"); + + execute(table, "CREATE DATABASE " + viewDb); + execute(table, "USE " + viewDb); + execute(table, "CREATE VIEW v (dev STRING TAG, dev_s1 STRING TAG) AS " + treeDb + ".**"); + + // Create the measurement after the view so that it is not auto-detected as a column. + execute(tree, "CREATE TIMESERIES " + treeDb + ".d1.dev_s1 DOUBLE"); + + execute(table, "ALTER VIEW v ADD COLUMN test DOUBLE FIELD FROM dev_s1"); + + Assert.assertEquals("DOUBLE", fieldColumns(table, viewDb, "v").get("test")); + } + } + // ==================== helpers ==================== private static Connection treeConnection() throws SQLException { diff --git a/iotdb-core/confignode/src/test/java/org/apache/iotdb/confignode/manager/ClusterSchemaManagerTest.java b/iotdb-core/confignode/src/test/java/org/apache/iotdb/confignode/manager/ClusterSchemaManagerTest.java index b86f62dec5e93..ce6d7df577eac 100644 --- a/iotdb-core/confignode/src/test/java/org/apache/iotdb/confignode/manager/ClusterSchemaManagerTest.java +++ b/iotdb-core/confignode/src/test/java/org/apache/iotdb/confignode/manager/ClusterSchemaManagerTest.java @@ -23,9 +23,13 @@ import org.apache.iotdb.commons.exception.table.ColumnInAlterException; import org.apache.iotdb.commons.exception.table.ColumnInDeletionException; import org.apache.iotdb.commons.exception.table.TableInDeletionException; +import org.apache.iotdb.commons.path.PartialPath; +import org.apache.iotdb.commons.schema.table.TableNodeStatus; +import org.apache.iotdb.commons.schema.table.TreeViewSchema; import org.apache.iotdb.commons.schema.table.TsTable; import org.apache.iotdb.commons.schema.table.TsTableInternalRPCUtil; import org.apache.iotdb.commons.schema.table.column.FieldColumnSchema; +import org.apache.iotdb.commons.schema.table.column.TagColumnSchema; import org.apache.iotdb.commons.schema.table.column.TsTableColumnSchema; import org.apache.iotdb.confignode.conf.ConfigNodeConfig; import org.apache.iotdb.confignode.conf.ConfigNodeDescriptor; @@ -37,6 +41,7 @@ import org.apache.iotdb.confignode.consensus.request.write.table.PreCreateTablePlan; import org.apache.iotdb.confignode.consensus.request.write.table.PreDeleteColumnPlan; import org.apache.iotdb.confignode.consensus.request.write.table.PreDeleteTablePlan; +import org.apache.iotdb.confignode.consensus.request.write.table.view.PreCreateTableViewPlan; import org.apache.iotdb.confignode.manager.consensus.ConsensusManager; import org.apache.iotdb.confignode.manager.node.NodeManager; import org.apache.iotdb.confignode.manager.partition.PartitionManager; @@ -373,6 +378,34 @@ private static FieldColumnSchema field(final String columnName) { return new FieldColumnSchema(columnName, TSDataType.INT32, TSEncoding.RLE, CompressionType.LZ4); } + @Test + public void testAddColumnIgnoresNonFieldColumnNamesAsSources() throws Exception { + final String database = "root.source_name_test"; + final String viewName = "tree_view"; + final ClusterSchemaInfo clusterSchemaInfo = new ClusterSchemaInfo(); + clusterSchemaInfo.createDatabase( + new DatabaseSchemaPlan( + ConfigPhysicalPlanType.CreateDatabase, + new TDatabaseSchema(database).setIsTableModel(true))); + final TsTable view = new TsTable(viewName); + Assert.assertNull(TreeViewSchema.setPathPattern(view, new PartialPath("root.sg.**"))); + view.addColumnSchema(new FieldColumnSchema("s1", TSDataType.INT32)); + view.addColumnSchema(new TagColumnSchema("dev_s1", TSDataType.STRING)); + clusterSchemaInfo.preCreateTableView( + new PreCreateTableViewPlan(database, view, TableNodeStatus.PRE_CREATE)); + clusterSchemaInfo.commitCreateTable(new CommitCreateTablePlan(database, viewName)); + final ClusterSchemaManager clusterSchemaManager = managerOf(clusterSchemaInfo); + + // The TAG column dev_s1 is not a source measurement, so it must not block this FROM column. + final FieldColumnSchema test = new FieldColumnSchema("test", TSDataType.INT32); + TreeViewSchema.setOriginalName(test, "dev_s1"); + final Pair result = + clusterSchemaManager.tableColumnCheckForColumnExtension( + database, viewName, new ArrayList<>(Collections.singletonList(test)), true); + Assert.assertEquals(TSStatusCode.SUCCESS_STATUS.getStatusCode(), result.getLeft().getCode()); + Assert.assertNotNull(result.getRight().getColumnSchema("test")); + } + private static ClusterSchemaManager managerOf(final ClusterSchemaInfo clusterSchemaInfo) { return new ClusterSchemaManager( Mockito.mock(IManager.class), diff --git a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/schema/table/TreeViewSchema.java b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/schema/table/TreeViewSchema.java index e1068a21d1558..e4ce221d71bfd 100644 --- a/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/schema/table/TreeViewSchema.java +++ b/iotdb-core/node-commons/src/main/java/org/apache/iotdb/commons/schema/table/TreeViewSchema.java @@ -24,6 +24,7 @@ import org.apache.iotdb.commons.i18n.SchemaMessages; import org.apache.iotdb.commons.path.PartialPath; import org.apache.iotdb.commons.path.PathPatternUtil; +import org.apache.iotdb.commons.schema.table.column.TsTableColumnCategory; import org.apache.iotdb.commons.schema.table.column.TsTableColumnSchema; import org.apache.iotdb.rpc.TSStatusCode; @@ -86,18 +87,17 @@ public static void setOriginalName(final TsTableColumnSchema schema, final Strin } /** - * A source measurement can only be referenced once, so this set decides whether a column may - * still be added for one. + * Source measurements already referenced by the view's FIELD columns, so this set decides whether + * a column may still be added for one. TAG/TIME/ATTRIBUTE columns do not reference tree + * measurements and therefore do not occupy the source-name namespace. */ public static Set getSourceNames(final TsTable view) { final Set sourceNames = new HashSet<>(); for (final TsTableColumnSchema schema : view.getColumnList()) { - if (Objects.isNull(schema.getProps())) { - // the columns which carry no props reference their own name - sourceNames.add(schema.getColumnName()); - } else { - sourceNames.add(getSourceName(schema)); + if (schema.getColumnCategory() != TsTableColumnCategory.FIELD) { + continue; } + sourceNames.add(getSourceName(schema)); } return sourceNames; }