Skip to content

fix(mssql,azuresql): keep OpenMetadata's own queries out of usage and lineage - #31163

Merged
akashverma0786 merged 22 commits into
mainfrom
fix/mssql-azuresql-query-header-inline
Sep 26, 2026
Merged

akashverma0786 merged 22 commits into
mainfrom
fix/mssql-azuresql-query-header-inline

Conversation

@IceS2

@IceS2 IceS2 commented Aug 7, 2026 •

Copy link
Copy Markdown
Collaborator

SQL Server reports the connector's own reflection SQL back as user queries. There are two
independent defects behind it, one per code path, and fixing either alone changes nothing.

Query Store never sees the header

sys.query_store_query_text stores one row per statement, and a leading comment belongs to
the batch rather than to any statement, so SQL Server discards it:

sent     : /* {"app": "OpenMetadata", ...} */ SELECT 1 AS m FROM sales.orders
recorded : SELECT 1 AS m FROM sales.orders

No pattern can match a string that was never stored. Sending the header after the first token
keeps it inside the statement. This is why Vertica already has an override — though there the
history table drops the comment outright, which is a different failure with the same remedy.

Unlike Vertica's statement.split(" "), the SQL Server version splits on the first
non-whitespace token: 17 of the MSSQL query constants are textwrap.dedent strings that open
with a newline, and for those the naive split lands the comment between two whitespace runs —
still ahead of the first keyword, still discarded, and indistinguishable from success.

The plan cache keeps the header, but not at position 0

sp_executesql puts the parameter declarations in front of the statement:

(@P1 NVARCHAR(MAX),@P2 NVARCHAR(MAX))/* {"app": "OpenMetadata", ...} */ WITH fk_info AS (...

The four usage, lineage and stored-procedure filters anchored the header at position 0, so they
missed every parameterised statement while working correctly for unparameterised ones. The
patterns now match the header anywhere in the text.

Azure SQL was never tagging anything

AzuresqlUsageSource extends MssqlUsageSource and shares these queries, but
azureSQLConnection.json never declared supportsQueryComment, so the
hasattr(connection, "supportsQueryComment") gate in create_generic_db_connection skipped
header injection entirely. It advertised supportsUsageExtraction while being unable to
identify its own queries. Declaring the field turns tagging on.

Verification

Against SQL Server 2022 on both pytds and pyodbc, the header now survives into Query Store and
the plan cache for bare, dedented, CTE and parameterised statements, and none of them reach the
parser. The integration test asserts this through the server rather than by inspecting strings —
a header at "\n /* ... */ SELECT" looks correct, parses fine, and is still discarded.

Metadata, usage, profiler and auto-classification pipelines were run end to end against a local
SQL Server for both connectors.

Cost

The unanchored predicate roughly doubles the cost of that one query — 338 ms to 662 ms measured
over 16 089 distinct query texts with a window covering all of them. It runs once per usage run
per database. No index is affected: query_sql_text and dm_exec_sql_text.text are
nvarchar(max), which SQL Server cannot use as an index key.

Not covered

The dialect's own bootstrap queries (fn_listextendedproperty, sys.system_views, the
isolation-level probe) run on the raw DBAPI connection, outside before_cursor_execute, so no
injection strategy can reach them. Excluding those needs a system-object deny-list, which is a
separate change. Query Store rows recorded before this fix stay untagged and age out with
retention.

RetriggerConfidence Score: 5/5

The PR appears safe to merge; no new actionable failures remain, and both previously reported comment-handling defects are resolved.

Summary

This PR prevents MSSQL and Azure SQL connector queries from being reported as user usage or lineage.

  • Injects the OpenMetadata marker after the first executable SQL token while correctly skipping whitespace, line comments, and nested block comments.
  • Makes plan-cache and Query Store filters recognize markers appearing anywhere in recorded statement text.
  • Enables query comments for Azure SQL through the schema-first connection capability.
  • Adds unit and SQL Server integration coverage for bare, dedented, CTE, parameterized, commented, and nested-comment statements.
  • Both previous findings are resolved: leading comments and nested block comments are now skipped before selecting the injection anchor.
