diff --git a/crates/squawk_parser/tests/snapshots/tests__regression_database_ddl.snap b/crates/squawk_parser/tests/snapshots/tests__regression_database_ddl.snap deleted file mode 100644 index 467a6040..00000000 --- a/crates/squawk_parser/tests/snapshots/tests__regression_database_ddl.snap +++ /dev/null @@ -1,5 +0,0 @@ ---- -source: crates/squawk_parser/tests/tests.rs -input_file: postgres/regression_suite/database_ddl.sql ---- - diff --git a/crates/squawk_parser/tests/snapshots/tests__regression_role_ddl.snap b/crates/squawk_parser/tests/snapshots/tests__regression_role_ddl.snap deleted file mode 100644 index 025d9730..00000000 --- a/crates/squawk_parser/tests/snapshots/tests__regression_role_ddl.snap +++ /dev/null @@ -1,5 +0,0 @@ ---- -source: crates/squawk_parser/tests/tests.rs -input_file: postgres/regression_suite/role_ddl.sql ---- - diff --git a/crates/squawk_parser/tests/snapshots/tests__regression_tablespace_ddl.snap b/crates/squawk_parser/tests/snapshots/tests__regression_tablespace_ddl.snap deleted file mode 100644 index 1e92e1b8..00000000 --- a/crates/squawk_parser/tests/snapshots/tests__regression_tablespace_ddl.snap +++ /dev/null @@ -1,5 +0,0 @@ ---- -source: crates/squawk_parser/tests/tests.rs -input_file: postgres/regression_suite/tablespace_ddl.sql ---- - diff --git a/crates/xtask/src/sync_pg.rs b/crates/xtask/src/sync_pg.rs index b5421ee5..58b9d599 100644 --- a/crates/xtask/src/sync_pg.rs +++ b/crates/xtask/src/sync_pg.rs @@ -85,6 +85,13 @@ const IGNORED_LINES: &[&str] = &[ r#"select 'a\\bcd' as f1, 'a\\b\'cd' as f2, 'a\\b\'''cd' as f3, 'abcd\\' as f4, 'ab\\\'cd' as f5, '\\\\' as f6;"#, r#"select 'a\\bcd' as f1, 'a\\b\'cd' as f2, 'a\\b\'''cd' as f3, 'abcd\\' as f4, 'ab\\\'cd' as f5, '\\\\' as f6;"#, "copy (select * from test1) (t,id) to stdout;", + "copy (select t into temp test3 from test1 where id=3) to stdout;", + "DECLARE foo CURSOR FOR SELECT 1 INTO int4_tbl;", + "COPY (SELECT 1 INTO frak UNION SELECT 2) TO 'blob';", + "SELECT * FROM (SELECT 1 INTO f) bar;", + "CREATE VIEW foo AS SELECT 1 INTO int4_tbl;", + "INSERT INTO int4_tbl SELECT 1 INTO f;", + "REPACK (CONCURRENTLY) :toast_rel;", ]; const VARIABLE_REPLACEMENTS: &[(&str, &str)] = &[ diff --git a/postgres/kwlist.h b/postgres/kwlist.h index 688ae2b2..39227625 100644 --- a/postgres/kwlist.h +++ b/postgres/kwlist.h @@ -1,7 +1,7 @@ // synced from: -// commit: 972c14fb9134fdfd76ea6ebcf98a55a945bbc988 -// committed at: 2026-04-14T21:06:27-04:00 -// file: https://github.com/postgres/postgres/blob/972c14fb9134fdfd76ea6ebcf98a55a945bbc988/src/include/parser/kwlist.h +// commit: c06d1a4ba6b26eef27b04074683cccade6c277ee +// committed at: 2026-05-03T20:23:50+03:00 +// file: https://github.com/postgres/postgres/blob/c06d1a4ba6b26eef27b04074683cccade6c277ee/src/include/parser/kwlist.h // // update via: // cargo xtask sync-pg diff --git a/postgres/plpgsql/plpgsql_trap.sql b/postgres/plpgsql/plpgsql_trap.sql index 5459b347..731d5920 100644 --- a/postgres/plpgsql/plpgsql_trap.sql +++ b/postgres/plpgsql/plpgsql_trap.sql @@ -85,13 +85,11 @@ drop table foo; create function trap_timeout() returns void as $$ begin - declare x int; begin - -- we assume this will take longer than 1 second: - select count(*) into x from generate_series(1, 1_000_000_000_000); + perform pg_sleep(10); exception when others then - raise notice 'caught others?'; + raise notice 'caught others: %', sqlerrm; when query_canceled then raise notice 'nyeah nyeah, can''t stop me'; end; diff --git a/postgres/regression_suite/cluster.sql b/postgres/regression_suite/cluster.sql index 51e6bb24..5dac9ec4 100644 --- a/postgres/regression_suite/cluster.sql +++ b/postgres/regression_suite/cluster.sql @@ -248,13 +248,8 @@ REPACK clstrpart; CREATE TEMP TABLE new_cluster_info AS SELECT relname, level, relfilenode, relkind FROM pg_partition_tree('clstrpart'::regclass) AS tree JOIN pg_class c ON c.oid=tree.relid ; SELECT relname, old.level, old.relkind, old.relfilenode = new.relfilenode FROM old_cluster_info AS old JOIN new_cluster_info AS new USING (relname) ORDER BY relname COLLATE "C"; --- CONCURRENTLY doesn't like partitioned tables -REPACK (CONCURRENTLY) clstrpart; - -DROP TABLE clstrpart; - -- Ownership of partitions is checked -CREATE TABLE ptnowner(i int unique) PARTITION BY LIST (i); +CREATE TABLE ptnowner(i int unique not null) PARTITION BY LIST (i); CREATE INDEX ptnowner_i_idx ON ptnowner(i); CREATE TABLE ptnowner1 PARTITION OF ptnowner FOR VALUES IN (1); CREATE ROLE regress_ptnowner; @@ -270,8 +265,6 @@ CREATE TEMP TABLE ptnowner_oldnodes AS SET SESSION AUTHORIZATION regress_ptnowner; CLUSTER ptnowner USING ptnowner_i_idx; RESET SESSION AUTHORIZATION; -SELECT a.relname, a.relfilenode=b.relfilenode FROM pg_class a - JOIN ptnowner_oldnodes b USING (oid) ORDER BY a.relname COLLATE "C"; DROP TABLE ptnowner; DROP ROLE regress_ptnowner; @@ -339,6 +332,10 @@ COMMIT; -- -- REPACK -- +-- Note we cannot test working REPACK (CONCURRENTLY) here, because the +-- tests are sometimes run with wal_level=minimal. Tests for that appear +-- elsewhere. +-- ---------------------------------------------------------------------- -- REPACK handles individual tables identically to CLUSTER, but it's worth @@ -386,15 +383,58 @@ JOIN relnodes_new n ON o.relname = n.relname WHERE o.relfilenode <> n.relfilenode ORDER BY o.relname; --- concurrently +-- +-- Check concurrent mode requirements +-- + +-- Disallowed in catalogs REPACK (CONCURRENTLY) pg_class; +-- Doesn't like partitioned tables +REPACK (CONCURRENTLY) clstrpart; + +-- Doesn't support catalog tables +REPACK (CONCURRENTLY) pg_class; + +-- Only support permanent tables, temp and unlogged tables are not supported +CREATE TEMP TABLE repack_conc_temp (i int PRIMARY KEY); +REPACK (CONCURRENTLY) repack_conc_temp; +DROP TABLE repack_conc_temp; +CREATE UNLOGGED TABLE repack_conc_unlogged (i int PRIMARY KEY); +REPACK (CONCURRENTLY) repack_conc_unlogged; +DROP TABLE repack_conc_unlogged; + +-- Doesn't support TOAST tables directly +CREATE TABLE repack_conc_toast (t text); +SELECT reltoastrelid::regclass AS toast_rel +FROM pg_class WHERE oid = 'repack_conc_toast'::regclass /* \gset */; +-- \set VERBOSITY sqlstate +-- REPACK (CONCURRENTLY) 'toast_rel'; +-- \set VERBOSITY default +DROP TABLE repack_conc_toast; + +-- Doesn't support tables with REPLICA IDENTITY NOTHING, even if they have a primary key +CREATE TABLE repack_conc_replident (i int PRIMARY KEY); +ALTER TABLE repack_conc_replident REPLICA IDENTITY NOTHING; +REPACK (CONCURRENTLY) repack_conc_replident; + +-- Doesn't support tables without a primary key or replica identity index +ALTER TABLE repack_conc_replident DROP CONSTRAINT repack_conc_replident_pkey; +ALTER TABLE repack_conc_replident REPLICA IDENTITY DEFAULT; +REPACK (CONCURRENTLY) repack_conc_replident; + +-- Doesn't support tables with deferrable primary keys +ALTER TABLE repack_conc_replident ADD PRIMARY KEY (i) DEFERRABLE; +REPACK (CONCURRENTLY) repack_conc_replident; + -- clean up +DROP TABLE repack_conc_replident; DROP TABLE clustertest; DROP TABLE clstr_1; DROP TABLE clstr_2; DROP TABLE clstr_3; DROP TABLE clstr_4; DROP TABLE clstr_expression; +DROP TABLE clstrpart; DROP USER regress_clstr_user; diff --git a/postgres/regression_suite/collate.icu.utf8.sql b/postgres/regression_suite/collate.icu.utf8.sql index 947fae12..281ca00b 100644 --- a/postgres/regression_suite/collate.icu.utf8.sql +++ b/postgres/regression_suite/collate.icu.utf8.sql @@ -642,6 +642,72 @@ CREATE UNIQUE INDEX ON test3ci (x); -- error SELECT string_to_array('ABC,DEF,GHI' COLLATE case_insensitive, ',', 'abc'); SELECT string_to_array('ABCDEFGHI' COLLATE case_insensitive, NULL, 'b'); +-- Test HAVING-to-WHERE pushdown with nondeterministic collations. +-- When a HAVING clause uses a different collation than the GROUP BY's +-- nondeterministic collation, it must not be pushed to WHERE, otherwise +-- aggregate results can change because the stricter filter eliminates rows +-- before grouping. + +-- Negative: collation conflict, HAVING must not be pushed to WHERE +EXPLAIN (COSTS OFF) +SELECT x, count(*) FROM test3ci GROUP BY x HAVING x = 'abc' COLLATE case_sensitive; +SELECT x, count(*) FROM test3ci GROUP BY x HAVING x = 'abc' COLLATE case_sensitive; + +-- Positive: same collation, safe to push HAVING to WHERE +EXPLAIN (COSTS OFF) +SELECT x, count(*) FROM test3ci GROUP BY x HAVING x = 'abc' COLLATE case_insensitive; +SELECT x, count(*) FROM test3ci GROUP BY x HAVING x = 'abc' COLLATE case_insensitive; + +-- Negative: function applied to grouped column with conflicting collation +EXPLAIN (COSTS OFF) +SELECT x, count(*) FROM test3ci GROUP BY x HAVING upper(x) = 'ABC' COLLATE case_sensitive; +SELECT x, count(*) FROM test3ci GROUP BY x HAVING upper(x) = 'ABC' COLLATE case_sensitive; + +-- Positive: function with same collation as GROUP BY +EXPLAIN (COSTS OFF) +SELECT x, count(*) FROM test3ci GROUP BY x HAVING upper(x) = 'ABC' COLLATE case_insensitive; +SELECT x, count(*) FROM test3ci GROUP BY x HAVING upper(x) = 'ABC' COLLATE case_insensitive; + +-- Negative: inner function has conflicting collation, even though outer +-- operator's collation matches GROUP BY due to a COLLATE override +EXPLAIN (COSTS OFF) +SELECT x, count(*) FROM test3ci GROUP BY x HAVING upper(x COLLATE case_sensitive) COLLATE case_insensitive = 'ABC'; +SELECT x, count(*) FROM test3ci GROUP BY x HAVING upper(x COLLATE case_sensitive) COLLATE case_insensitive = 'ABC'; + +-- Mixed AND: conflicting clause stays in HAVING, safe clause pushed to WHERE +EXPLAIN (COSTS OFF) +SELECT x, count(*) FROM test3ci GROUP BY x HAVING x = 'abc' COLLATE case_sensitive AND length(x) > 1; +SELECT x, count(*) FROM test3ci GROUP BY x HAVING x = 'abc' COLLATE case_sensitive AND length(x) > 1; + +-- Positive: AND of two safe clauses, both can be pushed +EXPLAIN (COSTS OFF) +SELECT x, count(*) FROM test3ci GROUP BY x HAVING x = 'abc' COLLATE case_insensitive AND length(x) > 1; +SELECT x, count(*) FROM test3ci GROUP BY x HAVING x = 'abc' COLLATE case_insensitive AND length(x) > 1; + +-- Negative: OR with a conflicting clause: must stay in HAVING +EXPLAIN (COSTS OFF) +SELECT x, count(*) FROM test3ci GROUP BY x HAVING x = 'abc' COLLATE case_sensitive OR x = 'def' COLLATE case_sensitive ORDER BY 1; +SELECT x, count(*) FROM test3ci GROUP BY x HAVING x = 'abc' COLLATE case_sensitive OR x = 'def' COLLATE case_sensitive ORDER BY 1; + +-- Negative: collation conflict inside a RowCompareExpr +EXPLAIN (COSTS OFF) +SELECT x, count(*) FROM test3ci GROUP BY x HAVING ROW(x, 1) < ROW('ABC' COLLATE case_sensitive, 1) ORDER BY 1; +SELECT x, count(*) FROM test3ci GROUP BY x HAVING ROW(x, 1) < ROW('ABC' COLLATE case_sensitive, 1) ORDER BY 1; + +-- Positive: conflicting collation but no grouping expression reference +EXPLAIN (COSTS OFF) +SELECT x, count(*) FROM test3ci GROUP BY x HAVING current_setting('server_version') = 'abc' COLLATE case_sensitive; + +-- Positive: deterministic collation in GROUP BY: always safe to push, even if +-- HAVING uses a nondeterministic collation +EXPLAIN (COSTS OFF) +SELECT x, count(*) FROM test3cs GROUP BY x HAVING x = 'abc' COLLATE case_sensitive; +SELECT x, count(*) FROM test3cs GROUP BY x HAVING x = 'abc' COLLATE case_sensitive; + +EXPLAIN (COSTS OFF) +SELECT x, count(*) FROM test3cs GROUP BY x HAVING x = 'abc' COLLATE case_insensitive ORDER BY 1; +SELECT x, count(*) FROM test3cs GROUP BY x HAVING x = 'abc' COLLATE case_insensitive ORDER BY 1; + -- bpchar CREATE TABLE test1bpci (x char(3) COLLATE case_insensitive); CREATE TABLE test2bpci (x char(3) COLLATE case_insensitive); diff --git a/postgres/regression_suite/copyselect.sql b/postgres/regression_suite/copyselect.sql index 45ce1afd..92346c00 100644 --- a/postgres/regression_suite/copyselect.sql +++ b/postgres/regression_suite/copyselect.sql @@ -37,7 +37,7 @@ copy (select t from test1 where id=3 for update) to stdout; -- -- This should fail -- -copy (select t into temp test3 from test1 where id=3) to stdout; +-- copy (select t into temp test3 from test1 where id=3) to stdout; -- -- This should fail -- diff --git a/postgres/regression_suite/create_table_like.sql b/postgres/regression_suite/create_table_like.sql index c395305d..cb5be2db 100644 --- a/postgres/regression_suite/create_table_like.sql +++ b/postgres/regression_suite/create_table_like.sql @@ -276,6 +276,32 @@ CREATE FOREIGN TABLE ctl_foreign_table2(LIKE ctl_table INCLUDING ALL) SERVER ctl SELECT attname, attcompression FROM pg_attribute WHERE attrelid = 'ctl_foreign_table2'::regclass and attnum > 0 ORDER BY attnum; +-- LIKE ... INCLUDING STATISTICS with dropped columns in the parent, +-- so stxkeys attnums are not contiguous. +CREATE TABLE ctl_stats3_parent (a int, b int, c int); +ALTER TABLE ctl_stats3_parent DROP COLUMN b; +CREATE STATISTICS ctl_stats3_stat ON a, c FROM ctl_stats3_parent; +CREATE TABLE ctl_stats3_child (LIKE ctl_stats3_parent INCLUDING STATISTICS); +CREATE TABLE ctl_stats4_parent (a int, b int, c int, d int); +ALTER TABLE ctl_stats4_parent DROP COLUMN b; +CREATE STATISTICS ctl_stats4_stat ON a, c FROM ctl_stats4_parent; +CREATE TABLE ctl_stats4_child (LIKE ctl_stats4_parent INCLUDING STATISTICS); +SELECT s.stxrelid::regclass AS relation, + array_agg(a.attname ORDER BY u.ord) AS stats_columns +FROM pg_statistic_ext s +CROSS JOIN LATERAL + unnest(s.stxkeys::int2[]) WITH ORDINALITY AS u(attnum, ord) +JOIN pg_attribute a + ON a.attrelid = s.stxrelid AND a.attnum = u.attnum +WHERE s.stxrelid IN ('ctl_stats3_child'::regclass, + 'ctl_stats4_child'::regclass) +GROUP BY s.stxrelid +ORDER BY s.stxrelid::regclass::text; +DROP TABLE ctl_stats3_parent; +DROP TABLE ctl_stats3_child; +DROP TABLE ctl_stats4_parent; +DROP TABLE ctl_stats4_child; + DROP TABLE ctl_table; DROP FOREIGN TABLE ctl_foreign_table1; DROP FOREIGN TABLE ctl_foreign_table2; diff --git a/postgres/regression_suite/database_ddl.sql b/postgres/regression_suite/database_ddl.sql deleted file mode 100644 index e06a5c40..00000000 --- a/postgres/regression_suite/database_ddl.sql +++ /dev/null @@ -1,66 +0,0 @@ --- --- Tests for pg_get_database_ddl() --- - --- To produce stable regression test output, strip locale/collation details --- from the DDL output. Uses a plain SQL function to avoid a PL/pgSQL --- dependency. - -CREATE OR REPLACE FUNCTION ddl_filter(ddl_input TEXT) -RETURNS TEXT LANGUAGE sql AS $$ -SELECT regexp_replace( - regexp_replace( - regexp_replace( - regexp_replace( - regexp_replace( - ddl_input, - '\s*\mLOCALE_PROVIDER\M\s*=\s*([''"]?[^''"\s]+[''"]?)', '', 'gi'), - '\s*LC_COLLATE\s*=\s*([''"])[^''"]*\1', '', 'gi'), - '\s*LC_CTYPE\s*=\s*([''"])[^''"]*\1', '', 'gi'), - '\s*\S*LOCALE\S*\s*=?\s*([''"])[^''"]*\1', '', 'gi'), - '\s*\S*COLLATION\S*\s*=?\s*([''"])[^''"]*\1', '', 'gi') -$$; - -CREATE ROLE regress_datdba; -CREATE DATABASE regression_database_ddl - ENCODING utf8 LC_COLLATE "C" LC_CTYPE "C" TEMPLATE template0 - OWNER regress_datdba; -ALTER DATABASE regression_database_ddl CONNECTION_LIMIT 123; -ALTER DATABASE regression_database_ddl SET random_page_cost = 2.0; -ALTER ROLE regress_datdba IN DATABASE regression_database_ddl SET random_page_cost = 1.1; - --- Database doesn't exist -SELECT * FROM pg_get_database_ddl('regression_database'); - --- NULL value -SELECT * FROM pg_get_database_ddl(NULL); - --- Invalid option value (should error) -SELECT * FROM pg_get_database_ddl('regression_database_ddl', 'owner', 'invalid'); - --- Duplicate option (should error) -SELECT * FROM pg_get_database_ddl('regression_database_ddl', 'owner', 'false', 'owner', 'true'); - --- Without options -SELECT ddl_filter(pg_get_database_ddl) FROM pg_get_database_ddl('regression_database_ddl'); - --- With owner -SELECT ddl_filter(pg_get_database_ddl) FROM pg_get_database_ddl('regression_database_ddl', 'owner', 'true'); - --- Pretty-printed output --- \pset format unaligned -SELECT ddl_filter(pg_get_database_ddl) FROM pg_get_database_ddl('regression_database_ddl', 'pretty', 'true', 'tablespace', 'false'); --- \pset format aligned - --- Permission check: revoke CONNECT on database -CREATE ROLE regress_db_ddl_noaccess; -REVOKE CONNECT ON DATABASE regression_database_ddl FROM PUBLIC; -SET ROLE regress_db_ddl_noaccess; -SELECT * FROM pg_get_database_ddl('regression_database_ddl'); -- should fail -RESET ROLE; -GRANT CONNECT ON DATABASE regression_database_ddl TO PUBLIC; -DROP ROLE regress_db_ddl_noaccess; - -DROP DATABASE regression_database_ddl; -DROP FUNCTION ddl_filter(text); -DROP ROLE regress_datdba; diff --git a/postgres/regression_suite/expressions.sql b/postgres/regression_suite/expressions.sql index db96bea6..29f4ee24 100644 --- a/postgres/regression_suite/expressions.sql +++ b/postgres/regression_suite/expressions.sql @@ -196,16 +196,108 @@ default for type myint using hash as function 1 myinthash(myint); create table inttest (a myint); -insert into inttest values(1::myint),(null); - --- try an array with enough elements to cause hashing -select * from inttest where a in (1::myint,2::myint,3::myint,4::myint,5::myint,6::myint,7::myint,8::myint,9::myint, null); -select * from inttest where a not in (1::myint,2::myint,3::myint,4::myint,5::myint,6::myint,7::myint,8::myint,9::myint, null); -select * from inttest where a not in (0::myint,2::myint,3::myint,4::myint,5::myint,6::myint,7::myint,8::myint,9::myint, null); --- ensure the result matched with the non-hashed version. We simply remove --- some array elements so that we don't reach the hashing threshold. -select * from inttest where a in (1::myint,2::myint,3::myint,4::myint,5::myint, null); -select * from inttest where a not in (1::myint,2::myint,3::myint,4::myint,5::myint, null); -select * from inttest where a not in (0::myint,2::myint,3::myint,4::myint,5::myint, null); +insert into inttest values (null), (0::myint), (1::myint); + +-- Test EEOP_HASHED_SCALARARRAYOP against EEOP_SCALARARRAYOP. Ensure the +-- result of non-hashed vs hashed is the same. +select + a, + a in (1::myint,2::myint,3::myint,4::myint,5::myint,6::myint,7::myint,8::myint) as not_hashed, + a in (1::myint,2::myint,3::myint,4::myint,5::myint,6::myint,7::myint,8::myint,9::myint) as hashed +from inttest; + +select + a, + a in (null::myint,1::myint,2::myint,3::myint,4::myint,5::myint,6::myint,7::myint) as not_hashed, + a in (null::myint,1::myint,2::myint,3::myint,4::myint,5::myint,6::myint,7::myint,8::myint) as hashed + from inttest; + +select + a, + a not in (1::myint,2::myint,3::myint,4::myint,5::myint,6::myint,7::myint,8::myint) as not_hashed, + a not in (1::myint,2::myint,3::myint,4::myint,5::myint,6::myint,7::myint,8::myint,9::myint) as hashed +from inttest; + +select + a, + a not in (null::myint,1::myint,2::myint,3::myint,4::myint,5::myint,6::myint,7::myint) as not_hashed, + a not in (null::myint,1::myint,2::myint,3::myint,4::myint,5::myint,6::myint,7::myint,8::myint) as hashed +from inttest; + +-- Now make the equal function return false when given two NULLs +create or replace function myinteq(myint, myint) returns bool as $$ +begin + if $1 is null and $2 is null then + return false; + else + return $1::int = $2::int; + end if; +end; +$$ language plpgsql immutable; + +-- And try the same again to ensure EEOP_HASHED_SCALARARRAYOP does the same +-- thing as EEOP_SCALARARRAYOP. +select + a, + a in (1::myint,2::myint,3::myint,4::myint,5::myint,6::myint,7::myint,8::myint) as not_hashed, + a in (1::myint,2::myint,3::myint,4::myint,5::myint,6::myint,7::myint,8::myint,9::myint) as hashed +from inttest; + +select + a, + a in (null::myint,1::myint,2::myint,3::myint,4::myint,5::myint,6::myint,7::myint) as not_hashed, + a in (null::myint,1::myint,2::myint,3::myint,4::myint,5::myint,6::myint,7::myint,8::myint) as hashed + from inttest; + +select + a, + a not in (1::myint,2::myint,3::myint,4::myint,5::myint,6::myint,7::myint,8::myint) as not_hashed, + a not in (1::myint,2::myint,3::myint,4::myint,5::myint,6::myint,7::myint,8::myint,9::myint) as hashed +from inttest; + +select + a, + a not in (null::myint,1::myint,2::myint,3::myint,4::myint,5::myint,6::myint,7::myint) as not_hashed, + a not in (null::myint,1::myint,2::myint,3::myint,4::myint,5::myint,6::myint,7::myint,8::myint) as hashed +from inttest; + +-- Try again with an equality function that treats NULLs as equal to 0. +create or replace function myinteq(myint, myint) returns bool as $$ +begin + if $1 is null and $2 is null then + return false; + else + return coalesce($1::int,0) = coalesce($2::int, 0); + end if; +end; +$$ language plpgsql immutable; + +select + a, + a in (1::myint,2::myint,3::myint,4::myint,5::myint,6::myint,7::myint,8::myint) as not_hashed, + a in (1::myint,2::myint,3::myint,4::myint,5::myint,6::myint,7::myint,8::myint,9::myint) as hashed, + a in (0::myint,1::myint,2::myint,3::myint,4::myint,5::myint,6::myint,7::myint) as not_hashed_zero, + a in (0::myint,1::myint,2::myint,3::myint,4::myint,5::myint,6::myint,7::myint,8::myint) as hashed_zero +from inttest; + +select + a, + a in (null::myint,1::myint,2::myint,3::myint,4::myint,5::myint,6::myint,7::myint) as not_hashed, + a in (null::myint,1::myint,2::myint,3::myint,4::myint,5::myint,6::myint,7::myint,8::myint) as hashed + from inttest; + +select + a, + a not in (1::myint,2::myint,3::myint,4::myint,5::myint,6::myint,7::myint,8::myint) as not_hashed, + a not in (1::myint,2::myint,3::myint,4::myint,5::myint,6::myint,7::myint,8::myint,9::myint) as hashed, + a not in (0::myint,1::myint,2::myint,3::myint,4::myint,5::myint,6::myint,7::myint) as not_hashed_zero, + a not in (0::myint,1::myint,2::myint,3::myint,4::myint,5::myint,6::myint,7::myint,8::myint) as hashed_zero +from inttest; + +select + a, + a not in (null::myint,1::myint,2::myint,3::myint,4::myint,5::myint,6::myint,7::myint) as not_hashed, + a not in (null::myint,1::myint,2::myint,3::myint,4::myint,5::myint,6::myint,7::myint,8::myint) as hashed +from inttest; rollback; diff --git a/postgres/regression_suite/fast_default.sql b/postgres/regression_suite/fast_default.sql index 8ff29cf2..8b31d317 100644 --- a/postgres/regression_suite/fast_default.sql +++ b/postgres/regression_suite/fast_default.sql @@ -375,7 +375,7 @@ SELECT attname, atthasmissing, attmissingval FROM pg_attribute DROP TABLE T; DROP FUNCTION foolme(timestamptz); --- Simple querie +-- Simple queries CREATE TABLE T (pk INT NOT NULL PRIMARY KEY); SELECT set('t'); diff --git a/postgres/regression_suite/for_portion_of.sql b/postgres/regression_suite/for_portion_of.sql index cf7bcf3a..cb59bf5d 100644 --- a/postgres/regression_suite/for_portion_of.sql +++ b/postgres/regression_suite/for_portion_of.sql @@ -1365,4 +1365,37 @@ SELECT * FROM temporal_partitioned_5 ORDER BY id, valid_at; DROP TABLE temporal_partitioned; +-- UPDATE/DELETE FOR PORTION OF with RULEs +CREATE TABLE fpo_rule (f1 bigint, f2 int4range); +INSERT INTO fpo_rule VALUES (1, '[1, 11)'); + +CREATE RULE fpo_rule1 AS ON INSERT TO fpo_rule + DO INSTEAD UPDATE fpo_rule FOR PORTION OF f2 FROM 1 TO 4 SET f1 = 2; +INSERT INTO fpo_rule VALUES (1, '[1, 11)'); +SELECT * FROM fpo_rule ORDER BY f1; + +CREATE RULE fpo_rule2 AS ON INSERT TO fpo_rule + DO INSTEAD DELETE FROM fpo_rule FOR PORTION OF f2 FROM 1 TO 4; +INSERT INTO fpo_rule VALUES (1, '[1, 11)'); +SELECT * FROM fpo_rule ORDER BY f1; + +CREATE RULE fpo_rule3 AS ON DELETE TO fpo_rule + DO INSTEAD UPDATE fpo_rule FOR PORTION OF f2 FROM 1 TO 8 SET f1 = 2; +DELETE FROM fpo_rule FOR PORTION OF f2 FROM 1 TO 5; +SELECT * FROM fpo_rule ORDER BY f1; + +DROP RULE fpo_rule3 ON fpo_rule; +CREATE RULE fpo_rule4 AS ON UPDATE TO fpo_rule + DO INSTEAD DELETE FROM fpo_rule FOR PORTION OF f2 FROM 6 TO 9; +UPDATE fpo_rule FOR PORTION OF f2 FROM 4 TO 9 SET f1 = 12; +SELECT * FROM fpo_rule ORDER BY f1; + +DROP RULE fpo_rule4 ON fpo_rule; +CREATE RULE fpo_rule5 AS ON UPDATE TO fpo_rule + DO ALSO DELETE FROM fpo_rule FOR PORTION OF f2 FROM 4 TO 6; +UPDATE fpo_rule FOR PORTION OF f2 FROM 9 TO 10 SET f1 = 3; +SELECT * FROM fpo_rule ORDER BY f1; + +DROP TABLE fpo_rule; + RESET datestyle; diff --git a/postgres/regression_suite/generated_stored.sql b/postgres/regression_suite/generated_stored.sql index 5f70d914..f957905b 100644 --- a/postgres/regression_suite/generated_stored.sql +++ b/postgres/regression_suite/generated_stored.sql @@ -239,6 +239,9 @@ COPY gtest3 (a, b) FROM stdin; -- -- SELECT * FROM gtest3 ORDER BY a; +-- COPY JSON should exclude generated columns, same as text/CSV +COPY gtest1 TO stdout WITH (FORMAT json); + -- null values CREATE TABLE gtest2 (a int PRIMARY KEY, b int GENERATED ALWAYS AS (NULL) STORED); INSERT INTO gtest2 VALUES (1); @@ -798,6 +801,29 @@ CREATE TABLE gtest28b (LIKE gtest28a INCLUDING GENERATED); -- \d gtest28* +-- rule actions referring to generated columns: +-- NEW.b in a rule action should reflect the generated column's new value +CREATE TABLE gtest_rule (a int, b int GENERATED ALWAYS AS (a * 2) STORED); +CREATE TABLE gtest_rule_log (op text, old_b int, new_b int); +CREATE RULE gtest_rule_upd AS ON UPDATE TO gtest_rule + DO ALSO INSERT INTO gtest_rule_log VALUES ('UPD', OLD.b, NEW.b); +CREATE RULE gtest_rule_ins AS ON INSERT TO gtest_rule + DO ALSO INSERT INTO gtest_rule_log VALUES ('INS', NULL, NEW.b); +INSERT INTO gtest_rule (a) VALUES (1); +UPDATE gtest_rule SET a = 10; +UPDATE gtest_rule SET a = (SELECT max(b) FROM gtest_rule); +SELECT * FROM gtest_rule_log; +DROP RULE gtest_rule_upd ON gtest_rule; +DROP RULE gtest_rule_ins ON gtest_rule; +DROP TABLE gtest_rule_log; + +-- rule quals referring to generated columns: +-- NEW.b in the rule qual should reflect the generated column's new value +CREATE RULE gtest_rule_qual AS ON UPDATE TO gtest_rule WHERE NEW.b > 100 + DO INSTEAD NOTHING; +UPDATE gtest_rule SET a = 100; +SELECT * FROM gtest_rule; +DROP TABLE gtest_rule; -- sanity check of system catalog SELECT attrelid, attname, attgenerated FROM pg_attribute WHERE attgenerated NOT IN ('', 's', 'v'); diff --git a/postgres/regression_suite/generated_virtual.sql b/postgres/regression_suite/generated_virtual.sql index f9e2c7c8..5754a956 100644 --- a/postgres/regression_suite/generated_virtual.sql +++ b/postgres/regression_suite/generated_virtual.sql @@ -239,6 +239,9 @@ COPY gtest3 (a, b) FROM stdin; -- -- SELECT * FROM gtest3 ORDER BY a; +-- COPY JSON should exclude generated columns, same as text/CSV +COPY gtest1 TO stdout WITH (FORMAT json); + -- null values CREATE TABLE gtest2 (a int PRIMARY KEY, b int GENERATED ALWAYS AS (NULL) VIRTUAL); INSERT INTO gtest2 VALUES (1); @@ -811,6 +814,29 @@ CREATE TABLE gtest28b (LIKE gtest28a INCLUDING GENERATED); -- \d gtest28* +-- rule actions referring to generated columns: +-- NEW.b in a rule action should reflect the generated column's new value +CREATE TABLE gtest_rule (a int, b int GENERATED ALWAYS AS (a * 2) VIRTUAL); +CREATE TABLE gtest_rule_log (op text, old_b int, new_b int); +CREATE RULE gtest_rule_upd AS ON UPDATE TO gtest_rule + DO ALSO INSERT INTO gtest_rule_log VALUES ('UPD', OLD.b, NEW.b); +CREATE RULE gtest_rule_ins AS ON INSERT TO gtest_rule + DO ALSO INSERT INTO gtest_rule_log VALUES ('INS', NULL, NEW.b); +INSERT INTO gtest_rule (a) VALUES (1); +UPDATE gtest_rule SET a = 10; +UPDATE gtest_rule SET a = (SELECT max(b) FROM gtest_rule); +SELECT * FROM gtest_rule_log; +DROP RULE gtest_rule_upd ON gtest_rule; +DROP RULE gtest_rule_ins ON gtest_rule; +DROP TABLE gtest_rule_log; + +-- rule quals referring to generated columns: +-- NEW.b in the rule qual should reflect the generated column's new value +CREATE RULE gtest_rule_qual AS ON UPDATE TO gtest_rule WHERE NEW.b > 100 + DO INSTEAD NOTHING; +UPDATE gtest_rule SET a = 100; +SELECT * FROM gtest_rule; +DROP TABLE gtest_rule; -- sanity check of system catalog SELECT attrelid, attname, attgenerated FROM pg_attribute WHERE attgenerated NOT IN ('', 's', 'v'); @@ -903,3 +929,43 @@ select * from gtest33 where b is null; reset constraint_exclusion; drop table gtest33; + +-- Ensure that EXCLUDED. in INSERT ... ON CONFLICT +-- DO UPDATE is expanded to the generation expression, both for plain and +-- partitioned target relations. +create table gtest34 (id int primary key, a int, + c int generated always as (a * 10) virtual); +insert into gtest34 values (1, 5); +insert into gtest34 values (1, 7) + on conflict (id) do update set a = excluded.c returning *; +insert into gtest34 values (1, 2) + on conflict (id) do update set a = gtest34.c + excluded.c returning *; +insert into gtest34 values (1, 3) + on conflict (id) do update set a = 999 where excluded.c > 20 returning *; +drop table gtest34; + +create table gtest34p (id int primary key, a int, + c int generated always as (a * 10) virtual) + partition by range (id); +create table gtest34p_1 partition of gtest34p for values from (1) to (100); +insert into gtest34p values (1, 5); +insert into gtest34p values (1, 7) + on conflict (id) do update set a = excluded.c returning *; +insert into gtest34p values (1, 2) + on conflict (id) do update set a = gtest34p.c + excluded.c returning *; +drop table gtest34p; + +-- Ensure that virtual generated columns work with WHERE CURRENT OF +create table gtest_cursor (id int primary key, a int, b int generated always as (a * 2) virtual); +insert into gtest_cursor values (1, 10), (2, 20), (3, 30); + +begin; +declare curs cursor for select * from gtest_cursor order by id for update; +fetch 1 from curs; +update gtest_cursor set a = 99 where current of curs; +select * from gtest_cursor order by id; +delete from gtest_cursor where current of curs; +select * from gtest_cursor order by id; +commit; + +drop table gtest_cursor; diff --git a/postgres/regression_suite/graph_table.sql b/postgres/regression_suite/graph_table.sql index 5254c79f..f272b591 100644 --- a/postgres/regression_suite/graph_table.sql +++ b/postgres/regression_suite/graph_table.sql @@ -162,7 +162,6 @@ SELECT x1.a, g.* FROM x1, GRAPH_TABLE (myshop MATCH (x1 IS customers WHERE x1.ad -- reference is available SELECT x1.a, g.* FROM x1, GRAPH_TABLE (myshop MATCH (x1 IS customers)-[IS customer_orders]->(o IS orders WHERE o.order_id = x1.a) COLUMNS (x1.name AS customer_name, x1.customer_id AS cid, o.order_id)) g; -- error SELECT x1.a, g.* FROM x1, GRAPH_TABLE (myshop MATCH (c IS customers WHERE c.address = 'US' AND c.customer_id = x1.a)-[IS customer_orders]->(x1 IS orders) COLUMNS (c.name AS customer_name, c.customer_id AS cid, x1.order_id)) g; -- error -DROP TABLE x1; CREATE TABLE v1 ( id int PRIMARY KEY, @@ -394,9 +393,9 @@ SELECT * FROM GRAPH_TABLE (g1 MATCH (a)-[b]->(c)-[b]->(d) COLUMNS (a.vname AS an SELECT * FROM GRAPH_TABLE (g1 MATCH (a)-[c]-(a) COLUMNS (a.vname AS self, c.ename AS loop_name)); SELECT * FROM GRAPH_TABLE (g1 MATCH (a)-(a) COLUMNS (a.vname AS self)); --- test collation specified in the expression +-- test explicit and implicit collation assignment INSERT INTO e3_3 VALUES (2003, 2003, 'E331', 10011); -SELECT * FROM GRAPH_TABLE (g1 MATCH (a)-[b]->(a)-[b]->(a) COLUMNS (a.vname AS self, b.ename AS loop_name)) ORDER BY loop_name COLLATE "C" ASC; +SELECT * FROM GRAPH_TABLE (g1 MATCH (a)-[b]->(a)-[b]->(a) COLUMNS (upper(a.vname) AS self, b.ename AS loop_name)) ORDER BY loop_name COLLATE "C" ASC; SELECT * FROM GRAPH_TABLE (g1 MATCH (a)-[b IS el2 WHERE b.ename > 'E331' COLLATE "C"]->(a)-[b]->(a) COLUMNS (a.vname AS self, b.ename AS loop_name)); SELECT * FROM GRAPH_TABLE (g1 MATCH (a)-[b]->(a)-[b]->(a) WHERE b.ename > 'E331' COLLATE "C" COLUMNS (a.vname AS self, b.ename AS loop_name)); SELECT * FROM GRAPH_TABLE (g1 MATCH (a)-[b]->(a)-[b]->(a) COLUMNS (a.vname AS self, b.ename AS loop_name)) WHERE loop_name > 'E331' COLLATE "C"; @@ -525,7 +524,16 @@ SELECT * FROM GRAPH_TABLE (g4 MATCH (s)-[e]-(d) WHERE s.id = 3 COLUMNS (s.val, e SELECT * FROM GRAPH_TABLE (g4 MATCH (s WHERE s.id = 3)-[e]-(d) COLUMNS (s.val, e.val, d.val)) ORDER BY 1, 2, 3; -- ruleutils reverse parsing -CREATE VIEW customers_us AS SELECT * FROM GRAPH_TABLE (myshop MATCH (c IS customers WHERE c.address = 'US')-[IS customer_orders | customer_wishlists ]->(l IS orders | wishlists)-[ IS list_items]->(p IS products) COLUMNS (c.name AS customer_name, p.name AS product_name)) ORDER BY customer_name, product_name; +-- The query in the view definition is intentionally complex to test one view with many +-- features like label disjunction, lateral references, WHERE clauses in graph +-- patterns. +CREATE VIEW customers_us AS +SELECT g.* FROM x1, + GRAPH_TABLE (myshop MATCH (c IS customers WHERE c.address = 'US' AND c.customer_id = x1.a) + -[IS customer_orders | customer_wishlists ]-> + (l IS orders | wishlists)-[ IS list_items]->(p IS products) + COLUMNS (c.name AS customer_name, p.name AS product_name, x1.a AS a)) g + ORDER BY customer_name, product_name; SELECT pg_get_viewdef('customers_us'::regclass); -- test view/graph nesting diff --git a/postgres/regression_suite/indexing.sql b/postgres/regression_suite/indexing.sql index 0258e900..fe86d66d 100644 --- a/postgres/regression_suite/indexing.sql +++ b/postgres/regression_suite/indexing.sql @@ -246,6 +246,65 @@ select relname, indisvalid from pg_class join pg_index on indexrelid = oid where relname like 'idxpart%' order by relname; drop table idxpart; +-- Verify that re-attaching an already-attached partition index can +-- validate the parent index if it was still invalid, including +-- indirect ancestors in subpartitions. +create table idxpart (a int, b int) partition by range (a); +create table idxpart1 partition of idxpart for values from (0) to (1000) partition by range (a); +create table idxpart11 partition of idxpart1 for values from (0) to (500); +-- Partitioned table with no partitions +create table idxpart2 partition of idxpart for values from (1000) to (2000) partition by range (a); +-- create parent indexes +create index on only idxpart ((a/b)); +create index on only idxpart1 ((a/b)); +create index on only idxpart2 ((a/b)); +-- fail, leaves behind an invalid index on the leaf partition +insert into idxpart11 values (1, 0); +create index concurrently on idxpart11 ((a/b)); +select relname, indisvalid from pg_class join pg_index on indexrelid = oid + where relname like 'idxpart%' order by relname; +-- attach the indexes; parents stay invalid +alter index idxpart1_expr_idx attach partition idxpart11_expr_idx; +alter index idxpart_expr_idx attach partition idxpart1_expr_idx; +alter index idxpart_expr_idx attach partition idxpart2_expr_idx; +select relname, indisvalid from pg_class join pg_index on indexrelid = oid + where relname like 'idxpart%' order by relname; +-- fix the index on the leaf partition +delete from idxpart11 where b = 0; +reindex index concurrently idxpart11_expr_idx; +-- reattach the leaf partition index; parents should now be valid +alter index idxpart1_expr_idx attach partition idxpart11_expr_idx; +select relname, indisvalid from pg_class join pg_index on indexrelid = oid + where relname like 'idxpart%' order by relname; +drop table idxpart; + +-- Verify that re-attaching does not validate the parent when another +-- child index is still invalid. +create table idxpart (a int, b int) partition by range (a); +create table idxpart1 partition of idxpart for values from (0) to (500); +create table idxpart2 partition of idxpart for values from (500) to (1000); +create index on only idxpart ((a/b)); +-- create invalid indexes on both children +insert into idxpart1 values (1, 0); +insert into idxpart2 values (501, 0); +create index concurrently on idxpart1 ((a/b)); +create index concurrently on idxpart2 ((a/b)); +select relname, indisvalid from pg_class join pg_index on indexrelid = oid + where relname like 'idxpart%' order by relname; +-- attach both; parent stays invalid +alter index idxpart_expr_idx attach partition idxpart1_expr_idx; +alter index idxpart_expr_idx attach partition idxpart2_expr_idx; +select relname, indisvalid from pg_class join pg_index on indexrelid = oid + where relname like 'idxpart%' order by relname; +-- fix only idxpart1's index, leave idxpart2's still invalid +delete from idxpart1 where b = 0; +reindex index concurrently idxpart1_expr_idx; +-- re-attach the fixed child; parent should stay invalid +alter index idxpart_expr_idx attach partition idxpart1_expr_idx; +select relname, indisvalid from pg_class join pg_index on indexrelid = oid + where relname like 'idxpart%' order by relname; +drop table idxpart; + -- verify dependency handling during ALTER TABLE DETACH PARTITION create table idxpart (a int) partition by range (a); create table idxpart1 (like idxpart); diff --git a/postgres/regression_suite/join.sql b/postgres/regression_suite/join.sql index 30b479dd..fae19113 100644 --- a/postgres/regression_suite/join.sql +++ b/postgres/regression_suite/join.sql @@ -2271,6 +2271,29 @@ from int8_tbl t1 left join onek t4 on t2.q2 < t3.unique2; +-- bug #19460: we need to clean up RestrictInfos more than we had been doing +explain (costs off) +select * from + (select 1::int as id) as lhs +full join + (select dummy_source.id + from (select null::int as id) as dummy_source + left join (select a.id from a where a.id = 42) as sub + on sub.id = dummy_source.id + ) as rhs +on lhs.id = rhs.id; + +explain (costs off) +select * from + (select 1::int as id) as lhs +full join + (select dummy_source.id + from (select 2::int as id) as dummy_source + left join (select a.id from a) as sub + on sub.id = dummy_source.id + ) as rhs +on lhs.id = rhs.id; + -- More tests of correct placement of pseudoconstant quals -- simple constant-false condition diff --git a/postgres/regression_suite/multirangetypes.sql b/postgres/regression_suite/multirangetypes.sql index 412e7a83..c715d830 100644 --- a/postgres/regression_suite/multirangetypes.sql +++ b/postgres/regression_suite/multirangetypes.sql @@ -837,6 +837,9 @@ select *, row_to_json(upper(t)) as u from (values (two_ints_multirange(two_ints_range(row(1,2), row(3,4)))), (two_ints_multirange(two_ints_range(row(5,6), row(7,8))))) v(t); +-- this must be rejected to avoid self-inclusion issues: +alter type two_ints add attribute c two_ints_multirange; + drop type two_ints cascade; -- diff --git a/postgres/regression_suite/plancache.sql b/postgres/regression_suite/plancache.sql index 4b2f11dc..aed388d0 100644 --- a/postgres/regression_suite/plancache.sql +++ b/postgres/regression_suite/plancache.sql @@ -180,10 +180,15 @@ deallocate pstmt_def_insert; -- Test plan_cache_mode -create table test_mode (a int); +create table test_mode (a int) with (autovacuum_enabled = false); insert into test_mode select 1 from generate_series(1,1000) union all select 2; -create index on test_mode (a); + +-- ANALYZE before creating the index. CREATE INDEX scans the table, which may +-- set pages all-visible via on-access pruning. If relallvisible is then updated +-- by ANALYZE, the generic plan may pick an index-only scan instead of the +-- expected sequential scan. analyze test_mode; +create index on test_mode (a); prepare test_mode_pp (int) as select count(*) from test_mode where a = $1; select name, generic_plans, custom_plans from pg_prepared_statements diff --git a/postgres/regression_suite/portals.sql b/postgres/regression_suite/portals.sql index fc4cccb9..196b862c 100644 --- a/postgres/regression_suite/portals.sql +++ b/postgres/regression_suite/portals.sql @@ -508,6 +508,11 @@ DECLARE c1 CURSOR FOR SELECT * FROM ucview; FETCH FROM c1; DELETE FROM ucview WHERE CURRENT OF c1; -- fail, views not supported ROLLBACK; +BEGIN; +DECLARE c1 CURSOR FOR SELECT * FROM ucview; +FETCH FROM c1; +UPDATE ucview SET f1 = f1 + 10 WHERE CURRENT OF c1; -- fail, views not supported +ROLLBACK; -- Check WHERE CURRENT OF with an index-only scan BEGIN; diff --git a/postgres/regression_suite/publication.sql b/postgres/regression_suite/publication.sql index 7f83bed4..f8c06df2 100644 --- a/postgres/regression_suite/publication.sql +++ b/postgres/regression_suite/publication.sql @@ -76,6 +76,8 @@ ALTER PUBLICATION testpub_fortable SET TABLES IN SCHEMA pub_test; SET client_min_messages = 'ERROR'; CREATE PUBLICATION testpub_forschema FOR TABLES IN SCHEMA pub_test; +-- \dn pub_test + -- should be able to create publication with schema and table of the same -- schema CREATE PUBLICATION testpub_for_tbl_schema FOR TABLES IN SCHEMA pub_test, TABLE pub_test.testpub_nopk; diff --git a/postgres/regression_suite/role_ddl.sql b/postgres/regression_suite/role_ddl.sql deleted file mode 100644 index 2fb95219..00000000 --- a/postgres/regression_suite/role_ddl.sql +++ /dev/null @@ -1,96 +0,0 @@ --- Consistent test results -SET timezone TO 'UTC'; -SET DateStyle TO 'ISO, YMD'; - --- Create test database -CREATE DATABASE regression_role_ddl_test; - --- Basic role -CREATE ROLE regress_role_ddl_test1; -SELECT * FROM pg_get_role_ddl('regress_role_ddl_test1'); - --- Role with LOGIN -CREATE ROLE regress_role_ddl_test2 LOGIN; -SELECT * FROM pg_get_role_ddl('regress_role_ddl_test2'); - --- Role with multiple privileges -CREATE ROLE regress_role_ddl_test3 - LOGIN - SUPERUSER - CREATEDB - CREATEROLE - CONNECTION LIMIT 5 - VALID UNTIL '2030-12-31 23:59:59+00'; -SELECT * FROM pg_get_role_ddl('regress_role_ddl_test3'); - --- Role with configuration parameters -CREATE ROLE regress_role_ddl_test4; -ALTER ROLE regress_role_ddl_test4 SET work_mem TO '256MB'; -ALTER ROLE regress_role_ddl_test4 SET search_path TO myschema, public; -SELECT * FROM pg_get_role_ddl('regress_role_ddl_test4'); - --- Role with database-specific configuration -CREATE ROLE regress_role_ddl_test5; -ALTER ROLE regress_role_ddl_test5 IN DATABASE regression_role_ddl_test SET work_mem TO '128MB'; -SELECT * FROM pg_get_role_ddl('regress_role_ddl_test5'); - --- Role with special characters (requires quoting) -CREATE ROLE "regress_role-with-dash"; -SELECT * FROM pg_get_role_ddl('regress_role-with-dash'); - --- Pretty-printed output --- \pset format unaligned -SELECT * FROM pg_get_role_ddl('regress_role_ddl_test3', 'pretty', 'true'); --- \pset format aligned - --- Role with memberships -CREATE ROLE regress_role_ddl_grantor CREATEROLE; -CREATE ROLE regress_role_ddl_group1; -CREATE ROLE regress_role_ddl_group2; -CREATE ROLE regress_role_ddl_member; -GRANT regress_role_ddl_group1 TO regress_role_ddl_grantor WITH ADMIN TRUE; -GRANT regress_role_ddl_group2 TO regress_role_ddl_grantor WITH ADMIN TRUE; -SET ROLE regress_role_ddl_grantor; -GRANT regress_role_ddl_group1 TO regress_role_ddl_member WITH INHERIT TRUE, SET FALSE; -GRANT regress_role_ddl_group2 TO regress_role_ddl_member WITH ADMIN TRUE; -RESET ROLE; -SELECT * FROM pg_get_role_ddl('regress_role_ddl_member'); - --- Role with memberships suppressed -SELECT * FROM pg_get_role_ddl('regress_role_ddl_member', 'memberships', 'false'); - --- Non-existent role (should error) -SELECT * FROM pg_get_role_ddl(9999999::oid); - --- NULL input (should return no rows) -SELECT * FROM pg_get_role_ddl(NULL); - --- Permission check: revoke SELECT on pg_authid -CREATE ROLE regress_role_ddl_noaccess; -REVOKE SELECT ON pg_authid FROM PUBLIC; -SET ROLE regress_role_ddl_noaccess; -SELECT * FROM pg_get_role_ddl('regress_role_ddl_test1'); -- should fail -RESET ROLE; -GRANT SELECT ON pg_authid TO PUBLIC; -DROP ROLE regress_role_ddl_noaccess; - --- Cleanup -DROP ROLE regress_role_ddl_test1; -DROP ROLE regress_role_ddl_test2; -DROP ROLE regress_role_ddl_test3; -DROP ROLE regress_role_ddl_test4; -DROP ROLE regress_role_ddl_test5; -DROP ROLE "regress_role-with-dash"; -SET ROLE regress_role_ddl_grantor; -REVOKE regress_role_ddl_group1 FROM regress_role_ddl_member; -REVOKE regress_role_ddl_group2 FROM regress_role_ddl_member; -RESET ROLE; -DROP ROLE regress_role_ddl_member; -DROP ROLE regress_role_ddl_group1; -DROP ROLE regress_role_ddl_group2; -DROP ROLE regress_role_ddl_grantor; - -DROP DATABASE regression_role_ddl_test; - --- Reset timezone to default -RESET timezone; diff --git a/postgres/regression_suite/select_into.sql b/postgres/regression_suite/select_into.sql index f71e3940..a5e29201 100644 --- a/postgres/regression_suite/select_into.sql +++ b/postgres/regression_suite/select_into.sql @@ -110,11 +110,11 @@ DROP TABLE easi, easi2; -- -- Disallowed uses of SELECT ... INTO. All should fail -- -DECLARE foo CURSOR FOR SELECT 1 INTO int4_tbl; -COPY (SELECT 1 INTO frak UNION SELECT 2) TO 'blob'; -SELECT * FROM (SELECT 1 INTO f) bar; -CREATE VIEW foo AS SELECT 1 INTO int4_tbl; -INSERT INTO int4_tbl SELECT 1 INTO f; +-- DECLARE foo CURSOR FOR SELECT 1 INTO int4_tbl; +-- COPY (SELECT 1 INTO frak UNION SELECT 2) TO 'blob'; +-- SELECT * FROM (SELECT 1 INTO f) bar; +-- CREATE VIEW foo AS SELECT 1 INTO int4_tbl; +-- INSERT INTO int4_tbl SELECT 1 INTO f; -- Test CREATE TABLE AS ... IF NOT EXISTS CREATE TABLE ctas_ine_tbl AS SELECT 1; diff --git a/postgres/regression_suite/sqljson.sql b/postgres/regression_suite/sqljson.sql index 894a2301..22242aa2 100644 --- a/postgres/regression_suite/sqljson.sql +++ b/postgres/regression_suite/sqljson.sql @@ -193,6 +193,7 @@ SELECT JSON_ARRAY(JSON_ARRAY('{ "a" : 123 }' RETURNING text)); SELECT JSON_ARRAY(JSON_ARRAY('{ "a" : 123 }' FORMAT JSON RETURNING text)); SELECT JSON_ARRAY(JSON_ARRAY('{ "a" : 123 }' FORMAT JSON RETURNING text) FORMAT JSON); +-- JSON_ARRAY(subquery) SELECT JSON_ARRAY(SELECT i FROM (VALUES (1), (2), (NULL), (4)) foo(i)); SELECT JSON_ARRAY(SELECT i FROM (VALUES (NULL::int[]), ('{1,2}'), (NULL), (NULL), ('{3,4}'), (NULL)) foo(i)); SELECT JSON_ARRAY(SELECT i FROM (VALUES (NULL::int[]), ('{1,2}'), (NULL), (NULL), ('{3,4}'), (NULL)) foo(i) RETURNING jsonb); @@ -201,6 +202,17 @@ SELECT JSON_ARRAY(SELECT i FROM (VALUES (NULL::int[]), ('{1,2}'), (NULL), (NULL) SELECT JSON_ARRAY(SELECT i FROM (VALUES (3), (1), (NULL), (2)) foo(i) ORDER BY i); SELECT JSON_ARRAY(WITH x AS (SELECT 1) VALUES (TRUE)); +-- JSON_ARRAY(subquery) with empty result set +SELECT JSON_ARRAY(SELECT 1 WHERE FALSE); +SELECT JSON_ARRAY(SELECT i FROM (VALUES (1), (2), (NULL), (4)) foo(i) WHERE i > 4); + +-- JSON_ARRAY(subquery) with a correlated subquery in the WHERE clause +SELECT * FROM (VALUES (1), (2), (NULL), (4)) t1(a) +WHERE JSON_ARRAY( + SELECT b FROM (VALUES (1), (2), (3)) t2(b) WHERE b = t1.a + RETURNING jsonb +) = '[]'::jsonb; + -- Should fail SELECT JSON_ARRAY(SELECT FROM (VALUES (1)) foo(i)); SELECT JSON_ARRAY(SELECT i, i FROM (VALUES (1)) foo(i)); @@ -384,6 +396,43 @@ SELECT JSON_ARRAY(SELECT i FROM (VALUES (1), (2), (NULL), (4)) foo(i) RETURNING -- \sv json_array_subquery_view +EXPLAIN (VERBOSE, COSTS OFF) +SELECT JSON_ARRAY(SELECT i FROM (VALUES (1), (2), (NULL), (4)) foo(i) ORDER BY i LIMIT 3 RETURNING jsonb); + +CREATE OR REPLACE VIEW json_array_subquery_view AS +SELECT JSON_ARRAY(SELECT i FROM (VALUES (1), (2), (NULL), (4)) foo(i) ORDER BY i LIMIT 3 RETURNING jsonb); + +-- \sv json_array_subquery_view + +DROP VIEW json_array_subquery_view; + +EXPLAIN (VERBOSE, COSTS OFF) +SELECT * FROM (VALUES (1), (2), (NULL), (4)) t1(a) +WHERE JSON_ARRAY( + SELECT b FROM (VALUES (1), (2), (3)) t2(b) WHERE b = t1.a + RETURNING jsonb +) = '[]'::jsonb; + +CREATE VIEW json_array_subquery_view AS +SELECT * FROM (VALUES (1), (2), (NULL), (4)) t1(a) +WHERE JSON_ARRAY( + SELECT b FROM (VALUES (1), (2), (3)) t2(b) WHERE b = t1.a + RETURNING jsonb +) = '[]'::jsonb; + +-- \sv json_array_subquery_view + +DROP VIEW json_array_subquery_view; + +-- JSON_ARRAY(subquery) with RETURNING text +EXPLAIN (VERBOSE, COSTS OFF) +SELECT JSON_ARRAY(SELECT i FROM (VALUES (1), (2), (NULL), (4)) foo(i) RETURNING text); + +CREATE VIEW json_array_subquery_view AS +SELECT JSON_ARRAY(SELECT i FROM (VALUES (1), (2), (NULL), (4)) foo(i) RETURNING text); + +-- \sv json_array_subquery_view + DROP VIEW json_array_subquery_view; -- Test mutability of JSON_OBJECTAGG, JSON_ARRAYAGG, JSON_ARRAY, JSON_OBJECT diff --git a/postgres/regression_suite/tablespace_ddl.sql b/postgres/regression_suite/tablespace_ddl.sql deleted file mode 100644 index 62a963df..00000000 --- a/postgres/regression_suite/tablespace_ddl.sql +++ /dev/null @@ -1,58 +0,0 @@ --- --- Tests for pg_get_tablespace_ddl() --- - -SET allow_in_place_tablespaces = true; -CREATE ROLE regress_tblspc_ddl_user; - --- error: non-existent tablespace by name -SELECT * FROM pg_get_tablespace_ddl('regress_nonexistent_tblsp'); - --- error: non-existent tablespace by OID -SELECT * FROM pg_get_tablespace_ddl(0::oid); - --- NULL input returns no rows (name variant) -SELECT * FROM pg_get_tablespace_ddl(NULL::name); - --- NULL input returns no rows (OID variant) -SELECT * FROM pg_get_tablespace_ddl(NULL::oid); - --- tablespace name requiring quoting -CREATE TABLESPACE "regress_ tblsp" OWNER regress_tblspc_ddl_user LOCATION ''; -SELECT * FROM pg_get_tablespace_ddl('regress_ tblsp'); -DROP TABLESPACE "regress_ tblsp"; - --- tablespace with multiple options -CREATE TABLESPACE regress_allopt_tblsp OWNER regress_tblspc_ddl_user LOCATION '' - WITH (seq_page_cost = '1.5', random_page_cost = '1.1234567890', - effective_io_concurrency = '17', maintenance_io_concurrency = '18'); -SELECT * FROM pg_get_tablespace_ddl('regress_allopt_tblsp'); - --- pretty-printed output --- \pset format unaligned -SELECT * FROM pg_get_tablespace_ddl('regress_allopt_tblsp', 'pretty', 'true'); --- \pset format aligned - --- tablespace with owner suppressed -SELECT * FROM pg_get_tablespace_ddl('regress_allopt_tblsp', 'owner', 'false'); - -DROP TABLESPACE regress_allopt_tblsp; - --- test by OID -CREATE TABLESPACE regress_oid_tblsp OWNER regress_tblspc_ddl_user LOCATION ''; -SELECT oid AS tsid FROM pg_tablespace WHERE spcname = 'regress_oid_tblsp' /* \gset */; -SELECT * FROM pg_get_tablespace_ddl('tsid'); -DROP TABLESPACE regress_oid_tblsp; - --- Permission check: revoke SELECT on pg_tablespace -CREATE TABLESPACE regress_acl_tblsp OWNER regress_tblspc_ddl_user LOCATION ''; -CREATE ROLE regress_tblspc_ddl_noaccess; -REVOKE SELECT ON pg_tablespace FROM PUBLIC; -SET ROLE regress_tblspc_ddl_noaccess; -SELECT * FROM pg_get_tablespace_ddl('regress_acl_tblsp'); -- should fail -RESET ROLE; -GRANT SELECT ON pg_tablespace TO PUBLIC; -DROP TABLESPACE regress_acl_tblsp; -DROP ROLE regress_tblspc_ddl_noaccess; - -DROP ROLE regress_tblspc_ddl_user;