liquid_feedback_core

annotate core.sql @ 4:6133c0a62378

Version beta5

Precalculation of certain values to increase performance:
- Count of active members per area
- Count of active members altogether
- Count of people involved in the voting process for an issue

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

Impressum / About Us