2004-06-06 23:20:46 +02:00
--
-- ROWTYPES
--
-- Make both a standalone composite type and a table rowtype
create type complex as (r float8, i float8);
create temp table fullname (first text, last text);
-- Nested composite
create type quad as (c1 complex, c2 complex);
-- Some simple tests of I/O conversions and row construction
select (1.1,2.2)::complex, row((3.3,4.4),(5.5,null))::quad;
row | row
-----------+------------------------
(1.1,2.2) | ("(3.3,4.4)","(5.5,)")
(1 row)
select row('Joe', 'Blow')::fullname, '(Joe,Blow)'::fullname;
row | fullname
------------+------------
(Joe,Blow) | (Joe,Blow)
(1 row)
select '(Joe,von Blow)'::fullname, '(Joe,d''Blow)'::fullname;
fullname | fullname
------------------+--------------
(Joe,"von Blow") | (Joe,d'Blow)
(1 row)
2005-06-26 05:04:37 +02:00
select '(Joe,"von""Blow")'::fullname, E'(Joe,d\\\\Blow)'::fullname;
2004-06-06 23:20:46 +02:00
fullname | fullname
-------------------+-----------------
(Joe,"von""Blow") | (Joe,"d\\Blow")
(1 row)
select '(Joe,"Blow,Jr")'::fullname;
fullname
-----------------
(Joe,"Blow,Jr")
(1 row)
select '(Joe,)'::fullname; -- ok, null 2nd column
fullname
----------
(Joe,)
(1 row)
select '(Joe)'::fullname; -- bad
ERROR: malformed record literal: "(Joe)"
2008-09-01 22:42:46 +02:00
LINE 1: select '(Joe)'::fullname;
^
2004-06-06 23:20:46 +02:00
DETAIL: Too few columns.
select '(Joe,,)'::fullname; -- bad
ERROR: malformed record literal: "(Joe,,)"
2008-09-01 22:42:46 +02:00
LINE 1: select '(Joe,,)'::fullname;
^
2004-06-06 23:20:46 +02:00
DETAIL: Too many columns.
2018-01-23 16:13:45 +01:00
select '[]'::fullname; -- bad
ERROR: malformed record literal: "[]"
LINE 1: select '[]'::fullname;
^
DETAIL: Missing left parenthesis.
select ' (Joe,Blow) '::fullname; -- ok, extra whitespace
fullname
------------
(Joe,Blow)
(1 row)
select '(Joe,Blow) /'::fullname; -- bad
ERROR: malformed record literal: "(Joe,Blow) /"
LINE 1: select '(Joe,Blow) /'::fullname;
^
DETAIL: Junk after right parenthesis.
Convert a few datatype input functions to use "soft" error reporting.
This patch converts the input functions for bool, int2, int4, int8,
float4, float8, numeric, and contrib/cube to the new soft-error style.
array_in and record_in are also converted. There's lots more to do,
but this is enough to provide proof-of-concept that the soft-error
API is usable, as well as reference examples for how to convert
input functions.
This patch is mostly by me, but it owes very substantial debt to
earlier work by Nikita Glukhov, Andrew Dunstan, and Amul Sul.
Thanks to Andres Freund for review.
Discussion: https://postgr.es/m/3bbbb0df-7382-bf87-9737-340ba096e034@postgrespro.ru
2022-12-09 16:14:53 +01:00
-- test non-error-throwing API
SELECT pg_input_is_valid('(1,2)', 'complex');
pg_input_is_valid
-------------------
t
(1 row)
SELECT pg_input_is_valid('(1,2', 'complex');
pg_input_is_valid
-------------------
f
(1 row)
SELECT pg_input_is_valid('(1,zed)', 'complex');
pg_input_is_valid
-------------------
f
(1 row)
2023-02-28 00:04:13 +01:00
SELECT * FROM pg_input_error_info('(1,zed)', 'complex');
message | detail | hint | sql_error_code
-------------------------------------------------------+--------+------+----------------
invalid input syntax for type double precision: "zed" | | | 22P02
Convert a few datatype input functions to use "soft" error reporting.
This patch converts the input functions for bool, int2, int4, int8,
float4, float8, numeric, and contrib/cube to the new soft-error style.
array_in and record_in are also converted. There's lots more to do,
but this is enough to provide proof-of-concept that the soft-error
API is usable, as well as reference examples for how to convert
input functions.
This patch is mostly by me, but it owes very substantial debt to
earlier work by Nikita Glukhov, Andrew Dunstan, and Amul Sul.
Thanks to Andres Freund for review.
Discussion: https://postgr.es/m/3bbbb0df-7382-bf87-9737-340ba096e034@postgrespro.ru
2022-12-09 16:14:53 +01:00
(1 row)
2023-02-28 00:04:13 +01:00
SELECT * FROM pg_input_error_info('(1,1e400)', 'complex');
message | detail | hint | sql_error_code
---------------------------------------------------+--------+------+----------------
"1e400" is out of range for type double precision | | | 22003
Convert a few datatype input functions to use "soft" error reporting.
This patch converts the input functions for bool, int2, int4, int8,
float4, float8, numeric, and contrib/cube to the new soft-error style.
array_in and record_in are also converted. There's lots more to do,
but this is enough to provide proof-of-concept that the soft-error
API is usable, as well as reference examples for how to convert
input functions.
This patch is mostly by me, but it owes very substantial debt to
earlier work by Nikita Glukhov, Andrew Dunstan, and Amul Sul.
Thanks to Andres Freund for review.
Discussion: https://postgr.es/m/3bbbb0df-7382-bf87-9737-340ba096e034@postgrespro.ru
2022-12-09 16:14:53 +01:00
(1 row)
2004-06-06 23:20:46 +02:00
create temp table quadtable(f1 int, q quad);
insert into quadtable values (1, ((3.3,4.4),(5.5,6.6)));
insert into quadtable values (2, ((null,4.4),(5.5,6.6)));
select * from quadtable;
f1 | q
----+---------------------------
1 | ("(3.3,4.4)","(5.5,6.6)")
2 | ("(,4.4)","(5.5,6.6)")
(2 rows)
select f1, q.c1 from quadtable; -- fails, q is a table reference
2006-01-10 22:59:59 +01:00
ERROR: missing FROM-clause entry for table "q"
2006-03-14 23:48:25 +01:00
LINE 1: select f1, q.c1 from quadtable;
^
2004-06-06 23:20:46 +02:00
select f1, (q).c1, (qq.q).c1.i from quadtable qq;
f1 | c1 | i
----+-----------+-----
1 | (3.3,4.4) | 4.4
2 | (,4.4) | 4.4
(2 rows)
create temp table people (fn fullname, bd date);
insert into people values ('(Joe,Blow)', '1984-01-10');
select * from people;
fn | bd
------------+------------
(Joe,Blow) | 01-10-1984
(1 row)
2011-01-27 14:35:34 +01:00
-- at the moment this will not work due to ALTER TABLE inadequacy:
2004-06-06 23:20:46 +02:00
alter table fullname add column suffix text default '';
2011-06-21 23:33:20 +02:00
ERROR: cannot alter table "fullname" because column "people.fn" uses its row type
2011-01-27 14:35:34 +01:00
-- but this should work:
alter table fullname add column suffix text default null;
2004-06-06 23:20:46 +02:00
select * from people;
fn | bd
-------------+------------
(Joe,Blow,) | 01-10-1984
(1 row)
2004-06-09 21:08:20 +02:00
-- test insertion/updating of subfields
2004-06-06 23:20:46 +02:00
update people set fn.suffix = 'Jr';
select * from people;
2004-06-09 21:08:20 +02:00
fn | bd
---------------+------------
(Joe,Blow,Jr) | 01-10-1984
2004-06-06 23:20:46 +02:00
(1 row)
2004-06-09 21:08:20 +02:00
insert into quadtable (f1, q.c1.r, q.c2.i) values(44,55,66);
2020-12-07 17:10:21 +01:00
update quadtable set q.c1.r = 12 where f1 = 2;
update quadtable set q.c1 = 12; -- error, type mismatch
ERROR: subfield "c1" is of type complex but expression is of type integer
LINE 1: update quadtable set q.c1 = 12;
^
HINT: You will need to rewrite or cast the expression.
2004-06-09 21:08:20 +02:00
select * from quadtable;
f1 | q
----+---------------------------
1 | ("(3.3,4.4)","(5.5,6.6)")
44 | ("(55,)","(,66)")
2020-12-07 17:10:21 +01:00
2 | ("(12,4.4)","(5.5,6.6)")
2004-06-09 21:08:20 +02:00
(3 rows)
2004-06-06 23:20:46 +02:00
-- The object here is to ensure that toasted references inside
-- composite values don't cause problems. The large f1 value will
-- be toasted inside pp, it must still work after being copied to people.
create temp table pp (f1 text);
insert into pp values (repeat('abcdefghijkl', 100000));
insert into people select ('Jim', f1, null)::fullname, current_date from pp;
select (fn).first, substr((fn).last, 1, 20), length((fn).last) from people;
first | substr | length
-------+----------------------+---------
Joe | Blow | 4
Jim | abcdefghijklabcdefgh | 1200000
(2 rows)
2023-06-29 16:19:10 +02:00
-- try an update on a toasted composite value, too
update people set fn.first = 'Jack';
select (fn).first, substr((fn).last, 1, 20), length((fn).last) from people;
first | substr | length
-------+----------------------+---------
Jack | Blow | 4
Jack | abcdefghijklabcdefgh | 1200000
(2 rows)
2005-12-28 02:30:02 +01:00
-- Test row comparison semantics. Prior to PG 8.2 we did this in a totally
-- non-spec-compliant way.
select ROW(1,2) < ROW(1,3) as true;
true
------
t
(1 row)
select ROW(1,2) < ROW(1,1) as false;
false
-------
f
(1 row)
select ROW(1,2) < ROW(1,NULL) as null;
null
------
(1 row)
select ROW(1,2,3) < ROW(1,3,NULL) as true; -- the NULL is not examined
true
------
t
(1 row)
select ROW(11,'ABC') < ROW(11,'DEF') as true;
true
------
t
(1 row)
select ROW(11,'ABC') > ROW(11,'DEF') as false;
false
-------
f
(1 row)
select ROW(12,'ABC') > ROW(11,'DEF') as true;
true
------
t
(1 row)
-- = and <> have different NULL-behavior than < etc
select ROW(1,2,3) < ROW(1,NULL,4) as null;
null
------
(1 row)
select ROW(1,2,3) = ROW(1,NULL,4) as false;
false
-------
f
(1 row)
select ROW(1,2,3) <> ROW(1,NULL,4) as true;
true
------
t
(1 row)
-- We allow operators beyond the six standard ones, if they have btree
-- operator classes.
select ROW('ABC','DEF') ~<=~ ROW('DEF','ABC') as true;
true
------
t
(1 row)
select ROW('ABC','DEF') ~>=~ ROW('DEF','ABC') as false;
false
-------
f
(1 row)
select ROW('ABC','DEF') ~~ ROW('DEF','ABC') as fail;
ERROR: could not determine interpretation of row comparison operator ~~
2006-03-14 23:48:25 +01:00
LINE 1: select ROW('ABC','DEF') ~~ ROW('DEF','ABC') as fail;
^
2006-12-23 01:43:13 +01:00
HINT: Row comparison operators must be associated with btree operator families.
2013-06-10 00:39:20 +02:00
-- Comparisons of ROW() expressions can cope with some type mismatches
select ROW(1,2) = ROW(1,2::int8);
?column?
----------
t
(1 row)
select ROW(1,2) in (ROW(3,4), ROW(1,2));
?column?
----------
t
(1 row)
select ROW(1,2) in (ROW(3,4), ROW(1,2::int8));
?column?
----------
t
(1 row)
2005-12-28 02:30:02 +01:00
-- Check row comparison with a subselect
select unique1, unique2 from tenk1
2007-02-09 21:17:59 +01:00
where (unique1, unique2) < any (select ten, ten from tenk1 where hundred < 3)
2007-02-10 05:18:32 +01:00
and unique1 <= 20
order by 1;
2005-12-28 02:30:02 +01:00
unique1 | unique2
---------+---------
0 | 9998
2007-02-10 05:18:32 +01:00
1 | 2838
2005-12-28 02:30:02 +01:00
(2 rows)
-- Also check row comparison with an indexable condition
2013-06-10 00:39:20 +02:00
explain (costs off)
select thousand, tenthous from tenk1
where (thousand, tenthous) >= (997, 5000)
order by thousand, tenthous;
QUERY PLAN
-----------------------------------------------------------
Index Only Scan using tenk1_thous_tenthous on tenk1
Index Cond: (ROW(thousand, tenthous) >= ROW(997, 5000))
(2 rows)
2005-12-28 02:30:02 +01:00
select thousand, tenthous from tenk1
where (thousand, tenthous) >= (997, 5000)
order by thousand, tenthous;
thousand | tenthous
----------+----------
997 | 5997
997 | 6997
997 | 7997
997 | 8997
997 | 9997
998 | 998
998 | 1998
998 | 2998
998 | 3998
998 | 4998
998 | 5998
998 | 6998
998 | 7998
998 | 8998
998 | 9998
999 | 999
999 | 1999
999 | 2999
999 | 3999
999 | 4999
999 | 5999
999 | 6999
999 | 7999
999 | 8999
999 | 9999
(25 rows)
2019-02-12 03:26:08 +01:00
explain (costs off)
select thousand, tenthous, four from tenk1
where (thousand, tenthous, four) > (998, 5000, 3)
order by thousand, tenthous;
QUERY PLAN
-----------------------------------------------------------------------
Sort
Sort Key: thousand, tenthous
-> Bitmap Heap Scan on tenk1
Filter: (ROW(thousand, tenthous, four) > ROW(998, 5000, 3))
-> Bitmap Index Scan on tenk1_thous_tenthous
Index Cond: (ROW(thousand, tenthous) >= ROW(998, 5000))
(6 rows)
select thousand, tenthous, four from tenk1
where (thousand, tenthous, four) > (998, 5000, 3)
order by thousand, tenthous;
thousand | tenthous | four
----------+----------+------
998 | 5998 | 2
998 | 6998 | 2
998 | 7998 | 2
998 | 8998 | 2
998 | 9998 | 2
999 | 999 | 3
999 | 1999 | 3
999 | 2999 | 3
999 | 3999 | 3
999 | 4999 | 3
999 | 5999 | 3
999 | 6999 | 3
999 | 7999 | 3
999 | 8999 | 3
999 | 9999 | 3
(15 rows)
explain (costs off)
select thousand, tenthous from tenk1
where (998, 5000) < (thousand, tenthous)
order by thousand, tenthous;
QUERY PLAN
----------------------------------------------------------
Index Only Scan using tenk1_thous_tenthous on tenk1
Index Cond: (ROW(thousand, tenthous) > ROW(998, 5000))
(2 rows)
select thousand, tenthous from tenk1
where (998, 5000) < (thousand, tenthous)
order by thousand, tenthous;
thousand | tenthous
----------+----------
998 | 5998
998 | 6998
998 | 7998
998 | 8998
998 | 9998
999 | 999
999 | 1999
999 | 2999
999 | 3999
999 | 4999
999 | 5999
999 | 6999
999 | 7999
999 | 8999
999 | 9999
(15 rows)
explain (costs off)
select thousand, hundred from tenk1
where (998, 5000) < (thousand, hundred)
order by thousand, hundred;
QUERY PLAN
-----------------------------------------------------------
Sort
Sort Key: thousand, hundred
-> Bitmap Heap Scan on tenk1
Filter: (ROW(998, 5000) < ROW(thousand, hundred))
-> Bitmap Index Scan on tenk1_thous_tenthous
Index Cond: (thousand >= 998)
(6 rows)
select thousand, hundred from tenk1
where (998, 5000) < (thousand, hundred)
order by thousand, hundred;
thousand | hundred
----------+---------
999 | 99
999 | 99
999 | 99
999 | 99
999 | 99
999 | 99
999 | 99
999 | 99
999 | 99
999 | 99
(10 rows)
Fix incorrect handling of NULL index entries in indexed ROW() comparisons.
An index search using a row comparison such as ROW(a, b) > ROW('x', 'y')
would stop upon reaching a NULL entry in the "b" column, ignoring the
fact that there might be non-NULL "b" values associated with later values
of "a". This happens because _bt_mark_scankey_required() marks the
subsidiary scankey for "b" as required, which is just wrong: it's for
a column after the one with the first inequality key (namely "a"), and
thus can't be considered a required match.
This bit of brain fade dates back to the very beginnings of our support
for indexed ROW() comparisons, in 2006. Kind of astonishing that no one
came across it before Glen Takahashi, in bug #14010.
Back-patch to all supported versions.
Note: the given test case doesn't actually fail in unpatched 9.1, evidently
because the fix for bug #6278 (i.e., stopping at nulls in either scan
direction) is required to make it fail. I'm sure I could devise a case
that fails in 9.1 as well, perhaps with something involving making a cursor
back up; but it doesn't seem worth the trouble.
2016-03-09 20:51:01 +01:00
-- Test case for bug #14010: indexed row comparisons fail with nulls
create temp table test_table (a text, b text);
insert into test_table values ('a', 'b');
insert into test_table select 'a', null from generate_series(1,1000);
insert into test_table values ('b', 'a');
create index on test_table (a,b);
set enable_sort = off;
explain (costs off)
select a,b from test_table where (a,b) > ('a','a') order by a,b;
QUERY PLAN
--------------------------------------------------------
Index Only Scan using test_table_a_b_idx on test_table
Index Cond: (ROW(a, b) > ROW('a'::text, 'a'::text))
(2 rows)
select a,b from test_table where (a,b) > ('a','a') order by a,b;
a | b
---+---
a | b
b | a
(2 rows)
reset enable_sort;
2013-06-10 00:39:20 +02:00
-- Check row comparisons with IN
select * from int8_tbl i8 where i8 in (row(123,456)); -- fail, type mismatch
ERROR: cannot compare dissimilar column types bigint and integer at record column 1
explain (costs off)
select * from int8_tbl i8
where i8 in (row(123,456)::int8_tbl, '(4567890123456789,123)');
2018-01-03 18:35:09 +01:00
QUERY PLAN
-------------------------------------------------------------------------------
2013-06-10 00:39:20 +02:00
Seq Scan on int8_tbl i8
2018-01-03 18:35:09 +01:00
Filter: (i8.* = ANY ('{"(123,456)","(4567890123456789,123)"}'::int8_tbl[]))
2013-06-10 00:39:20 +02:00
(2 rows)
select * from int8_tbl i8
where i8 in (row(123,456)::int8_tbl, '(4567890123456789,123)');
q1 | q2
------------------+-----
123 | 456
4567890123456789 | 123
(2 rows)
2019-10-28 20:08:24 +01:00
-- Check ability to select columns from an anonymous rowtype
select (row(1, 2.0)).f1;
f1
----
1
(1 row)
select (row(1, 2.0)).f2;
f2
-----
2.0
(1 row)
select (row(1, 2.0)).nosuch; -- fail
ERROR: could not identify column "nosuch" in record data type
LINE 1: select (row(1, 2.0)).nosuch;
^
select (row(1, 2.0)).*;
f1 | f2
----+-----
1 | 2.0
(1 row)
select (r).f1 from (select row(1, 2.0) as r) ss;
f1
----
1
(1 row)
select (r).f3 from (select row(1, 2.0) as r) ss; -- fail
ERROR: could not identify column "f3" in record data type
LINE 1: select (r).f3 from (select row(1, 2.0) as r) ss;
^
select (r).* from (select row(1, 2.0) as r) ss;
f1 | f2
----+-----
1 | 2.0
(1 row)
2007-04-06 06:21:44 +02:00
-- Check some corner cases involving empty rowtypes
select ROW();
row
-----
()
(1 row)
select ROW() IS NULL;
?column?
----------
t
(1 row)
select ROW() = ROW();
ERROR: cannot compare rows of zero length
LINE 1: select ROW() = ROW();
^
2008-10-13 18:25:20 +02:00
-- Check ability to create arrays of anonymous rowtypes
select array[ row(1,2), row(3,4), row(5,6) ];
array
---------------------------
{"(1,2)","(3,4)","(5,6)"}
(1 row)
-- Check ability to compare an anonymous row to elements of an array
select row(1,1.1) = any (array[ row(7,7.7), row(1,1.1), row(0,0.0) ]);
?column?
----------
t
(1 row)
select row(1,1.1) = any (array[ row(7,7.7), row(1,1.0), row(0,0.0) ]);
?column?
----------
f
(1 row)
2011-06-03 21:38:12 +02:00
-- Check behavior with a non-comparable rowtype
create type cantcompare as (p point, r float8);
create temp table cc (f1 cantcompare);
insert into cc values('("(1,2)",3)');
insert into cc values('("(4,5)",6)');
select * from cc order by f1; -- fail, but should complain about cantcompare
ERROR: could not identify an ordering operator for type cantcompare
LINE 1: select * from cc order by f1;
^
HINT: Use an explicit ordering operator or modify the query.
2010-10-19 21:08:37 +02:00
--
2018-01-23 16:13:45 +01:00
-- Tests for record_{eq,cmp}
--
create type testtype1 as (a int, b int);
-- all true
select row(1, 2)::testtype1 < row(1, 3)::testtype1;
?column?
----------
t
(1 row)
select row(1, 2)::testtype1 <= row(1, 3)::testtype1;
?column?
----------
t
(1 row)
select row(1, 2)::testtype1 = row(1, 2)::testtype1;
?column?
----------
t
(1 row)
select row(1, 2)::testtype1 <> row(1, 3)::testtype1;
?column?
----------
t
(1 row)
select row(1, 3)::testtype1 >= row(1, 2)::testtype1;
?column?
----------
t
(1 row)
select row(1, 3)::testtype1 > row(1, 2)::testtype1;
?column?
----------
t
(1 row)
-- all false
select row(1, -2)::testtype1 < row(1, -3)::testtype1;
?column?
----------
f
(1 row)
select row(1, -2)::testtype1 <= row(1, -3)::testtype1;
?column?
----------
f
(1 row)
select row(1, -2)::testtype1 = row(1, -3)::testtype1;
?column?
----------
f
(1 row)
select row(1, -2)::testtype1 <> row(1, -2)::testtype1;
?column?
----------
f
(1 row)
select row(1, -3)::testtype1 >= row(1, -2)::testtype1;
?column?
----------
f
(1 row)
select row(1, -3)::testtype1 > row(1, -2)::testtype1;
?column?
----------
f
(1 row)
-- true, but see *< below
select row(1, -2)::testtype1 < row(1, 3)::testtype1;
?column?
----------
t
(1 row)
-- mismatches
create type testtype3 as (a int, b text);
select row(1, 2)::testtype1 < row(1, 'abc')::testtype3;
ERROR: cannot compare dissimilar column types integer and text at record column 2
select row(1, 2)::testtype1 <> row(1, 'abc')::testtype3;
ERROR: cannot compare dissimilar column types integer and text at record column 2
create type testtype5 as (a int);
select row(1, 2)::testtype1 < row(1)::testtype5;
ERROR: cannot compare record types with different numbers of columns
select row(1, 2)::testtype1 <> row(1)::testtype5;
ERROR: cannot compare record types with different numbers of columns
-- non-comparable types
create type testtype6 as (a int, b point);
select row(1, '(1,2)')::testtype6 < row(1, '(1,3)')::testtype6;
ERROR: could not identify a comparison function for type point
select row(1, '(1,2)')::testtype6 <> row(1, '(1,3)')::testtype6;
ERROR: could not identify an equality operator for type point
drop type testtype1, testtype3, testtype5, testtype6;
--
-- Tests for record_image_{eq,cmp}
--
create type testtype1 as (a int, b int);
-- all true
select row(1, 2)::testtype1 *< row(1, 3)::testtype1;
?column?
----------
t
(1 row)
select row(1, 2)::testtype1 *<= row(1, 3)::testtype1;
?column?
----------
t
(1 row)
select row(1, 2)::testtype1 *= row(1, 2)::testtype1;
?column?
----------
t
(1 row)
select row(1, 2)::testtype1 *<> row(1, 3)::testtype1;
?column?
----------
t
(1 row)
select row(1, 3)::testtype1 *>= row(1, 2)::testtype1;
?column?
----------
t
(1 row)
select row(1, 3)::testtype1 *> row(1, 2)::testtype1;
?column?
----------
t
(1 row)
-- all false
select row(1, -2)::testtype1 *< row(1, -3)::testtype1;
?column?
----------
f
(1 row)
select row(1, -2)::testtype1 *<= row(1, -3)::testtype1;
?column?
----------
f
(1 row)
select row(1, -2)::testtype1 *= row(1, -3)::testtype1;
?column?
----------
f
(1 row)
select row(1, -2)::testtype1 *<> row(1, -2)::testtype1;
?column?
----------
f
(1 row)
select row(1, -3)::testtype1 *>= row(1, -2)::testtype1;
?column?
----------
f
(1 row)
select row(1, -3)::testtype1 *> row(1, -2)::testtype1;
?column?
----------
f
(1 row)
-- This returns the "wrong" order because record_image_cmp works on
-- unsigned datums without knowing about the actual data type.
select row(1, -2)::testtype1 *< row(1, 3)::testtype1;
?column?
----------
f
(1 row)
-- other types
create type testtype2 as (a smallint, b bool); -- byval different sizes
select row(1, true)::testtype2 *< row(2, true)::testtype2;
?column?
----------
t
(1 row)
select row(-2, true)::testtype2 *< row(-1, true)::testtype2;
?column?
----------
t
(1 row)
select row(0, false)::testtype2 *< row(0, true)::testtype2;
?column?
----------
t
(1 row)
select row(0, false)::testtype2 *<> row(0, true)::testtype2;
?column?
----------
t
(1 row)
create type testtype3 as (a int, b text); -- variable length
select row(1, 'abc')::testtype3 *< row(1, 'abd')::testtype3;
?column?
----------
t
(1 row)
select row(1, 'abc')::testtype3 *< row(1, 'abcd')::testtype3;
?column?
----------
t
(1 row)
select row(1, 'abc')::testtype3 *> row(1, 'abd')::testtype3;
?column?
----------
f
(1 row)
select row(1, 'abc')::testtype3 *<> row(1, 'abd')::testtype3;
?column?
----------
t
(1 row)
create type testtype4 as (a int, b point); -- by ref, fixed length
select row(1, '(1,2)')::testtype4 *< row(1, '(1,3)')::testtype4;
?column?
----------
t
(1 row)
select row(1, '(1,2)')::testtype4 *<> row(1, '(1,3)')::testtype4;
?column?
----------
t
(1 row)
-- mismatches
select row(1, 2)::testtype1 *< row(1, 'abc')::testtype3;
ERROR: cannot compare dissimilar column types integer and text at record column 2
select row(1, 2)::testtype1 *<> row(1, 'abc')::testtype3;
ERROR: cannot compare dissimilar column types integer and text at record column 2
create type testtype5 as (a int);
select row(1, 2)::testtype1 *< row(1)::testtype5;
ERROR: cannot compare record types with different numbers of columns
select row(1, 2)::testtype1 *<> row(1)::testtype5;
ERROR: cannot compare record types with different numbers of columns
-- non-comparable types
create type testtype6 as (a int, b point);
select row(1, '(1,2)')::testtype6 *< row(1, '(1,3)')::testtype6;
?column?
----------
t
(1 row)
select row(1, '(1,2)')::testtype6 *>= row(1, '(1,3)')::testtype6;
?column?
----------
f
(1 row)
select row(1, '(1,2)')::testtype6 *<> row(1, '(1,3)')::testtype6;
?column?
----------
t
(1 row)
2019-01-31 01:25:33 +01:00
-- anonymous rowtypes in coldeflists
select q.a, q.b = row(2), q.c = array[row(3)], q.d = row(row(4)) from
unnest(array[row(1, row(2), array[row(3)], row(row(4))),
row(2, row(3), array[row(4)], row(row(5)))])
as q(a int, b record, c record[], d record);
a | ?column? | ?column? | ?column?
---+----------+----------+----------
1 | t | t | t
2 | f | f | f
(2 rows)
2018-01-23 16:13:45 +01:00
drop type testtype1, testtype2, testtype3, testtype4, testtype5, testtype6;
--
2010-10-19 21:08:37 +02:00
-- Test case derived from bug #5716: check multiple uses of a rowtype result
--
BEGIN;
CREATE TABLE price (
id SERIAL PRIMARY KEY,
active BOOLEAN NOT NULL,
price NUMERIC
);
CREATE TYPE price_input AS (
id INTEGER,
price NUMERIC
);
CREATE TYPE price_key AS (
id INTEGER
);
CREATE FUNCTION price_key_from_table(price) RETURNS price_key AS $$
SELECT $1.id
$$ LANGUAGE SQL;
CREATE FUNCTION price_key_from_input(price_input) RETURNS price_key AS $$
SELECT $1.id
$$ LANGUAGE SQL;
insert into price values (1,false,42), (10,false,100), (11,true,17.99);
UPDATE price
SET active = true, price = input_prices.price
FROM unnest(ARRAY[(10, 123.00), (11, 99.99)]::price_input[]) input_prices
WHERE price_key_from_table(price.*) = price_key_from_input(input_prices.*);
select * from price;
id | active | price
----+--------+--------
1 | f | 42
10 | t | 123.00
11 | t | 99.99
(3 rows)
rollback;
2014-02-03 20:46:51 +01:00
--
-- Test case derived from bug #9085: check * qualification of composite
-- parameters for SQL functions
--
create temp table compos (f1 int, f2 text);
create function fcompos1(v compos) returns void as $$
insert into compos values (v); -- fail
$$ language sql;
ERROR: column "f1" is of type integer but expression is of type compos
LINE 2: insert into compos values (v); -- fail
^
HINT: You will need to rewrite or cast the expression.
create function fcompos1(v compos) returns void as $$
insert into compos values (v.*);
$$ language sql;
create function fcompos2(v compos) returns void as $$
select fcompos1(v);
$$ language sql;
create function fcompos3(v compos) returns void as $$
select fcompos1(fcompos3.v.*);
$$ language sql;
select fcompos1(row(1,'one'));
fcompos1
----------
(1 row)
select fcompos2(row(2,'two'));
fcompos2
----------
(1 row)
select fcompos3(row(3,'three'));
fcompos3
----------
(1 row)
select * from compos;
f1 | f2
----+-------
1 | one
2 | two
3 | three
(3 rows)
2010-11-07 19:03:19 +01:00
--
-- We allow I/O conversion casts from composite types to strings to be
-- invoked via cast syntax, but not functional syntax. This is because
-- the latter is too prone to be invoked unintentionally.
--
select cast (fullname as text) from fullname;
fullname
----------
(0 rows)
select fullname::text from fullname;
fullname
----------
(0 rows)
select text(fullname) from fullname; -- error
ERROR: function text(fullname) does not exist
LINE 1: select text(fullname) from fullname;
^
HINT: No function matches the given name and argument types. You might need to add explicit type casts.
select fullname.text from fullname; -- error
ERROR: column fullname.text does not exist
LINE 1: select fullname.text from fullname;
^
-- same, but RECORD instead of named composite type:
select cast (row('Jim', 'Beam') as text);
row
------------
(Jim,Beam)
(1 row)
select (row('Jim', 'Beam'))::text;
row
------------
(Jim,Beam)
(1 row)
select text(row('Jim', 'Beam')); -- error
ERROR: function text(record) does not exist
LINE 1: select text(row('Jim', 'Beam'));
^
HINT: No function matches the given name and argument types. You might need to add explicit type casts.
select (row('Jim', 'Beam')).text; -- error
ERROR: could not identify column "text" in record data type
LINE 1: select (row('Jim', 'Beam')).text;
^
Consider syntactic form when disambiguating function vs column reference.
Postgres has traditionally considered the syntactic forms f(x) and x.f
to be equivalent, allowing tricks such as writing a function and then
using it as though it were a computed-on-demand column. However, our
behavior when both interpretations are feasible left something to be
desired: we always chose the column interpretation. This could lead
to very surprising results, as in a recent bug report from Neil Conway.
It also created a dump-and-reload hazard, since what was a function
call in a dumped view could get interpreted as a column reference
at reload, if a matching column name had been added to the underlying
table since the view was created.
What seems better, in ambiguous situations, is to prefer the choice
matching the syntactic form of the reference. This seems much less
astonishing in general, and it fixes the dump/reload hazard.
Although this could be called a bug fix, there have been few complaints
and there's some small risk of breaking applications that depend on the
old behavior, so no back-patch. It does seem reasonable to slip it
into v11, though.
Discussion: https://postgr.es/m/CAOW5sYa3Wp7KozCuzjOdw6PiOYPi6D=VvRybtH2S=2C0SVmRmA@mail.gmail.com
2018-06-18 17:39:33 +02:00
--
-- Check the equivalence of functional and column notation
--
insert into fullname values ('Joe', 'Blow');
select f.last from fullname f;
last
------
Blow
(1 row)
select last(f) from fullname f;
last
------
Blow
(1 row)
create function longname(fullname) returns text language sql
as $$select $1.first || ' ' || $1.last$$;
select f.longname from fullname f;
longname
----------
Joe Blow
(1 row)
select longname(f) from fullname f;
longname
----------
Joe Blow
(1 row)
-- Starting in v11, the notational form does matter if there's ambiguity
alter table fullname add column longname text;
select f.longname from fullname f;
longname
----------
(1 row)
select longname(f) from fullname f;
longname
----------
Joe Blow
(1 row)
Ensure that RowExprs and whole-row Vars produce the expected column names.
At one time it wasn't terribly important what column names were associated
with the fields of a composite Datum, but since the introduction of
operations like row_to_json(), it's important that looking up the rowtype
ID embedded in the Datum returns the column names that users would expect.
That did not work terribly well before this patch: you could get the column
names of the underlying table, or column aliases from any level of the
query, depending on minor details of the plan tree. You could even get
totally empty field names, which is disastrous for cases like row_to_json().
To fix this for whole-row Vars, look to the RTE referenced by the Var, and
make sure its column aliases are applied to the rowtype associated with
the result Datums. This is a tad scary because we might have to return
a transient RECORD type even though the Var is declared as having some
named rowtype. In principle it should be all right because the record
type will still be physically compatible with the named rowtype; but
I had to weaken one Assert in ExecEvalConvertRowtype, and there might be
third-party code containing similar assumptions.
Similarly, RowExprs have to be willing to override the column names coming
from a named composite result type and produce a RECORD when the column
aliases visible at the site of the RowExpr differ from the underlying
table's column names.
In passing, revert the decision made in commit 398f70ec070fe601 to add
an alias-list argument to ExecTypeFromExprList: better to provide that
functionality in a separate function. This also reverts most of the code
changes in d68581483564ec0f, which we don't need because we're no longer
depending on the tupdesc found in the child plan node's result slot to be
blessed.
Back-patch to 9.4, but not earlier, since this solution changes the results
in some cases that users might not have realized were buggy. We'll apply a
more restricted form of this patch in older branches.
2014-11-10 21:21:09 +01:00
--
-- Test that composite values are seen to have the correct column names
-- (bug #11210 and other reports)
--
select row_to_json(i) from int8_tbl i;
row_to_json
------------------------------------------------
{"q1":123,"q2":456}
{"q1":123,"q2":4567890123456789}
{"q1":4567890123456789,"q2":123}
{"q1":4567890123456789,"q2":4567890123456789}
{"q1":4567890123456789,"q2":-4567890123456789}
(5 rows)
Revert applying column aliases to the output of whole-row Vars.
In commit bf7ca1587, I had the bright idea that we could make the
result of a whole-row Var (that is, foo.*) track any column aliases
that had been applied to the FROM entry the Var refers to. However,
that's not terribly logically consistent, because now the output of
the Var is no longer of the named composite type that the Var claims
to emit. bf7ca1587 tried to handle that by changing the output
tuple values to be labeled with a blessed RECORD type, but that's
really pretty disastrous: we can wind up storing such tuples onto
disk, whereupon they're not readable by other sessions.
The only practical fix I can see is to give up on what bf7ca1587
tried to do, and say that the column names of tuples produced by
a whole-row Var are always those of the underlying named composite
type, query aliases or no. While this introduces some inconsistencies,
it removes others, so it's not that awful in the abstract. What *is*
kind of awful is to make such a behavioral change in a back-patched
bug fix. But corrupt data is worse, so back-patched it will be.
(A workaround available to anyone who's unhappy about this is to
introduce an extra level of sub-SELECT, so that the whole-row Var is
referring to the sub-SELECT's output and not to a named table type.
Then the Var is of type RECORD to begin with and there's no issue.)
Per report from Miles Delahunty. The faulty commit dates to 9.5,
so back-patch to all supported branches.
Discussion: https://postgr.es/m/2950001.1638729947@sss.pgh.pa.us
2022-03-17 23:18:05 +01:00
-- since "i" is of type "int8_tbl", attaching aliases doesn't change anything:
Ensure that RowExprs and whole-row Vars produce the expected column names.
At one time it wasn't terribly important what column names were associated
with the fields of a composite Datum, but since the introduction of
operations like row_to_json(), it's important that looking up the rowtype
ID embedded in the Datum returns the column names that users would expect.
That did not work terribly well before this patch: you could get the column
names of the underlying table, or column aliases from any level of the
query, depending on minor details of the plan tree. You could even get
totally empty field names, which is disastrous for cases like row_to_json().
To fix this for whole-row Vars, look to the RTE referenced by the Var, and
make sure its column aliases are applied to the rowtype associated with
the result Datums. This is a tad scary because we might have to return
a transient RECORD type even though the Var is declared as having some
named rowtype. In principle it should be all right because the record
type will still be physically compatible with the named rowtype; but
I had to weaken one Assert in ExecEvalConvertRowtype, and there might be
third-party code containing similar assumptions.
Similarly, RowExprs have to be willing to override the column names coming
from a named composite result type and produce a RECORD when the column
aliases visible at the site of the RowExpr differ from the underlying
table's column names.
In passing, revert the decision made in commit 398f70ec070fe601 to add
an alias-list argument to ExecTypeFromExprList: better to provide that
functionality in a separate function. This also reverts most of the code
changes in d68581483564ec0f, which we don't need because we're no longer
depending on the tupdesc found in the child plan node's result slot to be
blessed.
Back-patch to 9.4, but not earlier, since this solution changes the results
in some cases that users might not have realized were buggy. We'll apply a
more restricted form of this patch in older branches.
2014-11-10 21:21:09 +01:00
select row_to_json(i) from int8_tbl i(x,y);
row_to_json
------------------------------------------------
{"q1":123,"q2":456}
{"q1":123,"q2":4567890123456789}
{"q1":4567890123456789,"q2":123}
{"q1":4567890123456789,"q2":4567890123456789}
{"q1":4567890123456789,"q2":-4567890123456789}
(5 rows)
Revert applying column aliases to the output of whole-row Vars.
In commit bf7ca1587, I had the bright idea that we could make the
result of a whole-row Var (that is, foo.*) track any column aliases
that had been applied to the FROM entry the Var refers to. However,
that's not terribly logically consistent, because now the output of
the Var is no longer of the named composite type that the Var claims
to emit. bf7ca1587 tried to handle that by changing the output
tuple values to be labeled with a blessed RECORD type, but that's
really pretty disastrous: we can wind up storing such tuples onto
disk, whereupon they're not readable by other sessions.
The only practical fix I can see is to give up on what bf7ca1587
tried to do, and say that the column names of tuples produced by
a whole-row Var are always those of the underlying named composite
type, query aliases or no. While this introduces some inconsistencies,
it removes others, so it's not that awful in the abstract. What *is*
kind of awful is to make such a behavioral change in a back-patched
bug fix. But corrupt data is worse, so back-patched it will be.
(A workaround available to anyone who's unhappy about this is to
introduce an extra level of sub-SELECT, so that the whole-row Var is
referring to the sub-SELECT's output and not to a named table type.
Then the Var is of type RECORD to begin with and there's no issue.)
Per report from Miles Delahunty. The faulty commit dates to 9.5,
so back-patch to all supported branches.
Discussion: https://postgr.es/m/2950001.1638729947@sss.pgh.pa.us
2022-03-17 23:18:05 +01:00
-- in these examples, we'll report the exposed column names of the subselect:
Ensure that RowExprs and whole-row Vars produce the expected column names.
At one time it wasn't terribly important what column names were associated
with the fields of a composite Datum, but since the introduction of
operations like row_to_json(), it's important that looking up the rowtype
ID embedded in the Datum returns the column names that users would expect.
That did not work terribly well before this patch: you could get the column
names of the underlying table, or column aliases from any level of the
query, depending on minor details of the plan tree. You could even get
totally empty field names, which is disastrous for cases like row_to_json().
To fix this for whole-row Vars, look to the RTE referenced by the Var, and
make sure its column aliases are applied to the rowtype associated with
the result Datums. This is a tad scary because we might have to return
a transient RECORD type even though the Var is declared as having some
named rowtype. In principle it should be all right because the record
type will still be physically compatible with the named rowtype; but
I had to weaken one Assert in ExecEvalConvertRowtype, and there might be
third-party code containing similar assumptions.
Similarly, RowExprs have to be willing to override the column names coming
from a named composite result type and produce a RECORD when the column
aliases visible at the site of the RowExpr differ from the underlying
table's column names.
In passing, revert the decision made in commit 398f70ec070fe601 to add
an alias-list argument to ExecTypeFromExprList: better to provide that
functionality in a separate function. This also reverts most of the code
changes in d68581483564ec0f, which we don't need because we're no longer
depending on the tupdesc found in the child plan node's result slot to be
blessed.
Back-patch to 9.4, but not earlier, since this solution changes the results
in some cases that users might not have realized were buggy. We'll apply a
more restricted form of this patch in older branches.
2014-11-10 21:21:09 +01:00
select row_to_json(ss) from
(select q1, q2 from int8_tbl) as ss;
row_to_json
------------------------------------------------
{"q1":123,"q2":456}
{"q1":123,"q2":4567890123456789}
{"q1":4567890123456789,"q2":123}
{"q1":4567890123456789,"q2":4567890123456789}
{"q1":4567890123456789,"q2":-4567890123456789}
(5 rows)
select row_to_json(ss) from
(select q1, q2 from int8_tbl offset 0) as ss;
row_to_json
------------------------------------------------
{"q1":123,"q2":456}
{"q1":123,"q2":4567890123456789}
{"q1":4567890123456789,"q2":123}
{"q1":4567890123456789,"q2":4567890123456789}
{"q1":4567890123456789,"q2":-4567890123456789}
(5 rows)
select row_to_json(ss) from
(select q1 as a, q2 as b from int8_tbl) as ss;
row_to_json
----------------------------------------------
{"a":123,"b":456}
{"a":123,"b":4567890123456789}
{"a":4567890123456789,"b":123}
{"a":4567890123456789,"b":4567890123456789}
{"a":4567890123456789,"b":-4567890123456789}
(5 rows)
select row_to_json(ss) from
(select q1 as a, q2 as b from int8_tbl offset 0) as ss;
row_to_json
----------------------------------------------
{"a":123,"b":456}
{"a":123,"b":4567890123456789}
{"a":4567890123456789,"b":123}
{"a":4567890123456789,"b":4567890123456789}
{"a":4567890123456789,"b":-4567890123456789}
(5 rows)
select row_to_json(ss) from
(select q1 as a, q2 as b from int8_tbl) as ss(x,y);
row_to_json
----------------------------------------------
{"x":123,"y":456}
{"x":123,"y":4567890123456789}
{"x":4567890123456789,"y":123}
{"x":4567890123456789,"y":4567890123456789}
{"x":4567890123456789,"y":-4567890123456789}
(5 rows)
select row_to_json(ss) from
(select q1 as a, q2 as b from int8_tbl offset 0) as ss(x,y);
row_to_json
----------------------------------------------
{"x":123,"y":456}
{"x":123,"y":4567890123456789}
{"x":4567890123456789,"y":123}
{"x":4567890123456789,"y":4567890123456789}
{"x":4567890123456789,"y":-4567890123456789}
(5 rows)
explain (costs off)
select row_to_json(q) from
(select thousand, tenthous from tenk1
where thousand = 42 and tenthous < 2000 offset 0) q;
QUERY PLAN
-------------------------------------------------------------
Subquery Scan on q
-> Index Only Scan using tenk1_thous_tenthous on tenk1
Index Cond: ((thousand = 42) AND (tenthous < 2000))
(3 rows)
select row_to_json(q) from
(select thousand, tenthous from tenk1
where thousand = 42 and tenthous < 2000 offset 0) q;
row_to_json
---------------------------------
{"thousand":42,"tenthous":42}
{"thousand":42,"tenthous":1042}
(2 rows)
select row_to_json(q) from
(select thousand as x, tenthous as y from tenk1
where thousand = 42 and tenthous < 2000 offset 0) q;
row_to_json
-------------------
{"x":42,"y":42}
{"x":42,"y":1042}
(2 rows)
select row_to_json(q) from
(select thousand as x, tenthous as y from tenk1
where thousand = 42 and tenthous < 2000 offset 0) q(a,b);
row_to_json
-------------------
{"a":42,"b":42}
{"a":42,"b":1042}
(2 rows)
create temp table tt1 as select * from int8_tbl limit 2;
create temp table tt2 () inherits(tt1);
insert into tt2 values(0,0);
select row_to_json(r) from (select q2,q1 from tt1 offset 0) r;
row_to_json
----------------------------------
{"q2":456,"q1":123}
{"q2":4567890123456789,"q1":123}
{"q2":0,"q1":0}
(3 rows)
Clean up after insufficiently-researched optimization of tuple conversions.
tupconvert.c's functions formerly considered that an explicit tuple
conversion was necessary if the input and output tupdescs contained
different type OIDs. The point of that was to make sure that a composite
datum resulting from the conversion would contain the destination rowtype
OID in its composite-datum header. However, commit 3838074f8 entirely
misunderstood what that check was for, thinking that it had something to do
with presence or absence of an OID column within the tuple. Removal of the
check broke the no-op conversion path in ExecEvalConvertRowtype, as
reported by Ashutosh Bapat.
It turns out that of the dozen or so call sites for tupconvert.c functions,
ExecEvalConvertRowtype is the only one that cares about the composite-datum
header fields in the output tuple. In all the rest, we'd much rather avoid
an unnecessary conversion whenever the tuples are physically compatible.
Moreover, the comments in tupconvert.c only promise physical compatibility
not a metadata match. So, let's accept the removal of the guarantee about
the output tuple's rowtype marking, recognizing that this is a API change
that could conceivably break third-party callers of tupconvert.c. (So,
let's remember to mention it in the v10 release notes.)
However, commit 3838074f8 did have a bit of a point here, in that two
tuples mustn't be considered physically compatible if one has HEAP_HASOID
set and the other doesn't. (Some of the callers of tupconvert.c might not
really care about that, but we can't assume it in general.) The previous
check accidentally covered that issue, because no RECORD types ever have
OIDs, while if two tupdescs have the same named composite type OID then,
a fortiori, they have the same tdhasoid setting. If we're removing the
type OID match check then we'd better include tdhasoid match as part of
the physical compatibility check.
Without that hack in tupconvert.c, we need ExecEvalConvertRowtype to take
responsibility for inserting the correct rowtype OID label whenever
tupconvert.c decides it need not do anything. This is easily done with
heap_copy_tuple_as_datum, which will be considerably faster than a tuple
disassembly and reassembly anyway; so from a performance standpoint this
change is a win all around compared to what happened in earlier branches.
It just means a couple more lines of code in ExecEvalConvertRowtype.
Ashutosh Bapat and Tom Lane
Discussion: https://postgr.es/m/CAFjFpRfvHABV6+oVvGcshF8rHn+1LfRUhj7Jz1CDZ4gPUwehBg@mail.gmail.com
2017-04-07 03:10:09 +02:00
-- check no-op rowtype conversions
create temp table tt3 () inherits(tt2);
insert into tt3 values(33,44);
select row_to_json(tt3::tt2::tt1) from tt3;
row_to_json
-------------------
{"q1":33,"q2":44}
(1 row)
2016-07-26 21:25:02 +02:00
--
-- IS [NOT] NULL should not recurse into nested composites (bug #14235)
--
explain (verbose, costs off)
select r, r is null as isnull, r is not null as isnotnull
from (values (1,row(1,2)), (1,row(null,null)), (1,null),
(null,row(1,2)), (null,row(null,null)), (null,null) ) r(a,b);
Fix assorted fallout from IS [NOT] NULL patch.
Commits 4452000f3 et al established semantics for NullTest.argisrow that
are a bit different from its initial conception: rather than being merely
a cache of whether we've determined the input to have composite type,
the flag now has the further meaning that we should apply field-by-field
testing as per the standard's definition of IS [NOT] NULL. If argisrow
is false and yet the input has composite type, the construct instead has
the semantics of IS [NOT] DISTINCT FROM NULL. Update the comments in
primnodes.h to clarify this, and fix ruleutils.c and deparse.c to print
such cases correctly. In the case of ruleutils.c, this merely results in
cosmetic changes in EXPLAIN output, since the case can't currently arise
in stored rules. However, it represents a live bug for deparse.c, which
would formerly have sent a remote query that had semantics different
from the local behavior. (From the user's standpoint, this means that
testing a remote nested-composite column for null-ness could have had
unexpected recursive behavior much like that fixed in 4452000f3.)
In a related but somewhat independent fix, make plancat.c set argisrow
to false in all NullTest expressions constructed to represent "attnotnull"
constructs. Since attnotnull is actually enforced as a simple null-value
check, this is a more accurate representation of the semantics; we were
previously overpromising what it meant for composite columns, which might
possibly lead to incorrect planner optimizations. (It seems that what the
SQL spec expects a NOT NULL constraint to mean is an IS NOT NULL test, so
arguably we are violating the spec and should fix attnotnull to do the
other thing. If we ever do, this part should get reverted.)
Back-patch, same as the previous commit.
Discussion: <10682.1469566308@sss.pgh.pa.us>
2016-07-28 22:09:15 +02:00
QUERY PLAN
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
2016-07-26 21:25:02 +02:00
Values Scan on "*VALUES*"
Fix assorted fallout from IS [NOT] NULL patch.
Commits 4452000f3 et al established semantics for NullTest.argisrow that
are a bit different from its initial conception: rather than being merely
a cache of whether we've determined the input to have composite type,
the flag now has the further meaning that we should apply field-by-field
testing as per the standard's definition of IS [NOT] NULL. If argisrow
is false and yet the input has composite type, the construct instead has
the semantics of IS [NOT] DISTINCT FROM NULL. Update the comments in
primnodes.h to clarify this, and fix ruleutils.c and deparse.c to print
such cases correctly. In the case of ruleutils.c, this merely results in
cosmetic changes in EXPLAIN output, since the case can't currently arise
in stored rules. However, it represents a live bug for deparse.c, which
would formerly have sent a remote query that had semantics different
from the local behavior. (From the user's standpoint, this means that
testing a remote nested-composite column for null-ness could have had
unexpected recursive behavior much like that fixed in 4452000f3.)
In a related but somewhat independent fix, make plancat.c set argisrow
to false in all NullTest expressions constructed to represent "attnotnull"
constructs. Since attnotnull is actually enforced as a simple null-value
check, this is a more accurate representation of the semantics; we were
previously overpromising what it meant for composite columns, which might
possibly lead to incorrect planner optimizations. (It seems that what the
SQL spec expects a NOT NULL constraint to mean is an IS NOT NULL test, so
arguably we are violating the spec and should fix attnotnull to do the
other thing. If we ever do, this part should get reverted.)
Back-patch, same as the previous commit.
Discussion: <10682.1469566308@sss.pgh.pa.us>
2016-07-28 22:09:15 +02:00
Output: ROW("*VALUES*".column1, "*VALUES*".column2), (("*VALUES*".column1 IS NULL) AND ("*VALUES*".column2 IS NOT DISTINCT FROM NULL)), (("*VALUES*".column1 IS NOT NULL) AND ("*VALUES*".column2 IS DISTINCT FROM NULL))
2016-07-26 21:25:02 +02:00
(2 rows)
select r, r is null as isnull, r is not null as isnotnull
from (values (1,row(1,2)), (1,row(null,null)), (1,null),
(null,row(1,2)), (null,row(null,null)), (null,null) ) r(a,b);
r | isnull | isnotnull
-------------+--------+-----------
(1,"(1,2)") | f | t
(1,"(,)") | f | t
(1,) | f | f
(,"(1,2)") | f | f
(,"(,)") | f | f
(,) | t | f
(6 rows)
explain (verbose, costs off)
Allow user control of CTE materialization, and change the default behavior.
Historically we've always materialized the full output of a CTE query,
treating WITH as an optimization fence (so that, for example, restrictions
from the outer query cannot be pushed into it). This is appropriate when
the CTE query is INSERT/UPDATE/DELETE, or is recursive; but when the CTE
query is non-recursive and side-effect-free, there's no hazard of changing
the query results by pushing restrictions down.
Another argument for materialization is that it can avoid duplicate
computation of an expensive WITH query --- but that only applies if
the WITH query is called more than once in the outer query. Even then
it could still be a net loss, if each call has restrictions that
would allow just a small part of the WITH query to be computed.
Hence, let's change the behavior for WITH queries that are non-recursive
and side-effect-free. By default, we will inline them into the outer
query (removing the optimization fence) if they are called just once.
If they are called more than once, we will keep the old behavior by
default, but the user can override this and force inlining by specifying
NOT MATERIALIZED. Lastly, the user can force the old behavior by
specifying MATERIALIZED; this would mainly be useful when the query had
deliberately been employing WITH as an optimization fence to prevent a
poor choice of plan.
Andreas Karlsson, Andrew Gierth, David Fetter
Discussion: https://postgr.es/m/87sh48ffhb.fsf@news-spur.riddles.org.uk
2019-02-16 22:11:12 +01:00
with r(a,b) as materialized
2016-07-26 21:25:02 +02:00
(values (1,row(1,2)), (1,row(null,null)), (1,null),
(null,row(1,2)), (null,row(null,null)), (null,null) )
select r, r is null as isnull, r is not null as isnotnull from r;
QUERY PLAN
----------------------------------------------------------
CTE Scan on r
Output: r.*, (r.* IS NULL), (r.* IS NOT NULL)
CTE r
-> Values Scan on "*VALUES*"
Output: "*VALUES*".column1, "*VALUES*".column2
(5 rows)
Allow user control of CTE materialization, and change the default behavior.
Historically we've always materialized the full output of a CTE query,
treating WITH as an optimization fence (so that, for example, restrictions
from the outer query cannot be pushed into it). This is appropriate when
the CTE query is INSERT/UPDATE/DELETE, or is recursive; but when the CTE
query is non-recursive and side-effect-free, there's no hazard of changing
the query results by pushing restrictions down.
Another argument for materialization is that it can avoid duplicate
computation of an expensive WITH query --- but that only applies if
the WITH query is called more than once in the outer query. Even then
it could still be a net loss, if each call has restrictions that
would allow just a small part of the WITH query to be computed.
Hence, let's change the behavior for WITH queries that are non-recursive
and side-effect-free. By default, we will inline them into the outer
query (removing the optimization fence) if they are called just once.
If they are called more than once, we will keep the old behavior by
default, but the user can override this and force inlining by specifying
NOT MATERIALIZED. Lastly, the user can force the old behavior by
specifying MATERIALIZED; this would mainly be useful when the query had
deliberately been employing WITH as an optimization fence to prevent a
poor choice of plan.
Andreas Karlsson, Andrew Gierth, David Fetter
Discussion: https://postgr.es/m/87sh48ffhb.fsf@news-spur.riddles.org.uk
2019-02-16 22:11:12 +01:00
with r(a,b) as materialized
2016-07-26 21:25:02 +02:00
(values (1,row(1,2)), (1,row(null,null)), (1,null),
(null,row(1,2)), (null,row(null,null)), (null,null) )
select r, r is null as isnull, r is not null as isnotnull from r;
r | isnull | isnotnull
-------------+--------+-----------
(1,"(1,2)") | f | t
(1,"(,)") | f | t
(1,) | f | f
(,"(1,2)") | f | f
(,"(,)") | f | f
(,) | t | f
(6 rows)
2023-09-15 23:01:26 +02:00
--
-- Check parsing of indirect references to composite values (bug #18077)
--
explain (verbose, costs off)
with cte(c) as materialized (select row(1, 2)),
cte2(c) as (select * from cte)
select * from cte2 as t
where (select * from (select c as c1) s
where (select (c1).f1 > 0)) is not null;
Improve EXPLAIN's display of SubPlan nodes and output parameters.
Historically we've printed SubPlan expression nodes as "(SubPlan N)",
which is pretty uninformative. Trying to reproduce the original SQL
for the subquery is still as impractical as before, and would be
mighty verbose as well. However, we can still do better than that.
Displaying the "testexpr" when present, and adding a keyword to
indicate the SubLinkType, goes a long way toward showing what's
really going on.
In addition, this patch gets rid of EXPLAIN's use of "$n" to represent
subplan and initplan output Params. Instead we now print "(SubPlan
N).colX" or "(InitPlan N).colX" to represent the X'th output column
of that subplan. This eliminates confusion with the use of "$n" to
represent PARAM_EXTERN Params, and it's useful for the first part of
this change because it eliminates needing some other indication of
which subplan is referenced by a SubPlan that has a testexpr.
In passing, this adds simple regression test coverage of the
ROWCOMPARE_SUBLINK code paths, which were entirely unburdened
by testing before.
Tom Lane and Dean Rasheed, reviewed by Aleksander Alekseev.
Thanks to Chantal Keller for raising the question of whether
this area couldn't be improved.
Discussion: https://postgr.es/m/2838538.1705692747@sss.pgh.pa.us
2024-03-19 23:19:24 +01:00
QUERY PLAN
----------------------------------------------
2023-09-15 23:01:26 +02:00
CTE Scan on cte
Output: cte.c
Filter: ((SubPlan 3) IS NOT NULL)
CTE cte
-> Result
Output: '(1,2)'::record
SubPlan 3
-> Result
Output: cte.c
Improve EXPLAIN's display of SubPlan nodes and output parameters.
Historically we've printed SubPlan expression nodes as "(SubPlan N)",
which is pretty uninformative. Trying to reproduce the original SQL
for the subquery is still as impractical as before, and would be
mighty verbose as well. However, we can still do better than that.
Displaying the "testexpr" when present, and adding a keyword to
indicate the SubLinkType, goes a long way toward showing what's
really going on.
In addition, this patch gets rid of EXPLAIN's use of "$n" to represent
subplan and initplan output Params. Instead we now print "(SubPlan
N).colX" or "(InitPlan N).colX" to represent the X'th output column
of that subplan. This eliminates confusion with the use of "$n" to
represent PARAM_EXTERN Params, and it's useful for the first part of
this change because it eliminates needing some other indication of
which subplan is referenced by a SubPlan that has a testexpr.
In passing, this adds simple regression test coverage of the
ROWCOMPARE_SUBLINK code paths, which were entirely unburdened
by testing before.
Tom Lane and Dean Rasheed, reviewed by Aleksander Alekseev.
Thanks to Chantal Keller for raising the question of whether
this area couldn't be improved.
Discussion: https://postgr.es/m/2838538.1705692747@sss.pgh.pa.us
2024-03-19 23:19:24 +01:00
One-Time Filter: (InitPlan 2).col1
InitPlan 2
2023-09-15 23:01:26 +02:00
-> Result
Output: ((cte.c).f1 > 0)
(13 rows)
with cte(c) as materialized (select row(1, 2)),
cte2(c) as (select * from cte)
select * from cte2 as t
where (select * from (select c as c1) s
where (select (c1).f1 > 0)) is not null;
c
-------
(1,2)
(1 row)
-- Also check deparsing of such cases
create view composite_v as
with cte(c) as materialized (select row(1, 2)),
cte2(c) as (select * from cte)
select 1 as one from cte2 as t
where (select * from (select c as c1) s
where (select (c1).f1 > 0)) is not null;
select pg_get_viewdef('composite_v', true);
pg_get_viewdef
--------------------------------------------------------
WITH cte(c) AS MATERIALIZED ( +
SELECT ROW(1, 2) AS "row" +
), cte2(c) AS ( +
SELECT cte.c +
FROM cte +
) +
SELECT 1 AS one +
FROM cte2 t +
WHERE (( SELECT s.c1 +
FROM ( SELECT t.c AS c1) s +
WHERE ( SELECT (s.c1).f1 > 0))) IS NOT NULL;
(1 row)
drop view composite_v;
2017-03-12 00:36:50 +01:00
--
-- Tests for component access / FieldSelect
--
Remove WITH OIDS support, change oid catalog column visibility.
Previously tables declared WITH OIDS, including a significant fraction
of the catalog tables, stored the oid column not as a normal column,
but as part of the tuple header.
This special column was not shown by default, which was somewhat odd,
as it's often (consider e.g. pg_class.oid) one of the more important
parts of a row. Neither pg_dump nor COPY included the contents of the
oid column by default.
The fact that the oid column was not an ordinary column necessitated a
significant amount of special case code to support oid columns. That
already was painful for the existing, but upcoming work aiming to make
table storage pluggable, would have required expanding and duplicating
that "specialness" significantly.
WITH OIDS has been deprecated since 2005 (commit ff02d0a05280e0).
Remove it.
Removing includes:
- CREATE TABLE and ALTER TABLE syntax for declaring the table to be
WITH OIDS has been removed (WITH (oids[ = true]) will error out)
- pg_dump does not support dumping tables declared WITH OIDS and will
issue a warning when dumping one (and ignore the oid column).
- restoring an pg_dump archive with pg_restore will warn when
restoring a table with oid contents (and ignore the oid column)
- COPY will refuse to load binary dump that includes oids.
- pg_upgrade will error out when encountering tables declared WITH
OIDS, they have to be altered to remove the oid column first.
- Functionality to access the oid of the last inserted row (like
plpgsql's RESULT_OID, spi's SPI_lastoid, ...) has been removed.
The syntax for declaring a table WITHOUT OIDS (or WITH (oids = false)
for CREATE TABLE) is still supported. While that requires a bit of
support code, it seems unnecessary to break applications / dumps that
do not use oids, and are explicit about not using them.
The biggest user of WITH OID columns was postgres' catalog. This
commit changes all 'magic' oid columns to be columns that are normally
declared and stored. To reduce unnecessary query breakage all the
newly added columns are still named 'oid', even if a table's column
naming scheme would indicate 'reloid' or such. This obviously
requires adapting a lot code, mostly replacing oid access via
HeapTupleGetOid() with access to the underlying Form_pg_*->oid column.
The bootstrap process now assigns oids for all oid columns in
genbki.pl that do not have an explicit value (starting at the largest
oid previously used), only oids assigned later by oids will be above
FirstBootstrapObjectId. As the oid column now is a normal column the
special bootstrap syntax for oids has been removed.
Oids are not automatically assigned during insertion anymore, all
backend code explicitly assigns oids with GetNewOidWithIndex(). For
the rare case that insertions into the catalog via SQL are called for
the new pg_nextoid() function can be used (which only works on catalog
tables).
The fact that oid columns on system tables are now normal columns
means that they will be included in the set of columns expanded
by * (i.e. SELECT * FROM pg_class will now include the table's oid,
previously it did not). It'd not technically be hard to hide oid
column by default, but that'd mean confusing behavior would either
have to be carried forward forever, or it'd cause breakage down the
line.
While it's not unlikely that further adjustments are needed, the
scope/invasiveness of the patch makes it worthwhile to get merge this
now. It's painful to maintain externally, too complicated to commit
after the code code freeze, and a dependency of a number of other
patches.
Catversion bump, for obvious reasons.
Author: Andres Freund, with contributions by John Naylor
Discussion: https://postgr.es/m/20180930034810.ywp2c7awz7opzcfr@alap3.anarazel.de
2018-11-21 00:36:57 +01:00
CREATE TABLE compositetable(a text, b text);
2017-03-12 00:36:50 +01:00
INSERT INTO compositetable(a, b) VALUES('fa', 'fb');
-- composite type columns can't directly be accessed (error)
SELECT d.a FROM (SELECT compositetable AS d FROM compositetable) s;
ERROR: missing FROM-clause entry for table "d"
LINE 1: SELECT d.a FROM (SELECT compositetable AS d FROM compositeta...
^
-- but can be accessed with proper parens
SELECT (d).a, (d).b FROM (SELECT compositetable AS d FROM compositetable) s;
a | b
----+----
fa | fb
(1 row)
Remove WITH OIDS support, change oid catalog column visibility.
Previously tables declared WITH OIDS, including a significant fraction
of the catalog tables, stored the oid column not as a normal column,
but as part of the tuple header.
This special column was not shown by default, which was somewhat odd,
as it's often (consider e.g. pg_class.oid) one of the more important
parts of a row. Neither pg_dump nor COPY included the contents of the
oid column by default.
The fact that the oid column was not an ordinary column necessitated a
significant amount of special case code to support oid columns. That
already was painful for the existing, but upcoming work aiming to make
table storage pluggable, would have required expanding and duplicating
that "specialness" significantly.
WITH OIDS has been deprecated since 2005 (commit ff02d0a05280e0).
Remove it.
Removing includes:
- CREATE TABLE and ALTER TABLE syntax for declaring the table to be
WITH OIDS has been removed (WITH (oids[ = true]) will error out)
- pg_dump does not support dumping tables declared WITH OIDS and will
issue a warning when dumping one (and ignore the oid column).
- restoring an pg_dump archive with pg_restore will warn when
restoring a table with oid contents (and ignore the oid column)
- COPY will refuse to load binary dump that includes oids.
- pg_upgrade will error out when encountering tables declared WITH
OIDS, they have to be altered to remove the oid column first.
- Functionality to access the oid of the last inserted row (like
plpgsql's RESULT_OID, spi's SPI_lastoid, ...) has been removed.
The syntax for declaring a table WITHOUT OIDS (or WITH (oids = false)
for CREATE TABLE) is still supported. While that requires a bit of
support code, it seems unnecessary to break applications / dumps that
do not use oids, and are explicit about not using them.
The biggest user of WITH OID columns was postgres' catalog. This
commit changes all 'magic' oid columns to be columns that are normally
declared and stored. To reduce unnecessary query breakage all the
newly added columns are still named 'oid', even if a table's column
naming scheme would indicate 'reloid' or such. This obviously
requires adapting a lot code, mostly replacing oid access via
HeapTupleGetOid() with access to the underlying Form_pg_*->oid column.
The bootstrap process now assigns oids for all oid columns in
genbki.pl that do not have an explicit value (starting at the largest
oid previously used), only oids assigned later by oids will be above
FirstBootstrapObjectId. As the oid column now is a normal column the
special bootstrap syntax for oids has been removed.
Oids are not automatically assigned during insertion anymore, all
backend code explicitly assigns oids with GetNewOidWithIndex(). For
the rare case that insertions into the catalog via SQL are called for
the new pg_nextoid() function can be used (which only works on catalog
tables).
The fact that oid columns on system tables are now normal columns
means that they will be included in the set of columns expanded
by * (i.e. SELECT * FROM pg_class will now include the table's oid,
previously it did not). It'd not technically be hard to hide oid
column by default, but that'd mean confusing behavior would either
have to be carried forward forever, or it'd cause breakage down the
line.
While it's not unlikely that further adjustments are needed, the
scope/invasiveness of the patch makes it worthwhile to get merge this
now. It's painful to maintain externally, too complicated to commit
after the code code freeze, and a dependency of a number of other
patches.
Catversion bump, for obvious reasons.
Author: Andres Freund, with contributions by John Naylor
Discussion: https://postgr.es/m/20180930034810.ywp2c7awz7opzcfr@alap3.anarazel.de
2018-11-21 00:36:57 +01:00
-- system columns can't be accessed in composite types (error)
SELECT (d).ctid FROM (SELECT compositetable AS d FROM compositetable) s;
ERROR: column "ctid" not found in data type compositetable
LINE 1: SELECT (d).ctid FROM (SELECT compositetable AS d FROM compos...
2017-03-12 00:36:50 +01:00
^
-- accessing non-existing column in NULL datum errors out
2019-06-08 19:12:26 +02:00
SELECT (NULL::compositetable).nonexistent;
ERROR: column "nonexistent" not found in data type compositetable
LINE 1: SELECT (NULL::compositetable).nonexistent;
2017-03-12 00:36:50 +01:00
^
-- existing column in a NULL composite yield NULL
SELECT (NULL::compositetable).a;
a
---
(1 row)
-- oids can't be accessed in composite types (error)
SELECT (NULL::compositetable).oid;
ERROR: column "oid" not found in data type compositetable
LINE 1: SELECT (NULL::compositetable).oid;
^
DROP TABLE compositetable;