Don't crash on reference to an un-available system column.

Adopt a more consistent policy about what slot-type-specific
getsysattr functions should do when system attributes are not
available.  To wit, they should all throw the same user-oriented
error, rather than variously crashing or emitting developer-oriented
messages.

This closes a identifiable problem in commits a71cfc56b and
3fb93103a (in v13 and v12), so back-patch into those branches,
along with a test case to try to ensure we don't break it again.
It is not known that any of the former crash cases are reachable
in HEAD, but this seems like a good safety improvement in any case.

Discussion: https://postgr.es/m/141051591267657@mail.yandex.ru
This commit is contained in:
Tom Lane 2021-04-22 17:30:42 -04:00
parent 3fb93103a9
commit 05ce4bf8b1
3 changed files with 120 additions and 5 deletions

View File

@ -122,9 +122,8 @@ tts_virtual_clear(TupleTableSlot *slot)
}
/*
* Attribute values are readily available in tts_values and tts_isnull array
* in a VirtualTupleTableSlot. So there should be no need to call either of the
* following two functions.
* VirtualTupleTableSlots always have fully populated tts_values and
* tts_isnull arrays. So this function should never be called.
*/
static void
tts_virtual_getsomeattrs(TupleTableSlot *slot, int natts)
@ -132,10 +131,19 @@ tts_virtual_getsomeattrs(TupleTableSlot *slot, int natts)
elog(ERROR, "getsomeattrs is not required to be called on a virtual tuple table slot");
}
/*
* VirtualTupleTableSlots never provide system attributes (except those
* handled generically, such as tableoid). We generally shouldn't get
* here, but provide a user-friendly message if we do.
*/
static Datum
tts_virtual_getsysattr(TupleTableSlot *slot, int attnum, bool *isnull)
{
elog(ERROR, "virtual tuple table slot does not have system attributes");
Assert(!TTS_EMPTY(slot));
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot retrieve a system column in this context")));
return 0; /* silence compiler warnings */
}
@ -335,6 +343,15 @@ tts_heap_getsysattr(TupleTableSlot *slot, int attnum, bool *isnull)
Assert(!TTS_EMPTY(slot));
/*
* In some code paths it's possible to get here with a non-materialized
* slot, in which case we can't retrieve system columns.
*/
if (!hslot->tuple)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot retrieve a system column in this context")));
return heap_getsysattr(hslot->tuple, attnum,
slot->tts_tupleDescriptor, isnull);
}
@ -497,7 +514,11 @@ tts_minimal_getsomeattrs(TupleTableSlot *slot, int natts)
static Datum
tts_minimal_getsysattr(TupleTableSlot *slot, int attnum, bool *isnull)
{
elog(ERROR, "minimal tuple table slot does not have system attributes");
Assert(!TTS_EMPTY(slot));
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot retrieve a system column in this context")));
return 0; /* silence compiler warnings */
}
@ -681,6 +702,15 @@ tts_buffer_heap_getsysattr(TupleTableSlot *slot, int attnum, bool *isnull)
Assert(!TTS_EMPTY(slot));
/*
* In some code paths it's possible to get here with a non-materialized
* slot, in which case we can't retrieve system columns.
*/
if (!bslot->base.tuple)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot retrieve a system column in this context")));
return heap_getsysattr(bslot->base.tuple, attnum,
slot->tts_tupleDescriptor, isnull);
}

View File

@ -836,6 +836,59 @@ DETAIL: Failing row contains (a, 10).
-- ok
UPDATE list_default set a = 'x' WHERE a = 'd';
DROP TABLE list_parted;
-- Test retrieval of system columns with non-consistent partition row types.
-- This is only partially supported, as seen in the results.
create table utrtest (a int, b text) partition by list (a);
create table utr1 (a int check (a in (1)), q text, b text);
create table utr2 (a int check (a in (2)), b text);
alter table utr1 drop column q;
alter table utrtest attach partition utr1 for values in (1);
alter table utrtest attach partition utr2 for values in (2);
insert into utrtest values (1, 'foo')
returning *, tableoid::regclass, cmin;
a | b | tableoid | cmin
---+-----+----------+------
1 | foo | utr1 | 0
(1 row)
insert into utrtest values (2, 'bar')
returning *, tableoid::regclass, cmin; -- fails
ERROR: cannot retrieve a system column in this context
insert into utrtest values (2, 'bar')
returning *, tableoid::regclass;
a | b | tableoid
---+-----+----------
2 | bar | utr2
(1 row)
update utrtest set b = b || b from (values (1), (2)) s(x) where a = s.x
returning *, tableoid::regclass, cmin;
a | b | x | tableoid | cmin
---+--------+---+----------+------
1 | foofoo | 1 | utr1 | 0
2 | barbar | 2 | utr2 | 0
(2 rows)
update utrtest set a = 3 - a from (values (1), (2)) s(x) where a = s.x
returning *, tableoid::regclass, cmin; -- fails
ERROR: cannot retrieve a system column in this context
update utrtest set a = 3 - a from (values (1), (2)) s(x) where a = s.x
returning *, tableoid::regclass;
a | b | x | tableoid
---+--------+---+----------
2 | foofoo | 1 | utr2
1 | barbar | 2 | utr1
(2 rows)
delete from utrtest
returning *, tableoid::regclass, cmin;
a | b | tableoid | cmin
---+--------+----------+------
1 | barbar | utr1 | 0
2 | foofoo | utr2 | 0
(2 rows)
drop table utrtest;
--------------
-- Some more update-partition-key test scenarios below. This time use list
-- partitions.

View File

@ -529,6 +529,38 @@ UPDATE list_default set a = 'x' WHERE a = 'd';
DROP TABLE list_parted;
-- Test retrieval of system columns with non-consistent partition row types.
-- This is only partially supported, as seen in the results.
create table utrtest (a int, b text) partition by list (a);
create table utr1 (a int check (a in (1)), q text, b text);
create table utr2 (a int check (a in (2)), b text);
alter table utr1 drop column q;
alter table utrtest attach partition utr1 for values in (1);
alter table utrtest attach partition utr2 for values in (2);
insert into utrtest values (1, 'foo')
returning *, tableoid::regclass, cmin;
insert into utrtest values (2, 'bar')
returning *, tableoid::regclass, cmin; -- fails
insert into utrtest values (2, 'bar')
returning *, tableoid::regclass;
update utrtest set b = b || b from (values (1), (2)) s(x) where a = s.x
returning *, tableoid::regclass, cmin;
update utrtest set a = 3 - a from (values (1), (2)) s(x) where a = s.x
returning *, tableoid::regclass, cmin; -- fails
update utrtest set a = 3 - a from (values (1), (2)) s(x) where a = s.x
returning *, tableoid::regclass;
delete from utrtest
returning *, tableoid::regclass, cmin;
drop table utrtest;
--------------
-- Some more update-partition-key test scenarios below. This time use list
-- partitions.