DEV Community

umzzil nng
umzzil nng

Posted on • Originally published at oraerror.com

PostgreSQL 0F000 Error: Causes and Solutions Complete Guide

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;
$$;
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

Quick Fix Checklist

  • ✅ Always wrap LOB operations in BEGIN / COMMIT
  • ✅ Verify LOB OID existence in pg_largeobject_metadata before lo_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;
$$;
Enter fullscreen mode Exit fullscreen mode

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
);
Enter fullscreen mode Exit fullscreen mode

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)