feat(athena): resolve cross-account Glue catalogs in Spark Python models - #7
Open
dtaniwaki wants to merge 3 commits into
Open
feat(athena): resolve cross-account Glue catalogs in Spark Python models#7dtaniwaki wants to merge 3 commits into
dtaniwaki wants to merge 3 commits into
Conversation
Spark Python models using `dbt.source()` / `dbt.ref()` currently fail to
read from cross-account GLUE catalogs registered as Athena Data Catalogs.
`get_spark_df` strips the catalog component from the identifier, so the
Spark Glue Catalog Client always targets the local account's Glue.
Add `get_spark_cross_account_catalog_map()` on the adapter, which calls
`athena:ListDataCatalogs` + `athena:GetDataCatalog` and returns a
`{catalog_name: account_id}` mapping for all non-default GLUE catalogs.
The result is cached per adapter instance with `@lru_cache` to avoid
repeated API calls.
The mapping is injected into `athena__py_get_spark_dbt_object` as
`_CROSS_ACCOUNT_CATALOGS` at compile time. `get_spark_df` now:
- Returns `spark.table("schema.table")` for the local `awsdatacatalog`
(existing behavior).
- Returns `spark.table("<account_id>/schema.table")` for known
cross-account GLUE catalogs, which Spark can resolve when
`spark_cross_account_catalog` is enabled (sets
`spark.hadoop.aws.glue.catalog.separator` to `/`).
- Falls back to the legacy identifier for unknown catalogs to preserve
the current behavior for any edge case.
Users are no longer required to hard-code account IDs or bypass
`dbt.source()` to reference cross-account tables from Spark Python
models, so dbt lineage (selectors, docs, source freshness) remains
intact.
…talog config The macro `athena__py_get_spark_dbt_object` unconditionally populated `_CROSS_ACCOUNT_CATALOGS` from `adapter.get_spark_cross_account_catalog_map()`, so Python models that referenced a registered cross-account GLUE catalog emitted `<account_id>/<schema>.<table>` identifiers to `spark.table()`. Those identifiers are only parseable by Spark when `spark.hadoop.aws.glue.catalog.separator` is set to `/`, which `AthenaSparkSessionConfig` only adds when the model opts in with `spark_cross_account_catalog: true`. Models that had not opted in therefore failed at runtime with a Spark `ParseException`: pyspark.sql.utils.ParseException: mismatched input '<account_id>' ... == SQL == <account_id>/<schema>.<table> Gate the map population in the macro on the same model config. When the model has not opted in, `_CROSS_ACCOUNT_CATALOGS` stays empty and `get_spark_df` falls back to the legacy two-part `schema.table` form, matching the pre-feature behavior. Opt-in models continue to work exactly as before the fix. The adapter method itself is unchanged — its unit tests still pass.
spark.table() internally parses the name as a SQL identifier, and '/' is not a valid character in an unquoted identifier. Wrapping the account_id/schema portion in backticks lets the SQL parser accept it, after which the Glue Catalog Client resolves the cross-account reference using the configured separator.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Background
Spark Python models that use
dbt.source()/dbt.ref()cannot currently read from cross-account GLUE catalogs registered as Athena Data Catalogs.athena__py_get_spark_dbt_object'sget_spark_dfhelper strips the catalog component from the identifier before callingspark.table(), so the Spark Glue Catalog Client always targets the local account's Glue. Attemptingdbt.source("cross_account", "tbl")from a Python model fails with:Today the only workarounds are (a) hard-coding the account ID via
spark.table("<account_id>/schema.table")— which breaks dbt lineage (selectors, docs, source freshness) — or (b) copying all cross-account data to the local account, which is extremely heavyweight.Change
Introduce an adapter method that discovers cross-account GLUE catalogs at compile time and injects the resolved
{catalog_name: account_id}mapping into the generated Python code, soget_spark_dfcan translate identifiers transparently.Adapter
AthenaAdapter.get_spark_cross_account_catalog_map()@available+@lru_cache()(same pattern as_get_work_group)athena:ListDataCatalogsathena:GetDataCatalogand extractsParameters.catalog-idvia the existingget_catalog_id()helperDict[str, str]— the localawsdatacatalogis excluded (not needed) and non-GLUE catalogs are skipped (Spark's Glue Catalog Client can't read them)Macro
athena__py_get_spark_dbt_objectnow:config.spark_cross_account_catalog(model config). Whenfalse(default), it skips the adapter call entirely and injects an empty_CROSS_ACCOUNT_CATALOGS = {}— preserving the pre-feature behavior and avoiding identifiers the Spark parser cannot handle._CROSS_ACCOUNT_CATALOGS = {...}via| tojson.get_spark_dfto:spark.table("schema.table")forawsdatacatalog(existing behavior)spark.table("<account_id>/schema.table")for known cross-account GLUE catalogs — Spark can resolve this whenspark.hadoop.aws.glue.catalog.separatoris set to/, whichAthenaSparkSessionConfigdoes automatically when the samespark_cross_account_catalog: truemodel config is presentTests
test_get_spark_cross_account_catalog_map_only_default— onlyawsdatacatalogregistered → empty dict (regression guard)test_get_spark_cross_account_catalog_map_with_cross_account_glue— one cross-account GLUE catalog is included with itscatalog-idtest_get_spark_cross_account_catalog_map_excludes_non_glue— LAMBDA / federated catalogs are filtered out (Spark can't read them)All 101 tests in
tests/unit/test_adapter.py+tests/unit/test_python_submissions.pypass.Required permissions
athena:ListDataCatalogsis new but is typically already granted to any role that can run Athena queries.athena:GetDataCatalogis already used by_get_data_catalog. Both are only called when the model opts in withspark_cross_account_catalog: true, so models that do not use the feature do not require the new permissions.Runtime requirement
To activate cross-account resolution for a Python model, set
spark_cross_account_catalog: truein the model config (e.g. viadbt.config(...),schema.yml, or a project-level+spark_cross_account_catalog: true). This single flag:spark.hadoop.aws.glue.catalog.separator: /to the Spark session properties viaAthenaSparkSessionConfig, so Spark can parse<account_id>/<schema>.<table>._CROSS_ACCOUNT_CATALOGSis populated only when the flag istrue.Models that do not set the flag get an empty mapping and
get_spark_dffalls back to the legacy two-part<schema>.<table>form. This is important: without the gating, a model that had a cross-account catalog registered in Athena but had not enabled the separator would emit<account_id>/<schema>.<table>tospark.table()and fail with:Backwards compatibility
awsdatacatalogregistered get an empty map → identical to the pre-existing behavior (no regression).<account_id>/schema.tableviasession.table()continue to work unchanged.spark_cross_account_catalogon their models keep the pre-feature behavior (the adapter is not called and the map is empty).spark_cross_account_catalogconfig.