fix(ingest/powerbi): follow a native query's source argument to the connector - #19368
fix(ingest/powerbi): follow a native query's source argument to the connector#19368puneetagarwal-datahub wants to merge 1 commit into
Conversation
…onnector
Value.NativeQuery names its data source as the first argument, and that
argument is usually a navigation step rather than the connector call:
Source = Snowflake.Databases(host, warehouse),
DatabaseStep = Source{[Name="MYDB", Kind="Database"]}[Data],
QueryStep = Value.NativeQuery(DatabaseStep, "select ...")
_get_data_source_tokens resolved that identifier once, landed on
Source{...}[Data], read its head as the literal name "Source", and gave up
because no connector is called that. Only the inline form and a single hop
worked, which is why the existing tests -- all inline -- never caught it.
Follow the head back until it reaches something the let does not bind, and
append each hop's navigation record after the connector's own arguments, so
the platform stays at index 0 and the server at index 1 however deep the
chain runs. Aliases of aliases are followed too.
Not Databricks-specific: the same shape was losing lineage on Snowflake,
Redshift, MSSQL, Postgres and BigQuery.
Two properties are pinned by tests. Steps defined in terms of each other
would otherwise spin in the alias loop rather than raise, hanging ingestion
instead of failing it. And when the connector's positional arguments are
unresolved parameters, the navigation record shifts into the server
position and the existing guard still declines to guess a host.
|
Linear: ING-3350 Thanks for your contribution! We have created an internal ticket to track this PR. A member of the core DataHub team will be assigned to review it within the next few business days - you will get a follow-up comment once a reviewer is assigned. |
PR SummaryOverview It now walks let-bound aliases until it hits a name that is not a let variable (the connector), then appends each hop’s navigation record after the connector arguments so platform stays at index 0 and server at index 1. Cycles are cut with a Adds integration tests for multi-hop Snowflake and Databricks MultiCloud native queries, cyclic step aliases, and unresolved parameters. Reviewed by Cursor Bugbot for commit e6414fd. Bugbot is set up for automated code reviews on this repo. Configure here. |
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit e6414fd. Configure here.
| platform_name = "" | ||
| if head.get("kind") == "IdentifierExpression": | ||
| platform_name = head.get("identifier", {}).get("literal", "") | ||
| tokens = [platform_name] |
There was a problem hiding this comment.
Databricks DB lookup hits Kind value
Medium Severity
Multi-hop token assembly appends later navigation pairs after the database hop’s Kind/Database pair. For DatabricksMultiCloud.Catalogs with Database=null (so no real Database key is emitted), get_db_name’s get_next_item(..., "Database") matches that Kind value and returns the following token (Name) as the database, producing wrong upstream URNs on three-hop (or longer) native queries.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit e6414fd. Configure here.
Connector Tests ResultsAll connector tests passed for commit To skip connector tests, add the Autogenerated by the connector-tests CI pipeline. |
There was a problem hiding this comment.
1 issue found across 2 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="metadata-ingestion/src/datahub/ingestion/source/powerbi/m_query/pattern_handler.py">
<violation number="1" location="metadata-ingestion/src/datahub/ingestion/source/powerbi/m_query/pattern_handler.py:173">
P2: When `DatabricksMultiCloud.Catalogs` omits `Database` with `null` and another navigation hop follows, this recursive token assembly places the later `Name` after the navigation's `Kind=Database` value. `get_db_name` then treats `Name` as the database and emits an incorrect upstream URN. Preserve navigation field boundaries or require a real `Database` key/value pair before appending later hops.</violation>
</file>
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
| if resolved_head is not None: | ||
| # The head is another step, so the connector is further back. Its tokens | ||
| # come first; this level's navigation steps are appended below. | ||
| tokens = _get_data_source_tokens( |
There was a problem hiding this comment.
P2: When DatabricksMultiCloud.Catalogs omits Database with null and another navigation hop follows, this recursive token assembly places the later Name after the navigation's Kind=Database value. get_db_name then treats Name as the database and emits an incorrect upstream URN. Preserve navigation field boundaries or require a real Database key/value pair before appending later hops.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At metadata-ingestion/src/datahub/ingestion/source/powerbi/m_query/pattern_handler.py, line 173:
<comment>When `DatabricksMultiCloud.Catalogs` omits `Database` with `null` and another navigation hop follows, this recursive token assembly places the later `Name` after the navigation's `Kind=Database` value. `get_db_name` then treats `Name` as the database and emits an incorrect upstream URN. Preserve navigation field boundaries or require a real `Database` key/value pair before appending later hops.</comment>
<file context>
@@ -113,36 +113,73 @@ def _get_record_args(node_map: Dict[int, dict], invoke_node: dict) -> Dict[str,
+ if resolved_head is not None:
+ # The head is another step, so the connector is further back. Its tokens
+ # come first; this level's navigation steps are appended below.
+ tokens = _get_data_source_tokens(
+ node_map, resolved_head, parameters=parameters, seen=seen
+ )
</file context>
|
Moved to draft while a scope question is settled — not because of anything found in review. This fixes a real traversal bug, but I have not confirmed the reported customer M actually hits it. Their example table for that symptom fails silently, so it appears nowhere in their ingestion log and I have no sample of its M. Measured which branch state fixes which of their real shapes:
Two things follow. This PR fixes nothing on its own for that customer, because Waiting on the M text for that table. If it is multi-hop this is required and I will mark it ready; if it is inline it stands on its own merits as a latent bug — the same shape loses lineage on Snowflake, Redshift, MSSQL, Postgres and BigQuery — and can be scheduled independently. Code is complete and verified either way: 360 passed, lint and mypy clean, inline token output byte-identical to before, and the #18724 unresolved-parameter guard confirmed still firing. |


Summary
Value.NativeQuerynames its data source as the first argument, and in real M that argument is usually a navigation step rather than the connector call itself:_get_data_source_tokensresolved that identifier once, landed onSource{...}[Data], read its head as the literal nameSource, and gave up — no connector is called that. Measured on master:tokens[0]Snowflake.Databases(...){...}[Data]Snowflake.Databases✅NativeQuery(Source, ...)Snowflake.Databases✅NativeQuery(DatabaseStep, ...)Source❌Not Databricks-specific. The same shape was losing lineage on Snowflake, Redshift, MSSQL, Postgres and BigQuery. It survived because every existing native-query test uses the inline form, where the head is the connector.
Approach
Follow the head back until it reaches something the
letdoes not bind — that is what distinguishes a connector call from a step name — and append each hop's navigation record after the connector's own arguments. The platform stays at index 0 and the server at index 1 however deep the chain runs, soget_db_nameandcreate_lineageare unaffected. Aliases of aliases are followed too.Safety
The inline path is byte-for-byte unchanged; I verified the emitted token list is identical before and after.
Two properties are pinned by tests:
a = b, b = awould otherwise spin in the alias loop rather than raise — hanging an ingestion run instead of failing it. Guarded by a seen-set; returns no lineage.Also verified: three-hop chains accumulate both navigation records; multi-table native SQL (join / union / CTE) still yields one upstream per table.
Relationship to other PRs
This is the last piece of the M-Query native-query work. #19364 registers the
Databricks.Catalogsconnector for native queries; both are needed for a two-hop Databricks native query, since this PR fixes the traversal and that one fixes the registration. They touch different regions ofpattern_handler.pyand can merge in either order.Testing
360 passed, 1 xfailed· ruff, format and mypy clean · no existing goldens touched.New tests use Snowflake and
DatabricksMultiCloud.Catalogs, since plainDatabricks.Catalogsis not a registered native-query platform until #19364 lands.Checklist
Summary by cubic
Fixes lineage extraction for Power BI M
Value.NativeQuerywhen the source argument is a multi-hop step. Previously, resolving only one hop treated the head as a step name (e.g., "Source") and dropped lineage for Snowflake, Redshift, MSSQL, Postgres, and BigQuery; now the parser walks back through aliases/navigation to the connector and preserves argument positions.DatabricksMultiCloud.Catalogsmulti-hop, cycle handling, and unresolved parameters; no existing goldens changed.Databricks.Catalogs) are both required; they touch different parts ofpattern_handler.pyand can merge in any order.Written for commit e6414fd. Summary will update on new commits.