Diagram
%%{init: {'theme': 'neutral'}}%%
flowchart LR
    A[OpenMetadata SQL] --> B[Skip leading whitespace and comments]
    B --> C[Find first executable word]
    C --> D[Insert OpenMetadata marker after word]
    D --> E[SQL Server execution]
    E --> F[Plan cache or Query Store]
    F --> G{Marker present anywhere?}
    G -->|Yes| H[Exclude from usage and lineage]
    G -->|No| I[Process as user query]
Loading

Reviews (9) · Last reviewed commit: "Merge branch 'main' into fix/mssql-azure..."

… lineage

SQL Server reported the connector's own reflection SQL back as user queries.
Two independent defects had to be fixed together; either one alone leaves the
behaviour unchanged.

sys.query_store_query_text stores one row per statement, and a leading comment
belongs to the batch rather than to any statement, so the header was discarded
before it could ever be matched. Sending it after the first token keeps it
inside the statement. This is the same reason Vertica already has an override,
though there the history table drops the comment outright.

The plan-cache path did keep the header, but sp_executesql puts the parameter
declarations in front of it, so the position-0 anchor in the four usage,
lineage and stored-procedure filters missed every parameterised statement. The
patterns now match the header anywhere in the text.

Azure SQL reuses MssqlUsageSource and so shares those queries, but its schema
never declared supportsQueryComment, which left create_generic_db_connection
skipping header injection for it entirely. Declaring the field turns tagging on.

Verified against SQL Server 2022 on both pytds and pyodbc: the header now
survives into Query Store and the plan cache for bare, dedented, CTE and
parameterised statements, and none of them reach the parser.
Copilot AI lite review requested due to automatic review settings August 7, 2026 07:26
@IceS2
IceS2 requested a review from a team as a code owner August 7, 2026 07:26

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@github-actions

github-actions Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

❌ PR checklist incomplete

This PR cannot be merged until the following are addressed on its linked issue:

  • No GitHub issue is linked. Link an issue in the Development section of the PR (or add Fixes #12345 to the description). For a same-org cross-repo issue, add Fixes open-metadata/<repo>#123 to the description.

The fields live on the linked issue in the Shipping project (open the issue → right sidebar → Projects). After you set them, re-run this check (or push a commit) — issue/project changes do not re-trigger it automatically.

Maintainers can bypass this check by adding the skip-pr-checks label.

@github-actions github-actions Bot added Ingestion safe to test Add this label to run secure Github workflows on PRs labels Aug 7, 2026
Comment thread ingestion/src/metadata/ingestion/connections/headers.py Outdated
@github-actions

github-actions Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

✅ TypeScript Types Auto-Updated

The generated TypeScript types have been automatically updated based on JSON schema changes in this PR.

Copilot AI review requested due to automatic review settings August 7, 2026 07:31
@github-actions
github-actions Bot requested a review from a team as a code owner August 7, 2026 07:31

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

Copilot AI review requested due to automatic review settings August 7, 2026 12:22

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@github-actions

github-actions Bot commented Aug 7, 2026 •

Copy link
Copy Markdown
Contributor

Jest test Coverage

UI tests summary

Lines Statements Branches Functions
Coverage: 73%
73.23% (106679/145664) 58.59% (65295/111443) 59.77% (21536/36028)

@github-actions

github-actions Bot commented Aug 7, 2026 •

Copy link
Copy Markdown
Contributor

✅ Playwright Results — workflow succeeded

Validated commit f071f7726652332057150d670a5f35f8c2399fe5 in Playwright run 36256691608, attempt 1.

✅ 4599 passed · ❌ 0 failed · 🟡 6 flaky · ⏭️ 1 skipped · 🧰 0 lifecycle flaky

Performance

Blocking 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) 1h 5m 21s

⏱️ Max setup 4m 40s · max shard execution 20m 48s · max shard-job elapsed before upload 24m 11s · reporting 19s

🌐 122.64 requests/attempt · 2.23 app boots/UI scenario · 40.87% common-shard skew

