Skip to content

Core: Pass storage credentials to ioBuilder-created FileIO in RESTSes…#15752

Open
kaveti wants to merge 1 commit intoapache:mainfrom
kaveti:fix-iobuilder-storage-credentials
Open

Core: Pass storage credentials to ioBuilder-created FileIO in RESTSes…#15752
kaveti wants to merge 1 commit intoapache:mainfrom
kaveti:fix-iobuilder-storage-credentials

Conversation

@kaveti
Copy link

@kaveti kaveti commented Mar 24, 2026

What

RESTSessionCatalog.newFileIO() has two code paths for creating a FileIO:

  1. ioBuilder path — used when a custom ioBuilder is provided (e.g. by Trino)
  2. Reflection path — used when ioBuilder is null, delegates to CatalogUtil.loadFileIO()

The reflection path correctly passes storage credentials (vended via LoadTableResponse) to
FileIO implementations that implement SupportsStorageCredentials. The ioBuilder path,
however, completely ignores the storageCredentials parameter — silently discarding them.

Why this matters

This was introduced in #12591, which added storage credentials V3 support but only wired up
credential passing for the reflection path. The ioBuilder path was missed because Trino is
currently the only engine that uses it.

In practice, this means that when a REST catalog server vends storage credentials
(e.g. short-lived S3/GCS/ADLS tokens), any engine using a custom ioBuilder never receives
them. For Trino specifically, this blocks the ability to use vended credentials with their
custom FileIO — which is the subject of ongoing work in
trinodb/trino#28425.

confirmed as unintentional

The fix

After ioBuilder.apply(context, properties) creates the FileIO, we now check if the
instance implements SupportsStorageCredentials and call setCredentials() with the
converted credentials — matching exactly what CatalogUtil.loadFileIO() already does at
lines 418–419.

The change is 8 lines of logic, non-breaking, and covers all 5 call sites that flow through
tableFileIO() → newFileIO():

  • loadTable()
  • registerTable()
  • Builder.load()
  • Builder.createTransaction()
  • Builder.replaceTransaction()

@github-actions github-actions bot added the core label Mar 24, 2026
if (null != ioBuilder) {
return ioBuilder.apply(context, properties);
FileIO fileIO = ioBuilder.apply(context, properties);
if (!storageCredentials.isEmpty() && fileIO instanceof SupportsStorageCredentials) {
Copy link
Contributor

Choose a reason for hiding this comment

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

@nastra could you pls take a look at this change?

if (null != ioBuilder) {
return ioBuilder.apply(context, properties);
FileIO fileIO = ioBuilder.apply(context, properties);
if (!storageCredentials.isEmpty() && fileIO instanceof SupportsStorageCredentials) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
if (!storageCredentials.isEmpty() && fileIO instanceof SupportsStorageCredentials) {
if (!storageCredentials.isEmpty() && fileIO instanceof SupportsStorageCredentials ioWithCredentials) {

return ioBuilder.apply(context, properties);
FileIO fileIO = ioBuilder.apply(context, properties);
if (!storageCredentials.isEmpty() && fileIO instanceof SupportsStorageCredentials) {
((SupportsStorageCredentials) fileIO)
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
((SupportsStorageCredentials) fileIO)
ioWithCredentials

Consumer<Map<String, String>> responseHeaders) {
T response =
super.handleRequest(route, vars, httpRequest, responseType, responseHeaders);
if (route == Route.LOAD_TABLE && response instanceof LoadTableResponse) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
if (route == Route.LOAD_TABLE && response instanceof LoadTableResponse) {
if (route == Route.LOAD_TABLE && response instanceof LoadTableResponse loadResponse) {

T response =
super.handleRequest(route, vars, httpRequest, responseType, responseHeaders);
if (route == Route.LOAD_TABLE && response instanceof LoadTableResponse) {
LoadTableResponse loadResponse = (LoadTableResponse) response;
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
LoadTableResponse loadResponse = (LoadTableResponse) response;

Copy link
Contributor

@nastra nastra left a comment

Choose a reason for hiding this comment

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

LGTM, left just a few small comments

…sionCatalog

RESTSessionCatalog.newFileIO() has two code paths for creating a FileIO:
1. ioBuilder path - when a custom ioBuilder is provided (used by Trino)
2. Reflection path - when ioBuilder is null (uses CatalogUtil.loadFileIO())

The reflection path correctly passes storage credentials to FileIO
implementations that implement SupportsStorageCredentials via
setCredentials(). However, the ioBuilder path completely ignores the
storageCredentials parameter, silently discarding vended credentials.

After ioBuilder.apply() creates the FileIO, check if it implements
SupportsStorageCredentials and call setCredentials() - matching the
behavior of CatalogUtil.loadFileIO().
@kaveti kaveti force-pushed the fix-iobuilder-storage-credentials branch from 62f5b66 to 9f64efb Compare March 24, 2026 17:26
@kaveti
Copy link
Author

kaveti commented Mar 24, 2026

@nastra i have addressed your review comments.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants