/* * Author: Jim Nasby * Created at: 2014-10-07 17:50:51 -0500 * */ SET client_min_messages = warning; /* * Extension is configured to go into the variant schema, which Postgres * creates for us. We just need to fix the permissions. */ GRANT USAGE ON SCHEMA variant TO public; CREATE SCHEMA _variant; CREATE OR REPLACE FUNCTION _variant.exec( sql text ) RETURNS void LANGUAGE plpgsql AS $f$ BEGIN RAISE DEBUG 'Executing SQL %s', sql; EXECUTE sql; END $f$; CREATE TYPE variant._variant as ( original_type text, data text ); CREATE TYPE variant.variant; CREATE OR REPLACE FUNCTION _variant._variant_in(cstring, Oid, int) RETURNS variant.variant LANGUAGE c IMMUTABLE STRICT AS '$libdir/variant', 'variant_in'; CREATE OR REPLACE FUNCTION _variant._variant_typmod_in(cstring[]) RETURNS int LANGUAGE c IMMUTABLE STRICT AS '$libdir/variant', 'variant_typmod_in'; -- See also second definition at bottom of file CREATE OR REPLACE FUNCTION variant.text_in(text, int) RETURNS variant.variant LANGUAGE c IMMUTABLE AS '$libdir/variant', 'variant_text_in'; CREATE OR REPLACE FUNCTION _variant._variant_out(variant.variant) RETURNS cstring LANGUAGE c IMMUTABLE STRICT AS '$libdir/variant', 'variant_out'; CREATE OR REPLACE FUNCTION _variant._variant_typmod_out(int) RETURNS cstring LANGUAGE c IMMUTABLE STRICT AS '$libdir/variant', 'variant_typmod_out'; CREATE OR REPLACE FUNCTION variant.text_out(variant.variant) RETURNS text LANGUAGE c IMMUTABLE STRICT AS '$libdir/variant', 'variant_text_out'; CREATE TYPE variant.variant( INPUT = _variant._variant_in , OUTPUT = _variant._variant_out , TYPMOD_IN = _variant._variant_typmod_in , TYPMOD_OUT = _variant._variant_typmod_out , STORAGE = extended ); -- Can only create this after type is fully created -- See also second definition at bottom of file CREATE OR REPLACE FUNCTION variant.text_in(text) RETURNS variant.variant LANGUAGE sql IMMUTABLE STRICT AS $f$ SELECT variant.text_in( $1, -1 ) $f$; CREATE OR REPLACE FUNCTION _variant.variant_hash(variant.variant) RETURNS int LANGUAGE c IMMUTABLE STRICT AS '$libdir/variant', 'variant_hash'; CREATE OR REPLACE FUNCTION _variant.quote_variant_name(text) RETURNS text LANGUAGE c IMMUTABLE STRICT AS '$libdir/variant', 'quote_variant_name'; CREATE OR REPLACE FUNCTION variant.original_type(variant.variant) RETURNS regtype LANGUAGE c IMMUTABLE STRICT AS '$libdir/variant', 'variant_type_out'; SELECT NULL = count(*) FROM ( -- Supress tons of blank lines SELECT _variant.exec( format($$ CREATE OR REPLACE FUNCTION _variant.variant_%1$s(variant.variant, variant.variant) RETURNS boolean LANGUAGE c IMMUTABLE STRICT AS '$libdir/variant', 'variant_%1$s'; $$ , op ) ) FROM unnest(string_to_array('image_eq lt le eq ne ge gt', ' ')) AS op ) a; CREATE OPERATOR < ( PROCEDURE = _variant.variant_lt , LEFTARG = variant.variant , RIGHTARG = variant.variant , COMMUTATOR = > , NEGATOR = >= ); CREATE OPERATOR <= ( PROCEDURE = _variant.variant_le , LEFTARG = variant.variant , RIGHTARG = variant.variant , COMMUTATOR = >= , NEGATOR = > ); CREATE OPERATOR = ( PROCEDURE = _variant.variant_eq , LEFTARG = variant.variant , RIGHTARG = variant.variant , COMMUTATOR = = , NEGATOR = != ); CREATE OPERATOR != ( PROCEDURE = _variant.variant_ne , LEFTARG = variant.variant , RIGHTARG = variant.variant , COMMUTATOR = != , NEGATOR = = ); CREATE OPERATOR >= ( PROCEDURE = _variant.variant_ge , LEFTARG = variant.variant , RIGHTARG = variant.variant , COMMUTATOR = <= , NEGATOR = < ); CREATE OPERATOR > ( PROCEDURE = _variant.variant_gt , LEFTARG = variant.variant , RIGHTARG = variant.variant , COMMUTATOR = < , NEGATOR = <= ); CREATE OPERATOR *= ( PROCEDURE = _variant.variant_image_eq , LEFTARG = variant.variant , RIGHTARG = variant.variant , COMMUTATOR = *= -- TODO , NEGATOR = *!= ); CREATE OPERATOR CLASS hash__variant_ops DEFAULT FOR TYPE variant.variant USING hash AS OPERATOR 1 *= , FUNCTION 1 _variant.variant_hash(variant.variant) ; CREATE OR REPLACE VIEW _variant.allowed_types AS SELECT t.oid::regtype AS type_name , 'variant.variant'::regtype AS source , 'variant.variant'::regtype AS target FROM pg_catalog.pg_type t LEFT JOIN pg_catalog.pg_type e ON e.oid = t.typelem WHERE true AND t.typisdefined AND t.typtype != 'c' -- We don't currently support composite types AND (e.typtype IS NULL OR e.typtype != 'c' ) -- Or arrays of composite types AND t.typtype != 'p' -- Or pseudotypes AND t.typtype != 'd' -- You can't cast to or from domains :( ; CREATE OR REPLACE VIEW _variant.casts AS SELECT castsource::regtype AS source, casttarget::regtype AS target , castfunc::regprocedure AS cast_function , CASE castcontext WHEN 'e' THEN 'explicit' WHEN 'a' THEN 'assignment' WHEN 'i' THEN 'implicit' END AS cast_context , CASE castmethod WHEN 'f' THEN 'function' WHEN 'i' THEN 'inout' WHEN 'b' THEN 'binary' END AS cast_method FROM pg_catalog.pg_cast c ; CREATE OR REPLACE VIEW variant.variant_casts AS SELECT * FROM _variant.casts WHERE false OR source = 'variant.variant'::regtype OR target = 'variant.variant'::regtype ; CREATE OR REPLACE VIEW _variant.missing_casts_in AS SELECT t.type_name AS source, target FROM _variant.allowed_types t EXCEPT SELECT source, target FROM variant.variant_casts EXCEPT SELECT 'variant.variant', 'variant.variant' ; CREATE OR REPLACE VIEW _variant.missing_casts_out AS SELECT source, t.type_name AS target FROM _variant.allowed_types t EXCEPT SELECT source, target FROM variant.variant_casts EXCEPT SELECT 'variant.variant', 'variant.variant' ; CREATE OR REPLACE VIEW variant.missing_casts AS SELECT *, 'IN' AS direction FROM _variant.missing_casts_in UNION ALL SELECT *, 'OUT' AS direction FROM _variant.missing_casts_out ; CREATE OR REPLACE FUNCTION _variant.create_cast_in( p_source regtype ) RETURNS void LANGUAGE plpgsql AS $f$ BEGIN PERFORM _variant.exec( format( $sql$CREATE OR REPLACE FUNCTION _variant.cast_in( i %s , typmod int , explicit boolean ) RETURNS variant.variant LANGUAGE c IMMUTABLE AS '$libdir/variant', 'variant_cast_in' $sql$ , p_source -- i data type ) ); PERFORM _variant.exec( format( 'CREATE CAST( %s AS variant.variant ) WITH FUNCTION _variant.cast_in( %1$s, int, boolean ) AS %s' , p_source , CASE (SELECT typcategory FROM pg_type WHERE oid = p_source) WHEN 'A' THEN 'ASSIGNMENT' ELSE 'IMPLICIT' END ) ); END $f$; CREATE OR REPLACE FUNCTION _variant.create_cast_out( p_target regtype ) RETURNS void LANGUAGE plpgsql AS $f$ DECLARE v_function_name name := 'cast_to_' || regexp_replace( CASE WHEN p_target::text LIKE '%[]' THEN '_' || regexp_replace( p_target::text, '\[]$', '' ) ELSE p_target::text END , '[\. "]' -- Replace invarid identifier characters with '_' , '_' , 'g' -- Replace globally ) ; BEGIN PERFORM _variant.exec( format( $sql$CREATE OR REPLACE FUNCTION _variant.%s( v variant.variant ) RETURNS %1s LANGUAGE c IMMUTABLE AS '$libdir/variant', 'variant_cast_out' $sql$ , v_function_name , p_target ) ); PERFORM _variant.exec( format( 'CREATE CAST( variant.variant AS %s) WITH FUNCTION _variant.%s( variant.variant ) AS ASSIGNMENT' , p_target , v_function_name ) ); END $f$; CREATE OR REPLACE FUNCTION variant.create_casts() RETURNS void LANGUAGE plpgsql AS $f$ DECLARE r variant.missing_casts; sql text; BEGIN FOR r IN SELECT * FROM variant.missing_casts LOOP IF r.direction = 'IN' THEN PERFORM _variant.create_cast_in( r.source ); ELSIF r.direction = 'OUT' THEN PERFORM _variant.create_cast_out( r.target ); ELSE RAISE EXCEPTION 'Unknown cast direction "%"', r.direction; END IF; END LOOP; END $f$; -- Automagically create casts for everything we support SELECT variant.create_casts(); CREATE TABLE _variant._registered( variant_typmod SERIAL PRIMARY KEY CONSTRAINT variant_typemod_minimum_value CHECK( variant_typmod >= -1 ) , variant_name varchar(100) NOT NULL , variant_enabled boolean NOT NULL DEFAULT true , storage_allowed boolean NOT NULL DEFAULT false -- Fix variant.register() if you change the default , allowed_types regtype[] NOT NULL CONSTRAINT allowed_types_may_not_contain_nulls /* * Make sure there's no NULLs in allowed_types. Aside from being a good * idea, this is required by _variant._tg_check_type_usage. */ CHECK( allowed_types = array_remove(allowed_types, NULL) ) , CONSTRAINT storing_default_variant_not_supported CHECK( variant_typmod >= 0 OR NOT storage_allowed ) ); CREATE UNIQUE INDEX _registered__u_lcase_variant_name ON _variant._registered( lower( variant_name ) ); CREATE UNIQUE INDEX _registered__u_quote_variant_name ON _variant._registered( _variant.quote_variant_name( variant_name ) ); INSERT INTO _variant._registered VALUES( -1, 'DEFAULT', true, false, '{}' ); -- Necessary for internal functions to not be SECDEF CREATE VIEW variant._registered AS SELECT * FROM _variant._registered; GRANT SELECT ON variant._registered TO public; CREATE VIEW variant.registered AS SELECT variant_typmod, _variant.quote_variant_name(variant_name), variant_enabled, storage_allowed, coalesce( array_length(allowed_types, 1), 0 ) AS types_allowed FROM _variant._registered ; CREATE VIEW _variant.stored AS SELECT atttypmod AS variant_typmod, quote_ident(attname) AS column_name, a.* FROM pg_attribute a JOIN pg_class c ON c.oid = a.attrelid WHERE NOT attisdropped AND atttypid = 'variant.variant'::regtype AND c.relkind != 'v' -- Views don't have storage -- NOTE: We intentionally look at all temp tables, not just our own ; CREATE VIEW variant.stored AS SELECT * , array( SELECT attrelid::regclass || '.' || column_name FROM _variant.stored WHERE variant_typmod = r.variant_typmod ) AS columns_using_variant FROM variant.registered r ; CREATE VIEW variant.stored__bad AS SELECT r.*, attrelid::regclass AS table_name, column_name, format_type(atttypid, variant_typmod) AS type_name FROM _variant.stored s LEFT JOIN variant.registered r USING( variant_typmod ) WHERE NOT storage_allowed OR NOT variant_enabled ; CREATE OR REPLACE FUNCTION _variant._tg_check_type_usage( ) RETURNS trigger LANGUAGE plpgsql AS $f$ /* * Verify that if we're removing a type from the list of allowed types that * this registered variant isn't being used in a table anywhere. * * TODO: We should have a way to verify that a table doesn't contain any rows * with a particular type. */ DECLARE v_columns text[]; v_new_types _variant._registered.allowed_types%TYPE; v_new_storage _variant._registered.storage_allowed%TYPE; BEGIN -- Special cases for DEFAULT variant IF OLD.variant_typmod = -1 THEN IF TG_OP = 'DELETE' THEN RAISE EXCEPTION 'Deleting DEFAULT variant is not allowed'; END IF; IF EXISTS( SELECT 1 FROM _variant.stored WHERE variant_typmod = -1 ) THEN RAISE WARNING 'There are columns storing a DEFAULT variant. These must be changed to a registered variant immediately.' USING DETAIL = 'Affected columns:' || E'\n\t' || array_to_string( array( SELECT attrelid::regclass || '.' || column_name FROM _variant.stored WHERE variant_typmod = -1 ) , E'\n\t' ) ; END IF; IF NEW.storage_allowed THEN RAISE EXCEPTION 'Enabling storage of DEFAULT variant is not allowed' USING ERRCODE = 'invalid_parameter_value' ; END IF; END IF; IF TG_OP = 'UPDATE' THEN IF NEW.variant_typmod IS DISTINCT FROM OLD.variant_typmod THEN RAISE EXCEPTION 'Changing variant typmods is not allowed' USING ERRCODE = 'invalid_parameter_value' ; END IF; IF NEW.allowed_types @> OLD.allowed_types AND NEW.storage_allowed THEN -- User didn't remove any types or disable storage RETURN NULL; END IF; v_new_types := NEW.allowed_types; v_new_storage := NEW.storage_allowed; ELSE v_new_types := '{}'; END IF; v_columns := columns_using_variant FROM variant.stored WHERE variant_typmod = OLD.variant_typmod; RAISE DEBUG 'TG_OP: %, OLD.allowed_types %, NEW.allowed_types %, OLD.storage_allowed %, NEW.storage_allowed %, v_columns %' , TG_WHEN , OLD.allowed_types , v_new_types , OLD.storage_allowed , v_new_storage , v_columns ; IF v_columns IS DISTINCT FROM '{}' THEN RAISE EXCEPTION 'variant "%" is still in use', OLD.variant_name USING ERRCODE = 'dependent_objects_still_exist' , DETAIL = E'in use by ' || array_to_string( v_columns, ', ' ) ; END IF; RETURN NULL; END $f$; CREATE TRIGGER check_type_usage AFTER UPDATE OR DELETE ON _variant._registered FOR EACH ROW EXECUTE PROCEDURE _variant._tg_check_type_usage() ; CREATE OR REPLACE FUNCTION _variant.stored__bad( ) RETURNS text[] LANGUAGE sql AS $body$ SELECT array( SELECT table_name || '.' || column_name || ' ' || type_name FROM variant.stored__bad ) $body$; /* * WARNING: This function is called from a SECDEF trigger! */ CREATE OR REPLACE FUNCTION _variant._verify_storage( p_fix_it boolean ) RETURNS void LANGUAGE plpgsql AS $body$ DECLARE v_bad CONSTANT text[] := _variant.stored__bad(); BEGIN IF v_bad IS DISTINCT FROM '{}' THEN IF p_fix_it THEN UPDATE _variant._registered SET storage_allowed = true , variant_enabled = true WHERE _variant.quote_variant_name( variant_name ) IN ( SELECT quote_variant_name FROM variant.stored__bad ) ; RAISE WARNING 'Found table columns with variants that were disabled or disallowed storage' USING DETAIL = 'The following variants were enabled and had storage allowed:' || E'\n\t' || array_to_string( v_bad, E'\n\t' ) ; ELSE RAISE EXCEPTION 'detected tables containing variants that do not allow storage' USING ERRCODE = 'invalid_parameter_value' , DETAIL = 'Bad table columns and types:' || E'\n\t' || array_to_string( v_bad, E'\n\t' ) , HINT = 'Use variant.storage_allowed() to allow storage for a variant' ; END IF; END IF; END $body$; CREATE OR REPLACE FUNCTION variant._etg_verify_storage_start( ) RETURNS event_trigger SECURITY DEFINER LANGUAGE plpgsql AS $body$ BEGIN PERFORM _variant._verify_storage( true ); END $body$; CREATE OR REPLACE FUNCTION variant._etg_verify_storage_end( ) RETURNS event_trigger SECURITY DEFINER LANGUAGE plpgsql AS $body$ BEGIN PERFORM _variant._verify_storage( false ); END $body$; CREATE OR REPLACE FUNCTION _variant._ensure_storage_check_one( p_warning boolean , p_start_end text ) RETURNS boolean LANGUAGE plpgsql AS $body$ DECLARE t_enable CONSTANT text := 'ALTER EVENT TRIGGER %I ENABLE'; t_create CONSTANT text := $template$ CREATE EVENT TRIGGER variant_storage_check_%1$s ON ddl_command_%1$s WHEN tag IN ( 'ALTER DOMAIN', 'ALTER TABLE' , 'CREATE DOMAIN', 'CREATE TABLE', 'CREATE TABLE AS' ) EXECUTE PROCEDURE variant._etg_verify_storage_%1$s() $template$; v_enabled "char"; v_name name; BEGIN IF p_start_end NOT IN ( 'start', 'end' ) THEN RAISE 'something seriously wrong just happened'; END IF; BEGIN SELECT evtenabled, evtname INTO STRICT v_enabled, v_name FROM pg_event_trigger WHERE evtevent = 'ddl_command_' || p_start_end AND evtfoid = ('variant._etg_verify_storage_' || p_start_end )::regproc ; EXCEPTION WHEN NO_DATA_FOUND THEN IF p_warning THEN RAISE WARNING 'No ddl_command_% event trigger to verify variant storage; re-creating', p_start_end; END IF; PERFORM _variant.exec( format(t_create, p_start_end) ); RETURN true; END; IF v_enabled IS DISTINCT FROM 'O' THEN PERFORM _variant.exec( format(t_enable, evtname) ); RETURN true; END IF; RETURN false; END $body$; CREATE OR REPLACE FUNCTION _variant._ensure_storage_check( p_warning boolean DEFAULT true ) RETURNS void LANGUAGE plpgsql AS $body$ BEGIN -- NOTE: Must do it this way or the optimizer gets cute and doesn't call the function both times IF bool_or( _variant._ensure_storage_check_one( p_warning, a ) ) FROM unnest( array[ 'start', 'end' ] ) a THEN -- Since we were missing one or both event triggers fix anything that's now broken PERFORM _variant._verify_storage(true); END IF; END $body$; SELECT _variant._ensure_storage_check( false ); CREATE OR REPLACE FUNCTION variant.register( p_variant_name _variant._registered.variant_name%TYPE , p_allowed_types _variant._registered.allowed_types%TYPE DEFAULT '{}' , p_storage_allowed _variant._registered.storage_allowed%TYPE DEFAULT NULL ) RETURNS _variant._registered.variant_typmod%TYPE LANGUAGE plpgsql AS $func$ DECLARE c_test_table CONSTANT text := 'test_ability_to_create_table_with_just_registered_variant'; v_storage_allowed CONSTANT _variant._registered.storage_allowed%TYPE := coalesce( p_storage_allowed, false ); v_formatted_type text; ret _variant._registered.variant_typmod%TYPE; BEGIN PERFORM _variant._ensure_storage_check(); IF p_variant_name IS NULL THEN RAISE EXCEPTION 'variant_name may not be NULL'; END IF; IF p_variant_name = '' THEN RAISE EXCEPTION 'variant_name may not be an empty string'; END IF; INSERT INTO _variant._registered( variant_name, storage_allowed, allowed_types ) VALUES( p_variant_name, true, p_allowed_types ) RETURNING variant_typmod INTO ret ; v_formatted_type := pg_catalog.format_type( 'variant.variant'::regtype, ret ); -- This ensures that the user can actually use the variant that they're registering BEGIN PERFORM _variant.exec(format( $$CREATE TEMP TABLE %I(v %s)$$ , c_test_table , v_formatted_type )); EXCEPTION WHEN syntax_error THEN RAISE EXCEPTION '% is not a valid name for a variant', p_variant_name USING ERRCODE = 'syntax_error' , HINT = 'variant names must be valid type modifiers: string literals or numbers' , DETAIL = 'formatted type output: ' || coalesce(v_formatted_type, '<>') ; END; PERFORM _variant.exec(format( $$DROP TABLE %I$$ , c_test_table )); -- Now disable storage, which is the default UPDATE _variant._registered SET storage_allowed = v_storage_allowed WHERE variant_typmod = ret -- Don't make a needless update AND storage_allowed IS DISTINCT FROM v_storage_allowed ; RETURN ret; END $func$; CREATE OR REPLACE FUNCTION _variant.registered__get( p_variant_typmod _variant._registered.variant_typmod%TYPE ) RETURNS _variant._registered LANGUAGE plpgsql STABLE AS $f$ DECLARE r_variant _variant._registered%ROWTYPE; BEGIN SELECT * INTO STRICT r_variant FROM _variant._registered WHERE variant_typmod = p_variant_typmod ; RETURN r_variant; EXCEPTION WHEN NO_DATA_FOUND THEN RAISE EXCEPTION 'Invalid typmod %', coalesce(p_variant_typmod::text, '<>') USING ERRCODE = 'invalid_parameter_value' ; END $f$; CREATE OR REPLACE FUNCTION _variant.registered__get__variant_name__enabled( _variant._registered.variant_typmod%TYPE ) RETURNS TABLE( variant_name _variant._registered.variant_name%TYPE , variant_enabled _variant._registered.variant_enabled%TYPE ) LANGUAGE sql STABLE AS $f$ SELECT variant_name, variant_enabled FROM _variant.registered__get( $1 ) $f$; -- TODO: Might want a non-locking version of this that we can mark stable CREATE OR REPLACE FUNCTION _variant.registered__get( p_variant_name _variant._registered.variant_name%TYPE , p_lock boolean DEFAULT false ) RETURNS _variant._registered LANGUAGE plpgsql AS $f$ DECLARE r_variant _variant._registered%ROWTYPE; BEGIN IF p_lock THEN SELECT * INTO STRICT r_variant FROM _variant._registered WHERE lower( variant_name ) = lower( p_variant_name ) FOR UPDATE ; ELSE SELECT * INTO STRICT r_variant FROM _variant._registered WHERE lower( variant_name ) = lower( p_variant_name ) ; END IF; RETURN r_variant; EXCEPTION WHEN NO_DATA_FOUND THEN RAISE EXCEPTION 'Invalid variant type %', coalesce(p_variant_name, '<>') USING ERRCODE = 'invalid_parameter_value' ; END $f$; CREATE OR REPLACE FUNCTION _variant.registered__get__typmod( _variant._registered.variant_name%TYPE ) RETURNS _variant._registered.variant_typmod%TYPE LANGUAGE sql STABLE AS $f$ SELECT variant_typmod FROM _variant.registered__get( $1 ) $f$; CREATE OR REPLACE FUNCTION variant._registered__get__typmod( _variant._registered.variant_name%TYPE ) RETURNS _variant._registered.variant_typmod%TYPE SECURITY DEFINER LANGUAGE sql STABLE AS $f$ SELECT _variant.registered__get__typmod($1) $f$; CREATE OR REPLACE FUNCTION variant.text_in(text, text) RETURNS variant.variant LANGUAGE sql IMMUTABLE STRICT AS $f$ SELECT variant.text_in( $1, variant._registered__get__typmod($2) ) $f$; CREATE OR REPLACE FUNCTION variant.storage_allowed( p_variant_name _variant._registered.variant_name%TYPE , p_storage_allowed _variant._registered.storage_allowed%TYPE ) RETURNS void LANGUAGE plpgsql AS $body$ DECLARE v_typmod CONSTANT _variant._registered.variant_typmod%TYPE := _variant.registered__get__typmod( p_variant_name ); BEGIN PERFORM _variant._ensure_storage_check(); IF v_typmod = -1 AND p_storage_allowed THEN RAISE EXCEPTION 'Enabling storage of DEFAULT variant is not allowed' USING ERRCODE = 'invalid_parameter_value' ; END IF; UPDATE _variant._registered SET storage_allowed = p_storage_allowed WHERE variant_typmod = v_typmod AND storage_allowed IS DISTINCT FROM p_storage_allowed ; END $body$; CREATE OR REPLACE FUNCTION variant.allowed_types( p_variant_name _variant._registered.variant_name%TYPE ) RETURNS TABLE(allowed_type regtype) LANGUAGE sql STABLE AS $f$ SELECT * FROM unnest( ( SELECT allowed_types FROM _variant.registered__get( p_variant_name ) ) ) t(t) ORDER BY t::text ; $f$; CREATE OR REPLACE FUNCTION variant.add_types( p_variant_name _variant._registered.variant_name%TYPE , p_allowed_types _variant._registered.allowed_types%TYPE ) RETURNS TABLE(allowed_type regtype) LANGUAGE plpgsql AS $f$ DECLARE v_new_allowed _variant._registered.allowed_types%TYPE; v_current record; BEGIN PERFORM _variant._ensure_storage_check(); -- Lock this record when we get it v_current := _variant.registered__get( p_variant_name, true ); UPDATE _variant._registered -- It seems worthwhile to keep stuff unique SET allowed_types = array( SELECT * FROM unnest( v_current.allowed_types ) UNION ALL SELECT * FROM unnest( p_allowed_types ) ) WHERE variant_typmod = v_current.variant_typmod RETURNING allowed_types INTO v_new_allowed ; RETURN QUERY SELECT t FROM unnest(v_new_allowed) t(t) ORDER BY t::text; END $f$; CREATE OR REPLACE FUNCTION variant.add_type( p_variant_name _variant._registered.variant_name%TYPE , p_type text ) RETURNS TABLE(allowed_type regtype) LANGUAGE sql AS $f$ SELECT * FROM variant.add_types($1,array[ $2::regtype ]) $f$; CREATE OR REPLACE FUNCTION variant.remove_types( p_variant_name _variant._registered.variant_name%TYPE , p_removed_types _variant._registered.allowed_types%TYPE ) RETURNS TABLE(allowed_type regtype) LANGUAGE plpgsql AS $f$ DECLARE v_new_allowed _variant._registered.allowed_types%TYPE; v_current record; BEGIN PERFORM _variant._ensure_storage_check(); -- Lock this record when we get it v_current := _variant.registered__get( p_variant_name, true ); IF NOT v_current.allowed_types && p_removed_types THEN IF array_length(p_removed_types, 1) = 1 THEN RAISE NOTICE 'type %s was already not allowed in variant %s', p_removed_types[1], v_current.variant_name; ELSE RAISE NOTICE 'types %s were already not allowed in variant %s', p_removed_types, v_current.variant_name; END IF; v_new_allowed := v_current.allowed_types; ELSE UPDATE _variant._registered -- It seems worthwhile to keep stuff unique SET allowed_types = array( SELECT t FROM unnest( v_current.allowed_types ) t WHERE t != ANY( p_removed_types ) ) WHERE variant_typmod = v_current.variant_typmod RETURNING allowed_types INTO v_new_allowed ; END IF; RETURN QUERY SELECT t FROM unnest(v_new_allowed) t(t) ORDER BY t::text; END $f$; CREATE OR REPLACE FUNCTION variant.remove_type( p_variant_name _variant._registered.variant_name%TYPE , p_type text ) RETURNS TABLE(allowed_type regtype) LANGUAGE sql AS $f$ SELECT * FROM variant.remove_types($1,array[ $2::regtype ]) $f$; -- vi: expandtab sw=2 ts=2