Skip to content

Subscription: refactor payload APIs to use ResultSet and tsfile readers#17225

Open
VGalaxies wants to merge 7 commits intoapache:masterfrom
VGalaxies:sub-client
Open

Subscription: refactor payload APIs to use ResultSet and tsfile readers#17225
VGalaxies wants to merge 7 commits intoapache:masterfrom
VGalaxies:sub-client

Conversation

@VGalaxies
Copy link
Contributor

@VGalaxies VGalaxies commented Feb 27, 2026

Summary

This PR refactors the subscription client payload APIs to expose record-oriented and tsfile-oriented interfaces directly, instead of the previous handler-centric API surface.

The main goal is to make the client API align better with the public tsfile interfaces and remove the custom dataset wrapper that was only used by subscription consumers.

Main interface changes

  • Replace SubscriptionSessionDataSet / SubscriptionSessionDataSetsHandler with List<org.apache.tsfile.read.query.dataset.ResultSet> exposed from SubscriptionMessage#getRecords().
  • Rename SubscriptionMessage#getSessionDataSetsHandler() to SubscriptionMessage#getRecords().
  • Rename SubscriptionMessage#getTsFileHandler() to SubscriptionMessage#getTsFile().
  • Rename the payload message types to RECORD_HANDLER and TS_FILE.
  • Keep a single SubscriptionTsFileHandler#openReader() entry point while avoiding hard-coding consumer-facing logic around concrete reader construction.

Reader changes

  • For tree-model topics, openReader() now builds the reader with TsFileTreeReaderBuilder.
  • For table-model topics, openReader() now builds the reader with TsFileReaderBuilder.
  • This keeps reader creation aligned with the public tsfile builder APIs:
    • new TsFileTreeReaderBuilder().file(file).build()
    • new TsFileReaderBuilder().file(file).build()

Payload and implementation updates

  • Introduce SubscriptionRecordHandler as the payload adapter for record-format messages.
  • Remove the obsolete SubscriptionSessionDataSet and SubscriptionSessionDataSetsHandler classes.
  • Reuse the tsfile ResultSet interface instead of exposing a custom subscription-only dataset abstraction.
  • Preserve helper capabilities needed by the client/tests during migration, such as record iteration and tablet extraction.
  • Update subscription config/topic constants and examples to match the new API naming.

Compatibility and migration notes

  • Consumer code should move from handler-based accessors to:
    • message.getRecords()
    • message.getTsFile()
  • Code that previously depended on SubscriptionSessionDataSet should now use the tsfile ResultSet API directly.
  • Integration tests and examples in this PR were updated accordingly for both tree-model and table-model cases.

Verification

  • mvn -T 8 spotless:apply -P with-integration-tests
  • mvn -T 8 clean package -P with-integration-tests -DskipUTs -pl integration-test,distribution -DfailIfNoTests=false -am -U

This PR was primarily authored with Codex using gpt-5.4 xhigh and then hand-reviewed by me. I AM responsible for every change made in this PR. I aimed to keep it aligned with our goals, though I may have missed minor issues. Please flag anything that feels off, I'll fix it quickly.

…ile APIs

Reuse tsfile ResultSet in subscription payloads, rename message accessors to getRecords()/getTsFile(), and return ITsFileReader from tsfile handler. Also rename format constants to SubscriptionRecordHandler/SubscriptionTsFileHandler with legacy TsFileHandler compatibility.

BREAKING CHANGE: SubscriptionMessage.getSessionDataSetsHandler() and getTsFileHandler() are removed in favor of getRecords() and getTsFile(). SubscriptionSessionDataSet/SubscriptionSessionDataSetsHandler are removed and format default/value names are updated.
…aders

- migrate subscription message usage to getRecords/getTsFile across tests and examples

- add record helpers (tablet iterator, row iteration) for ResultSet-based payloads

- unify tsfile access through openReader() with tree/table model-specific implementations

