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

Reply via email to