jbe@453: BEGIN; jbe@453: jbe@453: CREATE OR REPLACE VIEW "liquid_feedback_version" AS jbe@453: SELECT * FROM (VALUES ('3.1.0', 3, 1, 0)) jbe@453: AS "subquery"("string", "major", "minor", "revision"); jbe@453: jbe@453: ALTER TABLE "member" DROP CONSTRAINT "authority_requires_uid_and_vice_versa"; jbe@453: ALTER TABLE "member" ADD CONSTRAINT "authority_requires_uid_and_vice_versa" CHECK ( jbe@453: ("authority" NOTNULL) = ("authority_uid" NOTNULL) ); jbe@453: jbe@453: DROP TABLE "member_application"; jbe@453: DROP TYPE "application_access_level"; jbe@453: jbe@456: ALTER TABLE "policy" ADD COLUMN "min_admission_time" INTERVAL; jbe@453: ALTER TABLE "policy" RENAME COLUMN "admission_time" TO "max_admission_time"; jbe@453: jbe@456: ALTER TABLE "policy" DROP CONSTRAINT "timing"; -- re-added after UPDATE jbe@453: jbe@453: ALTER TABLE "policy" DROP CONSTRAINT "issue_quorum_if_and_only_if_not_polling"; jbe@453: ALTER TABLE "policy" ADD CONSTRAINT "issue_quorum_if_and_only_if_not_polling" CHECK ( jbe@453: "polling" = ("issue_quorum_num" ISNULL) AND jbe@453: "polling" = ("issue_quorum_den" ISNULL) ); jbe@453: jbe@453: COMMENT ON COLUMN "policy"."polling" IS 'TRUE = special policy for non-user-generated issues without issue quorum, where certain initiatives (those having the "polling" flag set) do not need to pass the initiative quorum; "min_admission_time" and "max_admission_time" MUST be set to NULL, the other timings may be set to NULL altogether, allowing individual timing for those issues'; jbe@453: COMMENT ON COLUMN "policy"."min_admission_time" IS 'Minimum duration of issue state ''admission''; Minimum time an issue stays open'; jbe@453: jbe@456: ALTER TABLE "issue" ADD COLUMN "min_admission_time" INTERVAL; jbe@453: ALTER TABLE "issue" RENAME COLUMN "admission_time" TO "max_admission_time"; jbe@453: jbe@456: ALTER TABLE "issue" DROP CONSTRAINT "admission_time_not_null_unless_instantly_accepted"; -- re-added after UPDATE jbe@453: jbe@453: ALTER TABLE "issue" DROP CONSTRAINT "set_both_or_none_of_snapshot_and_latest_snapshot_event"; jbe@453: ALTER TABLE "issue" ADD CONSTRAINT "set_both_or_none_of_snapshot_and_latest_snapshot_event" CHECK ( jbe@453: (("snapshot" NOTNULL) = ("latest_snapshot_event" NOTNULL)) ); jbe@453: jbe@453: COMMENT ON COLUMN "issue"."closed" IS 'Point in time, when "max_admission_time" or "voting_time" have elapsed, and issue is no longer active; Frontends must ensure that for closed issues additionally to the restrictions for half_frozen and fully_frozen issues a) no voter is added or removed to/from the direct_voter table, b) no votes are added, modified or removed.'; jbe@453: COMMENT ON COLUMN "issue"."min_admission_time" IS 'Copied from "policy" table at creation of issue'; jbe@453: jbe@453: DROP TRIGGER "update_text_search_data" ON "initiative"; jbe@453: ALTER TABLE "initiative" DROP COLUMN "discussion_url"; jbe@453: jbe@453: ALTER TABLE "initiative" DROP CONSTRAINT "all_or_none_of_revoked_and_revoked_by_member_id_must_be_null"; jbe@453: ALTER TABLE "initiative" ADD CONSTRAINT "all_or_none_of_revoked_and_revoked_by_member_id_must_be_null" CHECK ( jbe@453: ("revoked" NOTNULL) = ("revoked_by_member_id" NOTNULL) ); jbe@453: jbe@453: CREATE TRIGGER "update_text_search_data" jbe@453: BEFORE INSERT OR UPDATE ON "initiative" jbe@453: FOR EACH ROW EXECUTE PROCEDURE jbe@453: tsvector_update_trigger('text_search_data', 'pg_catalog.simple', "name"); jbe@453: jbe@453: ALTER TABLE "event" DROP CONSTRAINT "null_constraints_for_issue_state_changed"; jbe@453: ALTER TABLE "event" DROP CONSTRAINT "null_constraints_for_initiative_creation_or_revocation_or_new_draft"; jbe@453: ALTER TABLE "event" DROP CONSTRAINT "null_constraints_for_suggestion_creation"; jbe@453: jbe@453: ALTER TABLE "event" ADD CONSTRAINT "null_constr_for_issue_state_changed" CHECK ( jbe@453: "event" != 'issue_state_changed' OR ( jbe@453: "member_id" ISNULL AND jbe@453: "issue_id" NOTNULL AND jbe@453: "state" NOTNULL AND jbe@453: "initiative_id" ISNULL AND jbe@453: "draft_id" ISNULL AND jbe@453: "suggestion_id" ISNULL ) ); jbe@453: ALTER TABLE "event" ADD CONSTRAINT "null_constr_for_initiative_creation_or_revocation_or_new_draft" CHECK ( jbe@453: "event" NOT IN ( jbe@453: 'initiative_created_in_new_issue', jbe@453: 'initiative_created_in_existing_issue', jbe@453: 'initiative_revoked', jbe@453: 'new_draft_created' jbe@453: ) OR ( jbe@453: "member_id" NOTNULL AND jbe@453: "issue_id" NOTNULL AND jbe@453: "state" NOTNULL AND jbe@453: "initiative_id" NOTNULL AND jbe@453: "draft_id" NOTNULL AND jbe@453: "suggestion_id" ISNULL ) ); jbe@453: ALTER TABLE "event" ADD CONSTRAINT "null_constr_for_suggestion_creation" CHECK ( jbe@453: "event" != 'suggestion_created' OR ( jbe@453: "member_id" NOTNULL AND jbe@453: "issue_id" NOTNULL AND jbe@453: "state" NOTNULL AND jbe@453: "initiative_id" NOTNULL AND jbe@453: "draft_id" ISNULL AND jbe@453: "suggestion_id" NOTNULL ) ); jbe@453: jbe@453: CREATE OR REPLACE FUNCTION "copy_timings_trigger"() jbe@453: RETURNS TRIGGER jbe@453: LANGUAGE 'plpgsql' VOLATILE AS $$ jbe@453: DECLARE jbe@453: "policy_row" "policy"%ROWTYPE; jbe@453: BEGIN jbe@453: SELECT * INTO "policy_row" FROM "policy" jbe@453: WHERE "id" = NEW."policy_id"; jbe@453: IF NEW."min_admission_time" ISNULL THEN jbe@453: NEW."min_admission_time" := "policy_row"."min_admission_time"; jbe@453: END IF; jbe@453: IF NEW."max_admission_time" ISNULL THEN jbe@453: NEW."max_admission_time" := "policy_row"."max_admission_time"; jbe@453: END IF; jbe@453: IF NEW."discussion_time" ISNULL THEN jbe@453: NEW."discussion_time" := "policy_row"."discussion_time"; jbe@453: END IF; jbe@453: IF NEW."verification_time" ISNULL THEN jbe@453: NEW."verification_time" := "policy_row"."verification_time"; jbe@453: END IF; jbe@453: IF NEW."voting_time" ISNULL THEN jbe@453: NEW."voting_time" := "policy_row"."voting_time"; jbe@453: END IF; jbe@453: RETURN NEW; jbe@453: END; jbe@453: $$; jbe@453: jbe@453: CREATE OR REPLACE FUNCTION "check_issue" jbe@453: ( "issue_id_p" "issue"."id"%TYPE, jbe@453: "persist" "check_issue_persistence" ) jbe@453: RETURNS "check_issue_persistence" jbe@453: LANGUAGE 'plpgsql' VOLATILE AS $$ jbe@453: DECLARE jbe@453: "issue_row" "issue"%ROWTYPE; jbe@453: "policy_row" "policy"%ROWTYPE; jbe@453: "initiative_row" "initiative"%ROWTYPE; jbe@453: "state_v" "issue_state"; jbe@453: BEGIN jbe@453: PERFORM "require_transaction_isolation"(); jbe@453: IF "persist" ISNULL THEN jbe@453: SELECT * INTO "issue_row" FROM "issue" WHERE "id" = "issue_id_p" jbe@453: FOR UPDATE; jbe@453: IF "issue_row"."closed" NOTNULL THEN jbe@453: RETURN NULL; jbe@453: END IF; jbe@453: "persist"."state" := "issue_row"."state"; jbe@453: IF jbe@453: ( "issue_row"."state" = 'admission' AND now() >= jbe@453: "issue_row"."created" + "issue_row"."max_admission_time" ) OR jbe@453: ( "issue_row"."state" = 'discussion' AND now() >= jbe@453: "issue_row"."accepted" + "issue_row"."discussion_time" ) OR jbe@453: ( "issue_row"."state" = 'verification' AND now() >= jbe@453: "issue_row"."half_frozen" + "issue_row"."verification_time" ) OR jbe@453: ( "issue_row"."state" = 'voting' AND now() >= jbe@453: "issue_row"."fully_frozen" + "issue_row"."voting_time" ) jbe@453: THEN jbe@453: "persist"."phase_finished" := TRUE; jbe@453: ELSE jbe@453: "persist"."phase_finished" := FALSE; jbe@453: END IF; jbe@453: IF jbe@453: NOT EXISTS ( jbe@453: -- all initiatives are revoked jbe@453: SELECT NULL FROM "initiative" jbe@453: WHERE "issue_id" = "issue_id_p" AND "revoked" ISNULL jbe@453: ) AND ( jbe@453: -- and issue has not been accepted yet jbe@453: "persist"."state" = 'admission' OR jbe@453: -- or verification time has elapsed jbe@453: ( "persist"."state" = 'verification' AND jbe@453: "persist"."phase_finished" ) OR jbe@453: -- or no initiatives have been revoked lately jbe@453: NOT EXISTS ( jbe@453: SELECT NULL FROM "initiative" jbe@453: WHERE "issue_id" = "issue_id_p" jbe@453: AND now() < "revoked" + "issue_row"."verification_time" jbe@453: ) jbe@453: ) jbe@453: THEN jbe@453: "persist"."issue_revoked" := TRUE; jbe@453: ELSE jbe@453: "persist"."issue_revoked" := FALSE; jbe@453: END IF; jbe@453: IF "persist"."phase_finished" OR "persist"."issue_revoked" THEN jbe@453: UPDATE "issue" SET "phase_finished" = now() jbe@453: WHERE "id" = "issue_row"."id"; jbe@453: RETURN "persist"; jbe@453: ELSIF jbe@453: "persist"."state" IN ('admission', 'discussion', 'verification') jbe@453: THEN jbe@453: RETURN "persist"; jbe@453: ELSE jbe@453: RETURN NULL; jbe@453: END IF; jbe@453: END IF; jbe@453: IF jbe@453: "persist"."state" IN ('admission', 'discussion', 'verification') AND jbe@453: coalesce("persist"."snapshot_created", FALSE) = FALSE jbe@453: THEN jbe@453: PERFORM "create_snapshot"("issue_id_p"); jbe@453: "persist"."snapshot_created" = TRUE; jbe@453: IF "persist"."phase_finished" THEN jbe@453: IF "persist"."state" = 'admission' THEN jbe@453: PERFORM "set_snapshot_event"("issue_id_p", 'end_of_admission'); jbe@453: ELSIF "persist"."state" = 'discussion' THEN jbe@453: PERFORM "set_snapshot_event"("issue_id_p", 'half_freeze'); jbe@453: ELSIF "persist"."state" = 'verification' THEN jbe@453: PERFORM "set_snapshot_event"("issue_id_p", 'full_freeze'); jbe@453: SELECT * INTO "issue_row" FROM "issue" WHERE "id" = "issue_id_p"; jbe@453: SELECT * INTO "policy_row" FROM "policy" jbe@453: WHERE "id" = "issue_row"."policy_id"; jbe@453: FOR "initiative_row" IN jbe@453: SELECT * FROM "initiative" jbe@453: WHERE "issue_id" = "issue_id_p" AND "revoked" ISNULL jbe@453: FOR UPDATE jbe@453: LOOP jbe@453: IF jbe@453: "initiative_row"."polling" OR ( jbe@453: "initiative_row"."satisfied_supporter_count" > 0 AND jbe@453: "initiative_row"."satisfied_supporter_count" * jbe@453: "policy_row"."initiative_quorum_den" >= jbe@453: "issue_row"."population" * "policy_row"."initiative_quorum_num" jbe@453: ) jbe@453: THEN jbe@453: UPDATE "initiative" SET "admitted" = TRUE jbe@453: WHERE "id" = "initiative_row"."id"; jbe@453: ELSE jbe@453: UPDATE "initiative" SET "admitted" = FALSE jbe@453: WHERE "id" = "initiative_row"."id"; jbe@453: END IF; jbe@453: END LOOP; jbe@453: END IF; jbe@453: END IF; jbe@453: RETURN "persist"; jbe@453: END IF; jbe@453: IF jbe@453: "persist"."state" IN ('admission', 'discussion', 'verification') AND jbe@453: coalesce("persist"."harmonic_weights_set", FALSE) = FALSE jbe@453: THEN jbe@453: PERFORM "set_harmonic_initiative_weights"("issue_id_p"); jbe@453: "persist"."harmonic_weights_set" = TRUE; jbe@453: IF jbe@453: "persist"."phase_finished" OR jbe@453: "persist"."issue_revoked" OR jbe@453: "persist"."state" = 'admission' jbe@453: THEN jbe@453: RETURN "persist"; jbe@453: ELSE jbe@453: RETURN NULL; jbe@453: END IF; jbe@453: END IF; jbe@453: IF "persist"."issue_revoked" THEN jbe@453: IF "persist"."state" = 'admission' THEN jbe@453: "state_v" := 'canceled_revoked_before_accepted'; jbe@453: ELSIF "persist"."state" = 'discussion' THEN jbe@453: "state_v" := 'canceled_after_revocation_during_discussion'; jbe@453: ELSIF "persist"."state" = 'verification' THEN jbe@453: "state_v" := 'canceled_after_revocation_during_verification'; jbe@453: END IF; jbe@453: UPDATE "issue" SET jbe@453: "state" = "state_v", jbe@453: "closed" = "phase_finished", jbe@453: "phase_finished" = NULL jbe@453: WHERE "id" = "issue_id_p"; jbe@453: RETURN NULL; jbe@453: END IF; jbe@453: IF "persist"."state" = 'admission' THEN jbe@453: SELECT * INTO "issue_row" FROM "issue" WHERE "id" = "issue_id_p" jbe@453: FOR UPDATE; jbe@453: SELECT * INTO "policy_row" jbe@453: FROM "policy" WHERE "id" = "issue_row"."policy_id"; jbe@453: IF jbe@453: ( now() >= jbe@453: "issue_row"."created" + "issue_row"."min_admission_time" ) AND jbe@453: EXISTS ( jbe@453: SELECT NULL FROM "initiative" jbe@453: WHERE "issue_id" = "issue_id_p" jbe@453: AND "supporter_count" > 0 jbe@453: AND "supporter_count" * "policy_row"."issue_quorum_den" jbe@453: >= "issue_row"."population" * "policy_row"."issue_quorum_num" jbe@453: ) jbe@453: THEN jbe@453: UPDATE "issue" SET jbe@453: "state" = 'discussion', jbe@453: "accepted" = coalesce("phase_finished", now()), jbe@453: "phase_finished" = NULL jbe@453: WHERE "id" = "issue_id_p"; jbe@453: ELSIF "issue_row"."phase_finished" NOTNULL THEN jbe@453: UPDATE "issue" SET jbe@453: "state" = 'canceled_issue_not_accepted', jbe@453: "closed" = "phase_finished", jbe@453: "phase_finished" = NULL jbe@453: WHERE "id" = "issue_id_p"; jbe@453: END IF; jbe@453: RETURN NULL; jbe@453: END IF; jbe@453: IF "persist"."phase_finished" THEN jbe@453: IF "persist"."state" = 'discussion' THEN jbe@453: UPDATE "issue" SET jbe@453: "state" = 'verification', jbe@453: "half_frozen" = "phase_finished", jbe@453: "phase_finished" = NULL jbe@453: WHERE "id" = "issue_id_p"; jbe@453: RETURN NULL; jbe@453: END IF; jbe@453: IF "persist"."state" = 'verification' THEN jbe@453: SELECT * INTO "issue_row" FROM "issue" WHERE "id" = "issue_id_p" jbe@453: FOR UPDATE; jbe@453: SELECT * INTO "policy_row" FROM "policy" jbe@453: WHERE "id" = "issue_row"."policy_id"; jbe@453: IF EXISTS ( jbe@453: SELECT NULL FROM "initiative" jbe@453: WHERE "issue_id" = "issue_id_p" AND "admitted" = TRUE jbe@453: ) THEN jbe@453: UPDATE "issue" SET jbe@453: "state" = 'voting', jbe@453: "fully_frozen" = "phase_finished", jbe@453: "phase_finished" = NULL jbe@453: WHERE "id" = "issue_id_p"; jbe@453: ELSE jbe@453: UPDATE "issue" SET jbe@453: "state" = 'canceled_no_initiative_admitted', jbe@453: "fully_frozen" = "phase_finished", jbe@453: "closed" = "phase_finished", jbe@453: "phase_finished" = NULL jbe@453: WHERE "id" = "issue_id_p"; jbe@453: -- NOTE: The following DELETE statements have effect only when jbe@453: -- issue state has been manipulated jbe@453: DELETE FROM "direct_voter" WHERE "issue_id" = "issue_id_p"; jbe@453: DELETE FROM "delegating_voter" WHERE "issue_id" = "issue_id_p"; jbe@453: DELETE FROM "battle" WHERE "issue_id" = "issue_id_p"; jbe@453: END IF; jbe@453: RETURN NULL; jbe@453: END IF; jbe@453: IF "persist"."state" = 'voting' THEN jbe@453: IF coalesce("persist"."closed_voting", FALSE) = FALSE THEN jbe@453: PERFORM "close_voting"("issue_id_p"); jbe@453: "persist"."closed_voting" = TRUE; jbe@453: RETURN "persist"; jbe@453: END IF; jbe@453: PERFORM "calculate_ranks"("issue_id_p"); jbe@453: RETURN NULL; jbe@453: END IF; jbe@453: END IF; jbe@453: RAISE WARNING 'should not happen'; jbe@453: RETURN NULL; jbe@453: END; jbe@453: $$; jbe@453: jbe@456: -- temporarily drop deferred trigger to allow UPDATE with subsequent ALTER TABLE: jbe@456: DROP TRIGGER "issue_requires_first_initiative" ON "issue"; jbe@456: jbe@456: -- set "min_admission_time" values where appropriate: jbe@456: UPDATE "policy" SET "min_admission_time" = '0' WHERE "max_admission_time" NOTNULL; jbe@456: UPDATE "issue" SET "min_admission_time" = '0' WHERE "max_admission_time" NOTNULL; jbe@456: jbe@456: -- add constraints related to "min_admission_time" after UPDATE: jbe@456: ALTER TABLE "policy" ADD CONSTRAINT "timing" CHECK ( jbe@456: ( "polling" = FALSE AND jbe@456: "min_admission_time" NOTNULL AND "max_admission_time" NOTNULL AND jbe@456: "min_admission_time" <= "max_admission_time" AND jbe@456: "discussion_time" NOTNULL AND jbe@456: "verification_time" NOTNULL AND jbe@456: "voting_time" NOTNULL ) OR jbe@456: ( "polling" = TRUE AND jbe@456: "min_admission_time" ISNULL AND "max_admission_time" ISNULL AND jbe@456: "discussion_time" NOTNULL AND jbe@456: "verification_time" NOTNULL AND jbe@456: "voting_time" NOTNULL ) OR jbe@456: ( "polling" = TRUE AND jbe@456: "min_admission_time" ISNULL AND "max_admission_time" ISNULL AND jbe@456: "discussion_time" ISNULL AND jbe@456: "verification_time" ISNULL AND jbe@456: "voting_time" ISNULL ) ); jbe@456: jbe@456: ALTER TABLE "issue" ADD CONSTRAINT "admission_time_not_null_unless_instantly_accepted" CHECK ( jbe@456: ("min_admission_time" NOTNULL) = ("max_admission_time" NOTNULL) AND jbe@456: ("min_admission_time" NOTNULL OR ("accepted" NOTNULL AND "accepted" = "created")) ); jbe@456: jbe@456: -- re-add temporarily dropped trigger: jbe@456: CREATE CONSTRAINT TRIGGER "issue_requires_first_initiative" jbe@456: AFTER INSERT OR UPDATE ON "issue" DEFERRABLE INITIALLY DEFERRED jbe@456: FOR EACH ROW EXECUTE PROCEDURE jbe@456: "issue_requires_first_initiative_trigger"(); jbe@456: COMMENT ON TRIGGER "issue_requires_first_initiative" ON "issue" IS 'Ensure that new issues have at least one initiative'; jbe@456: jbe@453: COMMIT;