Skip to content

feat: implement MSSQL cancelQuery and applyQueryTimeout#273

Merged
datlechin merged 3 commits intomainfrom
feat/mssql-cancel-query
Mar 11, 2026
Merged

feat: implement MSSQL cancelQuery and applyQueryTimeout#273
datlechin merged 3 commits intomainfrom
feat/mssql-cancel-query

Conversation

@datlechin
Copy link
Owner

@datlechin datlechin commented Mar 11, 2026

Summary

  • Implement cancelQuery() for MSSQL using FreeTDS dbcancel() — enables stopping long-running queries from another thread
  • Implement applyQueryTimeout() using SET LOCK_TIMEOUT — the closest SQL Server equivalent to session-level query timeout
  • Add _isCancelled flag to FreeTDSConnection with proper lock synchronization and reset logic to avoid stale cancellations

Test plan

  • Connect to a SQL Server instance and run a long-running query, verify it can be cancelled via the Stop button
  • Verify that after cancelling a query, subsequent queries execute normally (flag reset works)
  • Verify SET LOCK_TIMEOUT is applied when query timeout is configured in settings
  • Build succeeds with no new warnings

Summary by CodeRabbit

  • New Features
    • Cancel running MSSQL queries to interrupt long-running operations and regain control.
    • Apply per-query MSSQL timeout (lock timeout) to automatically stop queries that exceed a specified duration.

@chatgpt-codex-connector
Copy link

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@coderabbitai
Copy link

coderabbitai bot commented Mar 11, 2026

📝 Walkthrough

Walkthrough

Adds MSSQL query control: support for cancelling running queries via cancelQuery() and applying per-query lock timeout via applyQueryTimeout(_:); internal cancellation flag and polling integrated into query execution; tests updated and CHANGELOG entry added.

Changes

Cohort / File(s) Summary
Changelog
CHANGELOG.md
Added Unreleased note documenting MSSQL query cancellation (cancelQuery) and lock timeout (applyQueryTimeout) support.
MSSQL Driver Implementation
Plugins/MSSQLDriverPlugin/MSSQLPlugin.swift
Introduced cancellation flag (_isCancelled) in FreeTDSConnection, added cancelCurrentQuery() usage, exposed MSSQLPluginDriver.cancelQuery() and applyQueryTimeout(_:), and added cancellation checks during result/row iteration that throw on cancellation.
Tests
TableProTests/Core/Database/MSSQLDriverTests.swift
Added MockMSSQLPluginDriver and tests verifying delegation and behavior of cancelQuery() and applyQueryTimeout(_:), including zero/non-zero timeout handling and multiple invocations.
Manifest
Package.swift
Modified (small change) as part of the diff.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant MSSQLPluginDriver
    participant FreeTDSConnection
    participant Database

    rect rgba(100,150,200,0.5)
    Note over Client,MSSQLPluginDriver: Timeout setup
    Client->>MSSQLPluginDriver: applyQueryTimeout(seconds)
    MSSQLPluginDriver->>Database: Execute "SET LOCK_TIMEOUT ..."
    Database-->>MSSQLPluginDriver: OK
    MSSQLPluginDriver-->>Client: Return
    end

    rect rgba(200,150,100,0.5)
    Note over Client,FreeTDSConnection: Cancellation flow during query
    Client->>MSSQLPluginDriver: Execute query
    MSSQLPluginDriver->>FreeTDSConnection: Start query (reset _isCancelled)
    FreeTDSConnection->>Database: Execute query
    Database-->>FreeTDSConnection: Stream rows

    par Concurrent
        FreeTDSConnection->>FreeTDSConnection: Iterate rows, poll _isCancelled
        Client->>MSSQLPluginDriver: cancelQuery()
        MSSQLPluginDriver->>FreeTDSConnection: cancelCurrentQuery() (set _isCancelled, call dbcancel)
        FreeTDSConnection->>Database: dbcancel
    end

    FreeTDSConnection->>FreeTDSConnection: Detect _isCancelled, throw "Query cancelled"
    FreeTDSConnection-->>MSSQLPluginDriver: Propagate error
    MSSQLPluginDriver-->>Client: Error
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 I nudged a flag, the query froze,
I tapped a timeout, soft and slow,
With hops and hops the rows retreat,
Cancelled clean, no tangled sleet —
A rabbit cheers, the logs now neat.

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 12.50% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately and specifically describes the main changes in the pull request: implementing the cancelQuery and applyQueryTimeout features for MSSQL.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/mssql-cancel-query

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@Plugins/MSSQLDriverPlugin/MSSQLPlugin.swift`:
- Around line 416-420: The current applyQueryTimeout(_:) uses "SET LOCK_TIMEOUT"
which only affects lock acquisition, not overall query execution; update the
implementation to either (A) document this limitation in applyQueryTimeout(_:)'s
method comment and in CHANGELOG so callers know it only controls lock waits, or
(B) implement application-level enforcement: store the timeout value and run
queries through a wrapper (e.g., executeWithTimeout or similar) that launches
the query in an async Task, uses Task.sleep for the timeout duration, and calls
cancelQuery() if the task hasn't completed—ensure you reference
applyQueryTimeout(_:), execute(query:), and cancelQuery() when adding the
wrapper and wire existing callers to use it.
- Around line 176-184: cancelCurrentQuery() currently captures dbproc under lock
but calls dbcancel() directly, racing with disconnect() which clears dbproc and
enqueues dbclose() asynchronously; instead, dispatch the dbcancel() call onto
the same serial queue used for dbclose() so dbcancel() and dbclose() are
serialized, and modify disconnect() to read/clear dbproc under the same lock (or
while on the serial queue) to avoid the data race on dbproc; reference the
cancelCurrentQuery(), disconnect(), dbcancel(), dbclose(), dbproc and lock
symbols when making the changes.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 753ee17e-67a2-4cf7-a822-5e847a0891aa

📥 Commits

Reviewing files that changed from the base of the PR and between d30fba6 and dfd73af.

📒 Files selected for processing (2)
  • CHANGELOG.md
  • Plugins/MSSQLDriverPlugin/MSSQLPlugin.swift

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (2)
Plugins/MSSQLDriverPlugin/MSSQLPlugin.swift (1)

220-226: Consider extracting duplicated cancellation check logic.

The cancellation check pattern (lock, read flag, clear if set, unlock, throw) appears twice: lines 220-226 and lines 258-264. Extracting this to a helper method would reduce duplication and make the cancellation behavior consistent.

♻️ Suggested helper extraction
private func checkCancellationSync() throws {
    lock.lock()
    let cancelled = _isCancelled
    if cancelled { _isCancelled = false }
    lock.unlock()
    if cancelled {
        throw MSSQLPluginError.queryFailed("Query cancelled")
    }
}

Then replace both occurrences with:

try checkCancellationSync()

Also applies to: 258-264

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Plugins/MSSQLDriverPlugin/MSSQLPlugin.swift` around lines 220 - 226, Extract
the duplicated cancellation check into a private helper (e.g.,
checkCancellationSync) that acquires lock, reads _isCancelled, clears it if set,
releases the lock, and throws MSSQLPluginError.queryFailed("Query cancelled")
when cancelled; then replace both in-place blocks around lock/_isCancelled with
a single call (try checkCancellationSync()) so the cancellation behavior is
centralized and consistent.
TableProTests/Core/Database/MSSQLDriverTests.swift (1)

