liquid_feedback_core

annotate core.sql @ 22:063baac35f79

Copy admission_, discussion_, verification_ and voting_time values into issue
and use copied values for issue state changes
author jbe
date Sat Feb 06 03:31:13 2010 +0100 (2010-02-06)
parents 406090b1ed8e
children 137c98fa0b4f
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@5 8 CREATE VIEW "liquid_feedback_version" AS
jbe@19 9 SELECT * FROM (VALUES ('beta19-dev', NULL, NULL, NULL))
jbe@5 10 AS "subquery"("string", "major", "minor", "revision");
jbe@5 11
jbe@0 12
jbe@0 13
jbe@7 14 ----------------------
jbe@7 15 -- Full text search --
jbe@7 16 ----------------------
jbe@7 17
jbe@7 18
jbe@7 19 CREATE FUNCTION "text_search_query"("query_text_p" TEXT)
jbe@7 20 RETURNS TSQUERY
jbe@7 21 LANGUAGE 'plpgsql' IMMUTABLE AS $$
jbe@7 22 BEGIN
jbe@7 23 RETURN plainto_tsquery('pg_catalog.simple', "query_text_p");
jbe@7 24 END;
jbe@7 25 $$;
jbe@7 26
jbe@7 27 COMMENT ON FUNCTION "text_search_query"(TEXT) IS 'Usage: WHERE "text_search_data" @@ "text_search_query"(''<user query>'')';
jbe@7 28
jbe@7 29
jbe@7 30 CREATE FUNCTION "highlight"
jbe@7 31 ( "body_p" TEXT,
jbe@7 32 "query_text_p" TEXT )
jbe@7 33 RETURNS TEXT
jbe@7 34 LANGUAGE 'plpgsql' IMMUTABLE AS $$
jbe@7 35 BEGIN
jbe@7 36 RETURN ts_headline(
jbe@7 37 'pg_catalog.simple',
jbe@8 38 replace(replace("body_p", e'\\', e'\\\\'), '*', e'\\*'),
jbe@7 39 "text_search_query"("query_text_p"),
jbe@7 40 'StartSel=* StopSel=* HighlightAll=TRUE' );
jbe@7 41 END;
jbe@7 42 $$;
jbe@7 43
jbe@7 44 COMMENT ON FUNCTION "highlight"
jbe@7 45 ( "body_p" TEXT,
jbe@7 46 "query_text_p" TEXT )
jbe@7 47 IS 'For a given a user query this function encapsulates all matches with asterisks. Asterisks and backslashes being already present are preceeded with one extra backslash.';
jbe@7 48
jbe@7 49
jbe@7 50
jbe@0 51 -------------------------
jbe@0 52 -- Tables and indicies --
jbe@0 53 -------------------------
jbe@0 54
jbe@8 55
jbe@0 56 CREATE TABLE "member" (
jbe@0 57 "id" SERIAL4 PRIMARY KEY,
jbe@13 58 "created" TIMESTAMPTZ NOT NULL DEFAULT now(),
jbe@0 59 "login" TEXT NOT NULL UNIQUE,
jbe@0 60 "password" TEXT,
jbe@0 61 "active" BOOLEAN NOT NULL DEFAULT TRUE,
jbe@0 62 "admin" BOOLEAN NOT NULL DEFAULT FALSE,
jbe@7 63 "notify_email" TEXT,
jbe@11 64 "notify_email_unconfirmed" TEXT,
jbe@11 65 "notify_email_secret" TEXT UNIQUE,
jbe@11 66 "notify_email_secret_expiry" TIMESTAMPTZ,
jbe@11 67 "password_reset_secret" TEXT UNIQUE,
jbe@11 68 "password_reset_secret_expiry" TIMESTAMPTZ,
jbe@7 69 "name" TEXT NOT NULL UNIQUE,
jbe@7 70 "identification" TEXT UNIQUE,
jbe@7 71 "organizational_unit" TEXT,
jbe@7 72 "internal_posts" TEXT,
jbe@7 73 "realname" TEXT,
jbe@7 74 "birthday" DATE,
jbe@7 75 "address" TEXT,
jbe@7 76 "email" TEXT,
jbe@7 77 "xmpp_address" TEXT,
jbe@7 78 "website" TEXT,
jbe@7 79 "phone" TEXT,
jbe@7 80 "mobile_phone" TEXT,
jbe@7 81 "profession" TEXT,
jbe@7 82 "external_memberships" TEXT,
jbe@7 83 "external_posts" TEXT,
jbe@7 84 "statement" TEXT,
jbe@10 85 "text_search_data" TSVECTOR );
jbe@0 86 CREATE INDEX "member_active_idx" ON "member" ("active");
jbe@8 87 CREATE INDEX "member_text_search_data_idx" ON "member" USING gin ("text_search_data");
jbe@7 88 CREATE TRIGGER "update_text_search_data"
jbe@7 89 BEFORE INSERT OR UPDATE ON "member"
jbe@7 90 FOR EACH ROW EXECUTE PROCEDURE
jbe@7 91 tsvector_update_trigger('text_search_data', 'pg_catalog.simple',
jbe@7 92 "name", "identification", "organizational_unit", "internal_posts",
jbe@7 93 "realname", "external_memberships", "external_posts", "statement" );
jbe@0 94
jbe@0 95 COMMENT ON TABLE "member" IS 'Users of the system, e.g. members of an organization';
jbe@0 96
jbe@10 97 COMMENT ON COLUMN "member"."login" IS 'Login name';
jbe@10 98 COMMENT ON COLUMN "member"."password" IS 'Password (preferably as crypto-hash, depending on the frontend or access layer)';
jbe@10 99 COMMENT ON COLUMN "member"."active" IS 'Inactive members can not login and their supports/votes are not counted by the system.';
jbe@10 100 COMMENT ON COLUMN "member"."admin" IS 'TRUE for admins, which can administrate other users and setup policies and areas';
jbe@10 101 COMMENT ON COLUMN "member"."notify_email" IS 'Email address where notifications of the system are sent to';
jbe@10 102 COMMENT ON COLUMN "member"."notify_email_unconfirmed" IS 'Unconfirmed email address provided by the member to be copied into "notify_email" field after verification';
jbe@10 103 COMMENT ON COLUMN "member"."notify_email_secret" IS 'Secret sent to the address in "notify_email_unconformed"';
jbe@10 104 COMMENT ON COLUMN "member"."notify_email_secret_expiry" IS 'Expiry date/time for "notify_email_secret"';
jbe@10 105 COMMENT ON COLUMN "member"."name" IS 'Distinct name of the member';
jbe@10 106 COMMENT ON COLUMN "member"."identification" IS 'Optional identification number or code of the member';
jbe@10 107 COMMENT ON COLUMN "member"."organizational_unit" IS 'Branch or division of the organization the member belongs to';
jbe@10 108 COMMENT ON COLUMN "member"."internal_posts" IS 'Posts (offices) of the member inside the organization';
jbe@10 109 COMMENT ON COLUMN "member"."realname" IS 'Real name of the member, may be identical with "name"';
jbe@10 110 COMMENT ON COLUMN "member"."email" IS 'Published email address of the member; not used for system notifications';
jbe@10 111 COMMENT ON COLUMN "member"."external_memberships" IS 'Other organizations the member is involved in';
jbe@10 112 COMMENT ON COLUMN "member"."external_posts" IS 'Posts (offices) outside the organization';
jbe@10 113 COMMENT ON COLUMN "member"."statement" IS 'Freely chosen text of the member for his homepage within the system';
jbe@7 114
jbe@7 115
jbe@13 116 CREATE TABLE "member_history" (
jbe@13 117 "id" SERIAL8 PRIMARY KEY,
jbe@13 118 "member_id" INT4 NOT NULL REFERENCES "member" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@13 119 "until" TIMESTAMPTZ NOT NULL DEFAULT now(),
jbe@13 120 "login" TEXT NOT NULL,
jbe@13 121 "name" TEXT NOT NULL );
jbe@13 122
jbe@13 123 COMMENT ON TABLE "member_history" IS 'Filled by trigger; keeps information about old names and login names of members';
jbe@13 124
jbe@13 125 COMMENT ON COLUMN "member_history"."id" IS 'Primary key, which can be used to sort entries correctly (and time warp resistant)';
jbe@13 126 COMMENT ON COLUMN "member_history"."until" IS 'Timestamp until the name and login had been valid';
jbe@13 127
jbe@13 128
jbe@9 129 CREATE TABLE "invite_code" (
jbe@9 130 "code" TEXT PRIMARY KEY,
jbe@9 131 "created" TIMESTAMPTZ NOT NULL DEFAULT now(),
jbe@9 132 "used" TIMESTAMPTZ,
jbe@9 133 "member_id" INT4 UNIQUE REFERENCES "member" ("id") ON DELETE SET NULL ON UPDATE CASCADE,
jbe@9 134 "comment" TEXT,
jbe@9 135 CONSTRAINT "only_used_codes_may_refer_to_member" CHECK ("used" NOTNULL OR "member_id" ISNULL) );
jbe@9 136
jbe@9 137 COMMENT ON TABLE "invite_code" IS 'Invite codes can be used once to create a new member account.';
jbe@9 138
jbe@9 139 COMMENT ON COLUMN "invite_code"."code" IS 'Secret code';
jbe@9 140 COMMENT ON COLUMN "invite_code"."created" IS 'Time of creation of the secret code';
jbe@9 141 COMMENT ON COLUMN "invite_code"."used" IS 'NULL, if not used yet, otherwise tells when this code was used to create a member account';
jbe@9 142 COMMENT ON COLUMN "invite_code"."member_id" IS 'References the member whose account was created with this code';
jbe@9 143 COMMENT ON COLUMN "invite_code"."comment" IS 'Comment on the code, which is to be used for administrative reasons only';
jbe@9 144
jbe@9 145
jbe@9 146 CREATE TABLE "setting" (
jbe@9 147 PRIMARY KEY ("member_id", "key"),
jbe@9 148 "member_id" INT4 REFERENCES "member" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@9 149 "key" TEXT NOT NULL,
jbe@9 150 "value" TEXT NOT NULL );
jbe@9 151 CREATE INDEX "setting_key_idx" ON "setting" ("key");
jbe@9 152
jbe@16 153 COMMENT ON TABLE "setting" IS 'Place to store a frontend specific member setting as a string';
jbe@9 154
jbe@9 155 COMMENT ON COLUMN "setting"."key" IS 'Name of the setting, preceded by a frontend specific prefix';
jbe@9 156
jbe@9 157
jbe@16 158 CREATE TABLE "setting_map" (
jbe@16 159 PRIMARY KEY ("member_id", "key", "subkey"),
jbe@16 160 "member_id" INT4 REFERENCES "member" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@16 161 "key" TEXT NOT NULL,
jbe@16 162 "subkey" TEXT NOT NULL,
jbe@16 163 "value" TEXT NOT NULL );
jbe@16 164 CREATE INDEX "setting_map_key_idx" ON "setting_map" ("key");
jbe@16 165
jbe@16 166 COMMENT ON TABLE "setting_map" IS 'Place to store a frontend specific member setting as a map of key value pairs';
jbe@16 167
jbe@16 168 COMMENT ON COLUMN "setting_map"."key" IS 'Name of the setting, preceded by a frontend specific prefix';
jbe@16 169 COMMENT ON COLUMN "setting_map"."subkey" IS 'Key of a map entry';
jbe@16 170 COMMENT ON COLUMN "setting_map"."value" IS 'Value of a map entry';
jbe@16 171
jbe@16 172
jbe@7 173 CREATE TYPE "member_image_type" AS ENUM ('photo', 'avatar');
jbe@7 174
jbe@7 175 COMMENT ON TYPE "member_image_type" IS 'Types of images for a member';
jbe@7 176
jbe@7 177
jbe@7 178 CREATE TABLE "member_image" (
jbe@7 179 PRIMARY KEY ("member_id", "image_type", "scaled"),
jbe@7 180 "member_id" INT4 REFERENCES "member" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@7 181 "image_type" "member_image_type",
jbe@7 182 "scaled" BOOLEAN,
jbe@7 183 "content_type" TEXT,
jbe@7 184 "data" BYTEA NOT NULL );
jbe@7 185
jbe@7 186 COMMENT ON TABLE "member_image" IS 'Images of members';
jbe@7 187
jbe@7 188 COMMENT ON COLUMN "member_image"."scaled" IS 'FALSE for original image, TRUE for scaled version of the image';
jbe@0 189
jbe@0 190
jbe@4 191 CREATE TABLE "member_count" (
jbe@5 192 "calculated" TIMESTAMPTZ NOT NULL DEFAULT NOW(),
jbe@5 193 "total_count" INT4 NOT NULL );
jbe@4 194
jbe@5 195 COMMENT ON TABLE "member_count" IS 'Contains one row which contains the total count of active(!) members and a timestamp indicating when the total member count and area member counts were calculated';
jbe@4 196
jbe@5 197 COMMENT ON COLUMN "member_count"."calculated" IS 'timestamp indicating when the total member count and area member counts were calculated';
jbe@5 198 COMMENT ON COLUMN "member_count"."total_count" IS 'Total count of active(!) members';
jbe@4 199
jbe@4 200
jbe@0 201 CREATE TABLE "contact" (
jbe@0 202 PRIMARY KEY ("member_id", "other_member_id"),
jbe@0 203 "member_id" INT4 REFERENCES "member" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@0 204 "other_member_id" INT4 REFERENCES "member" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@11 205 "public" BOOLEAN NOT NULL DEFAULT FALSE,
jbe@11 206 CONSTRAINT "cant_save_yourself_as_contact"
jbe@11 207 CHECK ("member_id" != "other_member_id") );
jbe@0 208
jbe@0 209 COMMENT ON TABLE "contact" IS 'Contact lists';
jbe@0 210
jbe@0 211 COMMENT ON COLUMN "contact"."member_id" IS 'Member having the contact list';
jbe@0 212 COMMENT ON COLUMN "contact"."other_member_id" IS 'Member referenced in the contact list';
jbe@0 213 COMMENT ON COLUMN "contact"."public" IS 'TRUE = display contact publically';
jbe@0 214
jbe@0 215
jbe@0 216 CREATE TABLE "session" (
jbe@0 217 "ident" TEXT PRIMARY KEY,
jbe@0 218 "additional_secret" TEXT,
jbe@0 219 "expiry" TIMESTAMPTZ NOT NULL DEFAULT now() + '24 hours',
jbe@0 220 "member_id" INT8 REFERENCES "member" ("id") ON DELETE SET NULL,
jbe@0 221 "lang" TEXT );
jbe@0 222 CREATE INDEX "session_expiry_idx" ON "session" ("expiry");
jbe@0 223
jbe@0 224 COMMENT ON TABLE "session" IS 'Sessions, i.e. for a web-frontend';
jbe@0 225
jbe@0 226 COMMENT ON COLUMN "session"."ident" IS 'Secret session identifier (i.e. random string)';
jbe@0 227 COMMENT ON COLUMN "session"."additional_secret" IS 'Additional field to store a secret, which can be used against CSRF attacks';
jbe@0 228 COMMENT ON COLUMN "session"."member_id" IS 'Reference to member, who is logged in';
jbe@0 229 COMMENT ON COLUMN "session"."lang" IS 'Language code of the selected language';
jbe@0 230
jbe@0 231
jbe@0 232 CREATE TABLE "policy" (
jbe@0 233 "id" SERIAL4 PRIMARY KEY,
jbe@9 234 "index" INT4 NOT NULL,
jbe@0 235 "active" BOOLEAN NOT NULL DEFAULT TRUE,
jbe@0 236 "name" TEXT NOT NULL UNIQUE,
jbe@0 237 "description" TEXT NOT NULL DEFAULT '',
jbe@0 238 "admission_time" INTERVAL NOT NULL,
jbe@0 239 "discussion_time" INTERVAL NOT NULL,
jbe@3 240 "verification_time" INTERVAL NOT NULL,
jbe@0 241 "voting_time" INTERVAL NOT NULL,
jbe@0 242 "issue_quorum_num" INT4 NOT NULL,
jbe@0 243 "issue_quorum_den" INT4 NOT NULL,
jbe@0 244 "initiative_quorum_num" INT4 NOT NULL,
jbe@10 245 "initiative_quorum_den" INT4 NOT NULL,
jbe@10 246 "majority_num" INT4 NOT NULL DEFAULT 1,
jbe@10 247 "majority_den" INT4 NOT NULL DEFAULT 2,
jbe@10 248 "majority_strict" BOOLEAN NOT NULL DEFAULT TRUE );
jbe@0 249 CREATE INDEX "policy_active_idx" ON "policy" ("active");
jbe@0 250
jbe@0 251 COMMENT ON TABLE "policy" IS 'Policies for a particular proceeding type (timelimits, quorum)';
jbe@0 252
jbe@9 253 COMMENT ON COLUMN "policy"."index" IS 'Determines the order in listings';
jbe@0 254 COMMENT ON COLUMN "policy"."active" IS 'TRUE = policy can be used for new issues';
jbe@0 255 COMMENT ON COLUMN "policy"."admission_time" IS 'Maximum time an issue stays open without being "accepted"';
jbe@3 256 COMMENT ON COLUMN "policy"."discussion_time" IS 'Regular time until an issue is "half_frozen" after being "accepted"';
jbe@3 257 COMMENT ON COLUMN "policy"."verification_time" IS 'Regular time until an issue is "fully_frozen" after being "half_frozen"';
jbe@3 258 COMMENT ON COLUMN "policy"."voting_time" IS 'Time after an issue is "fully_frozen" but not "closed"';
jbe@10 259 COMMENT ON COLUMN "policy"."issue_quorum_num" IS 'Numerator of potential supporter quorum to be reached by one initiative of an issue to be "accepted"';
jbe@10 260 COMMENT ON COLUMN "policy"."issue_quorum_den" IS 'Denominator of potential supporter quorum to be reached by one initiative of an issue to be "accepted"';
jbe@10 261 COMMENT ON COLUMN "policy"."initiative_quorum_num" IS 'Numerator of satisfied supporter quorum to be reached by an initiative to be "admitted" for voting';
jbe@10 262 COMMENT ON COLUMN "policy"."initiative_quorum_den" IS 'Denominator of satisfied supporter quorum to be reached by an initiative to be "admitted" for voting';
jbe@10 263 COMMENT ON COLUMN "policy"."majority_num" IS 'Numerator of fraction of majority to be reached during voting by an initiative to be aggreed upon';
jbe@10 264 COMMENT ON COLUMN "policy"."majority_den" IS 'Denominator of fraction of majority to be reached during voting by an initiative to be aggreed upon';
jbe@10 265 COMMENT ON COLUMN "policy"."majority_strict" IS 'If TRUE, then the majority must be strictly greater than "majority_num"/"majority_den", otherwise it may also be equal.';
jbe@0 266
jbe@0 267
jbe@0 268 CREATE TABLE "area" (
jbe@0 269 "id" SERIAL4 PRIMARY KEY,
jbe@0 270 "active" BOOLEAN NOT NULL DEFAULT TRUE,
jbe@0 271 "name" TEXT NOT NULL,
jbe@4 272 "description" TEXT NOT NULL DEFAULT '',
jbe@5 273 "direct_member_count" INT4,
jbe@5 274 "member_weight" INT4,
jbe@7 275 "autoreject_weight" INT4,
jbe@7 276 "text_search_data" TSVECTOR );
jbe@0 277 CREATE INDEX "area_active_idx" ON "area" ("active");
jbe@8 278 CREATE INDEX "area_text_search_data_idx" ON "area" USING gin ("text_search_data");
jbe@7 279 CREATE TRIGGER "update_text_search_data"
jbe@7 280 BEFORE INSERT OR UPDATE ON "area"
jbe@7 281 FOR EACH ROW EXECUTE PROCEDURE
jbe@7 282 tsvector_update_trigger('text_search_data', 'pg_catalog.simple',
jbe@7 283 "name", "description" );
jbe@0 284
jbe@0 285 COMMENT ON TABLE "area" IS 'Subject areas';
jbe@0 286
jbe@5 287 COMMENT ON COLUMN "area"."active" IS 'TRUE means new issues can be created in this area';
jbe@5 288 COMMENT ON COLUMN "area"."direct_member_count" IS 'Number of active members of that area (ignoring their weight), as calculated from view "area_member_count"';
jbe@5 289 COMMENT ON COLUMN "area"."member_weight" IS 'Same as "direct_member_count" but respecting delegations';
jbe@5 290 COMMENT ON COLUMN "area"."autoreject_weight" IS 'Sum of weight of members using the autoreject feature';
jbe@0 291
jbe@0 292
jbe@9 293 CREATE TABLE "allowed_policy" (
jbe@9 294 PRIMARY KEY ("area_id", "policy_id"),
jbe@9 295 "area_id" INT4 REFERENCES "area" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@9 296 "policy_id" INT4 NOT NULL REFERENCES "policy" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@9 297 "default_policy" BOOLEAN NOT NULL DEFAULT FALSE );
jbe@9 298 CREATE UNIQUE INDEX "allowed_policy_one_default_per_area_idx" ON "allowed_policy" ("area_id") WHERE "default_policy";
jbe@9 299
jbe@9 300 COMMENT ON TABLE "allowed_policy" IS 'Selects which policies can be used in each area';
jbe@9 301
jbe@9 302 COMMENT ON COLUMN "allowed_policy"."default_policy" IS 'One policy per area can be set as default.';
jbe@9 303
jbe@9 304
jbe@21 305 CREATE TYPE "snapshot_event" AS ENUM ('periodic', 'end_of_admission', 'half_freeze', 'full_freeze');
jbe@21 306
jbe@21 307 COMMENT ON TYPE "snapshot_event" IS 'Reason for snapshots: ''periodic'' = due to periodic recalculation, ''end_of_admission'' = saved state at end of admission period, ''half_freeze'' = saved state at end of discussion period, ''full_freeze'' = saved state at end of verification period';
jbe@8 308
jbe@8 309
jbe@0 310 CREATE TABLE "issue" (
jbe@0 311 "id" SERIAL4 PRIMARY KEY,
jbe@0 312 "area_id" INT4 NOT NULL REFERENCES "area" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@0 313 "policy_id" INT4 NOT NULL REFERENCES "policy" ("id") ON DELETE RESTRICT ON UPDATE CASCADE,
jbe@0 314 "created" TIMESTAMPTZ NOT NULL DEFAULT now(),
jbe@0 315 "accepted" TIMESTAMPTZ,
jbe@3 316 "half_frozen" TIMESTAMPTZ,
jbe@3 317 "fully_frozen" TIMESTAMPTZ,
jbe@0 318 "closed" TIMESTAMPTZ,
jbe@0 319 "ranks_available" BOOLEAN NOT NULL DEFAULT FALSE,
jbe@22 320 "admission_time" INTERVAL NOT NULL,
jbe@22 321 "discussion_time" INTERVAL NOT NULL,
jbe@22 322 "verification_time" INTERVAL NOT NULL,
jbe@22 323 "voting_time" INTERVAL NOT NULL,
jbe@0 324 "snapshot" TIMESTAMPTZ,
jbe@8 325 "latest_snapshot_event" "snapshot_event",
jbe@0 326 "population" INT4,
jbe@0 327 "vote_now" INT4,
jbe@0 328 "vote_later" INT4,
jbe@4 329 "voter_count" INT4,
jbe@0 330 CONSTRAINT "valid_state" CHECK (
jbe@3 331 ("accepted" ISNULL AND "half_frozen" ISNULL AND "fully_frozen" ISNULL AND "closed" ISNULL AND "ranks_available" = FALSE) OR
jbe@3 332 ("accepted" ISNULL AND "half_frozen" ISNULL AND "fully_frozen" ISNULL AND "closed" NOTNULL AND "ranks_available" = FALSE) OR
jbe@3 333 ("accepted" NOTNULL AND "half_frozen" ISNULL AND "fully_frozen" ISNULL AND "closed" ISNULL AND "ranks_available" = FALSE) OR
jbe@3 334 ("accepted" NOTNULL AND "half_frozen" NOTNULL AND "fully_frozen" ISNULL AND "closed" ISNULL AND "ranks_available" = FALSE) OR
jbe@3 335 ("accepted" NOTNULL AND "half_frozen" NOTNULL AND "fully_frozen" NOTNULL AND "closed" ISNULL AND "ranks_available" = FALSE) OR
jbe@3 336 ("accepted" NOTNULL AND "half_frozen" NOTNULL AND "fully_frozen" NOTNULL AND "closed" NOTNULL AND "ranks_available" = FALSE) OR
jbe@3 337 ("accepted" NOTNULL AND "half_frozen" NOTNULL AND "fully_frozen" NOTNULL AND "closed" NOTNULL AND "ranks_available" = TRUE) ),
jbe@3 338 CONSTRAINT "state_change_order" CHECK (
jbe@10 339 "created" <= "accepted" AND
jbe@10 340 "accepted" <= "half_frozen" AND
jbe@10 341 "half_frozen" <= "fully_frozen" AND
jbe@3 342 "fully_frozen" <= "closed" ),
jbe@10 343 CONSTRAINT "last_snapshot_on_full_freeze"
jbe@10 344 CHECK ("snapshot" = "fully_frozen"), -- NOTE: snapshot can be set, while frozen is NULL yet
jbe@10 345 CONSTRAINT "freeze_requires_snapshot"
jbe@10 346 CHECK ("fully_frozen" ISNULL OR "snapshot" NOTNULL),
jbe@10 347 CONSTRAINT "set_both_or_none_of_snapshot_and_latest_snapshot_event"
jbe@10 348 CHECK ("snapshot" NOTNULL = "latest_snapshot_event" NOTNULL) );
jbe@0 349 CREATE INDEX "issue_area_id_idx" ON "issue" ("area_id");
jbe@0 350 CREATE INDEX "issue_policy_id_idx" ON "issue" ("policy_id");
jbe@16 351 CREATE INDEX "issue_created_idx" ON "issue" ("created");
jbe@16 352 CREATE INDEX "issue_accepted_idx" ON "issue" ("accepted");
jbe@16 353 CREATE INDEX "issue_half_frozen_idx" ON "issue" ("half_frozen");
jbe@16 354 CREATE INDEX "issue_fully_frozen_idx" ON "issue" ("fully_frozen");
jbe@16 355 CREATE INDEX "issue_closed_idx" ON "issue" ("closed");
jbe@0 356 CREATE INDEX "issue_created_idx_open" ON "issue" ("created") WHERE "closed" ISNULL;
jbe@16 357 CREATE INDEX "issue_closed_idx_canceled" ON "issue" ("closed") WHERE "fully_frozen" ISNULL;
jbe@0 358
jbe@0 359 COMMENT ON TABLE "issue" IS 'Groups of initiatives';
jbe@0 360
jbe@8 361 COMMENT ON COLUMN "issue"."accepted" IS 'Point in time, when one initiative of issue reached the "issue_quorum"';
jbe@10 362 COMMENT ON COLUMN "issue"."half_frozen" IS 'Point in time, when "discussion_time" has elapsed, or members voted for voting; Frontends must ensure that for half_frozen issues a) initiatives are not revoked, b) no new drafts are created, c) no initiators are added or removed.';
jbe@10 363 COMMENT ON COLUMN "issue"."fully_frozen" IS 'Point in time, when "verification_time" has elapsed; Frontends must ensure that for fully_frozen issues additionally to the restrictions for half_frozen issues a) initiatives are not created, b) no interest is created or removed, c) no supporters are added or removed, d) no opinions are created, changed or deleted.';
jbe@10 364 COMMENT ON COLUMN "issue"."closed" IS 'Point in time, when "admission_time" or "voting_time" have elapsed, and issue is no longer active; Frontends must ensure that for closed issues additionally to the restrictions for half_frozen and fully_frozen issues a) no voter is added or removed to/from the direct_voter table, b) no votes are added, modified or removed.';
jbe@8 365 COMMENT ON COLUMN "issue"."ranks_available" IS 'TRUE = ranks have been calculated';
jbe@22 366 COMMENT ON COLUMN "issue"."admission_time" IS 'Copied from "policy" table at creation of issue';
jbe@22 367 COMMENT ON COLUMN "issue"."discussion_time" IS 'Copied from "policy" table at creation of issue';
jbe@22 368 COMMENT ON COLUMN "issue"."verification_time" IS 'Copied from "policy" table at creation of issue';
jbe@22 369 COMMENT ON COLUMN "issue"."voting_time" IS 'Copied from "policy" table at creation of issue';
jbe@8 370 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@8 371 COMMENT ON COLUMN "issue"."latest_snapshot_event" IS 'Event type of latest snapshot for issue; Can be used to select the latest snapshot data in the snapshot tables';
jbe@8 372 COMMENT ON COLUMN "issue"."population" IS 'Sum of "weight" column in table "direct_population_snapshot"';
jbe@8 373 COMMENT ON COLUMN "issue"."vote_now" IS 'Number of votes in favor of voting now, as calculated from table "direct_interest_snapshot"';
jbe@8 374 COMMENT ON COLUMN "issue"."vote_later" IS 'Number of votes against voting now, as calculated from table "direct_interest_snapshot"';
jbe@8 375 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 376
jbe@0 377
jbe@0 378 CREATE TABLE "initiative" (
jbe@0 379 UNIQUE ("issue_id", "id"), -- index needed for foreign-key on table "vote"
jbe@0 380 "issue_id" INT4 NOT NULL REFERENCES "issue" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@0 381 "id" SERIAL4 PRIMARY KEY,
jbe@0 382 "name" TEXT NOT NULL,
jbe@8 383 "discussion_url" TEXT,
jbe@0 384 "created" TIMESTAMPTZ NOT NULL DEFAULT now(),
jbe@0 385 "revoked" TIMESTAMPTZ,
jbe@14 386 "suggested_initiative_id" INT4 REFERENCES "initiative" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@0 387 "admitted" BOOLEAN,
jbe@0 388 "supporter_count" INT4,
jbe@0 389 "informed_supporter_count" INT4,
jbe@0 390 "satisfied_supporter_count" INT4,
jbe@0 391 "satisfied_informed_supporter_count" INT4,
jbe@0 392 "positive_votes" INT4,
jbe@0 393 "negative_votes" INT4,
jbe@10 394 "agreed" BOOLEAN,
jbe@0 395 "rank" INT4,
jbe@7 396 "text_search_data" TSVECTOR,
jbe@14 397 CONSTRAINT "non_revoked_initiatives_cant_suggest_other"
jbe@14 398 CHECK ("revoked" NOTNULL OR "suggested_initiative_id" ISNULL),
jbe@0 399 CONSTRAINT "revoked_initiatives_cant_be_admitted"
jbe@0 400 CHECK ("revoked" ISNULL OR "admitted" ISNULL),
jbe@0 401 CONSTRAINT "non_admitted_initiatives_cant_contain_voting_results"
jbe@10 402 CHECK (("admitted" NOTNULL AND "admitted" = TRUE) OR ("positive_votes" ISNULL AND "negative_votes" ISNULL AND "agreed" ISNULL)),
jbe@10 403 CONSTRAINT "all_or_none_of_positive_votes_negative_votes_and_agreed_must_be_null"
jbe@10 404 CHECK ("positive_votes" NOTNULL = "negative_votes" NOTNULL AND "positive_votes" NOTNULL = "agreed" NOTNULL),
jbe@10 405 CONSTRAINT "non_agreed_initiatives_cant_get_a_rank"
jbe@10 406 CHECK (("agreed" NOTNULL AND "agreed" = TRUE) OR "rank" ISNULL) );
jbe@16 407 CREATE INDEX "initiative_created_idx" ON "initiative" ("created");
jbe@16 408 CREATE INDEX "initiative_revoked_idx" ON "initiative" ("revoked");
jbe@8 409 CREATE INDEX "initiative_text_search_data_idx" ON "initiative" USING gin ("text_search_data");
jbe@7 410 CREATE TRIGGER "update_text_search_data"
jbe@7 411 BEFORE INSERT OR UPDATE ON "initiative"
jbe@7 412 FOR EACH ROW EXECUTE PROCEDURE
jbe@8 413 tsvector_update_trigger('text_search_data', 'pg_catalog.simple',
jbe@8 414 "name", "discussion_url");
jbe@0 415
jbe@10 416 COMMENT ON TABLE "initiative" IS 'Group of members publishing drafts for resolutions to be passed; Frontends must ensure that initiatives of half_frozen issues are not revoked, and that initiatives of fully_frozen or closed issues are neither revoked nor created.';
jbe@0 417
jbe@8 418 COMMENT ON COLUMN "initiative"."discussion_url" IS 'URL pointing to a discussion platform for this initiative';
jbe@0 419 COMMENT ON COLUMN "initiative"."revoked" IS 'Point in time, when one initiator decided to revoke the initiative';
jbe@10 420 COMMENT ON COLUMN "initiative"."admitted" IS 'TRUE, if initiative reaches the "initiative_quorum" when freezing the issue';
jbe@0 421 COMMENT ON COLUMN "initiative"."supporter_count" IS 'Calculated from table "direct_supporter_snapshot"';
jbe@0 422 COMMENT ON COLUMN "initiative"."informed_supporter_count" IS 'Calculated from table "direct_supporter_snapshot"';
jbe@0 423 COMMENT ON COLUMN "initiative"."satisfied_supporter_count" IS 'Calculated from table "direct_supporter_snapshot"';
jbe@0 424 COMMENT ON COLUMN "initiative"."satisfied_informed_supporter_count" IS 'Calculated from table "direct_supporter_snapshot"';
jbe@0 425 COMMENT ON COLUMN "initiative"."positive_votes" IS 'Calculated from table "direct_voter"';
jbe@0 426 COMMENT ON COLUMN "initiative"."negative_votes" IS 'Calculated from table "direct_voter"';
jbe@10 427 COMMENT ON COLUMN "initiative"."agreed" IS 'TRUE, if "positive_votes"/("positive_votes"+"negative_votes") is strictly greater or greater-equal than "majority_num"/"majority_den"';
jbe@0 428 COMMENT ON COLUMN "initiative"."rank" IS 'Rank of approved initiatives (winner is 1), calculated from table "direct_voter"';
jbe@0 429
jbe@0 430
jbe@0 431 CREATE TABLE "draft" (
jbe@0 432 UNIQUE ("initiative_id", "id"), -- index needed for foreign-key on table "supporter"
jbe@0 433 "initiative_id" INT4 NOT NULL REFERENCES "initiative" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@0 434 "id" SERIAL8 PRIMARY KEY,
jbe@0 435 "created" TIMESTAMPTZ NOT NULL DEFAULT now(),
jbe@0 436 "author_id" INT4 NOT NULL REFERENCES "member" ("id") ON DELETE RESTRICT ON UPDATE CASCADE,
jbe@9 437 "formatting_engine" TEXT,
jbe@7 438 "content" TEXT NOT NULL,
jbe@7 439 "text_search_data" TSVECTOR );
jbe@16 440 CREATE INDEX "draft_created_idx" ON "draft" ("created");
jbe@9 441 CREATE INDEX "draft_author_id_created_idx" ON "draft" ("author_id", "created");
jbe@8 442 CREATE INDEX "draft_text_search_data_idx" ON "draft" USING gin ("text_search_data");
jbe@7 443 CREATE TRIGGER "update_text_search_data"
jbe@7 444 BEFORE INSERT OR UPDATE ON "draft"
jbe@7 445 FOR EACH ROW EXECUTE PROCEDURE
jbe@7 446 tsvector_update_trigger('text_search_data', 'pg_catalog.simple', "content");
jbe@0 447
jbe@10 448 COMMENT ON TABLE "draft" IS 'Drafts of initiatives to solve issues; Frontends must ensure that new drafts for initiatives of half_frozen, fully_frozen or closed issues can''t be created.';
jbe@0 449
jbe@9 450 COMMENT ON COLUMN "draft"."formatting_engine" IS 'Allows different formatting engines (i.e. wiki formats) to be used';
jbe@9 451 COMMENT ON COLUMN "draft"."content" IS 'Text of the draft in a format depending on the field "formatting_engine"';
jbe@9 452
jbe@0 453
jbe@0 454 CREATE TABLE "suggestion" (
jbe@0 455 UNIQUE ("initiative_id", "id"), -- index needed for foreign-key on table "opinion"
jbe@0 456 "initiative_id" INT4 NOT NULL REFERENCES "initiative" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@0 457 "id" SERIAL8 PRIMARY KEY,
jbe@0 458 "created" TIMESTAMPTZ NOT NULL DEFAULT now(),
jbe@0 459 "author_id" INT4 NOT NULL REFERENCES "member" ("id") ON DELETE RESTRICT ON UPDATE CASCADE,
jbe@0 460 "name" TEXT NOT NULL,
jbe@0 461 "description" TEXT NOT NULL DEFAULT '',
jbe@7 462 "text_search_data" TSVECTOR,
jbe@0 463 "minus2_unfulfilled_count" INT4,
jbe@0 464 "minus2_fulfilled_count" INT4,
jbe@0 465 "minus1_unfulfilled_count" INT4,
jbe@0 466 "minus1_fulfilled_count" INT4,
jbe@0 467 "plus1_unfulfilled_count" INT4,
jbe@0 468 "plus1_fulfilled_count" INT4,
jbe@0 469 "plus2_unfulfilled_count" INT4,
jbe@0 470 "plus2_fulfilled_count" INT4 );
jbe@16 471 CREATE INDEX "suggestion_created_idx" ON "suggestion" ("created");
jbe@9 472 CREATE INDEX "suggestion_author_id_created_idx" ON "suggestion" ("author_id", "created");
jbe@8 473 CREATE INDEX "suggestion_text_search_data_idx" ON "suggestion" USING gin ("text_search_data");
jbe@7 474 CREATE TRIGGER "update_text_search_data"
jbe@7 475 BEFORE INSERT OR UPDATE ON "suggestion"
jbe@7 476 FOR EACH ROW EXECUTE PROCEDURE
jbe@7 477 tsvector_update_trigger('text_search_data', 'pg_catalog.simple',
jbe@7 478 "name", "description");
jbe@0 479
jbe@10 480 COMMENT ON TABLE "suggestion" IS 'Suggestions to initiators, to change the current draft; must not be deleted explicitly, as they vanish automatically if the last opinion is deleted';
jbe@0 481
jbe@0 482 COMMENT ON COLUMN "suggestion"."minus2_unfulfilled_count" IS 'Calculated from table "direct_supporter_snapshot", not requiring informed supporters';
jbe@0 483 COMMENT ON COLUMN "suggestion"."minus2_fulfilled_count" IS 'Calculated from table "direct_supporter_snapshot", not requiring informed supporters';
jbe@0 484 COMMENT ON COLUMN "suggestion"."minus1_unfulfilled_count" IS 'Calculated from table "direct_supporter_snapshot", not requiring informed supporters';
jbe@0 485 COMMENT ON COLUMN "suggestion"."minus1_fulfilled_count" IS 'Calculated from table "direct_supporter_snapshot", not requiring informed supporters';
jbe@0 486 COMMENT ON COLUMN "suggestion"."plus1_unfulfilled_count" IS 'Calculated from table "direct_supporter_snapshot", not requiring informed supporters';
jbe@0 487 COMMENT ON COLUMN "suggestion"."plus1_fulfilled_count" IS 'Calculated from table "direct_supporter_snapshot", not requiring informed supporters';
jbe@0 488 COMMENT ON COLUMN "suggestion"."plus2_unfulfilled_count" IS 'Calculated from table "direct_supporter_snapshot", not requiring informed supporters';
jbe@0 489 COMMENT ON COLUMN "suggestion"."plus2_fulfilled_count" IS 'Calculated from table "direct_supporter_snapshot", not requiring informed supporters';
jbe@0 490
jbe@0 491
jbe@0 492 CREATE TABLE "membership" (
jbe@0 493 PRIMARY KEY ("area_id", "member_id"),
jbe@0 494 "area_id" INT4 REFERENCES "area" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@0 495 "member_id" INT4 REFERENCES "member" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@0 496 "autoreject" BOOLEAN NOT NULL DEFAULT FALSE );
jbe@0 497 CREATE INDEX "membership_member_id_idx" ON "membership" ("member_id");
jbe@0 498
jbe@0 499 COMMENT ON TABLE "membership" IS 'Interest of members in topic areas';
jbe@0 500
jbe@0 501 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 502
jbe@0 503
jbe@0 504 CREATE TABLE "interest" (
jbe@0 505 PRIMARY KEY ("issue_id", "member_id"),
jbe@0 506 "issue_id" INT4 REFERENCES "issue" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@0 507 "member_id" INT4 REFERENCES "member" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@0 508 "autoreject" BOOLEAN NOT NULL,
jbe@0 509 "voting_requested" BOOLEAN );
jbe@0 510 CREATE INDEX "interest_member_id_idx" ON "interest" ("member_id");
jbe@0 511
jbe@10 512 COMMENT ON TABLE "interest" IS 'Interest of members in a particular issue; Frontends must ensure that interest for fully_frozen or closed issues is not added or removed.';
jbe@0 513
jbe@0 514 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 515 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 516
jbe@0 517
jbe@0 518 CREATE TABLE "initiator" (
jbe@0 519 PRIMARY KEY ("initiative_id", "member_id"),
jbe@0 520 "initiative_id" INT4 REFERENCES "initiative" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@0 521 "member_id" INT4 REFERENCES "member" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@14 522 "accepted" BOOLEAN );
jbe@0 523 CREATE INDEX "initiator_member_id_idx" ON "initiator" ("member_id");
jbe@0 524
jbe@10 525 COMMENT ON TABLE "initiator" IS 'Members who are allowed to post new drafts; Frontends must ensure that initiators are not added or removed from half_frozen, fully_frozen or closed initiatives.';
jbe@0 526
jbe@14 527 COMMENT ON COLUMN "initiator"."accepted" IS 'If "accepted" is NULL, then the member was invited to be a co-initiator, but has not answered yet. If it is TRUE, the member has accepted the invitation, if it is FALSE, the member has rejected the invitation.';
jbe@0 528
jbe@0 529
jbe@0 530 CREATE TABLE "supporter" (
jbe@0 531 "issue_id" INT4 NOT NULL,
jbe@0 532 PRIMARY KEY ("initiative_id", "member_id"),
jbe@0 533 "initiative_id" INT4,
jbe@0 534 "member_id" INT4,
jbe@0 535 "draft_id" INT8 NOT NULL,
jbe@10 536 FOREIGN KEY ("issue_id", "member_id") REFERENCES "interest" ("issue_id", "member_id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@0 537 FOREIGN KEY ("initiative_id", "draft_id") REFERENCES "draft" ("initiative_id", "id") ON DELETE CASCADE ON UPDATE CASCADE );
jbe@0 538 CREATE INDEX "supporter_member_id_idx" ON "supporter" ("member_id");
jbe@0 539
jbe@10 540 COMMENT ON TABLE "supporter" IS 'Members who support an initiative (conditionally); Frontends must ensure that supporters are not added or removed from fully_frozen or closed initiatives.';
jbe@0 541
jbe@2 542 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 543
jbe@0 544
jbe@0 545 CREATE TABLE "opinion" (
jbe@0 546 "initiative_id" INT4 NOT NULL,
jbe@0 547 PRIMARY KEY ("suggestion_id", "member_id"),
jbe@0 548 "suggestion_id" INT8,
jbe@0 549 "member_id" INT4,
jbe@0 550 "degree" INT2 NOT NULL CHECK ("degree" >= -2 AND "degree" <= 2 AND "degree" != 0),
jbe@0 551 "fulfilled" BOOLEAN NOT NULL DEFAULT FALSE,
jbe@0 552 FOREIGN KEY ("initiative_id", "suggestion_id") REFERENCES "suggestion" ("initiative_id", "id") ON DELETE RESTRICT ON UPDATE CASCADE,
jbe@0 553 FOREIGN KEY ("initiative_id", "member_id") REFERENCES "supporter" ("initiative_id", "member_id") ON DELETE CASCADE ON UPDATE CASCADE );
jbe@10 554 CREATE INDEX "opinion_member_id_initiative_id_idx" ON "opinion" ("member_id", "initiative_id");
jbe@0 555
jbe@10 556 COMMENT ON TABLE "opinion" IS 'Opinion on suggestions (criticism related to initiatives); Frontends must ensure that opinions are not created modified or deleted when related to fully_frozen or closed issues.';
jbe@0 557
jbe@0 558 COMMENT ON COLUMN "opinion"."degree" IS '2 = fulfillment required for support; 1 = fulfillment desired; -1 = fulfillment unwanted; -2 = fulfillment cancels support';
jbe@0 559
jbe@0 560
jbe@10 561 CREATE TYPE "delegation_scope" AS ENUM ('global', 'area', 'issue');
jbe@10 562
jbe@10 563 COMMENT ON TYPE "delegation_scope" IS 'Scope for delegations: ''global'', ''area'', or ''issue'' (order is relevant)';
jbe@10 564
jbe@10 565
jbe@0 566 CREATE TABLE "delegation" (
jbe@0 567 "id" SERIAL8 PRIMARY KEY,
jbe@0 568 "truster_id" INT4 NOT NULL REFERENCES "member" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@0 569 "trustee_id" INT4 NOT NULL REFERENCES "member" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@10 570 "scope" "delegation_scope" NOT NULL,
jbe@0 571 "area_id" INT4 REFERENCES "area" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@0 572 "issue_id" INT4 REFERENCES "issue" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@0 573 CONSTRAINT "cant_delegate_to_yourself" CHECK ("truster_id" != "trustee_id"),
jbe@10 574 CONSTRAINT "area_id_and_issue_id_set_according_to_scope" CHECK (
jbe@10 575 ("scope" = 'global' AND "area_id" ISNULL AND "issue_id" ISNULL ) OR
jbe@10 576 ("scope" = 'area' AND "area_id" NOTNULL AND "issue_id" ISNULL ) OR
jbe@10 577 ("scope" = 'issue' AND "area_id" ISNULL AND "issue_id" NOTNULL) ),
jbe@0 578 UNIQUE ("area_id", "truster_id", "trustee_id"),
jbe@0 579 UNIQUE ("issue_id", "truster_id", "trustee_id") );
jbe@10 580 CREATE UNIQUE INDEX "delegation_global_truster_id_trustee_id_unique_idx"
jbe@10 581 ON "delegation" ("truster_id", "trustee_id") WHERE "scope" = 'global';
jbe@0 582 CREATE INDEX "delegation_truster_id_idx" ON "delegation" ("truster_id");
jbe@0 583 CREATE INDEX "delegation_trustee_id_idx" ON "delegation" ("trustee_id");
jbe@0 584
jbe@0 585 COMMENT ON TABLE "delegation" IS 'Delegation of vote-weight to other members';
jbe@0 586
jbe@0 587 COMMENT ON COLUMN "delegation"."area_id" IS 'Reference to area, if delegation is area-wide, otherwise NULL';
jbe@0 588 COMMENT ON COLUMN "delegation"."issue_id" IS 'Reference to issue, if delegation is issue-wide, otherwise NULL';
jbe@0 589
jbe@0 590
jbe@0 591 CREATE TABLE "direct_population_snapshot" (
jbe@0 592 PRIMARY KEY ("issue_id", "event", "member_id"),
jbe@0 593 "issue_id" INT4 REFERENCES "issue" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@0 594 "event" "snapshot_event",
jbe@0 595 "member_id" INT4 REFERENCES "member" ("id") ON DELETE RESTRICT ON UPDATE CASCADE,
jbe@0 596 "weight" INT4,
jbe@0 597 "interest_exists" BOOLEAN NOT NULL );
jbe@0 598 CREATE INDEX "direct_population_snapshot_member_id_idx" ON "direct_population_snapshot" ("member_id");
jbe@0 599
jbe@0 600 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 601
jbe@0 602 COMMENT ON COLUMN "direct_population_snapshot"."event" IS 'Reason for snapshot, see "snapshot_event" type for details';
jbe@0 603 COMMENT ON COLUMN "direct_population_snapshot"."weight" IS 'Weight of member (1 or higher) according to "delegating_population_snapshot"';
jbe@0 604 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 605
jbe@0 606
jbe@0 607 CREATE TABLE "delegating_population_snapshot" (
jbe@0 608 PRIMARY KEY ("issue_id", "event", "member_id"),
jbe@0 609 "issue_id" INT4 REFERENCES "issue" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@0 610 "event" "snapshot_event",
jbe@0 611 "member_id" INT4 REFERENCES "member" ("id") ON DELETE RESTRICT ON UPDATE CASCADE,
jbe@8 612 "weight" INT4,
jbe@10 613 "scope" "delegation_scope" NOT NULL,
jbe@0 614 "delegate_member_ids" INT4[] NOT NULL );
jbe@0 615 CREATE INDEX "delegating_population_snapshot_member_id_idx" ON "delegating_population_snapshot" ("member_id");
jbe@0 616
jbe@0 617 COMMENT ON TABLE "direct_population_snapshot" IS 'Delegations increasing the weight of entries in the "direct_population_snapshot" table';
jbe@0 618
jbe@0 619 COMMENT ON COLUMN "delegating_population_snapshot"."event" IS 'Reason for snapshot, see "snapshot_event" type for details';
jbe@0 620 COMMENT ON COLUMN "delegating_population_snapshot"."member_id" IS 'Delegating member';
jbe@8 621 COMMENT ON COLUMN "delegating_population_snapshot"."weight" IS 'Intermediate weight';
jbe@0 622 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 623
jbe@0 624
jbe@0 625 CREATE TABLE "direct_interest_snapshot" (
jbe@0 626 PRIMARY KEY ("issue_id", "event", "member_id"),
jbe@0 627 "issue_id" INT4 REFERENCES "issue" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@0 628 "event" "snapshot_event",
jbe@0 629 "member_id" INT4 REFERENCES "member" ("id") ON DELETE RESTRICT ON UPDATE CASCADE,
jbe@0 630 "weight" INT4,
jbe@0 631 "voting_requested" BOOLEAN );
jbe@0 632 CREATE INDEX "direct_interest_snapshot_member_id_idx" ON "direct_interest_snapshot" ("member_id");
jbe@0 633
jbe@0 634 COMMENT ON TABLE "direct_interest_snapshot" IS 'Snapshot of active members having an "interest" in the "issue"';
jbe@0 635
jbe@0 636 COMMENT ON COLUMN "direct_interest_snapshot"."event" IS 'Reason for snapshot, see "snapshot_event" type for details';
jbe@0 637 COMMENT ON COLUMN "direct_interest_snapshot"."weight" IS 'Weight of member (1 or higher) according to "delegating_interest_snapshot"';
jbe@0 638 COMMENT ON COLUMN "direct_interest_snapshot"."voting_requested" IS 'Copied from column "voting_requested" of table "interest"';
jbe@0 639
jbe@0 640
jbe@0 641 CREATE TABLE "delegating_interest_snapshot" (
jbe@0 642 PRIMARY KEY ("issue_id", "event", "member_id"),
jbe@0 643 "issue_id" INT4 REFERENCES "issue" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@0 644 "event" "snapshot_event",
jbe@0 645 "member_id" INT4 REFERENCES "member" ("id") ON DELETE RESTRICT ON UPDATE CASCADE,
jbe@8 646 "weight" INT4,
jbe@10 647 "scope" "delegation_scope" NOT NULL,
jbe@0 648 "delegate_member_ids" INT4[] NOT NULL );
jbe@0 649 CREATE INDEX "delegating_interest_snapshot_member_id_idx" ON "delegating_interest_snapshot" ("member_id");
jbe@0 650
jbe@0 651 COMMENT ON TABLE "delegating_interest_snapshot" IS 'Delegations increasing the weight of entries in the "direct_interest_snapshot" table';
jbe@0 652
jbe@0 653 COMMENT ON COLUMN "delegating_interest_snapshot"."event" IS 'Reason for snapshot, see "snapshot_event" type for details';
jbe@0 654 COMMENT ON COLUMN "delegating_interest_snapshot"."member_id" IS 'Delegating member';
jbe@8 655 COMMENT ON COLUMN "delegating_interest_snapshot"."weight" IS 'Intermediate weight';
jbe@0 656 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 657
jbe@0 658
jbe@0 659 CREATE TABLE "direct_supporter_snapshot" (
jbe@0 660 "issue_id" INT4 NOT NULL,
jbe@0 661 PRIMARY KEY ("initiative_id", "event", "member_id"),
jbe@0 662 "initiative_id" INT4,
jbe@0 663 "event" "snapshot_event",
jbe@0 664 "member_id" INT4 REFERENCES "member" ("id") ON DELETE RESTRICT ON UPDATE CASCADE,
jbe@0 665 "informed" BOOLEAN NOT NULL,
jbe@0 666 "satisfied" BOOLEAN NOT NULL,
jbe@0 667 FOREIGN KEY ("issue_id", "initiative_id") REFERENCES "initiative" ("issue_id", "id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@0 668 FOREIGN KEY ("issue_id", "event", "member_id") REFERENCES "direct_interest_snapshot" ("issue_id", "event", "member_id") ON DELETE CASCADE ON UPDATE CASCADE );
jbe@0 669 CREATE INDEX "direct_supporter_snapshot_member_id_idx" ON "direct_supporter_snapshot" ("member_id");
jbe@0 670
jbe@8 671 COMMENT ON TABLE "direct_supporter_snapshot" IS 'Snapshot of supporters of initiatives (weight is stored in "direct_interest_snapshot")';
jbe@0 672
jbe@0 673 COMMENT ON COLUMN "direct_supporter_snapshot"."event" IS 'Reason for snapshot, see "snapshot_event" type for details';
jbe@0 674 COMMENT ON COLUMN "direct_supporter_snapshot"."informed" IS 'Supporter has seen the latest draft of the initiative';
jbe@0 675 COMMENT ON COLUMN "direct_supporter_snapshot"."satisfied" IS 'Supporter has no "critical_opinion"s';
jbe@0 676
jbe@0 677
jbe@0 678 CREATE TABLE "direct_voter" (
jbe@0 679 PRIMARY KEY ("issue_id", "member_id"),
jbe@0 680 "issue_id" INT4 REFERENCES "issue" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@0 681 "member_id" INT4 REFERENCES "member" ("id") ON DELETE RESTRICT ON UPDATE CASCADE,
jbe@0 682 "weight" INT4,
jbe@0 683 "autoreject" BOOLEAN NOT NULL DEFAULT FALSE );
jbe@0 684 CREATE INDEX "direct_voter_member_id_idx" ON "direct_voter" ("member_id");
jbe@0 685
jbe@10 686 COMMENT ON TABLE "direct_voter" IS 'Members having directly voted for/against initiatives of an issue; Frontends must ensure that no voters are added or removed to/from this table when the issue has been closed.';
jbe@0 687
jbe@0 688 COMMENT ON COLUMN "direct_voter"."weight" IS 'Weight of member (1 or higher) according to "delegating_voter" table';
jbe@0 689 COMMENT ON COLUMN "direct_voter"."autoreject" IS 'Votes were inserted due to "autoreject" feature';
jbe@0 690
jbe@0 691
jbe@0 692 CREATE TABLE "delegating_voter" (
jbe@0 693 PRIMARY KEY ("issue_id", "member_id"),
jbe@0 694 "issue_id" INT4 REFERENCES "issue" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@0 695 "member_id" INT4 REFERENCES "member" ("id") ON DELETE RESTRICT ON UPDATE CASCADE,
jbe@8 696 "weight" INT4,
jbe@10 697 "scope" "delegation_scope" NOT NULL,
jbe@0 698 "delegate_member_ids" INT4[] NOT NULL );
jbe@0 699 CREATE INDEX "delegating_voter_member_id_idx" ON "direct_voter" ("member_id");
jbe@0 700
jbe@0 701 COMMENT ON TABLE "delegating_voter" IS 'Delegations increasing the weight of entries in the "direct_voter" table';
jbe@0 702
jbe@0 703 COMMENT ON COLUMN "delegating_voter"."member_id" IS 'Delegating member';
jbe@8 704 COMMENT ON COLUMN "delegating_voter"."weight" IS 'Intermediate weight';
jbe@0 705 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 706
jbe@0 707
jbe@0 708 CREATE TABLE "vote" (
jbe@0 709 "issue_id" INT4 NOT NULL,
jbe@0 710 PRIMARY KEY ("initiative_id", "member_id"),
jbe@0 711 "initiative_id" INT4,
jbe@0 712 "member_id" INT4,
jbe@0 713 "grade" INT4,
jbe@0 714 FOREIGN KEY ("issue_id", "initiative_id") REFERENCES "initiative" ("issue_id", "id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@0 715 FOREIGN KEY ("issue_id", "member_id") REFERENCES "direct_voter" ("issue_id", "member_id") ON DELETE CASCADE ON UPDATE CASCADE );
jbe@0 716 CREATE INDEX "vote_member_id_idx" ON "vote" ("member_id");
jbe@0 717
jbe@10 718 COMMENT ON TABLE "vote" IS 'Manual and delegated votes without abstentions; Frontends must ensure that no votes are added modified or removed when the issue has been closed.';
jbe@0 719
jbe@0 720 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 721
jbe@0 722
jbe@9 723 CREATE TABLE "contingent" (
jbe@9 724 "time_frame" INTERVAL PRIMARY KEY,
jbe@9 725 "text_entry_limit" INT4,
jbe@9 726 "initiative_limit" INT4 );
jbe@9 727
jbe@9 728 COMMENT ON TABLE "contingent" IS 'Amount of text entries or initiatives a user may create within a given time frame. Only one row needs to be fulfilled for a member to be allowed to post. This table must not be empty.';
jbe@9 729
jbe@9 730 COMMENT ON COLUMN "contingent"."text_entry_limit" IS 'Number of new drafts or suggestions to be submitted by each member within the given time frame';
jbe@9 731 COMMENT ON COLUMN "contingent"."initiative_limit" IS 'Number of new initiatives to be opened by each member within a given time frame';
jbe@9 732
jbe@9 733
jbe@0 734
jbe@13 735 --------------------------------
jbe@13 736 -- Writing of history entries --
jbe@13 737 --------------------------------
jbe@13 738
jbe@13 739 CREATE FUNCTION "write_member_history_trigger"()
jbe@13 740 RETURNS TRIGGER
jbe@13 741 LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@13 742 BEGIN
jbe@13 743 IF NEW."login" != OLD."login" OR NEW."name" != OLD."name" THEN
jbe@13 744 INSERT INTO "member_history" ("member_id", "login", "name")
jbe@13 745 VALUES (NEW."id", OLD."login", OLD."name");
jbe@13 746 END IF;
jbe@13 747 RETURN NULL;
jbe@13 748 END;
jbe@13 749 $$;
jbe@13 750
jbe@13 751 CREATE TRIGGER "write_member_history"
jbe@13 752 AFTER UPDATE ON "member" FOR EACH ROW EXECUTE PROCEDURE
jbe@13 753 "write_member_history_trigger"();
jbe@13 754
jbe@13 755 COMMENT ON FUNCTION "write_member_history_trigger"() IS 'Implementation of trigger "write_member_history" on table "member"';
jbe@13 756 COMMENT ON TRIGGER "write_member_history" ON "member" IS 'When changing name or login of a member, create a history entry in "member_history" table';
jbe@13 757
jbe@13 758
jbe@13 759
jbe@0 760 ----------------------------
jbe@0 761 -- Additional constraints --
jbe@0 762 ----------------------------
jbe@0 763
jbe@0 764
jbe@0 765 CREATE FUNCTION "issue_requires_first_initiative_trigger"()
jbe@0 766 RETURNS TRIGGER
jbe@0 767 LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@0 768 BEGIN
jbe@0 769 IF NOT EXISTS (
jbe@0 770 SELECT NULL FROM "initiative" WHERE "issue_id" = NEW."id"
jbe@0 771 ) THEN
jbe@0 772 --RAISE 'Cannot create issue without an initial initiative.' USING
jbe@0 773 -- ERRCODE = 'integrity_constraint_violation',
jbe@0 774 -- HINT = 'Create issue, initiative, and draft within the same transaction.';
jbe@0 775 RAISE EXCEPTION 'Cannot create issue without an initial initiative.';
jbe@0 776 END IF;
jbe@0 777 RETURN NULL;
jbe@0 778 END;
jbe@0 779 $$;
jbe@0 780
jbe@0 781 CREATE CONSTRAINT TRIGGER "issue_requires_first_initiative"
jbe@0 782 AFTER INSERT OR UPDATE ON "issue" DEFERRABLE INITIALLY DEFERRED
jbe@0 783 FOR EACH ROW EXECUTE PROCEDURE
jbe@0 784 "issue_requires_first_initiative_trigger"();
jbe@0 785
jbe@0 786 COMMENT ON FUNCTION "issue_requires_first_initiative_trigger"() IS 'Implementation of trigger "issue_requires_first_initiative" on table "issue"';
jbe@0 787 COMMENT ON TRIGGER "issue_requires_first_initiative" ON "issue" IS 'Ensure that new issues have at least one initiative';
jbe@0 788
jbe@0 789
jbe@0 790 CREATE FUNCTION "last_initiative_deletes_issue_trigger"()
jbe@0 791 RETURNS TRIGGER
jbe@0 792 LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@0 793 DECLARE
jbe@0 794 "reference_lost" BOOLEAN;
jbe@0 795 BEGIN
jbe@0 796 IF TG_OP = 'DELETE' THEN
jbe@0 797 "reference_lost" := TRUE;
jbe@0 798 ELSE
jbe@0 799 "reference_lost" := NEW."issue_id" != OLD."issue_id";
jbe@0 800 END IF;
jbe@0 801 IF
jbe@0 802 "reference_lost" AND NOT EXISTS (
jbe@0 803 SELECT NULL FROM "initiative" WHERE "issue_id" = OLD."issue_id"
jbe@0 804 )
jbe@0 805 THEN
jbe@0 806 DELETE FROM "issue" WHERE "id" = OLD."issue_id";
jbe@0 807 END IF;
jbe@0 808 RETURN NULL;
jbe@0 809 END;
jbe@0 810 $$;
jbe@0 811
jbe@0 812 CREATE CONSTRAINT TRIGGER "last_initiative_deletes_issue"
jbe@0 813 AFTER UPDATE OR DELETE ON "initiative" DEFERRABLE INITIALLY DEFERRED
jbe@0 814 FOR EACH ROW EXECUTE PROCEDURE
jbe@0 815 "last_initiative_deletes_issue_trigger"();
jbe@0 816
jbe@0 817 COMMENT ON FUNCTION "last_initiative_deletes_issue_trigger"() IS 'Implementation of trigger "last_initiative_deletes_issue" on table "initiative"';
jbe@0 818 COMMENT ON TRIGGER "last_initiative_deletes_issue" ON "initiative" IS 'Removing the last initiative of an issue deletes the issue';
jbe@0 819
jbe@0 820
jbe@0 821 CREATE FUNCTION "initiative_requires_first_draft_trigger"()
jbe@0 822 RETURNS TRIGGER
jbe@0 823 LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@0 824 BEGIN
jbe@0 825 IF NOT EXISTS (
jbe@0 826 SELECT NULL FROM "draft" WHERE "initiative_id" = NEW."id"
jbe@0 827 ) THEN
jbe@0 828 --RAISE 'Cannot create initiative without an initial draft.' USING
jbe@0 829 -- ERRCODE = 'integrity_constraint_violation',
jbe@0 830 -- HINT = 'Create issue, initiative and draft within the same transaction.';
jbe@0 831 RAISE EXCEPTION 'Cannot create initiative without an initial draft.';
jbe@0 832 END IF;
jbe@0 833 RETURN NULL;
jbe@0 834 END;
jbe@0 835 $$;
jbe@0 836
jbe@0 837 CREATE CONSTRAINT TRIGGER "initiative_requires_first_draft"
jbe@0 838 AFTER INSERT OR UPDATE ON "initiative" DEFERRABLE INITIALLY DEFERRED
jbe@0 839 FOR EACH ROW EXECUTE PROCEDURE
jbe@0 840 "initiative_requires_first_draft_trigger"();
jbe@0 841
jbe@0 842 COMMENT ON FUNCTION "initiative_requires_first_draft_trigger"() IS 'Implementation of trigger "initiative_requires_first_draft" on table "initiative"';
jbe@0 843 COMMENT ON TRIGGER "initiative_requires_first_draft" ON "initiative" IS 'Ensure that new initiatives have at least one draft';
jbe@0 844
jbe@0 845
jbe@0 846 CREATE FUNCTION "last_draft_deletes_initiative_trigger"()
jbe@0 847 RETURNS TRIGGER
jbe@0 848 LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@0 849 DECLARE
jbe@0 850 "reference_lost" BOOLEAN;
jbe@0 851 BEGIN
jbe@0 852 IF TG_OP = 'DELETE' THEN
jbe@0 853 "reference_lost" := TRUE;
jbe@0 854 ELSE
jbe@0 855 "reference_lost" := NEW."initiative_id" != OLD."initiative_id";
jbe@0 856 END IF;
jbe@0 857 IF
jbe@0 858 "reference_lost" AND NOT EXISTS (
jbe@0 859 SELECT NULL FROM "draft" WHERE "initiative_id" = OLD."initiative_id"
jbe@0 860 )
jbe@0 861 THEN
jbe@0 862 DELETE FROM "initiative" WHERE "id" = OLD."initiative_id";
jbe@0 863 END IF;
jbe@0 864 RETURN NULL;
jbe@0 865 END;
jbe@0 866 $$;
jbe@0 867
jbe@0 868 CREATE CONSTRAINT TRIGGER "last_draft_deletes_initiative"
jbe@0 869 AFTER UPDATE OR DELETE ON "draft" DEFERRABLE INITIALLY DEFERRED
jbe@0 870 FOR EACH ROW EXECUTE PROCEDURE
jbe@0 871 "last_draft_deletes_initiative_trigger"();
jbe@0 872
jbe@0 873 COMMENT ON FUNCTION "last_draft_deletes_initiative_trigger"() IS 'Implementation of trigger "last_draft_deletes_initiative" on table "draft"';
jbe@0 874 COMMENT ON TRIGGER "last_draft_deletes_initiative" ON "draft" IS 'Removing the last draft of an initiative deletes the initiative';
jbe@0 875
jbe@0 876
jbe@0 877 CREATE FUNCTION "suggestion_requires_first_opinion_trigger"()
jbe@0 878 RETURNS TRIGGER
jbe@0 879 LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@0 880 BEGIN
jbe@0 881 IF NOT EXISTS (
jbe@0 882 SELECT NULL FROM "opinion" WHERE "suggestion_id" = NEW."id"
jbe@0 883 ) THEN
jbe@0 884 RAISE EXCEPTION 'Cannot create a suggestion without an opinion.';
jbe@0 885 END IF;
jbe@0 886 RETURN NULL;
jbe@0 887 END;
jbe@0 888 $$;
jbe@0 889
jbe@0 890 CREATE CONSTRAINT TRIGGER "suggestion_requires_first_opinion"
jbe@0 891 AFTER INSERT OR UPDATE ON "suggestion" DEFERRABLE INITIALLY DEFERRED
jbe@0 892 FOR EACH ROW EXECUTE PROCEDURE
jbe@0 893 "suggestion_requires_first_opinion_trigger"();
jbe@0 894
jbe@0 895 COMMENT ON FUNCTION "suggestion_requires_first_opinion_trigger"() IS 'Implementation of trigger "suggestion_requires_first_opinion" on table "suggestion"';
jbe@0 896 COMMENT ON TRIGGER "suggestion_requires_first_opinion" ON "suggestion" IS 'Ensure that new suggestions have at least one opinion';
jbe@0 897
jbe@0 898
jbe@0 899 CREATE FUNCTION "last_opinion_deletes_suggestion_trigger"()
jbe@0 900 RETURNS TRIGGER
jbe@0 901 LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@0 902 DECLARE
jbe@0 903 "reference_lost" BOOLEAN;
jbe@0 904 BEGIN
jbe@0 905 IF TG_OP = 'DELETE' THEN
jbe@0 906 "reference_lost" := TRUE;
jbe@0 907 ELSE
jbe@0 908 "reference_lost" := NEW."suggestion_id" != OLD."suggestion_id";
jbe@0 909 END IF;
jbe@0 910 IF
jbe@0 911 "reference_lost" AND NOT EXISTS (
jbe@0 912 SELECT NULL FROM "opinion" WHERE "suggestion_id" = OLD."suggestion_id"
jbe@0 913 )
jbe@0 914 THEN
jbe@0 915 DELETE FROM "suggestion" WHERE "id" = OLD."suggestion_id";
jbe@0 916 END IF;
jbe@0 917 RETURN NULL;
jbe@0 918 END;
jbe@0 919 $$;
jbe@0 920
jbe@0 921 CREATE CONSTRAINT TRIGGER "last_opinion_deletes_suggestion"
jbe@0 922 AFTER UPDATE OR DELETE ON "opinion" DEFERRABLE INITIALLY DEFERRED
jbe@0 923 FOR EACH ROW EXECUTE PROCEDURE
jbe@0 924 "last_opinion_deletes_suggestion_trigger"();
jbe@0 925
jbe@0 926 COMMENT ON FUNCTION "last_opinion_deletes_suggestion_trigger"() IS 'Implementation of trigger "last_opinion_deletes_suggestion" on table "opinion"';
jbe@0 927 COMMENT ON TRIGGER "last_opinion_deletes_suggestion" ON "opinion" IS 'Removing the last opinion of a suggestion deletes the suggestion';
jbe@0 928
jbe@0 929
jbe@0 930
jbe@20 931 ---------------------------------------------------------------
jbe@20 932 -- Ensure that votes are not modified when issues are frozen --
jbe@20 933 ---------------------------------------------------------------
jbe@20 934
jbe@20 935 -- NOTE: Frontends should ensure this anyway, but in case of programming
jbe@20 936 -- errors the following triggers ensure data integrity.
jbe@20 937
jbe@20 938
jbe@20 939 CREATE FUNCTION "forbid_changes_on_closed_issue_trigger"()
jbe@20 940 RETURNS TRIGGER
jbe@20 941 LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@20 942 DECLARE
jbe@20 943 "issue_row" "issue"%ROWTYPE;
jbe@20 944 BEGIN
jbe@20 945 SELECT INTO "issue_row" * FROM "issue"
jbe@20 946 WHERE "id" = NEW."issue_id" FOR SHARE;
jbe@20 947 IF "issue_row"."closed" NOTNULL THEN
jbe@20 948 RAISE EXCEPTION 'Tried to modify data belonging to a closed issue.';
jbe@20 949 END IF;
jbe@20 950 RETURN NULL;
jbe@20 951 END;
jbe@20 952 $$;
jbe@20 953
jbe@20 954 CREATE TRIGGER "forbid_changes_on_closed_issue"
jbe@20 955 AFTER INSERT OR UPDATE OR DELETE ON "direct_voter"
jbe@20 956 FOR EACH ROW EXECUTE PROCEDURE
jbe@20 957 "forbid_changes_on_closed_issue_trigger"();
jbe@20 958
jbe@20 959 CREATE TRIGGER "forbid_changes_on_closed_issue"
jbe@20 960 AFTER INSERT OR UPDATE OR DELETE ON "delegating_voter"
jbe@20 961 FOR EACH ROW EXECUTE PROCEDURE
jbe@20 962 "forbid_changes_on_closed_issue_trigger"();
jbe@20 963
jbe@20 964 CREATE TRIGGER "forbid_changes_on_closed_issue"
jbe@20 965 AFTER INSERT OR UPDATE OR DELETE ON "vote"
jbe@20 966 FOR EACH ROW EXECUTE PROCEDURE
jbe@20 967 "forbid_changes_on_closed_issue_trigger"();
jbe@20 968
jbe@20 969 COMMENT ON FUNCTION "forbid_changes_on_closed_issue_trigger"() IS 'Implementation of triggers "forbid_changes_on_closed_issue" on tables "direct_voter", "delegating_voter" and "vote"';
jbe@20 970 COMMENT ON TRIGGER "forbid_changes_on_closed_issue" ON "direct_voter" IS 'Ensures that frontends can''t tamper with votings of closed issues, in case of programming errors';
jbe@20 971 COMMENT ON TRIGGER "forbid_changes_on_closed_issue" ON "delegating_voter" IS 'Ensures that frontends can''t tamper with votings of closed issues, in case of programming errors';
jbe@20 972 COMMENT ON TRIGGER "forbid_changes_on_closed_issue" ON "vote" IS 'Ensures that frontends can''t tamper with votings of closed issues, in case of programming errors';
jbe@20 973
jbe@20 974
jbe@20 975
jbe@0 976 --------------------------------------------------------------------
jbe@0 977 -- Auto-retrieval of fields only needed for referential integrity --
jbe@0 978 --------------------------------------------------------------------
jbe@0 979
jbe@20 980
jbe@0 981 CREATE FUNCTION "autofill_issue_id_trigger"()
jbe@0 982 RETURNS TRIGGER
jbe@0 983 LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@0 984 BEGIN
jbe@0 985 IF NEW."issue_id" ISNULL THEN
jbe@0 986 SELECT "issue_id" INTO NEW."issue_id"
jbe@0 987 FROM "initiative" WHERE "id" = NEW."initiative_id";
jbe@0 988 END IF;
jbe@0 989 RETURN NEW;
jbe@0 990 END;
jbe@0 991 $$;
jbe@0 992
jbe@0 993 CREATE TRIGGER "autofill_issue_id" BEFORE INSERT ON "supporter"
jbe@0 994 FOR EACH ROW EXECUTE PROCEDURE "autofill_issue_id_trigger"();
jbe@0 995
jbe@0 996 CREATE TRIGGER "autofill_issue_id" BEFORE INSERT ON "vote"
jbe@0 997 FOR EACH ROW EXECUTE PROCEDURE "autofill_issue_id_trigger"();
jbe@0 998
jbe@0 999 COMMENT ON FUNCTION "autofill_issue_id_trigger"() IS 'Implementation of triggers "autofill_issue_id" on tables "supporter" and "vote"';
jbe@0 1000 COMMENT ON TRIGGER "autofill_issue_id" ON "supporter" IS 'Set "issue_id" field automatically, if NULL';
jbe@0 1001 COMMENT ON TRIGGER "autofill_issue_id" ON "vote" IS 'Set "issue_id" field automatically, if NULL';
jbe@0 1002
jbe@0 1003
jbe@0 1004 CREATE FUNCTION "autofill_initiative_id_trigger"()
jbe@0 1005 RETURNS TRIGGER
jbe@0 1006 LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@0 1007 BEGIN
jbe@0 1008 IF NEW."initiative_id" ISNULL THEN
jbe@0 1009 SELECT "initiative_id" INTO NEW."initiative_id"
jbe@0 1010 FROM "suggestion" WHERE "id" = NEW."suggestion_id";
jbe@0 1011 END IF;
jbe@0 1012 RETURN NEW;
jbe@0 1013 END;
jbe@0 1014 $$;
jbe@0 1015
jbe@0 1016 CREATE TRIGGER "autofill_initiative_id" BEFORE INSERT ON "opinion"
jbe@0 1017 FOR EACH ROW EXECUTE PROCEDURE "autofill_initiative_id_trigger"();
jbe@0 1018
jbe@0 1019 COMMENT ON FUNCTION "autofill_initiative_id_trigger"() IS 'Implementation of trigger "autofill_initiative_id" on table "opinion"';
jbe@0 1020 COMMENT ON TRIGGER "autofill_initiative_id" ON "opinion" IS 'Set "initiative_id" field automatically, if NULL';
jbe@0 1021
jbe@0 1022
jbe@0 1023
jbe@4 1024 -----------------------------------------------------
jbe@4 1025 -- Automatic calculation of certain default values --
jbe@4 1026 -----------------------------------------------------
jbe@0 1027
jbe@22 1028
jbe@22 1029 CREATE FUNCTION "copy_timings_trigger"()
jbe@22 1030 RETURNS TRIGGER
jbe@22 1031 LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@22 1032 DECLARE
jbe@22 1033 "policy_row" "policy"%ROWTYPE;
jbe@22 1034 BEGIN
jbe@22 1035 SELECT * INTO "policy_row" FROM "policy"
jbe@22 1036 WHERE "id" = NEW."policy_id";
jbe@22 1037 IF NEW."admission_time" ISNULL THEN
jbe@22 1038 NEW."admission_time" := "policy_row"."admission_time";
jbe@22 1039 END IF;
jbe@22 1040 IF NEW."discussion_time" ISNULL THEN
jbe@22 1041 NEW."discussion_time" := "policy_row"."discussion_time";
jbe@22 1042 END IF;
jbe@22 1043 IF NEW."verification_time" ISNULL THEN
jbe@22 1044 NEW."verification_time" := "policy_row"."verification_time";
jbe@22 1045 END IF;
jbe@22 1046 IF NEW."voting_time" ISNULL THEN
jbe@22 1047 NEW."voting_time" := "policy_row"."voting_time";
jbe@22 1048 END IF;
jbe@22 1049 RETURN NEW;
jbe@22 1050 END;
jbe@22 1051 $$;
jbe@22 1052
jbe@22 1053 CREATE TRIGGER "copy_timings" BEFORE INSERT OR UPDATE ON "issue"
jbe@22 1054 FOR EACH ROW EXECUTE PROCEDURE "copy_timings_trigger"();
jbe@22 1055
jbe@22 1056 COMMENT ON FUNCTION "copy_timings_trigger"() IS 'Implementation of trigger "copy_timings" on table "issue"';
jbe@22 1057 COMMENT ON TRIGGER "copy_timings" ON "issue" IS 'If timing fields are NULL, copy values from policy.';
jbe@22 1058
jbe@22 1059
jbe@0 1060 CREATE FUNCTION "copy_autoreject_trigger"()
jbe@0 1061 RETURNS TRIGGER
jbe@0 1062 LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@0 1063 BEGIN
jbe@0 1064 IF NEW."autoreject" ISNULL THEN
jbe@0 1065 SELECT "membership"."autoreject" INTO NEW."autoreject"
jbe@0 1066 FROM "issue" JOIN "membership"
jbe@0 1067 ON "issue"."area_id" = "membership"."area_id"
jbe@0 1068 WHERE "issue"."id" = NEW."issue_id"
jbe@0 1069 AND "membership"."member_id" = NEW."member_id";
jbe@0 1070 END IF;
jbe@0 1071 IF NEW."autoreject" ISNULL THEN
jbe@0 1072 NEW."autoreject" := FALSE;
jbe@0 1073 END IF;
jbe@0 1074 RETURN NEW;
jbe@0 1075 END;
jbe@0 1076 $$;
jbe@0 1077
jbe@0 1078 CREATE TRIGGER "copy_autoreject" BEFORE INSERT OR UPDATE ON "interest"
jbe@0 1079 FOR EACH ROW EXECUTE PROCEDURE "copy_autoreject_trigger"();
jbe@0 1080
jbe@0 1081 COMMENT ON FUNCTION "copy_autoreject_trigger"() IS 'Implementation of trigger "copy_autoreject" on table "interest"';
jbe@0 1082 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 1083
jbe@0 1084
jbe@2 1085 CREATE FUNCTION "supporter_default_for_draft_id_trigger"()
jbe@2 1086 RETURNS TRIGGER
jbe@2 1087 LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@2 1088 BEGIN
jbe@2 1089 IF NEW."draft_id" ISNULL THEN
jbe@2 1090 SELECT "id" INTO NEW."draft_id" FROM "current_draft"
jbe@2 1091 WHERE "initiative_id" = NEW."initiative_id";
jbe@2 1092 END IF;
jbe@2 1093 RETURN NEW;
jbe@2 1094 END;
jbe@2 1095 $$;
jbe@2 1096
jbe@2 1097 CREATE TRIGGER "default_for_draft_id" BEFORE INSERT OR UPDATE ON "supporter"
jbe@2 1098 FOR EACH ROW EXECUTE PROCEDURE "supporter_default_for_draft_id_trigger"();
jbe@2 1099
jbe@2 1100 COMMENT ON FUNCTION "supporter_default_for_draft_id_trigger"() IS 'Implementation of trigger "default_for_draft" on table "supporter"';
jbe@2 1101 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 1102
jbe@2 1103
jbe@0 1104
jbe@0 1105 ----------------------------------------
jbe@0 1106 -- Automatic creation of dependencies --
jbe@0 1107 ----------------------------------------
jbe@0 1108
jbe@22 1109
jbe@0 1110 CREATE FUNCTION "autocreate_interest_trigger"()
jbe@0 1111 RETURNS TRIGGER
jbe@0 1112 LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@0 1113 BEGIN
jbe@0 1114 IF NOT EXISTS (
jbe@0 1115 SELECT NULL FROM "initiative" JOIN "interest"
jbe@0 1116 ON "initiative"."issue_id" = "interest"."issue_id"
jbe@0 1117 WHERE "initiative"."id" = NEW."initiative_id"
jbe@0 1118 AND "interest"."member_id" = NEW."member_id"
jbe@0 1119 ) THEN
jbe@0 1120 BEGIN
jbe@0 1121 INSERT INTO "interest" ("issue_id", "member_id")
jbe@0 1122 SELECT "issue_id", NEW."member_id"
jbe@0 1123 FROM "initiative" WHERE "id" = NEW."initiative_id";
jbe@0 1124 EXCEPTION WHEN unique_violation THEN END;
jbe@0 1125 END IF;
jbe@0 1126 RETURN NEW;
jbe@0 1127 END;
jbe@0 1128 $$;
jbe@0 1129
jbe@0 1130 CREATE TRIGGER "autocreate_interest" BEFORE INSERT ON "supporter"
jbe@0 1131 FOR EACH ROW EXECUTE PROCEDURE "autocreate_interest_trigger"();
jbe@0 1132
jbe@0 1133 COMMENT ON FUNCTION "autocreate_interest_trigger"() IS 'Implementation of trigger "autocreate_interest" on table "supporter"';
jbe@0 1134 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 1135
jbe@0 1136
jbe@0 1137 CREATE FUNCTION "autocreate_supporter_trigger"()
jbe@0 1138 RETURNS TRIGGER
jbe@0 1139 LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@0 1140 BEGIN
jbe@0 1141 IF NOT EXISTS (
jbe@0 1142 SELECT NULL FROM "suggestion" JOIN "supporter"
jbe@0 1143 ON "suggestion"."initiative_id" = "supporter"."initiative_id"
jbe@0 1144 WHERE "suggestion"."id" = NEW."suggestion_id"
jbe@0 1145 AND "supporter"."member_id" = NEW."member_id"
jbe@0 1146 ) THEN
jbe@0 1147 BEGIN
jbe@0 1148 INSERT INTO "supporter" ("initiative_id", "member_id")
jbe@0 1149 SELECT "initiative_id", NEW."member_id"
jbe@0 1150 FROM "suggestion" WHERE "id" = NEW."suggestion_id";
jbe@0 1151 EXCEPTION WHEN unique_violation THEN END;
jbe@0 1152 END IF;
jbe@0 1153 RETURN NEW;
jbe@0 1154 END;
jbe@0 1155 $$;
jbe@0 1156
jbe@0 1157 CREATE TRIGGER "autocreate_supporter" BEFORE INSERT ON "opinion"
jbe@0 1158 FOR EACH ROW EXECUTE PROCEDURE "autocreate_supporter_trigger"();
jbe@0 1159
jbe@0 1160 COMMENT ON FUNCTION "autocreate_supporter_trigger"() IS 'Implementation of trigger "autocreate_supporter" on table "opinion"';
jbe@0 1161 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 1162
jbe@0 1163
jbe@0 1164
jbe@0 1165 ------------------------------------------
jbe@0 1166 -- Views and helper functions for views --
jbe@0 1167 ------------------------------------------
jbe@0 1168
jbe@5 1169
jbe@5 1170 CREATE VIEW "global_delegation" AS
jbe@5 1171 SELECT
jbe@5 1172 "delegation"."id",
jbe@5 1173 "delegation"."truster_id",
jbe@5 1174 "delegation"."trustee_id"
jbe@5 1175 FROM "delegation" JOIN "member"
jbe@5 1176 ON "delegation"."trustee_id" = "member"."id"
jbe@10 1177 WHERE "delegation"."scope" = 'global' AND "member"."active";
jbe@5 1178
jbe@5 1179 COMMENT ON VIEW "global_delegation" IS 'Global delegations to active members';
jbe@5 1180
jbe@5 1181
jbe@5 1182 CREATE VIEW "area_delegation" AS
jbe@5 1183 SELECT "subquery".* FROM (
jbe@5 1184 SELECT DISTINCT ON ("area"."id", "delegation"."truster_id")
jbe@10 1185 "area"."id" AS "area_id",
jbe@10 1186 "delegation"."id",
jbe@10 1187 "delegation"."truster_id",
jbe@10 1188 "delegation"."trustee_id",
jbe@10 1189 "delegation"."scope"
jbe@5 1190 FROM "area" JOIN "delegation"
jbe@10 1191 ON "delegation"."scope" = 'global'
jbe@10 1192 OR "delegation"."area_id" = "area"."id"
jbe@5 1193 ORDER BY
jbe@5 1194 "area"."id",
jbe@5 1195 "delegation"."truster_id",
jbe@10 1196 "delegation"."scope" DESC
jbe@5 1197 ) AS "subquery"
jbe@5 1198 JOIN "member" ON "subquery"."trustee_id" = "member"."id"
jbe@5 1199 WHERE "member"."active";
jbe@5 1200
jbe@5 1201 COMMENT ON VIEW "area_delegation" IS 'Active delegations for areas';
jbe@5 1202
jbe@5 1203
jbe@5 1204 CREATE VIEW "issue_delegation" AS
jbe@5 1205 SELECT "subquery".* FROM (
jbe@5 1206 SELECT DISTINCT ON ("issue"."id", "delegation"."truster_id")
jbe@10 1207 "issue"."id" AS "issue_id",
jbe@10 1208 "delegation"."id",
jbe@10 1209 "delegation"."truster_id",
jbe@10 1210 "delegation"."trustee_id",
jbe@10 1211 "delegation"."scope"
jbe@5 1212 FROM "issue" JOIN "delegation"
jbe@10 1213 ON "delegation"."scope" = 'global'
jbe@10 1214 OR "delegation"."area_id" = "issue"."area_id"
jbe@10 1215 OR "delegation"."issue_id" = "issue"."id"
jbe@5 1216 ORDER BY
jbe@5 1217 "issue"."id",
jbe@5 1218 "delegation"."truster_id",
jbe@10 1219 "delegation"."scope" DESC
jbe@5 1220 ) AS "subquery"
jbe@5 1221 JOIN "member" ON "subquery"."trustee_id" = "member"."id"
jbe@5 1222 WHERE "member"."active";
jbe@5 1223
jbe@5 1224 COMMENT ON VIEW "issue_delegation" IS 'Active delegations for issues';
jbe@5 1225
jbe@5 1226
jbe@5 1227 CREATE FUNCTION "membership_weight_with_skipping"
jbe@5 1228 ( "area_id_p" "area"."id"%TYPE,
jbe@5 1229 "member_id_p" "member"."id"%TYPE,
jbe@5 1230 "skip_member_ids_p" INT4[] ) -- "member"."id"%TYPE[]
jbe@5 1231 RETURNS INT4
jbe@5 1232 LANGUAGE 'plpgsql' STABLE AS $$
jbe@5 1233 DECLARE
jbe@5 1234 "sum_v" INT4;
jbe@5 1235 "delegation_row" "area_delegation"%ROWTYPE;
jbe@5 1236 BEGIN
jbe@5 1237 "sum_v" := 1;
jbe@5 1238 FOR "delegation_row" IN
jbe@5 1239 SELECT "area_delegation".*
jbe@5 1240 FROM "area_delegation" LEFT JOIN "membership"
jbe@5 1241 ON "membership"."area_id" = "area_id_p"
jbe@5 1242 AND "membership"."member_id" = "area_delegation"."truster_id"
jbe@5 1243 WHERE "area_delegation"."area_id" = "area_id_p"
jbe@5 1244 AND "area_delegation"."trustee_id" = "member_id_p"
jbe@5 1245 AND "membership"."member_id" ISNULL
jbe@5 1246 LOOP
jbe@5 1247 IF NOT
jbe@5 1248 "skip_member_ids_p" @> ARRAY["delegation_row"."truster_id"]
jbe@5 1249 THEN
jbe@5 1250 "sum_v" := "sum_v" + "membership_weight_with_skipping"(
jbe@5 1251 "area_id_p",
jbe@5 1252 "delegation_row"."truster_id",
jbe@5 1253 "skip_member_ids_p" || "delegation_row"."truster_id"
jbe@5 1254 );
jbe@5 1255 END IF;
jbe@5 1256 END LOOP;
jbe@5 1257 RETURN "sum_v";
jbe@5 1258 END;
jbe@5 1259 $$;
jbe@5 1260
jbe@8 1261 COMMENT ON FUNCTION "membership_weight_with_skipping"
jbe@8 1262 ( "area"."id"%TYPE,
jbe@8 1263 "member"."id"%TYPE,
jbe@8 1264 INT4[] )
jbe@8 1265 IS 'Helper function for "membership_weight" function';
jbe@8 1266
jbe@8 1267
jbe@5 1268 CREATE FUNCTION "membership_weight"
jbe@5 1269 ( "area_id_p" "area"."id"%TYPE,
jbe@5 1270 "member_id_p" "member"."id"%TYPE ) -- "member"."id"%TYPE[]
jbe@5 1271 RETURNS INT4
jbe@5 1272 LANGUAGE 'plpgsql' STABLE AS $$
jbe@5 1273 BEGIN
jbe@5 1274 RETURN "membership_weight_with_skipping"(
jbe@5 1275 "area_id_p",
jbe@5 1276 "member_id_p",
jbe@5 1277 ARRAY["member_id_p"]
jbe@5 1278 );
jbe@5 1279 END;
jbe@5 1280 $$;
jbe@5 1281
jbe@8 1282 COMMENT ON FUNCTION "membership_weight"
jbe@8 1283 ( "area"."id"%TYPE,
jbe@8 1284 "member"."id"%TYPE )
jbe@8 1285 IS 'Calculates the potential voting weight of a member in a given area';
jbe@8 1286
jbe@5 1287
jbe@4 1288 CREATE VIEW "member_count_view" AS
jbe@5 1289 SELECT count(1) AS "total_count" FROM "member" WHERE "active";
jbe@4 1290
jbe@4 1291 COMMENT ON VIEW "member_count_view" IS 'View used to update "member_count" table';
jbe@4 1292
jbe@4 1293
jbe@4 1294 CREATE VIEW "area_member_count" AS
jbe@5 1295 SELECT
jbe@5 1296 "area"."id" AS "area_id",
jbe@5 1297 count("member"."id") AS "direct_member_count",
jbe@5 1298 coalesce(
jbe@5 1299 sum(
jbe@5 1300 CASE WHEN "member"."id" NOTNULL THEN
jbe@5 1301 "membership_weight"("area"."id", "member"."id")
jbe@5 1302 ELSE 0 END
jbe@5 1303 )
jbe@5 1304 ) AS "member_weight",
jbe@5 1305 coalesce(
jbe@5 1306 sum(
jbe@5 1307 CASE WHEN "member"."id" NOTNULL AND "membership"."autoreject" THEN
jbe@5 1308 "membership_weight"("area"."id", "member"."id")
jbe@5 1309 ELSE 0 END
jbe@5 1310 )
jbe@5 1311 ) AS "autoreject_weight"
jbe@4 1312 FROM "area"
jbe@4 1313 LEFT JOIN "membership"
jbe@4 1314 ON "area"."id" = "membership"."area_id"
jbe@4 1315 LEFT JOIN "member"
jbe@4 1316 ON "membership"."member_id" = "member"."id"
jbe@4 1317 AND "member"."active"
jbe@4 1318 GROUP BY "area"."id";
jbe@4 1319
jbe@4 1320 COMMENT ON VIEW "area_member_count" IS 'View used to update "member_count" column of table "area"';
jbe@4 1321
jbe@4 1322
jbe@9 1323 CREATE VIEW "opening_draft" AS
jbe@9 1324 SELECT "draft".* FROM (
jbe@9 1325 SELECT
jbe@9 1326 "initiative"."id" AS "initiative_id",
jbe@9 1327 min("draft"."id") AS "draft_id"
jbe@9 1328 FROM "initiative" JOIN "draft"
jbe@9 1329 ON "initiative"."id" = "draft"."initiative_id"
jbe@9 1330 GROUP BY "initiative"."id"
jbe@9 1331 ) AS "subquery"
jbe@9 1332 JOIN "draft" ON "subquery"."draft_id" = "draft"."id";
jbe@9 1333
jbe@9 1334 COMMENT ON VIEW "opening_draft" IS 'First drafts of all initiatives';
jbe@9 1335
jbe@9 1336
jbe@0 1337 CREATE VIEW "current_draft" AS
jbe@0 1338 SELECT "draft".* FROM (
jbe@0 1339 SELECT
jbe@0 1340 "initiative"."id" AS "initiative_id",
jbe@0 1341 max("draft"."id") AS "draft_id"
jbe@0 1342 FROM "initiative" JOIN "draft"
jbe@0 1343 ON "initiative"."id" = "draft"."initiative_id"
jbe@0 1344 GROUP BY "initiative"."id"
jbe@0 1345 ) AS "subquery"
jbe@0 1346 JOIN "draft" ON "subquery"."draft_id" = "draft"."id";
jbe@0 1347
jbe@0 1348 COMMENT ON VIEW "current_draft" IS 'All latest drafts for each initiative';
jbe@0 1349
jbe@0 1350
jbe@0 1351 CREATE VIEW "critical_opinion" AS
jbe@0 1352 SELECT * FROM "opinion"
jbe@0 1353 WHERE ("degree" = 2 AND "fulfilled" = FALSE)
jbe@0 1354 OR ("degree" = -2 AND "fulfilled" = TRUE);
jbe@0 1355
jbe@0 1356 COMMENT ON VIEW "critical_opinion" IS 'Opinions currently causing dissatisfaction';
jbe@0 1357
jbe@0 1358
jbe@0 1359 CREATE VIEW "battle" AS
jbe@0 1360 SELECT
jbe@0 1361 "issue"."id" AS "issue_id",
jbe@10 1362 "winning_initiative"."id" AS "winning_initiative_id",
jbe@10 1363 "losing_initiative"."id" AS "losing_initiative_id",
jbe@0 1364 sum(
jbe@0 1365 CASE WHEN
jbe@0 1366 coalesce("better_vote"."grade", 0) >
jbe@0 1367 coalesce("worse_vote"."grade", 0)
jbe@0 1368 THEN "direct_voter"."weight" ELSE 0 END
jbe@0 1369 ) AS "count"
jbe@0 1370 FROM "issue"
jbe@0 1371 LEFT JOIN "direct_voter"
jbe@0 1372 ON "issue"."id" = "direct_voter"."issue_id"
jbe@10 1373 JOIN "initiative" AS "winning_initiative"
jbe@10 1374 ON "issue"."id" = "winning_initiative"."issue_id"
jbe@10 1375 AND "winning_initiative"."agreed"
jbe@10 1376 JOIN "initiative" AS "losing_initiative"
jbe@10 1377 ON "issue"."id" = "losing_initiative"."issue_id"
jbe@10 1378 AND "losing_initiative"."agreed"
jbe@0 1379 LEFT JOIN "vote" AS "better_vote"
jbe@10 1380 ON "direct_voter"."member_id" = "better_vote"."member_id"
jbe@10 1381 AND "winning_initiative"."id" = "better_vote"."initiative_id"
jbe@0 1382 LEFT JOIN "vote" AS "worse_vote"
jbe@10 1383 ON "direct_voter"."member_id" = "worse_vote"."member_id"
jbe@10 1384 AND "losing_initiative"."id" = "worse_vote"."initiative_id"
jbe@0 1385 WHERE
jbe@10 1386 "winning_initiative"."id" != "losing_initiative"."id"
jbe@0 1387 GROUP BY
jbe@0 1388 "issue"."id",
jbe@10 1389 "winning_initiative"."id",
jbe@10 1390 "losing_initiative"."id";
jbe@0 1391
jbe@0 1392 COMMENT ON VIEW "battle" IS 'Number of members preferring one initiative over another';
jbe@0 1393
jbe@0 1394
jbe@1 1395 CREATE VIEW "expired_session" AS
jbe@1 1396 SELECT * FROM "session" WHERE now() > "expiry";
jbe@1 1397
jbe@1 1398 CREATE RULE "delete" AS ON DELETE TO "expired_session" DO INSTEAD
jbe@1 1399 DELETE FROM "session" WHERE "ident" = OLD."ident";
jbe@1 1400
jbe@1 1401 COMMENT ON VIEW "expired_session" IS 'View containing all expired sessions where DELETE is possible';
jbe@1 1402 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 1403
jbe@1 1404
jbe@0 1405 CREATE VIEW "open_issue" AS
jbe@0 1406 SELECT * FROM "issue" WHERE "closed" ISNULL;
jbe@0 1407
jbe@0 1408 COMMENT ON VIEW "open_issue" IS 'All open issues';
jbe@0 1409
jbe@0 1410
jbe@0 1411 CREATE VIEW "issue_with_ranks_missing" AS
jbe@0 1412 SELECT * FROM "issue"
jbe@3 1413 WHERE "fully_frozen" NOTNULL
jbe@0 1414 AND "closed" NOTNULL
jbe@0 1415 AND "ranks_available" = FALSE;
jbe@0 1416
jbe@0 1417 COMMENT ON VIEW "issue_with_ranks_missing" IS 'Issues where voting was finished, but no ranks have been calculated yet';
jbe@0 1418
jbe@0 1419
jbe@9 1420 CREATE VIEW "member_contingent" AS
jbe@9 1421 SELECT
jbe@9 1422 "member"."id" AS "member_id",
jbe@9 1423 "contingent"."time_frame",
jbe@9 1424 CASE WHEN "contingent"."text_entry_limit" NOTNULL THEN
jbe@9 1425 (
jbe@9 1426 SELECT count(1) FROM "draft"
jbe@9 1427 WHERE "draft"."author_id" = "member"."id"
jbe@9 1428 AND "draft"."created" > now() - "contingent"."time_frame"
jbe@9 1429 ) + (
jbe@9 1430 SELECT count(1) FROM "suggestion"
jbe@9 1431 WHERE "suggestion"."author_id" = "member"."id"
jbe@9 1432 AND "suggestion"."created" > now() - "contingent"."time_frame"
jbe@9 1433 )
jbe@9 1434 ELSE NULL END AS "text_entry_count",
jbe@9 1435 "contingent"."text_entry_limit",
jbe@9 1436 CASE WHEN "contingent"."initiative_limit" NOTNULL THEN (
jbe@9 1437 SELECT count(1) FROM "opening_draft"
jbe@9 1438 WHERE "opening_draft"."author_id" = "member"."id"
jbe@9 1439 AND "opening_draft"."created" > now() - "contingent"."time_frame"
jbe@9 1440 ) ELSE NULL END AS "initiative_count",
jbe@9 1441 "contingent"."initiative_limit"
jbe@9 1442 FROM "member" CROSS JOIN "contingent";
jbe@9 1443
jbe@9 1444 COMMENT ON VIEW "member_contingent" IS 'Actual counts of text entries and initiatives are calculated per member for each limit in the "contingent" table.';
jbe@9 1445
jbe@9 1446 COMMENT ON COLUMN "member_contingent"."text_entry_count" IS 'Only calculated when "text_entry_limit" is not null in the same row';
jbe@9 1447 COMMENT ON COLUMN "member_contingent"."initiative_count" IS 'Only calculated when "initiative_limit" is not null in the same row';
jbe@9 1448
jbe@9 1449
jbe@9 1450 CREATE VIEW "member_contingent_left" AS
jbe@9 1451 SELECT
jbe@9 1452 "member_id",
jbe@9 1453 max("text_entry_limit" - "text_entry_count") AS "text_entries_left",
jbe@9 1454 max("initiative_limit" - "initiative_count") AS "initiatives_left"
jbe@9 1455 FROM "member_contingent" GROUP BY "member_id";
jbe@9 1456
jbe@9 1457 COMMENT ON VIEW "member_contingent_left" IS 'Amount of text entries or initiatives which can be posted now instantly by a member. This view should be used by a frontend to determine, if the contingent for posting is exhausted.';
jbe@9 1458
jbe@9 1459
jbe@16 1460 CREATE TYPE "timeline_event" AS ENUM (
jbe@16 1461 'issue_created',
jbe@16 1462 'issue_canceled',
jbe@16 1463 'issue_accepted',
jbe@16 1464 'issue_half_frozen',
jbe@16 1465 'issue_finished_without_voting',
jbe@16 1466 'issue_voting_started',
jbe@16 1467 'issue_finished_after_voting',
jbe@16 1468 'initiative_created',
jbe@16 1469 'initiative_revoked',
jbe@16 1470 'draft_created',
jbe@16 1471 'suggestion_created');
jbe@16 1472
jbe@16 1473 COMMENT ON TYPE "timeline_event" IS 'Types of event in timeline tables';
jbe@16 1474
jbe@16 1475
jbe@16 1476 CREATE VIEW "timeline_issue" AS
jbe@16 1477 SELECT
jbe@16 1478 "created" AS "occurrence",
jbe@16 1479 'issue_created'::"timeline_event" AS "event",
jbe@16 1480 "id" AS "issue_id"
jbe@16 1481 FROM "issue"
jbe@16 1482 UNION ALL
jbe@16 1483 SELECT
jbe@16 1484 "closed" AS "occurrence",
jbe@16 1485 'issue_canceled'::"timeline_event" AS "event",
jbe@16 1486 "id" AS "issue_id"
jbe@16 1487 FROM "issue" WHERE "closed" NOTNULL AND "fully_frozen" ISNULL
jbe@16 1488 UNION ALL
jbe@16 1489 SELECT
jbe@16 1490 "accepted" AS "occurrence",
jbe@16 1491 'issue_accepted'::"timeline_event" AS "event",
jbe@16 1492 "id" AS "issue_id"
jbe@16 1493 FROM "issue" WHERE "accepted" NOTNULL
jbe@16 1494 UNION ALL
jbe@16 1495 SELECT
jbe@16 1496 "half_frozen" AS "occurrence",
jbe@16 1497 'issue_half_frozen'::"timeline_event" AS "event",
jbe@16 1498 "id" AS "issue_id"
jbe@16 1499 FROM "issue" WHERE "half_frozen" NOTNULL
jbe@16 1500 UNION ALL
jbe@16 1501 SELECT
jbe@16 1502 "fully_frozen" AS "occurrence",
jbe@16 1503 'issue_voting_started'::"timeline_event" AS "event",
jbe@16 1504 "id" AS "issue_id"
jbe@16 1505 FROM "issue"
jbe@17 1506 WHERE "fully_frozen" NOTNULL
jbe@17 1507 AND ("closed" ISNULL OR "closed" != "fully_frozen")
jbe@16 1508 UNION ALL
jbe@16 1509 SELECT
jbe@16 1510 "closed" AS "occurrence",
jbe@16 1511 CASE WHEN "fully_frozen" = "closed" THEN
jbe@16 1512 'issue_finished_without_voting'::"timeline_event"
jbe@16 1513 ELSE
jbe@16 1514 'issue_finished_after_voting'::"timeline_event"
jbe@16 1515 END AS "event",
jbe@16 1516 "id" AS "issue_id"
jbe@16 1517 FROM "issue" WHERE "closed" NOTNULL AND "fully_frozen" NOTNULL;
jbe@16 1518
jbe@16 1519 COMMENT ON VIEW "timeline_issue" IS 'Helper view for "timeline" view';
jbe@16 1520
jbe@16 1521
jbe@16 1522 CREATE VIEW "timeline_initiative" AS
jbe@16 1523 SELECT
jbe@16 1524 "created" AS "occurrence",
jbe@16 1525 'initiative_created'::"timeline_event" AS "event",
jbe@16 1526 "id" AS "initiative_id"
jbe@16 1527 FROM "initiative"
jbe@16 1528 UNION ALL
jbe@16 1529 SELECT
jbe@16 1530 "revoked" AS "occurrence",
jbe@16 1531 'initiative_revoked'::"timeline_event" AS "event",
jbe@16 1532 "id" AS "initiative_id"
jbe@16 1533 FROM "initiative" WHERE "revoked" NOTNULL;
jbe@16 1534
jbe@16 1535 COMMENT ON VIEW "timeline_initiative" IS 'Helper view for "timeline" view';
jbe@16 1536
jbe@16 1537
jbe@16 1538 CREATE VIEW "timeline_draft" AS
jbe@16 1539 SELECT
jbe@16 1540 "created" AS "occurrence",
jbe@16 1541 'draft_created'::"timeline_event" AS "event",
jbe@16 1542 "id" AS "draft_id"
jbe@16 1543 FROM "draft";
jbe@16 1544
jbe@16 1545 COMMENT ON VIEW "timeline_draft" IS 'Helper view for "timeline" view';
jbe@16 1546
jbe@16 1547
jbe@16 1548 CREATE VIEW "timeline_suggestion" AS
jbe@16 1549 SELECT
jbe@16 1550 "created" AS "occurrence",
jbe@16 1551 'suggestion_created'::"timeline_event" AS "event",
jbe@16 1552 "id" AS "suggestion_id"
jbe@16 1553 FROM "suggestion";
jbe@16 1554
jbe@16 1555 COMMENT ON VIEW "timeline_suggestion" IS 'Helper view for "timeline" view';
jbe@16 1556
jbe@16 1557
jbe@16 1558 CREATE VIEW "timeline" AS
jbe@16 1559 SELECT
jbe@16 1560 "occurrence",
jbe@16 1561 "event",
jbe@16 1562 "issue_id",
jbe@16 1563 NULL AS "initiative_id",
jbe@16 1564 NULL::INT8 AS "draft_id", -- TODO: Why do we need a type-cast here? Is this due to 32 bit architecture?
jbe@16 1565 NULL::INT8 AS "suggestion_id"
jbe@16 1566 FROM "timeline_issue"
jbe@16 1567 UNION ALL
jbe@16 1568 SELECT
jbe@16 1569 "occurrence",
jbe@16 1570 "event",
jbe@16 1571 NULL AS "issue_id",
jbe@16 1572 "initiative_id",
jbe@16 1573 NULL AS "draft_id",
jbe@16 1574 NULL AS "suggestion_id"
jbe@16 1575 FROM "timeline_initiative"
jbe@16 1576 UNION ALL
jbe@16 1577 SELECT
jbe@16 1578 "occurrence",
jbe@16 1579 "event",
jbe@16 1580 NULL AS "issue_id",
jbe@16 1581 NULL AS "initiative_id",
jbe@16 1582 "draft_id",
jbe@16 1583 NULL AS "suggestion_id"
jbe@16 1584 FROM "timeline_draft"
jbe@16 1585 UNION ALL
jbe@16 1586 SELECT
jbe@16 1587 "occurrence",
jbe@16 1588 "event",
jbe@16 1589 NULL AS "issue_id",
jbe@16 1590 NULL AS "initiative_id",
jbe@16 1591 NULL AS "draft_id",
jbe@16 1592 "suggestion_id"
jbe@16 1593 FROM "timeline_suggestion";
jbe@16 1594
jbe@16 1595 COMMENT ON VIEW "timeline" IS 'Aggregation of different events in the system';
jbe@16 1596
jbe@16 1597
jbe@0 1598
jbe@5 1599 --------------------------------------------------
jbe@5 1600 -- Set returning function for delegation chains --
jbe@5 1601 --------------------------------------------------
jbe@5 1602
jbe@5 1603
jbe@5 1604 CREATE TYPE "delegation_chain_loop_tag" AS ENUM
jbe@5 1605 ('first', 'intermediate', 'last', 'repetition');
jbe@5 1606
jbe@5 1607 COMMENT ON TYPE "delegation_chain_loop_tag" IS 'Type for loop tags in "delegation_chain_row" type';
jbe@5 1608
jbe@5 1609
jbe@5 1610 CREATE TYPE "delegation_chain_row" AS (
jbe@5 1611 "index" INT4,
jbe@5 1612 "member_id" INT4,
jbe@5 1613 "member_active" BOOLEAN,
jbe@5 1614 "participation" BOOLEAN,
jbe@5 1615 "overridden" BOOLEAN,
jbe@5 1616 "scope_in" "delegation_scope",
jbe@5 1617 "scope_out" "delegation_scope",
jbe@5 1618 "loop" "delegation_chain_loop_tag" );
jbe@5 1619
jbe@5 1620 COMMENT ON TYPE "delegation_chain_row" IS 'Type of rows returned by "delegation_chain"(...) functions';
jbe@5 1621
jbe@5 1622 COMMENT ON COLUMN "delegation_chain_row"."index" IS 'Index starting with 0 and counting up';
jbe@5 1623 COMMENT ON COLUMN "delegation_chain_row"."participation" IS 'In case of delegation chains for issues: interest, for areas: membership, for global delegation chains: always null';
jbe@5 1624 COMMENT ON COLUMN "delegation_chain_row"."overridden" IS 'True, if an entry with lower index has "participation" set to true';
jbe@5 1625 COMMENT ON COLUMN "delegation_chain_row"."scope_in" IS 'Scope of used incoming delegation';
jbe@5 1626 COMMENT ON COLUMN "delegation_chain_row"."scope_out" IS 'Scope of used outgoing delegation';
jbe@5 1627 COMMENT ON COLUMN "delegation_chain_row"."loop" IS 'Not null, if member is part of a loop, see "delegation_chain_loop_tag" type';
jbe@5 1628
jbe@5 1629
jbe@5 1630 CREATE FUNCTION "delegation_chain"
jbe@5 1631 ( "member_id_p" "member"."id"%TYPE,
jbe@5 1632 "area_id_p" "area"."id"%TYPE,
jbe@5 1633 "issue_id_p" "issue"."id"%TYPE,
jbe@5 1634 "simulate_trustee_id_p" "member"."id"%TYPE )
jbe@5 1635 RETURNS SETOF "delegation_chain_row"
jbe@5 1636 LANGUAGE 'plpgsql' STABLE AS $$
jbe@5 1637 DECLARE
jbe@5 1638 "issue_row" "issue"%ROWTYPE;
jbe@5 1639 "visited_member_ids" INT4[]; -- "member"."id"%TYPE[]
jbe@5 1640 "loop_member_id_v" "member"."id"%TYPE;
jbe@5 1641 "output_row" "delegation_chain_row";
jbe@5 1642 "output_rows" "delegation_chain_row"[];
jbe@5 1643 "delegation_row" "delegation"%ROWTYPE;
jbe@5 1644 "row_count" INT4;
jbe@5 1645 "i" INT4;
jbe@5 1646 "loop_v" BOOLEAN;
jbe@5 1647 BEGIN
jbe@5 1648 SELECT * INTO "issue_row" FROM "issue" WHERE "id" = "issue_id_p";
jbe@5 1649 "visited_member_ids" := '{}';
jbe@5 1650 "loop_member_id_v" := NULL;
jbe@5 1651 "output_rows" := '{}';
jbe@5 1652 "output_row"."index" := 0;
jbe@5 1653 "output_row"."member_id" := "member_id_p";
jbe@5 1654 "output_row"."member_active" := TRUE;
jbe@5 1655 "output_row"."participation" := FALSE;
jbe@5 1656 "output_row"."overridden" := FALSE;
jbe@5 1657 "output_row"."scope_out" := NULL;
jbe@5 1658 LOOP
jbe@5 1659 IF "visited_member_ids" @> ARRAY["output_row"."member_id"] THEN
jbe@5 1660 "loop_member_id_v" := "output_row"."member_id";
jbe@5 1661 ELSE
jbe@5 1662 "visited_member_ids" :=
jbe@5 1663 "visited_member_ids" || "output_row"."member_id";
jbe@5 1664 END IF;
jbe@5 1665 IF "output_row"."participation" THEN
jbe@5 1666 "output_row"."overridden" := TRUE;
jbe@5 1667 END IF;
jbe@5 1668 "output_row"."scope_in" := "output_row"."scope_out";
jbe@5 1669 IF EXISTS (
jbe@5 1670 SELECT NULL FROM "member"
jbe@5 1671 WHERE "id" = "output_row"."member_id" AND "active"
jbe@5 1672 ) THEN
jbe@5 1673 IF "area_id_p" ISNULL AND "issue_id_p" ISNULL THEN
jbe@5 1674 SELECT * INTO "delegation_row" FROM "delegation"
jbe@5 1675 WHERE "truster_id" = "output_row"."member_id"
jbe@10 1676 AND "scope" = 'global';
jbe@5 1677 ELSIF "area_id_p" NOTNULL AND "issue_id_p" ISNULL THEN
jbe@5 1678 "output_row"."participation" := EXISTS (
jbe@5 1679 SELECT NULL FROM "membership"
jbe@5 1680 WHERE "area_id" = "area_id_p"
jbe@5 1681 AND "member_id" = "output_row"."member_id"
jbe@5 1682 );
jbe@5 1683 SELECT * INTO "delegation_row" FROM "delegation"
jbe@5 1684 WHERE "truster_id" = "output_row"."member_id"
jbe@10 1685 AND ("scope" = 'global' OR "area_id" = "area_id_p")
jbe@10 1686 ORDER BY "scope" DESC;
jbe@5 1687 ELSIF "area_id_p" ISNULL AND "issue_id_p" NOTNULL THEN
jbe@5 1688 "output_row"."participation" := EXISTS (
jbe@5 1689 SELECT NULL FROM "interest"
jbe@5 1690 WHERE "issue_id" = "issue_id_p"
jbe@5 1691 AND "member_id" = "output_row"."member_id"
jbe@5 1692 );
jbe@5 1693 SELECT * INTO "delegation_row" FROM "delegation"
jbe@5 1694 WHERE "truster_id" = "output_row"."member_id"
jbe@10 1695 AND ("scope" = 'global' OR
jbe@10 1696 "area_id" = "issue_row"."area_id" OR
jbe@10 1697 "issue_id" = "issue_id_p"
jbe@10 1698 )
jbe@10 1699 ORDER BY "scope" DESC;
jbe@5 1700 ELSE
jbe@5 1701 RAISE EXCEPTION 'Either area_id or issue_id or both must be NULL.';
jbe@5 1702 END IF;
jbe@5 1703 ELSE
jbe@5 1704 "output_row"."member_active" := FALSE;
jbe@5 1705 "output_row"."participation" := FALSE;
jbe@5 1706 "output_row"."scope_out" := NULL;
jbe@5 1707 "delegation_row" := ROW(NULL);
jbe@5 1708 END IF;
jbe@5 1709 IF
jbe@5 1710 "output_row"."member_id" = "member_id_p" AND
jbe@5 1711 "simulate_trustee_id_p" NOTNULL
jbe@5 1712 THEN
jbe@5 1713 "output_row"."scope_out" := CASE
jbe@5 1714 WHEN "area_id_p" ISNULL AND "issue_id_p" ISNULL THEN 'global'
jbe@5 1715 WHEN "area_id_p" NOTNULL AND "issue_id_p" ISNULL THEN 'area'
jbe@5 1716 WHEN "area_id_p" ISNULL AND "issue_id_p" NOTNULL THEN 'issue'
jbe@5 1717 END;
jbe@5 1718 "output_rows" := "output_rows" || "output_row";
jbe@5 1719 "output_row"."member_id" := "simulate_trustee_id_p";
jbe@5 1720 ELSIF "delegation_row"."trustee_id" NOTNULL THEN
jbe@10 1721 "output_row"."scope_out" := "delegation_row"."scope";
jbe@5 1722 "output_rows" := "output_rows" || "output_row";
jbe@5 1723 "output_row"."member_id" := "delegation_row"."trustee_id";
jbe@5 1724 ELSE
jbe@5 1725 "output_row"."scope_out" := NULL;
jbe@5 1726 "output_rows" := "output_rows" || "output_row";
jbe@5 1727 EXIT;
jbe@5 1728 END IF;
jbe@5 1729 EXIT WHEN "loop_member_id_v" NOTNULL;
jbe@5 1730 "output_row"."index" := "output_row"."index" + 1;
jbe@5 1731 END LOOP;
jbe@5 1732 "row_count" := array_upper("output_rows", 1);
jbe@5 1733 "i" := 1;
jbe@5 1734 "loop_v" := FALSE;
jbe@5 1735 LOOP
jbe@5 1736 "output_row" := "output_rows"["i"];
jbe@5 1737 EXIT WHEN "output_row"."member_id" ISNULL;
jbe@5 1738 IF "loop_v" THEN
jbe@5 1739 IF "i" + 1 = "row_count" THEN
jbe@5 1740 "output_row"."loop" := 'last';
jbe@5 1741 ELSIF "i" = "row_count" THEN
jbe@5 1742 "output_row"."loop" := 'repetition';
jbe@5 1743 ELSE
jbe@5 1744 "output_row"."loop" := 'intermediate';
jbe@5 1745 END IF;
jbe@5 1746 ELSIF "output_row"."member_id" = "loop_member_id_v" THEN
jbe@5 1747 "output_row"."loop" := 'first';
jbe@5 1748 "loop_v" := TRUE;
jbe@5 1749 END IF;
jbe@5 1750 IF "area_id_p" ISNULL AND "issue_id_p" ISNULL THEN
jbe@5 1751 "output_row"."participation" := NULL;
jbe@5 1752 END IF;
jbe@5 1753 RETURN NEXT "output_row";
jbe@5 1754 "i" := "i" + 1;
jbe@5 1755 END LOOP;
jbe@5 1756 RETURN;
jbe@5 1757 END;
jbe@5 1758 $$;
jbe@5 1759
jbe@5 1760 COMMENT ON FUNCTION "delegation_chain"
jbe@5 1761 ( "member"."id"%TYPE,
jbe@5 1762 "area"."id"%TYPE,
jbe@5 1763 "issue"."id"%TYPE,
jbe@5 1764 "member"."id"%TYPE )
jbe@5 1765 IS 'Helper function for frontends to display delegation chains; Not part of internal voting logic';
jbe@5 1766
jbe@5 1767 CREATE FUNCTION "delegation_chain"
jbe@5 1768 ( "member_id_p" "member"."id"%TYPE,
jbe@5 1769 "area_id_p" "area"."id"%TYPE,
jbe@5 1770 "issue_id_p" "issue"."id"%TYPE )
jbe@5 1771 RETURNS SETOF "delegation_chain_row"
jbe@5 1772 LANGUAGE 'plpgsql' STABLE AS $$
jbe@5 1773 DECLARE
jbe@5 1774 "result_row" "delegation_chain_row";
jbe@5 1775 BEGIN
jbe@5 1776 FOR "result_row" IN
jbe@5 1777 SELECT * FROM "delegation_chain"(
jbe@5 1778 "member_id_p", "area_id_p", "issue_id_p", NULL
jbe@5 1779 )
jbe@5 1780 LOOP
jbe@5 1781 RETURN NEXT "result_row";
jbe@5 1782 END LOOP;
jbe@5 1783 RETURN;
jbe@5 1784 END;
jbe@5 1785 $$;
jbe@5 1786
jbe@5 1787 COMMENT ON FUNCTION "delegation_chain"
jbe@5 1788 ( "member"."id"%TYPE,
jbe@5 1789 "area"."id"%TYPE,
jbe@5 1790 "issue"."id"%TYPE )
jbe@5 1791 IS 'Shortcut for "delegation_chain"(...) function where 4th parameter is null';
jbe@5 1792
jbe@5 1793
jbe@5 1794
jbe@0 1795 ------------------------------
jbe@0 1796 -- Comparison by vote count --
jbe@0 1797 ------------------------------
jbe@0 1798
jbe@0 1799 CREATE FUNCTION "vote_ratio"
jbe@0 1800 ( "positive_votes_p" "initiative"."positive_votes"%TYPE,
jbe@0 1801 "negative_votes_p" "initiative"."negative_votes"%TYPE )
jbe@0 1802 RETURNS FLOAT8
jbe@0 1803 LANGUAGE 'plpgsql' STABLE AS $$
jbe@0 1804 DECLARE
jbe@0 1805 "total_v" INT4;
jbe@0 1806 BEGIN
jbe@0 1807 "total_v" := "positive_votes_p" + "negative_votes_p";
jbe@0 1808 IF "total_v" > 0 THEN
jbe@0 1809 RETURN "positive_votes_p"::FLOAT8 / "total_v"::FLOAT8;
jbe@0 1810 ELSE
jbe@0 1811 RETURN 0.5;
jbe@0 1812 END IF;
jbe@0 1813 END;
jbe@0 1814 $$;
jbe@0 1815
jbe@0 1816 COMMENT ON FUNCTION "vote_ratio"
jbe@0 1817 ( "initiative"."positive_votes"%TYPE,
jbe@0 1818 "initiative"."negative_votes"%TYPE )
jbe@0 1819 IS 'Ratio of positive votes to sum of positive and negative votes; 0.5, if there are neither positive nor negative votes';
jbe@0 1820
jbe@0 1821
jbe@0 1822
jbe@0 1823 ------------------------------------------------
jbe@0 1824 -- Locking for snapshots and voting procedure --
jbe@0 1825 ------------------------------------------------
jbe@0 1826
jbe@0 1827 CREATE FUNCTION "global_lock"() RETURNS VOID
jbe@0 1828 LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@0 1829 BEGIN
jbe@0 1830 -- NOTE: PostgreSQL allows reading, while tables are locked in
jbe@0 1831 -- exclusive move. Transactions should be kept short anyway!
jbe@0 1832 LOCK TABLE "member" IN EXCLUSIVE MODE;
jbe@4 1833 LOCK TABLE "area" IN EXCLUSIVE MODE;
jbe@4 1834 LOCK TABLE "membership" IN EXCLUSIVE MODE;
jbe@4 1835 -- NOTE: "member", "area" and "membership" are locked first to
jbe@4 1836 -- prevent deadlocks in combination with "calculate_member_counts"()
jbe@0 1837 LOCK TABLE "policy" IN EXCLUSIVE MODE;
jbe@0 1838 LOCK TABLE "issue" IN EXCLUSIVE MODE;
jbe@0 1839 LOCK TABLE "initiative" IN EXCLUSIVE MODE;
jbe@0 1840 LOCK TABLE "draft" IN EXCLUSIVE MODE;
jbe@0 1841 LOCK TABLE "suggestion" IN EXCLUSIVE MODE;
jbe@0 1842 LOCK TABLE "interest" IN EXCLUSIVE MODE;
jbe@0 1843 LOCK TABLE "initiator" IN EXCLUSIVE MODE;
jbe@0 1844 LOCK TABLE "supporter" IN EXCLUSIVE MODE;
jbe@0 1845 LOCK TABLE "opinion" IN EXCLUSIVE MODE;
jbe@0 1846 LOCK TABLE "delegation" IN EXCLUSIVE MODE;
jbe@0 1847 LOCK TABLE "direct_population_snapshot" IN EXCLUSIVE MODE;
jbe@0 1848 LOCK TABLE "delegating_population_snapshot" IN EXCLUSIVE MODE;
jbe@0 1849 LOCK TABLE "direct_interest_snapshot" IN EXCLUSIVE MODE;
jbe@0 1850 LOCK TABLE "delegating_interest_snapshot" IN EXCLUSIVE MODE;
jbe@0 1851 LOCK TABLE "direct_supporter_snapshot" IN EXCLUSIVE MODE;
jbe@0 1852 LOCK TABLE "direct_voter" IN EXCLUSIVE MODE;
jbe@0 1853 LOCK TABLE "delegating_voter" IN EXCLUSIVE MODE;
jbe@0 1854 LOCK TABLE "vote" IN EXCLUSIVE MODE;
jbe@0 1855 RETURN;
jbe@0 1856 END;
jbe@0 1857 $$;
jbe@0 1858
jbe@0 1859 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 1860
jbe@0 1861
jbe@0 1862
jbe@4 1863 -------------------------------
jbe@4 1864 -- Materialize member counts --
jbe@4 1865 -------------------------------
jbe@4 1866
jbe@4 1867 CREATE FUNCTION "calculate_member_counts"()
jbe@4 1868 RETURNS VOID
jbe@4 1869 LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@4 1870 BEGIN
jbe@4 1871 LOCK TABLE "member" IN EXCLUSIVE MODE;
jbe@4 1872 LOCK TABLE "area" IN EXCLUSIVE MODE;
jbe@4 1873 LOCK TABLE "membership" IN EXCLUSIVE MODE;
jbe@4 1874 DELETE FROM "member_count";
jbe@5 1875 INSERT INTO "member_count" ("total_count")
jbe@5 1876 SELECT "total_count" FROM "member_count_view";
jbe@5 1877 UPDATE "area" SET
jbe@5 1878 "direct_member_count" = "view"."direct_member_count",
jbe@5 1879 "member_weight" = "view"."member_weight",
jbe@5 1880 "autoreject_weight" = "view"."autoreject_weight"
jbe@5 1881 FROM "area_member_count" AS "view"
jbe@5 1882 WHERE "view"."area_id" = "area"."id";
jbe@4 1883 RETURN;
jbe@4 1884 END;
jbe@4 1885 $$;
jbe@4 1886
jbe@4 1887 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 1888
jbe@4 1889
jbe@4 1890
jbe@0 1891 ------------------------------
jbe@0 1892 -- Calculation of snapshots --
jbe@0 1893 ------------------------------
jbe@0 1894
jbe@0 1895 CREATE FUNCTION "weight_of_added_delegations_for_population_snapshot"
jbe@0 1896 ( "issue_id_p" "issue"."id"%TYPE,
jbe@0 1897 "member_id_p" "member"."id"%TYPE,
jbe@0 1898 "delegate_member_ids_p" "delegating_population_snapshot"."delegate_member_ids"%TYPE )
jbe@0 1899 RETURNS "direct_population_snapshot"."weight"%TYPE
jbe@0 1900 LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@0 1901 DECLARE
jbe@0 1902 "issue_delegation_row" "issue_delegation"%ROWTYPE;
jbe@0 1903 "delegate_member_ids_v" "delegating_population_snapshot"."delegate_member_ids"%TYPE;
jbe@0 1904 "weight_v" INT4;
jbe@8 1905 "sub_weight_v" INT4;
jbe@0 1906 BEGIN
jbe@0 1907 "weight_v" := 0;
jbe@0 1908 FOR "issue_delegation_row" IN
jbe@0 1909 SELECT * FROM "issue_delegation"
jbe@0 1910 WHERE "trustee_id" = "member_id_p"
jbe@0 1911 AND "issue_id" = "issue_id_p"
jbe@0 1912 LOOP
jbe@0 1913 IF NOT EXISTS (
jbe@0 1914 SELECT NULL FROM "direct_population_snapshot"
jbe@0 1915 WHERE "issue_id" = "issue_id_p"
jbe@0 1916 AND "event" = 'periodic'
jbe@0 1917 AND "member_id" = "issue_delegation_row"."truster_id"
jbe@0 1918 ) AND NOT EXISTS (
jbe@0 1919 SELECT NULL FROM "delegating_population_snapshot"
jbe@0 1920 WHERE "issue_id" = "issue_id_p"
jbe@0 1921 AND "event" = 'periodic'
jbe@0 1922 AND "member_id" = "issue_delegation_row"."truster_id"
jbe@0 1923 ) THEN
jbe@0 1924 "delegate_member_ids_v" :=
jbe@0 1925 "member_id_p" || "delegate_member_ids_p";
jbe@10 1926 INSERT INTO "delegating_population_snapshot" (
jbe@10 1927 "issue_id",
jbe@10 1928 "event",
jbe@10 1929 "member_id",
jbe@10 1930 "scope",
jbe@10 1931 "delegate_member_ids"
jbe@10 1932 ) VALUES (
jbe@0 1933 "issue_id_p",
jbe@0 1934 'periodic',
jbe@0 1935 "issue_delegation_row"."truster_id",
jbe@10 1936 "issue_delegation_row"."scope",
jbe@0 1937 "delegate_member_ids_v"
jbe@0 1938 );
jbe@8 1939 "sub_weight_v" := 1 +
jbe@0 1940 "weight_of_added_delegations_for_population_snapshot"(
jbe@0 1941 "issue_id_p",
jbe@0 1942 "issue_delegation_row"."truster_id",
jbe@0 1943 "delegate_member_ids_v"
jbe@0 1944 );
jbe@8 1945 UPDATE "delegating_population_snapshot"
jbe@8 1946 SET "weight" = "sub_weight_v"
jbe@8 1947 WHERE "issue_id" = "issue_id_p"
jbe@8 1948 AND "event" = 'periodic'
jbe@8 1949 AND "member_id" = "issue_delegation_row"."truster_id";
jbe@8 1950 "weight_v" := "weight_v" + "sub_weight_v";
jbe@0 1951 END IF;
jbe@0 1952 END LOOP;
jbe@0 1953 RETURN "weight_v";
jbe@0 1954 END;
jbe@0 1955 $$;
jbe@0 1956
jbe@0 1957 COMMENT ON FUNCTION "weight_of_added_delegations_for_population_snapshot"
jbe@0 1958 ( "issue"."id"%TYPE,
jbe@0 1959 "member"."id"%TYPE,
jbe@0 1960 "delegating_population_snapshot"."delegate_member_ids"%TYPE )
jbe@0 1961 IS 'Helper function for "create_population_snapshot" function';
jbe@0 1962
jbe@0 1963
jbe@0 1964 CREATE FUNCTION "create_population_snapshot"
jbe@0 1965 ( "issue_id_p" "issue"."id"%TYPE )
jbe@0 1966 RETURNS VOID
jbe@0 1967 LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@0 1968 DECLARE
jbe@0 1969 "member_id_v" "member"."id"%TYPE;
jbe@0 1970 BEGIN
jbe@0 1971 DELETE FROM "direct_population_snapshot"
jbe@0 1972 WHERE "issue_id" = "issue_id_p"
jbe@0 1973 AND "event" = 'periodic';
jbe@0 1974 DELETE FROM "delegating_population_snapshot"
jbe@0 1975 WHERE "issue_id" = "issue_id_p"
jbe@0 1976 AND "event" = 'periodic';
jbe@0 1977 INSERT INTO "direct_population_snapshot"
jbe@0 1978 ("issue_id", "event", "member_id", "interest_exists")
jbe@0 1979 SELECT DISTINCT ON ("issue_id", "member_id")
jbe@0 1980 "issue_id_p" AS "issue_id",
jbe@0 1981 'periodic' AS "event",
jbe@0 1982 "subquery"."member_id",
jbe@0 1983 "subquery"."interest_exists"
jbe@0 1984 FROM (
jbe@0 1985 SELECT
jbe@0 1986 "member"."id" AS "member_id",
jbe@0 1987 FALSE AS "interest_exists"
jbe@0 1988 FROM "issue"
jbe@0 1989 JOIN "area" ON "issue"."area_id" = "area"."id"
jbe@0 1990 JOIN "membership" ON "area"."id" = "membership"."area_id"
jbe@0 1991 JOIN "member" ON "membership"."member_id" = "member"."id"
jbe@0 1992 WHERE "issue"."id" = "issue_id_p"
jbe@0 1993 AND "member"."active"
jbe@0 1994 UNION
jbe@0 1995 SELECT
jbe@0 1996 "member"."id" AS "member_id",
jbe@0 1997 TRUE AS "interest_exists"
jbe@0 1998 FROM "interest" JOIN "member"
jbe@0 1999 ON "interest"."member_id" = "member"."id"
jbe@0 2000 WHERE "interest"."issue_id" = "issue_id_p"
jbe@0 2001 AND "member"."active"
jbe@0 2002 ) AS "subquery"
jbe@0 2003 ORDER BY
jbe@0 2004 "issue_id_p",
jbe@0 2005 "subquery"."member_id",
jbe@0 2006 "subquery"."interest_exists" DESC;
jbe@0 2007 FOR "member_id_v" IN
jbe@0 2008 SELECT "member_id" FROM "direct_population_snapshot"
jbe@0 2009 WHERE "issue_id" = "issue_id_p"
jbe@0 2010 AND "event" = 'periodic'
jbe@0 2011 LOOP
jbe@0 2012 UPDATE "direct_population_snapshot" SET
jbe@0 2013 "weight" = 1 +
jbe@0 2014 "weight_of_added_delegations_for_population_snapshot"(
jbe@0 2015 "issue_id_p",
jbe@0 2016 "member_id_v",
jbe@0 2017 '{}'
jbe@0 2018 )
jbe@0 2019 WHERE "issue_id" = "issue_id_p"
jbe@0 2020 AND "event" = 'periodic'
jbe@0 2021 AND "member_id" = "member_id_v";
jbe@0 2022 END LOOP;
jbe@0 2023 RETURN;
jbe@0 2024 END;
jbe@0 2025 $$;
jbe@0 2026
jbe@0 2027 COMMENT ON FUNCTION "create_population_snapshot"
jbe@0 2028 ( "issue_id_p" "issue"."id"%TYPE )
jbe@0 2029 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 2030
jbe@0 2031
jbe@0 2032 CREATE FUNCTION "weight_of_added_delegations_for_interest_snapshot"
jbe@0 2033 ( "issue_id_p" "issue"."id"%TYPE,
jbe@0 2034 "member_id_p" "member"."id"%TYPE,
jbe@0 2035 "delegate_member_ids_p" "delegating_interest_snapshot"."delegate_member_ids"%TYPE )
jbe@0 2036 RETURNS "direct_interest_snapshot"."weight"%TYPE
jbe@0 2037 LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@0 2038 DECLARE
jbe@0 2039 "issue_delegation_row" "issue_delegation"%ROWTYPE;
jbe@0 2040 "delegate_member_ids_v" "delegating_interest_snapshot"."delegate_member_ids"%TYPE;
jbe@0 2041 "weight_v" INT4;
jbe@8 2042 "sub_weight_v" INT4;
jbe@0 2043 BEGIN
jbe@0 2044 "weight_v" := 0;
jbe@0 2045 FOR "issue_delegation_row" IN
jbe@0 2046 SELECT * FROM "issue_delegation"
jbe@0 2047 WHERE "trustee_id" = "member_id_p"
jbe@0 2048 AND "issue_id" = "issue_id_p"
jbe@0 2049 LOOP
jbe@0 2050 IF NOT EXISTS (
jbe@0 2051 SELECT NULL FROM "direct_interest_snapshot"
jbe@0 2052 WHERE "issue_id" = "issue_id_p"
jbe@0 2053 AND "event" = 'periodic'
jbe@0 2054 AND "member_id" = "issue_delegation_row"."truster_id"
jbe@0 2055 ) AND NOT EXISTS (
jbe@0 2056 SELECT NULL FROM "delegating_interest_snapshot"
jbe@0 2057 WHERE "issue_id" = "issue_id_p"
jbe@0 2058 AND "event" = 'periodic'
jbe@0 2059 AND "member_id" = "issue_delegation_row"."truster_id"
jbe@0 2060 ) THEN
jbe@0 2061 "delegate_member_ids_v" :=
jbe@0 2062 "member_id_p" || "delegate_member_ids_p";
jbe@10 2063 INSERT INTO "delegating_interest_snapshot" (
jbe@10 2064 "issue_id",
jbe@10 2065 "event",
jbe@10 2066 "member_id",
jbe@10 2067 "scope",
jbe@10 2068 "delegate_member_ids"
jbe@10 2069 ) VALUES (
jbe@0 2070 "issue_id_p",
jbe@0 2071 'periodic',
jbe@0 2072 "issue_delegation_row"."truster_id",
jbe@10 2073 "issue_delegation_row"."scope",
jbe@0 2074 "delegate_member_ids_v"
jbe@0 2075 );
jbe@8 2076 "sub_weight_v" := 1 +
jbe@0 2077 "weight_of_added_delegations_for_interest_snapshot"(
jbe@0 2078 "issue_id_p",
jbe@0 2079 "issue_delegation_row"."truster_id",
jbe@0 2080 "delegate_member_ids_v"
jbe@0 2081 );
jbe@8 2082 UPDATE "delegating_interest_snapshot"
jbe@8 2083 SET "weight" = "sub_weight_v"
jbe@8 2084 WHERE "issue_id" = "issue_id_p"
jbe@8 2085 AND "event" = 'periodic'
jbe@8 2086 AND "member_id" = "issue_delegation_row"."truster_id";
jbe@8 2087 "weight_v" := "weight_v" + "sub_weight_v";
jbe@0 2088 END IF;
jbe@0 2089 END LOOP;
jbe@0 2090 RETURN "weight_v";
jbe@0 2091 END;
jbe@0 2092 $$;
jbe@0 2093
jbe@0 2094 COMMENT ON FUNCTION "weight_of_added_delegations_for_interest_snapshot"
jbe@0 2095 ( "issue"."id"%TYPE,
jbe@0 2096 "member"."id"%TYPE,
jbe@0 2097 "delegating_interest_snapshot"."delegate_member_ids"%TYPE )
jbe@0 2098 IS 'Helper function for "create_interest_snapshot" function';
jbe@0 2099
jbe@0 2100
jbe@0 2101 CREATE FUNCTION "create_interest_snapshot"
jbe@0 2102 ( "issue_id_p" "issue"."id"%TYPE )
jbe@0 2103 RETURNS VOID
jbe@0 2104 LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@0 2105 DECLARE
jbe@0 2106 "member_id_v" "member"."id"%TYPE;
jbe@0 2107 BEGIN
jbe@0 2108 DELETE FROM "direct_interest_snapshot"
jbe@0 2109 WHERE "issue_id" = "issue_id_p"
jbe@0 2110 AND "event" = 'periodic';
jbe@0 2111 DELETE FROM "delegating_interest_snapshot"
jbe@0 2112 WHERE "issue_id" = "issue_id_p"
jbe@0 2113 AND "event" = 'periodic';
jbe@0 2114 DELETE FROM "direct_supporter_snapshot"
jbe@0 2115 WHERE "issue_id" = "issue_id_p"
jbe@0 2116 AND "event" = 'periodic';
jbe@0 2117 INSERT INTO "direct_interest_snapshot"
jbe@0 2118 ("issue_id", "event", "member_id", "voting_requested")
jbe@0 2119 SELECT
jbe@0 2120 "issue_id_p" AS "issue_id",
jbe@0 2121 'periodic' AS "event",
jbe@0 2122 "member"."id" AS "member_id",
jbe@0 2123 "interest"."voting_requested"
jbe@0 2124 FROM "interest" JOIN "member"
jbe@0 2125 ON "interest"."member_id" = "member"."id"
jbe@0 2126 WHERE "interest"."issue_id" = "issue_id_p"
jbe@0 2127 AND "member"."active";
jbe@0 2128 FOR "member_id_v" IN
jbe@0 2129 SELECT "member_id" FROM "direct_interest_snapshot"
jbe@0 2130 WHERE "issue_id" = "issue_id_p"
jbe@0 2131 AND "event" = 'periodic'
jbe@0 2132 LOOP
jbe@0 2133 UPDATE "direct_interest_snapshot" SET
jbe@0 2134 "weight" = 1 +
jbe@0 2135 "weight_of_added_delegations_for_interest_snapshot"(
jbe@0 2136 "issue_id_p",
jbe@0 2137 "member_id_v",
jbe@0 2138 '{}'
jbe@0 2139 )
jbe@0 2140 WHERE "issue_id" = "issue_id_p"
jbe@0 2141 AND "event" = 'periodic'
jbe@0 2142 AND "member_id" = "member_id_v";
jbe@0 2143 END LOOP;
jbe@0 2144 INSERT INTO "direct_supporter_snapshot"
jbe@0 2145 ( "issue_id", "initiative_id", "event", "member_id",
jbe@0 2146 "informed", "satisfied" )
jbe@0 2147 SELECT
jbe@0 2148 "issue_id_p" AS "issue_id",
jbe@0 2149 "initiative"."id" AS "initiative_id",
jbe@0 2150 'periodic' AS "event",
jbe@0 2151 "member"."id" AS "member_id",
jbe@0 2152 "supporter"."draft_id" = "current_draft"."id" AS "informed",
jbe@0 2153 NOT EXISTS (
jbe@0 2154 SELECT NULL FROM "critical_opinion"
jbe@0 2155 WHERE "initiative_id" = "initiative"."id"
jbe@0 2156 AND "member_id" = "member"."id"
jbe@0 2157 ) AS "satisfied"
jbe@0 2158 FROM "supporter"
jbe@0 2159 JOIN "member"
jbe@0 2160 ON "supporter"."member_id" = "member"."id"
jbe@0 2161 JOIN "initiative"
jbe@0 2162 ON "supporter"."initiative_id" = "initiative"."id"
jbe@0 2163 JOIN "current_draft"
jbe@0 2164 ON "initiative"."id" = "current_draft"."initiative_id"
jbe@0 2165 JOIN "direct_interest_snapshot"
jbe@0 2166 ON "member"."id" = "direct_interest_snapshot"."member_id"
jbe@0 2167 AND "initiative"."issue_id" = "direct_interest_snapshot"."issue_id"
jbe@3 2168 AND "event" = 'periodic'
jbe@0 2169 WHERE "member"."active"
jbe@0 2170 AND "initiative"."issue_id" = "issue_id_p";
jbe@0 2171 RETURN;
jbe@0 2172 END;
jbe@0 2173 $$;
jbe@0 2174
jbe@0 2175 COMMENT ON FUNCTION "create_interest_snapshot"
jbe@0 2176 ( "issue"."id"%TYPE )
jbe@0 2177 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 2178
jbe@0 2179
jbe@0 2180 CREATE FUNCTION "create_snapshot"
jbe@0 2181 ( "issue_id_p" "issue"."id"%TYPE )
jbe@0 2182 RETURNS VOID
jbe@0 2183 LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@0 2184 DECLARE
jbe@0 2185 "initiative_id_v" "initiative"."id"%TYPE;
jbe@0 2186 "suggestion_id_v" "suggestion"."id"%TYPE;
jbe@0 2187 BEGIN
jbe@0 2188 PERFORM "global_lock"();
jbe@0 2189 PERFORM "create_population_snapshot"("issue_id_p");
jbe@0 2190 PERFORM "create_interest_snapshot"("issue_id_p");
jbe@0 2191 UPDATE "issue" SET
jbe@8 2192 "snapshot" = now(),
jbe@8 2193 "latest_snapshot_event" = 'periodic',
jbe@0 2194 "population" = (
jbe@0 2195 SELECT coalesce(sum("weight"), 0)
jbe@0 2196 FROM "direct_population_snapshot"
jbe@0 2197 WHERE "issue_id" = "issue_id_p"
jbe@0 2198 AND "event" = 'periodic'
jbe@0 2199 ),
jbe@8 2200 "vote_now" = (
jbe@0 2201 SELECT coalesce(sum("weight"), 0)
jbe@0 2202 FROM "direct_interest_snapshot"
jbe@0 2203 WHERE "issue_id" = "issue_id_p"
jbe@0 2204 AND "event" = 'periodic'
jbe@0 2205 AND "voting_requested" = TRUE
jbe@0 2206 ),
jbe@0 2207 "vote_later" = (
jbe@0 2208 SELECT coalesce(sum("weight"), 0)
jbe@0 2209 FROM "direct_interest_snapshot"
jbe@0 2210 WHERE "issue_id" = "issue_id_p"
jbe@0 2211 AND "event" = 'periodic'
jbe@0 2212 AND "voting_requested" = FALSE
jbe@0 2213 )
jbe@0 2214 WHERE "id" = "issue_id_p";
jbe@0 2215 FOR "initiative_id_v" IN
jbe@0 2216 SELECT "id" FROM "initiative" WHERE "issue_id" = "issue_id_p"
jbe@0 2217 LOOP
jbe@0 2218 UPDATE "initiative" SET
jbe@0 2219 "supporter_count" = (
jbe@0 2220 SELECT coalesce(sum("di"."weight"), 0)
jbe@0 2221 FROM "direct_interest_snapshot" AS "di"
jbe@0 2222 JOIN "direct_supporter_snapshot" AS "ds"
jbe@0 2223 ON "di"."member_id" = "ds"."member_id"
jbe@0 2224 WHERE "di"."issue_id" = "issue_id_p"
jbe@0 2225 AND "di"."event" = 'periodic'
jbe@0 2226 AND "ds"."initiative_id" = "initiative_id_v"
jbe@0 2227 AND "ds"."event" = 'periodic'
jbe@0 2228 ),
jbe@0 2229 "informed_supporter_count" = (
jbe@0 2230 SELECT coalesce(sum("di"."weight"), 0)
jbe@0 2231 FROM "direct_interest_snapshot" AS "di"
jbe@0 2232 JOIN "direct_supporter_snapshot" AS "ds"
jbe@0 2233 ON "di"."member_id" = "ds"."member_id"
jbe@0 2234 WHERE "di"."issue_id" = "issue_id_p"
jbe@0 2235 AND "di"."event" = 'periodic'
jbe@0 2236 AND "ds"."initiative_id" = "initiative_id_v"
jbe@0 2237 AND "ds"."event" = 'periodic'
jbe@0 2238 AND "ds"."informed"
jbe@0 2239 ),
jbe@0 2240 "satisfied_supporter_count" = (
jbe@0 2241 SELECT coalesce(sum("di"."weight"), 0)
jbe@0 2242 FROM "direct_interest_snapshot" AS "di"
jbe@0 2243 JOIN "direct_supporter_snapshot" AS "ds"
jbe@0 2244 ON "di"."member_id" = "ds"."member_id"
jbe@0 2245 WHERE "di"."issue_id" = "issue_id_p"
jbe@0 2246 AND "di"."event" = 'periodic'
jbe@0 2247 AND "ds"."initiative_id" = "initiative_id_v"
jbe@0 2248 AND "ds"."event" = 'periodic'
jbe@0 2249 AND "ds"."satisfied"
jbe@0 2250 ),
jbe@0 2251 "satisfied_informed_supporter_count" = (
jbe@0 2252 SELECT coalesce(sum("di"."weight"), 0)
jbe@0 2253 FROM "direct_interest_snapshot" AS "di"
jbe@0 2254 JOIN "direct_supporter_snapshot" AS "ds"
jbe@0 2255 ON "di"."member_id" = "ds"."member_id"
jbe@0 2256 WHERE "di"."issue_id" = "issue_id_p"
jbe@0 2257 AND "di"."event" = 'periodic'
jbe@0 2258 AND "ds"."initiative_id" = "initiative_id_v"
jbe@0 2259 AND "ds"."event" = 'periodic'
jbe@0 2260 AND "ds"."informed"
jbe@0 2261 AND "ds"."satisfied"
jbe@0 2262 )
jbe@0 2263 WHERE "id" = "initiative_id_v";
jbe@0 2264 FOR "suggestion_id_v" IN
jbe@0 2265 SELECT "id" FROM "suggestion"
jbe@0 2266 WHERE "initiative_id" = "initiative_id_v"
jbe@0 2267 LOOP
jbe@0 2268 UPDATE "suggestion" SET
jbe@0 2269 "minus2_unfulfilled_count" = (
jbe@0 2270 SELECT coalesce(sum("snapshot"."weight"), 0)
jbe@0 2271 FROM "opinion" JOIN "direct_interest_snapshot" AS "snapshot"
jbe@0 2272 ON "opinion"."member_id" = "snapshot"."member_id"
jbe@1 2273 WHERE "opinion"."suggestion_id" = "suggestion_id_v"
jbe@0 2274 AND "snapshot"."issue_id" = "issue_id_p"
jbe@0 2275 AND "opinion"."degree" = -2
jbe@0 2276 AND "opinion"."fulfilled" = FALSE
jbe@0 2277 ),
jbe@0 2278 "minus2_fulfilled_count" = (
jbe@0 2279 SELECT coalesce(sum("snapshot"."weight"), 0)
jbe@0 2280 FROM "opinion" JOIN "direct_interest_snapshot" AS "snapshot"
jbe@0 2281 ON "opinion"."member_id" = "snapshot"."member_id"
jbe@1 2282 WHERE "opinion"."suggestion_id" = "suggestion_id_v"
jbe@0 2283 AND "snapshot"."issue_id" = "issue_id_p"
jbe@0 2284 AND "opinion"."degree" = -2
jbe@0 2285 AND "opinion"."fulfilled" = TRUE
jbe@0 2286 ),
jbe@0 2287 "minus1_unfulfilled_count" = (
jbe@0 2288 SELECT coalesce(sum("snapshot"."weight"), 0)
jbe@0 2289 FROM "opinion" JOIN "direct_interest_snapshot" AS "snapshot"
jbe@0 2290 ON "opinion"."member_id" = "snapshot"."member_id"
jbe@1 2291 WHERE "opinion"."suggestion_id" = "suggestion_id_v"
jbe@0 2292 AND "snapshot"."issue_id" = "issue_id_p"
jbe@0 2293 AND "opinion"."degree" = -1
jbe@0 2294 AND "opinion"."fulfilled" = FALSE
jbe@0 2295 ),
jbe@0 2296 "minus1_fulfilled_count" = (
jbe@0 2297 SELECT coalesce(sum("snapshot"."weight"), 0)
jbe@0 2298 FROM "opinion" JOIN "direct_interest_snapshot" AS "snapshot"
jbe@0 2299 ON "opinion"."member_id" = "snapshot"."member_id"
jbe@1 2300 WHERE "opinion"."suggestion_id" = "suggestion_id_v"
jbe@0 2301 AND "snapshot"."issue_id" = "issue_id_p"
jbe@0 2302 AND "opinion"."degree" = -1
jbe@0 2303 AND "opinion"."fulfilled" = TRUE
jbe@0 2304 ),
jbe@0 2305 "plus1_unfulfilled_count" = (
jbe@0 2306 SELECT coalesce(sum("snapshot"."weight"), 0)
jbe@0 2307 FROM "opinion" JOIN "direct_interest_snapshot" AS "snapshot"
jbe@0 2308 ON "opinion"."member_id" = "snapshot"."member_id"
jbe@1 2309 WHERE "opinion"."suggestion_id" = "suggestion_id_v"
jbe@0 2310 AND "snapshot"."issue_id" = "issue_id_p"
jbe@0 2311 AND "opinion"."degree" = 1
jbe@0 2312 AND "opinion"."fulfilled" = FALSE
jbe@0 2313 ),
jbe@0 2314 "plus1_fulfilled_count" = (
jbe@0 2315 SELECT coalesce(sum("snapshot"."weight"), 0)
jbe@0 2316 FROM "opinion" JOIN "direct_interest_snapshot" AS "snapshot"
jbe@0 2317 ON "opinion"."member_id" = "snapshot"."member_id"
jbe@1 2318 WHERE "opinion"."suggestion_id" = "suggestion_id_v"
jbe@0 2319 AND "snapshot"."issue_id" = "issue_id_p"
jbe@0 2320 AND "opinion"."degree" = 1
jbe@0 2321 AND "opinion"."fulfilled" = TRUE
jbe@0 2322 ),
jbe@0 2323 "plus2_unfulfilled_count" = (
jbe@0 2324 SELECT coalesce(sum("snapshot"."weight"), 0)
jbe@0 2325 FROM "opinion" JOIN "direct_interest_snapshot" AS "snapshot"
jbe@0 2326 ON "opinion"."member_id" = "snapshot"."member_id"
jbe@1 2327 WHERE "opinion"."suggestion_id" = "suggestion_id_v"
jbe@0 2328 AND "snapshot"."issue_id" = "issue_id_p"
jbe@0 2329 AND "opinion"."degree" = 2
jbe@0 2330 AND "opinion"."fulfilled" = FALSE
jbe@0 2331 ),
jbe@0 2332 "plus2_fulfilled_count" = (
jbe@0 2333 SELECT coalesce(sum("snapshot"."weight"), 0)
jbe@0 2334 FROM "opinion" JOIN "direct_interest_snapshot" AS "snapshot"
jbe@0 2335 ON "opinion"."member_id" = "snapshot"."member_id"
jbe@1 2336 WHERE "opinion"."suggestion_id" = "suggestion_id_v"
jbe@0 2337 AND "snapshot"."issue_id" = "issue_id_p"
jbe@0 2338 AND "opinion"."degree" = 2
jbe@0 2339 AND "opinion"."fulfilled" = TRUE
jbe@0 2340 )
jbe@0 2341 WHERE "suggestion"."id" = "suggestion_id_v";
jbe@0 2342 END LOOP;
jbe@0 2343 END LOOP;
jbe@0 2344 RETURN;
jbe@0 2345 END;
jbe@0 2346 $$;
jbe@0 2347
jbe@0 2348 COMMENT ON FUNCTION "create_snapshot"
jbe@0 2349 ( "issue"."id"%TYPE )
jbe@0 2350 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 2351
jbe@0 2352
jbe@0 2353 CREATE FUNCTION "set_snapshot_event"
jbe@0 2354 ( "issue_id_p" "issue"."id"%TYPE,
jbe@0 2355 "event_p" "snapshot_event" )
jbe@0 2356 RETURNS VOID
jbe@0 2357 LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@21 2358 DECLARE
jbe@21 2359 "event_v" "issue"."latest_snapshot_event"%TYPE;
jbe@0 2360 BEGIN
jbe@21 2361 SELECT "latest_snapshot_event" INTO "event_v" FROM "issue"
jbe@21 2362 WHERE "id" = "issue_id_p" FOR UPDATE;
jbe@8 2363 UPDATE "issue" SET "latest_snapshot_event" = "event_p"
jbe@8 2364 WHERE "id" = "issue_id_p";
jbe@3 2365 UPDATE "direct_population_snapshot" SET "event" = "event_p"
jbe@21 2366 WHERE "issue_id" = "issue_id_p" AND "event" = "event_v";
jbe@3 2367 UPDATE "delegating_population_snapshot" SET "event" = "event_p"
jbe@21 2368 WHERE "issue_id" = "issue_id_p" AND "event" = "event_v";
jbe@3 2369 UPDATE "direct_interest_snapshot" SET "event" = "event_p"
jbe@21 2370 WHERE "issue_id" = "issue_id_p" AND "event" = "event_v";
jbe@3 2371 UPDATE "delegating_interest_snapshot" SET "event" = "event_p"
jbe@21 2372 WHERE "issue_id" = "issue_id_p" AND "event" = "event_v";
jbe@3 2373 UPDATE "direct_supporter_snapshot" SET "event" = "event_p"
jbe@21 2374 WHERE "issue_id" = "issue_id_p" AND "event" = "event_v";
jbe@0 2375 RETURN;
jbe@0 2376 END;
jbe@0 2377 $$;
jbe@0 2378
jbe@0 2379 COMMENT ON FUNCTION "set_snapshot_event"
jbe@0 2380 ( "issue"."id"%TYPE,
jbe@0 2381 "snapshot_event" )
jbe@0 2382 IS 'Change "event" attribute of the previous ''periodic'' snapshot';
jbe@0 2383
jbe@0 2384
jbe@0 2385
jbe@0 2386 ---------------------
jbe@0 2387 -- Freezing issues --
jbe@0 2388 ---------------------
jbe@0 2389
jbe@0 2390 CREATE FUNCTION "freeze_after_snapshot"
jbe@0 2391 ( "issue_id_p" "issue"."id"%TYPE )
jbe@0 2392 RETURNS VOID
jbe@0 2393 LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@0 2394 DECLARE
jbe@0 2395 "issue_row" "issue"%ROWTYPE;
jbe@0 2396 "policy_row" "policy"%ROWTYPE;
jbe@0 2397 "initiative_row" "initiative"%ROWTYPE;
jbe@0 2398 BEGIN
jbe@0 2399 SELECT * INTO "issue_row" FROM "issue" WHERE "id" = "issue_id_p";
jbe@0 2400 SELECT * INTO "policy_row"
jbe@0 2401 FROM "policy" WHERE "id" = "issue_row"."policy_id";
jbe@21 2402 PERFORM "set_snapshot_event"("issue_id_p", 'full_freeze');
jbe@3 2403 UPDATE "issue" SET
jbe@4 2404 "accepted" = coalesce("accepted", now()),
jbe@4 2405 "half_frozen" = coalesce("half_frozen", now()),
jbe@3 2406 "fully_frozen" = now()
jbe@3 2407 WHERE "id" = "issue_id_p";
jbe@0 2408 FOR "initiative_row" IN
jbe@15 2409 SELECT * FROM "initiative"
jbe@15 2410 WHERE "issue_id" = "issue_id_p" AND "revoked" ISNULL
jbe@0 2411 LOOP
jbe@0 2412 IF
jbe@0 2413 "initiative_row"."satisfied_supporter_count" > 0 AND
jbe@0 2414 "initiative_row"."satisfied_supporter_count" *
jbe@0 2415 "policy_row"."initiative_quorum_den" >=
jbe@0 2416 "issue_row"."population" * "policy_row"."initiative_quorum_num"
jbe@0 2417 THEN
jbe@0 2418 UPDATE "initiative" SET "admitted" = TRUE
jbe@0 2419 WHERE "id" = "initiative_row"."id";
jbe@0 2420 ELSE
jbe@0 2421 UPDATE "initiative" SET "admitted" = FALSE
jbe@0 2422 WHERE "id" = "initiative_row"."id";
jbe@0 2423 END IF;
jbe@0 2424 END LOOP;
jbe@9 2425 IF NOT EXISTS (
jbe@9 2426 SELECT NULL FROM "initiative"
jbe@9 2427 WHERE "issue_id" = "issue_id_p" AND "admitted" = TRUE
jbe@9 2428 ) THEN
jbe@9 2429 PERFORM "close_voting"("issue_id_p");
jbe@9 2430 END IF;
jbe@0 2431 RETURN;
jbe@0 2432 END;
jbe@0 2433 $$;
jbe@0 2434
jbe@0 2435 COMMENT ON FUNCTION "freeze_after_snapshot"
jbe@0 2436 ( "issue"."id"%TYPE )
jbe@9 2437 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 2438
jbe@0 2439
jbe@0 2440 CREATE FUNCTION "manual_freeze"("issue_id_p" "issue"."id"%TYPE)
jbe@0 2441 RETURNS VOID
jbe@0 2442 LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@0 2443 DECLARE
jbe@0 2444 "issue_row" "issue"%ROWTYPE;
jbe@0 2445 BEGIN
jbe@0 2446 PERFORM "create_snapshot"("issue_id_p");
jbe@0 2447 PERFORM "freeze_after_snapshot"("issue_id_p");
jbe@0 2448 RETURN;
jbe@0 2449 END;
jbe@0 2450 $$;
jbe@0 2451
jbe@0 2452 COMMENT ON FUNCTION "freeze_after_snapshot"
jbe@0 2453 ( "issue"."id"%TYPE )
jbe@3 2454 IS 'Freeze an issue manually (fully) and start voting';
jbe@0 2455
jbe@0 2456
jbe@0 2457
jbe@0 2458 -----------------------
jbe@0 2459 -- Counting of votes --
jbe@0 2460 -----------------------
jbe@0 2461
jbe@0 2462
jbe@5 2463 CREATE FUNCTION "weight_of_added_vote_delegations"
jbe@0 2464 ( "issue_id_p" "issue"."id"%TYPE,
jbe@0 2465 "member_id_p" "member"."id"%TYPE,
jbe@0 2466 "delegate_member_ids_p" "delegating_voter"."delegate_member_ids"%TYPE )
jbe@0 2467 RETURNS "direct_voter"."weight"%TYPE
jbe@0 2468 LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@0 2469 DECLARE
jbe@0 2470 "issue_delegation_row" "issue_delegation"%ROWTYPE;
jbe@0 2471 "delegate_member_ids_v" "delegating_voter"."delegate_member_ids"%TYPE;
jbe@0 2472 "weight_v" INT4;
jbe@8 2473 "sub_weight_v" INT4;
jbe@0 2474 BEGIN
jbe@0 2475 "weight_v" := 0;
jbe@0 2476 FOR "issue_delegation_row" IN
jbe@0 2477 SELECT * FROM "issue_delegation"
jbe@0 2478 WHERE "trustee_id" = "member_id_p"
jbe@0 2479 AND "issue_id" = "issue_id_p"
jbe@0 2480 LOOP
jbe@0 2481 IF NOT EXISTS (
jbe@0 2482 SELECT NULL FROM "direct_voter"
jbe@0 2483 WHERE "member_id" = "issue_delegation_row"."truster_id"
jbe@0 2484 AND "issue_id" = "issue_id_p"
jbe@0 2485 ) AND NOT EXISTS (
jbe@0 2486 SELECT NULL FROM "delegating_voter"
jbe@0 2487 WHERE "member_id" = "issue_delegation_row"."truster_id"
jbe@0 2488 AND "issue_id" = "issue_id_p"
jbe@0 2489 ) THEN
jbe@0 2490 "delegate_member_ids_v" :=
jbe@0 2491 "member_id_p" || "delegate_member_ids_p";
jbe@10 2492 INSERT INTO "delegating_voter" (
jbe@10 2493 "issue_id",
jbe@10 2494 "member_id",
jbe@10 2495 "scope",
jbe@10 2496 "delegate_member_ids"
jbe@10 2497 ) VALUES (
jbe@5 2498 "issue_id_p",
jbe@5 2499 "issue_delegation_row"."truster_id",
jbe@10 2500 "issue_delegation_row"."scope",
jbe@5 2501 "delegate_member_ids_v"
jbe@5 2502 );
jbe@8 2503 "sub_weight_v" := 1 +
jbe@8 2504 "weight_of_added_vote_delegations"(
jbe@8 2505 "issue_id_p",
jbe@8 2506 "issue_delegation_row"."truster_id",
jbe@8 2507 "delegate_member_ids_v"
jbe@8 2508 );
jbe@8 2509 UPDATE "delegating_voter"
jbe@8 2510 SET "weight" = "sub_weight_v"
jbe@8 2511 WHERE "issue_id" = "issue_id_p"
jbe@8 2512 AND "member_id" = "issue_delegation_row"."truster_id";
jbe@8 2513 "weight_v" := "weight_v" + "sub_weight_v";
jbe@0 2514 END IF;
jbe@0 2515 END LOOP;
jbe@0 2516 RETURN "weight_v";
jbe@0 2517 END;
jbe@0 2518 $$;
jbe@0 2519
jbe@5 2520 COMMENT ON FUNCTION "weight_of_added_vote_delegations"
jbe@0 2521 ( "issue"."id"%TYPE,
jbe@0 2522 "member"."id"%TYPE,
jbe@0 2523 "delegating_voter"."delegate_member_ids"%TYPE )
jbe@0 2524 IS 'Helper function for "add_vote_delegations" function';
jbe@0 2525
jbe@0 2526
jbe@0 2527 CREATE FUNCTION "add_vote_delegations"
jbe@0 2528 ( "issue_id_p" "issue"."id"%TYPE )
jbe@0 2529 RETURNS VOID
jbe@0 2530 LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@0 2531 DECLARE
jbe@0 2532 "member_id_v" "member"."id"%TYPE;
jbe@0 2533 BEGIN
jbe@0 2534 FOR "member_id_v" IN
jbe@0 2535 SELECT "member_id" FROM "direct_voter"
jbe@0 2536 WHERE "issue_id" = "issue_id_p"
jbe@0 2537 LOOP
jbe@0 2538 UPDATE "direct_voter" SET
jbe@5 2539 "weight" = "weight" + "weight_of_added_vote_delegations"(
jbe@0 2540 "issue_id_p",
jbe@0 2541 "member_id_v",
jbe@0 2542 '{}'
jbe@0 2543 )
jbe@0 2544 WHERE "member_id" = "member_id_v"
jbe@0 2545 AND "issue_id" = "issue_id_p";
jbe@0 2546 END LOOP;
jbe@0 2547 RETURN;
jbe@0 2548 END;
jbe@0 2549 $$;
jbe@0 2550
jbe@0 2551 COMMENT ON FUNCTION "add_vote_delegations"
jbe@0 2552 ( "issue_id_p" "issue"."id"%TYPE )
jbe@0 2553 IS 'Helper function for "close_voting" function';
jbe@0 2554
jbe@0 2555
jbe@0 2556 CREATE FUNCTION "close_voting"("issue_id_p" "issue"."id"%TYPE)
jbe@0 2557 RETURNS VOID
jbe@0 2558 LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@0 2559 DECLARE
jbe@0 2560 "issue_row" "issue"%ROWTYPE;
jbe@0 2561 "member_id_v" "member"."id"%TYPE;
jbe@0 2562 BEGIN
jbe@0 2563 PERFORM "global_lock"();
jbe@0 2564 SELECT * INTO "issue_row" FROM "issue" WHERE "id" = "issue_id_p";
jbe@0 2565 DELETE FROM "delegating_voter"
jbe@0 2566 WHERE "issue_id" = "issue_id_p";
jbe@0 2567 DELETE FROM "direct_voter"
jbe@0 2568 WHERE "issue_id" = "issue_id_p"
jbe@0 2569 AND "autoreject" = TRUE;
jbe@0 2570 DELETE FROM "direct_voter" USING "member"
jbe@0 2571 WHERE "direct_voter"."member_id" = "member"."id"
jbe@0 2572 AND "direct_voter"."issue_id" = "issue_id_p"
jbe@0 2573 AND "member"."active" = FALSE;
jbe@0 2574 UPDATE "direct_voter" SET "weight" = 1
jbe@0 2575 WHERE "issue_id" = "issue_id_p";
jbe@0 2576 PERFORM "add_vote_delegations"("issue_id_p");
jbe@0 2577 FOR "member_id_v" IN
jbe@0 2578 SELECT "interest"."member_id"
jbe@0 2579 FROM "interest"
jbe@0 2580 LEFT JOIN "direct_voter"
jbe@0 2581 ON "interest"."member_id" = "direct_voter"."member_id"
jbe@0 2582 AND "interest"."issue_id" = "direct_voter"."issue_id"
jbe@0 2583 LEFT JOIN "delegating_voter"
jbe@0 2584 ON "interest"."member_id" = "delegating_voter"."member_id"
jbe@0 2585 AND "interest"."issue_id" = "delegating_voter"."issue_id"
jbe@0 2586 WHERE "interest"."issue_id" = "issue_id_p"
jbe@0 2587 AND "interest"."autoreject" = TRUE
jbe@0 2588 AND "direct_voter"."member_id" ISNULL
jbe@0 2589 AND "delegating_voter"."member_id" ISNULL
jbe@0 2590 UNION SELECT "membership"."member_id"
jbe@0 2591 FROM "membership"
jbe@0 2592 LEFT JOIN "interest"
jbe@0 2593 ON "membership"."member_id" = "interest"."member_id"
jbe@0 2594 AND "interest"."issue_id" = "issue_id_p"
jbe@0 2595 LEFT JOIN "direct_voter"
jbe@0 2596 ON "membership"."member_id" = "direct_voter"."member_id"
jbe@0 2597 AND "direct_voter"."issue_id" = "issue_id_p"
jbe@0 2598 LEFT JOIN "delegating_voter"
jbe@0 2599 ON "membership"."member_id" = "delegating_voter"."member_id"
jbe@0 2600 AND "delegating_voter"."issue_id" = "issue_id_p"
jbe@0 2601 WHERE "membership"."area_id" = "issue_row"."area_id"
jbe@0 2602 AND "membership"."autoreject" = TRUE
jbe@0 2603 AND "interest"."autoreject" ISNULL
jbe@0 2604 AND "direct_voter"."member_id" ISNULL
jbe@0 2605 AND "delegating_voter"."member_id" ISNULL
jbe@0 2606 LOOP
jbe@11 2607 INSERT INTO "direct_voter"
jbe@11 2608 ("member_id", "issue_id", "weight", "autoreject") VALUES
jbe@11 2609 ("member_id_v", "issue_id_p", 1, TRUE);
jbe@0 2610 INSERT INTO "vote" (
jbe@0 2611 "member_id",
jbe@0 2612 "issue_id",
jbe@0 2613 "initiative_id",
jbe@0 2614 "grade"
jbe@0 2615 ) SELECT
jbe@0 2616 "member_id_v" AS "member_id",
jbe@0 2617 "issue_id_p" AS "issue_id",
jbe@0 2618 "id" AS "initiative_id",
jbe@0 2619 -1 AS "grade"
jbe@0 2620 FROM "initiative" WHERE "issue_id" = "issue_id_p";
jbe@0 2621 END LOOP;
jbe@0 2622 PERFORM "add_vote_delegations"("issue_id_p");
jbe@4 2623 UPDATE "issue" SET
jbe@4 2624 "voter_count" = (
jbe@4 2625 SELECT coalesce(sum("weight"), 0)
jbe@4 2626 FROM "direct_voter" WHERE "issue_id" = "issue_id_p"
jbe@6 2627 )
jbe@6 2628 WHERE "id" = "issue_id_p";
jbe@0 2629 UPDATE "initiative" SET
jbe@10 2630 "positive_votes" = "vote_counts"."positive_votes",
jbe@10 2631 "negative_votes" = "vote_counts"."negative_votes",
jbe@10 2632 "agreed" = CASE WHEN "majority_strict" THEN
jbe@10 2633 "vote_counts"."positive_votes" * "majority_den" >
jbe@10 2634 "majority_num" *
jbe@10 2635 ("vote_counts"."positive_votes"+"vote_counts"."negative_votes")
jbe@10 2636 ELSE
jbe@10 2637 "vote_counts"."positive_votes" * "majority_den" >=
jbe@10 2638 "majority_num" *
jbe@10 2639 ("vote_counts"."positive_votes"+"vote_counts"."negative_votes")
jbe@10 2640 END
jbe@10 2641 FROM
jbe@10 2642 ( SELECT
jbe@10 2643 "initiative"."id" AS "initiative_id",
jbe@10 2644 coalesce(
jbe@10 2645 sum(
jbe@10 2646 CASE WHEN "grade" > 0 THEN "direct_voter"."weight" ELSE 0 END
jbe@10 2647 ),
jbe@10 2648 0
jbe@10 2649 ) AS "positive_votes",
jbe@10 2650 coalesce(
jbe@10 2651 sum(
jbe@10 2652 CASE WHEN "grade" < 0 THEN "direct_voter"."weight" ELSE 0 END
jbe@10 2653 ),
jbe@10 2654 0
jbe@10 2655 ) AS "negative_votes"
jbe@10 2656 FROM "initiative"
jbe@10 2657 JOIN "issue" ON "initiative"."issue_id" = "issue"."id"
jbe@10 2658 JOIN "policy" ON "issue"."policy_id" = "policy"."id"
jbe@10 2659 LEFT JOIN "direct_voter"
jbe@10 2660 ON "direct_voter"."issue_id" = "initiative"."issue_id"
jbe@10 2661 LEFT JOIN "vote"
jbe@10 2662 ON "vote"."initiative_id" = "initiative"."id"
jbe@10 2663 AND "vote"."member_id" = "direct_voter"."member_id"
jbe@10 2664 WHERE "initiative"."issue_id" = "issue_id_p"
jbe@15 2665 AND "initiative"."admitted" -- NOTE: NULL case is handled too
jbe@10 2666 GROUP BY "initiative"."id"
jbe@10 2667 ) AS "vote_counts",
jbe@10 2668 "issue",
jbe@10 2669 "policy"
jbe@10 2670 WHERE "vote_counts"."initiative_id" = "initiative"."id"
jbe@10 2671 AND "issue"."id" = "initiative"."issue_id"
jbe@10 2672 AND "policy"."id" = "issue"."policy_id";
jbe@0 2673 UPDATE "issue" SET "closed" = now() WHERE "id" = "issue_id_p";
jbe@0 2674 END;
jbe@0 2675 $$;
jbe@0 2676
jbe@0 2677 COMMENT ON FUNCTION "close_voting"
jbe@0 2678 ( "issue"."id"%TYPE )
jbe@0 2679 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 2680
jbe@0 2681
jbe@0 2682 CREATE FUNCTION "init_array"("dim_p" INTEGER)
jbe@0 2683 RETURNS INT4[]
jbe@0 2684 LANGUAGE 'plpgsql' IMMUTABLE AS $$
jbe@0 2685 DECLARE
jbe@0 2686 "i" INTEGER;
jbe@0 2687 "ary_text_v" TEXT;
jbe@0 2688 BEGIN
jbe@0 2689 IF "dim_p" >= 1 THEN
jbe@0 2690 "ary_text_v" := '{NULL';
jbe@0 2691 "i" := "dim_p";
jbe@0 2692 LOOP
jbe@0 2693 "i" := "i" - 1;
jbe@0 2694 EXIT WHEN "i" = 0;
jbe@0 2695 "ary_text_v" := "ary_text_v" || ',NULL';
jbe@0 2696 END LOOP;
jbe@0 2697 "ary_text_v" := "ary_text_v" || '}';
jbe@0 2698 RETURN "ary_text_v"::INT4[][];
jbe@0 2699 ELSE
jbe@0 2700 RAISE EXCEPTION 'Dimension needs to be at least 1.';
jbe@0 2701 END IF;
jbe@0 2702 END;
jbe@0 2703 $$;
jbe@0 2704
jbe@0 2705 COMMENT ON FUNCTION "init_array"(INTEGER) IS 'Needed for PostgreSQL < 8.4, due to missing "array_fill" function';
jbe@0 2706
jbe@0 2707
jbe@0 2708 CREATE FUNCTION "init_square_matrix"("dim_p" INTEGER)
jbe@0 2709 RETURNS INT4[][]
jbe@0 2710 LANGUAGE 'plpgsql' IMMUTABLE AS $$
jbe@0 2711 DECLARE
jbe@0 2712 "i" INTEGER;
jbe@0 2713 "row_text_v" TEXT;
jbe@0 2714 "ary_text_v" TEXT;
jbe@0 2715 BEGIN
jbe@0 2716 IF "dim_p" >= 1 THEN
jbe@0 2717 "row_text_v" := '{NULL';
jbe@0 2718 "i" := "dim_p";
jbe@0 2719 LOOP
jbe@0 2720 "i" := "i" - 1;
jbe@0 2721 EXIT WHEN "i" = 0;
jbe@0 2722 "row_text_v" := "row_text_v" || ',NULL';
jbe@0 2723 END LOOP;
jbe@0 2724 "row_text_v" := "row_text_v" || '}';
jbe@0 2725 "ary_text_v" := '{' || "row_text_v";
jbe@0 2726 "i" := "dim_p";
jbe@0 2727 LOOP
jbe@0 2728 "i" := "i" - 1;
jbe@0 2729 EXIT WHEN "i" = 0;
jbe@0 2730 "ary_text_v" := "ary_text_v" || ',' || "row_text_v";
jbe@0 2731 END LOOP;
jbe@0 2732 "ary_text_v" := "ary_text_v" || '}';
jbe@0 2733 RETURN "ary_text_v"::INT4[][];
jbe@0 2734 ELSE
jbe@0 2735 RAISE EXCEPTION 'Dimension needs to be at least 1.';
jbe@0 2736 END IF;
jbe@0 2737 END;
jbe@0 2738 $$;
jbe@0 2739
jbe@0 2740 COMMENT ON FUNCTION "init_square_matrix"(INTEGER) IS 'Needed for PostgreSQL < 8.4, due to missing "array_fill" function';
jbe@0 2741
jbe@0 2742
jbe@0 2743 CREATE FUNCTION "calculate_ranks"("issue_id_p" "issue"."id"%TYPE)
jbe@0 2744 RETURNS VOID
jbe@0 2745 LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@0 2746 DECLARE
jbe@0 2747 "dimension_v" INTEGER;
jbe@0 2748 "matrix" INT4[][];
jbe@0 2749 "i" INTEGER;
jbe@0 2750 "j" INTEGER;
jbe@0 2751 "k" INTEGER;
jbe@0 2752 "battle_row" "battle"%ROWTYPE;
jbe@0 2753 "rank_ary" INT4[];
jbe@0 2754 "rank_v" INT4;
jbe@0 2755 "done_v" INTEGER;
jbe@0 2756 "winners_ary" INTEGER[];
jbe@0 2757 "initiative_id_v" "initiative"."id"%TYPE;
jbe@0 2758 BEGIN
jbe@0 2759 PERFORM NULL FROM "issue" WHERE "id" = "issue_id_p" FOR UPDATE;
jbe@0 2760 -- Prepare matrix for Schulze-Method:
jbe@10 2761 SELECT count(1) INTO "dimension_v" FROM "initiative"
jbe@10 2762 WHERE "issue_id" = "issue_id_p" AND "agreed";
jbe@0 2763 IF "dimension_v" = 1 THEN
jbe@10 2764 UPDATE "initiative" SET "rank" = 1
jbe@10 2765 WHERE "issue_id" = "issue_id_p" AND "agreed";
jbe@0 2766 ELSIF "dimension_v" > 1 THEN
jbe@0 2767 "matrix" := "init_square_matrix"("dimension_v"); -- TODO: replace by "array_fill" function (PostgreSQL 8.4)
jbe@0 2768 "i" := 1;
jbe@0 2769 "j" := 2;
jbe@0 2770 -- Fill matrix with data from "battle" view
jbe@0 2771 FOR "battle_row" IN
jbe@0 2772 SELECT * FROM "battle" WHERE "issue_id" = "issue_id_p"
jbe@0 2773 ORDER BY "winning_initiative_id", "losing_initiative_id"
jbe@0 2774 LOOP
jbe@0 2775 "matrix"["i"]["j"] := "battle_row"."count";
jbe@0 2776 IF "j" = "dimension_v" THEN
jbe@0 2777 "i" := "i" + 1;
jbe@0 2778 "j" := 1;
jbe@0 2779 ELSE
jbe@0 2780 "j" := "j" + 1;
jbe@0 2781 IF "j" = "i" THEN
jbe@0 2782 "j" := "j" + 1;
jbe@0 2783 END IF;
jbe@0 2784 END IF;
jbe@0 2785 END LOOP;
jbe@0 2786 IF "i" != "dimension_v" OR "j" != "dimension_v" + 1 THEN
jbe@0 2787 RAISE EXCEPTION 'Wrong battle count (should not happen)';
jbe@0 2788 END IF;
jbe@0 2789 -- Delete losers from matrix:
jbe@0 2790 "i" := 1;
jbe@0 2791 LOOP
jbe@0 2792 "j" := "i" + 1;
jbe@0 2793 LOOP
jbe@0 2794 IF "i" != "j" THEN
jbe@0 2795 IF "matrix"["i"]["j"] < "matrix"["j"]["i"] THEN
jbe@0 2796 "matrix"["i"]["j"] := 0;
jbe@0 2797 ELSIF matrix[j][i] < matrix[i][j] THEN
jbe@0 2798 "matrix"["j"]["i"] := 0;
jbe@0 2799 ELSE
jbe@0 2800 "matrix"["i"]["j"] := 0;
jbe@0 2801 "matrix"["j"]["i"] := 0;
jbe@0 2802 END IF;
jbe@0 2803 END IF;
jbe@0 2804 EXIT WHEN "j" = "dimension_v";
jbe@0 2805 "j" := "j" + 1;
jbe@0 2806 END LOOP;
jbe@0 2807 EXIT WHEN "i" = "dimension_v" - 1;
jbe@0 2808 "i" := "i" + 1;
jbe@0 2809 END LOOP;
jbe@0 2810 -- Find best paths:
jbe@0 2811 "i" := 1;
jbe@0 2812 LOOP
jbe@0 2813 "j" := 1;
jbe@0 2814 LOOP
jbe@0 2815 IF "i" != "j" THEN
jbe@0 2816 "k" := 1;
jbe@0 2817 LOOP
jbe@0 2818 IF "i" != "k" AND "j" != "k" THEN
jbe@0 2819 IF "matrix"["j"]["i"] < "matrix"["i"]["k"] THEN
jbe@0 2820 IF "matrix"["j"]["i"] > "matrix"["j"]["k"] THEN
jbe@0 2821 "matrix"["j"]["k"] := "matrix"["j"]["i"];
jbe@0 2822 END IF;
jbe@0 2823 ELSE
jbe@0 2824 IF "matrix"["i"]["k"] > "matrix"["j"]["k"] THEN
jbe@0 2825 "matrix"["j"]["k"] := "matrix"["i"]["k"];
jbe@0 2826 END IF;
jbe@0 2827 END IF;
jbe@0 2828 END IF;
jbe@0 2829 EXIT WHEN "k" = "dimension_v";
jbe@0 2830 "k" := "k" + 1;
jbe@0 2831 END LOOP;
jbe@0 2832 END IF;
jbe@0 2833 EXIT WHEN "j" = "dimension_v";
jbe@0 2834 "j" := "j" + 1;
jbe@0 2835 END LOOP;
jbe@0 2836 EXIT WHEN "i" = "dimension_v";
jbe@0 2837 "i" := "i" + 1;
jbe@0 2838 END LOOP;
jbe@0 2839 -- Determine order of winners:
jbe@0 2840 "rank_ary" := "init_array"("dimension_v"); -- TODO: replace by "array_fill" function (PostgreSQL 8.4)
jbe@0 2841 "rank_v" := 1;
jbe@0 2842 "done_v" := 0;
jbe@0 2843 LOOP
jbe@0 2844 "winners_ary" := '{}';
jbe@0 2845 "i" := 1;
jbe@0 2846 LOOP
jbe@0 2847 IF "rank_ary"["i"] ISNULL THEN
jbe@0 2848 "j" := 1;
jbe@0 2849 LOOP
jbe@0 2850 IF
jbe@0 2851 "i" != "j" AND
jbe@0 2852 "rank_ary"["j"] ISNULL AND
jbe@0 2853 "matrix"["j"]["i"] > "matrix"["i"]["j"]
jbe@0 2854 THEN
jbe@0 2855 -- someone else is better
jbe@0 2856 EXIT;
jbe@0 2857 END IF;
jbe@0 2858 IF "j" = "dimension_v" THEN
jbe@0 2859 -- noone is better
jbe@0 2860 "winners_ary" := "winners_ary" || "i";
jbe@0 2861 EXIT;
jbe@0 2862 END IF;
jbe@0 2863 "j" := "j" + 1;
jbe@0 2864 END LOOP;
jbe@0 2865 END IF;
jbe@0 2866 EXIT WHEN "i" = "dimension_v";
jbe@0 2867 "i" := "i" + 1;
jbe@0 2868 END LOOP;
jbe@0 2869 "i" := 1;
jbe@0 2870 LOOP
jbe@0 2871 "rank_ary"["winners_ary"["i"]] := "rank_v";
jbe@0 2872 "done_v" := "done_v" + 1;
jbe@0 2873 EXIT WHEN "i" = array_upper("winners_ary", 1);
jbe@0 2874 "i" := "i" + 1;
jbe@0 2875 END LOOP;
jbe@0 2876 EXIT WHEN "done_v" = "dimension_v";
jbe@0 2877 "rank_v" := "rank_v" + 1;
jbe@0 2878 END LOOP;
jbe@0 2879 -- write preliminary ranks:
jbe@0 2880 "i" := 1;
jbe@0 2881 FOR "initiative_id_v" IN
jbe@10 2882 SELECT "id" FROM "initiative"
jbe@10 2883 WHERE "issue_id" = "issue_id_p" AND "agreed"
jbe@10 2884 ORDER BY "id"
jbe@0 2885 LOOP
jbe@0 2886 UPDATE "initiative" SET "rank" = "rank_ary"["i"]
jbe@0 2887 WHERE "id" = "initiative_id_v";
jbe@0 2888 "i" := "i" + 1;
jbe@0 2889 END LOOP;
jbe@0 2890 IF "i" != "dimension_v" + 1 THEN
jbe@0 2891 RAISE EXCEPTION 'Wrong winner count (should not happen)';
jbe@0 2892 END IF;
jbe@0 2893 -- straighten ranks (start counting with 1, no equal ranks):
jbe@0 2894 "rank_v" := 1;
jbe@0 2895 FOR "initiative_id_v" IN
jbe@0 2896 SELECT "id" FROM "initiative"
jbe@0 2897 WHERE "issue_id" = "issue_id_p" AND "rank" NOTNULL
jbe@0 2898 ORDER BY
jbe@0 2899 "rank",
jbe@0 2900 "vote_ratio"("positive_votes", "negative_votes") DESC,
jbe@0 2901 "id"
jbe@0 2902 LOOP
jbe@0 2903 UPDATE "initiative" SET "rank" = "rank_v"
jbe@0 2904 WHERE "id" = "initiative_id_v";
jbe@0 2905 "rank_v" := "rank_v" + 1;
jbe@0 2906 END LOOP;
jbe@0 2907 END IF;
jbe@0 2908 -- mark issue as finished
jbe@0 2909 UPDATE "issue" SET "ranks_available" = TRUE
jbe@0 2910 WHERE "id" = "issue_id_p";
jbe@0 2911 RETURN;
jbe@0 2912 END;
jbe@0 2913 $$;
jbe@0 2914
jbe@0 2915 COMMENT ON FUNCTION "calculate_ranks"
jbe@0 2916 ( "issue"."id"%TYPE )
jbe@0 2917 IS 'Determine ranking (Votes have to be counted first)';
jbe@0 2918
jbe@0 2919
jbe@0 2920
jbe@0 2921 -----------------------------
jbe@0 2922 -- Automatic state changes --
jbe@0 2923 -----------------------------
jbe@0 2924
jbe@0 2925
jbe@0 2926 CREATE FUNCTION "check_issue"
jbe@0 2927 ( "issue_id_p" "issue"."id"%TYPE )
jbe@0 2928 RETURNS VOID
jbe@0 2929 LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@0 2930 DECLARE
jbe@0 2931 "issue_row" "issue"%ROWTYPE;
jbe@0 2932 "policy_row" "policy"%ROWTYPE;
jbe@0 2933 "voting_requested_v" BOOLEAN;
jbe@0 2934 BEGIN
jbe@0 2935 PERFORM "global_lock"();
jbe@0 2936 SELECT * INTO "issue_row" FROM "issue" WHERE "id" = "issue_id_p";
jbe@0 2937 IF "issue_row"."closed" ISNULL THEN
jbe@0 2938 SELECT * INTO "policy_row" FROM "policy"
jbe@0 2939 WHERE "id" = "issue_row"."policy_id";
jbe@3 2940 IF "issue_row"."fully_frozen" ISNULL THEN
jbe@0 2941 PERFORM "create_snapshot"("issue_id_p");
jbe@0 2942 SELECT * INTO "issue_row" FROM "issue" WHERE "id" = "issue_id_p";
jbe@0 2943 END IF;
jbe@0 2944 IF "issue_row"."accepted" ISNULL THEN
jbe@0 2945 IF EXISTS (
jbe@0 2946 SELECT NULL FROM "initiative"
jbe@0 2947 WHERE "issue_id" = "issue_id_p"
jbe@0 2948 AND "supporter_count" > 0
jbe@0 2949 AND "supporter_count" * "policy_row"."issue_quorum_den"
jbe@0 2950 >= "issue_row"."population" * "policy_row"."issue_quorum_num"
jbe@0 2951 ) THEN
jbe@3 2952 PERFORM "set_snapshot_event"("issue_id_p", 'end_of_admission');
jbe@0 2953 "issue_row"."accepted" = now(); -- NOTE: "issue_row" used later
jbe@0 2954 UPDATE "issue" SET "accepted" = "issue_row"."accepted"
jbe@0 2955 WHERE "id" = "issue_row"."id";
jbe@0 2956 ELSIF
jbe@22 2957 now() >= "issue_row"."created" + "issue_row"."admission_time"
jbe@0 2958 THEN
jbe@0 2959 PERFORM "set_snapshot_event"("issue_id_p", 'end_of_admission');
jbe@0 2960 UPDATE "issue" SET "closed" = now()
jbe@0 2961 WHERE "id" = "issue_row"."id";
jbe@0 2962 END IF;
jbe@0 2963 END IF;
jbe@0 2964 IF
jbe@0 2965 "issue_row"."accepted" NOTNULL AND
jbe@3 2966 "issue_row"."half_frozen" ISNULL
jbe@0 2967 THEN
jbe@0 2968 SELECT
jbe@0 2969 CASE
jbe@0 2970 WHEN "vote_now" * 2 > "issue_row"."population" THEN
jbe@0 2971 TRUE
jbe@0 2972 WHEN "vote_later" * 2 > "issue_row"."population" THEN
jbe@0 2973 FALSE
jbe@0 2974 ELSE NULL
jbe@0 2975 END
jbe@0 2976 INTO "voting_requested_v"
jbe@0 2977 FROM "issue" WHERE "id" = "issue_id_p";
jbe@0 2978 IF
jbe@0 2979 "voting_requested_v" OR (
jbe@3 2980 "voting_requested_v" ISNULL AND
jbe@22 2981 now() >= "issue_row"."accepted" + "issue_row"."discussion_time"
jbe@0 2982 )
jbe@0 2983 THEN
jbe@21 2984 PERFORM "set_snapshot_event"("issue_id_p", 'half_freeze');
jbe@3 2985 "issue_row"."half_frozen" = now(); -- NOTE: "issue_row" used later
jbe@3 2986 UPDATE "issue" SET "half_frozen" = "issue_row"."half_frozen"
jbe@3 2987 WHERE "id" = "issue_row"."id";
jbe@0 2988 END IF;
jbe@0 2989 END IF;
jbe@0 2990 IF
jbe@3 2991 "issue_row"."half_frozen" NOTNULL AND
jbe@3 2992 "issue_row"."fully_frozen" ISNULL AND
jbe@22 2993 now() >= "issue_row"."half_frozen" + "issue_row"."verification_time"
jbe@3 2994 THEN
jbe@3 2995 PERFORM "freeze_after_snapshot"("issue_id_p");
jbe@9 2996 -- "issue" might change, thus "issue_row" has to be updated below
jbe@3 2997 END IF;
jbe@9 2998 SELECT * INTO "issue_row" FROM "issue" WHERE "id" = "issue_id_p";
jbe@3 2999 IF
jbe@9 3000 "issue_row"."closed" ISNULL AND
jbe@3 3001 "issue_row"."fully_frozen" NOTNULL AND
jbe@22 3002 now() >= "issue_row"."fully_frozen" + "issue_row"."voting_time"
jbe@0 3003 THEN
jbe@0 3004 PERFORM "close_voting"("issue_id_p");
jbe@0 3005 END IF;
jbe@0 3006 END IF;
jbe@0 3007 RETURN;
jbe@0 3008 END;
jbe@0 3009 $$;
jbe@0 3010
jbe@0 3011 COMMENT ON FUNCTION "check_issue"
jbe@0 3012 ( "issue"."id"%TYPE )
jbe@0 3013 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 3014
jbe@0 3015
jbe@0 3016 CREATE FUNCTION "check_everything"()
jbe@0 3017 RETURNS VOID
jbe@0 3018 LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@0 3019 DECLARE
jbe@0 3020 "issue_id_v" "issue"."id"%TYPE;
jbe@0 3021 BEGIN
jbe@1 3022 DELETE FROM "expired_session";
jbe@4 3023 PERFORM "calculate_member_counts"();
jbe@4 3024 FOR "issue_id_v" IN SELECT "id" FROM "open_issue" LOOP
jbe@0 3025 PERFORM "check_issue"("issue_id_v");
jbe@0 3026 END LOOP;
jbe@4 3027 FOR "issue_id_v" IN SELECT "id" FROM "issue_with_ranks_missing" LOOP
jbe@0 3028 PERFORM "calculate_ranks"("issue_id_v");
jbe@0 3029 END LOOP;
jbe@0 3030 RETURN;
jbe@0 3031 END;
jbe@0 3032 $$;
jbe@0 3033
jbe@0 3034 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 3035
jbe@0 3036
jbe@0 3037
jbe@8 3038 ------------------------------
jbe@8 3039 -- Deletion of private data --
jbe@8 3040 ------------------------------
jbe@8 3041
jbe@8 3042
jbe@8 3043 CREATE FUNCTION "delete_private_data"()
jbe@8 3044 RETURNS VOID
jbe@8 3045 LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@8 3046 DECLARE
jbe@8 3047 "issue_id_v" "issue"."id"%TYPE;
jbe@8 3048 BEGIN
jbe@9 3049 UPDATE "member" SET
jbe@11 3050 "login" = 'login' || "id"::text,
jbe@11 3051 "password" = NULL,
jbe@11 3052 "notify_email" = NULL,
jbe@11 3053 "notify_email_unconfirmed" = NULL,
jbe@11 3054 "notify_email_secret" = NULL,
jbe@11 3055 "notify_email_secret_expiry" = NULL,
jbe@11 3056 "password_reset_secret" = NULL,
jbe@11 3057 "password_reset_secret_expiry" = NULL,
jbe@11 3058 "organizational_unit" = NULL,
jbe@11 3059 "internal_posts" = NULL,
jbe@11 3060 "realname" = NULL,
jbe@11 3061 "birthday" = NULL,
jbe@11 3062 "address" = NULL,
jbe@11 3063 "email" = NULL,
jbe@11 3064 "xmpp_address" = NULL,
jbe@11 3065 "website" = NULL,
jbe@11 3066 "phone" = NULL,
jbe@11 3067 "mobile_phone" = NULL,
jbe@11 3068 "profession" = NULL,
jbe@11 3069 "external_memberships" = NULL,
jbe@11 3070 "external_posts" = NULL,
jbe@11 3071 "statement" = NULL;
jbe@11 3072 -- "text_search_data" is updated by triggers
jbe@8 3073 DELETE FROM "session";
jbe@12 3074 DELETE FROM "invite_code";
jbe@13 3075 DELETE FROM "contact";
jbe@11 3076 DELETE FROM "setting";
jbe@11 3077 DELETE FROM "member_image";
jbe@8 3078 DELETE FROM "direct_voter" USING "issue"
jbe@8 3079 WHERE "direct_voter"."issue_id" = "issue"."id"
jbe@8 3080 AND "issue"."closed" ISNULL;
jbe@8 3081 RETURN;
jbe@8 3082 END;
jbe@8 3083 $$;
jbe@8 3084
jbe@8 3085 COMMENT ON FUNCTION "delete_private_data"() IS 'DO NOT USE on productive database, but only on a copy! This function deletes all data which should not be publicly available, and can be used to create a database dump for publication.';
jbe@8 3086
jbe@8 3087
jbe@8 3088
jbe@0 3089 COMMIT;

Impressum / About Us