Fixes 24764: add SQL-based lineage for SAP HANA, including Cloud - #33144
Fixes 24764: add SQL-based lineage for SAP HANA, including Cloud#33144mohittilala wants to merge 10 commits into
Conversation
The SAP HANA lineage source only read _SYS_REPO.ACTIVE_OBJECT, which is the classic repository and does not exist on HANA Cloud. Cloud runs therefore completed with zero lineage edges once the crash guard landed in #31357. Read HANA's own dependency catalog instead, which is present on every deployment. Split _iter into one pass per source so the repository pass and the dependency pass sit alongside their own helpers.
…NCIES" This reverts commit f05c3b3.
SaphanaLineageSource read only _SYS_REPO.ACTIVE_OBJECT and inherited the bare Source step. _SYS_REPO does not exist on SAP HANA Cloud, so Cloud produced no lineage at all, and no deployment ever got view, query or stored-procedure lineage. Inherit LineageSource so view definitions give column-level lineage and the plan cache gives table-to-table edges. The CDATA pass stays for repository models, which are XML rather than SQL, so the two passes never describe the same object.
yield_procedure_lineage is a no-op without StoredProcedureLineageMixin, which this source does not mix in, so the log line and docstring were describing lineage that never gets produced.
There was a problem hiding this comment.
🟡 Changes recommended
Unresolved lineage pass isolation, lifecycle, filtering, and test coverage findings remain.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Pull request overview
Adds SQL-based view and query lineage for SAP HANA Cloud while retaining legacy _SYS_REPO CDATA lineage for on-premises deployments.
Changes:
- Integrates shared SQL lineage processing with the existing repository pass.
- Adds SAP HANA plan-cache lineage queries and DML filtering.
- Expands unit tests and updates the Ruff baseline.
File summaries
| File | Summary and review findings |
|---|---|
ingestion/tests/unit/topology/database/test_sap_hana.py |
Adds SAP HANA lineage tests. Moderate (3): fix the iter([]) short-circuit. Nit (1): add end-to-end SQL-path assertions, including missing-schema handling. |
ingestion/src/metadata/ingestion/source/database/saphana/query_parser.py |
Adds the SAP HANA query-parser base class. |
ingestion/src/metadata/ingestion/source/database/saphana/queries.py |
Adds the plan-cache lineage query. |
ingestion/src/metadata/ingestion/source/database/saphana/lineage.py |
Combines SQL and CDATA lineage. Critical (2): isolate SQL-history failures from the repository pass. Moderate (3): delegate to super().close(). Moderate (2): gate CDATA lineage with processViewLineage. Moderate (1): support leading whitespace/comments in DML matching. Nits (2, 1): correct edge-count reporting and clarify stored-procedure support. |
ingestion/.ruff-g004-baseline.json |
Removes resolved Ruff findings. |
Review details
Suppressed comments (3)
ingestion/src/metadata/ingestion/source/database/saphana/lineage.py:76
- [P1] Do not anchor DML matching at the first character of
STATEMENT_STRING. TheseLIKEpatterns only match text that starts exactly withINSERT/UPSERT/etc., so valid plan-cache entries with leading whitespace or comments are excluded and produce no lineage. Prefix the patterns with a wildcard (as the other lineage connectors do) or normalize the statement before filtering.
UPPER(STATEMENT_STRING) LIKE 'INSERT INTO%%SELECT%%'
OR UPPER(STATEMENT_STRING) LIKE 'UPSERT%%SELECT%%'
OR UPPER(STATEMENT_STRING) LIKE 'REPLACE%%SELECT%%'
OR UPPER(STATEMENT_STRING) LIKE 'MERGE INTO%%'
OR UPPER(STATEMENT_STRING) LIKE 'UPDATE%%SET%%'
ingestion/src/metadata/ingestion/source/database/saphana/lineage.py:132
- This message says stored-procedure lineage is “unaffected,” but this source deliberately does not implement
StoredProcedureLineageMixinandLineageSource.yield_procedure_lineageis a no-op. On Cloud, that wording can imply stored-procedure lineage is supported; please remove that claim or state explicitly that it remains unsupported.
"SAP HANA Cloud, where the classic repository was never carried over. View, query and "
"stored-procedure lineage are unaffected. Cause: %s",
ingestion/tests/unit/topology/database/test_sap_hana.py:1527
- These additions validate inheritance and text fragments, but never run the connector’s new SQL path and assert an emitted edge:
test_iter_runs_both_passesmocks both passes, and the other tests only inspect class attributes. A wrong HANA alias, plan-cache row mapping, or_SYS_REPOfallback would still pass. Add a test that feeds representative view/query rows through the source and assertsAddLineageRequestoutput, including the 362 missing-schema case.
def test_iter_runs_both_passes() -> None:
"""_iter must run the shared SQL passes and the repository pass.
Dropping either one silently halves lineage on the deployment that depends on it.
"""
- Files reviewed: 5/5 changed files
- Comments generated: 5
- Review effort level: Lite
💡 Configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
_iter passes through whatever the shared framework yields, which includes CreateQueryRequest from query lineage, so declaring only AddLineageRequest was wrong and the edge counter was reporting query records as edges. Count the two apart and split the zero-edge warning, since queries read but unresolved points at metadata coverage while nothing read at all points at a missing CATALOG READ grant.
✅ Playwright Results — workflow succeededValidated commit ✅ 108 passed · ❌ 0 failed · 🟡 1 flaky · ⏭️ 0 skipped · 🧰 0 lifecycle flaky PerformanceBlocking targets: ✅ met · Optimization targets: 🟡 in progress Shard-job maxima below are not the full workflow wall time; the linked run includes build, fixture, planning, and reporting. 🕒 Full workflow signal wall (to summary) 44m 48s ⏱️ Max setup 4m 55s · max shard execution 14m 19s · max shard-job elapsed before upload 19m 57s · reporting 7s 🌐 235.44 requests/attempt · 1.80 app boots/UI scenario · 0.00% common-shard skew Optimization targets still in progress:
🟡 1 flaky test(s) (passed on retry)
How to debug locally# Download playwright-test-results-<shard> artifact and unzip
npx playwright show-trace path/to/trace.zip # view trace |
…ndings A failure in the shared SQL pass propagated out of _iter, so an unreadable SYS.M_SQL_PLAN_CACHE stopped the repository pass and cost on-premise instances the calculation-view lineage they already had. Delegate close to the base so masked_query_cache is cleared, gate the repository pass on processViewLineage since calculation views are views, and drop the %% escaping that hdbcli never unescapes.
… findings Cached statements keep the whitespace they were submitted with, so a filter anchored at character one dropped real DML. Match on the trimmed statement and keep the anchor, since a leading wildcard also matches a SELECT that merely quotes the keyword. Drop the last claim that stored-procedure lineage is unaffected, and add tests that resolve a real plan-cache statement rather than only inspecting class attributes.
database_field and schema_field are declared on QueryParserSource but never read by it, and no sibling query parser sets them. The module logger was unused too. The filter and _iter comments had grown to restate each other, so they are cut back to the parts that are not obvious from the code.
There was a problem hiding this comment.
🟡 Changes recommended
Unresolved critical and moderate review findings remain in the SQL lineage path.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Review details
Suppressed comments (6)
ingestion/src/metadata/ingestion/source/database/saphana/lineage.py:71
- The new Cloud lineage path is not exercised here:
test_iter_runs_both_passesreplacesLineageSource._iter, and the parser test bypassesyield_table_query/query_lineage_processorentirely. A regression in the HANA row aliases,NULL AS database_name/SCHEMA_NAMEmapping, or the actual SQL-row-to-lineage emission would therefore pass these tests; please add a focused test that feeds a representative plan-cache row throughyield_table_queryand asserts the emitted lineage/query request.
sql_stmt = SAPHANA_QUERY_HISTORY_STATEMENT
ingestion/src/metadata/ingestion/source/database/saphana/lineage.py:153
sql_queriesis incremented only forCreateQueryRequest, which the shared processor emits after it has already found a lineage result. A plan-cache row whose SQL parses but has no resolvable endpoint therefore takes this branch and the log incorrectly says HANA “read no queries,” even though rows were read; that sends operators toward the wrong diagnosis. Either count rawTableQueryrows or change this message to describe the absence of emitted query-lineage records rather than the absence of reads.
logger.warning(
"SAP HANA lineage finished with no edges and read no queries. Check that the metadata "
"workflow has already ingested the tables and views, that processViewLineage or "
"processQueryLineage is enabled, and that the ingestion user holds CATALOG READ, without "
"which SYS.M_SQL_PLAN_CACHE only returns the ingestion user's own statements."
ingestion/src/metadata/ingestion/source/database/saphana/lineage.py:85
- The predicate is anchored after whitespace trimming, but it still rejects valid plan-cache statements that begin with a SQL comment, such as
/* job */ INSERT INTO ... SELECT .... The shared parser explicitly accepts comment-prefixed DML (for example,test_query_parser.py:189-195), and this query already handles comment-prefixed applications below, so those HANA lineage statements are silently omitted. Normalize leading comments before applying the anchored DML test, and add a regression case for both block and line comments.
# Matched against the trimmed statement, because cached statements keep the
# whitespace they were submitted with. Still anchored, since a leading wildcard
# also matches a SELECT that merely quotes the keyword.
#
# CREATE TABLE ... AS SELECT is missing by necessity: the plan cache holds no DDL.
filters = f"""
AND (
{_STATEMENT} LIKE 'INSERT INTO%SELECT%'
OR {_STATEMENT} LIKE 'UPSERT%SELECT%'
OR {_STATEMENT} LIKE 'REPLACE%SELECT%'
OR {_STATEMENT} LIKE 'MERGE INTO%'
OR {_STATEMENT} LIKE 'UPDATE%SET%'
)
ingestion/src/metadata/ingestion/source/database/saphana/lineage.py:115
- This catch preserves the repository pass, but it consumes every SQL lineage exception without recording a
StackTraceErroror yielding an error. SinceSource._iterthen sees normal generator completion, a HANA Cloud run with a denied or failedSYS.M_SQL_PLAN_CACHEquery can report success while producing no SQL lineage. Record the failure inself.statusbefore continuing (or re-raise after the fallback) so the workflow exposes the SQL-pass failure.
except Exception as exc:
# yield_table_query does not guard its own execute, so an unreadable plan
# cache raises here and would otherwise take the repository pass with it.
logger.warning(
"SAP HANA SQL lineage pass failed and produced %d edges before stopping. The repository "
ingestion/src/metadata/ingestion/source/database/saphana/lineage.py:109
- The shared SQL lineage path does not emit direct
AddLineageRequestobjects for these cases: query parsing returnsOMetaFQNLineageRequest, and view lineage is wrapped inOMetaLineageRequest. As a resultsql_edgesremains zero on the normal Cloud path even when edges are yielded, so the final warning falsely says no edges were produced (and, whenCreateQueryRequestis counted, falsely blames unresolved endpoints). Count the framework's lineage wrapper types or the underlying lineage request instead.
if isinstance(either.right, AddLineageRequest):
sql_edges += 1
elif isinstance(either.right, CreateQueryRequest):
sql_queries += 1
ingestion/tests/unit/topology/database/test_sap_hana.py:1564
- The new SQL-path tests validate the query text and standalone parser, but
test_iter_runs_both_passesmocksLineageSource._iter, so it never executes HANA'syield_table_query, the plan-cache row-to-TableQuerymapping, or the processor that emitsAddLineageRequest/CreateQueryRequest. A regression in the actual connector wiring could therefore pass all these tests. Add a focused test with a representative plan-cache row that runs the real SQL path and asserts the emitted lineage request (including the HANA schema/database context).
def test_iter_runs_both_passes() -> None:
"""_iter must run the shared SQL passes and the repository pass.
Dropping either one silently halves lineage on the deployment that depends on it.
"""
calls = []
def record_sql(*_, **__):
calls.append("sql")
return iter([])
def record_cdata():
calls.append("cdata")
return iter([])
with (
patch.object(LineageSource, "_iter", side_effect=record_sql),
patch.object(SaphanaLineageSource, "yield_cdata_lineage", side_effect=record_cdata),
patch.object(SaphanaLineageSource, "test_connection"),
patch("metadata.ingestion.source.database.query_parser_source.get_ssl_connection"),
):
source = SaphanaLineageSource(
config=WorkflowSource(
type="saphana-lineage",
serviceName="test_sap_hana",
serviceConnection=DatabaseConnection(
config=SapHanaConnection(
connection=SapHanaSQLConnection(username="test", password="test", hostPort="localhost:39015")
)
),
sourceConfig=SourceConfig(config=DatabaseServiceQueryLineagePipeline()),
),
metadata=create_autospec(OpenMetadata),
)
list(source._iter())
assert calls == ["sql", "cdata"]
- Files reviewed: 5/5 changed files
- Comments generated: 1
- Review effort level: Lite
The guard sat around the whole shared _iter, so a view-pass failure was logged as a plan-cache failure and swallowed. On Cloud the view pass is the entire result, so that turned a real error into a silent empty run. Override yield_query_lineage instead, which is the pass that actually reads the plan cache, and let everything else propagate.
…rk emits The shared passes wrap lineage in OMetaLineageRequest and OMetaFQNLineageRequest rather than yielding AddLineageRequest, so the edge counter stayed at zero and the no-edges warning fired on every successful run. Strip leading comments before the DML match, since tools routinely prefix statements, and surface a query-history failure on the workflow status instead of only logging it.
There was a problem hiding this comment.
🔵 Needs a closer look
Unresolved issues affect edge detection, exception handling, comment-prefixed DML, query-duration units, and representative plan-cache test coverage.
Review details
Suppressed comments (5)
ingestion/src/metadata/ingestion/source/database/saphana/lineage.py:108
- The shared lineage framework does not emit
AddLineageRequestfor these SQL passes: view edges are wrapped asOMetaLineageRequestand query edges asOMetaFQNLineageRequest. Consequentlysql_edgesremains zero even when SQL lineage was produced, so_iterlogs the misleading “no edges” warning (and can take the no-edge path) on successful Cloud runs. Count any non-CreateQueryRequestsuccessful result instead of checking onlyAddLineageRequest.
records are counted apart, since the shared passes emit both.
"""
sql_edges = 0
sql_queries = 0
ingestion/src/metadata/ingestion/source/database/saphana/lineage.py:153
- This catches every exception raised by the entire shared query-lineage generator, not only failures reading
SYS.M_SQL_PLAN_CACHE. Parser, worker, or programming errors are therefore downgraded to a warning and the workflow can finish successfully with missing Cloud lineage, making an actual regression indistinguishable from a permissions issue. Catch only the expected plan-cache DBAPI/permission failures (or report and re-raise unexpected exceptions).
def yield_query_lineage(self) -> Iterable[Either[AddLineageRequest | CreateQueryRequest]]:
"""Query-history lineage, guarded so a restricted plan cache is not fatal.
ingestion/src/metadata/ingestion/source/database/saphana/lineage.py:84
- The predicate trims whitespace but not leading SQL comments. A valid plan-cache entry such as
/* job */ INSERT INTO ... SELECT ...or a comment-prefixedMERGE INTO ...therefore starts with/*and matches none of these anchored patterns, so its lineage is discarded beforeLineageParsercan process it. Normalize leading comments before applying the DML filter (or filter after parsing) and add a regression case.
# Anchored rather than wildcarded, so a SELECT that merely quotes the keyword does
# not match. CREATE TABLE ... AS SELECT is missing by necessity: no DDL is cached.
filters = f"""
AND (
ingestion/src/metadata/ingestion/source/database/saphana/queries.py:53
TOTAL_EXECUTION_TIMEfrom HANA's plan cache is a microsecond counter, whileTableQuery.durationis consumed in milliseconds (the other query-history adapters convert with/ 1000). Dividing by1000000makes every emitted query duration 1000x too small; convert it with/ 1000here.
TOTAL_EXECUTION_TIME / 1000000 AS duration,
ingestion/tests/unit/topology/database/test_sap_hana.py:1534
- These tests cover orchestration by replacing both lineage passes with empty iterators, while the SQL test only checks rendered string fragments. Nothing exercises a
TableQueryproduced from a representative plan-cache row, so the Cloud path could have broken row mapping or source-level wiring while all new tests still pass; add a source-level regression test and, where possible, a real HANA integration fixture.
def test_iter_runs_both_passes() -> None:
"""_iter must run the shared SQL passes and the repository pass.
Dropping either one silently halves lineage on the deployment that depends on it.
"""
- Files reviewed: 5/5 changed files
- Comments generated: 0 new
- Review effort level: Lite
| _STATEMENT = ( | ||
| r"LTRIM(UPPER(REPLACE_REGEXPR('^(\s*(/\*.*?\*/|--[^\n]*\n))+' IN STATEMENT_STRING WITH '' OCCURRENCE ALL))" | ||
| r", ' ' || CHAR(9) || CHAR(13) || CHAR(10))" | ||
| ) |
There was a problem hiding this comment.
💡 Edge Case: Comment-stripping regex misses multi-line block comments
The /\*.*?\*/ branch relies on . matching the comment body, but SAP HANA's REGEXPR engine (ICU) does not let . match line terminators unless the DOTALL flag is set. A DML statement prefixed with a multi-line /* ... */ header (common in tool- or ETL-generated SQL) therefore won't be fully stripped, so the anchored LIKE 'INSERT INTO%SELECT%' filters can silently drop that statement and its lineage. If multi-line comment headers matter here, add the DOTALL flag to REPLACE_REGEXPR (or change the block-comment branch to /\*[^]*?\*/-equivalent) so the body can span newlines.
Was this helpful? React with 👍 / 👎
Code Review 👍 Approved with suggestions 1 resolved / 2 findingsAdds SQL-based lineage for SAP HANA Cloud by extending 💡 Edge Case: Comment-stripping regex misses multi-line block comments📄 ingestion/src/metadata/ingestion/source/database/saphana/lineage.py:55-58 The ✅ 1 resolved✅ Quality: Filters use %% escaping though HANA driver keeps it literal
🤖 Prompt for agentsOptionsDisplay: compact → Counting what did not apply, without listing it. Comment with these commands to change the behavior for this request:
Was this helpful? React with 👍 / 👎 | Powered by Gitar — free for open source |
There was a problem hiding this comment.
🔵 Needs a closer look
Moderate findings remain for DML filtering, query-duration conversion, and real connector-path test coverage.
Review details
Suppressed comments (5)
ingestion/src/metadata/ingestion/source/database/saphana/lineage.py:89
- The comment says the plan cache preserves submitted whitespace, but these predicates require the exact single-space tokens
INSERT INTOandMERGE INTO. Valid multiline or multiply-spaced DML such asINSERT\nINTO ... SELECT ...will be silently excluded and produce no lineage; normalize internal whitespace or use token-boundary-safe matching before applying theseLIKEclauses.
{_STATEMENT} LIKE 'INSERT INTO%SELECT%'
OR {_STATEMENT} LIKE 'UPSERT%SELECT%'
OR {_STATEMENT} LIKE 'REPLACE%SELECT%'
OR {_STATEMENT} LIKE 'MERGE INTO%'
OR {_STATEMENT} LIKE 'UPDATE%SET%'
ingestion/src/metadata/ingestion/source/database/saphana/lineage.py:79
- The new Cloud lineage path is not exercised end-to-end by the added tests: the parser test calls
LineageParserdirectly, the orchestration test patchesLineageSource._iter, and the SQL test only checks rendered text. A regression inSaphanaLineageSource's query execution/row mapping or in this HANA statement would therefore remain green. Add a source-level test with representative plan-cache rows that asserts the emitted lineage/query requests, or a HANA integration fixture.
sql_stmt = SAPHANA_QUERY_HISTORY_STATEMENT
ingestion/src/metadata/ingestion/source/database/saphana/lineage.py:58
- Because the block-comment branch uses
.*?without HANA's DOTALL flag, a leading/* ... */that spans lines is not removed. The normalized value still starts with/*, so the anchored DML predicates drop valid plan-cache statements and their lineage; addFLAGS 's'(or an equivalent newline-aware pattern) and a regression case for a multiline header.
_STATEMENT = (
r"LTRIM(UPPER(REPLACE_REGEXPR('^(\s*(/\*.*?\*/|--[^\n]*\n))+' IN STATEMENT_STRING WITH '' OCCURRENCE ALL))"
r", ' ' || CHAR(9) || CHAR(13) || CHAR(10))"
)
ingestion/src/metadata/ingestion/source/database/saphana/queries.py:53
M_SQL_PLAN_CACHE.TOTAL_EXECUTION_TIMEis reported in microseconds, whileTableQuery.durationis defined in milliseconds. Dividing by 1,000,000 therefore sends seconds under the millisecond field and makes every emitted query duration 1,000× too small; convert with/ 1000instead.
TOTAL_EXECUTION_TIME / 1000000 AS duration,
ingestion/tests/unit/topology/database/test_sap_hana.py:1566
- This orchestration test replaces
LineageSource._iterandyield_cdata_lineagewith callbacks, so it verifies only that two mocked generators are called in order; it never exercises the realyield_table_query/yield_view_lineagepath that implements the Cloud fix. A regression in the actual plan-cache row mapping or view-definition lineage could therefore stay green. Add a representative plan-cache/view-definition test (or a HANA-backed integration fixture) that asserts the emitted lineage requests.
def test_iter_runs_both_passes() -> None:
"""_iter must run the shared SQL passes and the repository pass.
Dropping either one silently halves lineage on the deployment that depends on it.
"""
calls = []
def record_sql(*_, **__):
calls.append("sql")
return iter([])
def record_cdata():
calls.append("cdata")
return iter([])
with (
patch.object(LineageSource, "_iter", side_effect=record_sql),
patch.object(SaphanaLineageSource, "yield_cdata_lineage", side_effect=record_cdata),
patch.object(SaphanaLineageSource, "test_connection"),
patch("metadata.ingestion.source.database.query_parser_source.get_ssl_connection"),
):
source = SaphanaLineageSource(
config=WorkflowSource(
type="saphana-lineage",
serviceName="test_sap_hana",
serviceConnection=DatabaseConnection(
config=SapHanaConnection(
connection=SapHanaSQLConnection(username="test", password="test", hostPort="localhost:39015")
)
),
sourceConfig=SourceConfig(config=DatabaseServiceQueryLineagePipeline()),
),
metadata=create_autospec(OpenMetadata),
)
list(source._iter())
assert calls == ["sql", "cdata"]
- Files reviewed: 5/5 changed files
- Comments generated: 0 new
- Review effort level: Lite
|



Describe your changes:
Fixes #24764
SaphanaLineageSourceread exactly one thing, CDATA XML from_SYS_REPO.ACTIVE_OBJECT. That schema is the classic HANA repository, deprecated since 2018 and never carried into HANA Cloud, so a Cloud lineage run produced zero edges. It also inherited the bareSourcestep rather than the sharedLineageSource, so no HANA deployment has ever had view, query or stored-procedure lineage.I verified the scope on a live HANA Cloud instance:
_SYS_REPOis absent, and all 969 views areVIEW_TYPE = ROW. There is not one calculation, analytic or attribute view, which are the only three types the CDATA parser reads. The existing strategy cannot apply to Cloud at all.This inherits
LineageSource, so view definitions produce column-level lineage and the plan cache produces table-to-table edges. The CDATA pass is unchanged and still serves repository models on on-prem instances. The two passes read different object kinds, so no edge has two origins.Stored-procedure lineage stays unsupported. It needs
StoredProcedureLineageMixin, which is deliberately not mixed in here, so that remains a follow-up.Type of change:
High-level design:
SaphanaLineageSourcenow extendsSapHanaQueryParserSourceandLineageSource, matching how roughly twenty other connectors are built._iterruns the shared SQL passes and then the repository pass, each behind its existingsourceConfigflag.I also evaluated
SYS.OBJECT_DEPENDENCIES, HANA's own dependency catalog, and rejected it. Measured against a purpose-built fixture, the SQL parser recovered the same upstream tables for 6 of 6 views and added column-level lineage the catalog cannot express, so the catalog contributed nothing unique and would have produced a competing second set of edges over the same views.One platform limit worth stating.
CREATE TABLE ... AS SELECTlineage is not recoverable on SAP HANA.OBJECT_DEPENDENCIESrecords derivation rather than data movement, and the plan cache holds execution plans, so DDL never enters it. I confirmed directly that the instance had zero cachedCREATEstatements.INSERT INTO ... SELECTand the other DML forms are covered.Tests:
Use cases covered
_SYS_REPO, produces view and column-level lineage where it previously produced none.INSERT INTO ... SELECTproduces a table-to-table edge with column lineage.Unit tests
ingestion/tests/unit/topology/database/test_sap_hana.py, 4 added and 1 updated for the new base class. 42 pass.Backend integration tests
Ingestion integration tests
Playwright (UI) tests
Manual testing performed
Verified against a live SAP HANA Cloud instance (HANA 4.00.000.00) using a purpose-built fixture with a known lineage graph: 3 base tables, 6 views covering join, aggregate, chained, union and hyphenated-name shapes, plus a CTAS table, an
INSERT INTO ... SELECT, and a stored procedure._SYS_REPO, and every view on the instance isVIEW_TYPE = ROW.LT_ORDER -> LT_ORDER_ARCHIVEwith 3 column pairs.CREATEstatements.One parser gap observed and not fixed here: the
UNION ALLview resolved both upstream tables correctly but produced no column pairs, while every other shape produced them.UI screen recording / screenshots:
Not applicable.
Checklist:
Fixes <issue-number>: <short explanation>Fixes #<issue-number>above.