Skip to content

24000 — invalid_cursor_state

PostgreSQL SQLSTATE 24000: invalid_cursor_state for a cursor that is not positioned on a row.

24000 — invalid_cursor_state

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”.

Field Value
SQLSTATE 24000
Condition invalid_cursor_state
Status active
Known present by 7.4
Locked snapshots 9.0.23, 9.1.24, 9.2.24, 9.3.25, 9.4.26, 9.5.25, 9.6.24, 10.23, 11.22, 12.22, 13.23, 14.24, 15.19, 16.15, 17.11, 18.6, 19beta3
Macros ERRCODE_INVALID_CURSOR_STATE
Aliases

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.

Compare 25001 active SQL transaction, 25P02 failed SQL transaction, and 34000 invalid cursor name.

Sources