liquid_feedback_core

annotate core.sql @ 42:0dfc5e164b52

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

Impressum / About Us