The following steps produce a "cache lookup failed for function" error:
CREATE TABLE t (a int);
COPY t FROM stdin WHERE NOT (a > 0);
1
0
\.
I think what's happening is that negate_clause() is calling get_negator()
with a 0 argument because DoCopy() neglects to call fix_opfuncids() (like
RelationGetIndexPredicate() does). The attached patch fixes this.
--
nathan
>From f5b6cf2c5eefe24b1fb39e85174d6516f2cba36c Mon Sep 17 00:00:00 2001
From: Nathan Bossart <[email protected]>
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