Skip to content

fix(ingestion): apply AUTOCOMMIT via create_engine so releasing a connection cannot assert - #31535

Open
ulixius9 wants to merge 1 commit into
mainfrom
fix/autocommit-isolation-reset
Open

fix(ingestion): apply AUTOCOMMIT via create_engine so releasing a connection cannot assert#31535
ulixius9 wants to merge 1 commit into
mainfrom
fix/autocommit-isolation-reset

Conversation

@ulixius9

@ulixius9 ulixius9 commented Aug 14, 2026

Copy link
Copy Markdown
Member

Describe your changes

Ingestion engines opt into AUTOCOMMIT so read-only crawls stop pinning AccessShareLock for the length of a run (#29092, #29658). It was applied with engine.update_execution_options(isolation_level="AUTOCOMMIT"), which makes it a per-connection execution option. SQLAlchemy then tracks it as an IsolationLevelCharacteristic and restores the level when the connection returns to the pool.

Only the create_engine(isolation_level=...) kwarg records the level on the dialect (_on_connect_isolation_level), and that is what the restore path reads. Supplied as an execution option, the field stays unset and reset_isolation_level falls back to dialect.default_isolation_level — which Dialect.initialize leaves as None on any dialect whose get_isolation_level raises NotImplementedError.

MSDialect.get_isolation_level raises exactly that whenever its opening probe returns no row or errors:

SELECT name FROM sys.system_views
WHERE name IN ('dm_exec_sessions', 'dm_pdw_nodes_exec_sessions')

That is the reported state on Azure Synapse. Every mssql+pyodbc connection then died on release:

File ".../sqlalchemy/engine/characteristics.py", line 103, in reset_characteristic
    dialect.reset_isolation_level(dbapi_conn)
File ".../sqlalchemy/engine/default.py", line 1015, in reset_isolation_level
    assert self.default_isolation_level is not None
AssertionError

The probe query had already succeeded, so the user-facing result was Failed to connect, please validate the credentials on perfectly good credentials, and metadata ingestion never got past CheckAccess. This affects any mssql+pyodbc target where the isolation level cannot be read back.

Dialect support for AUTOCOMMIT can only be probed once the engine has resolved the dialect, so the engine is built twice when it applies. create_engine opens no connection, so discarding the first one costs nothing; the call itself is unchanged, just moved behind a local build_engine(**extra).

Type of change

  • Bug fix

Checklist

  • I have read the CONTRIBUTING document
  • My PR title is following the naming convention
  • I have added tests around the new logic
  • Existing unit tests pass locally with my changes

Verification

Against a live SQL Server 2022 over ODBC Driver 18

Three logins on the same instance, differing only in whether the isolation-level probe succeeds, each driven through the real create_generic_db_connection:

login probe result default_isolation_level main this PR
GRANT VIEW SERVER STATE ['dm_exec_sessions'] 'READ COMMITTED' OK OK
db_datareader only ['dm_exec_sessions'] 'READ COMMITTED' OK OK
probe errors (msg 229) ProgrammingErrorNotImplementedError None AssertionError OK

The third row is the field failure, reproduced on a real dialect and driver, and fixed.

Note rows 1–2: permissions are not the trigger. A principal without VIEW SERVER STATE still reads its own session row from sys.dm_exec_sessions, so granting it is not a workaround. What matters is only whether the probe resolves at all.

Unit tests

The new test fails on main at the same frame and passes with the fix:

# without the fix
FAILED test_create_generic_db_connection_autocommit_survives_pool_checkin
  .../sqlalchemy/engine/default.py:1015: AssertionError
1 failed, 8 passed

# with the fix
9 passed

Wider sweep — ingestion/tests/unit/{test_connection_builders.py,source/database,topology/database}: 1566 passed, with the same 7 failures and 31 collection errors (missing optional drivers) present on main before the change.

ruff check / ruff format --check / basedpyright clean on both files.

Test change worth a look

test_create_generic_db_connection_applies_autocommit asserted engine.get_execution_options()["isolation_level"] == "AUTOCOMMIT" — the exact plumbing this PR deliberately moves. It now asserts the behaviour #29092 actually asked for: a second connection sees a write that was never committed. That assertion passes both before and after this change, so it still guards the original fix.

Backport

The regression is in every branch carrying #29658, including 1.13.

@ulixius9
ulixius9 requested a review from a team as a code owner August 14, 2026 10:41
Copilot AI lite review requested due to automatic review settings August 14, 2026 10:41

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

❌ 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 14, 2026
@github-actions

github-actions Bot commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

✅ Playwright Results — workflow succeeded

Validated commit e70ff99525ff6735e7ace613bf729c957470e773 in Playwright run 31801923253, attempt 1.

✅ 110 passed · ❌ 0 failed · 🟡 0 flaky · ⏭️ 0 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) 49m 29s

