Skip to content

[improve][client] Add Builder Methods to Create Message-based TableView - #24809

Open
namest504 wants to merge 11 commits into
apache:masterfrom
namest504:master
Open

[improve][client] Add Builder Methods to Create Message-based TableView#24809
namest504 wants to merge 11 commits into
apache:masterfrom
namest504:master

Conversation

@namest504

@namest504 namest504 commented Oct 2, 2025

Copy link
Copy Markdown
Contributor

Fixes: #24744

Main Issue: #24744

PIP: 445 #24842

Motivation

The current TableView API only exposes the deserialized message value, which limits access to essential message metadata like properties, event time, or the raw message object itself.

This PR introduces a flexible, non-breaking mechanism to create value-mapped views over a topic. It replaces the initial proposal of adding a getRawMessage() method, which would have performance implications for all users. Instead, it adds a createMapped method to the TableViewBuilder, allowing users to transform a full Message<T> into any custom object V that suits their needs. This provides maximum flexibility, from accessing the full raw message to creating custom, memory-efficient objects.

Modifications

  • Added createMapped and createMappedAsync to TableViewBuilder: These methods accept a Function<Message<T>, V> mapper, letting users define their own mapping from a message to the value stored in the TableView. A null mapper is rejected with IllegalArgumentException (failed future in the async variant), and a null return value from the mapper is treated as a tombstone.
  • Moved the core logic to AbstractTableViewImpl<T, V>: TableViewImpl keeps its name and becomes a thin subclass, so the behavior for existing users (including message pooling and release) is unchanged. The class naming follows the structure from @lhotari's experiment branch.
  • Introduced MessageMapperTableViewImpl<T, V>: Implements the mapped views. Message pooling is disabled for mapped views since the mapper may retain the Message instance (e.g. with Function.identity()), so the pooled payload buffer must not be reused or released.

Verifying this change

  • Unit tests: TableViewBuilderImplTest.testCreateMapped* (mapped view creation, null mapper validation for both sync and async variants).

  • Integration tests in TableViewTest: testCreateMapped (custom mapping, value updates, tombstone via null mapping) and testCreateMappedWithIdentityMapper (TableView<Message<T>> with metadata access).

  • Verified locally with ./gradlew quickCheck and the test classes above.

  • Make sure that the change passes the CI checks.

Does this pull request potentially affect one of the following parts:

If the box was checked, please highlight the changes

  • Dependencies (add or upgrade a dependency)
  • The public API
  • The schema
  • The default values of configurations
  • The threading model
  • The binary protocol
  • The REST endpoints
  • The admin CLI options
  • The metrics
  • Anything that affects deployment

Documentation

  • doc
  • doc-required
  • doc-not-needed
  • doc-complete

Matching PR in forked repository

PR in forked repository: fork-repo

@github-actions github-actions Bot added the doc-required Your PR changes impact docs and you will update later. label Oct 2, 2025
…agement by adding retain() before storing message to rawMessages

- Prevent premature message release that causes getRawMessage to return null keys.
- Ensure message reference count is balanced by retaining message before storing and releasing old message after replacement.
- Keep message release in finally block for safety.

@lhotari lhotari left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks for the contribution!

Since this changes the Pulsar API, we'd need to first go through the PIP process to modify the public API.

Instead of adding a separate ConcurrentMap for storing the messages, I'd suggest going with an approach where the TableViewBuilder would have separate methods for creating a view for messages.

TableView<Message<T>> createForMessages() throws PulsarClientException;
CompletableFuture<TableView<Message<T>>> createForMessagesAsync() throws PulsarClientException;

I'm not exactly sure if there's any obstacles with this approach, but it seems that it could be a better way forward so that the existing runtime behavior of TableView implementation wouldn't change (like it does with the current PR changes).

Comment thread pulsar-client/src/main/java/org/apache/pulsar/client/impl/TableViewImpl.java Outdated
@namest504

Copy link
Copy Markdown
Contributor Author

Hi @lhotari,

Thanks for the review and the great suggestion about using the TableViewBuilder!

I completely agree that we should avoid impacting the runtime behavior for existing users.

As you recommended, I've created a PIP for this API change, which you can find here: #24842

Let's continue the design discussion over on the PIP PR.

@lhotari

lhotari commented Oct 14, 2025

Copy link
Copy Markdown
Member

Hi @lhotari,

Thanks for the review and the great suggestion about using the TableViewBuilder!

I completely agree that we should avoid impacting the runtime behavior for existing users.

As you recommended, I've created a PIP for this API change, which you can find here: #24842

Let's continue the design discussion over on the PIP PR.

@namest504 you seemed to ignore the suggestion in the comment. I'd suggest revisiting the PIP accordingly and renaming it. You can reply to the comment on the PIP PR, #24842 (review).

Signed-off-by: namest504 <namest504@gmail.com>
Signed-off-by: namest504 <namest504@gmail.com>
Signed-off-by: namest504 <namest504@gmail.com>
@namest504 namest504 changed the title [improve][api] Add getRawMessage() method to TableView for accessing raw Pulsar message [improve][api] Add Builder Methods to Create Message-based TableView Oct 20, 2025
@namest504
namest504 requested a review from lhotari October 20, 2025 04:47
… method

Signed-off-by: namest504 <namest504@gmail.com>
Resolve conflicts by rebuilding the PIP-445 changes on top of current master:
- Keep the original TableViewImpl class name; move the shared logic to AbstractTableViewImpl
- Rename MessageMapperTableView to MessageMapperTableViewImpl
- Drop the unused MessageTableView class and the import/FQN churn
…r argument

- The mapper may retain the Message instance (e.g. Function.identity()), so the
  reader must not use pooled messages for mapped views; the payload buffer would
  otherwise be released or leaked. The classic TableView keeps using pooled
  messages and releasing them as before.
- Reject a null mapper with IllegalArgumentException (failed future in the async
  variant) instead of failing later in the async pipeline.
- Add unit tests for createMapped/createMappedAsync in TableViewBuilderImplTest.
@namest504

Copy link
Copy Markdown
Contributor Author

@lhotari I've addressed the review comments and resolved the conflicts with master (rebuilt the refactoring on top of the current Gradle/slog codebase).

Main changes since your last review:

  • Kept the original TableViewImpl class name and moved the shared logic to a package-private AbstractTableViewImpl<T, V>, following the naming in your experiment branch. No imports are removed and there are no fully qualified class name references anymore.
  • Removed the unused MessageTableView class left over from the earlier createForMessages approach.
  • Disabled message pooling for mapped views: the mapper may retain the Message instance, so the pooled payload buffer must not be reused or released. The classic TableView path keeps pooling and releasing as before.
  • Added mapper null-validation and unit tests in TableViewBuilderImplTest.

The vote result for PIP-445 has been posted on the dev mailing list (3 binding +1s), and I've also updated the PIP document in #24842 to match the final class names. PTAL when you have a chance.

@namest504 namest504 changed the title [improve][api] Add Builder Methods to Create Message-based TableView [improve][client] Add Builder Methods to Create Message-based TableView Jul 10, 2026
…bleViewImpl

FieldUtils.readDeclaredField only looks at the exact class, so reading the
reader/compactionStrategy fields from TableViewImpl fails now that they live
in AbstractTableViewImpl. Use readField, which traverses the class hierarchy.
@namest504
namest504 requested a review from lhotari July 10, 2026 03:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

doc-required Your PR changes impact docs and you will update later.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

API to allow access to raw message for TableView

2 participants