Repository navigation
Conversation
| @Test | ||
| public void testFilteredTabletBatchIsAckedWithoutReturningEmptyMessage() | ||
| throws SubscriptionException { | ||
| final TestPullConsumer consumer = new TestPullConsumer(1, 0, true); | ||
| try { | ||
| consumer.open(); | ||
| consumer.subscribeTopic(); | ||
|
|
||
| final List<SubscriptionMessage> messages = consumer.pollForTest(1_000L); | ||
|
|
||
| Assert.assertTrue(messages.isEmpty()); | ||
| Assert.assertEquals(1, consumer.getAcknowledgedCommitContexts().size()); | ||
| Assert.assertEquals( | ||
| new SubscriptionCommitContext(1, 0, TOPIC, CONSUMER_GROUP_ID, 0L), | ||
| consumer.getAcknowledgedCommitContexts().get(0)); |
There was a problem hiding this comment.
Why do you call the test WithoutReturningEmptyMessage but the messages are empty?
There was a problem hiding this comment.
Renamed the test to testFullyFilteredTabletBatchIsAcknowledgedAndPollReturnsNoMessages. The expected result is an empty poll result, rather than a SubscriptionMessage carrying an empty Tablet payload; the filtered batch is still acknowledged.
Commit: 7caec10.
| public static final String | ||
| EXCEPTION_FAILED_TO_CREATE_OR_ALTER_TOPIC_DUPLICATE_ARG_ATTRIBUTES_ARE_NOT_ALLOWED_27315578 = | ||
| "\u521b\u5efa\u6216\u4fee\u6539 Topic \u5931\u8d25\uff0c\u4e0d\u5141\u8bb8\u91cd\u590d\u7684 %s \u5c5e\u6027"; | ||
| public static final String | ||
| EXCEPTION_FAILED_TO_CREATE_OR_ALTER_TOPIC_ARG_IS_ONLY_SUPPORTED_FOR_TABLE_TOPICS_A5126607 = | ||
| "\u521b\u5efa\u6216\u4fee\u6539 Topic \u5931\u8d25\uff0c%s \u4ec5\u652f\u6301\u8868\u6a21\u578b Topic"; | ||
| public static final String | ||
| EXCEPTION_FAILED_TO_CREATE_OR_ALTER_TOPIC_ARG_SHOULD_NOT_BE_EMPTY_767B1148 = | ||
| "\u521b\u5efa\u6216\u4fee\u6539 Topic \u5931\u8d25\uff0c%s \u4e0d\u5e94\u4e3a\u7a7a"; |
There was a problem hiding this comment.
Replaced the Unicode escapes with readable UTF-8 Chinese text for all three messages, preserving the format placeholders.
Commit: 7caec10.
| retainedAlignedFlags.add(insertNodeTabletInsertionEvent.isAligned(i)); | ||
| final TabletTransformResult transformedTablet = | ||
| transformTablet( | ||
| isTableModel ? insertNodeTabletInsertionEvent.getTableModelDatabaseName() : null, |
There was a problem hiding this comment.
Is it possible to let getTableModelDatabaseName return null when it is not a table model event?
There was a problem hiding this comment.
Applied: PipeInsertionEvent.getTableModelDatabaseName() now returns null for tree-model events. Both transformation call sites use the getter directly. Added coverage for the database name passed for both event models.
Commit: 7caec10.
| calculateTabletsSizeInBytes( | ||
| retainedTablets.stream() | ||
| .map(TabletTransformResult::getTablet) | ||
| .collect(java.util.stream.Collectors.toList()))); |
There was a problem hiding this comment.
Added the Collectors import and removed the fully qualified reference.
Commit: 7caec10.
| isTableModel ? insertNodeTabletInsertionEvent.getTableModelDatabaseName() : null, | ||
| tablet, | ||
| isTableModel, | ||
| !isTableModel && insertNodeTabletInsertionEvent.isAligned(i)); |
There was a problem hiding this comment.
Why is ' isAligned ' false when isTableModel is true?
There was a problem hiding this comment.
Table-model data is aligned. Changed both transformation inputs and projected Tree View table results to report true; tree-model inputs preserve their original alignment. Added coverage for both models and alignment values.
Commit: 7caec10.
| throw new TagFilterEvaluationException( | ||
| DataNodeMiscMessages.EXCEPTION_TABLET_TIMESTAMPS_ARE_INCOMPLETE_24F8CE6F); | ||
| } | ||
| final org.apache.tsfile.utils.BitMap[] bitMaps = tablet.getBitMaps(); |
There was a problem hiding this comment.
Added the BitMap import and removed the fully qualified references. The array-type validation helpers, including the qualified LocalDate reference, were removed with the redundant structural validation.
Commit: 7caec10.
| for (int i = 0; i < schemas.size(); i++) { | ||
| final IMeasurementSchema schema = schemas.get(i); | ||
| if (schema == null || schema.getMeasurementName() == null || schema.getType() == null) { | ||
| throw TagFilterEvaluationException.schemaBinding( | ||
| DataNodeMiscMessages.EXCEPTION_TABLET_MEASUREMENT_SCHEMA_IS_INCOMPLETE_6A813472); | ||
| } | ||
| if (values[i] == null) { | ||
| // A column whose every row is NULL may legitimately omit its value array and represent | ||
| // the values solely through a bitmap. It still has a complete schema and must remain | ||
| // distinguishable from a missing or malformed column. | ||
| if (!isEntirelyNull(bitMaps, i, tablet.getRowSize())) { | ||
| throw new TagFilterEvaluationException( | ||
| DataNodeMiscMessages.EXCEPTION_TABLET_VALUE_COLUMN_IS_INCOMPLETE_845721FE); | ||
| } | ||
| continue; | ||
| } | ||
| if (!hasArrayLength(values[i], tablet.getRowSize())) { | ||
| throw new TagFilterEvaluationException( | ||
| DataNodeMiscMessages.EXCEPTION_TABLET_VALUE_COLUMN_IS_INCOMPLETE_845721FE); | ||
| } | ||
| if (categories.get(i) == null) { | ||
| throw TagFilterEvaluationException.schemaBinding( | ||
| DataNodeMiscMessages.EXCEPTION_TABLET_COLUMN_CATEGORY_IS_MISSING_A812B500); | ||
| } | ||
| } |
There was a problem hiding this comment.
Too many validations in this class.
The tablet coming from upper streams should have already been validated.
Check the necessity of the checks here and do not let unnecessary checks slow down and complicate the process.
There was a problem hiding this comment.
Removed the full Tablet structural scan, value-array length/type checks, per-cell null validation, and repeated schema/category validation. Upstream conversion supplies the structural guarantees. The filter retains predicate-specific checks for missing bindings, missing referenced columns, and non-TAG columns. It also uses primitive row indexes and copies typed arrays directly during compaction.
Commit: 7caec10.
| } | ||
| hasUnknown |= result == TruthValue.UNKNOWN; | ||
| } |
There was a problem hiding this comment.
We need to continue after UNKNOWN to preserve SQL three-valued logic: UNKNOWN AND FALSE is FALSE, and UNKNOWN OR TRUE is TRUE. In particular, NOT (UNKNOWN AND FALSE) must match. Kept short-circuiting on FALSE for AND and TRUE for OR, documented why UNKNOWN is not decisive, and added regression assertions for both operand orders and NOT. These assertions also caught and fixed the shared parser's false rejection of valid NOT (...) expressions.
Commit: 7caec10.
|
|
||
| private static TableBinding bindTable( | ||
| final List<Identifier> referencedFields, final TsTable table) { | ||
| final java.util.Set<String> tagNames = new java.util.HashSet<>(); |
There was a problem hiding this comment.
Added normal imports for Set, HashSet, and Locale, and removed their fully qualified references throughout the class.
Commit: 7caec10.
| private static String canonicalizeTopicAttributeKey(final String key) { | ||
| if (Objects.isNull(key)) { | ||
| return null; | ||
| } | ||
| final String normalizedKey = key.trim().toLowerCase(Locale.ROOT); | ||
| switch (normalizedKey) { | ||
| case TopicConstant.PATH_KEY: | ||
| return TopicConstant.PATH_KEY; | ||
| case TopicConstant.PATTERN_KEY: | ||
| return TopicConstant.PATTERN_KEY; | ||
| case TopicConstant.DATABASE_KEY: | ||
| return TopicConstant.DATABASE_KEY; | ||
| case TopicConstant.TABLE_KEY: | ||
| return TopicConstant.TABLE_KEY; | ||
| case TopicConstant.COLUMN_FILTER_KEY: | ||
| return TopicConstant.COLUMN_FILTER_KEY; | ||
| case TopicConstant.TAG_FILTER_KEY: | ||
| return TopicConstant.TAG_FILTER_KEY; | ||
| case TopicConstant.RETENTION_BYTES_KEY: | ||
| return TopicConstant.RETENTION_BYTES_KEY; | ||
| case TopicConstant.RETENTION_MS_KEY: | ||
| return TopicConstant.RETENTION_MS_KEY; | ||
| case TopicConstant.START_TIME_KEY: | ||
| return TopicConstant.START_TIME_KEY; | ||
| case TopicConstant.END_TIME_KEY: | ||
| return TopicConstant.END_TIME_KEY; |
There was a problem hiding this comment.
This canonicalizes case-insensitive built-in topic keys before creation, ALTER merging, and deserialization. For example, ALTER with TAG-FILTER must replace an existing tag-filter rather than leave two values that different getters could read inconsistently. Replaced the switch with the shared TopicProperty registry, documented the purpose, and added tests for mixed-case ALTER and both deserialization paths. Extension keys and predicate values retain their original case.
Commit: 7caec10.
Preserve subscription configuration tests and bilingual message additions. Reuse the existing table-cache version API and adapt subscription tests to topic metadata and model-specific lookups.
Description
Table subscriptions can select databases, tables, and output columns, but they cannot filter individual rows by TAG value. This PR adds the
tag-filtertopic attribute for table-model subscriptions. Its default value istrue, so existing topics keep their current behavior.Filter semantics
AND,OR,NOT,=,!=,IN,LIKE,REGEXP, andIS NULL, evaluated with SQL three-valued logic.column-filterprojection.Delivery paths and configuration changes
TagFilterStatusandTagFilterMessageto table-modelSHOW TOPICS. The status reportsMATCH_ALL,MATCH_NONE,ACTIVE, orERRORfor the DataNode executing the statement; the message contains the local matcher failure when the status isERROR. Tree-model output remains unchanged.Validation
ShowTopicsTaskTest,TabletTagFilterTest, andSubscriptionTopicAgentTest).TopicConfigTest: 10 tests passed.mvn test-compile -pl iotdb-core/datanode -P with-zh-locale -DskipTests).git diff --check: passed.Performance comparison
Benchmarked commit
5e9f4c07cd7with the subscription feature gate enabled in the test build; the TAG filtering implementation was unchanged.iot-benchmarkgenerated a fixed table with 1,000,000 rows, 200 devices, 10 random DOUBLE fields, and one uniformly distributed TAG (tag_0, 10 values). All columns were retained.Snapshot subscriptions; one warm-up and three measured runs per case, with shuffled order. The table reports medians. Consumption time runs from the start of
subscribe()until all expected field cells are delivered and the last data batch is acknowledged. Topic creation, the later automatic snapshot-termination notification, and subsequent TsFile content verification are excluded. Input throughput uses all 1,000,000 source rows for every case.true)tag_0 IS NOT NULL(100%)tag_0 IN ("value_0", "value_1", "value_2", "value_3", "value_4")(50%)tag_0 = "value_0"(10%)true)tag_0 IS NOT NULL(100%)tag_0 IN ("value_0", "value_1", "value_2", "value_3", "value_4")(50%)tag_0 = "value_0"(10%)true)tag_0 IS NOT NULL(100%)tag_0 IN ("value_0", "value_1", "value_2", "value_3", "value_4")(50%)tag_0 = "value_0"(10%)All 32 runs (including warm-ups) passed TAG-value, per-field row completeness, and duplicate-cell verification. Expected outputs were 1,000,000, 1,000,000, 500,000, and 100,000 logical rows with all 10 fields. Column-split Tablet/TsFile payloads were verified by device, timestamp, and field. TsFile contents were read after the measured delivery interval.
This PR has:
Key changed/added classes (or packages if there are too many classes) in this PR
org.apache.iotdb.db.subscription.tagfilterSubscriptionFilterSnapshotSubscriptionTreeViewProjectorSubscriptionBrokerAgentConsensusLogToTabletConverterPipeTabletEventTsFileBatchShowTopicsTaskTopicConfigandTopicMeta