⏱️ Max setup 3m 9s · max shard execution 12m 14s · max shard-job elapsed before upload 17m 45s · reporting 4s

🌐 213.83 requests/attempt · 1.79 app boots/UI scenario · 0.00% common-shard skew

Optimization targets still in progress:

  • Browser traffic was 213.83 requests per attempt (convergence target: fewer than 200).
  • Application boot ratio was 1.79 per UI scenario (216 boots / 121 scenarios; convergence target: at most 1).
Shard Passed Failed Flaky Skipped Lifecycle failed Lifecycle flaky
✅ Shard chromium-01 46 0 0 0 0 0
✅ Shard ingestion-01 26 0 0 0 0 0
✅ Shard ingestion-02 38 0 0 0 0 0

📦 Download artifacts

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

…nection cannot assert

Ingestion engines opt into AUTOCOMMIT so read-only crawls stop pinning
AccessShareLock for the length of a run (#29092, #29658). That was applied with
engine.update_execution_options, which makes it a per-connection execution
option: SQLAlchemy then tracks it as an IsolationLevelCharacteristic and restores
the level when the connection returns to the pool. Only the create_engine kwarg
records the level on the dialect, so with the execution option that field stays
unset and the restore falls back to dialect.default_isolation_level -- which
Dialect.initialize leaves as None on any dialect whose get_isolation_level raises
NotImplementedError.

MSDialect.get_isolation_level raises exactly that whenever its opening probe --
SELECT name FROM sys.system_views WHERE name IN ('dm_exec_sessions',
'dm_pdw_nodes_exec_sessions') -- returns no row or errors, which is the reported
state on Azure Synapse. Every mssql+pyodbc connection then died in
reset_isolation_level on `assert self.default_isolation_level is not None` the
moment it was released -- after the probe query had already succeeded. Test
connection reported "Failed to connect, please validate the credentials" on
perfectly good credentials, and metadata ingestion never got past CheckAccess.

Reproduced against SQL Server 2022 over ODBC Driver 18: a login whose probe
succeeds is unaffected either way, while a login whose probe errors takes the
AssertionError before this change and connects cleanly after it.

Dialect support for AUTOCOMMIT can only be probed once the engine has resolved
the dialect, so the engine is built twice when it applies; create_engine opens no
connection, so discarding the first costs nothing.

test_create_generic_db_connection_applies_autocommit now asserts the behaviour
#29092 asked for -- a second connection sees an uncommitted write -- instead of
the execution-option plumbing, which this change deliberately moves.
@ulixius9
ulixius9 force-pushed the fix/autocommit-isolation-reset branch from b7877fd to e70ff99 Compare August 14, 2026 12:48
@gitar-bot

gitar-bot Bot commented Aug 14, 2026

Copy link
Copy Markdown
Code Review ✅ Approved

Applies AUTOCOMMIT via create_engine rather than execution options to prevent connection pool reset assertions on dialects like Azure Synapse. No issues found.

Options

Display: compact → Showing less information.

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

@pmbrull pmbrull added this to Shipping Aug 14, 2026
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

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

3 participants