Postgres trigger-based insert redirection without breaking RETURNING

后端 未结 2 1184
自闭症患者
自闭症患者 2020-12-18 10:54

I\'m using table inheritance in postgres, but the trigger I\'m using to partition data into the child tables isn\'t quite behaving right. For example, this query returns nil

相关标签:
2条回答
  • 2020-12-18 11:21

    @pozs provided a correct answer but didn't quite provide the code for a full working implementation. I tried to include the code in an edit on his question, but it was not accepted. He instead suggested yet another approach, which looks cleaner, but may have some drawbacks (in the case where you re-use your trigger function elsewhere).

    Including my solution here for reference:

    CREATE TABLE base_flags (
      id integer NOT NULL,
      flaggable_type character varying(255) NOT NULL,
      flaggable_id integer NOT NULL,
      body text
    );
    
    ALTER TABLE ONLY base_flags
      ADD CONSTRAINT base_flags_pkey PRIMARY KEY (id);
    
    CREATE SEQUENCE base_flags_id_seq
      START WITH 1
      INCREMENT BY 1
      NO MINVALUE
      NO MAXVALUE
      CACHE 1;
    
    ALTER SEQUENCE base_flags_id_seq OWNED BY base_flags.id;
    
    CREATE OR REPLACE VIEW flags AS SELECT * FROM base_flags;
    
    CREATE TABLE "comment_flags" (
      CHECK ("flaggable_type" = 'Comment'),
      PRIMARY KEY ("id"),
      FOREIGN KEY ("flaggable_id") REFERENCES "comments"("id")
    ) INHERITS ("flags");
    
    CREATE TABLE "profile_flags" (
      CHECK ("flaggable_type" = 'Profile'),
      PRIMARY KEY ("id"),
      FOREIGN KEY ("flaggable_id") REFERENCES "profiles"("id")
    ) INHERITS ("flags");
    
    CREATE OR REPLACE FUNCTION flag_insert_trigger_fun() RETURNS TRIGGER AS $BODY$
      BEGIN
        IF NEW.id IS NULL THEN
          NEW.id := nextval('base_flags_id_seq');
        END IF; 
        IF (NEW."flaggable_type" = 'Comment') THEN
          INSERT INTO comment_flags VALUES (NEW.*);
        ELSIF (NEW."flaggable_type" = 'Profile') THEN
          INSERT INTO profile_flags VALUES (NEW.*);
        ELSE
          RAISE EXCEPTION 'Wrong "flaggable_type"="%", fix flag_insert_trigger_fun() function', NEW."flaggable_type";
        END IF;
        RETURN NEW;
      END;
    $BODY$
    LANGUAGE plpgsql;
    
    CREATE TRIGGER flag_insert_trigger
      INSTEAD OF INSERT ON base_flags
      FOR EACH ROW EXECUTE PROCEDURE flag_insert_trigger_fun();
    
    0 讨论(0)
  • 2020-12-18 11:24

    The only workaround I found, is to create a view for the base table & use INSTEAD OF triggers on that view:

    CREATE TABLE flags_base (
        id integer NOT NULL,
        flaggable_type character varying(255) NOT NULL,
        flaggable_id integer NOT NULL,
        body text
    );
    
    ALTER TABLE ONLY flags_base
        ADD CONSTRAINT flags_base_pkey PRIMARY KEY (id);
    
    CREATE TABLE "comment_flags" (
     CHECK ("flaggable_type" = 'Comment'),
     PRIMARY KEY ("id")
    ) INHERITS ("flags_base");
    
    CREATE TABLE "profile_flags" (
     CHECK ("flaggable_type" = 'Profile'),
     PRIMARY KEY ("id")
    ) INHERITS ("flags_base");
    
    CREATE OR REPLACE VIEW flags AS SELECT * FROM flags_base;
    
    CREATE OR REPLACE FUNCTION flag_insert_trigger_fun() RETURNS TRIGGER AS $BODY$
    BEGIN
      IF (NEW."flaggable_type" = 'Comment') THEN
        INSERT INTO comment_flags VALUES (NEW.*);
      ELSIF (NEW."flaggable_type" = 'Profile') THEN
        INSERT INTO profile_flags VALUES (NEW.*);
      ELSE
        RAISE EXCEPTION 'Wrong "flaggable_type"="%", fix flag_insert_trigger_fun() function', NEW."flaggable_type";
      END IF;
      RETURN NEW;
    END; $BODY$ LANGUAGE plpgsql;
    
    CREATE TRIGGER flag_insert_trigger
      INSTEAD OF INSERT ON flags
      FOR EACH ROW EXECUTE PROCEDURE flag_insert_trigger_fun();
    

    But this way you must supply the id field on each insertion (even if flags_base's primary key has a default value / is a serial), so you must prepare your insert trigger to fix NEW.id if it is a NULL.

    UPDATE: It seems views' columns can have a default values too, set with

    ALTER VIEW [ IF EXISTS ] name ALTER [ COLUMN ] column_name SET DEFAULT expression
    

    which is only used in views have an insert/update rule/trigger.

    http://www.postgresql.org/docs/9.3/static/sql-alterview.html

    0 讨论(0)
提交回复
热议问题