BREAKING CHANGE: subscription payload consumers should use getRecords()/getTsFile() and the new message type names (RECORD_HANDLER/TS_FILE); legacy handler-style payload APIs are no longer used.
@VGalaxies VGalaxies marked this pull request as ready for review March 12, 2026 14:32
@VGalaxies VGalaxies requested review from Copilot and jt2594838 March 12, 2026 14:34
@VGalaxies VGalaxies changed the title Subscription: refact client interface Subscription: refactor payload APIs to use ResultSet and tsfile readers Mar 12, 2026
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR refactors the IoTDB subscription client payload APIs, replacing SubscriptionSessionDataSet / SubscriptionSessionDataSetsHandler with a ResultSet-based interface, renaming getSessionDataSetsHandler() to getRecords() and getTsFileHandler() to getTsFile(), and updating openReader() to use the v4 TsFileTreeReaderBuilder / TsFileReaderBuilder APIs. The message type enum values are renamed from SESSION_DATA_SETS_HANDLER/TS_FILE_HANDLER to RECORD_HANDLER/TS_FILE.

Changes:

  • Introduce SubscriptionRecordHandler as the new payload adapter, exposing List<ResultSet> instead of the custom SubscriptionSessionDataSetsHandler, and remove the old SubscriptionSessionDataSet / SubscriptionSessionDataSetsHandler classes.
  • Refactor SubscriptionTsFileHandler.openReader() to use generic return type dispatching between TsFileTreeReaderBuilder (tree-model) and TsFileReaderBuilder (table-model).
  • Update all integration tests, examples, CLI tools, and topic constants to use the new API naming, with backward-compatible deprecated constants for old format values.

Reviewed changes

Copilot reviewed 68 out of 68 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
SubscriptionTsFileHandler.java Replace TsFileReader/TsFileSequenceReader with generic openReader() dispatching to v4 builders
SubscriptionRecordHandler.java New payload adapter implementing Iterable<ResultSet> with inner SubscriptionRecord class
SubscriptionSessionDataSetsHandler.java Deleted (replaced by SubscriptionRecordHandler)
SubscriptionSessionDataSet.java Deleted (replaced by SubscriptionRecord)
SubscriptionMessage.java Rename accessors to getRecords(), getTsFile(), add getRecordTabletIterator()
SubscriptionMessageType.java Rename enum values to RECORD_HANDLER and TS_FILE
TopicConstant.java Rename format constants, add deprecated aliases
TopicConfig.java Add isTsFileFormat() with legacy backward compat
AbstractSubscriptionConsumer.java Update references to new API names
CLI & example files Update to new API names
~30 integration test files Update to new API, adding downcasts from ResultSet to SubscriptionRecord

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +48 to +51
@Deprecated
public static final String FORMAT_SESSION_DATA_SETS_HANDLER_VALUE = FORMAT_RECORD_HANDLER_VALUE;

@Deprecated public static final String FORMAT_TS_FILE_HANDLER_VALUE = FORMAT_TS_FILE_VALUE;
Copy link

Copilot AI Mar 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The @Deprecated constants have had their values silently changed. FORMAT_TS_FILE_HANDLER_VALUE was previously "TsFileHandler" and is now "SubscriptionTsFileHandler". Similarly, FORMAT_SESSION_DATA_SETS_HANDLER_VALUE was "SessionDataSetsHandler" and is now "SubscriptionRecordHandler".

Any external code that was using these deprecated constants as string values (e.g., stored in topic configurations or compared against persisted strings) will break silently. The isTsFileFormat() method in TopicConfig correctly handles the legacy "TsFileHandler" string, but there's no equivalent backward compat for the old "SessionDataSetsHandler" string in the non-TsFile path. If existing topics were created with format=SessionDataSetsHandler, the behavior may change.

Consider preserving the original string values in the deprecated constants, or adding a legacy check for "SessionDataSetsHandler" similar to LEGACY_FORMAT_TS_FILE_HANDLER_VALUE.