40-43: Mock applyQueryTimeout records both value and synthetic SQL string.

The mock records the timeout value in applyQueryTimeoutValues and also appends a synthetic SQL string to executedQueries. Since applyQueryTimeout in the mock doesn't call execute(), this synthetic entry may mislead tests expecting to verify actual SQL execution. Consider either:

  • Only recording the timeout value (simpler, clearer mock)
  • Or documenting this behavior in a comment
♻️ Simplify mock to only record timeout values
 func applyQueryTimeout(_ seconds: Int) async throws {
     applyQueryTimeoutValues.append(seconds)
-    executedQueries.append("SET LOCK_TIMEOUT \(seconds * 1_000)")
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@TableProTests/Core/Database/MSSQLDriverTests.swift` around lines 40 - 43, The
mock applyQueryTimeout currently appends both the numeric timeout to
applyQueryTimeoutValues and a synthetic SQL string to executedQueries; remove
the synthetic SQL append so applyQueryTimeout only records the timeout value
(leave applyQueryTimeoutValues.append(seconds) intact) to avoid misleading tests
that inspect executedQueries; update the applyQueryTimeout implementation (and
optionally add a brief comment in the mock near applyQueryTimeout) to reflect
that it only records the timeout and does not execute SQL.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@Plugins/MSSQLDriverPlugin/MSSQLPlugin.swift`:
- Around line 220-226: Extract the duplicated cancellation check into a private
helper (e.g., checkCancellationSync) that acquires lock, reads _isCancelled,
clears it if set, releases the lock, and throws
MSSQLPluginError.queryFailed("Query cancelled") when cancelled; then replace
both in-place blocks around lock/_isCancelled with a single call (try
checkCancellationSync()) so the cancellation behavior is centralized and
consistent.

In `@TableProTests/Core/Database/MSSQLDriverTests.swift`:
- Around line 40-43: The mock applyQueryTimeout currently appends both the
numeric timeout to applyQueryTimeoutValues and a synthetic SQL string to
executedQueries; remove the synthetic SQL append so applyQueryTimeout only
records the timeout value (leave applyQueryTimeoutValues.append(seconds) intact)
to avoid misleading tests that inspect executedQueries; update the
applyQueryTimeout implementation (and optionally add a brief comment in the mock
near applyQueryTimeout) to reflect that it only records the timeout and does not
execute SQL.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 19a0c549-5382-4bed-886b-f3024c8a5553

📥 Commits

Reviewing files that changed from the base of the PR and between dfd73af and b6292f9.

📒 Files selected for processing (2)
  • Plugins/MSSQLDriverPlugin/MSSQLPlugin.swift
  • TableProTests/Core/Database/MSSQLDriverTests.swift

@datlechin datlechin merged commit e416c04 into main Mar 11, 2026
3 checks passed
@datlechin datlechin deleted the feat/mssql-cancel-query branch March 11, 2026 09:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant