poelzi@79: BEGIN; poelzi@79: poelzi@79: CREATE OR REPLACE VIEW "liquid_feedback_version" AS jbe@86: SELECT * FROM (VALUES ('1.3.0', 1, 3, 0)) poelzi@79: AS "subquery"("string", "major", "minor", "revision"); poelzi@79: jbe@86: jbe@86: -- update comment for column "fully_frozen" of table "issue" jbe@86: jbe@86: COMMENT ON COLUMN "issue"."fully_frozen" IS 'Point in time, when "verification_time" has elapsed and voting has started; Frontends must ensure that for fully_frozen issues additionally to the restrictions for half_frozen issues a) initiatives are not created, b) no interest is created or removed, c) no supporters are added or removed, d) no opinions are created, changed or deleted.'; jbe@86: jbe@86: jbe@86: -- update comment for column "autoreject" of table "membership" jbe@86: jbe@86: COMMENT ON COLUMN "membership"."autoreject" IS 'TRUE = member votes against all initiatives, if he is neither direct_ or delegating_voter; Entries in the "interest" table can override this setting.'; jbe@86: jbe@86: jbe@86: -- allow column "autoreject" of table "interest" to be NULL jbe@86: -- (thus defaulting to "membership") jbe@86: jbe@86: ALTER TABLE "interest" ALTER COLUMN "autoreject" DROP NOT NULL; jbe@86: jbe@86: jbe@86: -- new table "ignored_issue" to allow members to ignore particular issues in certain states jbe@86: jbe@86: CREATE TABLE "ignored_issue" ( jbe@86: PRIMARY KEY ("issue_id", "member_id"), jbe@86: "issue_id" INT4 REFERENCES "issue" ("id") ON DELETE CASCADE ON UPDATE CASCADE, jbe@86: "member_id" INT4 REFERENCES "member" ("id") ON DELETE CASCADE ON UPDATE CASCADE, jbe@86: "new" BOOLEAN NOT NULL DEFAULT FALSE, jbe@86: "accepted" BOOLEAN NOT NULL DEFAULT FALSE, jbe@86: "half_frozen" BOOLEAN NOT NULL DEFAULT FALSE, jbe@86: "fully_frozen" BOOLEAN NOT NULL DEFAULT FALSE ); jbe@86: CREATE INDEX "ignored_issue_member_id_idx" ON "ignored_issue" ("member_id"); jbe@86: jbe@86: COMMENT ON TABLE "ignored_issue" IS 'Table to store member specific options to ignore issues in selected states'; jbe@86: jbe@93: COMMENT ON COLUMN "ignored_issue"."new" IS 'Apply when issue is neither closed nor accepted'; jbe@93: COMMENT ON COLUMN "ignored_issue"."accepted" IS 'Apply when issue is accepted but not (half_)frozen or closed'; jbe@93: COMMENT ON COLUMN "ignored_issue"."half_frozen" IS 'Apply when issue is half_frozen but not fully_frozen or closed'; jbe@93: COMMENT ON COLUMN "ignored_issue"."fully_frozen" IS 'Apply when issue is fully_frozen (in voting) and not closed'; jbe@86: jbe@86: jbe@86: -- allow area and issue delegations with trustee_id set to NULL jbe@86: -- (indicating that global or area delegation is void for that area or issue) jbe@86: jbe@86: ALTER TABLE "delegation" ALTER COLUMN "trustee_id" DROP NOT NULL; jbe@86: jbe@86: ALTER TABLE "delegation" ADD CONSTRAINT "no_global_delegation_to_null" jbe@86: CHECK ("trustee_id" NOTNULL OR "scope" != 'global'); jbe@86: jbe@86: jbe@86: -- disable and delete "copy_autoreject" trigger on table "interest" jbe@86: jbe@86: DROP TRIGGER "copy_autoreject" ON "interest"; jbe@86: DROP FUNCTION "copy_autoreject_trigger"(); jbe@86: jbe@86: jbe@86: -- update comments on delegation views jbe@86: jbe@86: COMMENT ON VIEW "active_delegation" IS 'Helper view for views "global_delegation", "area_delegation" and "issue_delegation": Contains delegations where the truster_id refers to an active member and includes those delegations where trustee_id is NULL'; jbe@86: jbe@86: COMMENT ON VIEW "area_delegation" IS 'Resulting area delegations from active members; can include rows with trustee_id set to NULL'; jbe@86: jbe@86: COMMENT ON VIEW "issue_delegation" IS 'Resulting issue delegations from active members; can include rows with trustee_id set to NULL'; jbe@86: jbe@86: jbe@93: -- support for explicitly disabled delegations in "delegation_chain" functions jbe@86: jbe@86: DROP FUNCTION "delegation_chain" jbe@86: ( "member"."id"%TYPE, jbe@86: "area"."id"%TYPE, jbe@86: "issue"."id"%TYPE ); jbe@86: jbe@86: DROP FUNCTION "delegation_chain" jbe@86: ( "member"."id"%TYPE, jbe@86: "area"."id"%TYPE, jbe@86: "issue"."id"%TYPE, jbe@86: "member"."id"%TYPE ); jbe@86: jbe@86: DROP TYPE "delegation_chain_row"; jbe@86: jbe@86: CREATE TYPE "delegation_chain_row" AS ( jbe@86: "index" INT4, jbe@86: "member_id" INT4, jbe@86: "member_active" BOOLEAN, jbe@86: "participation" BOOLEAN, jbe@86: "overridden" BOOLEAN, jbe@86: "scope_in" "delegation_scope", jbe@86: "scope_out" "delegation_scope", jbe@86: "disabled_out" BOOLEAN, jbe@86: "loop" "delegation_chain_loop_tag" ); jbe@86: jbe@86: COMMENT ON TYPE "delegation_chain_row" IS 'Type of rows returned by "delegation_chain"(...) functions'; jbe@86: jbe@86: COMMENT ON COLUMN "delegation_chain_row"."index" IS 'Index starting with 0 and counting up'; jbe@86: COMMENT ON COLUMN "delegation_chain_row"."participation" IS 'In case of delegation chains for issues: interest, for areas: membership, for global delegation chains: always null'; jbe@86: COMMENT ON COLUMN "delegation_chain_row"."overridden" IS 'True, if an entry with lower index has "participation" set to true'; jbe@86: COMMENT ON COLUMN "delegation_chain_row"."scope_in" IS 'Scope of used incoming delegation'; jbe@86: COMMENT ON COLUMN "delegation_chain_row"."scope_out" IS 'Scope of used outgoing delegation'; jbe@86: COMMENT ON COLUMN "delegation_chain_row"."disabled_out" IS 'Outgoing delegation is explicitly disabled by a delegation with trustee_id set to NULL'; jbe@86: COMMENT ON COLUMN "delegation_chain_row"."loop" IS 'Not null, if member is part of a loop, see "delegation_chain_loop_tag" type'; jbe@86: jbe@86: jbe@86: CREATE FUNCTION "delegation_chain" jbe@86: ( "member_id_p" "member"."id"%TYPE, jbe@86: "area_id_p" "area"."id"%TYPE, jbe@86: "issue_id_p" "issue"."id"%TYPE, jbe@86: "simulate_trustee_id_p" "member"."id"%TYPE ) jbe@86: RETURNS SETOF "delegation_chain_row" jbe@86: LANGUAGE 'plpgsql' STABLE AS $$ jbe@86: DECLARE jbe@86: "issue_row" "issue"%ROWTYPE; jbe@86: "visited_member_ids" INT4[]; -- "member"."id"%TYPE[] jbe@86: "loop_member_id_v" "member"."id"%TYPE; jbe@86: "output_row" "delegation_chain_row"; jbe@86: "output_rows" "delegation_chain_row"[]; jbe@86: "delegation_row" "delegation"%ROWTYPE; jbe@86: "row_count" INT4; jbe@86: "i" INT4; jbe@86: "loop_v" BOOLEAN; jbe@86: BEGIN jbe@86: SELECT * INTO "issue_row" FROM "issue" WHERE "id" = "issue_id_p"; jbe@86: "visited_member_ids" := '{}'; jbe@86: "loop_member_id_v" := NULL; jbe@86: "output_rows" := '{}'; jbe@86: "output_row"."index" := 0; jbe@86: "output_row"."member_id" := "member_id_p"; jbe@86: "output_row"."member_active" := TRUE; jbe@86: "output_row"."participation" := FALSE; jbe@86: "output_row"."overridden" := FALSE; jbe@86: "output_row"."disabled_out" := FALSE; jbe@86: "output_row"."scope_out" := NULL; jbe@86: LOOP jbe@86: IF "visited_member_ids" @> ARRAY["output_row"."member_id"] THEN jbe@86: "loop_member_id_v" := "output_row"."member_id"; jbe@86: ELSE jbe@86: "visited_member_ids" := jbe@86: "visited_member_ids" || "output_row"."member_id"; jbe@86: END IF; jbe@86: IF "output_row"."participation" THEN jbe@86: "output_row"."overridden" := TRUE; jbe@86: END IF; jbe@86: "output_row"."scope_in" := "output_row"."scope_out"; jbe@86: IF EXISTS ( jbe@86: SELECT NULL FROM "member" jbe@86: WHERE "id" = "output_row"."member_id" AND "active" jbe@86: ) THEN jbe@86: IF "area_id_p" ISNULL AND "issue_id_p" ISNULL THEN jbe@86: SELECT * INTO "delegation_row" FROM "delegation" jbe@86: WHERE "truster_id" = "output_row"."member_id" jbe@86: AND "scope" = 'global'; jbe@86: ELSIF "area_id_p" NOTNULL AND "issue_id_p" ISNULL THEN jbe@86: "output_row"."participation" := EXISTS ( jbe@86: SELECT NULL FROM "membership" jbe@86: WHERE "area_id" = "area_id_p" jbe@86: AND "member_id" = "output_row"."member_id" jbe@86: ); jbe@86: SELECT * INTO "delegation_row" FROM "delegation" jbe@86: WHERE "truster_id" = "output_row"."member_id" jbe@86: AND ("scope" = 'global' OR "area_id" = "area_id_p") jbe@86: ORDER BY "scope" DESC; jbe@86: ELSIF "area_id_p" ISNULL AND "issue_id_p" NOTNULL THEN jbe@86: "output_row"."participation" := EXISTS ( jbe@86: SELECT NULL FROM "interest" jbe@86: WHERE "issue_id" = "issue_id_p" jbe@86: AND "member_id" = "output_row"."member_id" jbe@86: ); jbe@86: SELECT * INTO "delegation_row" FROM "delegation" jbe@86: WHERE "truster_id" = "output_row"."member_id" jbe@86: AND ("scope" = 'global' OR jbe@86: "area_id" = "issue_row"."area_id" OR jbe@86: "issue_id" = "issue_id_p" jbe@86: ) jbe@86: ORDER BY "scope" DESC; jbe@86: ELSE jbe@86: RAISE EXCEPTION 'Either area_id or issue_id or both must be NULL.'; jbe@86: END IF; jbe@86: ELSE jbe@86: "output_row"."member_active" := FALSE; jbe@86: "output_row"."participation" := FALSE; jbe@86: "output_row"."scope_out" := NULL; jbe@86: "delegation_row" := ROW(NULL); jbe@86: END IF; jbe@86: IF jbe@86: "output_row"."member_id" = "member_id_p" AND jbe@86: "simulate_trustee_id_p" NOTNULL jbe@86: THEN jbe@86: "output_row"."scope_out" := CASE jbe@86: WHEN "area_id_p" ISNULL AND "issue_id_p" ISNULL THEN 'global' jbe@86: WHEN "area_id_p" NOTNULL AND "issue_id_p" ISNULL THEN 'area' jbe@86: WHEN "area_id_p" ISNULL AND "issue_id_p" NOTNULL THEN 'issue' jbe@86: END; jbe@86: "output_rows" := "output_rows" || "output_row"; jbe@86: "output_row"."member_id" := "simulate_trustee_id_p"; jbe@86: ELSIF "delegation_row"."trustee_id" NOTNULL THEN jbe@86: "output_row"."scope_out" := "delegation_row"."scope"; jbe@86: "output_rows" := "output_rows" || "output_row"; jbe@86: "output_row"."member_id" := "delegation_row"."trustee_id"; jbe@86: ELSIF "delegation_row"."scope" NOTNULL THEN jbe@86: "output_row"."scope_out" := "delegation_row"."scope"; jbe@86: "output_row"."disabled_out" := TRUE; jbe@86: "output_rows" := "output_rows" || "output_row"; jbe@86: EXIT; jbe@86: ELSE jbe@86: "output_row"."scope_out" := NULL; jbe@86: "output_rows" := "output_rows" || "output_row"; jbe@86: EXIT; jbe@86: END IF; jbe@86: EXIT WHEN "loop_member_id_v" NOTNULL; jbe@86: "output_row"."index" := "output_row"."index" + 1; jbe@86: END LOOP; jbe@86: "row_count" := array_upper("output_rows", 1); jbe@86: "i" := 1; jbe@86: "loop_v" := FALSE; jbe@86: LOOP jbe@86: "output_row" := "output_rows"["i"]; jbe@86: EXIT WHEN "output_row" ISNULL; jbe@86: IF "loop_v" THEN jbe@86: IF "i" + 1 = "row_count" THEN jbe@86: "output_row"."loop" := 'last'; jbe@86: ELSIF "i" = "row_count" THEN jbe@86: "output_row"."loop" := 'repetition'; jbe@86: ELSE jbe@86: "output_row"."loop" := 'intermediate'; jbe@86: END IF; jbe@86: ELSIF "output_row"."member_id" = "loop_member_id_v" THEN jbe@86: "output_row"."loop" := 'first'; jbe@86: "loop_v" := TRUE; jbe@86: END IF; jbe@86: IF "area_id_p" ISNULL AND "issue_id_p" ISNULL THEN jbe@86: "output_row"."participation" := NULL; jbe@86: END IF; jbe@86: RETURN NEXT "output_row"; jbe@86: "i" := "i" + 1; jbe@86: END LOOP; jbe@86: RETURN; jbe@86: END; jbe@86: $$; jbe@86: jbe@86: COMMENT ON FUNCTION "delegation_chain" jbe@86: ( "member"."id"%TYPE, jbe@86: "area"."id"%TYPE, jbe@86: "issue"."id"%TYPE, jbe@86: "member"."id"%TYPE ) jbe@86: IS 'Helper function for frontends to display delegation chains; Not part of internal voting logic'; jbe@86: jbe@86: CREATE FUNCTION "delegation_chain" jbe@86: ( "member_id_p" "member"."id"%TYPE, jbe@86: "area_id_p" "area"."id"%TYPE, jbe@86: "issue_id_p" "issue"."id"%TYPE ) jbe@86: RETURNS SETOF "delegation_chain_row" jbe@86: LANGUAGE 'plpgsql' STABLE AS $$ jbe@86: DECLARE jbe@86: "result_row" "delegation_chain_row"; jbe@86: BEGIN jbe@86: FOR "result_row" IN jbe@86: SELECT * FROM "delegation_chain"( jbe@86: "member_id_p", "area_id_p", "issue_id_p", NULL jbe@86: ) jbe@86: LOOP jbe@86: RETURN NEXT "result_row"; jbe@86: END LOOP; jbe@86: RETURN; jbe@86: END; jbe@86: $$; jbe@86: jbe@86: COMMENT ON FUNCTION "delegation_chain" jbe@86: ( "member"."id"%TYPE, jbe@86: "area"."id"%TYPE, jbe@86: "issue"."id"%TYPE ) jbe@86: IS 'Shortcut for "delegation_chain"(...) function where 4th parameter is null'; jbe@86: jbe@86: jbe@86: -- delete entries of "ignored_issue" table in "delete_member"(...) and "delete_private_data"() functions jbe@86: jbe@94: CREATE OR REPLACE FUNCTION "clean_issue"("issue_id_p" "issue"."id"%TYPE) jbe@94: RETURNS VOID jbe@94: LANGUAGE 'plpgsql' VOLATILE AS $$ jbe@94: DECLARE jbe@94: "issue_row" "issue"%ROWTYPE; jbe@94: BEGIN jbe@94: SELECT * INTO "issue_row" jbe@94: FROM "issue" WHERE "id" = "issue_id_p" jbe@94: FOR UPDATE; jbe@94: IF "issue_row"."cleaned" ISNULL THEN jbe@94: UPDATE "issue" SET jbe@94: "closed" = NULL, jbe@94: "ranks_available" = FALSE jbe@94: WHERE "id" = "issue_id_p"; jbe@94: DELETE FROM "delegating_voter" jbe@94: WHERE "issue_id" = "issue_id_p"; jbe@94: DELETE FROM "direct_voter" jbe@94: WHERE "issue_id" = "issue_id_p"; jbe@94: DELETE FROM "delegating_interest_snapshot" jbe@94: WHERE "issue_id" = "issue_id_p"; jbe@94: DELETE FROM "direct_interest_snapshot" jbe@94: WHERE "issue_id" = "issue_id_p"; jbe@94: DELETE FROM "delegating_population_snapshot" jbe@94: WHERE "issue_id" = "issue_id_p"; jbe@94: DELETE FROM "direct_population_snapshot" jbe@94: WHERE "issue_id" = "issue_id_p"; jbe@94: DELETE FROM "ignored_issue" jbe@94: WHERE "issue_id" = "issue_id_p"; jbe@94: DELETE FROM "delegation" jbe@94: WHERE "issue_id" = "issue_id_p"; jbe@94: DELETE FROM "supporter" jbe@94: WHERE "issue_id" = "issue_id_p"; jbe@94: UPDATE "issue" SET jbe@94: "closed" = "issue_row"."closed", jbe@94: "ranks_available" = "issue_row"."ranks_available", jbe@94: "cleaned" = now() jbe@94: WHERE "id" = "issue_id_p"; jbe@94: END IF; jbe@94: RETURN; jbe@94: END; jbe@94: $$; jbe@94: jbe@86: CREATE OR REPLACE FUNCTION "delete_member"("member_id_p" "member"."id"%TYPE) jbe@86: RETURNS VOID jbe@86: LANGUAGE 'plpgsql' VOLATILE AS $$ jbe@86: BEGIN jbe@86: UPDATE "member" SET jbe@86: "last_login" = NULL, jbe@86: "login" = NULL, jbe@86: "password" = NULL, jbe@86: "active" = FALSE, jbe@86: "notify_email" = NULL, jbe@86: "notify_email_unconfirmed" = NULL, jbe@86: "notify_email_secret" = NULL, jbe@86: "notify_email_secret_expiry" = NULL, jbe@86: "notify_email_lock_expiry" = NULL, jbe@86: "password_reset_secret" = NULL, jbe@86: "password_reset_secret_expiry" = NULL, jbe@86: "organizational_unit" = NULL, jbe@86: "internal_posts" = NULL, jbe@86: "realname" = NULL, jbe@86: "birthday" = NULL, jbe@86: "address" = NULL, jbe@86: "email" = NULL, jbe@86: "xmpp_address" = NULL, jbe@86: "website" = NULL, jbe@86: "phone" = NULL, jbe@86: "mobile_phone" = NULL, jbe@86: "profession" = NULL, jbe@86: "external_memberships" = NULL, jbe@86: "external_posts" = NULL, jbe@86: "statement" = NULL jbe@86: WHERE "id" = "member_id_p"; jbe@86: -- "text_search_data" is updated by triggers jbe@86: DELETE FROM "setting" WHERE "member_id" = "member_id_p"; jbe@86: DELETE FROM "setting_map" WHERE "member_id" = "member_id_p"; jbe@86: DELETE FROM "member_relation_setting" WHERE "member_id" = "member_id_p"; jbe@86: DELETE FROM "member_image" WHERE "member_id" = "member_id_p"; jbe@86: DELETE FROM "contact" WHERE "member_id" = "member_id_p"; jbe@86: DELETE FROM "area_setting" WHERE "member_id" = "member_id_p"; jbe@86: DELETE FROM "issue_setting" WHERE "member_id" = "member_id_p"; jbe@86: DELETE FROM "initiative_setting" WHERE "member_id" = "member_id_p"; jbe@86: DELETE FROM "suggestion_setting" WHERE "member_id" = "member_id_p"; jbe@86: DELETE FROM "membership" WHERE "member_id" = "member_id_p"; jbe@94: DELETE FROM "ignored_issue" WHERE "member_id" = "member_id_p"; jbe@86: DELETE FROM "delegation" WHERE "truster_id" = "member_id_p"; jbe@86: DELETE FROM "direct_voter" USING "issue" jbe@86: WHERE "direct_voter"."issue_id" = "issue"."id" jbe@86: AND "issue"."closed" ISNULL jbe@86: AND "member_id" = "member_id_p"; jbe@86: RETURN; jbe@86: END; jbe@86: $$; jbe@86: jbe@86: CREATE OR REPLACE FUNCTION "delete_private_data"() jbe@86: RETURNS VOID jbe@86: LANGUAGE 'plpgsql' VOLATILE AS $$ jbe@86: BEGIN jbe@86: UPDATE "member" SET jbe@86: "last_login" = NULL, jbe@86: "login" = NULL, jbe@86: "password" = NULL, jbe@86: "notify_email" = NULL, jbe@86: "notify_email_unconfirmed" = NULL, jbe@86: "notify_email_secret" = NULL, jbe@86: "notify_email_secret_expiry" = NULL, jbe@86: "notify_email_lock_expiry" = NULL, jbe@86: "password_reset_secret" = NULL, jbe@86: "password_reset_secret_expiry" = NULL, jbe@86: "organizational_unit" = NULL, jbe@86: "internal_posts" = NULL, jbe@86: "realname" = NULL, jbe@86: "birthday" = NULL, jbe@86: "address" = NULL, jbe@86: "email" = NULL, jbe@86: "xmpp_address" = NULL, jbe@86: "website" = NULL, jbe@86: "phone" = NULL, jbe@86: "mobile_phone" = NULL, jbe@86: "profession" = NULL, jbe@86: "external_memberships" = NULL, jbe@86: "external_posts" = NULL, jbe@86: "statement" = NULL; jbe@86: -- "text_search_data" is updated by triggers jbe@86: DELETE FROM "invite_code"; jbe@86: DELETE FROM "setting"; jbe@86: DELETE FROM "setting_map"; jbe@86: DELETE FROM "member_relation_setting"; jbe@86: DELETE FROM "member_image"; jbe@86: DELETE FROM "contact"; jbe@86: DELETE FROM "session"; jbe@86: DELETE FROM "area_setting"; jbe@86: DELETE FROM "issue_setting"; jbe@86: DELETE FROM "initiative_setting"; jbe@86: DELETE FROM "suggestion_setting"; jbe@94: DELETE FROM "ignored_issue"; jbe@86: DELETE FROM "direct_voter" USING "issue" jbe@86: WHERE "direct_voter"."issue_id" = "issue"."id" jbe@86: AND "issue"."closed" ISNULL; jbe@86: RETURN; jbe@86: END; jbe@86: $$; jbe@86: jbe@86: poelzi@79: COMMIT;