This is an automated email from the ASF dual-hosted git repository. jt2594838 pushed a commit to branch delete_with_attribute in repository https://gitbox.apache.org/repos/asf/iotdb.git
commit 64105e48cce7ab54d247ec00bda49c1190207c2c Author: Tian Jiang <[email protected]> AuthorDate: Tue Jun 30 18:54:18 2026 +0800 convert attribute filter to DEVICE_IN --- .../iotdb/db/i18n/DataNodeQueryMessages.java | 3 + .../iotdb/db/i18n/DataNodeQueryMessages.java | 2 + .../db/queryengine/plan/analyze/AnalyzeUtils.java | 161 +++++++++++++++++++-- .../relational/analyzer/StatementAnalyzer.java | 2 +- .../plan/relational/sql/ast/DeleteDevice.java | 5 +- .../queryengine/plan/analyze/AnalyzeUtilsTest.java | 5 +- 6 files changed, 163 insertions(+), 15 deletions(-) diff --git a/iotdb-core/datanode/src/main/i18n/en/org/apache/iotdb/db/i18n/DataNodeQueryMessages.java b/iotdb-core/datanode/src/main/i18n/en/org/apache/iotdb/db/i18n/DataNodeQueryMessages.java index f870290c09a..9f8c4dd50b8 100644 --- a/iotdb-core/datanode/src/main/i18n/en/org/apache/iotdb/db/i18n/DataNodeQueryMessages.java +++ b/iotdb-core/datanode/src/main/i18n/en/org/apache/iotdb/db/i18n/DataNodeQueryMessages.java @@ -271,6 +271,9 @@ public final class DataNodeQueryMessages { "The table '%s' does not contain a time column"; public static final String THE_OPERATOR_OF_TAG_PREDICATE_MUST_BE_FOR = "The operator of tag predicate must be '=' for "; + public static final String TOO_MANY_DEVICES_MATCHED_BY_ATTRIBUTE_FILTERS_IN_DELETION = + "Too many devices (%d) matched by attribute filters in deletion, limit is %d. " + + "Please remove all attribute filters (%s) or add more attribute filters."; public static final String ONLY_TIME_FILTERS_ARE_SUPPORTED_IN_LAST_QUERY = "Only time filters are supported in LAST query"; public static final String VIEWS_CANNOT_BE_USED_IN_GROUP_BY_TAGS = diff --git a/iotdb-core/datanode/src/main/i18n/zh/org/apache/iotdb/db/i18n/DataNodeQueryMessages.java b/iotdb-core/datanode/src/main/i18n/zh/org/apache/iotdb/db/i18n/DataNodeQueryMessages.java index b0dae59e198..6d61d2bef70 100644 --- a/iotdb-core/datanode/src/main/i18n/zh/org/apache/iotdb/db/i18n/DataNodeQueryMessages.java +++ b/iotdb-core/datanode/src/main/i18n/zh/org/apache/iotdb/db/i18n/DataNodeQueryMessages.java @@ -269,6 +269,8 @@ public final class DataNodeQueryMessages { "表 '%s' 不包含时间列"; public static final String THE_OPERATOR_OF_TAG_PREDICATE_MUST_BE_FOR = "标签谓词的运算符必须为 '=',目标:"; + public static final String TOO_MANY_DEVICES_MATCHED_BY_ATTRIBUTE_FILTERS_IN_DELETION = + "删除语句中的属性过滤条件匹配了过多设备(%d),限制为 %d。请移除所有属性过滤条件(%s),或进一步增加属性过滤条件。"; public static final String ONLY_TIME_FILTERS_ARE_SUPPORTED_IN_LAST_QUERY = "LAST 查询中仅支持时间过滤器"; public static final String VIEWS_CANNOT_BE_USED_IN_GROUP_BY_TAGS = 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 3b5b664def6..aab117c6a0a 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 @@ -35,7 +35,9 @@ import org.apache.iotdb.commons.queryengine.plan.relational.sql.ast.LogicalExpre import org.apache.iotdb.commons.queryengine.plan.relational.sql.ast.LongLiteral; import org.apache.iotdb.commons.queryengine.plan.relational.sql.ast.NullLiteral; import org.apache.iotdb.commons.queryengine.plan.relational.sql.ast.StringLiteral; +import org.apache.iotdb.commons.queryengine.plan.relational.sql.ast.SymbolReference; import org.apache.iotdb.commons.schema.table.TsTable; +import org.apache.iotdb.commons.schema.table.column.TsTableColumnCategory; import org.apache.iotdb.commons.schema.table.column.TsTableColumnSchema; import org.apache.iotdb.commons.service.metric.PerformanceOverviewMetrics; import org.apache.iotdb.confignode.rpc.thrift.TGetRegionGroupsByTimeReq; @@ -45,6 +47,10 @@ import org.apache.iotdb.db.protocol.client.ConfigNodeClient; import org.apache.iotdb.db.protocol.client.ConfigNodeClientManager; import org.apache.iotdb.db.protocol.client.ConfigNodeInfo; import org.apache.iotdb.db.queryengine.common.MPPQueryContext; +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.sql.ast.Delete; import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.TimeRange; import org.apache.iotdb.db.queryengine.plan.statement.crud.InsertBaseStatement; @@ -58,6 +64,7 @@ import org.apache.iotdb.db.storageengine.dataregion.modification.DeletionPredica import org.apache.iotdb.db.storageengine.dataregion.modification.TableDeletionEntry; import org.apache.iotdb.db.storageengine.dataregion.modification.TagPredicate; import org.apache.iotdb.db.storageengine.dataregion.modification.TagPredicate.And; +import org.apache.iotdb.db.storageengine.dataregion.modification.TagPredicate.DeviceIn; import org.apache.iotdb.db.storageengine.dataregion.modification.TagPredicate.SegmentExactMatch; import org.apache.iotdb.rpc.RpcUtils; import org.apache.iotdb.rpc.TSStatusCode; @@ -82,6 +89,8 @@ import static org.apache.iotdb.db.queryengine.plan.execution.config.TableConfigT public class AnalyzeUtils { + private static final int ATTRIBUTE_FILTER_DELETE_DEVICE_IN_LIMIT = 1000; + private static final PerformanceOverviewMetrics PERFORMANCE_OVERVIEW_METRICS = PerformanceOverviewMetrics.getInstance(); private static final Logger LOGGER = LoggerFactory.getLogger(AnalyzeUtils.class); @@ -372,14 +381,20 @@ public class AnalyzeUtils { // Maybe set by pipe transfer if (Objects.isNull(node.getTableDeletionEntries())) { node.setTableDeletionEntries( - parseExpressions2ModEntries(node.getWhere().orElse(null), table)); + parseExpressions2ModEntries( + node.getWhere().orElse(null), table, databaseName, queryContext)); } } public static List<TableDeletionEntry> parseExpressions2ModEntries( - final Expression expression, final TsTable table) { + final Expression expression, + final TsTable table, + final String databaseName, + final MPPQueryContext queryContext) { return toDisjunctiveNormalForms(expression).stream() - .map(disjunctiveNormalForm -> parsePredicate(disjunctiveNormalForm, table)) + .map( + disjunctiveNormalForm -> + parsePredicate(disjunctiveNormalForm, table, databaseName, queryContext)) .collect(Collectors.toList()); } @@ -447,7 +462,11 @@ public class AnalyzeUtils { return results; } - private static TableDeletionEntry parsePredicate(Expression expression, TsTable table) { + private static TableDeletionEntry parsePredicate( + Expression expression, + TsTable table, + final String databaseName, + final MPPQueryContext queryContext) { if (expression == null) { return new TableDeletionEntry( new DeletionPredicate(table.getTableName()), @@ -458,21 +477,76 @@ public class AnalyzeUtils { expressionQueue.add(expression); DeletionPredicate predicate = new DeletionPredicate(table.getTableName()); TagPredicate tagPredicate = null; + List<Expression> deviceFilterExpressions = null; + List<String> attributeColumns = null; TimeRange timeRange = new TimeRange(Long.MIN_VALUE, Long.MAX_VALUE, true); while (!expressionQueue.isEmpty()) { Expression currExp = expressionQueue.remove(); if (currExp instanceof LogicalExpression) { parseAndPredicate(((LogicalExpression) currExp), expressionQueue); } else if (currExp instanceof ComparisonExpression) { - tagPredicate = + final PredicateParseResult parseResult = parseComparison(((ComparisonExpression) currExp), timeRange, tagPredicate, table); + tagPredicate = parseResult.tagPredicate; + if (parseResult.shouldQueryDevice()) { + if (Objects.isNull(deviceFilterExpressions)) { + deviceFilterExpressions = new ArrayList<>(); + attributeColumns = new ArrayList<>(); + } + deviceFilterExpressions.add(toSymbolReferenceExpression(currExp)); + collectAttributeColumn(attributeColumns, parseResult.attributeColumn); + } else if (parseResult.shouldFilterDevice()) { + if (Objects.isNull(deviceFilterExpressions)) { + deviceFilterExpressions = new ArrayList<>(); + } + deviceFilterExpressions.add(toSymbolReferenceExpression(currExp)); + } } else if (currExp instanceof IsNullPredicate) { - tagPredicate = parseIsNull((IsNullPredicate) currExp, tagPredicate, table); + final PredicateParseResult parseResult = + parseIsNull((IsNullPredicate) currExp, tagPredicate, table); + tagPredicate = parseResult.tagPredicate; + if (parseResult.shouldQueryDevice()) { + if (Objects.isNull(deviceFilterExpressions)) { + deviceFilterExpressions = new ArrayList<>(); + attributeColumns = new ArrayList<>(); + } + deviceFilterExpressions.add(toSymbolReferenceExpression(currExp)); + collectAttributeColumn(attributeColumns, parseResult.attributeColumn); + } else if (parseResult.shouldFilterDevice()) { + if (Objects.isNull(deviceFilterExpressions)) { + deviceFilterExpressions = new ArrayList<>(); + } + deviceFilterExpressions.add(toSymbolReferenceExpression(currExp)); + } } else { throw new SemanticException( DataNodeQueryMessages.UNSUPPORTED_EXPRESSION + currExp + " in " + expression); } } + if (Objects.nonNull(attributeColumns)) { + final Set<IDeviceID> deviceIDs = + TableDeviceSchemaFetcher.getInstance() + .fetchDeviceSchemaForDataQuery( + databaseName, + table.getTableName(), + deviceFilterExpressions, + attributeColumns, + queryContext) + .values() + .stream() + .flatMap(List::stream) + .map(DeviceEntry::getDeviceID) + .collect(Collectors.toSet()); + if (deviceIDs.size() > ATTRIBUTE_FILTER_DELETE_DEVICE_IN_LIMIT) { + throw new SemanticException( + String.format( + DataNodeQueryMessages.TOO_MANY_DEVICES_MATCHED_BY_ATTRIBUTE_FILTERS_IN_DELETION, + deviceIDs.size(), + ATTRIBUTE_FILTER_DELETE_DEVICE_IN_LIMIT, + attributeColumns)); + } + tagPredicate = new DeviceIn(deviceIDs); + } if (tagPredicate != null) { predicate.setTagPredicate(tagPredicate); } @@ -494,7 +568,14 @@ public class AnalyzeUtils { expressionQueue.addAll(expression.getTerms()); } - private static TagPredicate parseIsNull( + private static void collectAttributeColumn( + final List<String> attributeColumns, final String attributeColumn) { + if (Objects.nonNull(attributeColumn) && !attributeColumns.contains(attributeColumn)) { + attributeColumns.add(attributeColumn); + } + } + + private static PredicateParseResult parseIsNull( IsNullPredicate isNullPredicate, TagPredicate oldPredicate, TsTable table) { Expression leftHandExp = isNullPredicate.getValue(); if (!(leftHandExp instanceof Identifier)) { @@ -502,6 +583,11 @@ public class AnalyzeUtils { DataNodeQueryMessages.LEFT_HAND_EXPRESSION_IS_NOT_AN_IDENTIFIER + leftHandExp); } String columnName = ((Identifier) leftHandExp).getValue(); + final TsTableColumnSchema columnSchema = table.getColumnSchema(columnName); + if (Objects.nonNull(columnSchema) + && columnSchema.getColumnCategory().equals(TsTableColumnCategory.ATTRIBUTE)) { + return PredicateParseResult.attribute(columnName, oldPredicate); + } int tagColumnOrdinal = table.getTagColumnOrdinal(columnName); if (tagColumnOrdinal == -1) { throw new SemanticException( @@ -510,7 +596,7 @@ public class AnalyzeUtils { // the first segment is the table name, so + 1 TagPredicate newPredicate = new SegmentExactMatch(null, tagColumnOrdinal + 1); - return combinePredicates(oldPredicate, newPredicate); + return PredicateParseResult.tag(combinePredicates(oldPredicate, newPredicate)); } private static TagPredicate combinePredicates( @@ -525,7 +611,7 @@ public class AnalyzeUtils { return new TagPredicate.And(oldPredicate, newPredicate); } - private static TagPredicate parseComparison( + private static PredicateParseResult parseComparison( ComparisonExpression comparisonExpression, TimeRange timeRange, TagPredicate oldPredicate, @@ -571,10 +657,15 @@ public class AnalyzeUtils { "The operator of time predicate must be <, <=, >, or >=: " + right); } - return oldPredicate; + return PredicateParseResult.time(oldPredicate); } // tag predicate String columnName = identifier.getValue(); + final TsTableColumnSchema columnSchema = table.getColumnSchema(columnName); + if (Objects.nonNull(columnSchema) + && columnSchema.getColumnCategory().equals(TsTableColumnCategory.ATTRIBUTE)) { + return PredicateParseResult.attribute(columnName, oldPredicate); + } int tagColumnOrdinal = table.getTagColumnOrdinal(columnName); if (tagColumnOrdinal == -1) { throw new SemanticException( @@ -582,7 +673,7 @@ public class AnalyzeUtils { } TagPredicate newPredicate = getTagPredicate(comparisonExpression, right, tagColumnOrdinal); - return combinePredicates(oldPredicate, newPredicate); + return PredicateParseResult.tag(combinePredicates(oldPredicate, newPredicate)); } private static String getTimeColumnName(final TsTable table) { @@ -617,6 +708,54 @@ public class AnalyzeUtils { return new SegmentExactMatch(rightHandValue, tagColumnOrdinal + 1); } + private static Expression toSymbolReferenceExpression(final Expression expression) { + return ExpressionTreeRewriter.rewriteWith( + new ExpressionRewriter<>() { + @Override + public Expression rewriteIdentifier( + final Identifier node, + final Void context, + final ExpressionTreeRewriter<Void> treeRewriter) { + return new SymbolReference(node.getValue()); + } + }, + expression); + } + + private static class PredicateParseResult { + private final TagPredicate tagPredicate; + private final String attributeColumn; + private final boolean filterDevice; + + private PredicateParseResult( + final TagPredicate tagPredicate, final String attributeColumn, final boolean filterDevice) { + this.tagPredicate = tagPredicate; + this.attributeColumn = attributeColumn; + this.filterDevice = filterDevice; + } + + private static PredicateParseResult time(final TagPredicate tagPredicate) { + return new PredicateParseResult(tagPredicate, null, false); + } + + private static PredicateParseResult tag(final TagPredicate tagPredicate) { + return new PredicateParseResult(tagPredicate, null, true); + } + + private static PredicateParseResult attribute( + final String attributeColumn, final TagPredicate tagPredicate) { + return new PredicateParseResult(tagPredicate, attributeColumn, true); + } + + private boolean shouldQueryDevice() { + return Objects.nonNull(attributeColumn); + } + + private boolean shouldFilterDevice() { + return filterDevice; + } + } + public interface DataPartitionQueryFunc { DataPartition queryDataPartition( diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/StatementAnalyzer.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/StatementAnalyzer.java index 6bc5e2ab4f2..e9295240b7b 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/StatementAnalyzer.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/analyzer/StatementAnalyzer.java @@ -686,7 +686,7 @@ public class StatementAnalyzer { final TsTable table = DataNodeTableCache.getInstance().getTable(node.getDatabase(), node.getTableName()); DataNodeTreeViewSchemaUtils.checkTableInWrite(node.getDatabase(), table); - node.parseModEntries(table); + node.parseModEntries(table, node.getDatabase(), queryContext); analyzeTraverseDevice(node, context, node.getWhere().isPresent()); node.parseWhere( null, diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/sql/ast/DeleteDevice.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/sql/ast/DeleteDevice.java index d2fa9bbabde..39bc2ecfed5 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/sql/ast/DeleteDevice.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/queryengine/plan/relational/sql/ast/DeleteDevice.java @@ -94,8 +94,9 @@ public class DeleteDevice extends AbstractTraverseDevice { return mayDeleteDevice; } - public void parseModEntries(final TsTable table) { - modEntries = AnalyzeUtils.parseExpressions2ModEntries(where, table); + public void parseModEntries( + final TsTable table, final String databaseName, final MPPQueryContext context) { + modEntries = AnalyzeUtils.parseExpressions2ModEntries(where, table, databaseName, context); } public void serializeModEntries(final DataOutputStream stream) throws IOException { diff --git a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/queryengine/plan/analyze/AnalyzeUtilsTest.java b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/queryengine/plan/analyze/AnalyzeUtilsTest.java index bbeaa629d4f..ace46de6d0d 100644 --- a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/queryengine/plan/analyze/AnalyzeUtilsTest.java +++ b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/queryengine/plan/analyze/AnalyzeUtilsTest.java @@ -34,6 +34,8 @@ import org.apache.iotdb.commons.schema.table.column.TimeColumnSchema; import org.apache.iotdb.confignode.rpc.thrift.TGetRegionGroupsByTimeReq; import org.apache.iotdb.confignode.rpc.thrift.TGetRegionGroupsByTimeResp; import org.apache.iotdb.db.protocol.client.ConfigNodeClient; +import org.apache.iotdb.db.queryengine.common.MPPQueryContext; +import org.apache.iotdb.db.queryengine.common.QueryId; import org.apache.iotdb.db.queryengine.plan.relational.sql.ast.Delete; import org.apache.iotdb.db.storageengine.dataregion.modification.DeletionPredicate; import org.apache.iotdb.db.storageengine.dataregion.modification.TableDeletionEntry; @@ -66,7 +68,8 @@ public class AnalyzeUtilsTest { new Identifier("ts"), new LongLiteral("100")); - List<TableDeletionEntry> entries = AnalyzeUtils.parseExpressions2ModEntries(expression, table); + List<TableDeletionEntry> entries = + AnalyzeUtils.parseExpressions2ModEntries(expression, table, "db", new MPPQueryContext(new QueryId("1"))); assertEquals(1, entries.size()); assertEquals(Long.MIN_VALUE, entries.get(0).getStartTime());
