Fix null-pointer-deref crash while doing COPY IN with check constraints.
authorTom Lane <tgl@sss.pgh.pa.us>
Mon, 16 Feb 2015 04:26:45 +0000 (23:26 -0500)
committerTom Lane <tgl@sss.pgh.pa.us>
Mon, 16 Feb 2015 04:26:45 +0000 (23:26 -0500)
In commit bf7ca15875988a88e97302e012d7c4808bef3ea9 I introduced an
assumption that an RTE referenced by a whole-row Var must have a valid eref
field.  This is false for RTEs constructed by DoCopy, and there are other
places taking similar shortcuts.  Perhaps we should make all those places
go through addRangeTableEntryForRelation or its siblings instead of having
ad-hoc logic, but the most reliable fix seems to be to make the new code in
ExecEvalWholeRowVar cope if there's no eref.  We can reasonably assume that
there's no need to insert column aliases if no aliases were provided.

Add a regression test case covering this, and also verifying that a sane
column name is in fact available in this situation.

Although the known case only crashes in 9.4 and HEAD, it seems prudent to
back-patch the code change to 9.2, since all the ingredients for a similar
failure exist in the variant patch applied to 9.3 and 9.2.

Per report from Jean-Pierre Pelletier.

src/backend/executor/execQual.c
src/test/regress/expected/copy2.out
src/test/regress/sql/copy2.sql

index 0e7400fe086dd59a6e0cee9dbfed704a80afd619..dd9d0883e4041ad6fac1f574deba167f645c03ac 100644 (file)
@@ -900,7 +900,9 @@ ExecEvalWholeRowVar(WholeRowVarExprState *wrvstate, ExprContext *econtext,
         * If we can't locate the RTE, assume the column names we've got are OK.
         * (As of this writing, the only cases where we can't locate the RTE are
         * in execution of trigger WHEN clauses, and then the Var will have the
-        * trigger's relation's rowtype, so its names are fine.)
+        * trigger's relation's rowtype, so its names are fine.)  Also, if the
+        * creator of the RTE didn't bother to fill in an eref field, assume our
+        * column names are OK.  (This happens in COPY, and perhaps other places.)
         */
        if (econtext->ecxt_estate &&
                variable->varno <= list_length(econtext->ecxt_estate->es_range_table))
@@ -908,7 +910,8 @@ ExecEvalWholeRowVar(WholeRowVarExprState *wrvstate, ExprContext *econtext,
                RangeTblEntry *rte = rt_fetch(variable->varno,
                                                                          econtext->ecxt_estate->es_range_table);
 
-               ExecTypeSetColNames(output_tupdesc, rte->eref->colnames);
+               if (rte->eref)
+                       ExecTypeSetColNames(output_tupdesc, rte->eref->colnames);
        }
 
        /* Bless the tupdesc if needed, and save it in the execution state */
index 035d843c393b4bad03fb599cb3b7dda6ca52e28c..5e3173774f27bc8d245107e6ad68ecedf60a3cf2 100644 (file)
@@ -429,6 +429,40 @@ COPY forcetest (d, e) FROM STDIN WITH (FORMAT csv, FORCE_NULL(b));
 ERROR:  FORCE NULL column "b" not referenced by COPY
 ROLLBACK;
 \pset null ''
+-- test case with whole-row Var in a check constraint
+create table check_con_tbl (f1 int);
+create function check_con_function(check_con_tbl) returns bool as $$
+begin
+  raise notice 'input = %', row_to_json($1);
+  return $1.f1 > 0;
+end $$ language plpgsql immutable;
+alter table check_con_tbl add check (check_con_function(check_con_tbl.*));
+\d+ check_con_tbl
+                    Table "public.check_con_tbl"
+ Column |  Type   | Modifiers | Storage | Stats target | Description 
+--------+---------+-----------+---------+--------------+-------------
+ f1     | integer |           | plain   |              | 
+Check constraints:
+    "check_con_tbl_check" CHECK (check_con_function(check_con_tbl.*))
+
+copy check_con_tbl from stdin;
+NOTICE:  input = {"f1":1}
+CONTEXT:  COPY check_con_tbl, line 1: "1"
+NOTICE:  input = {"f1":null}
+CONTEXT:  COPY check_con_tbl, line 2: "\N"
+copy check_con_tbl from stdin;
+NOTICE:  input = {"f1":0}
+CONTEXT:  COPY check_con_tbl, line 1: "0"
+ERROR:  new row for relation "check_con_tbl" violates check constraint "check_con_tbl_check"
+DETAIL:  Failing row contains (0).
+CONTEXT:  COPY check_con_tbl, line 1: "0"
+select * from check_con_tbl;
+ f1 
+----
+  1
+   
+(2 rows)
+
 DROP TABLE forcetest;
 DROP TABLE vistest;
 DROP FUNCTION truncate_in_subxact();
index 248055f50644a3e6f39680aa7c17c37eefc0a295..39a9deb8f78369a2a194bb029355917ce272b269 100644 (file)
@@ -308,6 +308,25 @@ BEGIN;
 COPY forcetest (d, e) FROM STDIN WITH (FORMAT csv, FORCE_NULL(b));
 ROLLBACK;
 \pset null ''
+
+-- test case with whole-row Var in a check constraint
+create table check_con_tbl (f1 int);
+create function check_con_function(check_con_tbl) returns bool as $$
+begin
+  raise notice 'input = %', row_to_json($1);
+  return $1.f1 > 0;
+end $$ language plpgsql immutable;
+alter table check_con_tbl add check (check_con_function(check_con_tbl.*));
+\d+ check_con_tbl
+copy check_con_tbl from stdin;
+1
+\N
+\.
+copy check_con_tbl from stdin;
+0
+\.
+select * from check_con_tbl;
+
 DROP TABLE forcetest;
 DROP TABLE vistest;
 DROP FUNCTION truncate_in_subxact();