Copilot uses AI. Check for mistakes.
// System.out.println(FORMAT.format(new Date()) + " onReceived=" + onReceived.get());
final SubscriptionTsFileHandler tsFileHandler = message.getTsFileHandler();
final SubscriptionTsFileHandler tsFileHandler = message.getTsFile();
try (final TsFileReader tsFileReader = tsFileHandler.openReader()) {
Copy link

Copilot AI Mar 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These three call sites use openReader() without an explicit cast (e.g. TsFileReader tsFileReader = tsFileHandler.openReader()), relying on the generic type inference of the new <T extends AutoCloseable> T openReader() method. While this compiles, if the builder returns a type other than TsFileReader (e.g., ITsFileTreeReader), it will fail with a ClassCastException at runtime. This should be updated to use an explicit cast consistent with the other integration tests, or better yet, use the actual return type of the builder.

Suggested change
try (final TsFileReader tsFileReader = tsFileHandler.openReader()) {
try (final TsFileReader tsFileReader = (TsFileReader) tsFileHandler.openReader()) {

Copilot uses AI. Check for mistakes.
Comment on lines 41 to 47
@SuppressWarnings("unchecked")
public <T extends AutoCloseable> T openReader() throws IOException {
return (T)
(Objects.isNull(databaseName)
? new TsFileTreeReaderBuilder().file(new File(absolutePath)).build()
: new TsFileReaderBuilder().file(new File(absolutePath)).build());
}
Copy link

Copilot AI Mar 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The generic method signature <T extends AutoCloseable> T openReader() is type-unsafe. It relies on unchecked cast inference and the caller can assign the result to any AutoCloseable subtype without a compiler warning — but a ClassCastException will occur at runtime if the inferred type doesn't match the actual return type. For example, in AbstractSubscriptionTreeRegressionIT (lines 346, 413, 464), openReader() is called as TsFileReader tsFileReader = tsFileHandler.openReader() (no explicit cast), which will silently infer T = TsFileReader. If the builder returns a different type (e.g. ITsFileTreeReader), this will fail at runtime.

A safer approach is to either:

  1. Return AutoCloseable (or a common interface like ITsFileTreeReader / ITsFileReader) and let callers cast explicitly, making the risk visible.
  2. Provide two separate methods (e.g. openTreeReader() / openTableReader()) with concrete return types.

Copilot uses AI. Check for mistakes.
Comment on lines +103 to +109
public List<ResultSet> getRecords() {
if (handler instanceof SubscriptionRecordHandler) {
return ((SubscriptionRecordHandler) handler).getRecords();
}
throw new SubscriptionIncompatibleHandlerException(
String.format("%s do not support getRecords().", handler.getClass().getSimpleName()));
}
Copy link

Copilot AI Mar 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new public API exposes List<ResultSet> from getRecords(), but almost every consumer site immediately downcasts the ResultSet to SubscriptionRecordHandler.SubscriptionRecord to call .getTablet(), .hasNext(), .nextRecord(), .getColumnNames(), etc. This defeats the purpose of the refactoring — the API promises a standard ResultSet abstraction, but it isn't actually usable without the downcast.

Consider either:

  1. Exposing List<SubscriptionRecordHandler.SubscriptionRecord> directly from getRecords() (since that's what all callers need).
  2. Adding convenience methods on SubscriptionMessage for the most common operations (like the existing getRecordTabletIterator()) so callers don't need to know about SubscriptionRecord at all.

Copilot uses AI. Check for mistakes.
return records.iterator();
}

public static class SubscriptionRecord extends AbstractResultSet {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SubscriptionRecord -> SubscriptionResultSet

System.out.println(dataSet.getColumnTypes());
while (dataSet.hasNext()) {
System.out.println(dataSet.next());
for (final ResultSet dataSet : message.getRecords()) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getRecords -> getResultSets

Comment on lines 212 to 214
try (final ITsFileTreeReader reader = message.getTsFile().openReader()) {
reader.getAllDeviceIds().forEach(System.out::println);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

openReader -> openTreeReader, and also add openTableReader?

Comment on lines +206 to +207
while (((org.apache.iotdb.session.subscription.payload
.SubscriptionRecordHandler.SubscriptionRecord)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do not use the full name, check all places

Comment on lines +481 to +482
try (final TsFileReader tsFileReader =
(TsFileReader) message.getTsFile().openReader()) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TsFileReader is an inner implementation, better not to use it.

Replace SubscriptionTsFileHandler.openReader() with openTreeReader() and openTableReader(). Update tree-model examples and integration tests to use ITsFileTreeReader and a v4 query adapter.

BREAKING CHANGE: tree consumers must call openTreeReader(), and table consumers must call openTableReader().
Rename SubscriptionMessage.getRecords() to getResultSets() and SubscriptionRecordHandler.SubscriptionRecord to SubscriptionResultSet. Update examples and integration tests to use the new record payload names.

BREAKING CHANGE: callers must replace getRecords() with getResultSets() and SubscriptionRecord with SubscriptionResultSet.
@VGalaxies VGalaxies requested a review from jt2594838 March 13, 2026 13:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants