From d6a8447b0872755be8c6c761dc7069b5c6381680 Mon Sep 17 00:00:00 2001 From: Caideyipi <87789683+Caideyipi@users.noreply.github.com> Date: Tue, 3 Nov 2026 15:56:12 +0800 Subject: [PATCH 1/2] Fix table TTL reset propagation to DataNodes --- .../manual/enhanced/IoTDBPipeMetaIT.java | 9 +- .../relational/it/schema/IoTDBTableIT.java | 89 +++++++++++++ .../manager/schema/ClusterSchemaManager.java | 16 ++- .../manager/ClusterSchemaManagerTest.java | 118 ++++++++++++++++++ 4 files changed, 228 insertions(+), 4 deletions(-) diff --git a/integration-test/src/test/java/org/apache/iotdb/pipe/it/dual/tablemodel/manual/enhanced/IoTDBPipeMetaIT.java b/integration-test/src/test/java/org/apache/iotdb/pipe/it/dual/tablemodel/manual/enhanced/IoTDBPipeMetaIT.java index f513a3e4f3ad8..5d0d59609e826 100644 --- a/integration-test/src/test/java/org/apache/iotdb/pipe/it/dual/tablemodel/manual/enhanced/IoTDBPipeMetaIT.java +++ b/integration-test/src/test/java/org/apache/iotdb/pipe/it/dual/tablemodel/manual/enhanced/IoTDBPipeMetaIT.java @@ -46,6 +46,7 @@ import java.util.HashMap; import java.util.HashSet; import java.util.Map; +import java.util.concurrent.TimeUnit; import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; @@ -94,7 +95,9 @@ public void testTableSync() throws Exception { Assert.assertEquals(TSStatusCode.SUCCESS_STATUS.getStatusCode(), status.getCode()); final String dbName = "test"; - TableModelUtils.createDatabase(senderEnv, dbName, 300); + // Keep rows alive while asynchronous metadata and data synchronization finish. + final long databaseTtl = TimeUnit.DAYS.toMillis(1); + TableModelUtils.createDatabase(senderEnv, dbName, databaseTtl); TestUtils.executeNonQueries( dbName, @@ -115,7 +118,7 @@ public void testTableSync() throws Exception { receiverEnv, "show tables from test", "TableName,TTL(ms),", - Collections.singleton("table1,300,"), + Collections.singleton("table1," + databaseTtl + ","), dbName); // Test devices @@ -159,7 +162,7 @@ public void testTableSync() throws Exception { receiverEnv, "show tables from test", "TableName,TTL(ms),", - Collections.singleton("table1,300,"), + Collections.singleton("table1," + databaseTtl + ","), dbName); TestUtils.assertDataEventuallyOnEnv( diff --git a/integration-test/src/test/java/org/apache/iotdb/relational/it/schema/IoTDBTableIT.java b/integration-test/src/test/java/org/apache/iotdb/relational/it/schema/IoTDBTableIT.java index 037ac00cd1f2b..078a6a71a71cd 100644 --- a/integration-test/src/test/java/org/apache/iotdb/relational/it/schema/IoTDBTableIT.java +++ b/integration-test/src/test/java/org/apache/iotdb/relational/it/schema/IoTDBTableIT.java @@ -21,13 +21,16 @@ import org.apache.iotdb.db.it.utils.TestUtils; import org.apache.iotdb.isession.ITableSession; +import org.apache.iotdb.isession.SessionConfig; import org.apache.iotdb.it.env.EnvFactory; +import org.apache.iotdb.it.env.cluster.node.DataNodeWrapper; import org.apache.iotdb.it.framework.IoTDBTestRunner; import org.apache.iotdb.itbase.category.TableClusterIT; import org.apache.iotdb.itbase.category.TableLocalStandaloneIT; import org.apache.iotdb.itbase.env.BaseEnv; import org.apache.iotdb.rpc.IoTDBConnectionException; import org.apache.iotdb.rpc.StatementExecutionException; +import org.apache.iotdb.rpc.TSStatusCode; import org.apache.tsfile.enums.ColumnCategory; import org.apache.tsfile.enums.TSDataType; @@ -52,6 +55,7 @@ import java.util.HashSet; import java.util.List; import java.util.Set; +import java.util.concurrent.TimeUnit; import static org.apache.iotdb.commons.schema.column.ColumnHeaderConstant.describeTableColumnHeaders; import static org.apache.iotdb.commons.schema.column.ColumnHeaderConstant.describeTableDetailsColumnHeaders; @@ -79,6 +83,91 @@ public static void tearDown() throws Exception { EnvFactory.getEnv().cleanClusterEnvironment(); } + @Test + public void testResetTableTtlUsesDatabaseDefault() throws Exception { + final String database = "ttl_default_reset"; + final long day = TimeUnit.DAYS.toMillis(1); + final long old = System.currentTimeMillis() - 2 * day; + final long recent = System.currentTimeMillis() - TimeUnit.MINUTES.toMillis(1); + try (final Connection connection = + EnvFactory.getEnv().getConnection(BaseEnv.TABLE_SQL_DIALECT); + final Statement statement = connection.createStatement()) { + try { + statement.execute("CREATE DATABASE " + database + " WITH (ttl=" + 7 * day + ")"); + statement.execute("USE " + database); + statement.execute("CREATE TABLE inherited(dev STRING TAG,v INT32 FIELD)"); + statement.execute( + "CREATE TABLE overridden(dev STRING TAG,v INT32 FIELD) WITH (ttl=" + 30 * day + ")"); + for (final String table : new String[] {"inherited", "overridden"}) { + statement.execute( + "INSERT INTO " + + table + + "(time,dev,v) VALUES(" + + old + + ",'d',1),(" + + recent + + ",'d',2)"); + TestUtils.assertDataEventuallyOnEnv( + EnvFactory.getEnv(), + "SELECT v FROM " + table + " ORDER BY time", + "v,", + new HashSet<>(Arrays.asList("1,", "2,")), + 30, + database, + null); + } + + statement.execute("ALTER DATABASE " + database + " SET PROPERTIES ttl=" + day); + statement.execute("ALTER TABLE inherited SET PROPERTIES ttl=DEFAULT"); + TestUtils.tableResultSetEqualTestUnordered( + "SHOW TABLES", + new String[] {"TableName", "TTL(ms)"}, + new String[] {"inherited," + day + ",", "overridden," + 30 * day + ","}, + database); + TestUtils.tableResultSetEqualTestUnordered( + "SELECT v FROM inherited", new String[] {"v"}, new String[] {"2,"}, database); + TestUtils.tableResultSetEqualTestUnordered( + "SELECT v FROM overridden", new String[] {"v"}, new String[] {"1,", "2,"}, database); + for (final DataNodeWrapper node : EnvFactory.getEnv().getDataNodeWrapperList()) { + try (final Connection writer = + EnvFactory.getEnv() + .getConnection( + node, + SessionConfig.DEFAULT_USER, + SessionConfig.DEFAULT_PASSWORD, + BaseEnv.TABLE_SQL_DIALECT); + final Statement write = writer.createStatement()) { + final SQLException exception = + Assert.assertThrows( + SQLException.class, + () -> + write.execute( + "INSERT INTO " + + database + + ".inherited(time,dev,v) VALUES(" + + (old + 1) + + ",'d',3)")); + assertEquals(TSStatusCode.OUT_OF_TTL.getStatusCode(), exception.getErrorCode()); + } + } + + statement.execute("ALTER DATABASE " + database + " SET PROPERTIES ttl='INF'"); + statement.execute("ALTER TABLE inherited SET PROPERTIES ttl=DEFAULT"); + statement.execute("INSERT INTO inherited(time,dev,v) VALUES(" + (old + 1) + ",'d',3)"); + TestUtils.assertDataEventuallyOnEnv( + EnvFactory.getEnv(), + "SELECT v FROM inherited WHERE v=3", + "v,", + Collections.singleton("3,"), + 30, + database, + null); + } finally { + statement.execute("DROP DATABASE IF EXISTS " + database); + } + } + } + @Test public void testManageTable() { try (final Connection connection = 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 c38c559ae2980..0e33f2bd17c80 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 @@ -1968,7 +1968,8 @@ public Pair updateTableProperties( final Map updatedProperties, final boolean isTableView) throws MetadataException { - final TsTable originalTable = getTableWithUsingStatusIfExists(database, tableName).orElse(null); + final TsTable originalTable = + getTableWithUsingStatusIfExists(database, tableName).map(TsTable::new).orElse(null); if (Objects.isNull(originalTable)) { return new Pair<>( @@ -1994,6 +1995,19 @@ public Pair updateTableProperties( null); } + // Resolve DEFAULT before comparing properties and preparing the table sent to DataNodes. + if (updatedProperties.containsKey(TsTable.TTL_PROPERTY) + && Objects.isNull(updatedProperties.get(TsTable.TTL_PROPERTY))) { + try { + final TDatabaseSchema schema = getDatabaseSchemaByName(database); + if (schema.isSetTTL() && schema.getTTL() != Long.MAX_VALUE) { + updatedProperties.put(TsTable.TTL_PROPERTY, String.valueOf(schema.getTTL())); + } + } catch (final DatabaseNotExistsException e) { + throw new MetadataException(e); + } + } + updatedProperties .keySet() .removeIf( 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 ce6d7df577eac..21e3812336ffa 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 @@ -41,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.SetTablePropertiesPlan; 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; @@ -412,4 +413,121 @@ private static ClusterSchemaManager managerOf(final ClusterSchemaInfo clusterSch clusterSchemaInfo, Mockito.mock(ClusterSchemaQuotaStatistics.class)); } + + private static ClusterSchemaManager managerOf( + final ClusterSchemaInfo clusterSchemaInfo, final String existingDatabase) { + final IManager configManager = Mockito.mock(IManager.class); + final PartitionManager partitionManager = Mockito.mock(PartitionManager.class); + Mockito.when(configManager.getPartitionManager()).thenReturn(partitionManager); + Mockito.when(partitionManager.isDatabaseExist(existingDatabase)).thenReturn(true); + return new ClusterSchemaManager( + configManager, clusterSchemaInfo, Mockito.mock(ClusterSchemaQuotaStatistics.class)); + } + + @Test + public void testResetTableTtlUsesFiniteDatabaseDefault() throws Exception { + final long day = 86400000L; + final String database = "ttl_default_finite_test"; + for (final String initialTtl : + new String[] {null, String.valueOf(7 * day), String.valueOf(day)}) { + final ClusterSchemaInfo schemaInfo = new ClusterSchemaInfo(); + schemaInfo.createDatabase( + new DatabaseSchemaPlan( + ConfigPhysicalPlanType.CreateDatabase, + new TDatabaseSchema(database).setIsTableModel(true))); + final TsTable table = new TsTable(TABLE); + if (initialTtl != null) { + table.addProp(TsTable.TTL_PROPERTY, initialTtl); + } + schemaInfo.preCreateTable(new PreCreateTablePlan(database, table)); + schemaInfo.commitCreateTable(new CommitCreateTablePlan(database, TABLE)); + Assert.assertEquals( + TSStatusCode.SUCCESS_STATUS.getStatusCode(), + schemaInfo + .alterDatabase( + new DatabaseSchemaPlan( + ConfigPhysicalPlanType.AlterDatabase, + new TDatabaseSchema(database).setIsTableModel(true).setTTL(day))) + .getCode()); + final ClusterSchemaManager manager = managerOf(schemaInfo, database); + final Map originalProperties = new HashMap<>(); + final Map updatedProperties = new HashMap<>(); + updatedProperties.put(TsTable.TTL_PROPERTY, null); + + final Pair result = + manager.updateTableProperties( + database, TABLE, originalProperties, updatedProperties, false); + Assert.assertEquals(TSStatusCode.SUCCESS_STATUS.getStatusCode(), result.getLeft().getCode()); + if (String.valueOf(day).equals(initialTtl)) { + Assert.assertNull(result.getRight()); + Assert.assertTrue(updatedProperties.isEmpty()); + continue; + } + Assert.assertNotNull(result.getRight()); + Assert.assertEquals(String.valueOf(day), updatedProperties.get(TsTable.TTL_PROPERTY)); + Assert.assertEquals(initialTtl, originalProperties.get(TsTable.TTL_PROPERTY)); + Assert.assertEquals(day, result.getRight().getTableTTL()); + Assert.assertEquals( + initialTtl, + manager + .getTableIfExists(database, TABLE) + .get() + .getPropValue(TsTable.TTL_PROPERTY) + .orElse(null)); + Assert.assertEquals( + TSStatusCode.SUCCESS_STATUS.getStatusCode(), + schemaInfo + .setTableProperties(new SetTablePropertiesPlan(database, TABLE, updatedProperties)) + .getCode()); + Assert.assertEquals( + manager.getTableIfExists(database, TABLE).get().getPropValue(TsTable.TTL_PROPERTY), + result.getRight().getPropValue(TsTable.TTL_PROPERTY)); + } + } + + @Test + public void testResetTableTtlUsesInfiniteDatabaseDefault() throws Exception { + final String database = "ttl_default_infinite_test"; + for (final boolean explicitInfinite : new boolean[] {false, true}) { + final ClusterSchemaInfo schemaInfo = new ClusterSchemaInfo(); + final TDatabaseSchema schema = new TDatabaseSchema(database).setIsTableModel(true); + if (explicitInfinite) { + schema.setTTL(Long.MAX_VALUE); + } + schemaInfo.createDatabase( + new DatabaseSchemaPlan(ConfigPhysicalPlanType.CreateDatabase, schema)); + final TsTable table = new TsTable(TABLE); + table.addProp(TsTable.TTL_PROPERTY, "86400000"); + schemaInfo.preCreateTable(new PreCreateTablePlan(database, table)); + schemaInfo.commitCreateTable(new CommitCreateTablePlan(database, TABLE)); + final ClusterSchemaManager manager = managerOf(schemaInfo, database); + final Map originalProperties = new HashMap<>(); + final Map updatedProperties = new HashMap<>(); + updatedProperties.put(TsTable.TTL_PROPERTY, null); + + final Pair result = + manager.updateTableProperties( + database, TABLE, originalProperties, updatedProperties, false); + Assert.assertEquals(TSStatusCode.SUCCESS_STATUS.getStatusCode(), result.getLeft().getCode()); + Assert.assertNotNull(result.getRight()); + Assert.assertEquals("86400000", originalProperties.get(TsTable.TTL_PROPERTY)); + Assert.assertEquals(Long.MAX_VALUE, result.getRight().getTableTTL()); + Assert.assertFalse(result.getRight().getPropValue(TsTable.TTL_PROPERTY).isPresent()); + Assert.assertEquals( + "86400000", + manager + .getTableIfExists(database, TABLE) + .get() + .getPropValue(TsTable.TTL_PROPERTY) + .orElse(null)); + Assert.assertEquals( + TSStatusCode.SUCCESS_STATUS.getStatusCode(), + schemaInfo + .setTableProperties(new SetTablePropertiesPlan(database, TABLE, updatedProperties)) + .getCode()); + Assert.assertEquals( + manager.getTableIfExists(database, TABLE).get().getPropValue(TsTable.TTL_PROPERTY), + result.getRight().getPropValue(TsTable.TTL_PROPERTY)); + } + } } From 295c34f43ae91eb2ede40f4a43970898547b3f8f Mon Sep 17 00:00:00 2001 From: Caideyipi <87789683+Caideyipi@users.noreply.github.com> Date: Wed, 4 Nov 2026 14:59:37 +0800 Subject: [PATCH 2/2] Fix table TTL default reset consistency --- .../manager/schema/ClusterSchemaManager.java | 20 +++-- .../table/SetTablePropertiesProcedure.java | 16 +++- .../manager/ClusterSchemaManagerTest.java | 63 ++++++++++++++- .../SetTablePropertiesProcedureTest.java | 78 +++++++++++++++++++ 4 files changed, 168 insertions(+), 9 deletions(-) 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 0e33f2bd17c80..f6988828f405f 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 @@ -23,6 +23,7 @@ import org.apache.iotdb.common.rpc.thrift.TDataNodeLocation; import org.apache.iotdb.common.rpc.thrift.TSStatus; import org.apache.iotdb.commons.conf.CommonDescriptor; +import org.apache.iotdb.commons.conf.IoTDBConstant; import org.apache.iotdb.commons.exception.MetadataException; import org.apache.iotdb.commons.exception.table.ColumnInAlterException; import org.apache.iotdb.commons.exception.table.ColumnInDeletionException; @@ -2000,9 +2001,11 @@ public Pair updateTableProperties( && Objects.isNull(updatedProperties.get(TsTable.TTL_PROPERTY))) { try { final TDatabaseSchema schema = getDatabaseSchemaByName(database); - if (schema.isSetTTL() && schema.getTTL() != Long.MAX_VALUE) { - updatedProperties.put(TsTable.TTL_PROPERTY, String.valueOf(schema.getTTL())); - } + updatedProperties.put( + TsTable.TTL_PROPERTY, + schema.isSetTTL() && schema.getTTL() != Long.MAX_VALUE + ? String.valueOf(schema.getTTL()) + : IoTDBConstant.TTL_INFINITE); } catch (final DatabaseNotExistsException e) { throw new MetadataException(e); } @@ -2011,9 +2014,14 @@ public Pair updateTableProperties( updatedProperties .keySet() .removeIf( - key -> - Objects.equals( - updatedProperties.get(key), originalTable.getPropValue(key).orElse(null))); + key -> { + if (TsTable.TTL_PROPERTY.equals(key) + && IoTDBConstant.TTL_INFINITE.equalsIgnoreCase(updatedProperties.get(key))) { + return originalTable.getTableTTL() == Long.MAX_VALUE; + } + return Objects.equals( + updatedProperties.get(key), originalTable.getPropValue(key).orElse(null)); + }); if (updatedProperties.isEmpty()) { return new Pair<>(RpcUtils.SUCCESS_STATUS, null); } 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 56a033d0110a5..d48d857f5c239 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,6 +20,7 @@ package org.apache.iotdb.confignode.procedure.impl.schema.table; import org.apache.iotdb.common.rpc.thrift.TSStatus; +import org.apache.iotdb.commons.conf.IoTDBConstant; import org.apache.iotdb.commons.exception.MetadataException; import org.apache.iotdb.commons.schema.table.TsTable; import org.apache.iotdb.confignode.consensus.request.ConfigPhysicalPlan; @@ -35,7 +36,9 @@ import java.io.DataOutputStream; import java.io.IOException; import java.nio.ByteBuffer; +import java.util.HashMap; import java.util.Map; +import java.util.Objects; public class SetTablePropertiesProcedure extends AbstractSetPropertiesProcedure { @@ -68,9 +71,18 @@ protected Pair updateProperties(final ConfigNodeProcedureEnv @Override protected ConfigPhysicalPlan createSetPropertiesPlan( final Map properties, final boolean isRollback) { + final Map propertiesToSet = + isRollback + && properties.containsKey(TsTable.TTL_PROPERTY) + && Objects.isNull(properties.get(TsTable.TTL_PROPERTY)) + ? new HashMap<>(properties) + : properties; + if (propertiesToSet != properties) { + propertiesToSet.put(TsTable.TTL_PROPERTY, IoTDBConstant.TTL_INFINITE); + } return this instanceof SetViewPropertiesProcedure - ? new SetViewPropertiesPlan(database, tableName, properties) - : new SetTablePropertiesPlan(database, tableName, properties); + ? new SetViewPropertiesPlan(database, tableName, propertiesToSet) + : new SetTablePropertiesPlan(database, tableName, propertiesToSet); } @Override 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 21e3812336ffa..bc66d5e2b94e9 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 @@ -474,6 +474,11 @@ public void testResetTableTtlUsesFiniteDatabaseDefault() throws Exception { .get() .getPropValue(TsTable.TTL_PROPERTY) .orElse(null)); + // A concurrent database update must not change the TTL already sent to DataNodes. + schemaInfo.alterDatabase( + new DatabaseSchemaPlan( + ConfigPhysicalPlanType.AlterDatabase, + new TDatabaseSchema(database).setIsTableModel(true).setTTL(2 * day))); Assert.assertEquals( TSStatusCode.SUCCESS_STATUS.getStatusCode(), schemaInfo @@ -512,7 +517,7 @@ public void testResetTableTtlUsesInfiniteDatabaseDefault() throws Exception { Assert.assertNotNull(result.getRight()); Assert.assertEquals("86400000", originalProperties.get(TsTable.TTL_PROPERTY)); Assert.assertEquals(Long.MAX_VALUE, result.getRight().getTableTTL()); - Assert.assertFalse(result.getRight().getPropValue(TsTable.TTL_PROPERTY).isPresent()); + Assert.assertEquals("INF", updatedProperties.get(TsTable.TTL_PROPERTY)); Assert.assertEquals( "86400000", manager @@ -520,14 +525,70 @@ public void testResetTableTtlUsesInfiniteDatabaseDefault() throws Exception { .get() .getPropValue(TsTable.TTL_PROPERTY) .orElse(null)); + // The validated infinite default must not change after a concurrent database update. + schemaInfo.alterDatabase( + new DatabaseSchemaPlan( + ConfigPhysicalPlanType.AlterDatabase, + new TDatabaseSchema(database).setIsTableModel(true).setTTL(172800000L))); Assert.assertEquals( TSStatusCode.SUCCESS_STATUS.getStatusCode(), schemaInfo .setTableProperties(new SetTablePropertiesPlan(database, TABLE, updatedProperties)) .getCode()); + Assert.assertEquals( + Long.MAX_VALUE, manager.getTableIfExists(database, TABLE).get().getTableTTL()); + Assert.assertEquals( + "INF", + manager + .getTableIfExists(database, TABLE) + .get() + .getPropValue(TsTable.TTL_PROPERTY) + .orElse(null)); Assert.assertEquals( manager.getTableIfExists(database, TABLE).get().getPropValue(TsTable.TTL_PROPERTY), result.getRight().getPropValue(TsTable.TTL_PROPERTY)); } } + + @Test + public void testResetTableTtlWithInfiniteDefaultIsNoOp() throws Exception { + final String database = "ttl_default_no_op_test"; + for (final boolean explicitInfinite : new boolean[] {false, true}) { + for (final String initialTtl : new String[] {null, "INF", String.valueOf(Long.MAX_VALUE)}) { + final ClusterSchemaInfo schemaInfo = new ClusterSchemaInfo(); + final TDatabaseSchema schema = new TDatabaseSchema(database).setIsTableModel(true); + if (explicitInfinite) { + schema.setTTL(Long.MAX_VALUE); + } + schemaInfo.createDatabase( + new DatabaseSchemaPlan(ConfigPhysicalPlanType.CreateDatabase, schema)); + final TsTable table = new TsTable(TABLE); + if (initialTtl != null) { + table.addProp(TsTable.TTL_PROPERTY, initialTtl); + } + schemaInfo.preCreateTable(new PreCreateTablePlan(database, table)); + schemaInfo.commitCreateTable(new CommitCreateTablePlan(database, TABLE)); + final ClusterSchemaManager manager = managerOf(schemaInfo, database); + final Map originalProperties = new HashMap<>(); + final Map updatedProperties = new HashMap<>(); + updatedProperties.put(TsTable.TTL_PROPERTY, null); + + final Pair result = + manager.updateTableProperties( + database, TABLE, originalProperties, updatedProperties, false); + Assert.assertEquals( + TSStatusCode.SUCCESS_STATUS.getStatusCode(), result.getLeft().getCode()); + Assert.assertNull(result.getRight()); + Assert.assertTrue(updatedProperties.isEmpty()); + Assert.assertTrue(originalProperties.isEmpty()); + Assert.assertEquals( + initialTtl, + manager + .getTableIfExists(database, TABLE) + .get() + .getPropValue(TsTable.TTL_PROPERTY) + .orElse(null)); + } + } + } } diff --git a/iotdb-core/confignode/src/test/java/org/apache/iotdb/confignode/procedure/impl/schema/table/SetTablePropertiesProcedureTest.java b/iotdb-core/confignode/src/test/java/org/apache/iotdb/confignode/procedure/impl/schema/table/SetTablePropertiesProcedureTest.java index d91174f0ac05f..3704cb3fd0264 100644 --- a/iotdb-core/confignode/src/test/java/org/apache/iotdb/confignode/procedure/impl/schema/table/SetTablePropertiesProcedureTest.java +++ b/iotdb-core/confignode/src/test/java/org/apache/iotdb/confignode/procedure/impl/schema/table/SetTablePropertiesProcedureTest.java @@ -19,19 +19,97 @@ package org.apache.iotdb.confignode.procedure.impl.schema.table; +import org.apache.iotdb.common.rpc.thrift.TSStatus; import org.apache.iotdb.commons.exception.IllegalPathException; +import org.apache.iotdb.commons.schema.table.TsTable; +import org.apache.iotdb.confignode.consensus.request.ConfigPhysicalPlanType; +import org.apache.iotdb.confignode.consensus.request.write.database.DatabaseSchemaPlan; +import org.apache.iotdb.confignode.consensus.request.write.table.CommitCreateTablePlan; +import org.apache.iotdb.confignode.consensus.request.write.table.PreCreateTablePlan; +import org.apache.iotdb.confignode.consensus.request.write.table.SetTablePropertiesPlan; +import org.apache.iotdb.confignode.manager.ConfigManager; +import org.apache.iotdb.confignode.manager.consensus.ConsensusManager; +import org.apache.iotdb.confignode.manager.partition.PartitionManager; +import org.apache.iotdb.confignode.manager.schema.ClusterSchemaManager; +import org.apache.iotdb.confignode.manager.schema.ClusterSchemaQuotaStatistics; +import org.apache.iotdb.confignode.persistence.schema.ClusterSchemaInfo; +import org.apache.iotdb.confignode.procedure.env.ConfigNodeProcedureEnv; import org.apache.iotdb.confignode.procedure.store.ProcedureType; +import org.apache.iotdb.confignode.rpc.thrift.TDatabaseSchema; +import org.apache.iotdb.rpc.TSStatusCode; import org.junit.Assert; import org.junit.Test; +import org.mockito.Mockito; import java.io.ByteArrayOutputStream; import java.io.DataOutputStream; import java.io.IOException; import java.nio.ByteBuffer; import java.util.HashMap; +import java.util.Map; + +import static org.apache.iotdb.confignode.procedure.state.schema.SetTablePropertiesState.SET_PROPERTIES; public class SetTablePropertiesProcedureTest { + @Test + public void testRollbackRestoresMissingTableTtlAfterDatabaseDefaultChanges() throws Exception { + final String database = "ttl_rollback_test"; + final String tableName = "table1"; + final ClusterSchemaInfo schemaInfo = new ClusterSchemaInfo(); + schemaInfo.createDatabase( + new DatabaseSchemaPlan( + ConfigPhysicalPlanType.CreateDatabase, + new TDatabaseSchema(database).setIsTableModel(true).setTTL(86400000L))); + schemaInfo.preCreateTable(new PreCreateTablePlan(database, new TsTable(tableName))); + schemaInfo.commitCreateTable(new CommitCreateTablePlan(database, tableName)); + final ConfigManager configManager = Mockito.mock(ConfigManager.class); + final PartitionManager partitionManager = Mockito.mock(PartitionManager.class); + final ConsensusManager consensusManager = Mockito.mock(ConsensusManager.class); + Mockito.when(configManager.getPartitionManager()).thenReturn(partitionManager); + Mockito.when(partitionManager.isDatabaseExist(database)).thenReturn(true); + Mockito.when(configManager.getConsensusManager()).thenReturn(consensusManager); + final ClusterSchemaManager manager = + new ClusterSchemaManager( + configManager, schemaInfo, Mockito.mock(ClusterSchemaQuotaStatistics.class)); + Mockito.when(configManager.getClusterSchemaManager()).thenReturn(manager); + final ConfigNodeProcedureEnv env = Mockito.mock(ConfigNodeProcedureEnv.class); + Mockito.when(env.getConfigManager()).thenReturn(configManager); + final Map properties = new HashMap<>(); + properties.put(TsTable.TTL_PROPERTY, null); + final SetTablePropertiesProcedure procedure = + new SetTablePropertiesProcedure(database, tableName, "0", properties, false); + procedure.validateTable(env); + Assert.assertFalse(procedure.isFailed()); + Assert.assertEquals(86400000L, procedure.table.getTableTTL()); + Assert.assertTrue(procedure.originalProperties.containsKey(TsTable.TTL_PROPERTY)); + Assert.assertNull(procedure.originalProperties.get(TsTable.TTL_PROPERTY)); + Assert.assertEquals( + Long.MAX_VALUE, manager.getTableIfExists(database, tableName).get().getTableTTL()); + + // Recover the captured original properties, then fail the write after the default changes. + final ByteArrayOutputStream bytes = new ByteArrayOutputStream(); + procedure.serialize(new DataOutputStream(bytes)); + final ByteBuffer buffer = ByteBuffer.wrap(bytes.toByteArray()); + buffer.getShort(); + final SetTablePropertiesProcedure recovered = new SetTablePropertiesProcedure(false); + recovered.deserialize(buffer); + schemaInfo.alterDatabase( + new DatabaseSchemaPlan( + ConfigPhysicalPlanType.AlterDatabase, + new TDatabaseSchema(database).setIsTableModel(true).setTTL(172800000L))); + Mockito.when(consensusManager.write(Mockito.any(SetTablePropertiesPlan.class))) + .thenReturn(new TSStatus(TSStatusCode.INTERNAL_SERVER_ERROR.getStatusCode())) + .thenAnswer(invocation -> schemaInfo.setTableProperties(invocation.getArgument(0))); + recovered.executeFromState(env, SET_PROPERTIES); + Assert.assertTrue(recovered.isFailed()); + recovered.rollbackState(env, SET_PROPERTIES); + Assert.assertEquals( + Long.MAX_VALUE, manager.getTableIfExists(database, tableName).get().getTableTTL()); + Mockito.verify(consensusManager, Mockito.times(2)) + .write(Mockito.any(SetTablePropertiesPlan.class)); + } + @Test public void serializeDeserializeTest() throws IllegalPathException, IOException { final SetTablePropertiesProcedure setTablePropertiesProcedure =