Add table redirection support in SPI#7606
Conversation
|
@phd3 please see build failures |
There was a problem hiding this comment.
Why do we return Stream? Stream in SPI does not look like a good idea. Is it a lazy collection?
There was a problem hiding this comment.
@kokosing We just needed a redirection-aware method for listing table columns without breaking current SPI, we're just combining the return type change along with it. #5160 (comment)
the current implementation in this PR lets redirection go through Metadata instead of it being returned from ConnectorMetadata as suggested in the above comment.
There was a problem hiding this comment.
io.trino.spi.connector.ConnectorTableSchema?
There was a problem hiding this comment.
Ultimately we need ColumnMetadata objects from MetadataListing#listTableColumns, which seems to more information than ColumnSchema in ConnectorTableSchema right now. Since the purpose of ConnectorTableSchema seems to be avoiding fetching full metadata, may be we can keep the two separate. wdyt?
also open to comments/suggestions for class/method naming here. May be we can use Stream<TableColumnsMetadata> streamTableColumns(...) instead of current Stream<ColumnsMetadata> listTableColumnsStream(...).
There was a problem hiding this comment.
What it means that it is Optional?
e8a8546 to
8b77514
Compare
There was a problem hiding this comment.
In case of error, will error message refer to redirected table, e.g:
SELECT * FROM table_to_be_redirected
would cause e.g
Cannot analyze `redirected_table` table
?
There was a problem hiding this comment.
yeah, changes along the lines of #7134 would be helpful to propagate this information properly, when it's checked in.
for now, I think redirected table in error message helps keep the error message behavior consistent. i.e. any future errors (say during execution) will only refer to the target as the source won't be available. I agree though that it can be a bit confusing. wdyt?
There was a problem hiding this comment.
#7134 is only partially helpful as warning/events might not be shown by all tools.
or now, I think redirected table in error message helps keep the error message behavior consistent. i.e. any future errors (say during execution) will only refer to the target as the source won't be available.
I'm not sure we actually refer to table names during execution. We do during optimization, but even there we already use "virtual" table names for pushed down TableHandles.
I think the biggest confusion will actually come during analysis as error messages won't match queries.
I also look at a way to reduce amount of changes required by new SPI. Currently, it's pretty extensive and touches a lot of components. Potentially, transparent redirection within getTableHandle could reduce scope of changes.
...trino-main/src/main/java/io/trino/connector/informationschema/InformationSchemaMetadata.java
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
If we redirect here, then error messages below will be confusing for users, e.g:
CREATE TABLE foo LIKE source_catalog.bar
with error
LIKE table catalog `target_catalog` does not exist
If getTableHandle was redirection aware, we could run this checks on original table name and only within getTableHandle throw redirection specific errors.
There was a problem hiding this comment.
With the new implementation:
CREATE TABLE foo LIKE source_catalog.bar
- if source_catalog didn't exist, it's reported as is.
- if target_catalog didn't exist, it'd be reported in the error message, along with the message that the table was redirected from a source_catalog table.
- If target_catalog table is different from created table's catalog, the error message
LIKE table across catalogs is not supported ...would also contain information about redirection.
However, any issues with ACL checks would still be reported on the target. (similar to StatementAnalyzer#visitTable)
1a9c3d1 to
0ee134d
Compare
There was a problem hiding this comment.
extract this to separate method (starting from try)
There was a problem hiding this comment.
IMO extracting it to a separate method may not be very useful, because exception catching here is context dependent, and seems easier to read inline. wdyt?
There was a problem hiding this comment.
the ignoring logic assumes that we wouldn't want to break listing in general if one table has redirection issues. (Similar to HiveMetadata#getViews, HiveMetadata#listTableColumns)
In this particular case, do you mean that Optional<Set<String>> tables = filterString(constraint, TABLE_NAME_COLUMN_HANDLE) will return non-empty only if user has requested specific tables explicitly and we should fail in that case if redirection errors are observed?
core/trino-main/src/main/java/io/trino/metadata/MetadataListing.java
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Why Optional<List<ColumnMetadata>>? If table has no columns, there we should not list it at all as part of tableColumns
There was a problem hiding this comment.
The connector needs to tell the engine that the table is redirected so the engine needs to find the columns from the redirected table. e.g. If user lists columns for table A that's redirected to B, they should get the metadata from B, but the table name displayed is still A.
There was a problem hiding this comment.
It's not immediately clear that this is for a single catalog (the original code had this problem as well). My first thought is that it wasn't safe since table names could overlap. How about
List<TableColumnsMetadata> catalogColumns = getOnlyElement(metadata.listTableColumns(session, prefix).values(), List.of());
Map<SchemaTableName, Optional<List<ColumnMetadata>>> tableColumns = catalogColumns.stream()
.collect(toImmutableMap(TableColumnsMetadata::getTable, TableColumnsMetadata::getColumns));
core/trino-main/src/main/java/io/trino/metadata/MetadataListing.java
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
I think we can force connector to not redirect the storage table, since it can be writable and we've avoided similar usecases for now.
There was a problem hiding this comment.
We can leave addEmptyColumnReferencesForTable as is (see also #7606 (comment))
There was a problem hiding this comment.
Could you elaborate why we'd want to keep it that way?
my reasoning was that: tableColumnReferences are used for ACL checks. in case of views/materialized views, that should be the source, but for tables, that should be redirected table. so separating them makes this more readable.
If we consider that column metadata responsibility is always on target, the currently implemented behavior would make sense right?
There was a problem hiding this comment.
Let's leave this if is and add another and additional check (for redirected table name) if source catalog and schema exists
There was a problem hiding this comment.
-
for tables that haven't been redirected, the flow is untouched.
targetTableNameis same asnameand exception handling happens here for catalog, schema, table existence (like before). -
for redirected tables, imo source schema existence shouldn't matter - as long as the connector can redirect to a target.
MetadataManager#getRedirectedTableHandlethrows exception if (1) catalog doesn't exist during redirection at any step OR (2) getTableHandle fails on the target table due to catalog/schema/table existence issue, and reported accordingly.
If source catalog doesn't exist, the redirection will stop there and reported as "source catalog doesn't exist", which is the current behavior.
even though there's some code-change, I think scope of the flow-change is small here, as you had suggested earlier. Redirection related logic/exception-handling is contained in the MetadataManager redirection apis.
So IMO it'd be better to not duplicate the checks here for source and target both. Thoughts?
There was a problem hiding this comment.
Let's make analyzeFiltersAndMasks handle redirections itself
There was a problem hiding this comment.
I think we need to handle at a higher level, because we are not redirecting views, and createScopeForView also uses this method.
|
@sopel39 PTAL, I had split this out in separate commits and responded to comments. |
core/trino-main/src/main/java/io/trino/metadata/MetadataManager.java
Outdated
Show resolved
Hide resolved
core/trino-main/src/main/java/io/trino/metadata/MetadataManager.java
Outdated
Show resolved
Hide resolved
core/trino-main/src/main/java/io/trino/metadata/MetadataManager.java
Outdated
Show resolved
Hide resolved
core/trino-main/src/main/java/io/trino/metadata/MetadataManager.java
Outdated
Show resolved
Hide resolved
core/trino-main/src/main/java/io/trino/metadata/MetadataManager.java
Outdated
Show resolved
Hide resolved
core/trino-main/src/main/java/io/trino/metadata/MetadataManager.java
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
getTableHandle returns empty when catalog, schema or table in input QualifiedObjectName tableName is empty or if table was not found by connector.
Why do we throw in redirection case ? Could we fallback to original table (maybe provide warning about it) ?
There was a problem hiding this comment.
If I'm understanding the scenario, this means a connector returned a redirection, then the target did not exist. Why would we want to fallback?
In the case of the Hive connector redirecting to Iceberg, the subsequent read operation would fail (maybe in a confusing way). Though this might be a bad example, since they share the same metastore and thus this should not occur.
There was a problem hiding this comment.
I was wondering whether in case of problems in finding the target table (could be a bad redirection config or target connector metastore is unavailable temporarily), a silent fallback (with warning) is useful to avoid disrupting normal operations.
A contrived example would be that the target table is in memory connector, then the coordinator was restarted, now the target table doesn't exist. The source connector probably can't easily detect status of target table.
However, it's also useful to get an explicit error in case one doesn't want to fallback to source at all.
Would we have a catalog session property in each connector which implements this redirection to disable the redirection if needed ?
There was a problem hiding this comment.
IMO it'd be better to fail-fast in this case with a clear error, since the target table is "externally" provided by the connector, so that the engine code doesn't need to be aware of throwing redirection-related errors - whenever it needs a handle.
Having a catalog property switch seems like a good idea.
.../trino-main/src/main/java/io/trino/sql/planner/iterative/rule/ApplyTableScanRedirection.java
Outdated
Show resolved
Hide resolved
testing/trino-tests/src/test/java/io/trino/execution/TestTableRedirection.java
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
I think it would be better for the tests to be part of commit which introduces the tested functionality rather than a separate commit.
There was a problem hiding this comment.
I'll split it in the two commits that do name and listing redirection.
There was a problem hiding this comment.
Since all the changes are closely related, I'm leaning towards going with a single commit after the review.
testing/trino-tests/src/test/java/io/trino/execution/TestTableRedirection.java
Outdated
Show resolved
Hide resolved
testing/trino-tests/src/test/java/io/trino/execution/TestTableRedirection.java
Outdated
Show resolved
Hide resolved
testing/trino-tests/src/test/java/io/trino/execution/TestTableRedirection.java
Outdated
Show resolved
Hide resolved
testing/trino-tests/src/test/java/io/trino/execution/TestTableRedirection.java
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
maybe move this to AbstractTestQueryFramework
There was a problem hiding this comment.
have kept this in the test class for now as a quick utility, as ATQF has more generic methods around query assertions. I can generalize this method and do a followup if you feel strongly.
testing/trino-tests/src/test/java/io/trino/execution/TestTableRedirection.java
Outdated
Show resolved
Hide resolved
testing/trino-tests/src/test/java/io/trino/execution/TestTableRedirection.java
Outdated
Show resolved
Hide resolved
testing/trino-tests/src/test/java/io/trino/execution/TestTableRedirection.java
Outdated
Show resolved
Hide resolved
|
At Netflix, we rely heavily on redirecting iceberg tables referenced in hive connector session to iceberg connector and vice versa (currently using Netflix specific patch) since the users are largely unaware of the table format used for the table. This patch aligns well with how we redirect. A few high level questions/comments .. mostly around scope.
|
|
core/trino-main/src/main/java/io/trino/execution/CreateTableTask.java
Outdated
Show resolved
Hide resolved
core/trino-main/src/main/java/io/trino/metadata/MetadataManager.java
Outdated
Show resolved
Hide resolved
core/trino-main/src/main/java/io/trino/metadata/MetadataManager.java
Outdated
Show resolved
Hide resolved
core/trino-main/src/main/java/io/trino/metadata/MetadataManager.java
Outdated
Show resolved
Hide resolved
core/trino-main/src/main/java/io/trino/metadata/MetadataManager.java
Outdated
Show resolved
Hide resolved
core/trino-main/src/main/java/io/trino/metadata/MetadataManager.java
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
If I'm understanding the scenario, this means a connector returned a redirection, then the target did not exist. Why would we want to fallback?
In the case of the Hive connector redirecting to Iceberg, the subsequent read operation would fail (maybe in a confusing way). Though this might be a bad example, since they share the same metastore and thus this should not occur.
.../trino-main/src/main/java/io/trino/sql/planner/iterative/rule/ApplyTableScanRedirection.java
Outdated
Show resolved
Hide resolved
core/trino-spi/src/main/java/io/trino/spi/StandardErrorCode.java
Outdated
Show resolved
Hide resolved
...trino-main/src/main/java/io/trino/connector/informationschema/InformationSchemaMetadata.java
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
This method naming and semantics confused me when reading the listing code today (after I forgot how this method worked from reading it yesterday). I was thinking it would return empty when the table is not redirected, like redirectTable. But it would return a handle to the normal, non-redirected table in that case.
Maybe call this getTableHandleWithRedirection to better explain the behavior of "return a table with redirection semantics".
We should also consider renaming getTableHandle to getRawTableHandle or getTableHandleWithoutRedirection, so you have to make a choice of which to use.
There was a problem hiding this comment.
I see, yeah the semantics of connector-level and engine-level methods are confusing and different. Will change the method name. Changing getTableHandle name seems like a good idea, will do in followup.
...trino-main/src/main/java/io/trino/connector/informationschema/InformationSchemaMetadata.java
Outdated
Show resolved
Hide resolved
core/trino-spi/src/main/java/io/trino/spi/connector/TableColumnsMetadata.java
Outdated
Show resolved
Hide resolved
core/trino-spi/src/main/java/io/trino/spi/connector/TableColumnsMetadata.java
Outdated
Show resolved
Hide resolved
core/trino-spi/src/main/java/io/trino/spi/connector/ConnectorMetadata.java
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
It's not immediately clear that this is for a single catalog (the original code had this problem as well). My first thought is that it wasn't safe since table names could overlap. How about
List<TableColumnsMetadata> catalogColumns = getOnlyElement(metadata.listTableColumns(session, prefix).values(), List.of());
Map<SchemaTableName, Optional<List<ColumnMetadata>>> tableColumns = catalogColumns.stream()
.collect(toImmutableMap(TableColumnsMetadata::getTable, TableColumnsMetadata::getColumns));
core/trino-main/src/main/java/io/trino/metadata/MetadataListing.java
Outdated
Show resolved
Hide resolved
core/trino-main/src/main/java/io/trino/metadata/MetadataManager.java
Outdated
Show resolved
Hide resolved
2dbaf8e to
a27054c
Compare
|
@electrum @raunaqmorarka thanks for your thorough reviews. AC. |
core/trino-main/src/main/java/io/trino/metadata/MetadataListing.java
Outdated
Show resolved
Hide resolved
core/trino-main/src/main/java/io/trino/sql/analyzer/StatementAnalyzer.java
Outdated
Show resolved
Hide resolved
.../trino-main/src/main/java/io/trino/sql/planner/iterative/rule/ApplyTableScanRedirection.java
Outdated
Show resolved
Hide resolved
core/trino-main/src/main/java/io/trino/execution/CreateTableTask.java
Outdated
Show resolved
Hide resolved
|
Looks like we're ready to merge? |
The engine redirects table scans and column listing to the target table if the connector indicates this using ConnectorMetadata#redirectTable implementation.
|
Merged #7606 into master. |
Supersedes #7016 (SPI changes only).
ConnectorMetadata#getRedirectedTableandConnectorMetadata#getRedirectedNameto redirect tables. Behavior for views and materialized views is unaltered.ConnectorMetadata#streamColumnsmethod to introduce an alternate SPI method that is aware of redirection without breaking SPI.getTableHandlecalls here.Listing method contracts (Courtesy @electrum and @dain):
listTablesreturns the list from the source connector. We special case filtering down to a single table -- just check if the name exists in the source connector -- it is returned even if the redirected table does not exist.listTableColumnsreturns columns from the target of the redirect, since source connector does not know. If the redirect target does not exist, the table is skipped in the column list -- thus listing never fails. This is consistent with existing behavior for listing columns.filterTablesis only called for the source catalog. The target catalog is not involved at all with listing names. Because the list of columns comes from the target catalog, the access controlfilterColumnsneeds to be called for the target catalog for redirections. We do this by modifyingMetadataListing.listTableColumnsto do the redirection logic for listing columns, rather than doing it inMetadataManager.listTablePrivilegesremains unaware of redirection for now.