liquid_feedback_core

annotate core.sql @ 2:d45919d791ff

Version beta3

Minor bugfix: Autocreated entries in supporter table refer to the current draft of the initiative instead of throwing an error
author jbe
date Sat Nov 07 12:00:00 2009 +0100 (2009-11-07)
parents 23092eb00e16
children 3da35844c874
rev   line source
jbe@0 1
jbe@0 2 CREATE LANGUAGE plpgsql; -- Triggers are implemented in PL/pgSQL
jbe@0 3
jbe@0 4 -- NOTE: In PostgreSQL every UNIQUE constraint implies creation of an index
jbe@0 5
jbe@0 6 BEGIN;
jbe@0 7
jbe@0 8
jbe@0 9
jbe@0 10 -------------------------
jbe@0 11 -- Tables and indicies --
jbe@0 12 -------------------------
jbe@0 13
jbe@0 14
jbe@0 15 CREATE TABLE "member" (
jbe@0 16 "id" SERIAL4 PRIMARY KEY,
jbe@0 17 "login" TEXT NOT NULL UNIQUE,
jbe@0 18 "password" TEXT,
jbe@0 19 "active" BOOLEAN NOT NULL DEFAULT TRUE,
jbe@0 20 "admin" BOOLEAN NOT NULL DEFAULT FALSE,
jbe@0 21 "name" TEXT,
jbe@0 22 "ident_number" TEXT UNIQUE );
jbe@0 23 CREATE INDEX "member_active_idx" ON "member" ("active");
jbe@0 24
jbe@0 25 COMMENT ON TABLE "member" IS 'Users of the system, e.g. members of an organization';
jbe@0 26
jbe@0 27 COMMENT ON COLUMN "member"."login" IS 'Login name';
jbe@0 28 COMMENT ON COLUMN "member"."password" IS 'Password (preferably as crypto-hash, depending on the frontend or access layer)';
jbe@0 29 COMMENT ON COLUMN "member"."active" IS 'Inactive members can not login and their supports/votes are not counted by the system.';
jbe@0 30 COMMENT ON COLUMN "member"."ident_number" IS 'Additional information about the members idenficication number within the organization';
jbe@0 31
jbe@0 32
jbe@0 33 CREATE TABLE "contact" (
jbe@0 34 PRIMARY KEY ("member_id", "other_member_id"),
jbe@0 35 "member_id" INT4 REFERENCES "member" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@0 36 "other_member_id" INT4 REFERENCES "member" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@0 37 "public" BOOLEAN NOT NULL DEFAULT FALSE );
jbe@0 38
jbe@0 39 COMMENT ON TABLE "contact" IS 'Contact lists';
jbe@0 40
jbe@0 41 COMMENT ON COLUMN "contact"."member_id" IS 'Member having the contact list';
jbe@0 42 COMMENT ON COLUMN "contact"."other_member_id" IS 'Member referenced in the contact list';
jbe@0 43 COMMENT ON COLUMN "contact"."public" IS 'TRUE = display contact publically';
jbe@0 44
jbe@0 45
jbe@0 46 CREATE TABLE "session" (
jbe@0 47 "ident" TEXT PRIMARY KEY,
jbe@0 48 "additional_secret" TEXT,
jbe@0 49 "expiry" TIMESTAMPTZ NOT NULL DEFAULT now() + '24 hours',
jbe@0 50 "member_id" INT8 REFERENCES "member" ("id") ON DELETE SET NULL,
jbe@0 51 "lang" TEXT );
jbe@0 52 CREATE INDEX "session_expiry_idx" ON "session" ("expiry");
jbe@0 53
jbe@0 54 COMMENT ON TABLE "session" IS 'Sessions, i.e. for a web-frontend';
jbe@0 55
jbe@0 56 COMMENT ON COLUMN "session"."ident" IS 'Secret session identifier (i.e. random string)';
jbe@0 57 COMMENT ON COLUMN "session"."additional_secret" IS 'Additional field to store a secret, which can be used against CSRF attacks';
jbe@0 58 COMMENT ON COLUMN "session"."member_id" IS 'Reference to member, who is logged in';
jbe@0 59 COMMENT ON COLUMN "session"."lang" IS 'Language code of the selected language';
jbe@0 60
jbe@0 61
jbe@0 62 CREATE TABLE "policy" (
jbe@0 63 "id" SERIAL4 PRIMARY KEY,
jbe@0 64 "active" BOOLEAN NOT NULL DEFAULT TRUE,
jbe@0 65 "name" TEXT NOT NULL UNIQUE,
jbe@0 66 "description" TEXT NOT NULL DEFAULT '',
jbe@0 67 "admission_time" INTERVAL NOT NULL,
jbe@0 68 "discussion_time" INTERVAL NOT NULL,
jbe@0 69 "voting_time" INTERVAL NOT NULL,
jbe@0 70 "issue_quorum_num" INT4 NOT NULL,
jbe@0 71 "issue_quorum_den" INT4 NOT NULL,
jbe@0 72 "initiative_quorum_num" INT4 NOT NULL,
jbe@0 73 "initiative_quorum_den" INT4 NOT NULL );
jbe@0 74 CREATE INDEX "policy_active_idx" ON "policy" ("active");
jbe@0 75
jbe@0 76 COMMENT ON TABLE "policy" IS 'Policies for a particular proceeding type (timelimits, quorum)';
jbe@0 77
jbe@0 78 COMMENT ON COLUMN "policy"."active" IS 'TRUE = policy can be used for new issues';
jbe@0 79 COMMENT ON COLUMN "policy"."admission_time" IS 'Maximum time an issue stays open without being "accepted"';
jbe@0 80 COMMENT ON COLUMN "policy"."discussion_time" IS 'Regular time until an issue is "frozen" after being "accepted"';
jbe@0 81 COMMENT ON COLUMN "policy"."voting_time" IS 'Time after an issue is "frozen" but not "closed"';
jbe@0 82 COMMENT ON COLUMN "policy"."issue_quorum_num" IS 'Numerator of quorum to be reached by one initiative of an issue to be "accepted"';
jbe@0 83 COMMENT ON COLUMN "policy"."issue_quorum_den" IS 'Denominator of quorum to be reached by one initiative of an issue to be "accepted"';
jbe@0 84 COMMENT ON COLUMN "policy"."initiative_quorum_num" IS 'Numerator of quorum to be reached by an initiative to be "admitted" for voting';
jbe@0 85 COMMENT ON COLUMN "policy"."initiative_quorum_den" IS 'Denominator of quorum to be reached by an initiative to be "admitted" for voting';
jbe@0 86
jbe@0 87
jbe@0 88 CREATE TABLE "area" (
jbe@0 89 "id" SERIAL4 PRIMARY KEY,
jbe@0 90 "active" BOOLEAN NOT NULL DEFAULT TRUE,
jbe@0 91 "name" TEXT NOT NULL,
jbe@0 92 "description" TEXT NOT NULL DEFAULT '' );
jbe@0 93 CREATE INDEX "area_active_idx" ON "area" ("active");
jbe@0 94
jbe@0 95 COMMENT ON TABLE "area" IS 'Subject areas';
jbe@0 96
jbe@0 97 COMMENT ON COLUMN "area"."active" IS 'TRUE means new issues can be created in this area';
jbe@0 98
jbe@0 99
jbe@0 100 CREATE TABLE "issue" (
jbe@0 101 "id" SERIAL4 PRIMARY KEY,
jbe@0 102 "area_id" INT4 NOT NULL REFERENCES "area" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@0 103 "policy_id" INT4 NOT NULL REFERENCES "policy" ("id") ON DELETE RESTRICT ON UPDATE CASCADE,
jbe@0 104 "created" TIMESTAMPTZ NOT NULL DEFAULT now(),
jbe@0 105 "accepted" TIMESTAMPTZ,
jbe@0 106 "frozen" TIMESTAMPTZ,
jbe@0 107 "closed" TIMESTAMPTZ,
jbe@0 108 "ranks_available" BOOLEAN NOT NULL DEFAULT FALSE,
jbe@0 109 "snapshot" TIMESTAMPTZ,
jbe@0 110 "population" INT4,
jbe@0 111 "vote_now" INT4,
jbe@0 112 "vote_later" INT4,
jbe@0 113 CONSTRAINT "valid_state" CHECK (
jbe@0 114 ("accepted" ISNULL AND "frozen" ISNULL AND "closed" ISNULL AND "ranks_available" = FALSE) OR
jbe@0 115 ("accepted" ISNULL AND "frozen" ISNULL AND "closed" NOTNULL AND "ranks_available" = FALSE) OR
jbe@0 116 ("accepted" NOTNULL AND "frozen" ISNULL AND "closed" ISNULL AND "ranks_available" = FALSE) OR
jbe@0 117 ("accepted" NOTNULL AND "frozen" NOTNULL AND "closed" ISNULL AND "ranks_available" = FALSE) OR
jbe@0 118 ("accepted" NOTNULL AND "frozen" NOTNULL AND "closed" NOTNULL AND "ranks_available" = FALSE) OR
jbe@0 119 ("accepted" NOTNULL AND "frozen" NOTNULL AND "closed" NOTNULL AND "ranks_available" = TRUE) ),
jbe@0 120 CONSTRAINT "state_change_order" CHECK ("created" <= "accepted" AND "accepted" <= "frozen" AND "frozen" <= "closed"),
jbe@0 121 CONSTRAINT "last_snapshot_on_freeze" CHECK ("snapshot" = "frozen"), -- NOTE: snapshot can be set, while frozen is NULL yet
jbe@0 122 CONSTRAINT "freeze_requires_snapshot" CHECK ("frozen" ISNULL OR "snapshot" NOTNULL) );
jbe@0 123 CREATE INDEX "issue_area_id_idx" ON "issue" ("area_id");
jbe@0 124 CREATE INDEX "issue_policy_id_idx" ON "issue" ("policy_id");
jbe@0 125 CREATE INDEX "issue_created_idx_open" ON "issue" ("created") WHERE "closed" ISNULL;
jbe@0 126
jbe@0 127 COMMENT ON TABLE "issue" IS 'Groups of initiatives';
jbe@0 128
jbe@0 129 COMMENT ON COLUMN "issue"."accepted" IS 'Point in time, when one initiative of issue reached the "issue_quorum"';
jbe@0 130 COMMENT ON COLUMN "issue"."frozen" IS 'Point in time, when "discussion_time" has elapsed, or members voted for voting.';
jbe@0 131 COMMENT ON COLUMN "issue"."closed" IS 'Point in time, when "admission_time" or "voting_time" is over, and issue is no longer active';
jbe@0 132 COMMENT ON COLUMN "issue"."ranks_available" IS 'TRUE = ranks have been calculated';
jbe@0 133 COMMENT ON COLUMN "issue"."snapshot" IS 'Point in time, when snapshot tables have been updated and "population", "vote_now", "vote_later" and *_count values were precalculated';
jbe@0 134 COMMENT ON COLUMN "issue"."population" IS 'Calculated from table "direct_population_snapshot"';
jbe@0 135 COMMENT ON COLUMN "issue"."vote_now" IS 'Calculated from table "direct_interest_snapshot"';
jbe@0 136 COMMENT ON COLUMN "issue"."vote_later" IS 'Calculated from table "direct_interest_snapshot"';
jbe@0 137
jbe@0 138
jbe@0 139 CREATE TABLE "initiative" (
jbe@0 140 UNIQUE ("issue_id", "id"), -- index needed for foreign-key on table "vote"
jbe@0 141 "issue_id" INT4 NOT NULL REFERENCES "issue" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@0 142 "id" SERIAL4 PRIMARY KEY,
jbe@0 143 "name" TEXT NOT NULL,
jbe@0 144 "created" TIMESTAMPTZ NOT NULL DEFAULT now(),
jbe@0 145 "revoked" TIMESTAMPTZ,
jbe@0 146 "admitted" BOOLEAN,
jbe@0 147 "supporter_count" INT4,
jbe@0 148 "informed_supporter_count" INT4,
jbe@0 149 "satisfied_supporter_count" INT4,
jbe@0 150 "satisfied_informed_supporter_count" INT4,
jbe@0 151 "positive_votes" INT4,
jbe@0 152 "negative_votes" INT4,
jbe@0 153 "rank" INT4,
jbe@0 154 CONSTRAINT "revoked_initiatives_cant_be_admitted"
jbe@0 155 CHECK ("revoked" ISNULL OR "admitted" ISNULL),
jbe@0 156 CONSTRAINT "non_admitted_initiatives_cant_contain_voting_results"
jbe@0 157 CHECK ("admitted" = TRUE OR ("positive_votes" ISNULL AND "negative_votes" ISNULL AND "rank" ISNULL)) );
jbe@0 158
jbe@0 159 COMMENT ON TABLE "initiative" IS 'Group of members publishing drafts for resolutions to be passed';
jbe@0 160
jbe@0 161 COMMENT ON COLUMN "initiative"."revoked" IS 'Point in time, when one initiator decided to revoke the initiative';
jbe@0 162 COMMENT ON COLUMN "initiative"."admitted" IS 'True, if initiative reaches the "initiative_quorum" when freezing the issue';
jbe@0 163 COMMENT ON COLUMN "initiative"."supporter_count" IS 'Calculated from table "direct_supporter_snapshot"';
jbe@0 164 COMMENT ON COLUMN "initiative"."informed_supporter_count" IS 'Calculated from table "direct_supporter_snapshot"';
jbe@0 165 COMMENT ON COLUMN "initiative"."satisfied_supporter_count" IS 'Calculated from table "direct_supporter_snapshot"';
jbe@0 166 COMMENT ON COLUMN "initiative"."satisfied_informed_supporter_count" IS 'Calculated from table "direct_supporter_snapshot"';
jbe@0 167 COMMENT ON COLUMN "initiative"."positive_votes" IS 'Calculated from table "direct_voter"';
jbe@0 168 COMMENT ON COLUMN "initiative"."negative_votes" IS 'Calculated from table "direct_voter"';
jbe@0 169 COMMENT ON COLUMN "initiative"."rank" IS 'Rank of approved initiatives (winner is 1), calculated from table "direct_voter"';
jbe@0 170
jbe@0 171
jbe@0 172 CREATE TABLE "draft" (
jbe@0 173 UNIQUE ("initiative_id", "id"), -- index needed for foreign-key on table "supporter"
jbe@0 174 "initiative_id" INT4 NOT NULL REFERENCES "initiative" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@0 175 "id" SERIAL8 PRIMARY KEY,
jbe@0 176 "created" TIMESTAMPTZ NOT NULL DEFAULT now(),
jbe@0 177 "author_id" INT4 NOT NULL REFERENCES "member" ("id") ON DELETE RESTRICT ON UPDATE CASCADE,
jbe@0 178 "content" TEXT NOT NULL );
jbe@0 179
jbe@0 180 COMMENT ON TABLE "draft" IS 'Drafts of initiatives to solve issues';
jbe@0 181
jbe@0 182
jbe@0 183 CREATE TABLE "suggestion" (
jbe@0 184 UNIQUE ("initiative_id", "id"), -- index needed for foreign-key on table "opinion"
jbe@0 185 "initiative_id" INT4 NOT NULL REFERENCES "initiative" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@0 186 "id" SERIAL8 PRIMARY KEY,
jbe@0 187 "created" TIMESTAMPTZ NOT NULL DEFAULT now(),
jbe@0 188 "author_id" INT4 NOT NULL REFERENCES "member" ("id") ON DELETE RESTRICT ON UPDATE CASCADE,
jbe@0 189 "name" TEXT NOT NULL,
jbe@0 190 "description" TEXT NOT NULL DEFAULT '',
jbe@0 191 "minus2_unfulfilled_count" INT4,
jbe@0 192 "minus2_fulfilled_count" INT4,
jbe@0 193 "minus1_unfulfilled_count" INT4,
jbe@0 194 "minus1_fulfilled_count" INT4,
jbe@0 195 "plus1_unfulfilled_count" INT4,
jbe@0 196 "plus1_fulfilled_count" INT4,
jbe@0 197 "plus2_unfulfilled_count" INT4,
jbe@0 198 "plus2_fulfilled_count" INT4 );
jbe@0 199
jbe@0 200 COMMENT ON TABLE "suggestion" IS 'Suggestions to initiators, to change the current draft';
jbe@0 201
jbe@0 202 COMMENT ON COLUMN "suggestion"."minus2_unfulfilled_count" IS 'Calculated from table "direct_supporter_snapshot", not requiring informed supporters';
jbe@0 203 COMMENT ON COLUMN "suggestion"."minus2_fulfilled_count" IS 'Calculated from table "direct_supporter_snapshot", not requiring informed supporters';
jbe@0 204 COMMENT ON COLUMN "suggestion"."minus1_unfulfilled_count" IS 'Calculated from table "direct_supporter_snapshot", not requiring informed supporters';
jbe@0 205 COMMENT ON COLUMN "suggestion"."minus1_fulfilled_count" IS 'Calculated from table "direct_supporter_snapshot", not requiring informed supporters';
jbe@0 206 COMMENT ON COLUMN "suggestion"."plus1_unfulfilled_count" IS 'Calculated from table "direct_supporter_snapshot", not requiring informed supporters';
jbe@0 207 COMMENT ON COLUMN "suggestion"."plus1_fulfilled_count" IS 'Calculated from table "direct_supporter_snapshot", not requiring informed supporters';
jbe@0 208 COMMENT ON COLUMN "suggestion"."plus2_unfulfilled_count" IS 'Calculated from table "direct_supporter_snapshot", not requiring informed supporters';
jbe@0 209 COMMENT ON COLUMN "suggestion"."plus2_fulfilled_count" IS 'Calculated from table "direct_supporter_snapshot", not requiring informed supporters';
jbe@0 210
jbe@0 211
jbe@0 212 CREATE TABLE "membership" (
jbe@0 213 PRIMARY KEY ("area_id", "member_id"),
jbe@0 214 "area_id" INT4 REFERENCES "area" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@0 215 "member_id" INT4 REFERENCES "member" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@0 216 "autoreject" BOOLEAN NOT NULL DEFAULT FALSE );
jbe@0 217 CREATE INDEX "membership_member_id_idx" ON "membership" ("member_id");
jbe@0 218
jbe@0 219 COMMENT ON TABLE "membership" IS 'Interest of members in topic areas';
jbe@0 220
jbe@0 221 COMMENT ON COLUMN "membership"."autoreject" IS 'TRUE = member votes against all initiatives in case of not explicitly taking part in the voting procedure; If there exists an "interest" entry, the interest entry has precedence';
jbe@0 222
jbe@0 223
jbe@0 224 CREATE TABLE "interest" (
jbe@0 225 PRIMARY KEY ("issue_id", "member_id"),
jbe@0 226 "issue_id" INT4 REFERENCES "issue" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@0 227 "member_id" INT4 REFERENCES "member" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@0 228 "autoreject" BOOLEAN NOT NULL,
jbe@0 229 "voting_requested" BOOLEAN );
jbe@0 230 CREATE INDEX "interest_member_id_idx" ON "interest" ("member_id");
jbe@0 231
jbe@0 232 COMMENT ON TABLE "interest" IS 'Interest of members in a particular issue';
jbe@0 233
jbe@0 234 COMMENT ON COLUMN "interest"."autoreject" IS 'TRUE = member votes against all initiatives in case of not explicitly taking part in the voting procedure';
jbe@0 235 COMMENT ON COLUMN "interest"."voting_requested" IS 'TRUE = member wants to vote now, FALSE = member wants to vote later, NULL = policy rules should apply';
jbe@0 236
jbe@0 237
jbe@0 238 CREATE TABLE "initiator" (
jbe@0 239 PRIMARY KEY ("initiative_id", "member_id"),
jbe@0 240 "initiative_id" INT4 REFERENCES "initiative" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@0 241 "member_id" INT4 REFERENCES "member" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@0 242 "accepted" BOOLEAN NOT NULL DEFAULT TRUE );
jbe@0 243 CREATE INDEX "initiator_member_id_idx" ON "initiator" ("member_id");
jbe@0 244
jbe@0 245 COMMENT ON TABLE "initiator" IS 'Members who are allowed to post new drafts';
jbe@0 246
jbe@0 247 COMMENT ON COLUMN "initiator"."accepted" IS 'If "accepted" = FALSE, then the member was invited to be a co-initiator, but has not answered yet.';
jbe@0 248
jbe@0 249
jbe@0 250 CREATE TABLE "supporter" (
jbe@0 251 "issue_id" INT4 NOT NULL,
jbe@0 252 PRIMARY KEY ("initiative_id", "member_id"),
jbe@0 253 "initiative_id" INT4,
jbe@0 254 "member_id" INT4,
jbe@0 255 "draft_id" INT8 NOT NULL,
jbe@0 256 FOREIGN KEY ("issue_id", "member_id") REFERENCES "interest" ("issue_id", "member_id") ON DELETE RESTRICT ON UPDATE CASCADE,
jbe@0 257 FOREIGN KEY ("initiative_id", "draft_id") REFERENCES "draft" ("initiative_id", "id") ON DELETE CASCADE ON UPDATE CASCADE );
jbe@0 258 CREATE INDEX "supporter_member_id_idx" ON "supporter" ("member_id");
jbe@0 259
jbe@0 260 COMMENT ON TABLE "supporter" IS 'Members who support an initiative (conditionally)';
jbe@0 261
jbe@2 262 COMMENT ON COLUMN "supporter"."draft_id" IS 'Latest seen draft, defaults to current draft of the initiative (implemented by trigger "default_for_draft_id")';
jbe@0 263
jbe@0 264
jbe@0 265 CREATE TABLE "opinion" (
jbe@0 266 "initiative_id" INT4 NOT NULL,
jbe@0 267 PRIMARY KEY ("suggestion_id", "member_id"),
jbe@0 268 "suggestion_id" INT8,
jbe@0 269 "member_id" INT4,
jbe@0 270 "degree" INT2 NOT NULL CHECK ("degree" >= -2 AND "degree" <= 2 AND "degree" != 0),
jbe@0 271 "fulfilled" BOOLEAN NOT NULL DEFAULT FALSE,
jbe@0 272 FOREIGN KEY ("initiative_id", "suggestion_id") REFERENCES "suggestion" ("initiative_id", "id") ON DELETE RESTRICT ON UPDATE CASCADE,
jbe@0 273 FOREIGN KEY ("initiative_id", "member_id") REFERENCES "supporter" ("initiative_id", "member_id") ON DELETE CASCADE ON UPDATE CASCADE );
jbe@0 274 CREATE INDEX "opinion_member_id_idx" ON "opinion" ("member_id");
jbe@0 275
jbe@0 276 COMMENT ON TABLE "opinion" IS 'Opinion on suggestions (criticism related to initiatives)';
jbe@0 277
jbe@0 278 COMMENT ON COLUMN "opinion"."degree" IS '2 = fulfillment required for support; 1 = fulfillment desired; -1 = fulfillment unwanted; -2 = fulfillment cancels support';
jbe@0 279
jbe@0 280
jbe@0 281 CREATE TABLE "delegation" (
jbe@0 282 "id" SERIAL8 PRIMARY KEY,
jbe@0 283 "truster_id" INT4 NOT NULL REFERENCES "member" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@0 284 "trustee_id" INT4 NOT NULL REFERENCES "member" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@0 285 "area_id" INT4 REFERENCES "area" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@0 286 "issue_id" INT4 REFERENCES "issue" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@0 287 CONSTRAINT "cant_delegate_to_yourself" CHECK ("truster_id" != "trustee_id"),
jbe@0 288 CONSTRAINT "area_id_or_issue_id_can_be_set_but_not_both" CHECK ("area_id" ISNULL OR "issue_id" ISNULL),
jbe@0 289 UNIQUE ("area_id", "truster_id", "trustee_id"),
jbe@0 290 UNIQUE ("issue_id", "truster_id", "trustee_id") );
jbe@0 291 CREATE UNIQUE INDEX "delegation_default_truster_id_trustee_id_unique_idx"
jbe@0 292 ON "delegation" ("truster_id", "trustee_id")
jbe@0 293 WHERE "area_id" ISNULL AND "issue_id" ISNULL;
jbe@0 294 CREATE INDEX "delegation_truster_id_idx" ON "delegation" ("truster_id");
jbe@0 295 CREATE INDEX "delegation_trustee_id_idx" ON "delegation" ("trustee_id");
jbe@0 296
jbe@0 297 COMMENT ON TABLE "delegation" IS 'Delegation of vote-weight to other members';
jbe@0 298
jbe@0 299 COMMENT ON COLUMN "delegation"."area_id" IS 'Reference to area, if delegation is area-wide, otherwise NULL';
jbe@0 300 COMMENT ON COLUMN "delegation"."issue_id" IS 'Reference to issue, if delegation is issue-wide, otherwise NULL';
jbe@0 301
jbe@0 302
jbe@0 303 CREATE TYPE "snapshot_event" AS ENUM ('periodic', 'end_of_admission', 'end_of_discussion');
jbe@0 304
jbe@0 305 COMMENT ON TYPE "snapshot_event" IS 'Reason for snapshots: ''periodic'' = due to periodic recalculation, ''end_of_admission'' = saved state at end of admission period, ''end_of_discussion'' = saved state at end of discussion period';
jbe@0 306
jbe@0 307
jbe@0 308 CREATE TABLE "direct_population_snapshot" (
jbe@0 309 PRIMARY KEY ("issue_id", "event", "member_id"),
jbe@0 310 "issue_id" INT4 REFERENCES "issue" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@0 311 "event" "snapshot_event",
jbe@0 312 "member_id" INT4 REFERENCES "member" ("id") ON DELETE RESTRICT ON UPDATE CASCADE,
jbe@0 313 "weight" INT4,
jbe@0 314 "interest_exists" BOOLEAN NOT NULL );
jbe@0 315 CREATE INDEX "direct_population_snapshot_member_id_idx" ON "direct_population_snapshot" ("member_id");
jbe@0 316
jbe@0 317 COMMENT ON TABLE "direct_population_snapshot" IS 'Snapshot of active members having either a "membership" in the "area" or an "interest" in the "issue"';
jbe@0 318
jbe@0 319 COMMENT ON COLUMN "direct_population_snapshot"."event" IS 'Reason for snapshot, see "snapshot_event" type for details';
jbe@0 320 COMMENT ON COLUMN "direct_population_snapshot"."weight" IS 'Weight of member (1 or higher) according to "delegating_population_snapshot"';
jbe@0 321 COMMENT ON COLUMN "direct_population_snapshot"."interest_exists" IS 'TRUE if entry is due to interest in issue, FALSE if entry is only due to membership in area';
jbe@0 322
jbe@0 323
jbe@0 324 CREATE TABLE "delegating_population_snapshot" (
jbe@0 325 PRIMARY KEY ("issue_id", "event", "member_id"),
jbe@0 326 "issue_id" INT4 REFERENCES "issue" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@0 327 "event" "snapshot_event",
jbe@0 328 "member_id" INT4 REFERENCES "member" ("id") ON DELETE RESTRICT ON UPDATE CASCADE,
jbe@0 329 "delegate_member_ids" INT4[] NOT NULL );
jbe@0 330 CREATE INDEX "delegating_population_snapshot_member_id_idx" ON "delegating_population_snapshot" ("member_id");
jbe@0 331
jbe@0 332 COMMENT ON TABLE "direct_population_snapshot" IS 'Delegations increasing the weight of entries in the "direct_population_snapshot" table';
jbe@0 333
jbe@0 334 COMMENT ON COLUMN "delegating_population_snapshot"."event" IS 'Reason for snapshot, see "snapshot_event" type for details';
jbe@0 335 COMMENT ON COLUMN "delegating_population_snapshot"."member_id" IS 'Delegating member';
jbe@0 336 COMMENT ON COLUMN "delegating_population_snapshot"."delegate_member_ids" IS 'Chain of members who act as delegates; last entry referes to "member_id" column of table "direct_population_snapshot"';
jbe@0 337
jbe@0 338
jbe@0 339 CREATE TABLE "direct_interest_snapshot" (
jbe@0 340 PRIMARY KEY ("issue_id", "event", "member_id"),
jbe@0 341 "issue_id" INT4 REFERENCES "issue" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@0 342 "event" "snapshot_event",
jbe@0 343 "member_id" INT4 REFERENCES "member" ("id") ON DELETE RESTRICT ON UPDATE CASCADE,
jbe@0 344 "weight" INT4,
jbe@0 345 "voting_requested" BOOLEAN );
jbe@0 346 CREATE INDEX "direct_interest_snapshot_member_id_idx" ON "direct_interest_snapshot" ("member_id");
jbe@0 347
jbe@0 348 COMMENT ON TABLE "direct_interest_snapshot" IS 'Snapshot of active members having an "interest" in the "issue"';
jbe@0 349
jbe@0 350 COMMENT ON COLUMN "direct_interest_snapshot"."event" IS 'Reason for snapshot, see "snapshot_event" type for details';
jbe@0 351 COMMENT ON COLUMN "direct_interest_snapshot"."weight" IS 'Weight of member (1 or higher) according to "delegating_interest_snapshot"';
jbe@0 352 COMMENT ON COLUMN "direct_interest_snapshot"."voting_requested" IS 'Copied from column "voting_requested" of table "interest"';
jbe@0 353
jbe@0 354
jbe@0 355 CREATE TABLE "delegating_interest_snapshot" (
jbe@0 356 PRIMARY KEY ("issue_id", "event", "member_id"),
jbe@0 357 "issue_id" INT4 REFERENCES "issue" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@0 358 "event" "snapshot_event",
jbe@0 359 "member_id" INT4 REFERENCES "member" ("id") ON DELETE RESTRICT ON UPDATE CASCADE,
jbe@0 360 "delegate_member_ids" INT4[] NOT NULL );
jbe@0 361 CREATE INDEX "delegating_interest_snapshot_member_id_idx" ON "delegating_interest_snapshot" ("member_id");
jbe@0 362
jbe@0 363 COMMENT ON TABLE "delegating_interest_snapshot" IS 'Delegations increasing the weight of entries in the "direct_interest_snapshot" table';
jbe@0 364
jbe@0 365 COMMENT ON COLUMN "delegating_interest_snapshot"."event" IS 'Reason for snapshot, see "snapshot_event" type for details';
jbe@0 366 COMMENT ON COLUMN "delegating_interest_snapshot"."member_id" IS 'Delegating member';
jbe@0 367 COMMENT ON COLUMN "delegating_interest_snapshot"."delegate_member_ids" IS 'Chain of members who act as delegates; last entry referes to "member_id" column of table "direct_interest_snapshot"';
jbe@0 368
jbe@0 369
jbe@0 370 CREATE TABLE "direct_supporter_snapshot" (
jbe@0 371 "issue_id" INT4 NOT NULL,
jbe@0 372 PRIMARY KEY ("initiative_id", "event", "member_id"),
jbe@0 373 "initiative_id" INT4,
jbe@0 374 "event" "snapshot_event",
jbe@0 375 "member_id" INT4 REFERENCES "member" ("id") ON DELETE RESTRICT ON UPDATE CASCADE,
jbe@0 376 "informed" BOOLEAN NOT NULL,
jbe@0 377 "satisfied" BOOLEAN NOT NULL,
jbe@0 378 FOREIGN KEY ("issue_id", "initiative_id") REFERENCES "initiative" ("issue_id", "id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@0 379 FOREIGN KEY ("issue_id", "event", "member_id") REFERENCES "direct_interest_snapshot" ("issue_id", "event", "member_id") ON DELETE CASCADE ON UPDATE CASCADE );
jbe@0 380 CREATE INDEX "direct_supporter_snapshot_member_id_idx" ON "direct_supporter_snapshot" ("member_id");
jbe@0 381
jbe@0 382 COMMENT ON TABLE "direct_supporter_snapshot" IS 'Snapshot of supporters of initiatives (weight is stored in "direct_interest_snapshot)';
jbe@0 383
jbe@0 384 COMMENT ON COLUMN "direct_supporter_snapshot"."event" IS 'Reason for snapshot, see "snapshot_event" type for details';
jbe@0 385 COMMENT ON COLUMN "direct_supporter_snapshot"."informed" IS 'Supporter has seen the latest draft of the initiative';
jbe@0 386 COMMENT ON COLUMN "direct_supporter_snapshot"."satisfied" IS 'Supporter has no "critical_opinion"s';
jbe@0 387
jbe@0 388
jbe@0 389 CREATE TABLE "direct_voter" (
jbe@0 390 PRIMARY KEY ("issue_id", "member_id"),
jbe@0 391 "issue_id" INT4 REFERENCES "issue" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@0 392 "member_id" INT4 REFERENCES "member" ("id") ON DELETE RESTRICT ON UPDATE CASCADE,
jbe@0 393 "weight" INT4,
jbe@0 394 "autoreject" BOOLEAN NOT NULL DEFAULT FALSE );
jbe@0 395 CREATE INDEX "direct_voter_member_id_idx" ON "direct_voter" ("member_id");
jbe@0 396
jbe@0 397 COMMENT ON TABLE "direct_voter" IS 'Members having directly voted for/against initiatives of an issue';
jbe@0 398
jbe@0 399 COMMENT ON COLUMN "direct_voter"."weight" IS 'Weight of member (1 or higher) according to "delegating_voter" table';
jbe@0 400 COMMENT ON COLUMN "direct_voter"."autoreject" IS 'Votes were inserted due to "autoreject" feature';
jbe@0 401
jbe@0 402
jbe@0 403 CREATE TABLE "delegating_voter" (
jbe@0 404 PRIMARY KEY ("issue_id", "member_id"),
jbe@0 405 "issue_id" INT4 REFERENCES "issue" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@0 406 "member_id" INT4 REFERENCES "member" ("id") ON DELETE RESTRICT ON UPDATE CASCADE,
jbe@0 407 "delegate_member_ids" INT4[] NOT NULL );
jbe@0 408 CREATE INDEX "delegating_voter_member_id_idx" ON "direct_voter" ("member_id");
jbe@0 409
jbe@0 410 COMMENT ON TABLE "delegating_voter" IS 'Delegations increasing the weight of entries in the "direct_voter" table';
jbe@0 411
jbe@0 412 COMMENT ON COLUMN "delegating_voter"."member_id" IS 'Delegating member';
jbe@0 413 COMMENT ON COLUMN "delegating_voter"."delegate_member_ids" IS 'Chain of members who act as delegates; last entry referes to "member_id" column of table "direct_voter"';
jbe@0 414
jbe@0 415
jbe@0 416 CREATE TABLE "vote" (
jbe@0 417 "issue_id" INT4 NOT NULL,
jbe@0 418 PRIMARY KEY ("initiative_id", "member_id"),
jbe@0 419 "initiative_id" INT4,
jbe@0 420 "member_id" INT4,
jbe@0 421 "grade" INT4,
jbe@0 422 FOREIGN KEY ("issue_id", "initiative_id") REFERENCES "initiative" ("issue_id", "id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@0 423 FOREIGN KEY ("issue_id", "member_id") REFERENCES "direct_voter" ("issue_id", "member_id") ON DELETE CASCADE ON UPDATE CASCADE );
jbe@0 424 CREATE INDEX "vote_member_id_idx" ON "vote" ("member_id");
jbe@0 425
jbe@0 426 COMMENT ON TABLE "vote" IS 'Manual and delegated votes without abstentions';
jbe@0 427
jbe@0 428 COMMENT ON COLUMN "vote"."grade" IS 'Values smaller than zero mean reject, values greater than zero mean acceptance, zero or missing row means abstention. Preferences are expressed by different positive or negative numbers.';
jbe@0 429
jbe@0 430
jbe@0 431
jbe@0 432 ----------------------------
jbe@0 433 -- Additional constraints --
jbe@0 434 ----------------------------
jbe@0 435
jbe@0 436
jbe@0 437 CREATE FUNCTION "issue_requires_first_initiative_trigger"()
jbe@0 438 RETURNS TRIGGER
jbe@0 439 LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@0 440 BEGIN
jbe@0 441 IF NOT EXISTS (
jbe@0 442 SELECT NULL FROM "initiative" WHERE "issue_id" = NEW."id"
jbe@0 443 ) THEN
jbe@0 444 --RAISE 'Cannot create issue without an initial initiative.' USING
jbe@0 445 -- ERRCODE = 'integrity_constraint_violation',
jbe@0 446 -- HINT = 'Create issue, initiative, and draft within the same transaction.';
jbe@0 447 RAISE EXCEPTION 'Cannot create issue without an initial initiative.';
jbe@0 448 END IF;
jbe@0 449 RETURN NULL;
jbe@0 450 END;
jbe@0 451 $$;
jbe@0 452
jbe@0 453 CREATE CONSTRAINT TRIGGER "issue_requires_first_initiative"
jbe@0 454 AFTER INSERT OR UPDATE ON "issue" DEFERRABLE INITIALLY DEFERRED
jbe@0 455 FOR EACH ROW EXECUTE PROCEDURE
jbe@0 456 "issue_requires_first_initiative_trigger"();
jbe@0 457
jbe@0 458 COMMENT ON FUNCTION "issue_requires_first_initiative_trigger"() IS 'Implementation of trigger "issue_requires_first_initiative" on table "issue"';
jbe@0 459 COMMENT ON TRIGGER "issue_requires_first_initiative" ON "issue" IS 'Ensure that new issues have at least one initiative';
jbe@0 460
jbe@0 461
jbe@0 462 CREATE FUNCTION "last_initiative_deletes_issue_trigger"()
jbe@0 463 RETURNS TRIGGER
jbe@0 464 LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@0 465 DECLARE
jbe@0 466 "reference_lost" BOOLEAN;
jbe@0 467 BEGIN
jbe@0 468 IF TG_OP = 'DELETE' THEN
jbe@0 469 "reference_lost" := TRUE;
jbe@0 470 ELSE
jbe@0 471 "reference_lost" := NEW."issue_id" != OLD."issue_id";
jbe@0 472 END IF;
jbe@0 473 IF
jbe@0 474 "reference_lost" AND NOT EXISTS (
jbe@0 475 SELECT NULL FROM "initiative" WHERE "issue_id" = OLD."issue_id"
jbe@0 476 )
jbe@0 477 THEN
jbe@0 478 DELETE FROM "issue" WHERE "id" = OLD."issue_id";
jbe@0 479 END IF;
jbe@0 480 RETURN NULL;
jbe@0 481 END;
jbe@0 482 $$;
jbe@0 483
jbe@0 484 CREATE CONSTRAINT TRIGGER "last_initiative_deletes_issue"
jbe@0 485 AFTER UPDATE OR DELETE ON "initiative" DEFERRABLE INITIALLY DEFERRED
jbe@0 486 FOR EACH ROW EXECUTE PROCEDURE
jbe@0 487 "last_initiative_deletes_issue_trigger"();
jbe@0 488
jbe@0 489 COMMENT ON FUNCTION "last_initiative_deletes_issue_trigger"() IS 'Implementation of trigger "last_initiative_deletes_issue" on table "initiative"';
jbe@0 490 COMMENT ON TRIGGER "last_initiative_deletes_issue" ON "initiative" IS 'Removing the last initiative of an issue deletes the issue';
jbe@0 491
jbe@0 492
jbe@0 493 CREATE FUNCTION "initiative_requires_first_draft_trigger"()
jbe@0 494 RETURNS TRIGGER
jbe@0 495 LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@0 496 BEGIN
jbe@0 497 IF NOT EXISTS (
jbe@0 498 SELECT NULL FROM "draft" WHERE "initiative_id" = NEW."id"
jbe@0 499 ) THEN
jbe@0 500 --RAISE 'Cannot create initiative without an initial draft.' USING
jbe@0 501 -- ERRCODE = 'integrity_constraint_violation',
jbe@0 502 -- HINT = 'Create issue, initiative and draft within the same transaction.';
jbe@0 503 RAISE EXCEPTION 'Cannot create initiative without an initial draft.';
jbe@0 504 END IF;
jbe@0 505 RETURN NULL;
jbe@0 506 END;
jbe@0 507 $$;
jbe@0 508
jbe@0 509 CREATE CONSTRAINT TRIGGER "initiative_requires_first_draft"
jbe@0 510 AFTER INSERT OR UPDATE ON "initiative" DEFERRABLE INITIALLY DEFERRED
jbe@0 511 FOR EACH ROW EXECUTE PROCEDURE
jbe@0 512 "initiative_requires_first_draft_trigger"();
jbe@0 513
jbe@0 514 COMMENT ON FUNCTION "initiative_requires_first_draft_trigger"() IS 'Implementation of trigger "initiative_requires_first_draft" on table "initiative"';
jbe@0 515 COMMENT ON TRIGGER "initiative_requires_first_draft" ON "initiative" IS 'Ensure that new initiatives have at least one draft';
jbe@0 516
jbe@0 517
jbe@0 518 CREATE FUNCTION "last_draft_deletes_initiative_trigger"()
jbe@0 519 RETURNS TRIGGER
jbe@0 520 LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@0 521 DECLARE
jbe@0 522 "reference_lost" BOOLEAN;
jbe@0 523 BEGIN
jbe@0 524 IF TG_OP = 'DELETE' THEN
jbe@0 525 "reference_lost" := TRUE;
jbe@0 526 ELSE
jbe@0 527 "reference_lost" := NEW."initiative_id" != OLD."initiative_id";
jbe@0 528 END IF;
jbe@0 529 IF
jbe@0 530 "reference_lost" AND NOT EXISTS (
jbe@0 531 SELECT NULL FROM "draft" WHERE "initiative_id" = OLD."initiative_id"
jbe@0 532 )
jbe@0 533 THEN
jbe@0 534 DELETE FROM "initiative" WHERE "id" = OLD."initiative_id";
jbe@0 535 END IF;
jbe@0 536 RETURN NULL;
jbe@0 537 END;
jbe@0 538 $$;
jbe@0 539
jbe@0 540 CREATE CONSTRAINT TRIGGER "last_draft_deletes_initiative"
jbe@0 541 AFTER UPDATE OR DELETE ON "draft" DEFERRABLE INITIALLY DEFERRED
jbe@0 542 FOR EACH ROW EXECUTE PROCEDURE
jbe@0 543 "last_draft_deletes_initiative_trigger"();
jbe@0 544
jbe@0 545 COMMENT ON FUNCTION "last_draft_deletes_initiative_trigger"() IS 'Implementation of trigger "last_draft_deletes_initiative" on table "draft"';
jbe@0 546 COMMENT ON TRIGGER "last_draft_deletes_initiative" ON "draft" IS 'Removing the last draft of an initiative deletes the initiative';
jbe@0 547
jbe@0 548
jbe@0 549 CREATE FUNCTION "suggestion_requires_first_opinion_trigger"()
jbe@0 550 RETURNS TRIGGER
jbe@0 551 LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@0 552 BEGIN
jbe@0 553 IF NOT EXISTS (
jbe@0 554 SELECT NULL FROM "opinion" WHERE "suggestion_id" = NEW."id"
jbe@0 555 ) THEN
jbe@0 556 RAISE EXCEPTION 'Cannot create a suggestion without an opinion.';
jbe@0 557 END IF;
jbe@0 558 RETURN NULL;
jbe@0 559 END;
jbe@0 560 $$;
jbe@0 561
jbe@0 562 CREATE CONSTRAINT TRIGGER "suggestion_requires_first_opinion"
jbe@0 563 AFTER INSERT OR UPDATE ON "suggestion" DEFERRABLE INITIALLY DEFERRED
jbe@0 564 FOR EACH ROW EXECUTE PROCEDURE
jbe@0 565 "suggestion_requires_first_opinion_trigger"();
jbe@0 566
jbe@0 567 COMMENT ON FUNCTION "suggestion_requires_first_opinion_trigger"() IS 'Implementation of trigger "suggestion_requires_first_opinion" on table "suggestion"';
jbe@0 568 COMMENT ON TRIGGER "suggestion_requires_first_opinion" ON "suggestion" IS 'Ensure that new suggestions have at least one opinion';
jbe@0 569
jbe@0 570
jbe@0 571 CREATE FUNCTION "last_opinion_deletes_suggestion_trigger"()
jbe@0 572 RETURNS TRIGGER
jbe@0 573 LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@0 574 DECLARE
jbe@0 575 "reference_lost" BOOLEAN;
jbe@0 576 BEGIN
jbe@0 577 IF TG_OP = 'DELETE' THEN
jbe@0 578 "reference_lost" := TRUE;
jbe@0 579 ELSE
jbe@0 580 "reference_lost" := NEW."suggestion_id" != OLD."suggestion_id";
jbe@0 581 END IF;
jbe@0 582 IF
jbe@0 583 "reference_lost" AND NOT EXISTS (
jbe@0 584 SELECT NULL FROM "opinion" WHERE "suggestion_id" = OLD."suggestion_id"
jbe@0 585 )
jbe@0 586 THEN
jbe@0 587 DELETE FROM "suggestion" WHERE "id" = OLD."suggestion_id";
jbe@0 588 END IF;
jbe@0 589 RETURN NULL;
jbe@0 590 END;
jbe@0 591 $$;
jbe@0 592
jbe@0 593 CREATE CONSTRAINT TRIGGER "last_opinion_deletes_suggestion"
jbe@0 594 AFTER UPDATE OR DELETE ON "opinion" DEFERRABLE INITIALLY DEFERRED
jbe@0 595 FOR EACH ROW EXECUTE PROCEDURE
jbe@0 596 "last_opinion_deletes_suggestion_trigger"();
jbe@0 597
jbe@0 598 COMMENT ON FUNCTION "last_opinion_deletes_suggestion_trigger"() IS 'Implementation of trigger "last_opinion_deletes_suggestion" on table "opinion"';
jbe@0 599 COMMENT ON TRIGGER "last_opinion_deletes_suggestion" ON "opinion" IS 'Removing the last opinion of a suggestion deletes the suggestion';
jbe@0 600
jbe@0 601
jbe@0 602
jbe@0 603 --------------------------------------------------------------------
jbe@0 604 -- Auto-retrieval of fields only needed for referential integrity --
jbe@0 605 --------------------------------------------------------------------
jbe@0 606
jbe@0 607 CREATE FUNCTION "autofill_issue_id_trigger"()
jbe@0 608 RETURNS TRIGGER
jbe@0 609 LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@0 610 BEGIN
jbe@0 611 IF NEW."issue_id" ISNULL THEN
jbe@0 612 SELECT "issue_id" INTO NEW."issue_id"
jbe@0 613 FROM "initiative" WHERE "id" = NEW."initiative_id";
jbe@0 614 END IF;
jbe@0 615 RETURN NEW;
jbe@0 616 END;
jbe@0 617 $$;
jbe@0 618
jbe@0 619 CREATE TRIGGER "autofill_issue_id" BEFORE INSERT ON "supporter"
jbe@0 620 FOR EACH ROW EXECUTE PROCEDURE "autofill_issue_id_trigger"();
jbe@0 621
jbe@0 622 CREATE TRIGGER "autofill_issue_id" BEFORE INSERT ON "vote"
jbe@0 623 FOR EACH ROW EXECUTE PROCEDURE "autofill_issue_id_trigger"();
jbe@0 624
jbe@0 625 COMMENT ON FUNCTION "autofill_issue_id_trigger"() IS 'Implementation of triggers "autofill_issue_id" on tables "supporter" and "vote"';
jbe@0 626 COMMENT ON TRIGGER "autofill_issue_id" ON "supporter" IS 'Set "issue_id" field automatically, if NULL';
jbe@0 627 COMMENT ON TRIGGER "autofill_issue_id" ON "vote" IS 'Set "issue_id" field automatically, if NULL';
jbe@0 628
jbe@0 629
jbe@0 630 CREATE FUNCTION "autofill_initiative_id_trigger"()
jbe@0 631 RETURNS TRIGGER
jbe@0 632 LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@0 633 BEGIN
jbe@0 634 IF NEW."initiative_id" ISNULL THEN
jbe@0 635 SELECT "initiative_id" INTO NEW."initiative_id"
jbe@0 636 FROM "suggestion" WHERE "id" = NEW."suggestion_id";
jbe@0 637 END IF;
jbe@0 638 RETURN NEW;
jbe@0 639 END;
jbe@0 640 $$;
jbe@0 641
jbe@0 642 CREATE TRIGGER "autofill_initiative_id" BEFORE INSERT ON "opinion"
jbe@0 643 FOR EACH ROW EXECUTE PROCEDURE "autofill_initiative_id_trigger"();
jbe@0 644
jbe@0 645 COMMENT ON FUNCTION "autofill_initiative_id_trigger"() IS 'Implementation of trigger "autofill_initiative_id" on table "opinion"';
jbe@0 646 COMMENT ON TRIGGER "autofill_initiative_id" ON "opinion" IS 'Set "initiative_id" field automatically, if NULL';
jbe@0 647
jbe@0 648
jbe@0 649
jbe@0 650 -----------------------------------------------------------------------
jbe@0 651 -- Automatic copy of autoreject settings from membership to interest --
jbe@0 652 -----------------------------------------------------------------------
jbe@0 653
jbe@0 654 CREATE FUNCTION "copy_autoreject_trigger"()
jbe@0 655 RETURNS TRIGGER
jbe@0 656 LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@0 657 BEGIN
jbe@0 658 IF NEW."autoreject" ISNULL THEN
jbe@0 659 SELECT "membership"."autoreject" INTO NEW."autoreject"
jbe@0 660 FROM "issue" JOIN "membership"
jbe@0 661 ON "issue"."area_id" = "membership"."area_id"
jbe@0 662 WHERE "issue"."id" = NEW."issue_id"
jbe@0 663 AND "membership"."member_id" = NEW."member_id";
jbe@0 664 END IF;
jbe@0 665 IF NEW."autoreject" ISNULL THEN
jbe@0 666 NEW."autoreject" := FALSE;
jbe@0 667 END IF;
jbe@0 668 RETURN NEW;
jbe@0 669 END;
jbe@0 670 $$;
jbe@0 671
jbe@0 672 CREATE TRIGGER "copy_autoreject" BEFORE INSERT OR UPDATE ON "interest"
jbe@0 673 FOR EACH ROW EXECUTE PROCEDURE "copy_autoreject_trigger"();
jbe@0 674
jbe@0 675 COMMENT ON FUNCTION "copy_autoreject_trigger"() IS 'Implementation of trigger "copy_autoreject" on table "interest"';
jbe@0 676 COMMENT ON TRIGGER "copy_autoreject" ON "interest" IS 'If "autoreject" is NULL, then copy it from the area setting, or set to FALSE, if no membership existent';
jbe@0 677
jbe@0 678
jbe@2 679 CREATE FUNCTION "supporter_default_for_draft_id_trigger"()
jbe@2 680 RETURNS TRIGGER
jbe@2 681 LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@2 682 BEGIN
jbe@2 683 IF NEW."draft_id" ISNULL THEN
jbe@2 684 SELECT "id" INTO NEW."draft_id" FROM "current_draft"
jbe@2 685 WHERE "initiative_id" = NEW."initiative_id";
jbe@2 686 END IF;
jbe@2 687 RETURN NEW;
jbe@2 688 END;
jbe@2 689 $$;
jbe@2 690
jbe@2 691 CREATE TRIGGER "default_for_draft_id" BEFORE INSERT OR UPDATE ON "supporter"
jbe@2 692 FOR EACH ROW EXECUTE PROCEDURE "supporter_default_for_draft_id_trigger"();
jbe@2 693
jbe@2 694 COMMENT ON FUNCTION "supporter_default_for_draft_id_trigger"() IS 'Implementation of trigger "default_for_draft" on table "supporter"';
jbe@2 695 COMMENT ON TRIGGER "default_for_draft_id" ON "supporter" IS 'If "draft_id" is NULL, then use the current draft of the initiative as default';
jbe@2 696
jbe@2 697
jbe@0 698
jbe@0 699 ----------------------------------------
jbe@0 700 -- Automatic creation of dependencies --
jbe@0 701 ----------------------------------------
jbe@0 702
jbe@0 703 CREATE FUNCTION "autocreate_interest_trigger"()
jbe@0 704 RETURNS TRIGGER
jbe@0 705 LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@0 706 BEGIN
jbe@0 707 IF NOT EXISTS (
jbe@0 708 SELECT NULL FROM "initiative" JOIN "interest"
jbe@0 709 ON "initiative"."issue_id" = "interest"."issue_id"
jbe@0 710 WHERE "initiative"."id" = NEW."initiative_id"
jbe@0 711 AND "interest"."member_id" = NEW."member_id"
jbe@0 712 ) THEN
jbe@0 713 BEGIN
jbe@0 714 INSERT INTO "interest" ("issue_id", "member_id")
jbe@0 715 SELECT "issue_id", NEW."member_id"
jbe@0 716 FROM "initiative" WHERE "id" = NEW."initiative_id";
jbe@0 717 EXCEPTION WHEN unique_violation THEN END;
jbe@0 718 END IF;
jbe@0 719 RETURN NEW;
jbe@0 720 END;
jbe@0 721 $$;
jbe@0 722
jbe@0 723 CREATE TRIGGER "autocreate_interest" BEFORE INSERT ON "supporter"
jbe@0 724 FOR EACH ROW EXECUTE PROCEDURE "autocreate_interest_trigger"();
jbe@0 725
jbe@0 726 COMMENT ON FUNCTION "autocreate_interest_trigger"() IS 'Implementation of trigger "autocreate_interest" on table "supporter"';
jbe@0 727 COMMENT ON TRIGGER "autocreate_interest" ON "supporter" IS 'Supporting an initiative implies interest in the issue, thus automatically creates an entry in the "interest" table';
jbe@0 728
jbe@0 729
jbe@0 730 CREATE FUNCTION "autocreate_supporter_trigger"()
jbe@0 731 RETURNS TRIGGER
jbe@0 732 LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@0 733 BEGIN
jbe@0 734 IF NOT EXISTS (
jbe@0 735 SELECT NULL FROM "suggestion" JOIN "supporter"
jbe@0 736 ON "suggestion"."initiative_id" = "supporter"."initiative_id"
jbe@0 737 WHERE "suggestion"."id" = NEW."suggestion_id"
jbe@0 738 AND "supporter"."member_id" = NEW."member_id"
jbe@0 739 ) THEN
jbe@0 740 BEGIN
jbe@0 741 INSERT INTO "supporter" ("initiative_id", "member_id")
jbe@0 742 SELECT "initiative_id", NEW."member_id"
jbe@0 743 FROM "suggestion" WHERE "id" = NEW."suggestion_id";
jbe@0 744 EXCEPTION WHEN unique_violation THEN END;
jbe@0 745 END IF;
jbe@0 746 RETURN NEW;
jbe@0 747 END;
jbe@0 748 $$;
jbe@0 749
jbe@0 750 CREATE TRIGGER "autocreate_supporter" BEFORE INSERT ON "opinion"
jbe@0 751 FOR EACH ROW EXECUTE PROCEDURE "autocreate_supporter_trigger"();
jbe@0 752
jbe@0 753 COMMENT ON FUNCTION "autocreate_supporter_trigger"() IS 'Implementation of trigger "autocreate_supporter" on table "opinion"';
jbe@0 754 COMMENT ON TRIGGER "autocreate_supporter" ON "opinion" IS 'Opinions can only be added for supported initiatives. This trigger automatrically creates an entry in the "supporter" table, if not existent yet.';
jbe@0 755
jbe@0 756
jbe@0 757
jbe@0 758 ------------------------------------------
jbe@0 759 -- Views and helper functions for views --
jbe@0 760 ------------------------------------------
jbe@0 761
jbe@0 762 CREATE VIEW "issue_delegation_with_overridden_and_inactive" AS
jbe@0 763 SELECT "delegation".*, "issue"."id" AS "resulting_issue_id"
jbe@0 764 FROM "delegation"
jbe@0 765 JOIN "issue" ON
jbe@0 766 ("delegation"."area_id" ISNULL AND "delegation"."issue_id" ISNULL) OR
jbe@0 767 "delegation"."area_id" = "issue"."area_id" OR
jbe@0 768 "delegation"."issue_id" = "issue"."id";
jbe@0 769
jbe@0 770 COMMENT ON VIEW "issue_delegation_with_overridden_and_inactive" IS 'Helper view for "issue_delegation"';
jbe@0 771
jbe@0 772
jbe@0 773 CREATE VIEW "issue_delegation" AS
jbe@0 774 SELECT
jbe@0 775 "entry"."id" AS "id",
jbe@0 776 "entry"."truster_id" AS "truster_id",
jbe@0 777 "entry"."trustee_id" AS "trustee_id",
jbe@0 778 "entry"."resulting_issue_id" AS "issue_id"
jbe@0 779 FROM "issue_delegation_with_overridden_and_inactive" AS "entry"
jbe@0 780 JOIN "member" AS "truster" ON "entry"."truster_id" = "truster"."id"
jbe@0 781 JOIN "member" AS "trustee" ON "entry"."trustee_id" = "trustee"."id"
jbe@0 782 LEFT JOIN "issue_delegation_with_overridden_and_inactive" AS "override"
jbe@0 783 ON "entry"."truster_id" = "override"."truster_id"
jbe@0 784 AND "entry"."id" != "override"."id"
jbe@0 785 AND (
jbe@0 786 ("entry"."area_id" ISNULL AND "entry"."issue_id" ISNULL) OR
jbe@0 787 "override"."issue_id" NOTNULL
jbe@0 788 )
jbe@0 789 WHERE "truster"."active" AND "trustee"."active"
jbe@0 790 AND "override"."truster_id" ISNULL;
jbe@0 791
jbe@0 792 COMMENT ON VIEW "issue_delegation" IS 'Resulting delegations for issues, without those involving inactive members';
jbe@0 793
jbe@0 794
jbe@0 795 CREATE VIEW "current_draft" AS
jbe@0 796 SELECT "draft".* FROM (
jbe@0 797 SELECT
jbe@0 798 "initiative"."id" AS "initiative_id",
jbe@0 799 max("draft"."id") AS "draft_id"
jbe@0 800 FROM "initiative" JOIN "draft"
jbe@0 801 ON "initiative"."id" = "draft"."initiative_id"
jbe@0 802 GROUP BY "initiative"."id"
jbe@0 803 ) AS "subquery"
jbe@0 804 JOIN "draft" ON "subquery"."draft_id" = "draft"."id";
jbe@0 805
jbe@0 806 COMMENT ON VIEW "current_draft" IS 'All latest drafts for each initiative';
jbe@0 807
jbe@0 808
jbe@0 809 CREATE VIEW "critical_opinion" AS
jbe@0 810 SELECT * FROM "opinion"
jbe@0 811 WHERE ("degree" = 2 AND "fulfilled" = FALSE)
jbe@0 812 OR ("degree" = -2 AND "fulfilled" = TRUE);
jbe@0 813
jbe@0 814 COMMENT ON VIEW "critical_opinion" IS 'Opinions currently causing dissatisfaction';
jbe@0 815
jbe@0 816
jbe@0 817 CREATE VIEW "battle_participant" AS
jbe@0 818 SELECT "issue_id", "id" AS "initiative_id" FROM "initiative"
jbe@0 819 WHERE "admitted"
jbe@0 820 AND "positive_votes" > "negative_votes";
jbe@0 821
jbe@0 822 COMMENT ON VIEW "battle_participant" IS 'Helper view for "battle" view';
jbe@0 823
jbe@0 824
jbe@0 825 CREATE VIEW "battle" AS
jbe@0 826 SELECT
jbe@0 827 "issue"."id" AS "issue_id",
jbe@0 828 "winning_initiative"."initiative_id" AS "winning_initiative_id",
jbe@0 829 "losing_initiative"."initiative_id" AS "losing_initiative_id",
jbe@0 830 sum(
jbe@0 831 CASE WHEN
jbe@0 832 coalesce("better_vote"."grade", 0) >
jbe@0 833 coalesce("worse_vote"."grade", 0)
jbe@0 834 THEN "direct_voter"."weight" ELSE 0 END
jbe@0 835 ) AS "count"
jbe@0 836 FROM "issue"
jbe@0 837 LEFT JOIN "direct_voter"
jbe@0 838 ON "issue"."id" = "direct_voter"."issue_id"
jbe@0 839 JOIN "battle_participant" AS "winning_initiative"
jbe@0 840 ON "issue"."id" = "winning_initiative"."issue_id"
jbe@0 841 JOIN "battle_participant" AS "losing_initiative"
jbe@0 842 ON "issue"."id" = "losing_initiative"."issue_id"
jbe@0 843 LEFT JOIN "vote" AS "better_vote"
jbe@0 844 ON "direct_voter"."member_id" = "better_vote"."member_id"
jbe@0 845 AND "winning_initiative"."initiative_id" = "better_vote"."initiative_id"
jbe@0 846 LEFT JOIN "vote" AS "worse_vote"
jbe@0 847 ON "direct_voter"."member_id" = "worse_vote"."member_id"
jbe@0 848 AND "losing_initiative"."initiative_id" = "worse_vote"."initiative_id"
jbe@0 849 WHERE
jbe@0 850 "winning_initiative"."initiative_id" !=
jbe@0 851 "losing_initiative"."initiative_id"
jbe@0 852 GROUP BY
jbe@0 853 "issue"."id",
jbe@0 854 "winning_initiative"."initiative_id",
jbe@0 855 "losing_initiative"."initiative_id";
jbe@0 856
jbe@0 857 COMMENT ON VIEW "battle" IS 'Number of members preferring one initiative over another';
jbe@0 858
jbe@0 859
jbe@1 860 CREATE VIEW "expired_session" AS
jbe@1 861 SELECT * FROM "session" WHERE now() > "expiry";
jbe@1 862
jbe@1 863 CREATE RULE "delete" AS ON DELETE TO "expired_session" DO INSTEAD
jbe@1 864 DELETE FROM "session" WHERE "ident" = OLD."ident";
jbe@1 865
jbe@1 866 COMMENT ON VIEW "expired_session" IS 'View containing all expired sessions where DELETE is possible';
jbe@1 867 COMMENT ON RULE "delete" ON "expired_session" IS 'Rule allowing DELETE on rows in "expired_session" view, i.e. DELETE FROM "expired_session"';
jbe@1 868
jbe@1 869
jbe@0 870 CREATE VIEW "open_issue" AS
jbe@0 871 SELECT * FROM "issue" WHERE "closed" ISNULL;
jbe@0 872
jbe@0 873 COMMENT ON VIEW "open_issue" IS 'All open issues';
jbe@0 874
jbe@0 875
jbe@0 876 CREATE VIEW "issue_with_ranks_missing" AS
jbe@0 877 SELECT * FROM "issue"
jbe@0 878 WHERE "frozen" NOTNULL
jbe@0 879 AND "closed" NOTNULL
jbe@0 880 AND "ranks_available" = FALSE;
jbe@0 881
jbe@0 882 COMMENT ON VIEW "issue_with_ranks_missing" IS 'Issues where voting was finished, but no ranks have been calculated yet';
jbe@0 883
jbe@0 884
jbe@0 885
jbe@0 886 ------------------------------
jbe@0 887 -- Comparison by vote count --
jbe@0 888 ------------------------------
jbe@0 889
jbe@0 890 CREATE FUNCTION "vote_ratio"
jbe@0 891 ( "positive_votes_p" "initiative"."positive_votes"%TYPE,
jbe@0 892 "negative_votes_p" "initiative"."negative_votes"%TYPE )
jbe@0 893 RETURNS FLOAT8
jbe@0 894 LANGUAGE 'plpgsql' STABLE AS $$
jbe@0 895 DECLARE
jbe@0 896 "total_v" INT4;
jbe@0 897 BEGIN
jbe@0 898 "total_v" := "positive_votes_p" + "negative_votes_p";
jbe@0 899 IF "total_v" > 0 THEN
jbe@0 900 RETURN "positive_votes_p"::FLOAT8 / "total_v"::FLOAT8;
jbe@0 901 ELSE
jbe@0 902 RETURN 0.5;
jbe@0 903 END IF;
jbe@0 904 END;
jbe@0 905 $$;
jbe@0 906
jbe@0 907 COMMENT ON FUNCTION "vote_ratio"
jbe@0 908 ( "initiative"."positive_votes"%TYPE,
jbe@0 909 "initiative"."negative_votes"%TYPE )
jbe@0 910 IS 'Ratio of positive votes to sum of positive and negative votes; 0.5, if there are neither positive nor negative votes';
jbe@0 911
jbe@0 912
jbe@0 913
jbe@0 914 ------------------------------------------------
jbe@0 915 -- Locking for snapshots and voting procedure --
jbe@0 916 ------------------------------------------------
jbe@0 917
jbe@0 918 CREATE FUNCTION "global_lock"() RETURNS VOID
jbe@0 919 LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@0 920 BEGIN
jbe@0 921 -- NOTE: PostgreSQL allows reading, while tables are locked in
jbe@0 922 -- exclusive move. Transactions should be kept short anyway!
jbe@0 923 LOCK TABLE "member" IN EXCLUSIVE MODE;
jbe@0 924 LOCK TABLE "policy" IN EXCLUSIVE MODE;
jbe@0 925 LOCK TABLE "area" IN EXCLUSIVE MODE;
jbe@0 926 LOCK TABLE "issue" IN EXCLUSIVE MODE;
jbe@0 927 LOCK TABLE "initiative" IN EXCLUSIVE MODE;
jbe@0 928 LOCK TABLE "draft" IN EXCLUSIVE MODE;
jbe@0 929 LOCK TABLE "suggestion" IN EXCLUSIVE MODE;
jbe@0 930 LOCK TABLE "membership" IN EXCLUSIVE MODE;
jbe@0 931 LOCK TABLE "interest" IN EXCLUSIVE MODE;
jbe@0 932 LOCK TABLE "initiator" IN EXCLUSIVE MODE;
jbe@0 933 LOCK TABLE "supporter" IN EXCLUSIVE MODE;
jbe@0 934 LOCK TABLE "opinion" IN EXCLUSIVE MODE;
jbe@0 935 LOCK TABLE "delegation" IN EXCLUSIVE MODE;
jbe@0 936 LOCK TABLE "direct_population_snapshot" IN EXCLUSIVE MODE;
jbe@0 937 LOCK TABLE "delegating_population_snapshot" IN EXCLUSIVE MODE;
jbe@0 938 LOCK TABLE "direct_interest_snapshot" IN EXCLUSIVE MODE;
jbe@0 939 LOCK TABLE "delegating_interest_snapshot" IN EXCLUSIVE MODE;
jbe@0 940 LOCK TABLE "direct_supporter_snapshot" IN EXCLUSIVE MODE;
jbe@0 941 LOCK TABLE "direct_voter" IN EXCLUSIVE MODE;
jbe@0 942 LOCK TABLE "delegating_voter" IN EXCLUSIVE MODE;
jbe@0 943 LOCK TABLE "vote" IN EXCLUSIVE MODE;
jbe@0 944 RETURN;
jbe@0 945 END;
jbe@0 946 $$;
jbe@0 947
jbe@0 948 COMMENT ON FUNCTION "global_lock"() IS 'Locks all tables related to support/voting until end of transaction; read access is still possible though';
jbe@0 949
jbe@0 950
jbe@0 951
jbe@0 952 ------------------------------
jbe@0 953 -- Calculation of snapshots --
jbe@0 954 ------------------------------
jbe@0 955
jbe@0 956 CREATE FUNCTION "weight_of_added_delegations_for_population_snapshot"
jbe@0 957 ( "issue_id_p" "issue"."id"%TYPE,
jbe@0 958 "member_id_p" "member"."id"%TYPE,
jbe@0 959 "delegate_member_ids_p" "delegating_population_snapshot"."delegate_member_ids"%TYPE )
jbe@0 960 RETURNS "direct_population_snapshot"."weight"%TYPE
jbe@0 961 LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@0 962 DECLARE
jbe@0 963 "issue_delegation_row" "issue_delegation"%ROWTYPE;
jbe@0 964 "delegate_member_ids_v" "delegating_population_snapshot"."delegate_member_ids"%TYPE;
jbe@0 965 "weight_v" INT4;
jbe@0 966 BEGIN
jbe@0 967 "weight_v" := 0;
jbe@0 968 FOR "issue_delegation_row" IN
jbe@0 969 SELECT * FROM "issue_delegation"
jbe@0 970 WHERE "trustee_id" = "member_id_p"
jbe@0 971 AND "issue_id" = "issue_id_p"
jbe@0 972 LOOP
jbe@0 973 IF NOT EXISTS (
jbe@0 974 SELECT NULL FROM "direct_population_snapshot"
jbe@0 975 WHERE "issue_id" = "issue_id_p"
jbe@0 976 AND "event" = 'periodic'
jbe@0 977 AND "member_id" = "issue_delegation_row"."truster_id"
jbe@0 978 ) AND NOT EXISTS (
jbe@0 979 SELECT NULL FROM "delegating_population_snapshot"
jbe@0 980 WHERE "issue_id" = "issue_id_p"
jbe@0 981 AND "event" = 'periodic'
jbe@0 982 AND "member_id" = "issue_delegation_row"."truster_id"
jbe@0 983 ) THEN
jbe@0 984 "delegate_member_ids_v" :=
jbe@0 985 "member_id_p" || "delegate_member_ids_p";
jbe@0 986 INSERT INTO "delegating_population_snapshot"
jbe@0 987 ("issue_id", "event", "member_id", "delegate_member_ids")
jbe@0 988 VALUES (
jbe@0 989 "issue_id_p",
jbe@0 990 'periodic',
jbe@0 991 "issue_delegation_row"."truster_id",
jbe@0 992 "delegate_member_ids_v"
jbe@0 993 );
jbe@0 994 "weight_v" := "weight_v" + 1 +
jbe@0 995 "weight_of_added_delegations_for_population_snapshot"(
jbe@0 996 "issue_id_p",
jbe@0 997 "issue_delegation_row"."truster_id",
jbe@0 998 "delegate_member_ids_v"
jbe@0 999 );
jbe@0 1000 END IF;
jbe@0 1001 END LOOP;
jbe@0 1002 RETURN "weight_v";
jbe@0 1003 END;
jbe@0 1004 $$;
jbe@0 1005
jbe@0 1006 COMMENT ON FUNCTION "weight_of_added_delegations_for_population_snapshot"
jbe@0 1007 ( "issue"."id"%TYPE,
jbe@0 1008 "member"."id"%TYPE,
jbe@0 1009 "delegating_population_snapshot"."delegate_member_ids"%TYPE )
jbe@0 1010 IS 'Helper function for "create_population_snapshot" function';
jbe@0 1011
jbe@0 1012
jbe@0 1013 CREATE FUNCTION "create_population_snapshot"
jbe@0 1014 ( "issue_id_p" "issue"."id"%TYPE )
jbe@0 1015 RETURNS VOID
jbe@0 1016 LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@0 1017 DECLARE
jbe@0 1018 "member_id_v" "member"."id"%TYPE;
jbe@0 1019 BEGIN
jbe@0 1020 DELETE FROM "direct_population_snapshot"
jbe@0 1021 WHERE "issue_id" = "issue_id_p"
jbe@0 1022 AND "event" = 'periodic';
jbe@0 1023 DELETE FROM "delegating_population_snapshot"
jbe@0 1024 WHERE "issue_id" = "issue_id_p"
jbe@0 1025 AND "event" = 'periodic';
jbe@0 1026 INSERT INTO "direct_population_snapshot"
jbe@0 1027 ("issue_id", "event", "member_id", "interest_exists")
jbe@0 1028 SELECT DISTINCT ON ("issue_id", "member_id")
jbe@0 1029 "issue_id_p" AS "issue_id",
jbe@0 1030 'periodic' AS "event",
jbe@0 1031 "subquery"."member_id",
jbe@0 1032 "subquery"."interest_exists"
jbe@0 1033 FROM (
jbe@0 1034 SELECT
jbe@0 1035 "member"."id" AS "member_id",
jbe@0 1036 FALSE AS "interest_exists"
jbe@0 1037 FROM "issue"
jbe@0 1038 JOIN "area" ON "issue"."area_id" = "area"."id"
jbe@0 1039 JOIN "membership" ON "area"."id" = "membership"."area_id"
jbe@0 1040 JOIN "member" ON "membership"."member_id" = "member"."id"
jbe@0 1041 WHERE "issue"."id" = "issue_id_p"
jbe@0 1042 AND "member"."active"
jbe@0 1043 UNION
jbe@0 1044 SELECT
jbe@0 1045 "member"."id" AS "member_id",
jbe@0 1046 TRUE AS "interest_exists"
jbe@0 1047 FROM "interest" JOIN "member"
jbe@0 1048 ON "interest"."member_id" = "member"."id"
jbe@0 1049 WHERE "interest"."issue_id" = "issue_id_p"
jbe@0 1050 AND "member"."active"
jbe@0 1051 ) AS "subquery"
jbe@0 1052 ORDER BY
jbe@0 1053 "issue_id_p",
jbe@0 1054 "subquery"."member_id",
jbe@0 1055 "subquery"."interest_exists" DESC;
jbe@0 1056 FOR "member_id_v" IN
jbe@0 1057 SELECT "member_id" FROM "direct_population_snapshot"
jbe@0 1058 WHERE "issue_id" = "issue_id_p"
jbe@0 1059 AND "event" = 'periodic'
jbe@0 1060 LOOP
jbe@0 1061 UPDATE "direct_population_snapshot" SET
jbe@0 1062 "weight" = 1 +
jbe@0 1063 "weight_of_added_delegations_for_population_snapshot"(
jbe@0 1064 "issue_id_p",
jbe@0 1065 "member_id_v",
jbe@0 1066 '{}'
jbe@0 1067 )
jbe@0 1068 WHERE "issue_id" = "issue_id_p"
jbe@0 1069 AND "event" = 'periodic'
jbe@0 1070 AND "member_id" = "member_id_v";
jbe@0 1071 END LOOP;
jbe@0 1072 RETURN;
jbe@0 1073 END;
jbe@0 1074 $$;
jbe@0 1075
jbe@0 1076 COMMENT ON FUNCTION "create_population_snapshot"
jbe@0 1077 ( "issue_id_p" "issue"."id"%TYPE )
jbe@0 1078 IS 'This function creates a new ''periodic'' population snapshot for the given issue. It does neither lock any tables, nor updates precalculated values in other tables.';
jbe@0 1079
jbe@0 1080
jbe@0 1081 CREATE FUNCTION "weight_of_added_delegations_for_interest_snapshot"
jbe@0 1082 ( "issue_id_p" "issue"."id"%TYPE,
jbe@0 1083 "member_id_p" "member"."id"%TYPE,
jbe@0 1084 "delegate_member_ids_p" "delegating_interest_snapshot"."delegate_member_ids"%TYPE )
jbe@0 1085 RETURNS "direct_interest_snapshot"."weight"%TYPE
jbe@0 1086 LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@0 1087 DECLARE
jbe@0 1088 "issue_delegation_row" "issue_delegation"%ROWTYPE;
jbe@0 1089 "delegate_member_ids_v" "delegating_interest_snapshot"."delegate_member_ids"%TYPE;
jbe@0 1090 "weight_v" INT4;
jbe@0 1091 BEGIN
jbe@0 1092 "weight_v" := 0;
jbe@0 1093 FOR "issue_delegation_row" IN
jbe@0 1094 SELECT * FROM "issue_delegation"
jbe@0 1095 WHERE "trustee_id" = "member_id_p"
jbe@0 1096 AND "issue_id" = "issue_id_p"
jbe@0 1097 LOOP
jbe@0 1098 IF NOT EXISTS (
jbe@0 1099 SELECT NULL FROM "direct_interest_snapshot"
jbe@0 1100 WHERE "issue_id" = "issue_id_p"
jbe@0 1101 AND "event" = 'periodic'
jbe@0 1102 AND "member_id" = "issue_delegation_row"."truster_id"
jbe@0 1103 ) AND NOT EXISTS (
jbe@0 1104 SELECT NULL FROM "delegating_interest_snapshot"
jbe@0 1105 WHERE "issue_id" = "issue_id_p"
jbe@0 1106 AND "event" = 'periodic'
jbe@0 1107 AND "member_id" = "issue_delegation_row"."truster_id"
jbe@0 1108 ) THEN
jbe@0 1109 "delegate_member_ids_v" :=
jbe@0 1110 "member_id_p" || "delegate_member_ids_p";
jbe@0 1111 INSERT INTO "delegating_interest_snapshot"
jbe@0 1112 ("issue_id", "event", "member_id", "delegate_member_ids")
jbe@0 1113 VALUES (
jbe@0 1114 "issue_id_p",
jbe@0 1115 'periodic',
jbe@0 1116 "issue_delegation_row"."truster_id",
jbe@0 1117 "delegate_member_ids_v"
jbe@0 1118 );
jbe@0 1119 "weight_v" := "weight_v" + 1 +
jbe@0 1120 "weight_of_added_delegations_for_interest_snapshot"(
jbe@0 1121 "issue_id_p",
jbe@0 1122 "issue_delegation_row"."truster_id",
jbe@0 1123 "delegate_member_ids_v"
jbe@0 1124 );
jbe@0 1125 END IF;
jbe@0 1126 END LOOP;
jbe@0 1127 RETURN "weight_v";
jbe@0 1128 END;
jbe@0 1129 $$;
jbe@0 1130
jbe@0 1131 COMMENT ON FUNCTION "weight_of_added_delegations_for_interest_snapshot"
jbe@0 1132 ( "issue"."id"%TYPE,
jbe@0 1133 "member"."id"%TYPE,
jbe@0 1134 "delegating_interest_snapshot"."delegate_member_ids"%TYPE )
jbe@0 1135 IS 'Helper function for "create_interest_snapshot" function';
jbe@0 1136
jbe@0 1137
jbe@0 1138 CREATE FUNCTION "create_interest_snapshot"
jbe@0 1139 ( "issue_id_p" "issue"."id"%TYPE )
jbe@0 1140 RETURNS VOID
jbe@0 1141 LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@0 1142 DECLARE
jbe@0 1143 "member_id_v" "member"."id"%TYPE;
jbe@0 1144 BEGIN
jbe@0 1145 DELETE FROM "direct_interest_snapshot"
jbe@0 1146 WHERE "issue_id" = "issue_id_p"
jbe@0 1147 AND "event" = 'periodic';
jbe@0 1148 DELETE FROM "delegating_interest_snapshot"
jbe@0 1149 WHERE "issue_id" = "issue_id_p"
jbe@0 1150 AND "event" = 'periodic';
jbe@0 1151 DELETE FROM "direct_supporter_snapshot"
jbe@0 1152 WHERE "issue_id" = "issue_id_p"
jbe@0 1153 AND "event" = 'periodic';
jbe@0 1154 INSERT INTO "direct_interest_snapshot"
jbe@0 1155 ("issue_id", "event", "member_id", "voting_requested")
jbe@0 1156 SELECT
jbe@0 1157 "issue_id_p" AS "issue_id",
jbe@0 1158 'periodic' AS "event",
jbe@0 1159 "member"."id" AS "member_id",
jbe@0 1160 "interest"."voting_requested"
jbe@0 1161 FROM "interest" JOIN "member"
jbe@0 1162 ON "interest"."member_id" = "member"."id"
jbe@0 1163 WHERE "interest"."issue_id" = "issue_id_p"
jbe@0 1164 AND "member"."active";
jbe@0 1165 FOR "member_id_v" IN
jbe@0 1166 SELECT "member_id" FROM "direct_interest_snapshot"
jbe@0 1167 WHERE "issue_id" = "issue_id_p"
jbe@0 1168 AND "event" = 'periodic'
jbe@0 1169 LOOP
jbe@0 1170 UPDATE "direct_interest_snapshot" SET
jbe@0 1171 "weight" = 1 +
jbe@0 1172 "weight_of_added_delegations_for_interest_snapshot"(
jbe@0 1173 "issue_id_p",
jbe@0 1174 "member_id_v",
jbe@0 1175 '{}'
jbe@0 1176 )
jbe@0 1177 WHERE "issue_id" = "issue_id_p"
jbe@0 1178 AND "event" = 'periodic'
jbe@0 1179 AND "member_id" = "member_id_v";
jbe@0 1180 END LOOP;
jbe@0 1181 INSERT INTO "direct_supporter_snapshot"
jbe@0 1182 ( "issue_id", "initiative_id", "event", "member_id",
jbe@0 1183 "informed", "satisfied" )
jbe@0 1184 SELECT
jbe@0 1185 "issue_id_p" AS "issue_id",
jbe@0 1186 "initiative"."id" AS "initiative_id",
jbe@0 1187 'periodic' AS "event",
jbe@0 1188 "member"."id" AS "member_id",
jbe@0 1189 "supporter"."draft_id" = "current_draft"."id" AS "informed",
jbe@0 1190 NOT EXISTS (
jbe@0 1191 SELECT NULL FROM "critical_opinion"
jbe@0 1192 WHERE "initiative_id" = "initiative"."id"
jbe@0 1193 AND "member_id" = "member"."id"
jbe@0 1194 ) AS "satisfied"
jbe@0 1195 FROM "supporter"
jbe@0 1196 JOIN "member"
jbe@0 1197 ON "supporter"."member_id" = "member"."id"
jbe@0 1198 JOIN "initiative"
jbe@0 1199 ON "supporter"."initiative_id" = "initiative"."id"
jbe@0 1200 JOIN "current_draft"
jbe@0 1201 ON "initiative"."id" = "current_draft"."initiative_id"
jbe@0 1202 JOIN "direct_interest_snapshot"
jbe@0 1203 ON "member"."id" = "direct_interest_snapshot"."member_id"
jbe@0 1204 AND "initiative"."issue_id" = "direct_interest_snapshot"."issue_id"
jbe@0 1205 WHERE "member"."active"
jbe@0 1206 AND "initiative"."issue_id" = "issue_id_p";
jbe@0 1207 RETURN;
jbe@0 1208 END;
jbe@0 1209 $$;
jbe@0 1210
jbe@0 1211 COMMENT ON FUNCTION "create_interest_snapshot"
jbe@0 1212 ( "issue"."id"%TYPE )
jbe@0 1213 IS 'This function creates a new ''periodic'' interest/supporter snapshot for the given issue. It does neither lock any tables, nor updates precalculated values in other tables.';
jbe@0 1214
jbe@0 1215
jbe@0 1216 CREATE FUNCTION "create_snapshot"
jbe@0 1217 ( "issue_id_p" "issue"."id"%TYPE )
jbe@0 1218 RETURNS VOID
jbe@0 1219 LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@0 1220 DECLARE
jbe@0 1221 "initiative_id_v" "initiative"."id"%TYPE;
jbe@0 1222 "suggestion_id_v" "suggestion"."id"%TYPE;
jbe@0 1223 BEGIN
jbe@0 1224 PERFORM "global_lock"();
jbe@0 1225 PERFORM "create_population_snapshot"("issue_id_p");
jbe@0 1226 PERFORM "create_interest_snapshot"("issue_id_p");
jbe@0 1227 UPDATE "issue" SET
jbe@0 1228 "snapshot" = now(),
jbe@0 1229 "population" = (
jbe@0 1230 SELECT coalesce(sum("weight"), 0)
jbe@0 1231 FROM "direct_population_snapshot"
jbe@0 1232 WHERE "issue_id" = "issue_id_p"
jbe@0 1233 AND "event" = 'periodic'
jbe@0 1234 ),
jbe@0 1235 "vote_now" = (
jbe@0 1236 SELECT coalesce(sum("weight"), 0)
jbe@0 1237 FROM "direct_interest_snapshot"
jbe@0 1238 WHERE "issue_id" = "issue_id_p"
jbe@0 1239 AND "event" = 'periodic'
jbe@0 1240 AND "voting_requested" = TRUE
jbe@0 1241 ),
jbe@0 1242 "vote_later" = (
jbe@0 1243 SELECT coalesce(sum("weight"), 0)
jbe@0 1244 FROM "direct_interest_snapshot"
jbe@0 1245 WHERE "issue_id" = "issue_id_p"
jbe@0 1246 AND "event" = 'periodic'
jbe@0 1247 AND "voting_requested" = FALSE
jbe@0 1248 )
jbe@0 1249 WHERE "id" = "issue_id_p";
jbe@0 1250 FOR "initiative_id_v" IN
jbe@0 1251 SELECT "id" FROM "initiative" WHERE "issue_id" = "issue_id_p"
jbe@0 1252 LOOP
jbe@0 1253 UPDATE "initiative" SET
jbe@0 1254 "supporter_count" = (
jbe@0 1255 SELECT coalesce(sum("di"."weight"), 0)
jbe@0 1256 FROM "direct_interest_snapshot" AS "di"
jbe@0 1257 JOIN "direct_supporter_snapshot" AS "ds"
jbe@0 1258 ON "di"."member_id" = "ds"."member_id"
jbe@0 1259 WHERE "di"."issue_id" = "issue_id_p"
jbe@0 1260 AND "di"."event" = 'periodic'
jbe@0 1261 AND "ds"."initiative_id" = "initiative_id_v"
jbe@0 1262 AND "ds"."event" = 'periodic'
jbe@0 1263 ),
jbe@0 1264 "informed_supporter_count" = (
jbe@0 1265 SELECT coalesce(sum("di"."weight"), 0)
jbe@0 1266 FROM "direct_interest_snapshot" AS "di"
jbe@0 1267 JOIN "direct_supporter_snapshot" AS "ds"
jbe@0 1268 ON "di"."member_id" = "ds"."member_id"
jbe@0 1269 WHERE "di"."issue_id" = "issue_id_p"
jbe@0 1270 AND "di"."event" = 'periodic'
jbe@0 1271 AND "ds"."initiative_id" = "initiative_id_v"
jbe@0 1272 AND "ds"."event" = 'periodic'
jbe@0 1273 AND "ds"."informed"
jbe@0 1274 ),
jbe@0 1275 "satisfied_supporter_count" = (
jbe@0 1276 SELECT coalesce(sum("di"."weight"), 0)
jbe@0 1277 FROM "direct_interest_snapshot" AS "di"
jbe@0 1278 JOIN "direct_supporter_snapshot" AS "ds"
jbe@0 1279 ON "di"."member_id" = "ds"."member_id"
jbe@0 1280 WHERE "di"."issue_id" = "issue_id_p"
jbe@0 1281 AND "di"."event" = 'periodic'
jbe@0 1282 AND "ds"."initiative_id" = "initiative_id_v"
jbe@0 1283 AND "ds"."event" = 'periodic'
jbe@0 1284 AND "ds"."satisfied"
jbe@0 1285 ),
jbe@0 1286 "satisfied_informed_supporter_count" = (
jbe@0 1287 SELECT coalesce(sum("di"."weight"), 0)
jbe@0 1288 FROM "direct_interest_snapshot" AS "di"
jbe@0 1289 JOIN "direct_supporter_snapshot" AS "ds"
jbe@0 1290 ON "di"."member_id" = "ds"."member_id"
jbe@0 1291 WHERE "di"."issue_id" = "issue_id_p"
jbe@0 1292 AND "di"."event" = 'periodic'
jbe@0 1293 AND "ds"."initiative_id" = "initiative_id_v"
jbe@0 1294 AND "ds"."event" = 'periodic'
jbe@0 1295 AND "ds"."informed"
jbe@0 1296 AND "ds"."satisfied"
jbe@0 1297 )
jbe@0 1298 WHERE "id" = "initiative_id_v";
jbe@0 1299 FOR "suggestion_id_v" IN
jbe@0 1300 SELECT "id" FROM "suggestion"
jbe@0 1301 WHERE "initiative_id" = "initiative_id_v"
jbe@0 1302 LOOP
jbe@0 1303 UPDATE "suggestion" SET
jbe@0 1304 "minus2_unfulfilled_count" = (
jbe@0 1305 SELECT coalesce(sum("snapshot"."weight"), 0)
jbe@0 1306 FROM "opinion" JOIN "direct_interest_snapshot" AS "snapshot"
jbe@0 1307 ON "opinion"."member_id" = "snapshot"."member_id"
jbe@1 1308 WHERE "opinion"."suggestion_id" = "suggestion_id_v"
jbe@0 1309 AND "snapshot"."issue_id" = "issue_id_p"
jbe@0 1310 AND "opinion"."degree" = -2
jbe@0 1311 AND "opinion"."fulfilled" = FALSE
jbe@0 1312 ),
jbe@0 1313 "minus2_fulfilled_count" = (
jbe@0 1314 SELECT coalesce(sum("snapshot"."weight"), 0)
jbe@0 1315 FROM "opinion" JOIN "direct_interest_snapshot" AS "snapshot"
jbe@0 1316 ON "opinion"."member_id" = "snapshot"."member_id"
jbe@1 1317 WHERE "opinion"."suggestion_id" = "suggestion_id_v"
jbe@0 1318 AND "snapshot"."issue_id" = "issue_id_p"
jbe@0 1319 AND "opinion"."degree" = -2
jbe@0 1320 AND "opinion"."fulfilled" = TRUE
jbe@0 1321 ),
jbe@0 1322 "minus1_unfulfilled_count" = (
jbe@0 1323 SELECT coalesce(sum("snapshot"."weight"), 0)
jbe@0 1324 FROM "opinion" JOIN "direct_interest_snapshot" AS "snapshot"
jbe@0 1325 ON "opinion"."member_id" = "snapshot"."member_id"
jbe@1 1326 WHERE "opinion"."suggestion_id" = "suggestion_id_v"
jbe@0 1327 AND "snapshot"."issue_id" = "issue_id_p"
jbe@0 1328 AND "opinion"."degree" = -1
jbe@0 1329 AND "opinion"."fulfilled" = FALSE
jbe@0 1330 ),
jbe@0 1331 "minus1_fulfilled_count" = (
jbe@0 1332 SELECT coalesce(sum("snapshot"."weight"), 0)
jbe@0 1333 FROM "opinion" JOIN "direct_interest_snapshot" AS "snapshot"
jbe@0 1334 ON "opinion"."member_id" = "snapshot"."member_id"
jbe@1 1335 WHERE "opinion"."suggestion_id" = "suggestion_id_v"
jbe@0 1336 AND "snapshot"."issue_id" = "issue_id_p"
jbe@0 1337 AND "opinion"."degree" = -1
jbe@0 1338 AND "opinion"."fulfilled" = TRUE
jbe@0 1339 ),
jbe@0 1340 "plus1_unfulfilled_count" = (
jbe@0 1341 SELECT coalesce(sum("snapshot"."weight"), 0)
jbe@0 1342 FROM "opinion" JOIN "direct_interest_snapshot" AS "snapshot"
jbe@0 1343 ON "opinion"."member_id" = "snapshot"."member_id"
jbe@1 1344 WHERE "opinion"."suggestion_id" = "suggestion_id_v"
jbe@0 1345 AND "snapshot"."issue_id" = "issue_id_p"
jbe@0 1346 AND "opinion"."degree" = 1
jbe@0 1347 AND "opinion"."fulfilled" = FALSE
jbe@0 1348 ),
jbe@0 1349 "plus1_fulfilled_count" = (
jbe@0 1350 SELECT coalesce(sum("snapshot"."weight"), 0)
jbe@0 1351 FROM "opinion" JOIN "direct_interest_snapshot" AS "snapshot"
jbe@0 1352 ON "opinion"."member_id" = "snapshot"."member_id"
jbe@1 1353 WHERE "opinion"."suggestion_id" = "suggestion_id_v"
jbe@0 1354 AND "snapshot"."issue_id" = "issue_id_p"
jbe@0 1355 AND "opinion"."degree" = 1
jbe@0 1356 AND "opinion"."fulfilled" = TRUE
jbe@0 1357 ),
jbe@0 1358 "plus2_unfulfilled_count" = (
jbe@0 1359 SELECT coalesce(sum("snapshot"."weight"), 0)
jbe@0 1360 FROM "opinion" JOIN "direct_interest_snapshot" AS "snapshot"
jbe@0 1361 ON "opinion"."member_id" = "snapshot"."member_id"
jbe@1 1362 WHERE "opinion"."suggestion_id" = "suggestion_id_v"
jbe@0 1363 AND "snapshot"."issue_id" = "issue_id_p"
jbe@0 1364 AND "opinion"."degree" = 2
jbe@0 1365 AND "opinion"."fulfilled" = FALSE
jbe@0 1366 ),
jbe@0 1367 "plus2_fulfilled_count" = (
jbe@0 1368 SELECT coalesce(sum("snapshot"."weight"), 0)
jbe@0 1369 FROM "opinion" JOIN "direct_interest_snapshot" AS "snapshot"
jbe@0 1370 ON "opinion"."member_id" = "snapshot"."member_id"
jbe@1 1371 WHERE "opinion"."suggestion_id" = "suggestion_id_v"
jbe@0 1372 AND "snapshot"."issue_id" = "issue_id_p"
jbe@0 1373 AND "opinion"."degree" = 2
jbe@0 1374 AND "opinion"."fulfilled" = TRUE
jbe@0 1375 )
jbe@0 1376 WHERE "suggestion"."id" = "suggestion_id_v";
jbe@0 1377 END LOOP;
jbe@0 1378 END LOOP;
jbe@0 1379 RETURN;
jbe@0 1380 END;
jbe@0 1381 $$;
jbe@0 1382
jbe@0 1383 COMMENT ON FUNCTION "create_snapshot"
jbe@0 1384 ( "issue"."id"%TYPE )
jbe@0 1385 IS 'This function creates a complete new ''periodic'' snapshot of population, interest and support for the given issue. All involved tables are locked, and after completion precalculated values in the source tables are updated.';
jbe@0 1386
jbe@0 1387
jbe@0 1388 CREATE FUNCTION "set_snapshot_event"
jbe@0 1389 ( "issue_id_p" "issue"."id"%TYPE,
jbe@0 1390 "event_p" "snapshot_event" )
jbe@0 1391 RETURNS VOID
jbe@0 1392 LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@0 1393 BEGIN
jbe@0 1394 UPDATE "direct_population_snapshot"
jbe@0 1395 SET "event" = 'end_of_discussion'
jbe@0 1396 WHERE "issue_id" = "issue_id_p" AND "event" = 'periodic';
jbe@0 1397 UPDATE "delegating_population_snapshot"
jbe@0 1398 SET "event" = 'end_of_discussion'
jbe@0 1399 WHERE "issue_id" = "issue_id_p" AND "event" = 'periodic';
jbe@0 1400 UPDATE "direct_interest_snapshot"
jbe@0 1401 SET "event" = 'end_of_discussion'
jbe@0 1402 WHERE "issue_id" = "issue_id_p" AND "event" = 'periodic';
jbe@0 1403 UPDATE "delegating_interest_snapshot"
jbe@0 1404 SET "event" = 'end_of_discussion'
jbe@0 1405 WHERE "issue_id" = "issue_id_p" AND "event" = 'periodic';
jbe@0 1406 UPDATE "direct_supporter_snapshot"
jbe@0 1407 SET "event" = 'end_of_discussion'
jbe@0 1408 WHERE "issue_id" = "issue_id_p" AND "event" = 'periodic';
jbe@0 1409 RETURN;
jbe@0 1410 END;
jbe@0 1411 $$;
jbe@0 1412
jbe@0 1413 COMMENT ON FUNCTION "set_snapshot_event"
jbe@0 1414 ( "issue"."id"%TYPE,
jbe@0 1415 "snapshot_event" )
jbe@0 1416 IS 'Change "event" attribute of the previous ''periodic'' snapshot';
jbe@0 1417
jbe@0 1418
jbe@0 1419
jbe@0 1420 ---------------------
jbe@0 1421 -- Freezing issues --
jbe@0 1422 ---------------------
jbe@0 1423
jbe@0 1424 CREATE FUNCTION "freeze_after_snapshot"
jbe@0 1425 ( "issue_id_p" "issue"."id"%TYPE )
jbe@0 1426 RETURNS VOID
jbe@0 1427 LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@0 1428 DECLARE
jbe@0 1429 "issue_row" "issue"%ROWTYPE;
jbe@0 1430 "policy_row" "policy"%ROWTYPE;
jbe@0 1431 "initiative_row" "initiative"%ROWTYPE;
jbe@0 1432 BEGIN
jbe@0 1433 SELECT * INTO "issue_row" FROM "issue" WHERE "id" = "issue_id_p";
jbe@0 1434 SELECT * INTO "policy_row"
jbe@0 1435 FROM "policy" WHERE "id" = "issue_row"."policy_id";
jbe@0 1436 PERFORM "set_snapshot_event"("issue_id_p", 'end_of_discussion');
jbe@0 1437 UPDATE "issue" SET "frozen" = now() WHERE "id" = "issue_id_p";
jbe@0 1438 FOR "initiative_row" IN
jbe@0 1439 SELECT * FROM "initiative" WHERE "issue_id" = "issue_id_p"
jbe@0 1440 LOOP
jbe@0 1441 IF
jbe@0 1442 "initiative_row"."satisfied_supporter_count" > 0 AND
jbe@0 1443 "initiative_row"."satisfied_supporter_count" *
jbe@0 1444 "policy_row"."initiative_quorum_den" >=
jbe@0 1445 "issue_row"."population" * "policy_row"."initiative_quorum_num"
jbe@0 1446 THEN
jbe@0 1447 UPDATE "initiative" SET "admitted" = TRUE
jbe@0 1448 WHERE "id" = "initiative_row"."id";
jbe@0 1449 ELSE
jbe@0 1450 UPDATE "initiative" SET "admitted" = FALSE
jbe@0 1451 WHERE "id" = "initiative_row"."id";
jbe@0 1452 END IF;
jbe@0 1453 END LOOP;
jbe@0 1454 RETURN;
jbe@0 1455 END;
jbe@0 1456 $$;
jbe@0 1457
jbe@0 1458 COMMENT ON FUNCTION "freeze_after_snapshot"
jbe@0 1459 ( "issue"."id"%TYPE )
jbe@0 1460 IS 'This function freezes an issue, but must only be called when "create_snapshot" was called in the same transaction';
jbe@0 1461
jbe@0 1462
jbe@0 1463 CREATE FUNCTION "manual_freeze"("issue_id_p" "issue"."id"%TYPE)
jbe@0 1464 RETURNS VOID
jbe@0 1465 LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@0 1466 DECLARE
jbe@0 1467 "issue_row" "issue"%ROWTYPE;
jbe@0 1468 BEGIN
jbe@0 1469 PERFORM "create_snapshot"("issue_id_p");
jbe@0 1470 PERFORM "freeze_after_snapshot"("issue_id_p");
jbe@0 1471 RETURN;
jbe@0 1472 END;
jbe@0 1473 $$;
jbe@0 1474
jbe@0 1475 COMMENT ON FUNCTION "freeze_after_snapshot"
jbe@0 1476 ( "issue"."id"%TYPE )
jbe@0 1477 IS 'Freeze an issue manually';
jbe@0 1478
jbe@0 1479
jbe@0 1480
jbe@0 1481 -----------------------
jbe@0 1482 -- Counting of votes --
jbe@0 1483 -----------------------
jbe@0 1484
jbe@0 1485
jbe@0 1486 CREATE FUNCTION "weight_of_added_delegations"
jbe@0 1487 ( "issue_id_p" "issue"."id"%TYPE,
jbe@0 1488 "member_id_p" "member"."id"%TYPE,
jbe@0 1489 "delegate_member_ids_p" "delegating_voter"."delegate_member_ids"%TYPE )
jbe@0 1490 RETURNS "direct_voter"."weight"%TYPE
jbe@0 1491 LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@0 1492 DECLARE
jbe@0 1493 "issue_delegation_row" "issue_delegation"%ROWTYPE;
jbe@0 1494 "delegate_member_ids_v" "delegating_voter"."delegate_member_ids"%TYPE;
jbe@0 1495 "weight_v" INT4;
jbe@0 1496 BEGIN
jbe@0 1497 "weight_v" := 0;
jbe@0 1498 FOR "issue_delegation_row" IN
jbe@0 1499 SELECT * FROM "issue_delegation"
jbe@0 1500 WHERE "trustee_id" = "member_id_p"
jbe@0 1501 AND "issue_id" = "issue_id_p"
jbe@0 1502 LOOP
jbe@0 1503 IF NOT EXISTS (
jbe@0 1504 SELECT NULL FROM "direct_voter"
jbe@0 1505 WHERE "member_id" = "issue_delegation_row"."truster_id"
jbe@0 1506 AND "issue_id" = "issue_id_p"
jbe@0 1507 ) AND NOT EXISTS (
jbe@0 1508 SELECT NULL FROM "delegating_voter"
jbe@0 1509 WHERE "member_id" = "issue_delegation_row"."truster_id"
jbe@0 1510 AND "issue_id" = "issue_id_p"
jbe@0 1511 ) THEN
jbe@0 1512 "delegate_member_ids_v" :=
jbe@0 1513 "member_id_p" || "delegate_member_ids_p";
jbe@0 1514 INSERT INTO "delegating_voter"
jbe@0 1515 ("member_id", "issue_id", "delegate_member_ids")
jbe@0 1516 VALUES (
jbe@0 1517 "issue_delegation_row"."truster_id",
jbe@0 1518 "issue_id_p",
jbe@0 1519 "delegate_member_ids_v"
jbe@0 1520 );
jbe@0 1521 "weight_v" := "weight_v" + 1 + "weight_of_added_delegations"(
jbe@0 1522 "issue_id_p",
jbe@0 1523 "issue_delegation_row"."truster_id",
jbe@0 1524 "delegate_member_ids_v"
jbe@0 1525 );
jbe@0 1526 END IF;
jbe@0 1527 END LOOP;
jbe@0 1528 RETURN "weight_v";
jbe@0 1529 END;
jbe@0 1530 $$;
jbe@0 1531
jbe@0 1532 COMMENT ON FUNCTION "weight_of_added_delegations"
jbe@0 1533 ( "issue"."id"%TYPE,
jbe@0 1534 "member"."id"%TYPE,
jbe@0 1535 "delegating_voter"."delegate_member_ids"%TYPE )
jbe@0 1536 IS 'Helper function for "add_vote_delegations" function';
jbe@0 1537
jbe@0 1538
jbe@0 1539 CREATE FUNCTION "add_vote_delegations"
jbe@0 1540 ( "issue_id_p" "issue"."id"%TYPE )
jbe@0 1541 RETURNS VOID
jbe@0 1542 LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@0 1543 DECLARE
jbe@0 1544 "member_id_v" "member"."id"%TYPE;
jbe@0 1545 BEGIN
jbe@0 1546 FOR "member_id_v" IN
jbe@0 1547 SELECT "member_id" FROM "direct_voter"
jbe@0 1548 WHERE "issue_id" = "issue_id_p"
jbe@0 1549 LOOP
jbe@0 1550 UPDATE "direct_voter" SET
jbe@0 1551 "weight" = "weight" + "weight_of_added_delegations"(
jbe@0 1552 "issue_id_p",
jbe@0 1553 "member_id_v",
jbe@0 1554 '{}'
jbe@0 1555 )
jbe@0 1556 WHERE "member_id" = "member_id_v"
jbe@0 1557 AND "issue_id" = "issue_id_p";
jbe@0 1558 END LOOP;
jbe@0 1559 RETURN;
jbe@0 1560 END;
jbe@0 1561 $$;
jbe@0 1562
jbe@0 1563 COMMENT ON FUNCTION "add_vote_delegations"
jbe@0 1564 ( "issue_id_p" "issue"."id"%TYPE )
jbe@0 1565 IS 'Helper function for "close_voting" function';
jbe@0 1566
jbe@0 1567
jbe@0 1568 CREATE FUNCTION "close_voting"("issue_id_p" "issue"."id"%TYPE)
jbe@0 1569 RETURNS VOID
jbe@0 1570 LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@0 1571 DECLARE
jbe@0 1572 "issue_row" "issue"%ROWTYPE;
jbe@0 1573 "member_id_v" "member"."id"%TYPE;
jbe@0 1574 BEGIN
jbe@0 1575 PERFORM "global_lock"();
jbe@0 1576 SELECT * INTO "issue_row" FROM "issue" WHERE "id" = "issue_id_p";
jbe@0 1577 DELETE FROM "delegating_voter"
jbe@0 1578 WHERE "issue_id" = "issue_id_p";
jbe@0 1579 DELETE FROM "direct_voter"
jbe@0 1580 WHERE "issue_id" = "issue_id_p"
jbe@0 1581 AND "autoreject" = TRUE;
jbe@0 1582 DELETE FROM "direct_voter" USING "member"
jbe@0 1583 WHERE "direct_voter"."member_id" = "member"."id"
jbe@0 1584 AND "direct_voter"."issue_id" = "issue_id_p"
jbe@0 1585 AND "member"."active" = FALSE;
jbe@0 1586 UPDATE "direct_voter" SET "weight" = 1
jbe@0 1587 WHERE "issue_id" = "issue_id_p";
jbe@0 1588 PERFORM "add_vote_delegations"("issue_id_p");
jbe@0 1589 FOR "member_id_v" IN
jbe@0 1590 SELECT "interest"."member_id"
jbe@0 1591 FROM "interest"
jbe@0 1592 LEFT JOIN "direct_voter"
jbe@0 1593 ON "interest"."member_id" = "direct_voter"."member_id"
jbe@0 1594 AND "interest"."issue_id" = "direct_voter"."issue_id"
jbe@0 1595 LEFT JOIN "delegating_voter"
jbe@0 1596 ON "interest"."member_id" = "delegating_voter"."member_id"
jbe@0 1597 AND "interest"."issue_id" = "delegating_voter"."issue_id"
jbe@0 1598 WHERE "interest"."issue_id" = "issue_id_p"
jbe@0 1599 AND "interest"."autoreject" = TRUE
jbe@0 1600 AND "direct_voter"."member_id" ISNULL
jbe@0 1601 AND "delegating_voter"."member_id" ISNULL
jbe@0 1602 UNION SELECT "membership"."member_id"
jbe@0 1603 FROM "membership"
jbe@0 1604 LEFT JOIN "interest"
jbe@0 1605 ON "membership"."member_id" = "interest"."member_id"
jbe@0 1606 AND "interest"."issue_id" = "issue_id_p"
jbe@0 1607 LEFT JOIN "direct_voter"
jbe@0 1608 ON "membership"."member_id" = "direct_voter"."member_id"
jbe@0 1609 AND "direct_voter"."issue_id" = "issue_id_p"
jbe@0 1610 LEFT JOIN "delegating_voter"
jbe@0 1611 ON "membership"."member_id" = "delegating_voter"."member_id"
jbe@0 1612 AND "delegating_voter"."issue_id" = "issue_id_p"
jbe@0 1613 WHERE "membership"."area_id" = "issue_row"."area_id"
jbe@0 1614 AND "membership"."autoreject" = TRUE
jbe@0 1615 AND "interest"."autoreject" ISNULL
jbe@0 1616 AND "direct_voter"."member_id" ISNULL
jbe@0 1617 AND "delegating_voter"."member_id" ISNULL
jbe@0 1618 LOOP
jbe@0 1619 INSERT INTO "direct_voter" ("member_id", "issue_id", "autoreject")
jbe@0 1620 VALUES ("member_id_v", "issue_id_p", TRUE);
jbe@0 1621 INSERT INTO "vote" (
jbe@0 1622 "member_id",
jbe@0 1623 "issue_id",
jbe@0 1624 "initiative_id",
jbe@0 1625 "grade"
jbe@0 1626 ) SELECT
jbe@0 1627 "member_id_v" AS "member_id",
jbe@0 1628 "issue_id_p" AS "issue_id",
jbe@0 1629 "id" AS "initiative_id",
jbe@0 1630 -1 AS "grade"
jbe@0 1631 FROM "initiative" WHERE "issue_id" = "issue_id_p";
jbe@0 1632 END LOOP;
jbe@0 1633 PERFORM "add_vote_delegations"("issue_id_p");
jbe@0 1634 UPDATE "initiative" SET
jbe@0 1635 "positive_votes" = "subquery"."positive_votes",
jbe@0 1636 "negative_votes" = "subquery"."negative_votes"
jbe@0 1637 FROM (
jbe@0 1638 SELECT
jbe@0 1639 "initiative_id",
jbe@0 1640 sum(
jbe@0 1641 CASE WHEN "grade" > 0 THEN "direct_voter"."weight" ELSE 0 END
jbe@0 1642 ) AS "positive_votes",
jbe@0 1643 sum (
jbe@0 1644 CASE WHEN "grade" < 0 THEN "direct_voter"."weight" ELSE 0 END
jbe@0 1645 ) AS "negative_votes"
jbe@0 1646 FROM "vote" JOIN "direct_voter"
jbe@0 1647 ON "vote"."member_id" = "direct_voter"."member_id"
jbe@0 1648 AND "vote"."issue_id" = "direct_voter"."issue_id"
jbe@0 1649 WHERE "vote"."issue_id" = "issue_id_p"
jbe@0 1650 GROUP BY "initiative_id"
jbe@0 1651 ) AS "subquery"
jbe@0 1652 WHERE "initiative"."admitted"
jbe@0 1653 AND "initiative"."id" = "subquery"."initiative_id";
jbe@0 1654 UPDATE "issue" SET "closed" = now() WHERE "id" = "issue_id_p";
jbe@0 1655 END;
jbe@0 1656 $$;
jbe@0 1657
jbe@0 1658 COMMENT ON FUNCTION "close_voting"
jbe@0 1659 ( "issue"."id"%TYPE )
jbe@0 1660 IS 'Closes the voting on an issue, and calculates positive and negative votes for each initiative; The ranking is not calculated yet, to keep the (locking) transaction short.';
jbe@0 1661
jbe@0 1662
jbe@0 1663 CREATE FUNCTION "init_array"("dim_p" INTEGER)
jbe@0 1664 RETURNS INT4[]
jbe@0 1665 LANGUAGE 'plpgsql' IMMUTABLE AS $$
jbe@0 1666 DECLARE
jbe@0 1667 "i" INTEGER;
jbe@0 1668 "ary_text_v" TEXT;
jbe@0 1669 BEGIN
jbe@0 1670 IF "dim_p" >= 1 THEN
jbe@0 1671 "ary_text_v" := '{NULL';
jbe@0 1672 "i" := "dim_p";
jbe@0 1673 LOOP
jbe@0 1674 "i" := "i" - 1;
jbe@0 1675 EXIT WHEN "i" = 0;
jbe@0 1676 "ary_text_v" := "ary_text_v" || ',NULL';
jbe@0 1677 END LOOP;
jbe@0 1678 "ary_text_v" := "ary_text_v" || '}';
jbe@0 1679 RETURN "ary_text_v"::INT4[][];
jbe@0 1680 ELSE
jbe@0 1681 RAISE EXCEPTION 'Dimension needs to be at least 1.';
jbe@0 1682 END IF;
jbe@0 1683 END;
jbe@0 1684 $$;
jbe@0 1685
jbe@0 1686 COMMENT ON FUNCTION "init_array"(INTEGER) IS 'Needed for PostgreSQL < 8.4, due to missing "array_fill" function';
jbe@0 1687
jbe@0 1688
jbe@0 1689 CREATE FUNCTION "init_square_matrix"("dim_p" INTEGER)
jbe@0 1690 RETURNS INT4[][]
jbe@0 1691 LANGUAGE 'plpgsql' IMMUTABLE AS $$
jbe@0 1692 DECLARE
jbe@0 1693 "i" INTEGER;
jbe@0 1694 "row_text_v" TEXT;
jbe@0 1695 "ary_text_v" TEXT;
jbe@0 1696 BEGIN
jbe@0 1697 IF "dim_p" >= 1 THEN
jbe@0 1698 "row_text_v" := '{NULL';
jbe@0 1699 "i" := "dim_p";
jbe@0 1700 LOOP
jbe@0 1701 "i" := "i" - 1;
jbe@0 1702 EXIT WHEN "i" = 0;
jbe@0 1703 "row_text_v" := "row_text_v" || ',NULL';
jbe@0 1704 END LOOP;
jbe@0 1705 "row_text_v" := "row_text_v" || '}';
jbe@0 1706 "ary_text_v" := '{' || "row_text_v";
jbe@0 1707 "i" := "dim_p";
jbe@0 1708 LOOP
jbe@0 1709 "i" := "i" - 1;
jbe@0 1710 EXIT WHEN "i" = 0;
jbe@0 1711 "ary_text_v" := "ary_text_v" || ',' || "row_text_v";
jbe@0 1712 END LOOP;
jbe@0 1713 "ary_text_v" := "ary_text_v" || '}';
jbe@0 1714 RETURN "ary_text_v"::INT4[][];
jbe@0 1715 ELSE
jbe@0 1716 RAISE EXCEPTION 'Dimension needs to be at least 1.';
jbe@0 1717 END IF;
jbe@0 1718 END;
jbe@0 1719 $$;
jbe@0 1720
jbe@0 1721 COMMENT ON FUNCTION "init_square_matrix"(INTEGER) IS 'Needed for PostgreSQL < 8.4, due to missing "array_fill" function';
jbe@0 1722
jbe@0 1723
jbe@0 1724 CREATE FUNCTION "calculate_ranks"("issue_id_p" "issue"."id"%TYPE)
jbe@0 1725 RETURNS VOID
jbe@0 1726 LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@0 1727 DECLARE
jbe@0 1728 "dimension_v" INTEGER;
jbe@0 1729 "matrix" INT4[][];
jbe@0 1730 "i" INTEGER;
jbe@0 1731 "j" INTEGER;
jbe@0 1732 "k" INTEGER;
jbe@0 1733 "battle_row" "battle"%ROWTYPE;
jbe@0 1734 "rank_ary" INT4[];
jbe@0 1735 "rank_v" INT4;
jbe@0 1736 "done_v" INTEGER;
jbe@0 1737 "winners_ary" INTEGER[];
jbe@0 1738 "initiative_id_v" "initiative"."id"%TYPE;
jbe@0 1739 BEGIN
jbe@0 1740 PERFORM NULL FROM "issue" WHERE "id" = "issue_id_p" FOR UPDATE;
jbe@0 1741 -- Prepare matrix for Schulze-Method:
jbe@0 1742 SELECT count(1) INTO "dimension_v"
jbe@0 1743 FROM "battle_participant" WHERE "issue_id" = "issue_id_p";
jbe@0 1744 IF "dimension_v" = 1 THEN
jbe@0 1745 UPDATE "initiative" SET
jbe@0 1746 "rank" = 1
jbe@0 1747 FROM "battle_participant"
jbe@0 1748 WHERE "initiative"."issue_id" = "issue_id_p"
jbe@0 1749 AND "initiative"."id" = "battle_participant"."initiative_id";
jbe@0 1750 ELSIF "dimension_v" > 1 THEN
jbe@0 1751 "matrix" := "init_square_matrix"("dimension_v"); -- TODO: replace by "array_fill" function (PostgreSQL 8.4)
jbe@0 1752 "i" := 1;
jbe@0 1753 "j" := 2;
jbe@0 1754 -- Fill matrix with data from "battle" view
jbe@0 1755 FOR "battle_row" IN
jbe@0 1756 SELECT * FROM "battle" WHERE "issue_id" = "issue_id_p"
jbe@0 1757 ORDER BY "winning_initiative_id", "losing_initiative_id"
jbe@0 1758 LOOP
jbe@0 1759 "matrix"["i"]["j"] := "battle_row"."count";
jbe@0 1760 IF "j" = "dimension_v" THEN
jbe@0 1761 "i" := "i" + 1;
jbe@0 1762 "j" := 1;
jbe@0 1763 ELSE
jbe@0 1764 "j" := "j" + 1;
jbe@0 1765 IF "j" = "i" THEN
jbe@0 1766 "j" := "j" + 1;
jbe@0 1767 END IF;
jbe@0 1768 END IF;
jbe@0 1769 END LOOP;
jbe@0 1770 IF "i" != "dimension_v" OR "j" != "dimension_v" + 1 THEN
jbe@0 1771 RAISE EXCEPTION 'Wrong battle count (should not happen)';
jbe@0 1772 END IF;
jbe@0 1773 -- Delete losers from matrix:
jbe@0 1774 "i" := 1;
jbe@0 1775 LOOP
jbe@0 1776 "j" := "i" + 1;
jbe@0 1777 LOOP
jbe@0 1778 IF "i" != "j" THEN
jbe@0 1779 IF "matrix"["i"]["j"] < "matrix"["j"]["i"] THEN
jbe@0 1780 "matrix"["i"]["j"] := 0;
jbe@0 1781 ELSIF matrix[j][i] < matrix[i][j] THEN
jbe@0 1782 "matrix"["j"]["i"] := 0;
jbe@0 1783 ELSE
jbe@0 1784 "matrix"["i"]["j"] := 0;
jbe@0 1785 "matrix"["j"]["i"] := 0;
jbe@0 1786 END IF;
jbe@0 1787 END IF;
jbe@0 1788 EXIT WHEN "j" = "dimension_v";
jbe@0 1789 "j" := "j" + 1;
jbe@0 1790 END LOOP;
jbe@0 1791 EXIT WHEN "i" = "dimension_v" - 1;
jbe@0 1792 "i" := "i" + 1;
jbe@0 1793 END LOOP;
jbe@0 1794 -- Find best paths:
jbe@0 1795 "i" := 1;
jbe@0 1796 LOOP
jbe@0 1797 "j" := 1;
jbe@0 1798 LOOP
jbe@0 1799 IF "i" != "j" THEN
jbe@0 1800 "k" := 1;
jbe@0 1801 LOOP
jbe@0 1802 IF "i" != "k" AND "j" != "k" THEN
jbe@0 1803 IF "matrix"["j"]["i"] < "matrix"["i"]["k"] THEN
jbe@0 1804 IF "matrix"["j"]["i"] > "matrix"["j"]["k"] THEN
jbe@0 1805 "matrix"["j"]["k"] := "matrix"["j"]["i"];
jbe@0 1806 END IF;
jbe@0 1807 ELSE
jbe@0 1808 IF "matrix"["i"]["k"] > "matrix"["j"]["k"] THEN
jbe@0 1809 "matrix"["j"]["k"] := "matrix"["i"]["k"];
jbe@0 1810 END IF;
jbe@0 1811 END IF;
jbe@0 1812 END IF;
jbe@0 1813 EXIT WHEN "k" = "dimension_v";
jbe@0 1814 "k" := "k" + 1;
jbe@0 1815 END LOOP;
jbe@0 1816 END IF;
jbe@0 1817 EXIT WHEN "j" = "dimension_v";
jbe@0 1818 "j" := "j" + 1;
jbe@0 1819 END LOOP;
jbe@0 1820 EXIT WHEN "i" = "dimension_v";
jbe@0 1821 "i" := "i" + 1;
jbe@0 1822 END LOOP;
jbe@0 1823 -- Determine order of winners:
jbe@0 1824 "rank_ary" := "init_array"("dimension_v"); -- TODO: replace by "array_fill" function (PostgreSQL 8.4)
jbe@0 1825 "rank_v" := 1;
jbe@0 1826 "done_v" := 0;
jbe@0 1827 LOOP
jbe@0 1828 "winners_ary" := '{}';
jbe@0 1829 "i" := 1;
jbe@0 1830 LOOP
jbe@0 1831 IF "rank_ary"["i"] ISNULL THEN
jbe@0 1832 "j" := 1;
jbe@0 1833 LOOP
jbe@0 1834 IF
jbe@0 1835 "i" != "j" AND
jbe@0 1836 "rank_ary"["j"] ISNULL AND
jbe@0 1837 "matrix"["j"]["i"] > "matrix"["i"]["j"]
jbe@0 1838 THEN
jbe@0 1839 -- someone else is better
jbe@0 1840 EXIT;
jbe@0 1841 END IF;
jbe@0 1842 IF "j" = "dimension_v" THEN
jbe@0 1843 -- noone is better
jbe@0 1844 "winners_ary" := "winners_ary" || "i";
jbe@0 1845 EXIT;
jbe@0 1846 END IF;
jbe@0 1847 "j" := "j" + 1;
jbe@0 1848 END LOOP;
jbe@0 1849 END IF;
jbe@0 1850 EXIT WHEN "i" = "dimension_v";
jbe@0 1851 "i" := "i" + 1;
jbe@0 1852 END LOOP;
jbe@0 1853 "i" := 1;
jbe@0 1854 LOOP
jbe@0 1855 "rank_ary"["winners_ary"["i"]] := "rank_v";
jbe@0 1856 "done_v" := "done_v" + 1;
jbe@0 1857 EXIT WHEN "i" = array_upper("winners_ary", 1);
jbe@0 1858 "i" := "i" + 1;
jbe@0 1859 END LOOP;
jbe@0 1860 EXIT WHEN "done_v" = "dimension_v";
jbe@0 1861 "rank_v" := "rank_v" + 1;
jbe@0 1862 END LOOP;
jbe@0 1863 -- write preliminary ranks:
jbe@0 1864 "i" := 1;
jbe@0 1865 FOR "initiative_id_v" IN
jbe@0 1866 SELECT "initiative"."id"
jbe@0 1867 FROM "initiative" JOIN "battle_participant"
jbe@0 1868 ON "initiative"."id" = "battle_participant"."initiative_id"
jbe@0 1869 WHERE "initiative"."issue_id" = "issue_id_p"
jbe@0 1870 ORDER BY "initiative"."id"
jbe@0 1871 LOOP
jbe@0 1872 UPDATE "initiative" SET "rank" = "rank_ary"["i"]
jbe@0 1873 WHERE "id" = "initiative_id_v";
jbe@0 1874 "i" := "i" + 1;
jbe@0 1875 END LOOP;
jbe@0 1876 IF "i" != "dimension_v" + 1 THEN
jbe@0 1877 RAISE EXCEPTION 'Wrong winner count (should not happen)';
jbe@0 1878 END IF;
jbe@0 1879 -- straighten ranks (start counting with 1, no equal ranks):
jbe@0 1880 "rank_v" := 1;
jbe@0 1881 FOR "initiative_id_v" IN
jbe@0 1882 SELECT "id" FROM "initiative"
jbe@0 1883 WHERE "issue_id" = "issue_id_p" AND "rank" NOTNULL
jbe@0 1884 ORDER BY
jbe@0 1885 "rank",
jbe@0 1886 "vote_ratio"("positive_votes", "negative_votes") DESC,
jbe@0 1887 "id"
jbe@0 1888 LOOP
jbe@0 1889 UPDATE "initiative" SET "rank" = "rank_v"
jbe@0 1890 WHERE "id" = "initiative_id_v";
jbe@0 1891 "rank_v" := "rank_v" + 1;
jbe@0 1892 END LOOP;
jbe@0 1893 END IF;
jbe@0 1894 -- mark issue as finished
jbe@0 1895 UPDATE "issue" SET "ranks_available" = TRUE
jbe@0 1896 WHERE "id" = "issue_id_p";
jbe@0 1897 RETURN;
jbe@0 1898 END;
jbe@0 1899 $$;
jbe@0 1900
jbe@0 1901 COMMENT ON FUNCTION "calculate_ranks"
jbe@0 1902 ( "issue"."id"%TYPE )
jbe@0 1903 IS 'Determine ranking (Votes have to be counted first)';
jbe@0 1904
jbe@0 1905
jbe@0 1906
jbe@0 1907 -----------------------------
jbe@0 1908 -- Automatic state changes --
jbe@0 1909 -----------------------------
jbe@0 1910
jbe@0 1911
jbe@0 1912 CREATE FUNCTION "check_issue"
jbe@0 1913 ( "issue_id_p" "issue"."id"%TYPE )
jbe@0 1914 RETURNS VOID
jbe@0 1915 LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@0 1916 DECLARE
jbe@0 1917 "issue_row" "issue"%ROWTYPE;
jbe@0 1918 "policy_row" "policy"%ROWTYPE;
jbe@0 1919 "voting_requested_v" BOOLEAN;
jbe@0 1920 BEGIN
jbe@0 1921 PERFORM "global_lock"();
jbe@0 1922 SELECT * INTO "issue_row" FROM "issue" WHERE "id" = "issue_id_p";
jbe@0 1923 IF "issue_row"."closed" ISNULL THEN
jbe@0 1924 SELECT * INTO "policy_row" FROM "policy"
jbe@0 1925 WHERE "id" = "issue_row"."policy_id";
jbe@0 1926 IF "issue_row"."frozen" ISNULL THEN
jbe@0 1927 PERFORM "create_snapshot"("issue_id_p");
jbe@0 1928 SELECT * INTO "issue_row" FROM "issue" WHERE "id" = "issue_id_p";
jbe@0 1929 END IF;
jbe@0 1930 IF "issue_row"."accepted" ISNULL THEN
jbe@0 1931 IF EXISTS (
jbe@0 1932 SELECT NULL FROM "initiative"
jbe@0 1933 WHERE "issue_id" = "issue_id_p"
jbe@0 1934 AND "supporter_count" > 0
jbe@0 1935 AND "supporter_count" * "policy_row"."issue_quorum_den"
jbe@0 1936 >= "issue_row"."population" * "policy_row"."issue_quorum_num"
jbe@0 1937 ) THEN
jbe@0 1938 "issue_row"."accepted" = now(); -- NOTE: "issue_row" used later
jbe@0 1939 UPDATE "issue" SET "accepted" = "issue_row"."accepted"
jbe@0 1940 WHERE "id" = "issue_row"."id";
jbe@0 1941 ELSIF
jbe@0 1942 now() > "issue_row"."created" + "policy_row"."admission_time"
jbe@0 1943 THEN
jbe@0 1944 PERFORM "set_snapshot_event"("issue_id_p", 'end_of_admission');
jbe@0 1945 UPDATE "issue" SET "closed" = now()
jbe@0 1946 WHERE "id" = "issue_row"."id";
jbe@0 1947 END IF;
jbe@0 1948 END IF;
jbe@0 1949 IF
jbe@0 1950 "issue_row"."accepted" NOTNULL AND
jbe@0 1951 "issue_row"."frozen" ISNULL
jbe@0 1952 THEN
jbe@0 1953 SELECT
jbe@0 1954 CASE
jbe@0 1955 WHEN "vote_now" * 2 > "issue_row"."population" THEN
jbe@0 1956 TRUE
jbe@0 1957 WHEN "vote_later" * 2 > "issue_row"."population" THEN
jbe@0 1958 FALSE
jbe@0 1959 ELSE NULL
jbe@0 1960 END
jbe@0 1961 INTO "voting_requested_v"
jbe@0 1962 FROM "issue" WHERE "id" = "issue_id_p";
jbe@0 1963 IF
jbe@0 1964 "voting_requested_v" OR (
jbe@0 1965 "voting_requested_v" ISNULL AND now() >
jbe@0 1966 "issue_row"."accepted" + "policy_row"."discussion_time"
jbe@0 1967 )
jbe@0 1968 THEN
jbe@0 1969 PERFORM "freeze_after_snapshot"("issue_id_p");
jbe@0 1970 END IF;
jbe@0 1971 END IF;
jbe@0 1972 IF
jbe@0 1973 "issue_row"."frozen" NOTNULL AND
jbe@0 1974 now() > "issue_row"."frozen" + "policy_row"."voting_time"
jbe@0 1975 THEN
jbe@0 1976 PERFORM "close_voting"("issue_id_p");
jbe@0 1977 END IF;
jbe@0 1978 END IF;
jbe@0 1979 RETURN;
jbe@0 1980 END;
jbe@0 1981 $$;
jbe@0 1982
jbe@0 1983 COMMENT ON FUNCTION "check_issue"
jbe@0 1984 ( "issue"."id"%TYPE )
jbe@0 1985 IS 'Precalculate supporter counts etc. for a given issue, and check, if status change is required; At end of voting the ranking is not calculated by this function, but must be calculated in a seperate transaction using the "calculate_ranks" function.';
jbe@0 1986
jbe@0 1987
jbe@0 1988 CREATE FUNCTION "check_everything"()
jbe@0 1989 RETURNS VOID
jbe@0 1990 LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@0 1991 DECLARE
jbe@0 1992 "issue_id_v" "issue"."id"%TYPE;
jbe@0 1993 BEGIN
jbe@1 1994 DELETE FROM "expired_session";
jbe@0 1995 FOR "issue_id_v" IN
jbe@1 1996 SELECT "id" FROM "open_issue"
jbe@0 1997 LOOP
jbe@0 1998 PERFORM "check_issue"("issue_id_v");
jbe@0 1999 END LOOP;
jbe@0 2000 FOR "issue_id_v" IN
jbe@1 2001 SELECT "id" FROM "issue_with_ranks_missing"
jbe@0 2002 LOOP
jbe@0 2003 PERFORM "calculate_ranks"("issue_id_v");
jbe@0 2004 END LOOP;
jbe@0 2005 RETURN;
jbe@0 2006 END;
jbe@0 2007 $$;
jbe@0 2008
jbe@0 2009 COMMENT ON FUNCTION "check_everything"() IS 'Perform "check_issue" for every open issue, and if possible, automatically calculate ranks. Use this function only for development and debugging purposes, as long transactions with exclusive locking may result.';
jbe@0 2010
jbe@0 2011
jbe@0 2012
jbe@0 2013 COMMIT;

Impressum / About Us