postgresql 如何防止在Postgres中创建没有主键的表?

gorkyyrv  于 2023-01-30  发布在  PostgreSQL
关注(0)|答案(2)|浏览(182)

我想强制执行一个规则,这样当人们创建没有主键的表时,它会抛出一个错误。有可能在pgdb中完成吗?

jm81lzqq

jm81lzqq1#

DROP EVENT TRIGGER trig_test_event_trigger_table_have_primary_key;

CREATE OR REPLACE FUNCTION test_event_trigger_table_have_primary_key ()
    RETURNS event_trigger
    LANGUAGE plpgsql
    AS $$
DECLARE
    obj record;
    object_types text[];
    table_name text;
BEGIN
    FOR obj IN
    SELECT
        *
    FROM
        pg_event_trigger_ddl_commands ()
        LOOP
            RAISE NOTICE 'classid: % objid: %,object_type: %
    object_identity: % schema_name: % command_tag: %' , obj.classid , obj.objid , obj.object_type , obj.object_identity , obj.schema_name , obj.command_tag;
            IF obj.object_type ~ 'table' THEN
                table_name := obj.object_identity;
            END IF;
            object_types := object_types || obj.object_type;
        END LOOP;
    RAISE NOTICE 'table name: %' , table_name;
    IF EXISTS (
        SELECT
        FROM
            pg_index i
            JOIN pg_attribute a ON a.attrelid = i.indrelid
                AND a.attnum = ANY (i.indkey)
        WHERE
            i.indisprimary
            AND i.indrelid = table_name::regclass) IS FALSE THEN
    RAISE EXCEPTION ' no primary key, this table not created';
END IF;
END;
$$;

CREATE EVENT TRIGGER trig_test_event_trigger_table_have_primary_key ON ddl_command_end
    WHEN TAG IN ('CREATE TABLE')
        EXECUTE FUNCTION test_event_trigger_table_have_primary_key ();

演示:

DROP TABLE a3;
DROP TABLE a4;
DROP TABLE a5;

CREATE TABLE a3 (
    a int
);

CREATE TABLE a4 (
    a int PRIMARY KEY
);

CREATE TABLE a5 (
    a1 int UNIQUE
);

将只创建表a4
相关职位:PL/pgSQL检查行是否存在
https://wiki.postgresql.org/wiki/Retrieve_primary_key_columns

wwtsj6pe

wwtsj6pe2#

    • EDIT**:其他人已经回答了关于如何测试主键是否存在的问题,这就完成了下面的第2部分。您必须将这两个答案结合起来才能得到完整的解决方案。

该逻辑适用于多个event triggers(另请参见documentation for the create command)。
首先要注意的是这可以应用到的DDL命令,所有这些命令都记录在here中。
第1部分:CREATE TABLE ASSELECT INTO
如果我没有记错的话,CREATE TABLE ASSELECT INTO从来没有在创建的表上添加约束,它们必须被一个总是引发异常的事件触发器阻塞。

CREATE OR REPLACE FUNCTION block_ddl()
 RETURNS event_trigger
 LANGUAGE plpgsql AS
$$
BEGIN
    RAISE EXCEPTION 'It is forbidden to create tables using command: %', tg_tag ;
END;
$$;

CREATE EVENT TRIGGER AdHocTables_forbidden
ON ddl_command_end
WHEN TAG IN ('CREATE TABLE AS', 'SELECT INTO')
EXECUTE FUNCTION block_ddl();

请注意,您可以将触发器定义为ON ddl_command_start ',这会使它稍微快一点,但与我在最后发布的完整代码不匹配。
关于其余的解释,请看下一个不那么直接的部分。
第2部分:常规CREATE TABLEALTER TABLE
这种情况更为复杂,因为我们只想阻止一些命令,而不是所有命令。
下面的函数和事件触发器执行以下操作:
1.输出正在传递的整个命令。
1.将命令分解为子部分。
要做到这一点,它使用pg_event_trigger_ddl_commands()(这里的文档),顺便说一句,这就是为什么这个触发器必须在ddl_command_end上的原因。
您会注意到,在添加主键时,也会捕获CREATE INDEX
1.对于下面的函数,在所有情况下都会引发异常以阻止创建(这样您就可以测试它,而不必删除每次创建的表)。
下面是代码:

CREATE OR REPLACE FUNCTION pk_enforced()
 RETURNS event_trigger
 LANGUAGE plpgsql AS
$$
DECLARE r RECORD;
BEGIN
    RAISE NOTICE 'Caught command %', (SELECT current_query());
    FOR r IN SELECT * FROM pg_event_trigger_ddl_commands() LOOP
        RAISE NOTICE 'Caught inside command % (%)', r.command_tag, r.object_identity;
    END LOOP;
    RAISE EXCEPTION 'Blocking the Creation';
END;
$$;

CREATE EVENT TRIGGER pk_is_mandatory
ON ddl_command_end
WHEN TAG IN ('CREATE TABLE', 'ALTER TABLE')
EXECUTE FUNCTION pk_enforced();

补充说明:
通过测试schema_name是否为pg_temp,可以防止在临时表上实施这些约束。
完整的代码,包括这个测试,并与信贷建的功能,他张贴:

CREATE OR REPLACE FUNCTION public.pk_enforced()
    RETURNS event_trigger
    LANGUAGE 'plpgsql'
AS $BODY$
DECLARE
obj RECORD;
table_name text;
BEGIN
FOR obj IN SELECT * FROM pg_event_trigger_ddl_commands ()
LOOP
    IF obj.schema_name = 'pg_temp' THEN
        return;
    END IF;
    IF obj.object_type ~ 'table' THEN
        table_name := obj.object_identity;
    END IF;
END LOOP;
IF NOT EXISTS (
    SELECT 
    FROM pg_index i
    JOIN pg_attribute a ON a.attrelid = i.indrelid AND a.attnum = ANY (i.indkey)
    WHERE i.indrelid = table_name::regclass
      AND (i.indisprimary OR i.indisunique)) THEN
RAISE EXCEPTION 'A primary key or a unique constraint is mandatory to perform % on %.', tg_tag, obj.object_identity;
END IF;
END;
$BODY$;

CREATE OR REPLACE FUNCTION public.block_ddl()
    RETURNS event_trigger
    LANGUAGE 'plpgsql'
AS $BODY$
DECLARE
obj RECORD;
BEGIN
FOR obj IN SELECT * FROM pg_event_trigger_ddl_commands ()
LOOP
    IF obj.schema_name = 'pg_temp' THEN
        return;
    END IF;
END LOOP;
    RAISE EXCEPTION 'DDL command ''%'' is blocked.', tg_tag ;
END;
$BODY$;

CREATE EVENT TRIGGER pk_is_mandatory ON DDL_COMMAND_END
    WHEN TAG IN ('CREATE TABLE', 'ALTER TABLE')
    EXECUTE PROCEDURE public.pk_enforced();
    
CREATE EVENT TRIGGER adhoctables_forbidden ON DDL_COMMAND_END
    WHEN TAG IN ('CREATE TABLE AS', 'SELECT INTO')
    EXECUTE PROCEDURE public.block_ddl();

相关问题