SET search_path = fast_default; CREATESCHEMA fast_default; CREATETABLE m(id OID); INSERTINTO m VALUES (NULL::OID);
CREATE FUNCTION set(tabname name) RETURNS VOID AS $$
BEGIN UPDATE m SET id = (SELECT c.relfilenode FROM pg_class AS c, pg_namespace AS s WHERE c.relname = tabname AND c.relnamespace = s.oid AND s.nspname = 'fast_default');
END;
$$ LANGUAGE 'plpgsql';
CREATE FUNCTION comp() RETURNS TEXT AS $$
BEGIN RETURN (SELECTCASE WHEN m.id = c.relfilenode THEN'Unchanged' ELSE'Rewritten'
END FROM m, pg_class AS c, pg_namespace AS s WHERE c.relname = 't' AND c.relnamespace = s.oid AND s.nspname = 'fast_default');
END;
$$ LANGUAGE 'plpgsql';
CREATE FUNCTION log_rewrite() RETURNS event_trigger
LANGUAGE plpgsql as
$func$
declare
this_schema text;
begin selectinto this_schema relnamespace::regnamespace::text from pg_class where oid = pg_event_trigger_table_rewrite_oid(); if this_schema = 'fast_default' then
RAISE NOTICE 'rewriting table % for reason %',
pg_event_trigger_table_rewrite_oid()::regclass,
pg_event_trigger_table_rewrite_reason();
end if;
end;
$func$;
CREATETABLE has_volatile AS SELECT * FROM generate_series(1,10) id;
CREATE EVENT TRIGGER has_volatile_rewrite ON table_rewrite
EXECUTE PROCEDURE log_rewrite();
-- only the last of these should trigger a rewrite ALTERTABLE has_volatile ADD col1 int; ALTERTABLE has_volatile ADD col2 intDEFAULT1; ALTERTABLE has_volatile ADD col3 timestamptz DEFAULTcurrent_timestamp; ALTERTABLE has_volatile ADD col4 intDEFAULT (random() * 10000)::int;
-- virtual generated columns don't need a rewrite ALTERTABLE has_volatile ADD col5 int GENERATED ALWAYS AS (tableoid::int + col2) VIRTUAL; ALTERTABLE has_volatile ALTERCOLUMN col5 TYPE float8; ALTERTABLE has_volatile ALTERCOLUMN col5 TYPE numeric; ALTERTABLE has_volatile ALTERCOLUMN col5 TYPE numeric; -- here, we do need a rewrite ALTERTABLE has_volatile ALTERCOLUMN col1 SET DATA TYPE float8, ADDCOLUMN col6 float8 GENERATED ALWAYS AS (col1 * 4) VIRTUAL; -- stored generated columns need a rewrite ALTERTABLE has_volatile ADD col7 int GENERATED ALWAYS AS (55) stored;
-- Test a large sample of different datatypes CREATETABLE T(pk INTNOTNULLPRIMARYKEY, c_int INTDEFAULT1);
SELECTset('t');
INSERTINTO T VALUES (1), (2);
ALTERTABLE T ADDCOLUMN c_bpchar BPCHAR(5) DEFAULT'hello', ALTERCOLUMN c_int SETDEFAULT2;
INSERTINTO T VALUES (3), (4);
ALTERTABLE T ADDCOLUMN c_text TEXT DEFAULT'world', ALTERCOLUMN c_bpchar SETDEFAULT'dog';
INSERTINTO T VALUES (5), (6);
ALTERTABLE T ADDCOLUMN c_date DATE DEFAULT'2016-06-02', ALTERCOLUMN c_text SETDEFAULT'cat';
SELECT pk, c_int, c_bpchar, c_text, c_date, c_timestamp,
c_timestamp_null, c_array, c_small, c_small_null,
c_big, c_num, c_time, c_interval,
c_hugetext = repeat('abcdefg',1000) as c_hugetext_origdef,
c_hugetext = repeat('poiuyt', 1000) as c_hugetext_newdef FROM T ORDERBY pk;
SELECT comp();
DROPTABLE T;
-- Test expressions in the defaults CREATEORREPLACE FUNCTION foo(a INT) RETURNS TEXT AS $$ DECLARE res TEXT := '';
i INT;
BEGIN
i := 0; WHILE (i < a) LOOP
res := res || chr(ascii('a') + i);
i := i + 1;
END LOOP; RETURN res;
END; $$ LANGUAGE PLPGSQL STABLE;
-- Test domains with default value for table rewrite. CREATE DOMAIN domain1 ASintDEFAULT11; -- constant CREATE DOMAIN domain2 ASintDEFAULT random(min=>10, max=>100); -- volatile CREATE DOMAIN domain3 AS text DEFAULT foo(4); -- stable CREATE DOMAIN domain4 AS text[] DEFAULT ('{"This", "is", "' || foo(4) || '","the", "real", "world"}')::TEXT[];
CREATETABLE t2 (a domain1); INSERTINTO t2 VALUES (1),(2);
-- no table rewrite ALTERTABLE t2 ADDCOLUMN b domain1 default3;
SELECT attnum, attname, atthasmissing, atthasdef, attmissingval FROM pg_attribute WHERE attnum > 0AND attrelid = 't2'::regclass ORDERBY attnum;
-- table rewrite should happen ALTERTABLE t2 ADDCOLUMN c domain3 defaultleft(random()::text,3);
-- no table rewrite ALTERTABLE t2 ADDCOLUMN d domain4;
SELECT attnum, attname, atthasmissing, atthasdef, attmissingval FROM pg_attribute WHERE attnum > 0AND attrelid = 't2'::regclass ORDERBY attnum;
-- table rewrite should happen ALTERTABLE t2 ADDCOLUMN e domain2;
SELECT attnum, attname, atthasmissing, atthasdef, attmissingval FROM pg_attribute WHERE attnum > 0AND attrelid = 't2'::regclass ORDERBY attnum;
SELECT a, b, length(c) = 3as c_ok, d, e >= 10as e_ok FROM t2;
DROPTABLE t2; DROP DOMAIN domain1; DROP DOMAIN domain2; DROP DOMAIN domain3; DROP DOMAIN domain4; DROP FUNCTION foo(INT);
-- Fall back to full rewrite for volatile expressions CREATETABLE T(pk INTNOTNULLPRIMARYKEY);
INSERTINTO T VALUES (1);
SELECTset('t');
-- now() is stable, because it returns the transaction timestamp ALTERTABLE T ADDCOLUMN c1 TIMESTAMP DEFAULT now();
SELECT comp();
-- clock_timestamp() is volatile ALTERTABLE T ADDCOLUMN c2 TIMESTAMP DEFAULT clock_timestamp();
SELECT comp();
-- check that we notice insertion of a volatile default argument CREATE FUNCTION foolme(timestamptz DEFAULT clock_timestamp())
RETURNS timestamptz
IMMUTABLE AS'select $1' LANGUAGE sql; ALTERTABLE T ADDCOLUMN c3 timestamptz DEFAULT foolme();
SELECT attname, atthasmissing, attmissingval FROM pg_attribute WHERE attrelid = 't'::regclass AND attnum > 0 ORDERBY attnum;
DROPTABLE T; DROP FUNCTION foolme(timestamptz);
-- Simple querie CREATETABLE T (pk INTNOTNULLPRIMARYKEY);
SELECTset('t');
INSERTINTO T SELECT * FROM generate_series(1, 10) a;
ALTERTABLE T ADDCOLUMN c_bigint BIGINTNOTNULLDEFAULT -1;
INSERTINTO T SELECT b, b - 10FROM generate_series(11, 20) a(b);
ALTERTABLE T ADDCOLUMN c_text TEXT DEFAULT'hello';
INSERTINTO T SELECT b, b - 10, (b + 10)::text FROM generate_series(21, 30) a(b);
-- WHERE clause SELECT c_bigint, c_text FROM T WHERE c_bigint = -1LIMIT1;
EXPLAIN (VERBOSE TRUE, COSTS FALSE) SELECT c_bigint, c_text FROM T WHERE c_bigint = -1LIMIT1;
SELECT c_bigint, c_text FROM T WHERE c_text = 'hello'LIMIT1;
EXPLAIN (VERBOSE TRUE, COSTS FALSE) SELECT c_bigint, c_text FROM T WHERE c_text = 'hello'LIMIT1;
-- COALESCE SELECT COALESCE(c_bigint, pk), COALESCE(c_text, pk::text) FROM T ORDERBY pk LIMIT10;
-- Aggregate function SELECT SUM(c_bigint), MAX(c_text COLLATE"C" ), MIN(c_text COLLATE"C") FROM T;
-- ORDER BY SELECT * FROM T ORDERBY c_bigint, c_text, pk LIMIT10;
EXPLAIN (VERBOSE TRUE, COSTS FALSE) SELECT * FROM T ORDERBY c_bigint, c_text, pk LIMIT10;
-- LIMIT SELECT * FROM T WHERE c_bigint > -1ORDERBY c_bigint, c_text, pk LIMIT10;
EXPLAIN (VERBOSE TRUE, COSTS FALSE) SELECT * FROM T WHERE c_bigint > -1ORDERBY c_bigint, c_text, pk LIMIT10;
-- DELETE with RETURNING DELETEFROM T WHERE pk BETWEEN10AND20 RETURNING *; EXPLAIN (VERBOSE TRUE, COSTS FALSE) DELETEFROM T WHERE pk BETWEEN10AND20 RETURNING *;
-- UPDATE UPDATE T SET c_text = '"' || c_text || '"'WHERE pk < 10; SELECT * FROM T WHERE c_text LIKE'"%"'ORDERBY PK;
SELECT comp();
DROPTABLE T;
-- Combine with other DDL CREATETABLE T(pk INTNOTNULLPRIMARYKEY);
SELECTset('t');
INSERTINTO T VALUES (1), (2);
ALTERTABLE T ADDCOLUMN c_int INTNOTNULLDEFAULT -1;
INSERTINTO T VALUES (3), (4);
ALTERTABLE T ADDCOLUMN c_text TEXT DEFAULT'Hello';
INSERTINTO T VALUES (5), (6);
ALTERTABLE T ALTERCOLUMN c_text SETDEFAULT'world', ALTERCOLUMN c_int SETDEFAULT1;
INSERTINTO T VALUES (7), (8);
SELECT * FROM T ORDERBY pk;
-- Add an index CREATEINDEX i ON T(c_int, c_text);
SELECT c_text FROM T WHERE c_int = -1;
SELECT comp();
-- query to exercise expand_tuple function CREATETABLE t1 AS SELECT1::intAS a , 2::intAS b FROM generate_series(1,20) q;
ALTERTABLE t1 ADDCOLUMN c text;
SELECT a,
stddev(cast((SELECT sum(1) FROM generate_series(1,20) x) ASfloat4))
OVER (PARTITION BY a,b,c ORDERBY b) AS z FROM t1;
DROPTABLE T;
-- test that we account for missing columns without defaults correctly -- in expand_tuple, and that rows are correctly expanded for triggers
CREATE FUNCTION test_trigger()
RETURNS trigger
LANGUAGE plpgsql AS $$
begin
raise notice 'old tuple: %', to_json(OLD)::text; if TG_OP = 'DELETE' then return OLD; else return NEW;
end if;
end;
$$;
-- 2 new columns, both have defaults CREATETABLE t (id serial PRIMARYKEY, a int, b int, c int); INSERTINTO t (a,b,c) VALUES (1,2,3); ALTERTABLE t ADDCOLUMN x intNOTNULLDEFAULT4; ALTERTABLE t ADDCOLUMN y intNOTNULLDEFAULT5; CREATETRIGGER a BEFOREUPDATEON t FOREACH ROW EXECUTE PROCEDURE test_trigger(); SELECT * FROM t; UPDATE t SET y = 2; SELECT * FROM t; DROPTABLE t;
-- 2 new columns, first has default CREATETABLE t (id serial PRIMARYKEY, a int, b int, c int); INSERTINTO t (a,b,c) VALUES (1,2,3); ALTERTABLE t ADDCOLUMN x intNOTNULLDEFAULT4; ALTERTABLE t ADDCOLUMN y int; CREATETRIGGER a BEFOREUPDATEON t FOREACH ROW EXECUTE PROCEDURE test_trigger(); SELECT * FROM t; UPDATE t SET y = 2; SELECT * FROM t; DROPTABLE t;
-- 2 new columns, second has default CREATETABLE t (id serial PRIMARYKEY, a int, b int, c int); INSERTINTO t (a,b,c) VALUES (1,2,3); ALTERTABLE t ADDCOLUMN x int; ALTERTABLE t ADDCOLUMN y intNOTNULLDEFAULT5; CREATETRIGGER a BEFOREUPDATEON t FOREACH ROW EXECUTE PROCEDURE test_trigger(); SELECT * FROM t; UPDATE t SET y = 2; SELECT * FROM t; DROPTABLE t;
-- 2 new columns, neither has default CREATETABLE t (id serial PRIMARYKEY, a int, b int, c int); INSERTINTO t (a,b,c) VALUES (1,2,3); ALTERTABLE t ADDCOLUMN x int; ALTERTABLE t ADDCOLUMN y int; CREATETRIGGER a BEFOREUPDATEON t FOREACH ROW EXECUTE PROCEDURE test_trigger(); SELECT * FROM t; UPDATE t SET y = 2; SELECT * FROM t; DROPTABLE t;
-- same as last 4 tests but here the last original column has a NULL value -- 2 new columns, both have defaults CREATETABLE t (id serial PRIMARYKEY, a int, b int, c int); INSERTINTO t (a,b,c) VALUES (1,2,NULL); ALTERTABLE t ADDCOLUMN x intNOTNULLDEFAULT4; ALTERTABLE t ADDCOLUMN y intNOTNULLDEFAULT5; CREATETRIGGER a BEFOREUPDATEON t FOREACH ROW EXECUTE PROCEDURE test_trigger(); SELECT * FROM t; UPDATE t SET y = 2; SELECT * FROM t; DROPTABLE t;
-- 2 new columns, first has default CREATETABLE t (id serial PRIMARYKEY, a int, b int, c int); INSERTINTO t (a,b,c) VALUES (1,2,NULL); ALTERTABLE t ADDCOLUMN x intNOTNULLDEFAULT4; ALTERTABLE t ADDCOLUMN y int; CREATETRIGGER a BEFOREUPDATEON t FOREACH ROW EXECUTE PROCEDURE test_trigger(); SELECT * FROM t; UPDATE t SET y = 2; SELECT * FROM t; DROPTABLE t;
-- 2 new columns, second has default CREATETABLE t (id serial PRIMARYKEY, a int, b int, c int); INSERTINTO t (a,b,c) VALUES (1,2,NULL); ALTERTABLE t ADDCOLUMN x int; ALTERTABLE t ADDCOLUMN y intNOTNULLDEFAULT5; CREATETRIGGER a BEFOREUPDATEON t FOREACH ROW EXECUTE PROCEDURE test_trigger(); SELECT * FROM t; UPDATE t SET y = 2; SELECT * FROM t; DROPTABLE t;
-- 2 new columns, neither has default CREATETABLE t (id serial PRIMARYKEY, a int, b int, c int); INSERTINTO t (a,b,c) VALUES (1,2,NULL); ALTERTABLE t ADDCOLUMN x int; ALTERTABLE t ADDCOLUMN y int; CREATETRIGGER a BEFOREUPDATEON t FOREACH ROW EXECUTE PROCEDURE test_trigger(); SELECT * FROM t; UPDATE t SET y = 2; SELECT * FROM t; DROPTABLE t;
-- make sure expanded tuple has correct self pointer -- it will be required by the RI trigger doing the cascading delete
CREATETABLE leader (a intPRIMARYKEY, b int); CREATETABLE follower (a intREFERENCES leader ONDELETECASCADE, b int); INSERTINTO leader VALUES (1, 1), (2, 2); ALTERTABLE leader ADD c int; ALTERTABLE leader DROP c; DELETEFROM leader;
-- check that ALTER TABLE ... ALTER TYPE does the right thing
CREATETABLE vtype( a integer); INSERTINTO vtype VALUES (1); ALTERTABLE vtype ADDCOLUMN b DOUBLEPRECISIONDEFAULT0.2; ALTERTABLE vtype ADDCOLUMN c BOOLEAN DEFAULTtrue; SELECT * FROM vtype; ALTERTABLE vtype ALTER b TYPE text USING b::text, ALTER c TYPE text USING c::text; SELECT * FROM vtype;
-- also check the case that doesn't rewrite the table
CREATETABLE vtype2 (a int); INSERTINTO vtype2 VALUES (1); ALTERTABLE vtype2 ADDCOLUMN b varchar(10) DEFAULT'xxx'; ALTERTABLE vtype2 ALTERCOLUMN b SETDEFAULT'yyy'; INSERTINTO vtype2 VALUES (2);
ALTERTABLE vtype2 ALTERCOLUMN b TYPE varchar(20) USING b::varchar(20); SELECT * FROM vtype2;
-- Ensure that defaults are checked when evaluating whether HOT update -- is possible, this was broken for a while: -- https://postgr.es/m/20190202133521.ylauh3ckqa7colzj%40alap3.anarazel.de
BEGIN; CREATETABLE t(); INSERTINTO t DEFAULTVALUES; ALTERTABLE t ADDCOLUMN a intDEFAULT1; CREATEINDEXON t(a); -- set column with a default 1 to NULL, due to a bug that wasn't -- noticed has heap_getattr buggily returned NULL for default columns UPDATE t SET a = NULL;
-- verify that index and non-index scans show the same result SET LOCAL enable_seqscan = true; SELECT * FROM t WHERE a ISNULL; SET LOCAL enable_seqscan = false; SELECT * FROM t WHERE a ISNULL;
ROLLBACK;
-- verify that a default set on a non-plain table doesn't set a missing -- value on the attribute CREATEFOREIGN DATA WRAPPER dummy; CREATE SERVER s0 FOREIGN DATA WRAPPER dummy; CREATEFOREIGNTABLE ft1 (c1 integerNOTNULL) SERVER s0; ALTERFOREIGNTABLE ft1 ADDCOLUMN c8 integerDEFAULT0; ALTERFOREIGNTABLE ft1 ALTERCOLUMN c8 TYPE char(10); SELECT count(*) FROM pg_attribute WHERE attrelid = 'ft1'::regclass AND
(attmissingval ISNOTNULLOR atthasmissing);
-- cleanup DROPFOREIGNTABLE ft1; DROP SERVER s0; DROPFOREIGN DATA WRAPPER dummy; DROPTABLE vtype; DROPTABLE vtype2; DROPTABLE follower; DROPTABLE leader; DROP FUNCTION test_trigger(); DROPTABLE t1; DROP FUNCTION set(name); DROP FUNCTION comp(); DROPTABLE m; DROPTABLE has_volatile; DROP EVENT TRIGGER has_volatile_rewrite; DROP FUNCTION log_rewrite; DROPSCHEMA fast_default;
-- Leave a table with an active fast default in place, for pg_upgrade testing set search_path = public; createtable has_fast_default(f1 int); insertinto has_fast_default values(1); altertable has_fast_default addcolumn f2 intdefault42; table has_fast_default;
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.