PostgreSQL Error 0F000: Locator Exception — Causes, Fixes & Prevention
PostgreSQL error code 0F000 represents a Locator Exception, which occurs when your code attempts to use an invalid, expired, or non-existent Large Object (LOB) locator (descriptor). This error is rooted in the SQL standard's definition of LOB locator handling and typically surfaces when working with PostgreSQL's built-in Large Object subsystem (lo_open, loread, lowrite, etc.). If you're seeing this error, your LOB descriptor is either pointing to a deleted object, being used outside a transaction, or leaking across session boundaries.
Top 3 Causes
1. Accessing a Deleted or Non-Existent LOB OID
The most common cause is trying to open a Large Object whose OID no longer exists in pg_largeobject_metadata — usually because it was already deleted with lo_unlink().
-- BAD: Attempting to open a LOB that was already unlinked
DO $$
DECLARE
v_fd INTEGER;
BEGIN
-- If OID 99999 doesn't exist, this raises 0F000 or related error
v_fd := lo_open(99999, 262144); -- INV_READ
PERFORM lo_close(v_fd);
END;
$$;
-- GOOD: Always verify existence before opening
DO $$
DECLARE
v_loid OID := 99999;
v_fd INTEGER;
BEGIN
IF NOT EXISTS (
SELECT 1 FROM pg_largeobject_metadata WHERE oid = v_loid
) THEN
RAISE EXCEPTION 'LOB OID % does not exist', v_loid;
END IF;
v_fd := lo_open(v_loid, 262144);
PERFORM lo_close(v_fd);
END;
$$;
2. LOB Operations Outside an Explicit Transaction
PostgreSQL requires all Large Object operations to run inside an explicit transaction block. Without BEGIN/COMMIT, the LOB descriptor cannot be properly bound to a transaction context, leading to locator exceptions.
-- BAD: No explicit transaction (autocommit environment)
-- lo_open(12345, 262144); -- May raise locator exception
-- GOOD: Wrap all LOB work in an explicit transaction
BEGIN;
DO $$
DECLARE
v_loid OID;
v_fd INTEGER;
BEGIN
v_loid := lo_create(0);
v_fd := lo_open(v_loid, 131072); -- INV_WRITE
PERFORM lowrite(v_fd, 'Sample data'::BYTEA);
PERFORM lo_close(v_fd);
RAISE NOTICE 'Created and wrote to LOB OID: %', v_loid;
END;
$$;
COMMIT;
3. Reusing LOB Descriptors Across Sessions or Reconnections
LOB file descriptors are valid only within the session that created them. In connection pooling environments (e.g., PgBouncer), caching a descriptor and reusing it after the connection is recycled will trigger a locator exception.
-- GOOD: Create a session-safe LOB reader function
-- Always open, read, and close within the same transaction/session
CREATE OR REPLACE FUNCTION read_lob_safe(p_loid OID)
RETURNS BYTEA
LANGUAGE plpgsql
AS $$
DECLARE
v_fd INTEGER;
v_buf BYTEA;
v_out BYTEA := ''::BYTEA;
BEGIN
IF NOT EXISTS (
SELECT 1 FROM pg_largeobject_metadata WHERE oid = p_loid
) THEN
RETURN NULL;
END IF;
v_fd := lo_open(p_loid, 262144); -- INV_READ
LOOP
v_buf := loread(v_fd, 65536);
EXIT WHEN octet_length(v_buf) = 0;
v_out := v_out || v_buf;
END LOOP;
PERFORM lo_close(v_fd);
RETURN v_out;
EXCEPTION WHEN OTHERS THEN
RAISE WARNING 'LOB read failed for OID %: %', p_loid, SQLERRM;
RETURN NULL;
END;
$$;
-- Usage (must be inside a transaction)
BEGIN;
SELECT read_lob_safe(12345);
COMMIT;
Quick Fix Checklist
- ✅ Always wrap LOB operations in
BEGIN/COMMIT - ✅ Verify LOB OID existence in
pg_largeobject_metadatabeforelo_open() - ✅ Always call
lo_close()after reading/writing — use exception handlers to ensure cleanup - ✅ Never cache LOB descriptors across connections or sessions
- ✅ Use
lo_unlink()inside a trigger to auto-clean LOBs when parent rows are deleted
Prevention Tips
1. Use a cleanup trigger to avoid orphaned LOBs:
CREATE OR REPLACE FUNCTION auto_unlink_lob()
RETURNS TRIGGER LANGUAGE plpgsql AS $$
BEGIN
IF OLD.lob_oid IS NOT NULL THEN
PERFORM lo_unlink(OLD.lob_oid);
END IF;
RETURN OLD;
END;
$$;
2. Periodically audit orphaned Large Objects:
-- Find LOBs not referenced by your application table
SELECT lm.oid AS orphan_oid
FROM pg_largeobject_metadata lm
WHERE lm.oid NOT IN (
SELECT lob_oid FROM your_table WHERE lob_oid IS NOT NULL
);
Related Error Codes
| Code | Name | Notes |
|---|---|---|
0F001 |
invalid_locator_specification |
Subclass of 0F000; malformed or wrong locator value |
58030 |
io_error |
Disk I/O failure during LOB read/write |
55000 |
object_not_in_prerequisite_state |
Transaction context not ready for LOB ops |
📖 Want a more detailed guide?
Check out the full in-depth version (Korean) on oraerror.com — includes detailed analysis, additional SQL examples, and prevention tips.
Top comments (0)