Disallow generated columns in COPY WHERE clause
authorPeter Eisentraut <peter@eisentraut.org>
Thu, 6 Nov 2025 10:52:47 +0000 (11:52 +0100)
committerPeter Eisentraut <peter@eisentraut.org>
Thu, 6 Nov 2025 12:55:08 +0000 (13:55 +0100)
Stored generated columns are not yet computed when the filtering
happens, so we need to prohibit them to avoid incorrect behavior.

Virtual generated columns currently error out ("unexpected virtual
generated column reference").  They could probably work if we expand
them in the right place, but for now let's keep them consistent with
the stored variant.  This doesn't change the behavior, it only gives a
nicer error message.

Co-authored-by: jian he <jian.universality@gmail.com>
Reviewed-by: Kirill Reshke <reshkekirill@gmail.com>
Reviewed-by: Masahiko Sawada <sawada.mshk@gmail.com>
Discussion: https://www.postgresql.org/message-id/flat/CACJufxHb8YPQ095R_pYDr77W9XKNaXg5Rzy-WP525mkq+hRM3g@mail.gmail.com

src/backend/commands/copy.c
src/test/regress/expected/generated_stored.out
src/test/regress/expected/generated_virtual.out
src/test/regress/sql/generated_stored.sql
src/test/regress/sql/generated_virtual.sql

index 74ae42b19a710da948a461b506480d38978c64d0..f0be92e3192e3f794bad1bd606926370e55bbd4e 100644 (file)
@@ -133,6 +133,9 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
 
        if (stmt->whereClause)
        {
+           Bitmapset  *expr_attrs = NULL;
+           int         i;
+
            /* add nsitem to query namespace */
            addNSItemToQuery(pstate, nsitem, false, true, true);
 
@@ -145,6 +148,42 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
            /* we have to fix its collations too */
            assign_expr_collations(pstate, whereClause);
 
+           /*
+            * Examine all the columns in the WHERE clause expression.  When
+            * the whole-row reference is present, examine all the columns of
+            * the table.
+            */
+           pull_varattnos(whereClause, 1, &expr_attrs);
+           if (bms_is_member(0 - FirstLowInvalidHeapAttributeNumber, expr_attrs))
+           {
+               expr_attrs = bms_add_range(expr_attrs,
+                                          1 - FirstLowInvalidHeapAttributeNumber,
+                                          RelationGetNumberOfAttributes(rel) - FirstLowInvalidHeapAttributeNumber);
+               expr_attrs = bms_del_member(expr_attrs, 0 - FirstLowInvalidHeapAttributeNumber);
+           }
+
+           i = -1;
+           while ((i = bms_next_member(expr_attrs, i)) >= 0)
+           {
+               AttrNumber  attno = i + FirstLowInvalidHeapAttributeNumber;
+
+               Assert(attno != 0);
+
+               /*
+                * Prohibit generated columns in the WHERE clause.  Stored
+                * generated columns are not yet computed when the filtering
+                * happens.  Virtual generated columns could probably work (we
+                * would need to expand them somewhere around here), but for
+                * now we keep them consistent with the stored variant.
+                */
+               if (TupleDescAttr(RelationGetDescr(rel), attno - 1)->attgenerated)
+                   ereport(ERROR,
+                           errcode(ERRCODE_INVALID_COLUMN_REFERENCE),
+                           errmsg("generated columns are not supported in COPY FROM WHERE conditions"),
+                           errdetail("Column \"%s\" is a generated column.",
+                                     get_attname(RelationGetRelid(rel), attno, false)));
+           }
+
            whereClause = eval_const_expressions(NULL, whereClause);
 
            whereClause = (Node *) canonicalize_qual((Expr *) whereClause, false);
index b3710a49de628f0a22caa2bcc6906a174b3acdc8..8b7a71d8f0c482db54797244bfeaf3d741814276 100644 (file)
@@ -502,6 +502,12 @@ COPY gtest1 FROM stdin;
 COPY gtest1 (a, b) FROM stdin;
 ERROR:  column "b" is a generated column
 DETAIL:  Generated columns cannot be used in COPY.
+COPY gtest1 FROM stdin WHERE b <> 10;
+ERROR:  generated columns are not supported in COPY FROM WHERE conditions
+DETAIL:  Column "b" is a generated column.
+COPY gtest1 FROM stdin WHERE gtest1 IS NULL;
+ERROR:  generated columns are not supported in COPY FROM WHERE conditions
+DETAIL:  Column "b" is a generated column.
 SELECT * FROM gtest1 ORDER BY a;
  a | b 
 ---+---
index c5a993cbd8db1985d00a66ccdbb5fba388df0dfc..b7da03ce7ea211929bdb876a0e9d8641839eb52e 100644 (file)
@@ -496,6 +496,12 @@ COPY gtest1 FROM stdin;
 COPY gtest1 (a, b) FROM stdin;
 ERROR:  column "b" is a generated column
 DETAIL:  Generated columns cannot be used in COPY.
+COPY gtest1 FROM stdin WHERE b <> 10;
+ERROR:  generated columns are not supported in COPY FROM WHERE conditions
+DETAIL:  Column "b" is a generated column.
+COPY gtest1 FROM stdin WHERE gtest1 IS NULL;
+ERROR:  generated columns are not supported in COPY FROM WHERE conditions
+DETAIL:  Column "b" is a generated column.
 SELECT * FROM gtest1 ORDER BY a;
  a | b 
 ---+---
index 99ea0105685cff2f011249c0fc04bdbc9a9a17a8..2001a47bcc6a3b6e0e5652f5ca18652a06f09211 100644 (file)
@@ -217,6 +217,10 @@ COPY gtest1 FROM stdin;
 
 COPY gtest1 (a, b) FROM stdin;
 
+COPY gtest1 FROM stdin WHERE b <> 10;
+
+COPY gtest1 FROM stdin WHERE gtest1 IS NULL;
+
 SELECT * FROM gtest1 ORDER BY a;
 
 TRUNCATE gtest3;
index 6683538ac382bc89b035f28b2fd12491eb1eab9b..81a98995d8949bea8b86dad052feb8dcba3bc37a 100644 (file)
@@ -217,6 +217,10 @@ COPY gtest1 FROM stdin;
 
 COPY gtest1 (a, b) FROM stdin;
 
+COPY gtest1 FROM stdin WHERE b <> 10;
+
+COPY gtest1 FROM stdin WHERE gtest1 IS NULL;
+
 SELECT * FROM gtest1 ORDER BY a;
 
 TRUNCATE gtest3;