From f5b6cf2c5eefe24b1fb39e85174d6516f2cba36c Mon Sep 17 00:00:00 2001 From: Nathan Bossart Date: Mon, 5 Oct 2026 12:06:11 -0500 Subject: [PATCH v1 1/1] Fix COPY FROM ... WHERE for negated operators. COPY FROM runs its WHERE clause through eval_const_expressions() and then hands it straight to ExecInitQual(), skipping the fix_opfuncids() step that every other such caller applies. That goes unnoticed until negate_clause() replaces a NOT with the operator's negator, since the OpExpr it builds has no opfuncid, at which point the executor fails with "cache lookup failed for function 0". For example, COPY tab FROM stdin WHERE NOT (a > 0) has failed this way since COPY FROM ... WHERE was added. To fix, call fix_opfuncids() on the result, as the relcache already does for index predicates. Oversight in commit 31f3817402. Backpatch-through: 14 --- src/backend/commands/copy.c | 2 ++ src/test/regress/expected/copy2.out | 7 ++++++- src/test/regress/sql/copy2.sql | 6 ++++++ 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c index 003b70852bb..95b58cd2c84 100644 --- a/src/backend/commands/copy.c +++ b/src/backend/commands/copy.c @@ -29,6 +29,7 @@ #include "miscadmin.h" #include "nodes/makefuncs.h" #include "nodes/miscnodes.h" +#include "nodes/nodeFuncs.h" #include "optimizer/optimizer.h" #include "parser/parse_coerce.h" #include "parser/parse_collate.h" @@ -209,6 +210,7 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt, whereClause = (Node *) canonicalize_qual((Expr *) whereClause, false); whereClause = (Node *) make_ands_implicit((Expr *) whereClause); + fix_opfuncids(whereClause); } tupDesc = RelationGetDescr(rel); diff --git a/src/test/regress/expected/copy2.out b/src/test/regress/expected/copy2.out index 919eabd5f78..df6fcd91799 100644 --- a/src/test/regress/expected/copy2.out +++ b/src/test/regress/expected/copy2.out @@ -184,6 +184,7 @@ LINE 1: COPY x TO stdout WHERE a = 1; HINT: Try the COPY (SELECT ... WHERE ...) TO variant. COPY x from stdin WHERE a = 50004; COPY x from stdin WHERE a > 60003; +COPY x from stdin WHERE NOT (a < 70003); COPY x from stdin WHERE f > 60003; ERROR: column "f" does not exist LINE 1: COPY x from stdin WHERE f > 60003; @@ -234,12 +235,13 @@ SELECT * FROM x; 50004 | 25 | 35 | 45 | before trigger fired 60004 | 25 | 35 | 45 | before trigger fired 60005 | 26 | 36 | 46 | before trigger fired + 70003 | 24 | 34 | 44 | before trigger fired 1 | 1 | stuff | test_1 | after trigger fired 2 | 2 | stuff | test_2 | after trigger fired 3 | 3 | stuff | test_3 | after trigger fired 4 | 4 | stuff | test_4 | after trigger fired 5 | 5 | stuff | test_5 | after trigger fired -(28 rows) +(29 rows) -- check copy out COPY x TO stdout; @@ -266,6 +268,7 @@ COPY x TO stdout; 50004 25 35 45 before trigger fired 60004 25 35 45 before trigger fired 60005 26 36 46 before trigger fired +70003 24 34 44 before trigger fired 1 1 stuff test_1 after trigger fired 2 2 stuff test_2 after trigger fired 3 3 stuff test_3 after trigger fired @@ -295,6 +298,7 @@ Delimiter before trigger fired 35 before trigger fired 35 before trigger fired 36 before trigger fired +34 before trigger fired stuff after trigger fired stuff after trigger fired stuff after trigger fired @@ -324,6 +328,7 @@ I'm null before trigger fired 25 before trigger fired 25 before trigger fired 26 before trigger fired +24 before trigger fired 1 after trigger fired 2 after trigger fired 3 after trigger fired diff --git a/src/test/regress/sql/copy2.sql b/src/test/regress/sql/copy2.sql index 696ceeeaf82..ec6f5e8cfdc 100644 --- a/src/test/regress/sql/copy2.sql +++ b/src/test/regress/sql/copy2.sql @@ -195,6 +195,12 @@ COPY x from stdin WHERE a > 60003; 60005 26 36 46 56 \. +COPY x from stdin WHERE NOT (a < 70003); +70001 22 32 42 52 +70002 23 33 43 53 +70003 24 34 44 54 +\. + COPY x from stdin WHERE f > 60003; \. -- 2.55.0