Thread

  1. Re: Disallow whole-row index references with virtual generated columns?

    Ayush Tiwari <ayushtiwari.slg01@gmail.com> — 2026-05-08T14:40:10Z

    Hi,
    
    On Fri, 8 May 2026 at 19:42, Álvaro Herrera <alvherre@kurilemu.de> wrote:
    
    > On 2026-May-08, Ayush Tiwari wrote:
    >
    > > The shape that worried me the most was a partial unique index whose
    > > predicate uses a whole-row reference, e.g. WHERE rel IS NOT NULL.  As
    > > far as I can tell, the predicate can be true at the SQL level, but
    > > index build and maintenance evaluate the stored predicate against the
    > > physical heap tuple, where the virtual column is not stored.  If that
    > > reading is right, the index could end up with no entries for rows that
    > > satisfy the predicate, which would mean uniqueness is silently not
    > > enforced.  Does that sound like a bug, or am I missing something?
    > >
    > > This is what I tried on master:
    > >
    > >     CREATE TABLE t (a int, b int GENERATED ALWAYS AS (a * 2) VIRTUAL);
    > >     CREATE UNIQUE INDEX t_a_wholerow_pred_idx ON t (a) WHERE t IS NOT
    > NULL;
    > >     INSERT INTO t(a) VALUES (1);
    > >     INSERT INTO t(a) VALUES (1);  -- accepted, two rows with a = 1
    >
    > Hmm, but this also works just fine when the column b is a normal column,
    > so I don't see why you would want to restrict this specifically for
    > virtual generated columns.  If you want that to fail, you would use
    > WHERE t IS DISTINCT FROM NULL in the index predicate, and that makes the
    > second insert fail both for regular columns and for virtual generated
    > columns alike.
    >
    
    Good point, the IS NOT NULL example was a poor example.  For a normal
    nullable column, (1, NULL) makes "row IS NOT NULL" false at the SQL
    level, so excluding it from the partial index is correct.  For a virtual
    generated column the SQL-visible row is (1, 2):
    
        CREATE TABLE virtual_t (a int, b int GENERATED ALWAYS AS (a * 2)
    VIRTUAL);
        INSERT INTO virtual_t(a) VALUES (1);
        SELECT virtual_t, virtual_t IS NOT NULL FROM virtual_t;
         virtual_t | ?column?
        -----------+----------
         (1,2)     | t
    
    so SQL says the predicate is true, but the partial index appears to
    evaluate it against the physical heap tuple (1,) and excludes the row
    anyway.  That mismatch is what I was trying to point at.
    
    Does the rowtype index contain the right values for the generated column
    > though?
    >
    
    AFAICT the answer is no:
    
        CREATE TABLE virtual_expr_u (a int, b int GENERATED ALWAYS AS (a * 2)
    VIRTUAL);
        CREATE UNIQUE INDEX virtual_expr_u_idx ON virtual_expr_u
    ((virtual_expr_u));
        INSERT INTO virtual_expr_u(a) VALUES (1);
        INSERT INTO virtual_expr_u(a) VALUES (1);
        ERROR:  duplicate key value violates unique constraint
    "virtual_expr_u_idx"
        DETAIL:  Key ((virtual_expr_u.*))=((1,)) already exists.
    
    while SELECT virtual_expr_u FROM virtual_expr_u; returns (1,2).  So the
    whole-row index expression keys on the unexpanded heap tuple and
    the generated column is missing - same root cause as the partial-predicate
    case.
    
    The existing comment in DefineIndex already says virtual generated
    columns in index expressions/predicates "could be supported, but it
    needs support in RelationGetIndexExpressions() and
    RelationGetIndexPredicate()".  So this patch is just intended as a
    conservative consistent extension of that existing restriction to
    whole-row Vars, until that support is added.
    
    Regards,
    Ayush