ResultSet fetch methods silently return empty after close instead of raising (PEP 249 §Cursor)

Open
#792 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Assessment

Difficulty
4/5
Estimated time
3-5 days
Newbie friendliness
55/100
Issue type
Bug
Clarity
Mostly clear
Activity status
Quiet
Tech stack
python

Research direction

Start in src/databricks/sql/result_set.py, then trace the fetch methods on ThriftResultSet, SeaResultSet, and KernelResultSet. Reproduce the closed-cursor example first and inspect how close() sets the existing state and exhaustion flags. Done means every listed fetch method raises InterfaceError after close() for all three backends, with the PEP 249 behavior verified and the compatibility note considered.

Written by the indexing model from the issue text.

Description

enhancement

Summary

PEP 249 §Cursor says fetch operations on a closed cursor should raise an exception. Today, all three result-set implementations (ThriftResultSet, SeaResultSet, KernelResultSet) silently return empty after close() rather than raising InterfaceError.

Repro

import databricks.sql

with databricks.sql.connect(...) as conn:
    cur = conn.cursor()
    cur.execute("SELECT 1")
    cur.close()
    cur.fetchall()  # PEP 249 says raise; today returns [].

Root cause

# src/databricks/sql/result_set.py — base ResultSet.close():
def close(self) -> None:
    ...
    finally:
        self.has_been_closed_server_side = True
        self.status = CommandState.CLOSED

The flag is set, but the fetch methods don't read it. They walk self.results (an internal queue) which has been closed; next_n_rows returns empty rather than raising. KernelResultSet has the same shape with self._exhausted = True in close, which short-circuits its fetch loop to empty.

Scope

All three backends. This is a project-wide PEP 249 compliance gap, not specific to the kernel backend.

Concurrent fetch-vs-close (related)

There is no _closed flag readable by concurrent threads — if one thread is mid-fetch while another calls close(), behaviour is undefined (likely AttributeError from a nulled handle). Cursors are documented as not thread-safe, so this is "not a bug" by design, but a single _closed flag would make accidental misuse fail cleanly with InterfaceError instead of AttributeError.

Suggested fix

In base ResultSet (src/databricks/sql/result_set.py):

  1. Add self._closed: bool = False. Set to True inside close() after cleanup.
  2. Add a private _check_open() that raises InterfaceError("Result set is closed") if _closed.
  3. Call _check_open() at the top of every fetch method (fetchone, fetchmany, fetchall, fetchmany_arrow, fetchall_arrow).

Backwards-compat note: any user code that defensively does cur.close(); cur.fetchall() expecting [] will start raising. Likely warrants a release note.

Surfaced by

Review of PR #787 (kernel backend). Documented as P1 #8 + #9 in https://github.com/databricks/databricks-sql-python/pull/787#issuecomment-4475165482 — but the gap is pre-existing on Thrift and SEA too, not specific to the kernel backend.

Dominant language
Python
Stars
233
Forks
152
Avg merge
21h 5m
Merged PRs (30d)
10

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from databricks/databricks-sql-python

All issues in databricks/databricks-sql-python

Similar issues

More Python issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.