Optimization targets still in progress:

  • Common shard skew was 40.87% (convergence target: at most 15%).
  • Application boot ratio was 2.23 per UI scenario (10954 boots / 4902 scenarios; convergence target: at most 1).
Shard Passed Failed Flaky Skipped Lifecycle failed Lifecycle flaky
✅ Shard advanced-search-01 64 0 0 0 0 0
✅ Shard advanced-search-02 66 0 0 0 0 0
🟡 Shard chromium-01 127 0 1 0 0 0
✅ Shard chromium-02 154 0 0 0 0 0
🟡 Shard chromium-03 165 0 1 0 0 0
✅ Shard chromium-04 163 0 0 0 0 0
✅ Shard chromium-05 149 0 0 0 0 0
✅ Shard chromium-06 156 0 0 0 0 0
✅ Shard chromium-07 147 0 0 0 0 0
✅ Shard chromium-08 165 0 0 0 0 0
✅ Shard chromium-09 152 0 0 0 0 0
✅ Shard chromium-10 197 0 0 1 0 0
✅ Shard chromium-11 162 0 0 0 0 0
🟡 Shard chromium-12 158 0 1 0 0 0
✅ Shard chromium-13 175 0 0 0 0 0
✅ Shard chromium-14 127 0 0 0 0 0
✅ Shard chromium-15 177 0 0 0 0 0
🟡 Shard chromium-16 167 0 1 0 0 0
✅ Shard chromium-17 126 0 0 0 0 0
🟡 Shard chromium-18 157 0 1 0 0 0
✅ Shard chromium-19 132 0 0 0 0 0
✅ Shard chromium-20 151 0 0 0 0 0
✅ Shard chromium-21 122 0 0 0 0 0
✅ Shard chromium-22 151 0 0 0 0 0
✅ Shard chromium-23 159 0 0 0 0 0
✅ Shard chromium-24 157 0 0 0 0 0
✅ Shard chromium-25 171 0 0 0 0 0
✅ Shard chromium-26 155 0 0 0 0 0
✅ Shard data-asset-rules-01 65 0 0 0 0 0
✅ Shard domain-isolation-01 16 0 0 0 0 0
✅ Shard global-state-01 34 0 0 0 0 0
✅ Shard import-export-01 31 0 0 0 0 0
🟡 Shard import-export-02 81 0 1 0 0 0
✅ Shard import-export-03 37 0 0 0 0 0
✅ Shard ingestion-01 44 0 0 0 0 0
✅ Shard ingestion-02 57 0 0 0 0 0
✅ Shard reindex-01 41 0 0 0 0 0
✅ Shard search-01 12 0 0 0 0 0
✅ Shard search-rbac-01 29 0 0 0 0 0
🟡 6 flaky test(s) (passed on retry)
  • VersionPages/GlossaryVersionPage.spec.ts › GlossaryTerm (shard chromium-01, 1 retry)
  • Flow/NotificationAlerts.spec.ts › Single Filter Alert (shard chromium-03, 1 retry)
  • Features/Workflows/WorkflowOssRestrictions.spec.ts › cancel workflow opens confirmation modal; close-without-saving returns to view mode (shard chromium-12, 1 retry)
  • Features/AccessControlSettings.spec.ts › should remove a team from a policy via Teams tab (shard chromium-16, 1 retry)
  • Features/ActivityFeed.spec.ts › All badge, header and rendered list agree on the count (shard chromium-18, 1 retry)
  • Features/SearchExport.spec.ts › Export queues a background job and downloads from the jobs tray (shard import-export-02, 1 retry)

📦 Download artifacts

How to debug locally
# Download playwright-test-results-<shard> artifact and unzip
npx playwright show-trace path/to/trace.zip    # view trace

Copilot AI review requested due to automatic review settings August 10, 2026 06:53

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

Comment thread ingestion/src/metadata/ingestion/connections/headers.py Outdated
…r anchor

T-SQL nests block comments. The regex that skipped leading comments stopped at
the first `*/`, so for `/* outer /* inner */ AND condition */ SELECT ...` the
anchor landed on `AND`, still inside the outer comment. Query Store discards the
commented preamble along with the header, and the statement is reported as user
usage and lineage again -- the same failure the enclosing change fixes for the
simpler prefixes.

