select open change scope Open full search

PG.CENTER connects PostgreSQL documentation, reference, and ecosystem knowledge. Maintained by Pigsty.

SQLSTATE / CLASS 24 · INVALID CURSOR STATE

invalid_cursor_state

SQLSTATE
24000
Condition name
invalid_cursor_state
Class
Invalid Cursor State
Source macro
ERRCODE_INVALID_CURSOR_STATE
Evidence
Observed at runtime in the source evidence
Analysis & operational context

English SQLSTATE atlas: authored explanations and source/runtime evidence are separate from the manual definitions. View source ↗

<h1>24000 — invalid_cursor_state</h1>

At a glance

24000 means the cursor or portal state does not satisfy the operation. The selected CURRENT OF case declares a valid cursor but tries to update before FETCH has positioned it, producing “cursor ... is not positioned on a row”.

Meaning

WHERE CURRENT OF needs a cursor declared for an updatable query, with a current row selected by FETCH. Declaration alone is not positioning. PostgreSQL uses 24000 for other cursor states too, including non-SELECT, held, non-updatable, or missing FOR UPDATE/SHARE references.

Diagnosis

Record the cursor name and operation, then inspect declaration, transaction lifetime, FETCH direction, and whether the cursor is updatable. In an explicit transaction the selected error leaves INERROR; ROLLBACK restores IDLE. The selected repair updates by primary key, so it verifies transaction recovery and a safe direct repair, not a second DECLARE/FETCH path.

Response

Position the cursor with FETCH before CURRENT OF, or use a deterministic key-based update when that is the intended operation. Keep cursor lifetime and transaction boundaries explicit, and roll back a failed transaction before issuing the repair.

Observed diagnostics

18.6 (Homebrew) / latest:SQLSTATE 24000; primary cursor "item_cursor" is not positioned on a row; after_error INERROR; after_rollback IDLE. 10.21 (Debian 10.21-1.pgdg90+1) / pg10:SQLSTATE 24000; primary cursor "item_cursor" is not positioned on a row; after_error INERROR; after_rollback IDLE.

Representative case

The runner reads these statements from verify/cases/24000/snippets.json (SHA-256 cafca57825378362df0527384614bf658bf86cd3101e1eb520ea0641819f2789) and qualifies the temporary table names; the complete setup, assertions, and cleanup are in the case export.

-- create
CREATE TABLE items(id integer PRIMARY KEY, note text NOT NULL);
-- seed
INSERT INTO items VALUES (1, 'seed');
-- begin
BEGIN;
-- declare
DECLARE item_cursor CURSOR FOR SELECT id FROM items FOR UPDATE;
-- trigger
UPDATE items SET note = 'bad' WHERE CURRENT OF item_cursor;
-- rollback
ROLLBACK;
-- repair
UPDATE items SET note = 'repaired' WHERE id = 1;
-- verify
SELECT id, note FROM items;

The SQLSTATE, diagnostic, transaction-state, and repair assertions for this excerpt are produced from the shared registry; structured evidence · case export.

Authored evidence IDs: identity, current-of, runtime. Selected runtime records: runtime.24000-batch1-latest2-20260909.latest, runtime.24000-batch1-pg10-20260909.pg10.

Versions

The locked catalogue observes the condition by 7.4 and in all listed formal snapshots. The selected before-FETCH path passes on 18.6 and 10.21; other cursor-state templates require separate cases.

Sources

Source evidence

Evidence belongs to the frozen source and runtime versions listed here. It is not a runtime verification of the selected manual version.

24000 is invalid_cursor_state in Class 24.

Method: Read the fixed condition row.

Limits: The class has several cursor/portal states; the observed message is one path.

src.errcodes.18.6

UPDATE or DELETE ... WHERE CURRENT OF requires a positioned, updatable cursor row; before FETCH the executor reports 24000.

Method: Trace execCurrent and the documented DECLARE/CURRENT OF rules.

Limits: A cursor can be validly declared yet still be unpositioned; declaration alone does not make CURRENT OF usable.

src.execCurrent.18.6 doc.declare.18.6 doc.update.18.6 src.calls.REL_18_6

The selected explicit transaction case returned 24000 before FETCH, entered INERROR, recovered after ROLLBACK, and then directly repaired the row on PG18.6 and PG10.21.

Method: Read the selected summaries and raw diagnostics.

Limits: Only the before-FETCH CURRENT OF path was run; other cursor states use other source templates.

runtime.24000-batch1-latest2-20260909.latest runtime.24000-batch1-pg10-20260909.pg10

Message templates

ERROR · unpositioned

Primary

cursor "%s" is not positioned on a row

The same template is also used by another CURRENT OF path; the cursor name is substituted at runtime.

Reproduction & repair cases

current_of_before_fetch · PG 10, 18

Preconditions

  • A cursor is declared for a SELECT ... FOR UPDATE
  • The cursor has not fetched a row

Trigger: Use WHERE CURRENT OF before the cursor is positioned on a row.

Expected assertions

  • SQLSTATE is 24000
  • The connection recovers after rollback
  • A direct update succeeds afterward

Repair: Fetch or otherwise position the cursor before CURRENT OF, or use a key-based update whose concurrency semantics are explicit.

Cleanup: Drop the case schema with an owner connection.

Recorded runtime evidence

18.6 (Homebrew) · passed

Run: 24000-batch1-latest2-20260909

{
  "primary": "cursor \"item_cursor\" is not positioned on a row",
  "sqlstate": "24000",
  "after_error": "INERROR",
  "repaired_row": [
    1,
    "repaired"
  ],
  "after_rollback": "IDLE"
}
10.21 (Debian 10.21-1.pgdg90+1) · passed

Run: 24000-batch1-pg10-20260909

{
  "primary": "cursor \"item_cursor\" is not positioned on a row",
  "sqlstate": "24000",
  "after_error": "INERROR",
  "repaired_row": [
    1,
    "repaired"
  ],
  "after_rollback": "IDLE"
}

Definition snapshot: english-manuals:8a25d767a3420a02ec086b10a10… · English manual source