diff --git a/crates/squawk_parser/src/generated/syntax_kind.rs b/crates/squawk_parser/src/generated/syntax_kind.rs index 2b2ea529..0a1899eb 100644 --- a/crates/squawk_parser/src/generated/syntax_kind.rs +++ b/crates/squawk_parser/src/generated/syntax_kind.rs @@ -790,6 +790,7 @@ pub enum SyntaxKind { EVENT_TRIGGER_WHEN, EVENT_TRIGGER_WHEN_CLAUSE, EXCEPT_TABLES, + EXCEPT_TABLE_CLAUSE, EXCLUDE_CONSTRAINT, EXECUTE, EXISTS_FN, diff --git a/crates/squawk_parser/src/grammar.rs b/crates/squawk_parser/src/grammar.rs index 00149952..c4f7fe82 100644 --- a/crates/squawk_parser/src/grammar.rs +++ b/crates/squawk_parser/src/grammar.rs @@ -9173,7 +9173,7 @@ fn publication_object(p: &mut Parser<'_>) { } // CREATE PUBLICATION name -// [ FOR ALL TABLES +// [ FOR ALL TABLES [ EXCEPT TABLE ( relation_name [, ...] ) ] // | FOR publication_object [, ... ] ] // [ WITH ( publication_parameter [= value] [, ... ] ) ] // @@ -9192,6 +9192,7 @@ fn create_publication(p: &mut Parser<'_>) -> CompletedMarker { while !p.at(EOF) && p.eat(COMMA) { publication_all_object(p); } + opt_except_table_clause(p); } else { publication_object(p); while !p.at(EOF) && p.eat(COMMA) { @@ -9213,6 +9214,26 @@ fn publication_all_object(p: &mut Parser<'_>) { } } +fn opt_except_table_clause(p: &mut Parser<'_>) { + if !p.at(EXCEPT_KW) { + return; + } + + let m = p.start(); + p.bump(EXCEPT_KW); + p.expect(TABLE_KW); + delimited( + p, + L_PAREN, + R_PAREN, + COMMA, + || "unexpected comma".to_string(), + RELATION_NAME_FIRST, + |p| opt_relation_name(p).is_some(), + ); + m.complete(p, EXCEPT_TABLE_CLAUSE); +} + // CREATE ROLE name [ [ WITH ] option [ ... ] ] // where option can be: // SUPERUSER | NOSUPERUSER @@ -13656,6 +13677,8 @@ const NON_RESERVED_WORD: TokenSet = TokenSet::new(&[IDENT]) .union(COLUMN_NAME_KEYWORDS) .union(TYPE_FUNC_NAME_KEYWORDS); +const RELATION_NAME_FIRST: TokenSet = TokenSet::new(&[ONLY_KW]).union(PATH_FIRST); + fn relation_name(p: &mut Parser<'_>) { if opt_relation_name(p).is_none() { p.error("expected relation name"); @@ -13663,6 +13686,10 @@ fn relation_name(p: &mut Parser<'_>) { } fn opt_relation_name(p: &mut Parser<'_>) -> Option { + if !p.at_ts(RELATION_NAME_FIRST) { + return None; + } + let m = p.start(); if p.eat(ONLY_KW) { let trailing_paren = p.eat(L_PAREN); @@ -13672,10 +13699,7 @@ fn opt_relation_name(p: &mut Parser<'_>) -> Option { p.expect(R_PAREN); } } else { - if opt_path_name_ref(p).is_none() { - m.abandon(p); - return None; - }; + path_name_ref(p); p.eat(STAR); } Some(m.complete(p, RELATION_NAME)) diff --git a/crates/squawk_syntax/src/ast/generated/nodes.rs b/crates/squawk_syntax/src/ast/generated/nodes.rs index d272639d..e6db267a 100644 --- a/crates/squawk_syntax/src/ast/generated/nodes.rs +++ b/crates/squawk_syntax/src/ast/generated/nodes.rs @@ -4295,6 +4295,10 @@ pub struct CreatePublication { pub(crate) syntax: SyntaxNode, } impl CreatePublication { + #[inline] + pub fn except_table_clause(&self) -> Option { + support::child(&self.syntax) + } #[inline] pub fn name(&self) -> Option { support::child(&self.syntax) @@ -7560,6 +7564,33 @@ impl EventTriggerWhenClause { } } +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct ExceptTableClause { + pub(crate) syntax: SyntaxNode, +} +impl ExceptTableClause { + #[inline] + pub fn relation_names(&self) -> AstChildren { + support::children(&self.syntax) + } + #[inline] + pub fn l_paren_token(&self) -> Option { + support::token(&self.syntax, SyntaxKind::L_PAREN) + } + #[inline] + pub fn r_paren_token(&self) -> Option { + support::token(&self.syntax, SyntaxKind::R_PAREN) + } + #[inline] + pub fn except_token(&self) -> Option { + support::token(&self.syntax, SyntaxKind::EXCEPT_KW) + } + #[inline] + pub fn table_token(&self) -> Option { + support::token(&self.syntax, SyntaxKind::TABLE_KW) + } +} + #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct ExceptTables { pub(crate) syntax: SyntaxNode, @@ -21811,6 +21842,24 @@ impl AstNode for EventTriggerWhenClause { &self.syntax } } +impl AstNode for ExceptTableClause { + #[inline] + fn can_cast(kind: SyntaxKind) -> bool { + kind == SyntaxKind::EXCEPT_TABLE_CLAUSE + } + #[inline] + fn cast(syntax: SyntaxNode) -> Option { + if Self::can_cast(syntax.kind()) { + Some(Self { syntax }) + } else { + None + } + } + #[inline] + fn syntax(&self) -> &SyntaxNode { + &self.syntax + } +} impl AstNode for ExceptTables { #[inline] fn can_cast(kind: SyntaxKind) -> bool { diff --git a/crates/squawk_syntax/src/postgresql.ungram b/crates/squawk_syntax/src/postgresql.ungram index a4a63bbe..5ef67e5c 100644 --- a/crates/squawk_syntax/src/postgresql.ungram +++ b/crates/squawk_syntax/src/postgresql.ungram @@ -2300,9 +2300,12 @@ CreateProcedure = CreatePublication = 'create' 'publication' Name - ('for' 'all' 'tables' | 'for' (PublicationObject (',' PublicationObject)*)) + ('for' 'all' 'tables' ExceptTableClause? | 'for' (PublicationObject (',' PublicationObject)*)) WithParams? +ExceptTableClause = + 'except' 'table' '(' (RelationName (',' RelationName)*) ')' + PublicationObject = 'table' 'only'? (Path | '(' Path ')') '*'? ColumnList? WhereConditionClause? | 'tables' 'in' 'schema' ('current_schema' | NameRef) WhereConditionClause? diff --git a/postgres/kwlist.h b/postgres/kwlist.h index 2492dc72..f0a44291 100644 --- a/postgres/kwlist.h +++ b/postgres/kwlist.h @@ -1,7 +1,7 @@ // synced from: -// commit: ef3c3cf6d021ff9884c513afd850a9fe85cad736 -// committed at: 2026-02-14T06:50:06Z -// file: https://github.com/postgres/postgres/blob/ef3c3cf6d021ff9884c513afd850a9fe85cad736/src/include/parser/kwlist.h +// commit: f95d73ed433207c4323802dc96e52f3e5553a86c +// committed at: 2026-03-05T22:43:09Z +// file: https://github.com/postgres/postgres/blob/f95d73ed433207c4323802dc96e52f3e5553a86c/src/include/parser/kwlist.h // // update via: // cargo xtask sync-kwlist diff --git a/postgres/regression_suite/aggregates.sql b/postgres/regression_suite/aggregates.sql index 343eefcc..92d88ca7 100644 --- a/postgres/regression_suite/aggregates.sql +++ b/postgres/regression_suite/aggregates.sql @@ -649,6 +649,12 @@ select f2, count(*) from t1 x(x0,x1) left join (t1 left join t2 using(f2)) on (x0 = 0) group by f2; +-- check that we preserve join alias in GROUP BY expressions +create temp view v1 as +select f1::int from t1 left join t2 using (f1) group by f1; +select pg_get_viewdef('v1'::regclass); + +drop view v1; drop table t1, t2; -- diff --git a/postgres/regression_suite/alter_table.sql b/postgres/regression_suite/alter_table.sql index becb32db..055bb55a 100644 --- a/postgres/regression_suite/alter_table.sql +++ b/postgres/regression_suite/alter_table.sql @@ -2331,6 +2331,14 @@ ALTER TABLE test_add_column -- \d test_add_column ALTER TABLE test_add_column ADD COLUMN IF NOT EXISTS c5 SERIAL CHECK (c5 > 10); +ALTER TABLE test_add_column + ADD c6 integer; -- omit COLUMN +ALTER TABLE test_add_column + ADD IF NOT EXISTS c6 integer; +ALTER TABLE test_add_column + DROP c6; -- omit COLUMN +ALTER TABLE test_add_column + DROP IF EXISTS c6; -- \d test_add_column* DROP TABLE test_add_column; -- \d test_add_column* diff --git a/postgres/regression_suite/copy2.sql b/postgres/regression_suite/copy2.sql index 38afd408..81e3b0b7 100644 --- a/postgres/regression_suite/copy2.sql +++ b/postgres/regression_suite/copy2.sql @@ -67,12 +67,15 @@ COPY x from stdin (force_null (a), force_null (b)); COPY x from stdin (convert_selectively (a), convert_selectively (b)); COPY x from stdin (encoding 'sql_ascii', encoding 'sql_ascii'); COPY x from stdin (on_error ignore, on_error ignore); +COPY x from stdin (on_error set_null, on_error set_null); COPY x from stdin (log_verbosity default, log_verbosity verbose); -- -- -- incorrect options COPY x from stdin (format BINARY, delimiter ','); COPY x from stdin (format BINARY, null 'x'); COPY x from stdin (format BINARY, on_error ignore); +COPY x from stdin (format BINARY, on_error set_null); +COPY x from stdin (on_error set_null, reject_limit 2); COPY x from stdin (on_error unsupported); COPY x from stdin (format TEXT, force_quote(a)); COPY x from stdin (format TEXT, force_quote *); @@ -87,6 +90,7 @@ COPY x from stdin (format TEXT, force_null *); -- COPY x to stdout (format CSV, force_null(a)); COPY x to stdout (format CSV, force_null *); COPY x to stdout (format BINARY, on_error unsupported); +COPY x to stdout (on_error set_null); COPY x from stdin (log_verbosity unsupported); COPY x from stdin with (reject_limit 1); COPY x from stdin with (on_error ignore, reject_limit 0); @@ -540,6 +544,42 @@ COPY check_ign_err FROM STDIN WITH (on_error ignore, log_verbosity verbose); -- 8 {8} 8 -- \. +CREATE DOMAIN d_int_not_null AS integer NOT NULL CHECK (value > 0); +CREATE DOMAIN d_int_positive_maybe_null AS integer CHECK (value > 0); +CREATE TABLE t_on_error_null (a d_int_not_null, b d_int_positive_maybe_null, c integer); + +-- \pset null NULL +COPY t_on_error_null FROM STDIN WITH (on_error set_null); -- fail +-- \N 11 13 +-- \. + +COPY t_on_error_null FROM STDIN WITH (on_error set_null); -- fail +-- ss 11 14 +-- \. + +COPY t_on_error_null FROM STDIN WITH (on_error set_null); -- fail +-- -1 11 13 +-- \. + +-- fail, less data. +COPY t_on_error_null FROM STDIN WITH (delimiter ',', on_error set_null); +-- 1,1 +-- \. +-- fail, extra data. +COPY t_on_error_null FROM STDIN WITH (delimiter ',', on_error set_null); +-- 1,2,3,4 +-- \. + +COPY t_on_error_null FROM STDIN WITH (on_error set_null, log_verbosity verbose); -- ok +-- 10 x1 yx +-- 11 zx 12 +-- 13 14 ea +-- \. + +SELECT * FROM t_on_error_null ORDER BY a; + +-- \pset null '' + -- tests for on_error option with log_verbosity and null constraint via domain CREATE DOMAIN dcheck_ign_err2 varchar(15) NOT NULL; CREATE TABLE check_ign_err2 (n int, m int[], k int, l dcheck_ign_err2); @@ -609,6 +649,9 @@ DROP VIEW instead_of_insert_tbl_view; DROP VIEW instead_of_insert_tbl_view_2; DROP FUNCTION fun_instead_of_insert_tbl(); DROP TABLE check_ign_err; +DROP TABLE t_on_error_null; +DROP DOMAIN d_int_not_null; +DROP DOMAIN d_int_positive_maybe_null; DROP TABLE check_ign_err2; DROP DOMAIN dcheck_ign_err2; DROP TABLE hard_err; diff --git a/postgres/regression_suite/create_index.sql b/postgres/regression_suite/create_index.sql index 0fafe539..62e496be 100644 --- a/postgres/regression_suite/create_index.sql +++ b/postgres/regression_suite/create_index.sql @@ -45,6 +45,10 @@ CREATE INDEX six ON shighway USING btree (name text_ops); COMMENT ON INDEX six_wrong IS 'bad index'; COMMENT ON INDEX six IS 'good index'; COMMENT ON INDEX six IS NULL; +SELECT obj_description('six'::regclass, 'pg_class') IS NULL AS six_comment_is_null; +COMMENT ON INDEX six IS 'add the comment back'; +COMMENT ON INDEX six IS ''; -- empty string removes the comment, same as NULL +SELECT obj_description('six'::regclass, 'pg_class') IS NULL AS six_comment_is_null; -- -- BTREE partial indices diff --git a/postgres/regression_suite/create_role.sql b/postgres/regression_suite/create_role.sql index 4491a28a..b22f9c6f 100644 --- a/postgres/regression_suite/create_role.sql +++ b/postgres/regression_suite/create_role.sql @@ -92,6 +92,13 @@ CREATE ROLE regress_hasprivs CREATEROLE LOGIN INHERIT CONNECTION LIMIT 5; -- ok, we should be able to modify a role we created COMMENT ON ROLE regress_hasprivs IS 'some comment'; +SELECT shobj_description('regress_hasprivs'::regrole, 'pg_authid') IS NOT NULL AS has_comment; +COMMENT ON ROLE regress_hasprivs IS NULL; +SELECT shobj_description('regress_hasprivs'::regrole, 'pg_authid') IS NULL AS no_comment; +COMMENT ON ROLE regress_hasprivs IS 'add the comment back'; +SELECT shobj_description('regress_hasprivs'::regrole, 'pg_authid') IS NOT NULL AS has_comment; +COMMENT ON ROLE regress_hasprivs IS ''; -- empty string removes the comment, same as NULL +SELECT shobj_description('regress_hasprivs'::regrole, 'pg_authid') IS NULL AS no_comment; ALTER ROLE regress_hasprivs RENAME TO regress_tenant; ALTER ROLE regress_tenant NOINHERIT NOLOGIN CONNECTION LIMIT 7; diff --git a/postgres/regression_suite/create_table.sql b/postgres/regression_suite/create_table.sql index 81477546..6851d420 100644 --- a/postgres/regression_suite/create_table.sql +++ b/postgres/regression_suite/create_table.sql @@ -105,6 +105,13 @@ SAVEPOINT q; DROP TABLE remember_node_subid; ROLLBACK TO q; COMMIT; DROP TABLE remember_node_subid; +-- generated NOT NULL constraint names must not collide with explicitly named constraints +CREATE TABLE two_not_null_constraints ( + col integer NOT NULL, + CONSTRAINT two_not_null_constraints_col_not_null CHECK (col IS NOT NULL) +); +DROP TABLE two_not_null_constraints; + -- -- Partitioned tables -- diff --git a/postgres/regression_suite/encoding.sql b/postgres/regression_suite/encoding.sql index b5c85c0f..2071729d 100644 --- a/postgres/regression_suite/encoding.sql +++ b/postgres/regression_suite/encoding.sql @@ -40,7 +40,8 @@ SELECT reverse(good) FROM regress_encoding; -- invalid short mb character = error SELECT length(truncated) FROM regress_encoding; -SELECT substring(truncated, 1, 1) FROM regress_encoding; +SELECT substring(truncated, 1, 3) FROM regress_encoding; +SELECT substring(truncated, 1, 4) FROM regress_encoding; SELECT reverse(truncated) FROM regress_encoding; -- invalid short mb character = silently dropped SELECT regexp_replace(truncated, '^caf(.)$', '\1') FROM regress_encoding; @@ -212,9 +213,27 @@ INSERT INTO encoding_tests VALUES SELECT COUNT(test_encoding(encoding, description, input)) > 0 FROM encoding_tests; +-- substring fetches a slice of a toasted value; unused tail of that slice is +-- an incomplete char (bug #19406) +CREATE TABLE toast_3b_utf8 (c text); +INSERT INTO toast_3b_utf8 VALUES (repeat(U&'\2026', 4000)); +SELECT SUBSTRING(c FROM 1 FOR 1) FROM toast_3b_utf8; +SELECT SUBSTRING(c FROM 4001 FOR 1) FROM toast_3b_utf8; +-- diagnose incomplete char iff within the substring +UPDATE toast_3b_utf8 SET c = c || test_bytea_to_text('\xe280'); +SELECT SUBSTRING(c FROM 4000 FOR 1) FROM toast_3b_utf8; +SELECT SUBSTRING(c FROM 4001 FOR 1) FROM toast_3b_utf8; +-- substring needing last byte of its slice_size +ALTER TABLE toast_3b_utf8 RENAME TO toast_4b_utf8; +UPDATE toast_4b_utf8 SET c = repeat(U&'\+01F680', 3000); +SELECT SUBSTRING(c FROM 3000 FOR 1) FROM toast_4b_utf8; + DROP TABLE encoding_tests; +DROP TABLE toast_4b_utf8; DROP FUNCTION test_encoding; +DROP FUNCTION test_wchars_to_text; DROP FUNCTION test_text_to_wchars; +DROP FUNCTION test_valid_server_encoding; DROP FUNCTION test_mblen_func; DROP FUNCTION test_bytea_to_text; DROP FUNCTION test_text_to_bytea; diff --git a/postgres/regression_suite/event_trigger.sql b/postgres/regression_suite/event_trigger.sql index c613c0cf..32e9bb58 100644 --- a/postgres/regression_suite/event_trigger.sql +++ b/postgres/regression_suite/event_trigger.sql @@ -324,7 +324,12 @@ CREATE SCHEMA evttrig CREATE TABLE one (col_a SERIAL PRIMARY KEY, col_b text DEFAULT 'forty two', col_c SERIAL) CREATE INDEX one_idx ON one (col_b) CREATE TABLE two (col_c INTEGER CHECK (col_c > 0) REFERENCES one DEFAULT 42) - CREATE TABLE id (col_d int NOT NULL GENERATED ALWAYS AS IDENTITY); + CREATE TABLE id (col_d int NOT NULL GENERATED ALWAYS AS IDENTITY) + CREATE VIEW one_view AS SELECT * FROM two; + +-- View with column additions +CREATE OR REPLACE VIEW evttrig.one_view AS SELECT * FROM evttrig.two, evttrig.id; +DROP VIEW evttrig.one_view; -- Partitioned tables with a partitioned index CREATE TABLE evttrig.parted ( diff --git a/postgres/regression_suite/generated_virtual.sql b/postgres/regression_suite/generated_virtual.sql index 36acc5ff..4c61ed8b 100644 --- a/postgres/regression_suite/generated_virtual.sql +++ b/postgres/regression_suite/generated_virtual.sql @@ -317,8 +317,13 @@ CREATE TABLE gtest20 (a int PRIMARY KEY, b int GENERATED ALWAYS AS (a * 2) VIRTU INSERT INTO gtest20 (a) VALUES (10); -- ok INSERT INTO gtest20 (a) VALUES (30); -- violates constraint -ALTER TABLE gtest20 ALTER COLUMN b SET EXPRESSION AS (a * 100); -- violates constraint (currently not supported) -ALTER TABLE gtest20 ALTER COLUMN b SET EXPRESSION AS (a * 3); -- ok (currently not supported) +ALTER TABLE gtest20 ALTER COLUMN b SET EXPRESSION AS (a * 100); -- violates constraint +ALTER TABLE gtest20 ALTER COLUMN b SET EXPRESSION AS (a * 3); -- ok +-- table rewrite should not happen +SELECT pg_relation_filenode('gtest20') AS gtest20_filenode /* \gset */; +ALTER TABLE gtest20 ALTER COLUMN b SET EXPRESSION AS (a * 4), ADD COLUMN c INT DEFAULT 11; +SELECT pg_relation_filenode('gtest20') = 'gtest20_filenode' AS is_same_file; +-- \d gtest20 CREATE TABLE gtest20a (a int PRIMARY KEY, b int GENERATED ALWAYS AS (a * 2) VIRTUAL); INSERT INTO gtest20a (a) VALUES (10); @@ -536,6 +541,13 @@ ALTER TABLE gtest_child ALTER COLUMN f3 SET EXPRESSION AS (f2 * 10); -- \d gtest_child3 SELECT tableoid::regclass, * FROM gtest_parent ORDER BY 1, 2, 3; +-- check constraint was validated based on each partitions's generation expression +ALTER TABLE gtest_parent ADD CONSTRAINT cc1 CHECK (f3 < 19); -- error +ALTER TABLE gtest_parent ADD CONSTRAINT cc1 CHECK (f3 < 66); -- error +ALTER TABLE gtest_parent ADD CONSTRAINT cc1 CHECK (f3 <> 33); -- error +ALTER TABLE gtest_parent ADD CONSTRAINT cc CHECK (f3 < 67); -- ok +ALTER TABLE gtest_parent DROP CONSTRAINT cc; + -- alter generation expression of parent and all its children altogether ALTER TABLE gtest_parent ALTER COLUMN f3 SET EXPRESSION AS (f2 * 2); -- \d gtest_parent diff --git a/postgres/regression_suite/join.sql b/postgres/regression_suite/join.sql index 14cbec28..ad90c326 100644 --- a/postgres/regression_suite/join.sql +++ b/postgres/regression_suite/join.sql @@ -834,6 +834,25 @@ ORDER BY 1; reset enable_nestloop; +-- +-- test that estimate_hash_bucket_stats estimates correctly with skewed data +-- (we should choose to hash the filtered table) +-- + +create temp table skewedtable (val int not null, filt int not null); +insert into skewedtable +select + case when g <= 100 then 0 else (g % 100) + 1 end, + g % 10 +from generate_series(1, 1000) g; +analyze skewedtable; + +explain (costs off) +select * from skewedtable t1 join skewedtable t2 on t1.val = t2.val +where t1.filt = 5; + +drop table skewedtable; + -- -- basic semijoin and antijoin recognition tests -- @@ -1508,6 +1527,30 @@ select * from int4_tbl left join ( ) ss(x) on true where ss.x is null; +-- Test computation of varnullingrels when translating appendrel Var +begin; + +create temp table t_append (a int not null, b int); +insert into t_append values (1, 1); +insert into t_append values (2, 3); + +explain (verbose, costs off) +select t1.a, s.a from t_append t1 + left join t_append t2 on t1.a = t2.b + join lateral ( + select t1.a as a union all select t2.a as a + ) s on true +where s.a is not null; + +select t1.a, s.a from t_append t1 + left join t_append t2 on t1.a = t2.b + join lateral ( + select t1.a as a union all select t2.a as a + ) s on true +where s.a is not null; + +rollback; + -- -- test inlining of immutable functions -- diff --git a/postgres/regression_suite/prepared_xacts.sql b/postgres/regression_suite/prepared_xacts.sql index 8e681cca..227a04bf 100644 --- a/postgres/regression_suite/prepared_xacts.sql +++ b/postgres/regression_suite/prepared_xacts.sql @@ -1,3 +1,8 @@ +SELECT current_setting('max_prepared_transactions')::integer < 2 AS skip_test /* \gset */; +-- \if :skip_test +-- \quit +-- \endif + -- -- PREPARED TRANSACTIONS (two-phase commit) -- @@ -158,7 +163,32 @@ SELECT * FROM pxtest3; -- There should be no prepared transactions SELECT gid FROM pg_prepared_xacts WHERE gid ~ '^regress_' ORDER BY gid; + +-- Test row-level locks held by prepared transactions +CREATE TABLE pxtest_rowlock (id int PRIMARY KEY, data text); +INSERT INTO pxtest_rowlock VALUES (1, 'test data'); + +BEGIN; +SELECT * FROM pxtest_rowlock WHERE id = 1 FOR SHARE; +PREPARE TRANSACTION 'regress_p1'; + +-- Should fail because the row is locked +SELECT * FROM pxtest_rowlock WHERE id = 1 FOR UPDATE NOWAIT; + +-- Test prepared transactions that participate in multixacts. For +-- that, lock the same row again, creating a multixid. +BEGIN; +SELECT * FROM pxtest_rowlock WHERE id = 1 FOR SHARE; +PREPARE TRANSACTION 'regress_p2'; + +-- Should fail because the row is locked +SELECT * FROM pxtest_rowlock WHERE id = 1 FOR UPDATE NOWAIT; + +ROLLBACK PREPARED 'regress_p1'; +ROLLBACK PREPARED 'regress_p2'; + -- Clean up DROP TABLE pxtest2; -DROP TABLE pxtest3; -- will still be there if prepared xacts are disabled +-- pxtest3 was already dropped DROP TABLE pxtest4; +DROP TABLE pxtest_rowlock; diff --git a/postgres/regression_suite/privileges.sql b/postgres/regression_suite/privileges.sql index 15c6b735..65c124e5 100644 --- a/postgres/regression_suite/privileges.sql +++ b/postgres/regression_suite/privileges.sql @@ -1384,6 +1384,27 @@ SELECT loread(lo_open(1005, x'40000'::int), 32); SELECT lo_truncate(lo_open(1005, x'20000'::int), 10); -- to be denied SELECT lo_truncate(lo_open(2001, x'20000'::int), 10); +-- \c - +-- confirm role with privileges of pg_read_all_data can read large objects +SET SESSION AUTHORIZATION regress_priv_user6; + +SELECT loread(lo_open(1002, x'40000'::int), 32); +SELECT lo_get(1002); +SELECT lowrite(lo_open(1002, x'20000'::int), 'abcd'); -- to be denied +SELECT lo_put(1002, 1, 'abcd'); -- to be denied +SELECT lo_truncate(lo_open(1002, x'20000'::int), 0); -- to be denied +SELECT lo_unlink(1002); -- to be denied + +-- \c - +-- confirm role with privileges of pg_write_all_data can write large objects +GRANT SELECT ON LARGE OBJECT 1002 TO regress_priv_user7; +SET SESSION AUTHORIZATION regress_priv_user7; + +SELECT lowrite(lo_open(1002, x'20000'::int), 'abcd'); +SELECT lo_put(1002, 1, 'abcd'); +SELECT lo_truncate(lo_open(1002, x'20000'::int), 0); +SELECT lo_unlink(1002); -- to be denied + -- has_largeobject_privilege function -- superuser @@ -1619,7 +1640,7 @@ ALTER DEFAULT PRIVILEGES GRANT SELECT ON LARGE OBJECTS TO regress_priv_user2; SELECT lo_create(1008); SELECT has_largeobject_privilege('regress_priv_user2', 1008, 'SELECT'); -- yes -SELECT has_largeobject_privilege('regress_priv_user6', 1008, 'SELECT'); -- no +SELECT has_largeobject_privilege('regress_priv_user3', 1008, 'SELECT'); -- no SELECT has_largeobject_privilege('regress_priv_user2', 1008, 'UPDATE'); -- no ALTER DEFAULT PRIVILEGES GRANT ALL ON LARGE OBJECTS TO regress_priv_user2; diff --git a/postgres/regression_suite/publication.sql b/postgres/regression_suite/publication.sql index 38a76ca9..19414e50 100644 --- a/postgres/regression_suite/publication.sql +++ b/postgres/regression_suite/publication.sql @@ -105,20 +105,69 @@ SELECT pubname, puballtables FROM pg_publication WHERE pubname = 'testpub_forall -- \d+ testpub_tbl2 -- \dRp+ testpub_foralltables +--------------------------------------------- +-- EXCEPT TABLE tests for normal tables +--------------------------------------------- +SET client_min_messages = 'ERROR'; +-- Specify table list in the EXCEPT TABLE clause of a FOR ALL TABLES publication +CREATE PUBLICATION testpub_foralltables_excepttable FOR ALL TABLES EXCEPT TABLE (testpub_tbl1, testpub_tbl2); +-- \dRp+ testpub_foralltables_excepttable +-- Specify table in the EXCEPT TABLE clause of a FOR ALL TABLES publication +CREATE PUBLICATION testpub_foralltables_excepttable1 FOR ALL TABLES EXCEPT TABLE (testpub_tbl1); +-- \dRp+ testpub_foralltables_excepttable1 +-- Check that the table description shows the publications where it is listed +-- in the EXCEPT TABLE clause +-- \d testpub_tbl1 + +RESET client_min_messages; DROP TABLE testpub_tbl2; -DROP PUBLICATION testpub_foralltables, testpub_fortable, testpub_forschema, testpub_for_tbl_schema; +DROP PUBLICATION testpub_foralltables, testpub_fortable, testpub_forschema, testpub_for_tbl_schema, testpub_foralltables_excepttable, testpub_foralltables_excepttable1; -CREATE TABLE testpub_tbl3 (a int); -CREATE TABLE testpub_tbl3a (b text) INHERITS (testpub_tbl3); +--------------------------------------------- +-- Tests for inherited tables, and +-- EXCEPT TABLE tests for inherited tables +--------------------------------------------- SET client_min_messages = 'ERROR'; -CREATE PUBLICATION testpub3 FOR TABLE testpub_tbl3; -CREATE PUBLICATION testpub4 FOR TABLE ONLY testpub_tbl3; -RESET client_min_messages; +CREATE TABLE testpub_tbl_parent (a int); +CREATE TABLE testpub_tbl_child (b text) INHERITS (testpub_tbl_parent); +CREATE PUBLICATION testpub3 FOR TABLE testpub_tbl_parent; -- \dRp+ testpub3 +CREATE PUBLICATION testpub4 FOR TABLE ONLY testpub_tbl_parent; -- \dRp+ testpub4 +-- List the parent table in the EXCEPT TABLE clause (without ONLY or '*') +CREATE PUBLICATION testpub5 FOR ALL TABLES EXCEPT TABLE (testpub_tbl_parent); +-- \dRp+ testpub5 +-- EXCEPT with '*': list the table and all its descendants in the EXCEPT TABLE clause +CREATE PUBLICATION testpub6 FOR ALL TABLES EXCEPT TABLE (testpub_tbl_parent *); +-- \dRp+ testpub6 +-- EXCEPT with ONLY: list the table in the EXCEPT TABLE clause, but not its descendants +CREATE PUBLICATION testpub7 FOR ALL TABLES EXCEPT TABLE (ONLY testpub_tbl_parent); +-- \dRp+ testpub7 -DROP TABLE testpub_tbl3, testpub_tbl3a; -DROP PUBLICATION testpub3, testpub4; +RESET client_min_messages; +DROP TABLE testpub_tbl_parent, testpub_tbl_child; +DROP PUBLICATION testpub3, testpub4, testpub5, testpub6, testpub7; + +--------------------------------------------- +-- EXCEPT TABLE tests for partitioned tables +--------------------------------------------- +SET client_min_messages = 'ERROR'; +CREATE TABLE testpub_root(a int) PARTITION BY RANGE(a); +CREATE TABLE testpub_part1 PARTITION OF testpub_root FOR VALUES FROM (0) TO (100); +CREATE PUBLICATION testpub8 FOR ALL TABLES EXCEPT TABLE (testpub_root); +-- \dRp+ testpub8; +-- \d testpub_part1 +-- \d testpub_root +CREATE PUBLICATION testpub9 FOR ALL TABLES EXCEPT TABLE (testpub_part1); + +CREATE TABLE tab_main (a int) PARTITION BY RANGE(a); +-- Attaching a partition is not allowed if the partitioned table appears in a +-- publication's EXCEPT TABLE clause. +ALTER TABLE tab_main ATTACH PARTITION testpub_root FOR VALUES FROM (0) TO (200); + +RESET client_min_messages; +DROP TABLE testpub_root, testpub_part1, tab_main; +DROP PUBLICATION testpub8; --- Tests for publications with SEQUENCES CREATE SEQUENCE regress_pub_seq0; diff --git a/postgres/regression_suite/stats_import.sql b/postgres/regression_suite/stats_import.sql index 39dbee72..3db1e329 100644 --- a/postgres/regression_suite/stats_import.sql +++ b/postgres/regression_suite/stats_import.sql @@ -1521,6 +1521,49 @@ CREATE STATISTICS stats_import.test_mr_stat ON name, mrange, ( mrange + '{[10000,10200)}'::int4multirange) FROM stats_import.test_mr; +CREATE TABLE stats_import.test_mr_clone ( LIKE stats_import.test_mr ) + WITH (autovacuum_enabled = false); +CREATE STATISTICS stats_import.test_mr_stat_clone + ON name, mrange, ( mrange + '{[10000,10200)}'::int4multirange) + FROM stats_import.test_mr_clone; + +-- Check for invalid value combinations for range types. +-- Only range_bounds_histogram (other two missing) +SELECT pg_catalog.pg_restore_extended_stats( + 'schemaname', 'stats_import', + 'relname', 'test_mr', + 'statistics_schemaname', 'stats_import', + 'statistics_name', 'test_mr_stat', + 'inherited', false, + 'exprs', '[{"range_bounds_histogram": "{\"[1,10200)\",\"[11,10200)\"}"}]'::jsonb); +-- Only range_length_histogram and range_empty_frac +-- (range_bounds_histogram missing) +SELECT pg_catalog.pg_restore_extended_stats( + 'schemaname', 'stats_import', + 'relname', 'test_mr', + 'statistics_schemaname', 'stats_import', + 'statistics_name', 'test_mr_stat', + 'inherited', false, + 'exprs', '[{"range_length_histogram": "{10179,10189}", "range_empty_frac": "0"}]'::jsonb); +-- Only range_bounds_histogram and range_empty_frac +-- (range_length_histogram missing) +SELECT pg_catalog.pg_restore_extended_stats( + 'schemaname', 'stats_import', + 'relname', 'test_mr', + 'statistics_schemaname', 'stats_import', + 'statistics_name', 'test_mr_stat', + 'inherited', false, + 'exprs', '[{"range_bounds_histogram": "{\"[1,10200)\"}", "range_empty_frac": "0"}]'::jsonb); +-- Only range_bounds_histogram and range_length_histogram +-- (range_empty_frac missing) +SELECT pg_catalog.pg_restore_extended_stats( + 'schemaname', 'stats_import', + 'relname', 'test_mr', + 'statistics_schemaname', 'stats_import', + 'statistics_name', 'test_mr_stat', + 'inherited', false, + 'exprs', '[{"range_bounds_histogram": "{\"[1,10200)\"}", "range_length_histogram": "{10179}"}]'::jsonb); + -- ok: multirange stats SELECT pg_catalog.pg_restore_extended_stats( 'schemaname', 'stats_import', @@ -1543,8 +1586,12 @@ SELECT pg_catalog.pg_restore_extended_stats( {red,"{[11,13),[15,19),[20,30)}","{[11,13),[15,19),[20,30),[10000,10200)}"}, {red,"{[21,23),[25,29),[120,130)}","{[21,23),[25,29),[120,130),[10000,10200)}"}}'::text[], 'most_common_freqs', '{0.3333333333333333,0.3333333333333333,0.3333333333333333}'::double precision[], - 'most_common_base_freqs', '{0.1111111111111111,0.1111111111111111,0.1111111111111111}'::double precision[] -); + 'most_common_base_freqs', '{0.1111111111111111,0.1111111111111111,0.1111111111111111}'::double precision[], + 'exprs', '[{ "avg_width": "60", "null_frac": "0", "n_distinct": "-1", + "range_length_histogram": "{10179,10189,10199}", + "range_empty_frac": "0", + "range_bounds_histogram": "{\"[1,10200)\",\"[11,10200)\",\"[21,10200)\"}" + }]'::jsonb); SELECT replace(e.n_distinct, '}, ', E'},\n') AS n_distinct, replace(e.dependencies, '}, ', E'},\n') AS dependencies, @@ -1557,6 +1604,526 @@ WHERE e.statistics_schemaname = 'stats_import' AND e.inherited = false /* \gx */; +SELECT e.expr, e.null_frac, e.avg_width, e.n_distinct, e.most_common_vals, + e.most_common_freqs, e.histogram_bounds, e.correlation, + e.most_common_elems, e.most_common_elem_freqs, e.elem_count_histogram, + e.range_length_histogram, e.range_empty_frac, e.range_bounds_histogram +FROM pg_stats_ext_exprs AS e +WHERE e.statistics_schemaname = 'stats_import' AND + e.statistics_name = 'test_mr_stat' AND + e.inherited = false +/* \gx */; + +-- Incorrect extended stats kind, exprs not supported +SELECT pg_catalog.pg_restore_extended_stats( + 'schemaname', 'stats_import', + 'relname', 'test', + 'statistics_schemaname', 'stats_import', + 'statistics_name', 'test_stat_ndistinct', + 'inherited', false, + 'exprs', '[ { "avg_width": "4" } ]'::jsonb); + +-- Invalid exprs, not an array +SELECT pg_catalog.pg_restore_extended_stats( + 'schemaname', 'stats_import', + 'relname', 'test_clone', + 'statistics_schemaname', 'stats_import', + 'statistics_name', 'test_stat_clone', + 'inherited', false, + 'exprs', '{ "avg_width": "4", "null_frac": "0" }'::jsonb); +-- wrong number of exprs +SELECT pg_catalog.pg_restore_extended_stats( + 'schemaname', 'stats_import', + 'relname', 'test_clone', + 'statistics_schemaname', 'stats_import', + 'statistics_name', 'test_stat_clone', + 'inherited', false, + 'exprs', '[ { "avg_width": "4" } ]'::jsonb); +-- incorrect type of value: should be a string or a NULL. +SELECT pg_catalog.pg_restore_extended_stats( + 'schemaname', 'stats_import', + 'relname', 'test_clone', + 'statistics_schemaname', 'stats_import', + 'statistics_name', 'test_stat_clone', + 'inherited', false, + 'exprs', '[ { "null_frac": 1 }, + { "null_frac": "0.25" } ]'::jsonb); +-- exprs null_frac not a float +SELECT pg_catalog.pg_restore_extended_stats( + 'schemaname', 'stats_import', + 'relname', 'test_clone', + 'statistics_schemaname', 'stats_import', + 'statistics_name', 'test_stat_clone', + 'inherited', false, + 'exprs', '[ { "null_frac": "BADNULLFRAC" }, + { "null_frac": "0.25" } ]'::jsonb); +-- exprs avg_width not an integer +SELECT pg_catalog.pg_restore_extended_stats( + 'schemaname', 'stats_import', + 'relname', 'test_clone', + 'statistics_schemaname', 'stats_import', + 'statistics_name', 'test_stat_clone', + 'inherited', false, + 'exprs', '[ { "avg_width": "BADAVGWIDTH" }, + { "avg_width": "4" } ]'::jsonb); +-- exprs n_dinstinct not a float +SELECT pg_catalog.pg_restore_extended_stats( + 'schemaname', 'stats_import', + 'relname', 'test_clone', + 'statistics_schemaname', 'stats_import', + 'statistics_name', 'test_stat_clone', + 'inherited', false, + 'exprs', '[ { "n_distinct": "BADNDISTINCT" }, + { "n_distinct": "-0.5" } ]'::jsonb); +-- MCV not null, MCF null +SELECT pg_catalog.pg_restore_extended_stats( + 'schemaname', 'stats_import', + 'relname', 'test_clone', + 'statistics_schemaname', 'stats_import', + 'statistics_name', 'test_stat_clone', + 'inherited', false, + 'exprs', '[ + { "most_common_vals": "{1}", "most_common_elems": null }, + { "most_common_vals": "{2}", "most_common_freqs": "{0.5}" } + ]'::jsonb); +-- MCV not null, MCF missing +SELECT pg_catalog.pg_restore_extended_stats( + 'schemaname', 'stats_import', + 'relname', 'test_clone', + 'statistics_schemaname', 'stats_import', + 'statistics_name', 'test_stat_clone', + 'inherited', false, + 'exprs', '[ + { "most_common_vals": "{1}" }, + { "most_common_vals": "{2}", "most_common_freqs": "{0.5}" } + ]'::jsonb); +-- MCV null, MCF not null +SELECT pg_catalog.pg_restore_extended_stats( + 'schemaname', 'stats_import', + 'relname', 'test_clone', + 'statistics_schemaname', 'stats_import', + 'statistics_name', 'test_stat_clone', + 'inherited', false, + 'exprs', '[ + { "most_common_vals": null, "most_common_freqs": "{0.5}" }, + { "most_common_vals": "{2}", "most_common_freqs": "{0.5}" } + ]'::jsonb); +-- MCV missing, MCF not null +SELECT pg_catalog.pg_restore_extended_stats( + 'schemaname', 'stats_import', + 'relname', 'test_clone', + 'statistics_schemaname', 'stats_import', + 'statistics_name', 'test_stat_clone', + 'inherited', false, + 'exprs', '[ + { "most_common_freqs": "{0.5}" }, + { "most_common_vals": "{2}", "most_common_freqs": "{0.5}" } + ]'::jsonb); +-- exprs most_common_vals element wrong type +SELECT pg_catalog.pg_restore_extended_stats( + 'schemaname', 'stats_import', + 'relname', 'test_clone', + 'statistics_schemaname', 'stats_import', + 'statistics_name', 'test_stat_clone', + 'inherited', false, + 'exprs', '[ + { + "avg_width": "4", + "null_frac": "0", + "n_distinct": "-0.75", + "correlation": "-0.6", + "histogram_bounds": "{-1,0}", + "most_common_vals": "{BADMCV}", + "most_common_elems": null, + "most_common_freqs": "{0.5}", + "elem_count_histogram": null, + "most_common_elem_freqs": null + }, + { + "avg_width": "4", + "null_frac": "0.25", + "n_distinct": "-0.5", + "correlation": "1", + "histogram_bounds": null, + "most_common_vals": "{2}", + "most_common_elems": null, + "most_common_freqs": "{0.5}", + "elem_count_histogram": null, + "most_common_elem_freqs": null + } + ]'::jsonb); +-- exprs most_common_freqs element wrong type +SELECT pg_catalog.pg_restore_extended_stats( + 'schemaname', 'stats_import', + 'relname', 'test_clone', + 'statistics_schemaname', 'stats_import', + 'statistics_name', 'test_stat_clone', + 'inherited', false, + 'exprs', '[ + { "most_common_vals": "{1}", "most_common_freqs": "{BADMCF}" }, + { "most_common_vals": "{2}", "most_common_freqs": "{0.5}" } + ]'::jsonb); +-- exprs histogram wrong type +SELECT pg_catalog.pg_restore_extended_stats( + 'schemaname', 'stats_import', + 'relname', 'test_clone', + 'statistics_schemaname', 'stats_import', + 'statistics_name', 'test_stat_clone', + 'inherited', false, + 'exprs', '[ + { "histogram_bounds": "{BADHIST,0}" }, + { "histogram_bounds": null } + ]'::jsonb); +-- exprs correlation wrong type +SELECT pg_catalog.pg_restore_extended_stats( + 'schemaname', 'stats_import', + 'relname', 'test_clone', + 'statistics_schemaname', 'stats_import', + 'statistics_name', 'test_stat_clone', + 'inherited', false, + 'exprs', '[ { "correlation": "BADCORR" }, + { "correlation": "1" } + ]'::jsonb); +-- invalid element type in array +SELECT pg_catalog.pg_restore_extended_stats( + 'schemaname', 'stats_import', + 'relname', 'test_clone', + 'statistics_schemaname', 'stats_import', + 'statistics_name', 'test_stat_clone', + 'inherited', false, + 'exprs', '[1, null]'::jsonb); +-- invalid element in array, as valid jbvBinary. +SELECT pg_catalog.pg_restore_extended_stats( + 'schemaname', 'stats_import', + 'relname', 'test_clone', + 'statistics_schemaname', 'stats_import', + 'statistics_name', 'test_stat_clone', + 'inherited', false, + 'exprs', '[null, [{"avg_width" : [1]}]]'::jsonb); +-- only range types can have range_length_histogram, range_empty_frac +-- or range_bounds_histogram. +SELECT pg_catalog.pg_restore_extended_stats( + 'schemaname', 'stats_import', + 'relname', 'test_clone', + 'statistics_schemaname', 'stats_import', + 'statistics_name', 'test_stat_clone', + 'inherited', false, + 'exprs', '[ + null, + { + "range_length_histogram": "{10179,10189,10199}", + "range_empty_frac": "0", + "range_bounds_histogram": "{10200,10200,10200}" + } + ]'::jsonb); +-- only array types can have most_common_elems or elem_count_histogram. +SELECT pg_catalog.pg_restore_extended_stats( + 'schemaname', 'stats_import', + 'relname', 'test_clone', + 'statistics_schemaname', 'stats_import', + 'statistics_name', 'test_stat_clone', + 'inherited', false, + 'exprs', '[ + null, + { + "most_common_elems": "{1,2,3}", + "most_common_elem_freqs": "{0.3,0.3,0.4}" + } + ]'::jsonb); +SELECT pg_catalog.pg_restore_extended_stats( + 'schemaname', 'stats_import', + 'relname', 'test_clone', + 'statistics_schemaname', 'stats_import', + 'statistics_name', 'test_stat_clone', + 'inherited', false, + 'exprs', '[ + null, + { + "elem_count_histogram": "{1,2,3,4,5}" + } + ]'::jsonb); +-- ok: exprs first null +SELECT pg_catalog.pg_restore_extended_stats( + 'schemaname', 'stats_import', + 'relname', 'test_clone', + 'statistics_schemaname', 'stats_import', + 'statistics_name', 'test_stat_clone', + 'inherited', false, + 'exprs', '[ + null, + { + "avg_width": "4", + "null_frac": "0.25", + "n_distinct": "-0.5", + "correlation": "1", + "histogram_bounds": null, + "most_common_vals": "{2}", + "most_common_elems": null, + "most_common_freqs": "{0.5}", + "elem_count_histogram": null, + "most_common_elem_freqs": null + } + ]'::jsonb); + +SELECT e.expr, e.null_frac, e.avg_width, e.n_distinct, e.most_common_vals, + e.most_common_freqs, e.histogram_bounds, e.correlation, + e.most_common_elems, e.most_common_elem_freqs, e.elem_count_histogram, + e.range_length_histogram, e.range_empty_frac, e.range_bounds_histogram +FROM pg_stats_ext_exprs AS e +WHERE e.statistics_schemaname = 'stats_import' AND + e.statistics_name = 'test_stat_clone' AND + e.inherited = false +/* \gx */; +-- ok: exprs last null +SELECT pg_catalog.pg_restore_extended_stats( + 'schemaname', 'stats_import', + 'relname', 'test_clone', + 'statistics_schemaname', 'stats_import', + 'statistics_name', 'test_stat_clone', + 'inherited', false, + 'exprs', '[ + { + "avg_width": "4", + "null_frac": "0", + "n_distinct": "-0.75", + "correlation": "-0.6", + "histogram_bounds": "{-1,0}", + "most_common_vals": "{1}", + "most_common_elems": null, + "most_common_freqs": "{0.5}", + "elem_count_histogram": null, + "most_common_elem_freqs": null + }, + null + ]'::jsonb); + +SELECT e.expr, e.null_frac, e.avg_width, e.n_distinct, e.most_common_vals, + e.most_common_freqs, e.histogram_bounds, e.correlation, + e.most_common_elems, e.most_common_elem_freqs, e.elem_count_histogram +FROM pg_stats_ext_exprs AS e +WHERE e.statistics_schemaname = 'stats_import' AND + e.statistics_name = 'test_stat_clone' AND + e.inherited = false +/* \gx */; +-- ok: both exprs +SELECT pg_catalog.pg_restore_extended_stats( + 'schemaname', 'stats_import', + 'relname', 'test_clone', + 'statistics_schemaname', 'stats_import', + 'statistics_name', 'test_stat_clone', + 'inherited', false, + 'exprs', '[ + { + "avg_width": "4", + "null_frac": "0", + "n_distinct": "-0.75", + "correlation": "-0.6", + "histogram_bounds": "{-1,0}", + "most_common_vals": "{1}", + "most_common_elems": null, + "most_common_freqs": "{0.5}", + "elem_count_histogram": null, + "most_common_elem_freqs": null + }, + { + "avg_width": "4", + "null_frac": "0.25", + "n_distinct": "-0.5", + "correlation": "1", + "histogram_bounds": null, + "most_common_vals": "{2}", + "most_common_elems": null, + "most_common_freqs": "{0.5}", + "elem_count_histogram": null, + "most_common_elem_freqs": null + } + ]'::jsonb); +SELECT e.expr, e.null_frac, e.avg_width, e.n_distinct, e.most_common_vals, + e.most_common_freqs, e.histogram_bounds, e.correlation, + e.most_common_elems, e.most_common_elem_freqs, e.elem_count_histogram +FROM pg_stats_ext_exprs AS e +WHERE e.statistics_schemaname = 'stats_import' AND + e.statistics_name = 'test_stat_clone' AND + e.inherited = false +/* \gx */; + +-- A statistics object for testing MCELEM values in expressions +CREATE STATISTICS stats_import.test_stat_mcelem + ON name, (ARRAY[(comp).a, lower(arange)]) + FROM stats_import.test; + +-- MCEV not null, MCEF null +SELECT pg_catalog.pg_restore_extended_stats( + 'schemaname', 'stats_import', + 'relname', 'test', + 'statistics_schemaname', 'stats_import', + 'statistics_name', 'test_stat_mcelem', + 'inherited', false, + 'exprs', '[ + { + "most_common_elems": "{-1,0,1,2,3}", + "most_common_elem_freqs": null + } + ]'::jsonb); +-- MCEV not null, MCEF missing +SELECT pg_catalog.pg_restore_extended_stats( + 'schemaname', 'stats_import', + 'relname', 'test', + 'statistics_schemaname', 'stats_import', + 'statistics_name', 'test_stat_mcelem', + 'inherited', false, + 'exprs', '[ + { + "most_common_elems": "{-1,0,1,2,3}" + } + ]'::jsonb); +-- MCEV null, MCEF not null +SELECT pg_catalog.pg_restore_extended_stats( + 'schemaname', 'stats_import', + 'relname', 'test', + 'statistics_schemaname', 'stats_import', + 'statistics_name', 'test_stat_mcelem', + 'inherited', false, + 'exprs', '[ + { + "most_common_elems": null, + "most_common_elem_freqs": "{0.25,0.25,0.5,0.25,0.25,0.25,0.5,0.25}" + } + ]'::jsonb); +-- MCEV missing, MCEF not null +SELECT pg_catalog.pg_restore_extended_stats( + 'schemaname', 'stats_import', + 'relname', 'test', + 'statistics_schemaname', 'stats_import', + 'statistics_name', 'test_stat_mcelem', + 'inherited', false, + 'exprs', '[ + { + "most_common_elem_freqs": "{0.25,0.25,0.5,0.25,0.25,0.25,0.5,0.25}" + } + ]'::jsonb); +-- exprs most_common_elems element wrong type +SELECT pg_catalog.pg_restore_extended_stats( + 'schemaname', 'stats_import', + 'relname', 'test', + 'statistics_schemaname', 'stats_import', + 'statistics_name', 'test_stat_mcelem', + 'inherited', false, + 'exprs', '[ + { + "most_common_elems": "{-1,BADELEM,1,2,3}", + "most_common_elem_freqs": "{0.25,0.25,0.5,0.25,0.25,0.25,0.5,0.25}" + } + ]'::jsonb); +-- exprs most_common_elem_freqs element wrong type +SELECT pg_catalog.pg_restore_extended_stats( + 'schemaname', 'stats_import', + 'relname', 'test', + 'statistics_schemaname', 'stats_import', + 'statistics_name', 'test_stat_mcelem', + 'inherited', false, + 'exprs', '[ + { + "most_common_elems": "{-1,0,1,2,3}", + "most_common_elem_freqs": "{BADELEMFREQ,0.25,0.5,0.25,0.25,0.25,0.5,0.25}" + } + ]'::jsonb); +-- exprs histogram bounds element wrong type +SELECT pg_catalog.pg_restore_extended_stats( + 'schemaname', 'stats_import', + 'relname', 'test', + 'statistics_schemaname', 'stats_import', + 'statistics_name', 'test_stat_mcelem', + 'inherited', false, + 'exprs', '[ + { + "elem_count_histogram": "{BADELEMHIST,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1.5}" + } + ]'::jsonb); +-- ok: exprs mcelem +SELECT pg_catalog.pg_restore_extended_stats( + 'schemaname', 'stats_import', + 'relname', 'test', + 'statistics_schemaname', 'stats_import', + 'statistics_name', 'test_stat_mcelem', + 'inherited', false, + 'exprs', '[ + { + "avg_width": "33", + "null_frac": "0", + "n_distinct": "-1", + "correlation": "1", + "histogram_bounds": "{\"{1,1}\",\"{2,1}\",\"{3,-1}\",\"{NULL,0}\"}", + "most_common_vals": null, + "most_common_elems": "{-1,0,1,2,3}", + "most_common_freqs": null, + "elem_count_histogram": "{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1.5}", + "most_common_elem_freqs": "{0.25,0.25,0.5,0.25,0.25,0.25,0.5,0.25}" + } + ]'::jsonb); + +SELECT e.expr, e.null_frac, e.avg_width, e.n_distinct, e.most_common_vals, + e.most_common_freqs, e.histogram_bounds, e.correlation, + e.most_common_elems, e.most_common_elem_freqs, e.elem_count_histogram +FROM pg_stats_ext_exprs AS e +WHERE e.statistics_schemaname = 'stats_import' AND + e.statistics_name = 'test_stat_mcelem' AND + e.inherited = false +/* \gx */; + +-- ok, with warning: extra exprs param +SELECT pg_catalog.pg_restore_extended_stats( + 'schemaname', 'stats_import', + 'relname', 'test', + 'statistics_schemaname', 'stats_import', + 'statistics_name', 'test_stat_mcelem', + 'inherited', false, + 'exprs', '[ + { + "avg_width": "33", + "null_frac": "0", + "n_distinct": "-1", + "correlation": "1", + "histogram_bounds": "{\"{1,1}\",\"{2,1}\",\"{3,-1}\",\"{NULL,0}\"}", + "most_common_vals": null, + "most_common_elems": "{-1,0,1,2,3}", + "most_common_freqs": null, + "elem_count_histogram": "{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1.5}", + "most_common_elem_freqs": "{0.25,0.25,0.5,0.25,0.25,0.25,0.5,0.25}", + "bad_param": "text no one will ever parse" + } + ]'::jsonb); + +SELECT e.expr, e.null_frac, e.avg_width, e.n_distinct, e.most_common_vals, + e.most_common_freqs, e.histogram_bounds, e.correlation, + e.most_common_elems, e.most_common_elem_freqs, e.elem_count_histogram +FROM pg_stats_ext_exprs AS e +WHERE e.statistics_schemaname = 'stats_import' AND + e.statistics_name = 'test_stat_mcelem' AND + e.inherited = false +/* \gx */; + +-- ok: tsvector exceptions, test just the collation exceptions +CREATE STATISTICS stats_import.test_stat_tsvec ON (length(name)), (to_tsvector(name)) FROM stats_import.test; +SELECT pg_catalog.pg_restore_extended_stats( + 'schemaname', 'stats_import', + 'relname', 'test', + 'statistics_schemaname', 'stats_import', + 'statistics_name', 'test_stat_tsvec', + 'inherited', false, + 'exprs', '[null, + { + "most_common_elems": "{one,tre,two,four}", + "most_common_elem_freqs": "{0.25,0.25,0.25,0.25,0.25,0.25}" + } + ]'::jsonb); +SELECT e.expr, e.most_common_elems, e.most_common_elem_freqs +FROM pg_stats_ext_exprs AS e +WHERE e.statistics_schemaname = 'stats_import' AND + e.statistics_name = 'test_stat_tsvec' AND + e.inherited = false +/* \gx */; + -- Test the ability of pg_restore_extended_stats() to import all of the -- statistic values from an extended statistic object that has been -- populated via a regular ANALYZE. This checks after the statistics @@ -1578,8 +2145,29 @@ SELECT e.statistics_name, 'dependencies', e.dependencies, 'most_common_vals', e.most_common_vals, 'most_common_freqs', e.most_common_freqs, - 'most_common_base_freqs', e.most_common_base_freqs) + 'most_common_base_freqs', e.most_common_base_freqs, + 'exprs', x.exprs) FROM pg_stats_ext AS e +CROSS JOIN LATERAL ( + SELECT jsonb_agg(jsonb_strip_nulls(jsonb_build_object( + 'null_frac', ee.null_frac::text, + 'avg_width', ee.avg_width::text, + 'n_distinct', ee.n_distinct::text, + 'most_common_vals', ee.most_common_vals::text, + 'most_common_freqs', ee.most_common_freqs::text, + 'histogram_bounds', ee.histogram_bounds::text, + 'correlation', ee.correlation::text, + 'most_common_elems', ee.most_common_elems::text, + 'most_common_elem_freqs', ee.most_common_elem_freqs::text, + 'elem_count_histogram', ee.elem_count_histogram::text, + 'range_length_histogram', ee.range_length_histogram::text, + 'range_empty_frac', ee.range_empty_frac::text, + 'range_bounds_histogram', ee.range_bounds_histogram::text))) + FROM pg_stats_ext_exprs AS ee + WHERE ee.statistics_schemaname = e.statistics_schemaname AND + ee.statistics_name = e.statistics_name AND + ee.inherited = e.inherited + ) AS x(exprs) WHERE e.statistics_schemaname = 'stats_import' AND e.statistics_name = 'test_stat'; @@ -1612,4 +2200,257 @@ SELECT o.inherited, WHERE o.statistics_schemaname = 'stats_import' AND o.statistics_name = 'test_stat'; +-- Set difference for exprs: old MINUS new. +SELECT o.inherited, + o.null_frac, o.avg_width, o.n_distinct, + o.most_common_vals::text AS most_common_vals, + o.most_common_freqs, + o.histogram_bounds::text AS histogram_bounds, + o.correlation, + o.most_common_elems::text AS most_common_elems, + o.most_common_elem_freqs, o.elem_count_histogram, + o.range_length_histogram::text AS range_length_histogram, + o.range_empty_frac, + o.range_bounds_histogram::text AS range_bounds_histogram + FROM pg_stats_ext_exprs AS o + WHERE o.statistics_schemaname = 'stats_import' AND + o.statistics_name = 'test_stat' +EXCEPT +SELECT n.inherited, + n.null_frac, n.avg_width, n.n_distinct, + n.most_common_vals::text AS most_common_vals, + n.most_common_freqs, + n.histogram_bounds::text AS histogram_bounds, + n.correlation, + n.most_common_elems::text AS most_common_elems, + n.most_common_elem_freqs, n.elem_count_histogram, + n.range_length_histogram::text AS range_length_histogram, + n.range_empty_frac, + n.range_bounds_histogram::text AS range_bounds_histogram + FROM pg_stats_ext_exprs AS n + WHERE n.statistics_schemaname = 'stats_import' AND + n.statistics_name = 'test_stat_clone'; + +-- Set difference for exprs: new MINUS old. +SELECT n.inherited, + n.null_frac, n.avg_width, n.n_distinct, + n.most_common_vals::text AS most_common_vals, + n.most_common_freqs, + n.histogram_bounds::text AS histogram_bounds, + n.correlation, + n.most_common_elems::text AS most_common_elems, + n.most_common_elem_freqs, n.elem_count_histogram, + n.range_length_histogram::text AS range_length_histogram, + n.range_empty_frac, + n.range_bounds_histogram::text AS range_bounds_histogram + FROM pg_stats_ext_exprs AS n + WHERE n.statistics_schemaname = 'stats_import' AND + n.statistics_name = 'test_stat_clone' +EXCEPT +SELECT o.inherited, + o.null_frac, o.avg_width, o.n_distinct, + o.most_common_vals::text AS most_common_vals, + o.most_common_freqs, + o.histogram_bounds::text AS histogram_bounds, + o.correlation, + o.most_common_elems::text AS most_common_elems, + o.most_common_elem_freqs, o.elem_count_histogram, + o.range_length_histogram::text AS range_length_histogram, + o.range_empty_frac, + o.range_bounds_histogram::text AS range_bounds_histogram + FROM pg_stats_ext_exprs AS o + WHERE o.statistics_schemaname = 'stats_import' AND + o.statistics_name = 'test_stat'; + +ANALYZE stats_import.test_mr; + +-- Copy stats from test_mr_stat to test_mr_stat_clone +SELECT e.statistics_name, + pg_catalog.pg_restore_extended_stats( + 'schemaname', e.statistics_schemaname::text, + 'relname', 'test_mr_clone', + 'statistics_schemaname', e.statistics_schemaname::text, + 'statistics_name', 'test_mr_stat_clone', + 'inherited', e.inherited, + 'n_distinct', e.n_distinct, + 'dependencies', e.dependencies, + 'most_common_vals', e.most_common_vals, + 'most_common_freqs', e.most_common_freqs, + 'most_common_base_freqs', e.most_common_base_freqs, + 'exprs', x.exprs) +FROM pg_stats_ext AS e +CROSS JOIN LATERAL ( + SELECT jsonb_agg(jsonb_strip_nulls(jsonb_build_object( + 'null_frac', ee.null_frac::text, + 'avg_width', ee.avg_width::text, + 'n_distinct', ee.n_distinct::text, + 'most_common_vals', ee.most_common_vals::text, + 'most_common_freqs', ee.most_common_freqs::text, + 'histogram_bounds', ee.histogram_bounds::text, + 'correlation', ee.correlation::text, + 'most_common_elems', ee.most_common_elems::text, + 'most_common_elem_freqs', ee.most_common_elem_freqs::text, + 'elem_count_histogram', ee.elem_count_histogram::text, + 'range_length_histogram', ee.range_length_histogram::text, + 'range_empty_frac', ee.range_empty_frac::text, + 'range_bounds_histogram', ee.range_bounds_histogram::text))) + FROM pg_stats_ext_exprs AS ee + WHERE ee.statistics_schemaname = e.statistics_schemaname AND + ee.statistics_name = e.statistics_name AND + ee.inherited = e.inherited + ) AS x(exprs) +WHERE e.statistics_schemaname = 'stats_import' +AND e.statistics_name = 'test_mr_stat'; + +-- Set difference old MINUS new. +SELECT o.inherited, + o.n_distinct, o.dependencies, o.most_common_vals, + o.most_common_freqs, o.most_common_base_freqs + FROM pg_stats_ext AS o + WHERE o.statistics_schemaname = 'stats_import' AND + o.statistics_name = 'test_mr_stat' +EXCEPT +SELECT n.inherited, + n.n_distinct, n.dependencies, n.most_common_vals, + n.most_common_freqs, n.most_common_base_freqs + FROM pg_stats_ext AS n + WHERE n.statistics_schemaname = 'stats_import' AND + n.statistics_name = 'test_mr_stat_clone'; +-- Set difference new MINUS old. +SELECT n.inherited, + n.n_distinct, n.dependencies, n.most_common_vals, + n.most_common_freqs, n.most_common_base_freqs + FROM pg_stats_ext AS n + WHERE n.statistics_schemaname = 'stats_import' AND + n.statistics_name = 'test_mr_stat_clone' +EXCEPT +SELECT o.inherited, + o.n_distinct, o.dependencies, o.most_common_vals, + o.most_common_freqs, o.most_common_base_freqs + FROM pg_stats_ext AS o + WHERE o.statistics_schemaname = 'stats_import' AND + o.statistics_name = 'test_mr_stat'; + +-- Set difference for exprs: old MINUS new. +SELECT o.inherited, + o.null_frac, o.avg_width, o.n_distinct, + o.most_common_vals::text AS most_common_vals, + o.most_common_freqs, + o.histogram_bounds::text AS histogram_bounds, + o.correlation, + o.most_common_elems::text AS most_common_elems, + o.most_common_elem_freqs, o.elem_count_histogram, + o.range_length_histogram::text AS range_length_histogram, + o.range_empty_frac, + o.range_bounds_histogram::text AS range_bounds_histogram + FROM pg_stats_ext_exprs AS o + WHERE o.statistics_schemaname = 'stats_import' AND + o.statistics_name = 'test_mr_stat' +EXCEPT +SELECT n.inherited, + n.null_frac, n.avg_width, n.n_distinct, + n.most_common_vals::text AS most_common_vals, + n.most_common_freqs, + n.histogram_bounds::text AS histogram_bounds, + n.correlation, + n.most_common_elems::text AS most_common_elems, + n.most_common_elem_freqs, n.elem_count_histogram, + n.range_length_histogram::text AS range_length_histogram, + n.range_empty_frac, + n.range_bounds_histogram::text AS range_bounds_histogram + FROM pg_stats_ext_exprs AS n + WHERE n.statistics_schemaname = 'stats_import' AND + n.statistics_name = 'test_mr_stat_clone'; + +-- Set difference for exprs: new MINUS old. +SELECT n.inherited, + n.null_frac, n.avg_width, n.n_distinct, + n.most_common_vals::text AS most_common_vals, + n.most_common_freqs, + n.histogram_bounds::text AS histogram_bounds, + n.correlation, + n.most_common_elems::text AS most_common_elems, + n.most_common_elem_freqs, n.elem_count_histogram, + n.range_length_histogram::text AS range_length_histogram, + n.range_empty_frac, + n.range_bounds_histogram::text AS range_bounds_histogram + FROM pg_stats_ext_exprs AS n + WHERE n.statistics_schemaname = 'stats_import' AND + n.statistics_name = 'test_mr_stat_clone' +EXCEPT +SELECT o.inherited, + o.null_frac, o.avg_width, o.n_distinct, + o.most_common_vals::text AS most_common_vals, + o.most_common_freqs, + o.histogram_bounds::text AS histogram_bounds, + o.correlation, + o.most_common_elems::text AS most_common_elems, + o.most_common_elem_freqs, o.elem_count_histogram, + o.range_length_histogram::text AS range_length_histogram, + o.range_empty_frac, + o.range_bounds_histogram::text AS range_bounds_histogram + FROM pg_stats_ext_exprs AS o + WHERE o.statistics_schemaname = 'stats_import' AND + o.statistics_name = 'test_mr_stat'; + +-- range_length_histogram, range_empty_frac, and range_bounds_histogram +-- have been added to pg_stat_ext_exprs in PostgreSQL 19. When dumping +-- expression statistics in a cluster with an older version, these fields +-- are dumped as NULL, pg_restore_extended_stats() authorizing the partial +-- restore state of the extended statistics data. This test emulates such +-- a case by calling pg_restore_extended_stats() with NULL values for all +-- the three range fields, then checks the statistics loaded with some +-- queries. +CREATE TABLE stats_import.test_range_expr_null( + id INTEGER PRIMARY KEY, + name TEXT, + rng int4range NOT NULL +); +INSERT INTO stats_import.test_range_expr_null + SELECT i, 'name_' || (i % 10), int4range(i, i + 10) + FROM generate_series(1, 100) i; +-- Create statistics with a range expression +CREATE STATISTICS stats_import.stat_range_expr_null + ON name, (rng * int4range(50, 150)) + FROM stats_import.test_range_expr_null; +ANALYZE stats_import.test_range_expr_null; +-- Verify range statistics were created +SELECT e.expr, + e.range_length_histogram IS NOT NULL AS has_range_len, + e.range_empty_frac IS NOT NULL AS has_range_empty, + e.range_bounds_histogram IS NOT NULL AS has_range_bounds +FROM pg_stats_ext_exprs AS e +WHERE e.statistics_schemaname = 'stats_import' + AND e.statistics_name = 'stat_range_expr_null'; +-- Import statistics with NULL range fields, simulating dump from +-- older version. +SELECT pg_catalog.pg_restore_extended_stats( + 'schemaname', 'stats_import', + 'relname', 'test_range_expr_null', + 'statistics_schemaname', 'stats_import', + 'statistics_name', 'stat_range_expr_null', + 'inherited', false, + 'exprs', '[ + { + "avg_width": "14", + "null_frac": "0", + "n_distinct": "-1" + } + ]'::jsonb); +-- Verify that range fields are now NULL. +SELECT e.expr, + e.null_frac, + e.range_length_histogram IS NOT NULL AS has_range_len, + e.range_empty_frac IS NOT NULL AS has_range_empty, + e.range_bounds_histogram IS NOT NULL AS has_range_bounds +FROM pg_stats_ext_exprs AS e +WHERE e.statistics_schemaname = 'stats_import' + AND e.statistics_name = 'stat_range_expr_null'; +-- Trigger statistics loading through some queries. +EXPLAIN (COSTS OFF) +SELECT * FROM stats_import.test_range_expr_null + WHERE (rng * int4range(50, 150)) && '[60,70)'::int4range; +SELECT COUNT(*) FROM stats_import.test_range_expr_null + WHERE (rng * int4range(50, 150)) && '[60,70)'::int4range; + DROP SCHEMA stats_import CASCADE; diff --git a/postgres/regression_suite/subscription.sql b/postgres/regression_suite/subscription.sql index e6fe5637..ece0004e 100644 --- a/postgres/regression_suite/subscription.sql +++ b/postgres/regression_suite/subscription.sql @@ -139,6 +139,9 @@ RESET ROLE; ALTER SUBSCRIPTION regress_testsub RENAME TO regress_testsub_foo; ALTER SUBSCRIPTION regress_testsub_foo SET (synchronous_commit = local); ALTER SUBSCRIPTION regress_testsub_foo SET (synchronous_commit = foobar); +ALTER SUBSCRIPTION regress_testsub_foo SET (wal_receiver_timeout = '-1'); +ALTER SUBSCRIPTION regress_testsub_foo SET (wal_receiver_timeout = '80s'); +ALTER SUBSCRIPTION regress_testsub_foo SET (wal_receiver_timeout = 'foobar'); -- \dRs+ diff --git a/postgres/regression_suite/tablespace.sql b/postgres/regression_suite/tablespace.sql index 4db1b3b7..d58dc3ed 100644 --- a/postgres/regression_suite/tablespace.sql +++ b/postgres/regression_suite/tablespace.sql @@ -430,6 +430,10 @@ ALTER MATERIALIZED VIEW ALL IN TABLESPACE regress_tblspace_renamed SET TABLESPAC ALTER TABLE ALL IN TABLESPACE regress_tblspace_renamed SET TABLESPACE pg_default; ALTER MATERIALIZED VIEW ALL IN TABLESPACE regress_tblspace_renamed SET TABLESPACE pg_default; +-- Should fail, contains \n in name +ALTER TABLESPACE regress_tblspace_renamed RENAME TO "invalid +name"; + -- Should succeed DROP TABLESPACE regress_tblspace_renamed;