Python's `re` cannot count nesting depth, so the skip is a short scan that
tracks it instead. An unterminated block comment still yields no anchor: the
header carries a `*/` that would close it.

Verified against SQL Server 2022 with that exact statement -- previously the
usage filter handed the row to the parser, now it excludes it. Statements whose
leading comments are not nested are byte-identical to before.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

Only OpenMetadata's own marker moved inside the statement, so only its
filter needs to match anywhere in the text. dbt still sends its comment as
a batch preamble; matching it anywhere recovers no dbt rows and drops user
queries that merely quote the marker. Matches every other connector and the
Vertica precedent, which loosened only the OpenMetadata pattern.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

This reverts commit 5ffc2f4.

Anchoring the dbt filter was wrong. dbt's query comment is not always a
leading comment: query-comment.append moves it after the statement, and
only an unanchored pattern excludes those queries from usage and lineage.
Verified against SQL Server 2022 - with the comment appended, the anchored
filter hands dbt's own query to the parser as user activity.

StarRocks already unanchors both markers (starrocks/queries.py:81-82), so
this is not a new convention either.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

_executable_start scored 22 on Cognitive Complexity against a limit of 15
(SonarQube python:S3776). Most of that was the nesting penalty on the inner
depth-tracking loop rather than the logic itself, so moving that loop into
_past_block_comment drops the pair to 11 and 7 with no behavioural change:
old and new agree on every string up to length 6 over '/*- \nS' and on
300k random inputs.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@github-actions

Copy link
Copy Markdown
Contributor

🚦 Removed from the merge queue — manual (2026-09-25T15:20:48Z)

The entry left the queue before it was built, so no checks ran against it.

@github-actions

Copy link
Copy Markdown
Contributor

🚦 Removed from the merge queue — failed_checks (2026-09-25T20:04:24Z)

Blocked the queue: integration-tests-postgres-opensearch

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@gitar-bot

gitar-bot Bot commented Sep 26, 2026

Copy link
Copy Markdown
Code Review ✅ Approved 1 closed / 1 findings

🟡 Medium risk · MSSQL and Azure SQL query tagging and usage filtering change ingestion behavior.

Fixes SQL Server and Azure SQL connectors reporting their own queries as user usage and lineage by injecting the OpenMetadata marker after the first executable SQL token while correctly skipping whitespace, line comments, and nested block comments, and by making plan-cache and Query Store filters recognize markers anywhere in recorded statement text. The inline header injected inside a leading line comment issue has been resolved.

✅ 1 closed
✅ Edge Case: Inline header injected inside a leading line comment (--)

📄 ingestion/src/metadata/ingestion/connections/headers.py:73-80
inject_inline_query_header only skips statements whose first non-whitespace is a block comment (stripped.startswith("/*")). If a statement begins with a -- line comment (e.g. -- note SELECT ...), the first token is -- and the header is placed right after it: -- /* {...} */ note SELECT ..., so the header sits inside the line comment and is dropped — the exact failure this PR fixes. This is unlikely for OpenMetadata's own reflection queries today, so it is a latent gap rather than a live defect; if it matters, also guard stripped.startswith("--") (returning the statement unchanged) or inject after the first real keyword.

Review coverage

📋 Rules No rules evaluated

🧪 Functional validation Not enabled · Set up

🤖 Auto-approval Not enabled · Set up

Options

Display: compact → Counting what did not apply, without listing it.

Comment with these commands to change the behavior for this request:

Compact
gitar display:verbose         

Was this helpful? React with 👍 / 👎 | Powered by Gitar — free for open source

@sonarqubecloud

Copy link
Copy Markdown

@sonarqubecloud

Copy link
Copy Markdown

This branch was successfully deployed

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

Labels

Ingestion safe to test Add this label to run secure Github workflows on PRs To release Will cherry-pick this PR into the release branch

Projects

Status: Done ✅

Development

Successfully merging this pull request may close these issues.

6 participants