PostgreSQL Invalid Cursor Name (SQLSTATE 34000)

PostgreSQL raises ERROR: cursor "<name>" does not exist with SQLSTATE 34000 and condition name invalid_cursor_name when a FETCH, MOVE, or CLOSE command references a cursor name that is unknown in the current session. The error means PostgreSQL cannot find an open cursor by that name to operate on.

What This Error Means

SQLSTATE 34000 belongs to error class 34 — Cursor-related errors — which covers problems with cursor lifecycle operations. The condition name invalid_cursor_name is defined in the SQL standard and PostgreSQL follows it precisely.

PostgreSQL cursors are session-scoped and transaction-aware. When you declare a cursor with DECLARE, it is registered by name in the current session (or transaction, for non-WITH HOLD cursors). Any subsequent FETCH, MOVE, or CLOSE command that references a name not currently registered triggers SQLSTATE 34000. This includes names that were never declared, names that were already closed, or names declared in a different (now-ended) transaction.

For cursors declared without WITH HOLD, the cursor is automatically closed when the transaction ends — either by COMMIT, ROLLBACK, or an error that aborted the transaction. Attempting to use such a cursor after the transaction is over raises 34000. Cursors declared WITH HOLD survive transaction boundaries but are still dropped at the end of the session or when explicitly closed.

Common Causes

  1. Referencing a cursor before declaring it. Issuing FETCH or MOVE before the corresponding DECLARE statement has executed in the current session.

  2. Using a cursor after the transaction has ended. A cursor declared without WITH HOLD is implicitly closed when the transaction commits or rolls back. Code that fetches rows across multiple transactions without declaring the cursor WITH HOLD will hit this error on the second transaction.

  3. Cursor already closed. Explicitly calling CLOSE mycursor and then issuing another FETCH mycursor later in the same session.

  4. Typo or case mismatch in the cursor name. Unquoted cursor names are folded to lowercase by PostgreSQL. Declaring DECLARE MyCursor CURSOR FOR ... and then fetching from "MyCursor" (quoted, mixed case) will fail because PostgreSQL stored the name as mycursor.

  5. Cursor declared in a PL/pgSQL block that has already exited. Cursors opened inside a PL/pgSQL function or anonymous block are closed when that block exits; they cannot be referenced from outside.

  6. Race condition in connection pooling. If a connection is returned to a pool mid-transaction and re-used, the new consumer may attempt to use a cursor name from a prior session, which no longer exists.

How to Fix invalid_cursor_name

  1. Verify the cursor is declared before use. Ensure DECLARE runs in the same transaction as the subsequent FETCH/MOVE/CLOSE:

    BEGIN;
    DECLARE emp_cursor CURSOR FOR SELECT id, name FROM employees ORDER BY id;
    FETCH 10 FROM emp_cursor;
    CLOSE emp_cursor;
    COMMIT;
    
  2. Use WITH HOLD when fetching across transaction boundaries. If your application commits between fetches, declare the cursor WITH HOLD so it survives the commit:

    BEGIN;
    DECLARE emp_cursor CURSOR WITH HOLD FOR SELECT id, name FROM employees ORDER BY id;
    COMMIT;  -- cursor remains open
    
    FETCH 10 FROM emp_cursor;  -- works in a new implicit transaction
    CLOSE emp_cursor;
    
  3. Check for and handle already-closed cursors in PL/pgSQL. Use an exception handler if you are not certain whether a cursor is still open:

    DO $$
    DECLARE
        cur CURSOR FOR SELECT id FROM employees;
    BEGIN
        OPEN cur;
        FETCH cur INTO ...;
        CLOSE cur;
        -- Do not attempt to FETCH or CLOSE cur again here
    END;
    $$;
    
  4. Normalize cursor names to lowercase. To avoid case mismatch issues, always use lowercase unquoted names, or always double-quote the exact case you intend to use consistently:

    -- Safe: unquoted, both sides lowercase
    DECLARE emp_cursor CURSOR FOR SELECT 1;
    FETCH 1 FROM emp_cursor;
    
    -- Also safe: quoted consistently
    DECLARE "EmpCursor" CURSOR FOR SELECT 1;
    FETCH 1 FROM "EmpCursor";
    
  5. Check pg_cursors to inspect open cursors. If you are unsure which cursors are open in the current session, query the system view:

    SELECT name, statement, is_holdable, is_scrollable
    FROM pg_cursors;
    

Additional Information

  • SQLSTATE 34000 is defined in the SQL standard. PostgreSQL has implemented it since version 7.4 when error codes were formalized.
  • Related SQLSTATE codes in the cursor class (34): there is only one standard condition in this class — 34000 itself.
  • Adjacent cursor-related errors include 24000 (invalid_cursor_state), which is raised when a cursor operation is attempted in an inappropriate state (e.g., FETCH on a cursor that has not been opened with OPEN in PL/pgSQL context).
  • JDBC and psycopg2/psycopg3: Both drivers surface this as a DatabaseError or ProgrammingError with the SQLSTATE 34000 attached. Psycopg2 raises psycopg2.errors.InvalidCursorName. psycopg3 raises psycopg.errors.InvalidCursorName.
  • ORMs: ActiveRecord, SQLAlchemy, and similar ORMs do not use named SQL cursors directly; they use server-side cursors only when explicitly requested (e.g., SQLAlchemy's stream_results=True or yield_per()). The error is more common in raw SQL or stored procedure code.
  • WITH HOLD cursors hold a snapshot of the result set after commit, which consumes memory. Close them as soon as you are done to avoid resource leaks.

Frequently Asked Questions

Why does my cursor disappear after COMMIT? Cursors declared without WITH HOLD are scoped to the transaction in which they were declared. PostgreSQL automatically closes them when that transaction ends, whether by COMMIT or ROLLBACK. To keep a cursor open across transaction boundaries, declare it WITH HOLD.

Can I check whether a cursor is open before fetching from it? Yes. Query the pg_cursors view, which lists all currently open cursors in the session:

SELECT count(*) FROM pg_cursors WHERE name = 'my_cursor';

In PL/pgSQL you can also use an EXCEPTION WHEN invalid_cursor_name THEN block to handle the case gracefully.

Does invalid_cursor_name abort my transaction? Yes. Like most errors in PostgreSQL, 34000 puts the current transaction into an aborted state. You must issue ROLLBACK (or ROLLBACK TO SAVEPOINT) before you can execute further commands on that connection.

My cursor name looks correct but the error still fires — why? The most likely cause is case folding. PostgreSQL folds unquoted identifiers to lowercase at parse time. If you declared DECLARE MyC CURSOR FOR ..., PostgreSQL stored it as myc. A subsequent FETCH MyC FROM ... also folds to myc and should work — but FETCH "MyC" FROM ... (double-quoted) looks for the name MyC exactly, which does not exist. Use consistent quoting throughout.

Subscribe to the Pulse Newsletter

Get early access to new Pulse features, insightful blogs & exclusive events , webinars, and workshops.

We use cookies to provide an optimized user experience and understand our traffic. To learn more, read our use of cookies; otherwise, please choose 'Accept Cookies' to continue using our website.