24000
Read PG 18 manual ↗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
English SQLSTATE atlas: authored explanations and source/runtime evidence are separate from the manual definitions. View source ↗
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
src.errcodes.18.6(SHA-2566e8de346643ba84aa3c9c6a73360acfc7b2dfb89162c06c08ce9bf5bcd5bbcba)src.calls.REL_18_6(SHA-2569ee8a0e81d8f0825c5c1ae45583439859a26e602bdd4ce2f2a62aa278867ccbf)src.execCurrent.18.6(SHA-25645c8e48b3a5dc46f1014482dc88e0a26c25c3a4c403372112474d9171253d8a9)doc.declare.18.6(SHA-256f1d72befb9a989aa32123560aea784a8bccfcb3d3930804a101d8245e5a3cc95) · official documentationdoc.update.18.6(SHA-25647dd724cd724bd77478215a4853aa1f985996a0e5bba9b2451a1ad1ebb6e51fc) · official documentation
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.
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
- src/backend/utils/errcodes.txt · REL_18_6 ·
724edf9bde9d356724ad384a2e196edc3c9f80f7 raw/calls/REL_18_6.jsonl· ·- src/backend/executor/execCurrent.c · REL_18_6 ·
724edf9bde9d356724ad384a2e196edc3c9f80f7 - doc/src/sgml/ref/declare.sgml · REL_18_6 ·
724edf9bde9d356724ad384a2e196edc3c9f80f7 - doc/src/sgml/ref/update.sgml · REL_18_6 ·
724edf9bde9d356724ad384a2e196edc3c9f80f7
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