liquid_feedback_core

changeset 307:847d59f94ceb

Merged fix from v2.0.12: Removed unwanted (and broken) tie-breaking by approval rate
author jbe
date Fri Oct 05 13:45:37 2012 +0200 (2012-10-05)
parents e403f47525ce d8b455e3a9be
children 5c98265b39a0
files .hgtags core.sql update/core-update.v2.0.11-v2.1.0.sql update/core-update.v2.0.12-v2.1.0.sql
line diff
     1.1 --- a/.hgtags	Sun Sep 30 12:59:07 2012 +0200
     1.2 +++ b/.hgtags	Fri Oct 05 13:45:37 2012 +0200
     1.3 @@ -58,3 +58,4 @@
     1.4  5ee7eed2f5b09e6a45fda0ede32a7af96f5f6cd1 v2.0.9
     1.5  389200fd973d84082a7850e7e72adb0736100ca3 v2.0.10
     1.6  e818f83e133bc545681b4bc06ce3c4f280de4672 v2.0.11
     1.7 +a839e7efde9f8ff43a6ea71cf4d694bec84a1de1 v2.0.12
     2.1 --- a/core.sql	Sun Sep 30 12:59:07 2012 +0200
     2.2 +++ b/core.sql	Fri Oct 05 13:45:37 2012 +0200
     2.3 @@ -2860,37 +2860,6 @@
     2.4  
     2.5  
     2.6  
     2.7 -------------------------------
     2.8 --- Comparison by vote count --
     2.9 -------------------------------
    2.10 -
    2.11 -CREATE FUNCTION "vote_ratio"
    2.12 -  ( "positive_votes_p" "initiative"."positive_votes"%TYPE,
    2.13 -    "negative_votes_p" "initiative"."negative_votes"%TYPE )
    2.14 -  RETURNS FLOAT8
    2.15 -  LANGUAGE 'plpgsql' STABLE AS $$
    2.16 -    BEGIN
    2.17 -      IF "positive_votes_p" > 0 AND "negative_votes_p" > 0 THEN
    2.18 -        RETURN
    2.19 -          "positive_votes_p"::FLOAT8 /
    2.20 -          ("positive_votes_p" + "negative_votes_p")::FLOAT8;
    2.21 -      ELSIF "positive_votes_p" > 0 THEN
    2.22 -        RETURN "positive_votes_p";
    2.23 -      ELSIF "negative_votes_p" > 0 THEN
    2.24 -        RETURN 1 - "negative_votes_p";
    2.25 -      ELSE
    2.26 -        RETURN 0.5;
    2.27 -      END IF;
    2.28 -    END;
    2.29 -  $$;
    2.30 -
    2.31 -COMMENT ON FUNCTION "vote_ratio"
    2.32 -  ( "initiative"."positive_votes"%TYPE,
    2.33 -    "initiative"."negative_votes"%TYPE )
    2.34 -  IS 'Returns a number, which can be used for comparison of initiatives based on count of approvals and disapprovals. Greater numbers indicate a better result. This function is NOT injective.';
    2.35 -
    2.36 -
    2.37 -
    2.38  ------------------------------------------------
    2.39  -- Locking for snapshots and voting procedure --
    2.40  ------------------------------------------------
    2.41 @@ -4130,7 +4099,6 @@
    2.42            WHERE "issue_id" = "issue_id_p" AND "eligible"
    2.43            ORDER BY
    2.44              "schulze_rank",
    2.45 -            "vote_ratio"("positive_votes", "negative_votes"),
    2.46              "id"
    2.47            LIMIT 1
    2.48          ) AS "subquery"
    2.49 @@ -4145,7 +4113,6 @@
    2.50            "winner" DESC,
    2.51            "eligible" DESC,
    2.52            "schulze_rank",
    2.53 -          "vote_ratio"("positive_votes", "negative_votes"),
    2.54            "id"
    2.55        LOOP
    2.56          UPDATE "initiative" SET "rank" = "rank_v"
     3.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     3.2 +++ b/update/core-update.v2.0.11-v2.0.12.sql	Fri Oct 05 13:45:37 2012 +0200
     3.3 @@ -0,0 +1,309 @@
     3.4 +BEGIN;
     3.5 +
     3.6 +CREATE OR REPLACE VIEW "liquid_feedback_version" AS
     3.7 +  SELECT * FROM (VALUES ('2.0.12', 2, 0, 12))
     3.8 +  AS "subquery"("string", "major", "minor", "revision");
     3.9 +
    3.10 +CREATE OR REPLACE FUNCTION "calculate_ranks"("issue_id_p" "issue"."id"%TYPE)
    3.11 +  RETURNS VOID
    3.12 +  LANGUAGE 'plpgsql' VOLATILE AS $$
    3.13 +    DECLARE
    3.14 +      "issue_row"         "issue"%ROWTYPE;
    3.15 +      "policy_row"        "policy"%ROWTYPE;
    3.16 +      "dimension_v"       INTEGER;
    3.17 +      "vote_matrix"       INT4[][];  -- absolute votes
    3.18 +      "matrix"            INT8[][];  -- defeat strength / best paths
    3.19 +      "i"                 INTEGER;
    3.20 +      "j"                 INTEGER;
    3.21 +      "k"                 INTEGER;
    3.22 +      "battle_row"        "battle"%ROWTYPE;
    3.23 +      "rank_ary"          INT4[];
    3.24 +      "rank_v"            INT4;
    3.25 +      "done_v"            INTEGER;
    3.26 +      "winners_ary"       INTEGER[];
    3.27 +      "initiative_id_v"   "initiative"."id"%TYPE;
    3.28 +    BEGIN
    3.29 +      SELECT * INTO "issue_row"
    3.30 +        FROM "issue" WHERE "id" = "issue_id_p"
    3.31 +        FOR UPDATE;
    3.32 +      SELECT * INTO "policy_row"
    3.33 +        FROM "policy" WHERE "id" = "issue_row"."policy_id";
    3.34 +      SELECT count(1) INTO "dimension_v"
    3.35 +        FROM "battle_participant" WHERE "issue_id" = "issue_id_p";
    3.36 +      -- Create "vote_matrix" with absolute number of votes in pairwise
    3.37 +      -- comparison:
    3.38 +      "vote_matrix" := array_fill(NULL::INT4, ARRAY["dimension_v", "dimension_v"]);
    3.39 +      "i" := 1;
    3.40 +      "j" := 2;
    3.41 +      FOR "battle_row" IN
    3.42 +        SELECT * FROM "battle" WHERE "issue_id" = "issue_id_p"
    3.43 +        ORDER BY
    3.44 +        "winning_initiative_id" NULLS LAST,
    3.45 +        "losing_initiative_id" NULLS LAST
    3.46 +      LOOP
    3.47 +        "vote_matrix"["i"]["j"] := "battle_row"."count";
    3.48 +        IF "j" = "dimension_v" THEN
    3.49 +          "i" := "i" + 1;
    3.50 +          "j" := 1;
    3.51 +        ELSE
    3.52 +          "j" := "j" + 1;
    3.53 +          IF "j" = "i" THEN
    3.54 +            "j" := "j" + 1;
    3.55 +          END IF;
    3.56 +        END IF;
    3.57 +      END LOOP;
    3.58 +      IF "i" != "dimension_v" OR "j" != "dimension_v" + 1 THEN
    3.59 +        RAISE EXCEPTION 'Wrong battle count (should not happen)';
    3.60 +      END IF;
    3.61 +      -- Store defeat strengths in "matrix" using "defeat_strength"
    3.62 +      -- function:
    3.63 +      "matrix" := array_fill(NULL::INT8, ARRAY["dimension_v", "dimension_v"]);
    3.64 +      "i" := 1;
    3.65 +      LOOP
    3.66 +        "j" := 1;
    3.67 +        LOOP
    3.68 +          IF "i" != "j" THEN
    3.69 +            "matrix"["i"]["j"] := "defeat_strength"(
    3.70 +              "vote_matrix"["i"]["j"],
    3.71 +              "vote_matrix"["j"]["i"]
    3.72 +            );
    3.73 +          END IF;
    3.74 +          EXIT WHEN "j" = "dimension_v";
    3.75 +          "j" := "j" + 1;
    3.76 +        END LOOP;
    3.77 +        EXIT WHEN "i" = "dimension_v";
    3.78 +        "i" := "i" + 1;
    3.79 +      END LOOP;
    3.80 +      -- Find best paths:
    3.81 +      "i" := 1;
    3.82 +      LOOP
    3.83 +        "j" := 1;
    3.84 +        LOOP
    3.85 +          IF "i" != "j" THEN
    3.86 +            "k" := 1;
    3.87 +            LOOP
    3.88 +              IF "i" != "k" AND "j" != "k" THEN
    3.89 +                IF "matrix"["j"]["i"] < "matrix"["i"]["k"] THEN
    3.90 +                  IF "matrix"["j"]["i"] > "matrix"["j"]["k"] THEN
    3.91 +                    "matrix"["j"]["k"] := "matrix"["j"]["i"];
    3.92 +                  END IF;
    3.93 +                ELSE
    3.94 +                  IF "matrix"["i"]["k"] > "matrix"["j"]["k"] THEN
    3.95 +                    "matrix"["j"]["k"] := "matrix"["i"]["k"];
    3.96 +                  END IF;
    3.97 +                END IF;
    3.98 +              END IF;
    3.99 +              EXIT WHEN "k" = "dimension_v";
   3.100 +              "k" := "k" + 1;
   3.101 +            END LOOP;
   3.102 +          END IF;
   3.103 +          EXIT WHEN "j" = "dimension_v";
   3.104 +          "j" := "j" + 1;
   3.105 +        END LOOP;
   3.106 +        EXIT WHEN "i" = "dimension_v";
   3.107 +        "i" := "i" + 1;
   3.108 +      END LOOP;
   3.109 +      -- Determine order of winners:
   3.110 +      "rank_ary" := array_fill(NULL::INT4, ARRAY["dimension_v"]);
   3.111 +      "rank_v" := 1;
   3.112 +      "done_v" := 0;
   3.113 +      LOOP
   3.114 +        "winners_ary" := '{}';
   3.115 +        "i" := 1;
   3.116 +        LOOP
   3.117 +          IF "rank_ary"["i"] ISNULL THEN
   3.118 +            "j" := 1;
   3.119 +            LOOP
   3.120 +              IF
   3.121 +                "i" != "j" AND
   3.122 +                "rank_ary"["j"] ISNULL AND
   3.123 +                "matrix"["j"]["i"] > "matrix"["i"]["j"]
   3.124 +              THEN
   3.125 +                -- someone else is better
   3.126 +                EXIT;
   3.127 +              END IF;
   3.128 +              IF "j" = "dimension_v" THEN
   3.129 +                -- noone is better
   3.130 +                "winners_ary" := "winners_ary" || "i";
   3.131 +                EXIT;
   3.132 +              END IF;
   3.133 +              "j" := "j" + 1;
   3.134 +            END LOOP;
   3.135 +          END IF;
   3.136 +          EXIT WHEN "i" = "dimension_v";
   3.137 +          "i" := "i" + 1;
   3.138 +        END LOOP;
   3.139 +        "i" := 1;
   3.140 +        LOOP
   3.141 +          "rank_ary"["winners_ary"["i"]] := "rank_v";
   3.142 +          "done_v" := "done_v" + 1;
   3.143 +          EXIT WHEN "i" = array_upper("winners_ary", 1);
   3.144 +          "i" := "i" + 1;
   3.145 +        END LOOP;
   3.146 +        EXIT WHEN "done_v" = "dimension_v";
   3.147 +        "rank_v" := "rank_v" + 1;
   3.148 +      END LOOP;
   3.149 +      -- write preliminary results:
   3.150 +      "i" := 1;
   3.151 +      FOR "initiative_id_v" IN
   3.152 +        SELECT "id" FROM "initiative"
   3.153 +        WHERE "issue_id" = "issue_id_p" AND "admitted"
   3.154 +        ORDER BY "id"
   3.155 +      LOOP
   3.156 +        UPDATE "initiative" SET
   3.157 +          "direct_majority" =
   3.158 +            CASE WHEN "policy_row"."direct_majority_strict" THEN
   3.159 +              "positive_votes" * "policy_row"."direct_majority_den" >
   3.160 +              "policy_row"."direct_majority_num" * ("positive_votes"+"negative_votes")
   3.161 +            ELSE
   3.162 +              "positive_votes" * "policy_row"."direct_majority_den" >=
   3.163 +              "policy_row"."direct_majority_num" * ("positive_votes"+"negative_votes")
   3.164 +            END
   3.165 +            AND "positive_votes" >= "policy_row"."direct_majority_positive"
   3.166 +            AND "issue_row"."voter_count"-"negative_votes" >=
   3.167 +                "policy_row"."direct_majority_non_negative",
   3.168 +            "indirect_majority" =
   3.169 +            CASE WHEN "policy_row"."indirect_majority_strict" THEN
   3.170 +              "positive_votes" * "policy_row"."indirect_majority_den" >
   3.171 +              "policy_row"."indirect_majority_num" * ("positive_votes"+"negative_votes")
   3.172 +            ELSE
   3.173 +              "positive_votes" * "policy_row"."indirect_majority_den" >=
   3.174 +              "policy_row"."indirect_majority_num" * ("positive_votes"+"negative_votes")
   3.175 +            END
   3.176 +            AND "positive_votes" >= "policy_row"."indirect_majority_positive"
   3.177 +            AND "issue_row"."voter_count"-"negative_votes" >=
   3.178 +                "policy_row"."indirect_majority_non_negative",
   3.179 +          "schulze_rank"           = "rank_ary"["i"],
   3.180 +          "better_than_status_quo" = "rank_ary"["i"] < "rank_ary"["dimension_v"],
   3.181 +          "worse_than_status_quo"  = "rank_ary"["i"] > "rank_ary"["dimension_v"],
   3.182 +          "multistage_majority"    = "rank_ary"["i"] >= "rank_ary"["dimension_v"],
   3.183 +          "reverse_beat_path"      = "matrix"["dimension_v"]["i"] >= 0,
   3.184 +          "eligible"               = FALSE,
   3.185 +          "winner"                 = FALSE,
   3.186 +          "rank"                   = NULL  -- NOTE: in cases of manual reset of issue state
   3.187 +          WHERE "id" = "initiative_id_v";
   3.188 +        "i" := "i" + 1;
   3.189 +      END LOOP;
   3.190 +      IF "i" != "dimension_v" THEN
   3.191 +        RAISE EXCEPTION 'Wrong winner count (should not happen)';
   3.192 +      END IF;
   3.193 +      -- take indirect majorities into account:
   3.194 +      LOOP
   3.195 +        UPDATE "initiative" SET "indirect_majority" = TRUE
   3.196 +          FROM (
   3.197 +            SELECT "new_initiative"."id" AS "initiative_id"
   3.198 +            FROM "initiative" "old_initiative"
   3.199 +            JOIN "initiative" "new_initiative"
   3.200 +              ON "new_initiative"."issue_id" = "issue_id_p"
   3.201 +              AND "new_initiative"."indirect_majority" = FALSE
   3.202 +            JOIN "battle" "battle_win"
   3.203 +              ON "battle_win"."issue_id" = "issue_id_p"
   3.204 +              AND "battle_win"."winning_initiative_id" = "new_initiative"."id"
   3.205 +              AND "battle_win"."losing_initiative_id" = "old_initiative"."id"
   3.206 +            JOIN "battle" "battle_lose"
   3.207 +              ON "battle_lose"."issue_id" = "issue_id_p"
   3.208 +              AND "battle_lose"."losing_initiative_id" = "new_initiative"."id"
   3.209 +              AND "battle_lose"."winning_initiative_id" = "old_initiative"."id"
   3.210 +            WHERE "old_initiative"."issue_id" = "issue_id_p"
   3.211 +            AND "old_initiative"."indirect_majority" = TRUE
   3.212 +            AND CASE WHEN "policy_row"."indirect_majority_strict" THEN
   3.213 +              "battle_win"."count" * "policy_row"."indirect_majority_den" >
   3.214 +              "policy_row"."indirect_majority_num" *
   3.215 +              ("battle_win"."count"+"battle_lose"."count")
   3.216 +            ELSE
   3.217 +              "battle_win"."count" * "policy_row"."indirect_majority_den" >=
   3.218 +              "policy_row"."indirect_majority_num" *
   3.219 +              ("battle_win"."count"+"battle_lose"."count")
   3.220 +            END
   3.221 +            AND "battle_win"."count" >= "policy_row"."indirect_majority_positive"
   3.222 +            AND "issue_row"."voter_count"-"battle_lose"."count" >=
   3.223 +                "policy_row"."indirect_majority_non_negative"
   3.224 +          ) AS "subquery"
   3.225 +          WHERE "id" = "subquery"."initiative_id";
   3.226 +        EXIT WHEN NOT FOUND;
   3.227 +      END LOOP;
   3.228 +      -- set "multistage_majority" for remaining matching initiatives:
   3.229 +      UPDATE "initiative" SET "multistage_majority" = TRUE
   3.230 +        FROM (
   3.231 +          SELECT "losing_initiative"."id" AS "initiative_id"
   3.232 +          FROM "initiative" "losing_initiative"
   3.233 +          JOIN "initiative" "winning_initiative"
   3.234 +            ON "winning_initiative"."issue_id" = "issue_id_p"
   3.235 +            AND "winning_initiative"."admitted"
   3.236 +          JOIN "battle" "battle_win"
   3.237 +            ON "battle_win"."issue_id" = "issue_id_p"
   3.238 +            AND "battle_win"."winning_initiative_id" = "winning_initiative"."id"
   3.239 +            AND "battle_win"."losing_initiative_id" = "losing_initiative"."id"
   3.240 +          JOIN "battle" "battle_lose"
   3.241 +            ON "battle_lose"."issue_id" = "issue_id_p"
   3.242 +            AND "battle_lose"."losing_initiative_id" = "winning_initiative"."id"
   3.243 +            AND "battle_lose"."winning_initiative_id" = "losing_initiative"."id"
   3.244 +          WHERE "losing_initiative"."issue_id" = "issue_id_p"
   3.245 +          AND "losing_initiative"."admitted"
   3.246 +          AND "winning_initiative"."schulze_rank" <
   3.247 +              "losing_initiative"."schulze_rank"
   3.248 +          AND "battle_win"."count" > "battle_lose"."count"
   3.249 +          AND (
   3.250 +            "battle_win"."count" > "winning_initiative"."positive_votes" OR
   3.251 +            "battle_lose"."count" < "losing_initiative"."negative_votes" )
   3.252 +        ) AS "subquery"
   3.253 +        WHERE "id" = "subquery"."initiative_id";
   3.254 +      -- mark eligible initiatives:
   3.255 +      UPDATE "initiative" SET "eligible" = TRUE
   3.256 +        WHERE "issue_id" = "issue_id_p"
   3.257 +        AND "initiative"."direct_majority"
   3.258 +        AND "initiative"."indirect_majority"
   3.259 +        AND "initiative"."better_than_status_quo"
   3.260 +        AND (
   3.261 +          "policy_row"."no_multistage_majority" = FALSE OR
   3.262 +          "initiative"."multistage_majority" = FALSE )
   3.263 +        AND (
   3.264 +          "policy_row"."no_reverse_beat_path" = FALSE OR
   3.265 +          "initiative"."reverse_beat_path" = FALSE );
   3.266 +      -- mark final winner:
   3.267 +      UPDATE "initiative" SET "winner" = TRUE
   3.268 +        FROM (
   3.269 +          SELECT "id" AS "initiative_id"
   3.270 +          FROM "initiative"
   3.271 +          WHERE "issue_id" = "issue_id_p" AND "eligible"
   3.272 +          ORDER BY
   3.273 +            "schulze_rank",
   3.274 +            "id"
   3.275 +          LIMIT 1
   3.276 +        ) AS "subquery"
   3.277 +        WHERE "id" = "subquery"."initiative_id";
   3.278 +      -- write (final) ranks:
   3.279 +      "rank_v" := 1;
   3.280 +      FOR "initiative_id_v" IN
   3.281 +        SELECT "id"
   3.282 +        FROM "initiative"
   3.283 +        WHERE "issue_id" = "issue_id_p" AND "admitted"
   3.284 +        ORDER BY
   3.285 +          "winner" DESC,
   3.286 +          "eligible" DESC,
   3.287 +          "schulze_rank",
   3.288 +          "id"
   3.289 +      LOOP
   3.290 +        UPDATE "initiative" SET "rank" = "rank_v"
   3.291 +          WHERE "id" = "initiative_id_v";
   3.292 +        "rank_v" := "rank_v" + 1;
   3.293 +      END LOOP;
   3.294 +      -- set schulze rank of status quo and mark issue as finished:
   3.295 +      UPDATE "issue" SET
   3.296 +        "status_quo_schulze_rank" = "rank_ary"["dimension_v"],
   3.297 +        "state" =
   3.298 +          CASE WHEN EXISTS (
   3.299 +            SELECT NULL FROM "initiative"
   3.300 +            WHERE "issue_id" = "issue_id_p" AND "winner"
   3.301 +          ) THEN
   3.302 +            'finished_with_winner'::"issue_state"
   3.303 +          ELSE
   3.304 +            'finished_without_winner'::"issue_state"
   3.305 +          END,
   3.306 +        "ranks_available" = TRUE
   3.307 +        WHERE "id" = "issue_id_p";
   3.308 +      RETURN;
   3.309 +    END;
   3.310 +  $$;
   3.311 +
   3.312 +COMMIT;
     4.1 --- a/update/core-update.v2.0.11-v2.1.0.sql	Sun Sep 30 12:59:07 2012 +0200
     4.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
     4.3 @@ -1,458 +0,0 @@
     4.4 -BEGIN;
     4.5 -
     4.6 -
     4.7 --- update version number
     4.8 -
     4.9 -CREATE OR REPLACE VIEW "liquid_feedback_version" AS
    4.10 -  SELECT * FROM (VALUES ('2.1.0', 2, 1, 0))
    4.11 -  AS "subquery"("string", "major", "minor", "revision");
    4.12 -
    4.13 -
    4.14 --- old API tables are now deprecated
    4.15 -
    4.16 -COMMENT ON TYPE "application_access_level" IS 'DEPRECATED, WILL BE REMOVED! Access privileges for applications using the API';
    4.17 -COMMENT ON TABLE "member_application" IS 'DEPRECATED, WILL BE REMOVED! Registered application being allowed to use the API';
    4.18 -
    4.19 -
    4.20 --- new polling mode and changed privileges
    4.21 -
    4.22 -ALTER TABLE "policy" ADD COLUMN "polling" BOOLEAN NOT NULL DEFAULT FALSE;
    4.23 -ALTER TABLE "policy" ALTER COLUMN "admission_time"    DROP NOT NULL;
    4.24 -ALTER TABLE "policy" ALTER COLUMN "discussion_time"   DROP NOT NULL;
    4.25 -ALTER TABLE "policy" ALTER COLUMN "verification_time" DROP NOT NULL;
    4.26 -ALTER TABLE "policy" ALTER COLUMN "voting_time"       DROP NOT NULL;
    4.27 -ALTER TABLE "policy" ALTER COLUMN "issue_quorum_num"  DROP NOT NULL;
    4.28 -ALTER TABLE "policy" ALTER COLUMN "issue_quorum_den"  DROP NOT NULL;
    4.29 -ALTER TABLE "policy" ADD CONSTRAINT "timing" CHECK (
    4.30 -          ( "polling" = FALSE AND
    4.31 -            "admission_time" NOTNULL AND "discussion_time" NOTNULL AND
    4.32 -            "verification_time" NOTNULL AND "voting_time" NOTNULL ) OR
    4.33 -          ( "polling" = TRUE AND
    4.34 -            "admission_time" ISNULL AND "discussion_time" NOTNULL AND
    4.35 -            "verification_time" NOTNULL AND "voting_time" NOTNULL ) OR
    4.36 -          ( "polling" = TRUE AND
    4.37 -            "admission_time" ISNULL AND "discussion_time" ISNULL AND
    4.38 -            "verification_time" ISNULL AND "voting_time" ISNULL ) );
    4.39 -ALTER TABLE "policy" ADD CONSTRAINT "issue_quorum_if_and_only_if_not_polling" CHECK (
    4.40 -          "polling" = "issue_quorum_num" ISNULL AND
    4.41 -          "polling" = "issue_quorum_den" ISNULL );
    4.42 -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; "admission_time" MUST be set to NULL, the other timings may be set to NULL altogether, allowing individual timing for those issues';
    4.43 -
    4.44 -ALTER TABLE "issue" ALTER COLUMN "admission_time" DROP NOT NULL;
    4.45 -ALTER TABLE "issue" ADD CONSTRAINT "admission_time_not_null_unless_instantly_accepted" CHECK (
    4.46 -  "admission_time" NOTNULL OR ("accepted" NOTNULL AND "accepted" = "created") );
    4.47 -
    4.48 -ALTER TABLE "initiative" ADD COLUMN "polling" BOOLEAN NOT NULL DEFAULT FALSE;
    4.49 -COMMENT ON COLUMN "initiative"."polling" IS 'Initiative does not need to pass the initiative quorum (see "policy"."polling")';
    4.50 -
    4.51 -ALTER TABLE "privilege" RENAME COLUMN "voting_right_manager" TO "member_manager";
    4.52 -ALTER TABLE "privilege" ADD COLUMN "initiative_right" BOOLEAN NOT NULL DEFAULT TRUE;
    4.53 -ALTER TABLE "privilege" ADD COLUMN "polling_right"    BOOLEAN NOT NULL DEFAULT FALSE;
    4.54 -UPDATE "privilege" SET "initiative_right" = "voting_right";
    4.55 -COMMENT ON COLUMN "privilege"."admin_manager"    IS 'Grant/revoke any privileges to/from other members';
    4.56 -COMMENT ON COLUMN "privilege"."member_manager"   IS 'Adding/removing members from the unit, granting or revoking "initiative_right" and "voting_right"';
    4.57 -COMMENT ON COLUMN "privilege"."initiative_right" IS 'Right to create an initiative';
    4.58 -COMMENT ON COLUMN "privilege"."voting_right"     IS 'Right to support initiatives, create and rate suggestions, and to vote';
    4.59 -COMMENT ON COLUMN "privilege"."polling_right"    IS 'Right to create issues with policies having the "policy"."polling" flag set, and to add initiatives having the "initiative"."polling" flag set to those issues';
    4.60 -
    4.61 -DROP VIEW "member_contingent_left";
    4.62 -DROP VIEW "member_contingent";
    4.63 -ALTER TABLE "contingent" DROP CONSTRAINT "contingent_pkey";
    4.64 -ALTER TABLE "contingent" ALTER COLUMN "time_frame" DROP NOT NULL;
    4.65 -ALTER TABLE "contingent" ADD COLUMN "polling" BOOLEAN DEFAULT FALSE;
    4.66 -ALTER TABLE "contingent" ADD PRIMARY KEY ("polling", "time_frame");
    4.67 -ALTER TABLE "contingent" ALTER COLUMN "polling" DROP DEFAULT;
    4.68 -COMMENT ON COLUMN "contingent"."polling" IS 'Determines if settings are for creating initiatives and new drafts of initiatives with "polling" flag set';
    4.69 -
    4.70 -CREATE VIEW "member_contingent" AS
    4.71 -  SELECT
    4.72 -    "member"."id" AS "member_id",
    4.73 -    "contingent"."polling",
    4.74 -    "contingent"."time_frame",
    4.75 -    CASE WHEN "contingent"."text_entry_limit" NOTNULL THEN
    4.76 -      (
    4.77 -        SELECT count(1) FROM "draft"
    4.78 -        JOIN "initiative" ON "initiative"."id" = "draft"."initiative_id"
    4.79 -        WHERE "draft"."author_id" = "member"."id"
    4.80 -        AND "initiative"."polling" = "contingent"."polling"
    4.81 -        AND "draft"."created" > now() - "contingent"."time_frame"
    4.82 -      ) + (
    4.83 -        SELECT count(1) FROM "suggestion"
    4.84 -        JOIN "initiative" ON "initiative"."id" = "suggestion"."initiative_id"
    4.85 -        WHERE "suggestion"."author_id" = "member"."id"
    4.86 -        AND "contingent"."polling" = FALSE
    4.87 -        AND "suggestion"."created" > now() - "contingent"."time_frame"
    4.88 -      )
    4.89 -    ELSE NULL END AS "text_entry_count",
    4.90 -    "contingent"."text_entry_limit",
    4.91 -    CASE WHEN "contingent"."initiative_limit" NOTNULL THEN (
    4.92 -      SELECT count(1) FROM "opening_draft" AS "draft"
    4.93 -        JOIN "initiative" ON "initiative"."id" = "draft"."initiative_id"
    4.94 -      WHERE "draft"."author_id" = "member"."id"
    4.95 -      AND "initiative"."polling" = "contingent"."polling"
    4.96 -      AND "draft"."created" > now() - "contingent"."time_frame"
    4.97 -    ) ELSE NULL END AS "initiative_count",
    4.98 -    "contingent"."initiative_limit"
    4.99 -  FROM "member" CROSS JOIN "contingent";
   4.100 -
   4.101 -COMMENT ON VIEW "member_contingent" IS 'Actual counts of text entries and initiatives are calculated per member for each limit in the "contingent" table.';
   4.102 -
   4.103 -COMMENT ON COLUMN "member_contingent"."text_entry_count" IS 'Only calculated when "text_entry_limit" is not null in the same row';
   4.104 -COMMENT ON COLUMN "member_contingent"."initiative_count" IS 'Only calculated when "initiative_limit" is not null in the same row';
   4.105 -
   4.106 -CREATE VIEW "member_contingent_left" AS
   4.107 -  SELECT
   4.108 -    "member_id",
   4.109 -    "polling",
   4.110 -    max("text_entry_limit" - "text_entry_count") AS "text_entries_left",
   4.111 -    max("initiative_limit" - "initiative_count") AS "initiatives_left"
   4.112 -  FROM "member_contingent" GROUP BY "member_id", "polling";
   4.113 -
   4.114 -COMMENT ON VIEW "member_contingent_left" IS 'Amount of text entries or initiatives which can be posted now instantly by a member. This view should be used by a frontend to determine, if the contingent for posting is exhausted.';
   4.115 -
   4.116 -CREATE OR REPLACE FUNCTION "freeze_after_snapshot"
   4.117 -  ( "issue_id_p" "issue"."id"%TYPE )
   4.118 -  RETURNS VOID
   4.119 -  LANGUAGE 'plpgsql' VOLATILE AS $$
   4.120 -    DECLARE
   4.121 -      "issue_row"      "issue"%ROWTYPE;
   4.122 -      "policy_row"     "policy"%ROWTYPE;
   4.123 -      "initiative_row" "initiative"%ROWTYPE;
   4.124 -    BEGIN
   4.125 -      SELECT * INTO "issue_row" FROM "issue" WHERE "id" = "issue_id_p";
   4.126 -      SELECT * INTO "policy_row"
   4.127 -        FROM "policy" WHERE "id" = "issue_row"."policy_id";
   4.128 -      PERFORM "set_snapshot_event"("issue_id_p", 'full_freeze');
   4.129 -      FOR "initiative_row" IN
   4.130 -        SELECT * FROM "initiative"
   4.131 -        WHERE "issue_id" = "issue_id_p" AND "revoked" ISNULL
   4.132 -      LOOP
   4.133 -        IF
   4.134 -          "initiative_row"."polling" OR (
   4.135 -            "initiative_row"."satisfied_supporter_count" > 0 AND
   4.136 -            "initiative_row"."satisfied_supporter_count" *
   4.137 -            "policy_row"."initiative_quorum_den" >=
   4.138 -            "issue_row"."population" * "policy_row"."initiative_quorum_num"
   4.139 -          )
   4.140 -        THEN
   4.141 -          UPDATE "initiative" SET "admitted" = TRUE
   4.142 -            WHERE "id" = "initiative_row"."id";
   4.143 -        ELSE
   4.144 -          UPDATE "initiative" SET "admitted" = FALSE
   4.145 -            WHERE "id" = "initiative_row"."id";
   4.146 -        END IF;
   4.147 -      END LOOP;
   4.148 -      IF EXISTS (
   4.149 -        SELECT NULL FROM "initiative"
   4.150 -        WHERE "issue_id" = "issue_id_p" AND "admitted" = TRUE
   4.151 -      ) THEN
   4.152 -        UPDATE "issue" SET
   4.153 -          "state"        = 'voting',
   4.154 -          "accepted"     = coalesce("accepted", now()),
   4.155 -          "half_frozen"  = coalesce("half_frozen", now()),
   4.156 -          "fully_frozen" = now()
   4.157 -          WHERE "id" = "issue_id_p";
   4.158 -      ELSE
   4.159 -        UPDATE "issue" SET
   4.160 -          "state"           = 'canceled_no_initiative_admitted',
   4.161 -          "accepted"        = coalesce("accepted", now()),
   4.162 -          "half_frozen"     = coalesce("half_frozen", now()),
   4.163 -          "fully_frozen"    = now(),
   4.164 -          "closed"          = now(),
   4.165 -          "ranks_available" = TRUE
   4.166 -          WHERE "id" = "issue_id_p";
   4.167 -        -- NOTE: The following DELETE statements have effect only when
   4.168 -        --       issue state has been manipulated
   4.169 -        DELETE FROM "direct_voter"     WHERE "issue_id" = "issue_id_p";
   4.170 -        DELETE FROM "delegating_voter" WHERE "issue_id" = "issue_id_p";
   4.171 -        DELETE FROM "battle"           WHERE "issue_id" = "issue_id_p";
   4.172 -      END IF;
   4.173 -      RETURN;
   4.174 -    END;
   4.175 -  $$;
   4.176 -
   4.177 -
   4.178 --- issue comments removed, voting comments integrated in "direct_voter" table
   4.179 -
   4.180 -ALTER TABLE "direct_voter" ADD COLUMN "comment_changed"   TIMESTAMPTZ;
   4.181 -ALTER TABLE "direct_voter" ADD COLUMN "formatting_engine" TEXT;
   4.182 -ALTER TABLE "direct_voter" ADD COLUMN "comment"           TEXT;
   4.183 -ALTER TABLE "direct_voter" ADD COLUMN "text_search_data"  TSVECTOR;
   4.184 -CREATE INDEX "direct_voter_text_search_data_idx" ON "direct_voter" USING gin ("text_search_data");
   4.185 -CREATE TRIGGER "update_text_search_data"
   4.186 -  BEFORE INSERT OR UPDATE ON "direct_voter"
   4.187 -  FOR EACH ROW EXECUTE PROCEDURE
   4.188 -  tsvector_update_trigger('text_search_data', 'pg_catalog.simple', "comment");
   4.189 -
   4.190 -COMMENT ON COLUMN "direct_voter"."comment_changed"   IS 'Shall be set on comment change, to indicate a comment being modified after voting has been finished; Automatically set to NULL after voting phase; Automatically set to NULL by trigger, if "comment" is set to NULL';
   4.191 -COMMENT ON COLUMN "direct_voter"."formatting_engine" IS 'Allows different formatting engines (i.e. wiki formats) to be used for "direct_voter"."comment"; Automatically set to NULL by trigger, if "comment" is set to NULL';
   4.192 -COMMENT ON COLUMN "direct_voter"."comment"           IS 'Is to be set or updated by the frontend, if comment was inserted or updated AFTER the issue has been closed. Otherwise it shall be set to NULL.';
   4.193 -
   4.194 -CREATE TABLE "rendered_voter_comment" (
   4.195 -        PRIMARY KEY ("issue_id", "member_id", "format"),
   4.196 -        FOREIGN KEY ("issue_id", "member_id")
   4.197 -          REFERENCES "direct_voter" ("issue_id", "member_id")
   4.198 -          ON DELETE CASCADE ON UPDATE CASCADE,
   4.199 -        "issue_id"              INT4,
   4.200 -        "member_id"             INT4,
   4.201 -        "format"                TEXT,
   4.202 -        "content"               TEXT            NOT NULL );
   4.203 -
   4.204 -COMMENT ON TABLE "rendered_voter_comment" IS 'This table may be used by frontends to cache "rendered" voter comments (e.g. HTML output generated from wiki text)';
   4.205 -
   4.206 -DROP TABLE "rendered_issue_comment";
   4.207 -DROP TABLE "issue_comment";
   4.208 -DROP TABLE "rendered_voting_comment";
   4.209 -DROP TABLE "voting_comment";
   4.210 -
   4.211 -CREATE FUNCTION "voter_comment_fields_only_set_when_voter_comment_is_set_trigger"()
   4.212 -  RETURNS TRIGGER
   4.213 -  LANGUAGE 'plpgsql' VOLATILE AS $$
   4.214 -    BEGIN
   4.215 -      IF NEW."comment" ISNULL THEN
   4.216 -        NEW."comment_changed" := NULL;
   4.217 -        NEW."formatting_engine" := NULL;
   4.218 -      END IF;
   4.219 -      RETURN NEW;
   4.220 -    END;
   4.221 -  $$;
   4.222 -
   4.223 -CREATE TRIGGER "voter_comment_fields_only_set_when_voter_comment_is_set"
   4.224 -  BEFORE INSERT OR UPDATE ON "direct_voter"
   4.225 -  FOR EACH ROW EXECUTE PROCEDURE
   4.226 -  "voter_comment_fields_only_set_when_voter_comment_is_set_trigger"();
   4.227 -
   4.228 -COMMENT ON FUNCTION "voter_comment_fields_only_set_when_voter_comment_is_set_trigger"() IS 'Implementation of trigger "voter_comment_fields_only_set_when_voter_comment_is_set" ON table "direct_voter"';
   4.229 -COMMENT ON TRIGGER "voter_comment_fields_only_set_when_voter_comment_is_set" ON "direct_voter" IS 'If "comment" is set to NULL, then other comment related fields are also set to NULL.';
   4.230 -
   4.231 -CREATE OR REPLACE FUNCTION "forbid_changes_on_closed_issue_trigger"()
   4.232 -  RETURNS TRIGGER
   4.233 -  LANGUAGE 'plpgsql' VOLATILE AS $$
   4.234 -    DECLARE
   4.235 -      "issue_id_v" "issue"."id"%TYPE;
   4.236 -      "issue_row"  "issue"%ROWTYPE;
   4.237 -    BEGIN
   4.238 -      IF TG_RELID = 'direct_voter'::regclass AND TG_OP = 'UPDATE' THEN
   4.239 -        IF
   4.240 -          OLD."issue_id"  = NEW."issue_id"  AND
   4.241 -          OLD."member_id" = NEW."member_id" AND
   4.242 -          OLD."weight"    = NEW."weight"
   4.243 -        THEN
   4.244 -          RETURN NULL;  -- allows changing of voter comment
   4.245 -        END IF;
   4.246 -      END IF;
   4.247 -      IF TG_OP = 'DELETE' THEN
   4.248 -        "issue_id_v" := OLD."issue_id";
   4.249 -      ELSE
   4.250 -        "issue_id_v" := NEW."issue_id";
   4.251 -      END IF;
   4.252 -      SELECT INTO "issue_row" * FROM "issue"
   4.253 -        WHERE "id" = "issue_id_v" FOR SHARE;
   4.254 -      IF "issue_row"."closed" NOTNULL THEN
   4.255 -        RAISE EXCEPTION 'Tried to modify data belonging to a closed issue.';
   4.256 -      END IF;
   4.257 -      RETURN NULL;
   4.258 -    END;
   4.259 -  $$;
   4.260 -
   4.261 -CREATE OR REPLACE FUNCTION "close_voting"("issue_id_p" "issue"."id"%TYPE)
   4.262 -  RETURNS VOID
   4.263 -  LANGUAGE 'plpgsql' VOLATILE AS $$
   4.264 -    DECLARE
   4.265 -      "area_id_v"   "area"."id"%TYPE;
   4.266 -      "unit_id_v"   "unit"."id"%TYPE;
   4.267 -      "member_id_v" "member"."id"%TYPE;
   4.268 -    BEGIN
   4.269 -      PERFORM "lock_issue"("issue_id_p");
   4.270 -      SELECT "area_id" INTO "area_id_v" FROM "issue" WHERE "id" = "issue_id_p";
   4.271 -      SELECT "unit_id" INTO "unit_id_v" FROM "area"  WHERE "id" = "area_id_v";
   4.272 -      -- delete timestamp of voting comment:
   4.273 -      UPDATE "direct_voter" SET "comment_changed" = NULL
   4.274 -        WHERE "issue_id" = "issue_id_p";
   4.275 -      -- delete delegating votes (in cases of manual reset of issue state):
   4.276 -      DELETE FROM "delegating_voter"
   4.277 -        WHERE "issue_id" = "issue_id_p";
   4.278 -      -- delete votes from non-privileged voters:
   4.279 -      DELETE FROM "direct_voter"
   4.280 -        USING (
   4.281 -          SELECT
   4.282 -            "direct_voter"."member_id"
   4.283 -          FROM "direct_voter"
   4.284 -          JOIN "member" ON "direct_voter"."member_id" = "member"."id"
   4.285 -          LEFT JOIN "privilege"
   4.286 -          ON "privilege"."unit_id" = "unit_id_v"
   4.287 -          AND "privilege"."member_id" = "direct_voter"."member_id"
   4.288 -          WHERE "direct_voter"."issue_id" = "issue_id_p" AND (
   4.289 -            "member"."active" = FALSE OR
   4.290 -            "privilege"."voting_right" ISNULL OR
   4.291 -            "privilege"."voting_right" = FALSE
   4.292 -          )
   4.293 -        ) AS "subquery"
   4.294 -        WHERE "direct_voter"."issue_id" = "issue_id_p"
   4.295 -        AND "direct_voter"."member_id" = "subquery"."member_id";
   4.296 -      -- consider delegations:
   4.297 -      UPDATE "direct_voter" SET "weight" = 1
   4.298 -        WHERE "issue_id" = "issue_id_p";
   4.299 -      PERFORM "add_vote_delegations"("issue_id_p");
   4.300 -      -- set voter count and mark issue as being calculated:
   4.301 -      UPDATE "issue" SET
   4.302 -        "state"  = 'calculation',
   4.303 -        "closed" = now(),
   4.304 -        "voter_count" = (
   4.305 -          SELECT coalesce(sum("weight"), 0)
   4.306 -          FROM "direct_voter" WHERE "issue_id" = "issue_id_p"
   4.307 -        )
   4.308 -        WHERE "id" = "issue_id_p";
   4.309 -      -- materialize battle_view:
   4.310 -      -- NOTE: "closed" column of issue must be set at this point
   4.311 -      DELETE FROM "battle" WHERE "issue_id" = "issue_id_p";
   4.312 -      INSERT INTO "battle" (
   4.313 -        "issue_id",
   4.314 -        "winning_initiative_id", "losing_initiative_id",
   4.315 -        "count"
   4.316 -      ) SELECT
   4.317 -        "issue_id",
   4.318 -        "winning_initiative_id", "losing_initiative_id",
   4.319 -        "count"
   4.320 -        FROM "battle_view" WHERE "issue_id" = "issue_id_p";
   4.321 -      -- copy "positive_votes" and "negative_votes" from "battle" table:
   4.322 -      UPDATE "initiative" SET
   4.323 -        "positive_votes" = "battle_win"."count",
   4.324 -        "negative_votes" = "battle_lose"."count"
   4.325 -        FROM "battle" AS "battle_win", "battle" AS "battle_lose"
   4.326 -        WHERE
   4.327 -          "battle_win"."issue_id" = "issue_id_p" AND
   4.328 -          "battle_win"."winning_initiative_id" = "initiative"."id" AND
   4.329 -          "battle_win"."losing_initiative_id" ISNULL AND
   4.330 -          "battle_lose"."issue_id" = "issue_id_p" AND
   4.331 -          "battle_lose"."losing_initiative_id" = "initiative"."id" AND
   4.332 -          "battle_lose"."winning_initiative_id" ISNULL;
   4.333 -    END;
   4.334 -  $$;
   4.335 -
   4.336 -CREATE OR REPLACE FUNCTION "clean_issue"("issue_id_p" "issue"."id"%TYPE)
   4.337 -  RETURNS VOID
   4.338 -  LANGUAGE 'plpgsql' VOLATILE AS $$
   4.339 -    DECLARE
   4.340 -      "issue_row" "issue"%ROWTYPE;
   4.341 -    BEGIN
   4.342 -      SELECT * INTO "issue_row"
   4.343 -        FROM "issue" WHERE "id" = "issue_id_p"
   4.344 -        FOR UPDATE;
   4.345 -      IF "issue_row"."cleaned" ISNULL THEN
   4.346 -        UPDATE "issue" SET
   4.347 -          "state"           = 'voting',
   4.348 -          "closed"          = NULL,
   4.349 -          "ranks_available" = FALSE
   4.350 -          WHERE "id" = "issue_id_p";
   4.351 -        DELETE FROM "delegating_voter"
   4.352 -          WHERE "issue_id" = "issue_id_p";
   4.353 -        DELETE FROM "direct_voter"
   4.354 -          WHERE "issue_id" = "issue_id_p";
   4.355 -        DELETE FROM "delegating_interest_snapshot"
   4.356 -          WHERE "issue_id" = "issue_id_p";
   4.357 -        DELETE FROM "direct_interest_snapshot"
   4.358 -          WHERE "issue_id" = "issue_id_p";
   4.359 -        DELETE FROM "delegating_population_snapshot"
   4.360 -          WHERE "issue_id" = "issue_id_p";
   4.361 -        DELETE FROM "direct_population_snapshot"
   4.362 -          WHERE "issue_id" = "issue_id_p";
   4.363 -        DELETE FROM "non_voter"
   4.364 -          WHERE "issue_id" = "issue_id_p";
   4.365 -        DELETE FROM "delegation"
   4.366 -          WHERE "issue_id" = "issue_id_p";
   4.367 -        DELETE FROM "supporter"
   4.368 -          WHERE "issue_id" = "issue_id_p";
   4.369 -        UPDATE "issue" SET
   4.370 -          "state"           = "issue_row"."state",
   4.371 -          "closed"          = "issue_row"."closed",
   4.372 -          "ranks_available" = "issue_row"."ranks_available",
   4.373 -          "cleaned"         = now()
   4.374 -          WHERE "id" = "issue_id_p";
   4.375 -      END IF;
   4.376 -      RETURN;
   4.377 -    END;
   4.378 -  $$;
   4.379 -
   4.380 -
   4.381 --- "non_voter" deletes "direct_voter" and vice versa
   4.382 -
   4.383 -CREATE FUNCTION "non_voter_deletes_direct_voter_trigger"()
   4.384 -  RETURNS TRIGGER
   4.385 -  LANGUAGE 'plpgsql' VOLATILE AS $$
   4.386 -    BEGIN
   4.387 -      DELETE FROM "direct_voter"
   4.388 -        WHERE "issue_id" = NEW."issue_id" AND "member_id" = NEW."member_id";
   4.389 -      RETURN NULL;
   4.390 -    END;
   4.391 -  $$;
   4.392 -
   4.393 -CREATE TRIGGER "non_voter_deletes_direct_voter"
   4.394 -  AFTER INSERT OR UPDATE ON "non_voter"
   4.395 -  FOR EACH ROW EXECUTE PROCEDURE
   4.396 -  "non_voter_deletes_direct_voter_trigger"();
   4.397 -
   4.398 -COMMENT ON FUNCTION "non_voter_deletes_direct_voter_trigger"()     IS 'Implementation of trigger "non_voter_deletes_direct_voter" on table "non_voter"';
   4.399 -COMMENT ON TRIGGER "non_voter_deletes_direct_voter" ON "non_voter" IS 'An entry in the "non_voter" table deletes an entry in the "direct_voter" table (and vice versa due to trigger "direct_voter_deletes_non_voter" on table "direct_voter")';
   4.400 -
   4.401 -CREATE FUNCTION "direct_voter_deletes_non_voter_trigger"()
   4.402 -  RETURNS TRIGGER
   4.403 -  LANGUAGE 'plpgsql' VOLATILE AS $$
   4.404 -    BEGIN
   4.405 -      DELETE FROM "non_voter"
   4.406 -        WHERE "issue_id" = NEW."issue_id" AND "member_id" = NEW."member_id";
   4.407 -      RETURN NULL;
   4.408 -    END;
   4.409 -  $$;
   4.410 -
   4.411 -CREATE TRIGGER "direct_voter_deletes_non_voter"
   4.412 -  AFTER INSERT OR UPDATE ON "direct_voter"
   4.413 -  FOR EACH ROW EXECUTE PROCEDURE
   4.414 -  "direct_voter_deletes_non_voter_trigger"();
   4.415 -
   4.416 -COMMENT ON FUNCTION "direct_voter_deletes_non_voter_trigger"()        IS 'Implementation of trigger "direct_voter_deletes_non_voter" on table "direct_voter"';
   4.417 -COMMENT ON TRIGGER "direct_voter_deletes_non_voter" ON "direct_voter" IS 'An entry in the "direct_voter" table deletes an entry in the "non_voter" table (and vice versa due to trigger "non_voter_deletes_direct_voter" on table "non_voter")';
   4.418 -
   4.419 -
   4.420 --- different locking levels and different locking order to avoid deadlocks
   4.421 -
   4.422 -CREATE OR REPLACE FUNCTION "lock_issue"
   4.423 -  ( "issue_id_p" "issue"."id"%TYPE )
   4.424 -  RETURNS VOID
   4.425 -  LANGUAGE 'plpgsql' VOLATILE AS $$
   4.426 -    BEGIN
   4.427 -      -- The following locking order is used:
   4.428 -      -- 1st) row-level lock on the issue
   4.429 -      -- 2nd) table-level locks in order of occurrence in the core.sql file
   4.430 -      PERFORM NULL FROM "issue" WHERE "id" = "issue_id_p" FOR UPDATE;
   4.431 -      -- NOTE: The row-level exclusive lock in combination with the
   4.432 -      -- share_row_lock_issue(_via_initiative)_trigger functions (which
   4.433 -      -- acquire a row-level share lock on the issue) ensure that no data
   4.434 -      -- is changed, which could affect calculation of snapshots or
   4.435 -      -- counting of votes. Table "delegation" must be table-level-locked,
   4.436 -      -- as it also contains issue- and global-scope delegations.
   4.437 -      PERFORM NULL FROM "member" WHERE "active" FOR SHARE;
   4.438 -      -- NOTE: As we later cause implicit row-level share locks on many
   4.439 -      -- active members, we lock them before locking any other table
   4.440 -      -- to avoid deadlocks
   4.441 -      LOCK TABLE "member"     IN SHARE MODE;
   4.442 -      LOCK TABLE "privilege"  IN SHARE MODE;
   4.443 -      LOCK TABLE "membership" IN SHARE MODE;
   4.444 -      LOCK TABLE "policy"     IN SHARE MODE;
   4.445 -      LOCK TABLE "delegation" IN SHARE MODE;
   4.446 -      LOCK TABLE "direct_population_snapshot"     IN EXCLUSIVE MODE;
   4.447 -      LOCK TABLE "delegating_population_snapshot" IN EXCLUSIVE MODE;
   4.448 -      LOCK TABLE "direct_interest_snapshot"       IN EXCLUSIVE MODE;
   4.449 -      LOCK TABLE "delegating_interest_snapshot"   IN EXCLUSIVE MODE;
   4.450 -      LOCK TABLE "direct_supporter_snapshot"      IN EXCLUSIVE MODE;
   4.451 -      RETURN;
   4.452 -    END;
   4.453 -  $$;
   4.454 -
   4.455 -
   4.456 --- new comment on function "delete_private_data"()
   4.457 -
   4.458 -COMMENT ON FUNCTION "delete_private_data"() IS 'Used by lf_export script. DO NOT USE on productive database, but only on a copy! This function deletes all data which should not be publicly available, and can be used to create a database dump for publication. See source code to see which data is deleted. If you need a different behaviour, copy this function and modify lf_export accordingly, to avoid data-leaks after updating.';
   4.459 -
   4.460 -
   4.461 -COMMIT;
     5.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     5.2 +++ b/update/core-update.v2.0.12-v2.1.0.sql	Fri Oct 05 13:45:37 2012 +0200
     5.3 @@ -0,0 +1,458 @@
     5.4 +BEGIN;
     5.5 +
     5.6 +
     5.7 +-- update version number
     5.8 +
     5.9 +CREATE OR REPLACE VIEW "liquid_feedback_version" AS
    5.10 +  SELECT * FROM (VALUES ('2.1.0', 2, 1, 0))
    5.11 +  AS "subquery"("string", "major", "minor", "revision");
    5.12 +
    5.13 +
    5.14 +-- old API tables are now deprecated
    5.15 +
    5.16 +COMMENT ON TYPE "application_access_level" IS 'DEPRECATED, WILL BE REMOVED! Access privileges for applications using the API';
    5.17 +COMMENT ON TABLE "member_application" IS 'DEPRECATED, WILL BE REMOVED! Registered application being allowed to use the API';
    5.18 +
    5.19 +
    5.20 +-- new polling mode and changed privileges
    5.21 +
    5.22 +ALTER TABLE "policy" ADD COLUMN "polling" BOOLEAN NOT NULL DEFAULT FALSE;
    5.23 +ALTER TABLE "policy" ALTER COLUMN "admission_time"    DROP NOT NULL;
    5.24 +ALTER TABLE "policy" ALTER COLUMN "discussion_time"   DROP NOT NULL;
    5.25 +ALTER TABLE "policy" ALTER COLUMN "verification_time" DROP NOT NULL;
    5.26 +ALTER TABLE "policy" ALTER COLUMN "voting_time"       DROP NOT NULL;
    5.27 +ALTER TABLE "policy" ALTER COLUMN "issue_quorum_num"  DROP NOT NULL;
    5.28 +ALTER TABLE "policy" ALTER COLUMN "issue_quorum_den"  DROP NOT NULL;
    5.29 +ALTER TABLE "policy" ADD CONSTRAINT "timing" CHECK (
    5.30 +          ( "polling" = FALSE AND
    5.31 +            "admission_time" NOTNULL AND "discussion_time" NOTNULL AND
    5.32 +            "verification_time" NOTNULL AND "voting_time" NOTNULL ) OR
    5.33 +          ( "polling" = TRUE AND
    5.34 +            "admission_time" ISNULL AND "discussion_time" NOTNULL AND
    5.35 +            "verification_time" NOTNULL AND "voting_time" NOTNULL ) OR
    5.36 +          ( "polling" = TRUE AND
    5.37 +            "admission_time" ISNULL AND "discussion_time" ISNULL AND
    5.38 +            "verification_time" ISNULL AND "voting_time" ISNULL ) );
    5.39 +ALTER TABLE "policy" ADD CONSTRAINT "issue_quorum_if_and_only_if_not_polling" CHECK (
    5.40 +          "polling" = "issue_quorum_num" ISNULL AND
    5.41 +          "polling" = "issue_quorum_den" ISNULL );
    5.42 +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; "admission_time" MUST be set to NULL, the other timings may be set to NULL altogether, allowing individual timing for those issues';
    5.43 +
    5.44 +ALTER TABLE "issue" ALTER COLUMN "admission_time" DROP NOT NULL;
    5.45 +ALTER TABLE "issue" ADD CONSTRAINT "admission_time_not_null_unless_instantly_accepted" CHECK (
    5.46 +  "admission_time" NOTNULL OR ("accepted" NOTNULL AND "accepted" = "created") );
    5.47 +
    5.48 +ALTER TABLE "initiative" ADD COLUMN "polling" BOOLEAN NOT NULL DEFAULT FALSE;
    5.49 +COMMENT ON COLUMN "initiative"."polling" IS 'Initiative does not need to pass the initiative quorum (see "policy"."polling")';
    5.50 +
    5.51 +ALTER TABLE "privilege" RENAME COLUMN "voting_right_manager" TO "member_manager";
    5.52 +ALTER TABLE "privilege" ADD COLUMN "initiative_right" BOOLEAN NOT NULL DEFAULT TRUE;
    5.53 +ALTER TABLE "privilege" ADD COLUMN "polling_right"    BOOLEAN NOT NULL DEFAULT FALSE;
    5.54 +UPDATE "privilege" SET "initiative_right" = "voting_right";
    5.55 +COMMENT ON COLUMN "privilege"."admin_manager"    IS 'Grant/revoke any privileges to/from other members';
    5.56 +COMMENT ON COLUMN "privilege"."member_manager"   IS 'Adding/removing members from the unit, granting or revoking "initiative_right" and "voting_right"';
    5.57 +COMMENT ON COLUMN "privilege"."initiative_right" IS 'Right to create an initiative';
    5.58 +COMMENT ON COLUMN "privilege"."voting_right"     IS 'Right to support initiatives, create and rate suggestions, and to vote';
    5.59 +COMMENT ON COLUMN "privilege"."polling_right"    IS 'Right to create issues with policies having the "policy"."polling" flag set, and to add initiatives having the "initiative"."polling" flag set to those issues';
    5.60 +
    5.61 +DROP VIEW "member_contingent_left";
    5.62 +DROP VIEW "member_contingent";
    5.63 +ALTER TABLE "contingent" DROP CONSTRAINT "contingent_pkey";
    5.64 +ALTER TABLE "contingent" ALTER COLUMN "time_frame" DROP NOT NULL;
    5.65 +ALTER TABLE "contingent" ADD COLUMN "polling" BOOLEAN DEFAULT FALSE;
    5.66 +ALTER TABLE "contingent" ADD PRIMARY KEY ("polling", "time_frame");
    5.67 +ALTER TABLE "contingent" ALTER COLUMN "polling" DROP DEFAULT;
    5.68 +COMMENT ON COLUMN "contingent"."polling" IS 'Determines if settings are for creating initiatives and new drafts of initiatives with "polling" flag set';
    5.69 +
    5.70 +CREATE VIEW "member_contingent" AS
    5.71 +  SELECT
    5.72 +    "member"."id" AS "member_id",
    5.73 +    "contingent"."polling",
    5.74 +    "contingent"."time_frame",
    5.75 +    CASE WHEN "contingent"."text_entry_limit" NOTNULL THEN
    5.76 +      (
    5.77 +        SELECT count(1) FROM "draft"
    5.78 +        JOIN "initiative" ON "initiative"."id" = "draft"."initiative_id"
    5.79 +        WHERE "draft"."author_id" = "member"."id"
    5.80 +        AND "initiative"."polling" = "contingent"."polling"
    5.81 +        AND "draft"."created" > now() - "contingent"."time_frame"
    5.82 +      ) + (
    5.83 +        SELECT count(1) FROM "suggestion"
    5.84 +        JOIN "initiative" ON "initiative"."id" = "suggestion"."initiative_id"
    5.85 +        WHERE "suggestion"."author_id" = "member"."id"
    5.86 +        AND "contingent"."polling" = FALSE
    5.87 +        AND "suggestion"."created" > now() - "contingent"."time_frame"
    5.88 +      )
    5.89 +    ELSE NULL END AS "text_entry_count",
    5.90 +    "contingent"."text_entry_limit",
    5.91 +    CASE WHEN "contingent"."initiative_limit" NOTNULL THEN (
    5.92 +      SELECT count(1) FROM "opening_draft" AS "draft"
    5.93 +        JOIN "initiative" ON "initiative"."id" = "draft"."initiative_id"
    5.94 +      WHERE "draft"."author_id" = "member"."id"
    5.95 +      AND "initiative"."polling" = "contingent"."polling"
    5.96 +      AND "draft"."created" > now() - "contingent"."time_frame"
    5.97 +    ) ELSE NULL END AS "initiative_count",
    5.98 +    "contingent"."initiative_limit"
    5.99 +  FROM "member" CROSS JOIN "contingent";
   5.100 +
   5.101 +COMMENT ON VIEW "member_contingent" IS 'Actual counts of text entries and initiatives are calculated per member for each limit in the "contingent" table.';
   5.102 +
   5.103 +COMMENT ON COLUMN "member_contingent"."text_entry_count" IS 'Only calculated when "text_entry_limit" is not null in the same row';
   5.104 +COMMENT ON COLUMN "member_contingent"."initiative_count" IS 'Only calculated when "initiative_limit" is not null in the same row';
   5.105 +
   5.106 +CREATE VIEW "member_contingent_left" AS
   5.107 +  SELECT
   5.108 +    "member_id",
   5.109 +    "polling",
   5.110 +    max("text_entry_limit" - "text_entry_count") AS "text_entries_left",
   5.111 +    max("initiative_limit" - "initiative_count") AS "initiatives_left"
   5.112 +  FROM "member_contingent" GROUP BY "member_id", "polling";
   5.113 +
   5.114 +COMMENT ON VIEW "member_contingent_left" IS 'Amount of text entries or initiatives which can be posted now instantly by a member. This view should be used by a frontend to determine, if the contingent for posting is exhausted.';
   5.115 +
   5.116 +CREATE OR REPLACE FUNCTION "freeze_after_snapshot"
   5.117 +  ( "issue_id_p" "issue"."id"%TYPE )
   5.118 +  RETURNS VOID
   5.119 +  LANGUAGE 'plpgsql' VOLATILE AS $$
   5.120 +    DECLARE
   5.121 +      "issue_row"      "issue"%ROWTYPE;
   5.122 +      "policy_row"     "policy"%ROWTYPE;
   5.123 +      "initiative_row" "initiative"%ROWTYPE;
   5.124 +    BEGIN
   5.125 +      SELECT * INTO "issue_row" FROM "issue" WHERE "id" = "issue_id_p";
   5.126 +      SELECT * INTO "policy_row"
   5.127 +        FROM "policy" WHERE "id" = "issue_row"."policy_id";
   5.128 +      PERFORM "set_snapshot_event"("issue_id_p", 'full_freeze');
   5.129 +      FOR "initiative_row" IN
   5.130 +        SELECT * FROM "initiative"
   5.131 +        WHERE "issue_id" = "issue_id_p" AND "revoked" ISNULL
   5.132 +      LOOP
   5.133 +        IF
   5.134 +          "initiative_row"."polling" OR (
   5.135 +            "initiative_row"."satisfied_supporter_count" > 0 AND
   5.136 +            "initiative_row"."satisfied_supporter_count" *
   5.137 +            "policy_row"."initiative_quorum_den" >=
   5.138 +            "issue_row"."population" * "policy_row"."initiative_quorum_num"
   5.139 +          )
   5.140 +        THEN
   5.141 +          UPDATE "initiative" SET "admitted" = TRUE
   5.142 +            WHERE "id" = "initiative_row"."id";
   5.143 +        ELSE
   5.144 +          UPDATE "initiative" SET "admitted" = FALSE
   5.145 +            WHERE "id" = "initiative_row"."id";
   5.146 +        END IF;
   5.147 +      END LOOP;
   5.148 +      IF EXISTS (
   5.149 +        SELECT NULL FROM "initiative"
   5.150 +        WHERE "issue_id" = "issue_id_p" AND "admitted" = TRUE
   5.151 +      ) THEN
   5.152 +        UPDATE "issue" SET
   5.153 +          "state"        = 'voting',
   5.154 +          "accepted"     = coalesce("accepted", now()),
   5.155 +          "half_frozen"  = coalesce("half_frozen", now()),
   5.156 +          "fully_frozen" = now()
   5.157 +          WHERE "id" = "issue_id_p";
   5.158 +      ELSE
   5.159 +        UPDATE "issue" SET
   5.160 +          "state"           = 'canceled_no_initiative_admitted',
   5.161 +          "accepted"        = coalesce("accepted", now()),
   5.162 +          "half_frozen"     = coalesce("half_frozen", now()),
   5.163 +          "fully_frozen"    = now(),
   5.164 +          "closed"          = now(),
   5.165 +          "ranks_available" = TRUE
   5.166 +          WHERE "id" = "issue_id_p";
   5.167 +        -- NOTE: The following DELETE statements have effect only when
   5.168 +        --       issue state has been manipulated
   5.169 +        DELETE FROM "direct_voter"     WHERE "issue_id" = "issue_id_p";
   5.170 +        DELETE FROM "delegating_voter" WHERE "issue_id" = "issue_id_p";
   5.171 +        DELETE FROM "battle"           WHERE "issue_id" = "issue_id_p";
   5.172 +      END IF;
   5.173 +      RETURN;
   5.174 +    END;
   5.175 +  $$;
   5.176 +
   5.177 +
   5.178 +-- issue comments removed, voting comments integrated in "direct_voter" table
   5.179 +
   5.180 +ALTER TABLE "direct_voter" ADD COLUMN "comment_changed"   TIMESTAMPTZ;
   5.181 +ALTER TABLE "direct_voter" ADD COLUMN "formatting_engine" TEXT;
   5.182 +ALTER TABLE "direct_voter" ADD COLUMN "comment"           TEXT;
   5.183 +ALTER TABLE "direct_voter" ADD COLUMN "text_search_data"  TSVECTOR;
   5.184 +CREATE INDEX "direct_voter_text_search_data_idx" ON "direct_voter" USING gin ("text_search_data");
   5.185 +CREATE TRIGGER "update_text_search_data"
   5.186 +  BEFORE INSERT OR UPDATE ON "direct_voter"
   5.187 +  FOR EACH ROW EXECUTE PROCEDURE
   5.188 +  tsvector_update_trigger('text_search_data', 'pg_catalog.simple', "comment");
   5.189 +
   5.190 +COMMENT ON COLUMN "direct_voter"."comment_changed"   IS 'Shall be set on comment change, to indicate a comment being modified after voting has been finished; Automatically set to NULL after voting phase; Automatically set to NULL by trigger, if "comment" is set to NULL';
   5.191 +COMMENT ON COLUMN "direct_voter"."formatting_engine" IS 'Allows different formatting engines (i.e. wiki formats) to be used for "direct_voter"."comment"; Automatically set to NULL by trigger, if "comment" is set to NULL';
   5.192 +COMMENT ON COLUMN "direct_voter"."comment"           IS 'Is to be set or updated by the frontend, if comment was inserted or updated AFTER the issue has been closed. Otherwise it shall be set to NULL.';
   5.193 +
   5.194 +CREATE TABLE "rendered_voter_comment" (
   5.195 +        PRIMARY KEY ("issue_id", "member_id", "format"),
   5.196 +        FOREIGN KEY ("issue_id", "member_id")
   5.197 +          REFERENCES "direct_voter" ("issue_id", "member_id")
   5.198 +          ON DELETE CASCADE ON UPDATE CASCADE,
   5.199 +        "issue_id"              INT4,
   5.200 +        "member_id"             INT4,
   5.201 +        "format"                TEXT,
   5.202 +        "content"               TEXT            NOT NULL );
   5.203 +
   5.204 +COMMENT ON TABLE "rendered_voter_comment" IS 'This table may be used by frontends to cache "rendered" voter comments (e.g. HTML output generated from wiki text)';
   5.205 +
   5.206 +DROP TABLE "rendered_issue_comment";
   5.207 +DROP TABLE "issue_comment";
   5.208 +DROP TABLE "rendered_voting_comment";
   5.209 +DROP TABLE "voting_comment";
   5.210 +
   5.211 +CREATE FUNCTION "voter_comment_fields_only_set_when_voter_comment_is_set_trigger"()
   5.212 +  RETURNS TRIGGER
   5.213 +  LANGUAGE 'plpgsql' VOLATILE AS $$
   5.214 +    BEGIN
   5.215 +      IF NEW."comment" ISNULL THEN
   5.216 +        NEW."comment_changed" := NULL;
   5.217 +        NEW."formatting_engine" := NULL;
   5.218 +      END IF;
   5.219 +      RETURN NEW;
   5.220 +    END;
   5.221 +  $$;
   5.222 +
   5.223 +CREATE TRIGGER "voter_comment_fields_only_set_when_voter_comment_is_set"
   5.224 +  BEFORE INSERT OR UPDATE ON "direct_voter"
   5.225 +  FOR EACH ROW EXECUTE PROCEDURE
   5.226 +  "voter_comment_fields_only_set_when_voter_comment_is_set_trigger"();
   5.227 +
   5.228 +COMMENT ON FUNCTION "voter_comment_fields_only_set_when_voter_comment_is_set_trigger"() IS 'Implementation of trigger "voter_comment_fields_only_set_when_voter_comment_is_set" ON table "direct_voter"';
   5.229 +COMMENT ON TRIGGER "voter_comment_fields_only_set_when_voter_comment_is_set" ON "direct_voter" IS 'If "comment" is set to NULL, then other comment related fields are also set to NULL.';
   5.230 +
   5.231 +CREATE OR REPLACE FUNCTION "forbid_changes_on_closed_issue_trigger"()
   5.232 +  RETURNS TRIGGER
   5.233 +  LANGUAGE 'plpgsql' VOLATILE AS $$
   5.234 +    DECLARE
   5.235 +      "issue_id_v" "issue"."id"%TYPE;
   5.236 +      "issue_row"  "issue"%ROWTYPE;
   5.237 +    BEGIN
   5.238 +      IF TG_RELID = 'direct_voter'::regclass AND TG_OP = 'UPDATE' THEN
   5.239 +        IF
   5.240 +          OLD."issue_id"  = NEW."issue_id"  AND
   5.241 +          OLD."member_id" = NEW."member_id" AND
   5.242 +          OLD."weight"    = NEW."weight"
   5.243 +        THEN
   5.244 +          RETURN NULL;  -- allows changing of voter comment
   5.245 +        END IF;
   5.246 +      END IF;
   5.247 +      IF TG_OP = 'DELETE' THEN
   5.248 +        "issue_id_v" := OLD."issue_id";
   5.249 +      ELSE
   5.250 +        "issue_id_v" := NEW."issue_id";
   5.251 +      END IF;
   5.252 +      SELECT INTO "issue_row" * FROM "issue"
   5.253 +        WHERE "id" = "issue_id_v" FOR SHARE;
   5.254 +      IF "issue_row"."closed" NOTNULL THEN
   5.255 +        RAISE EXCEPTION 'Tried to modify data belonging to a closed issue.';
   5.256 +      END IF;
   5.257 +      RETURN NULL;
   5.258 +    END;
   5.259 +  $$;
   5.260 +
   5.261 +CREATE OR REPLACE FUNCTION "close_voting"("issue_id_p" "issue"."id"%TYPE)
   5.262 +  RETURNS VOID
   5.263 +  LANGUAGE 'plpgsql' VOLATILE AS $$
   5.264 +    DECLARE
   5.265 +      "area_id_v"   "area"."id"%TYPE;
   5.266 +      "unit_id_v"   "unit"."id"%TYPE;
   5.267 +      "member_id_v" "member"."id"%TYPE;
   5.268 +    BEGIN
   5.269 +      PERFORM "lock_issue"("issue_id_p");
   5.270 +      SELECT "area_id" INTO "area_id_v" FROM "issue" WHERE "id" = "issue_id_p";
   5.271 +      SELECT "unit_id" INTO "unit_id_v" FROM "area"  WHERE "id" = "area_id_v";
   5.272 +      -- delete timestamp of voting comment:
   5.273 +      UPDATE "direct_voter" SET "comment_changed" = NULL
   5.274 +        WHERE "issue_id" = "issue_id_p";
   5.275 +      -- delete delegating votes (in cases of manual reset of issue state):
   5.276 +      DELETE FROM "delegating_voter"
   5.277 +        WHERE "issue_id" = "issue_id_p";
   5.278 +      -- delete votes from non-privileged voters:
   5.279 +      DELETE FROM "direct_voter"
   5.280 +        USING (
   5.281 +          SELECT
   5.282 +            "direct_voter"."member_id"
   5.283 +          FROM "direct_voter"
   5.284 +          JOIN "member" ON "direct_voter"."member_id" = "member"."id"
   5.285 +          LEFT JOIN "privilege"
   5.286 +          ON "privilege"."unit_id" = "unit_id_v"
   5.287 +          AND "privilege"."member_id" = "direct_voter"."member_id"
   5.288 +          WHERE "direct_voter"."issue_id" = "issue_id_p" AND (
   5.289 +            "member"."active" = FALSE OR
   5.290 +            "privilege"."voting_right" ISNULL OR
   5.291 +            "privilege"."voting_right" = FALSE
   5.292 +          )
   5.293 +        ) AS "subquery"
   5.294 +        WHERE "direct_voter"."issue_id" = "issue_id_p"
   5.295 +        AND "direct_voter"."member_id" = "subquery"."member_id";
   5.296 +      -- consider delegations:
   5.297 +      UPDATE "direct_voter" SET "weight" = 1
   5.298 +        WHERE "issue_id" = "issue_id_p";
   5.299 +      PERFORM "add_vote_delegations"("issue_id_p");
   5.300 +      -- set voter count and mark issue as being calculated:
   5.301 +      UPDATE "issue" SET
   5.302 +        "state"  = 'calculation',
   5.303 +        "closed" = now(),
   5.304 +        "voter_count" = (
   5.305 +          SELECT coalesce(sum("weight"), 0)
   5.306 +          FROM "direct_voter" WHERE "issue_id" = "issue_id_p"
   5.307 +        )
   5.308 +        WHERE "id" = "issue_id_p";
   5.309 +      -- materialize battle_view:
   5.310 +      -- NOTE: "closed" column of issue must be set at this point
   5.311 +      DELETE FROM "battle" WHERE "issue_id" = "issue_id_p";
   5.312 +      INSERT INTO "battle" (
   5.313 +        "issue_id",
   5.314 +        "winning_initiative_id", "losing_initiative_id",
   5.315 +        "count"
   5.316 +      ) SELECT
   5.317 +        "issue_id",
   5.318 +        "winning_initiative_id", "losing_initiative_id",
   5.319 +        "count"
   5.320 +        FROM "battle_view" WHERE "issue_id" = "issue_id_p";
   5.321 +      -- copy "positive_votes" and "negative_votes" from "battle" table:
   5.322 +      UPDATE "initiative" SET
   5.323 +        "positive_votes" = "battle_win"."count",
   5.324 +        "negative_votes" = "battle_lose"."count"
   5.325 +        FROM "battle" AS "battle_win", "battle" AS "battle_lose"
   5.326 +        WHERE
   5.327 +          "battle_win"."issue_id" = "issue_id_p" AND
   5.328 +          "battle_win"."winning_initiative_id" = "initiative"."id" AND
   5.329 +          "battle_win"."losing_initiative_id" ISNULL AND
   5.330 +          "battle_lose"."issue_id" = "issue_id_p" AND
   5.331 +          "battle_lose"."losing_initiative_id" = "initiative"."id" AND
   5.332 +          "battle_lose"."winning_initiative_id" ISNULL;
   5.333 +    END;
   5.334 +  $$;
   5.335 +
   5.336 +CREATE OR REPLACE FUNCTION "clean_issue"("issue_id_p" "issue"."id"%TYPE)
   5.337 +  RETURNS VOID
   5.338 +  LANGUAGE 'plpgsql' VOLATILE AS $$
   5.339 +    DECLARE
   5.340 +      "issue_row" "issue"%ROWTYPE;
   5.341 +    BEGIN
   5.342 +      SELECT * INTO "issue_row"
   5.343 +        FROM "issue" WHERE "id" = "issue_id_p"
   5.344 +        FOR UPDATE;
   5.345 +      IF "issue_row"."cleaned" ISNULL THEN
   5.346 +        UPDATE "issue" SET
   5.347 +          "state"           = 'voting',
   5.348 +          "closed"          = NULL,
   5.349 +          "ranks_available" = FALSE
   5.350 +          WHERE "id" = "issue_id_p";
   5.351 +        DELETE FROM "delegating_voter"
   5.352 +          WHERE "issue_id" = "issue_id_p";
   5.353 +        DELETE FROM "direct_voter"
   5.354 +          WHERE "issue_id" = "issue_id_p";
   5.355 +        DELETE FROM "delegating_interest_snapshot"
   5.356 +          WHERE "issue_id" = "issue_id_p";
   5.357 +        DELETE FROM "direct_interest_snapshot"
   5.358 +          WHERE "issue_id" = "issue_id_p";
   5.359 +        DELETE FROM "delegating_population_snapshot"
   5.360 +          WHERE "issue_id" = "issue_id_p";
   5.361 +        DELETE FROM "direct_population_snapshot"
   5.362 +          WHERE "issue_id" = "issue_id_p";
   5.363 +        DELETE FROM "non_voter"
   5.364 +          WHERE "issue_id" = "issue_id_p";
   5.365 +        DELETE FROM "delegation"
   5.366 +          WHERE "issue_id" = "issue_id_p";
   5.367 +        DELETE FROM "supporter"
   5.368 +          WHERE "issue_id" = "issue_id_p";
   5.369 +        UPDATE "issue" SET
   5.370 +          "state"           = "issue_row"."state",
   5.371 +          "closed"          = "issue_row"."closed",
   5.372 +          "ranks_available" = "issue_row"."ranks_available",
   5.373 +          "cleaned"         = now()
   5.374 +          WHERE "id" = "issue_id_p";
   5.375 +      END IF;
   5.376 +      RETURN;
   5.377 +    END;
   5.378 +  $$;
   5.379 +
   5.380 +
   5.381 +-- "non_voter" deletes "direct_voter" and vice versa
   5.382 +
   5.383 +CREATE FUNCTION "non_voter_deletes_direct_voter_trigger"()
   5.384 +  RETURNS TRIGGER
   5.385 +  LANGUAGE 'plpgsql' VOLATILE AS $$
   5.386 +    BEGIN
   5.387 +      DELETE FROM "direct_voter"
   5.388 +        WHERE "issue_id" = NEW."issue_id" AND "member_id" = NEW."member_id";
   5.389 +      RETURN NULL;
   5.390 +    END;
   5.391 +  $$;
   5.392 +
   5.393 +CREATE TRIGGER "non_voter_deletes_direct_voter"
   5.394 +  AFTER INSERT OR UPDATE ON "non_voter"
   5.395 +  FOR EACH ROW EXECUTE PROCEDURE
   5.396 +  "non_voter_deletes_direct_voter_trigger"();
   5.397 +
   5.398 +COMMENT ON FUNCTION "non_voter_deletes_direct_voter_trigger"()     IS 'Implementation of trigger "non_voter_deletes_direct_voter" on table "non_voter"';
   5.399 +COMMENT ON TRIGGER "non_voter_deletes_direct_voter" ON "non_voter" IS 'An entry in the "non_voter" table deletes an entry in the "direct_voter" table (and vice versa due to trigger "direct_voter_deletes_non_voter" on table "direct_voter")';
   5.400 +
   5.401 +CREATE FUNCTION "direct_voter_deletes_non_voter_trigger"()
   5.402 +  RETURNS TRIGGER
   5.403 +  LANGUAGE 'plpgsql' VOLATILE AS $$
   5.404 +    BEGIN
   5.405 +      DELETE FROM "non_voter"
   5.406 +        WHERE "issue_id" = NEW."issue_id" AND "member_id" = NEW."member_id";
   5.407 +      RETURN NULL;
   5.408 +    END;
   5.409 +  $$;
   5.410 +
   5.411 +CREATE TRIGGER "direct_voter_deletes_non_voter"
   5.412 +  AFTER INSERT OR UPDATE ON "direct_voter"
   5.413 +  FOR EACH ROW EXECUTE PROCEDURE
   5.414 +  "direct_voter_deletes_non_voter_trigger"();
   5.415 +
   5.416 +COMMENT ON FUNCTION "direct_voter_deletes_non_voter_trigger"()        IS 'Implementation of trigger "direct_voter_deletes_non_voter" on table "direct_voter"';
   5.417 +COMMENT ON TRIGGER "direct_voter_deletes_non_voter" ON "direct_voter" IS 'An entry in the "direct_voter" table deletes an entry in the "non_voter" table (and vice versa due to trigger "non_voter_deletes_direct_voter" on table "non_voter")';
   5.418 +
   5.419 +
   5.420 +-- different locking levels and different locking order to avoid deadlocks
   5.421 +
   5.422 +CREATE OR REPLACE FUNCTION "lock_issue"
   5.423 +  ( "issue_id_p" "issue"."id"%TYPE )
   5.424 +  RETURNS VOID
   5.425 +  LANGUAGE 'plpgsql' VOLATILE AS $$
   5.426 +    BEGIN
   5.427 +      -- The following locking order is used:
   5.428 +      -- 1st) row-level lock on the issue
   5.429 +      -- 2nd) table-level locks in order of occurrence in the core.sql file
   5.430 +      PERFORM NULL FROM "issue" WHERE "id" = "issue_id_p" FOR UPDATE;
   5.431 +      -- NOTE: The row-level exclusive lock in combination with the
   5.432 +      -- share_row_lock_issue(_via_initiative)_trigger functions (which
   5.433 +      -- acquire a row-level share lock on the issue) ensure that no data
   5.434 +      -- is changed, which could affect calculation of snapshots or
   5.435 +      -- counting of votes. Table "delegation" must be table-level-locked,
   5.436 +      -- as it also contains issue- and global-scope delegations.
   5.437 +      PERFORM NULL FROM "member" WHERE "active" FOR SHARE;
   5.438 +      -- NOTE: As we later cause implicit row-level share locks on many
   5.439 +      -- active members, we lock them before locking any other table
   5.440 +      -- to avoid deadlocks
   5.441 +      LOCK TABLE "member"     IN SHARE MODE;
   5.442 +      LOCK TABLE "privilege"  IN SHARE MODE;
   5.443 +      LOCK TABLE "membership" IN SHARE MODE;
   5.444 +      LOCK TABLE "policy"     IN SHARE MODE;
   5.445 +      LOCK TABLE "delegation" IN SHARE MODE;
   5.446 +      LOCK TABLE "direct_population_snapshot"     IN EXCLUSIVE MODE;
   5.447 +      LOCK TABLE "delegating_population_snapshot" IN EXCLUSIVE MODE;
   5.448 +      LOCK TABLE "direct_interest_snapshot"       IN EXCLUSIVE MODE;
   5.449 +      LOCK TABLE "delegating_interest_snapshot"   IN EXCLUSIVE MODE;
   5.450 +      LOCK TABLE "direct_supporter_snapshot"      IN EXCLUSIVE MODE;
   5.451 +      RETURN;
   5.452 +    END;
   5.453 +  $$;
   5.454 +
   5.455 +
   5.456 +-- new comment on function "delete_private_data"()
   5.457 +
   5.458 +COMMENT ON FUNCTION "delete_private_data"() IS 'Used by lf_export script. DO NOT USE on productive database, but only on a copy! This function deletes all data which should not be publicly available, and can be used to create a database dump for publication. See source code to see which data is deleted. If you need a different behaviour, copy this function and modify lf_export accordingly, to avoid data-leaks after updating.';
   5.459 +
   5.460 +
   5.461 +COMMIT;

Impressum / About Us