liquid_feedback_core

annotate core.sql @ 498:10b90162e982

Corrected comparison operator in view "newsletter_to_send"
author jbe
date Sun Apr 03 20:57:44 2016 +0200 (2016-04-03)
parents 91e3d31c1de2
children bc4b590a8eec
rev   line source
jbe@0 1
jbe@92 2 -- Execute the following command manually for PostgreSQL prior version 9.0:
jbe@92 3 -- CREATE LANGUAGE plpgsql;
jbe@0 4
jbe@0 5 -- NOTE: In PostgreSQL every UNIQUE constraint implies creation of an index
jbe@0 6
jbe@0 7 BEGIN;
jbe@0 8
jbe@5 9 CREATE VIEW "liquid_feedback_version" AS
jbe@460 10 SELECT * FROM (VALUES ('3.2.0', 3, 2, 0))
jbe@5 11 AS "subquery"("string", "major", "minor", "revision");
jbe@5 12
jbe@0 13
jbe@0 14
jbe@7 15 ----------------------
jbe@7 16 -- Full text search --
jbe@7 17 ----------------------
jbe@7 18
jbe@7 19
jbe@7 20 CREATE FUNCTION "text_search_query"("query_text_p" TEXT)
jbe@7 21 RETURNS TSQUERY
jbe@7 22 LANGUAGE 'plpgsql' IMMUTABLE AS $$
jbe@7 23 BEGIN
jbe@7 24 RETURN plainto_tsquery('pg_catalog.simple', "query_text_p");
jbe@7 25 END;
jbe@7 26 $$;
jbe@7 27
jbe@7 28 COMMENT ON FUNCTION "text_search_query"(TEXT) IS 'Usage: WHERE "text_search_data" @@ "text_search_query"(''<user query>'')';
jbe@7 29
jbe@7 30
jbe@7 31 CREATE FUNCTION "highlight"
jbe@7 32 ( "body_p" TEXT,
jbe@7 33 "query_text_p" TEXT )
jbe@7 34 RETURNS TEXT
jbe@7 35 LANGUAGE 'plpgsql' IMMUTABLE AS $$
jbe@7 36 BEGIN
jbe@7 37 RETURN ts_headline(
jbe@7 38 'pg_catalog.simple',
jbe@8 39 replace(replace("body_p", e'\\', e'\\\\'), '*', e'\\*'),
jbe@7 40 "text_search_query"("query_text_p"),
jbe@7 41 'StartSel=* StopSel=* HighlightAll=TRUE' );
jbe@7 42 END;
jbe@7 43 $$;
jbe@7 44
jbe@7 45 COMMENT ON FUNCTION "highlight"
jbe@7 46 ( "body_p" TEXT,
jbe@7 47 "query_text_p" TEXT )
jbe@7 48 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 49
jbe@7 50
jbe@7 51
jbe@0 52 -------------------------
jbe@0 53 -- Tables and indicies --
jbe@0 54 -------------------------
jbe@0 55
jbe@8 56
jbe@385 57 CREATE TABLE "temporary_transaction_data" (
jbe@385 58 PRIMARY KEY ("txid", "key"),
jbe@385 59 "txid" INT8 DEFAULT txid_current(),
jbe@383 60 "key" TEXT,
jbe@383 61 "value" TEXT NOT NULL );
jbe@383 62
jbe@385 63 COMMENT ON TABLE "temporary_transaction_data" IS 'Table to store temporary transaction data; shall be emptied before a transaction is committed';
jbe@385 64
jbe@385 65 COMMENT ON COLUMN "temporary_transaction_data"."txid" IS 'Value returned by function txid_current(); should be added to WHERE clause, when doing SELECT on this table, but ignored when doing DELETE on this table';
jbe@383 66
jbe@383 67
jbe@104 68 CREATE TABLE "system_setting" (
jbe@104 69 "member_ttl" INTERVAL );
jbe@104 70 CREATE UNIQUE INDEX "system_setting_singleton_idx" ON "system_setting" ((1));
jbe@104 71
jbe@104 72 COMMENT ON TABLE "system_setting" IS 'This table contains only one row with different settings in each column.';
jbe@104 73 COMMENT ON INDEX "system_setting_singleton_idx" IS 'This index ensures that "system_setting" only contains one row maximum.';
jbe@104 74
jbe@184 75 COMMENT ON COLUMN "system_setting"."member_ttl" IS 'Time after members get their "active" flag set to FALSE, if they do not show any activity.';
jbe@104 76
jbe@104 77
jbe@111 78 CREATE TABLE "contingent" (
jbe@293 79 PRIMARY KEY ("polling", "time_frame"),
jbe@293 80 "polling" BOOLEAN,
jbe@293 81 "time_frame" INTERVAL,
jbe@111 82 "text_entry_limit" INT4,
jbe@111 83 "initiative_limit" INT4 );
jbe@111 84
jbe@111 85 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@111 86
jbe@293 87 COMMENT ON COLUMN "contingent"."polling" IS 'Determines if settings are for creating initiatives and new drafts of initiatives with "polling" flag set';
jbe@111 88 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@111 89 COMMENT ON COLUMN "contingent"."initiative_limit" IS 'Number of new initiatives to be opened by each member within a given time frame';
jbe@111 90
jbe@111 91
jbe@113 92 CREATE TYPE "notify_level" AS ENUM
jbe@113 93 ('none', 'voting', 'verification', 'discussion', 'all');
jbe@113 94
jbe@113 95 COMMENT ON TYPE "notify_level" IS 'Level of notification: ''none'' = no notifications, ''voting'' = notifications about finished issues and issues in voting, ''verification'' = notifications about finished issues, issues in voting and verification phase, ''discussion'' = notifications about everything except issues in admission phase, ''all'' = notifications about everything';
jbe@113 96
jbe@113 97
jbe@0 98 CREATE TABLE "member" (
jbe@0 99 "id" SERIAL4 PRIMARY KEY,
jbe@13 100 "created" TIMESTAMPTZ NOT NULL DEFAULT now(),
jbe@181 101 "invite_code" TEXT UNIQUE,
jbe@232 102 "invite_code_expiry" TIMESTAMPTZ,
jbe@182 103 "admin_comment" TEXT,
jbe@181 104 "activated" TIMESTAMPTZ,
jbe@184 105 "last_activity" DATE,
jbe@42 106 "last_login" TIMESTAMPTZ,
jbe@387 107 "last_delegation_check" TIMESTAMPTZ,
jbe@45 108 "login" TEXT UNIQUE,
jbe@0 109 "password" TEXT,
jbe@440 110 "authority" TEXT,
jbe@440 111 "authority_uid" TEXT,
jbe@440 112 "authority_login" TEXT,
jbe@99 113 "locked" BOOLEAN NOT NULL DEFAULT FALSE,
jbe@181 114 "active" BOOLEAN NOT NULL DEFAULT FALSE,
jbe@0 115 "admin" BOOLEAN NOT NULL DEFAULT FALSE,
jbe@221 116 "lang" TEXT,
jbe@7 117 "notify_email" TEXT,
jbe@11 118 "notify_email_unconfirmed" TEXT,
jbe@11 119 "notify_email_secret" TEXT UNIQUE,
jbe@11 120 "notify_email_secret_expiry" TIMESTAMPTZ,
jbe@55 121 "notify_email_lock_expiry" TIMESTAMPTZ,
jbe@486 122 "disable_notifications" BOOLEAN NOT NULL DEFAULT FALSE,
jbe@486 123 "notification_counter" INT4 NOT NULL DEFAULT 1,
jbe@486 124 "notification_sample_size" INT4 NOT NULL DEFAULT 3,
jbe@486 125 "notification_dow" INT4 CHECK ("notification_dow" BETWEEN 0 AND 6),
jbe@486 126 "notification_hour" INT4 CHECK ("notification_hour" BETWEEN 0 AND 23),
jbe@387 127 "login_recovery_expiry" TIMESTAMPTZ,
jbe@11 128 "password_reset_secret" TEXT UNIQUE,
jbe@11 129 "password_reset_secret_expiry" TIMESTAMPTZ,
jbe@225 130 "name" TEXT UNIQUE,
jbe@7 131 "identification" TEXT UNIQUE,
jbe@214 132 "authentication" TEXT,
jbe@7 133 "organizational_unit" TEXT,
jbe@7 134 "internal_posts" TEXT,
jbe@7 135 "realname" TEXT,
jbe@7 136 "birthday" DATE,
jbe@7 137 "address" TEXT,
jbe@7 138 "email" TEXT,
jbe@7 139 "xmpp_address" TEXT,
jbe@7 140 "website" TEXT,
jbe@7 141 "phone" TEXT,
jbe@7 142 "mobile_phone" TEXT,
jbe@7 143 "profession" TEXT,
jbe@7 144 "external_memberships" TEXT,
jbe@7 145 "external_posts" TEXT,
jbe@159 146 "formatting_engine" TEXT,
jbe@7 147 "statement" TEXT,
jbe@181 148 "text_search_data" TSVECTOR,
jbe@184 149 CONSTRAINT "active_requires_activated_and_last_activity"
jbe@225 150 CHECK ("active" = FALSE OR ("activated" NOTNULL AND "last_activity" NOTNULL)),
jbe@440 151 CONSTRAINT "authority_requires_uid_and_vice_versa"
jbe@447 152 CHECK (("authority" NOTNULL) = ("authority_uid" NOTNULL)),
jbe@440 153 CONSTRAINT "authority_uid_unique_per_authority"
jbe@440 154 UNIQUE ("authority", "authority_uid"),
jbe@440 155 CONSTRAINT "authority_login_requires_authority"
jbe@440 156 CHECK ("authority" NOTNULL OR "authority_login" ISNULL),
jbe@225 157 CONSTRAINT "name_not_null_if_activated"
jbe@225 158 CHECK ("activated" ISNULL OR "name" NOTNULL) );
jbe@440 159 CREATE INDEX "member_authority_login_idx" ON "member" ("authority_login");
jbe@0 160 CREATE INDEX "member_active_idx" ON "member" ("active");
jbe@8 161 CREATE INDEX "member_text_search_data_idx" ON "member" USING gin ("text_search_data");
jbe@7 162 CREATE TRIGGER "update_text_search_data"
jbe@7 163 BEFORE INSERT OR UPDATE ON "member"
jbe@7 164 FOR EACH ROW EXECUTE PROCEDURE
jbe@7 165 tsvector_update_trigger('text_search_data', 'pg_catalog.simple',
jbe@7 166 "name", "identification", "organizational_unit", "internal_posts",
jbe@7 167 "realname", "external_memberships", "external_posts", "statement" );
jbe@0 168
jbe@0 169 COMMENT ON TABLE "member" IS 'Users of the system, e.g. members of an organization';
jbe@0 170
jbe@181 171 COMMENT ON COLUMN "member"."created" IS 'Creation of member record and/or invite code';
jbe@181 172 COMMENT ON COLUMN "member"."invite_code" IS 'Optional invite code, to allow a member to initialize his/her account the first time';
jbe@232 173 COMMENT ON COLUMN "member"."invite_code_expiry" IS 'Expiry data/time for "invite_code"';
jbe@182 174 COMMENT ON COLUMN "member"."admin_comment" IS 'Hidden comment for administrative purposes';
jbe@207 175 COMMENT ON COLUMN "member"."activated" IS 'Timestamp of first activation of account (i.e. usage of "invite_code"); required to be set for "active" members';
jbe@184 176 COMMENT ON COLUMN "member"."last_activity" IS 'Date of last activity of member; required to be set for "active" members';
jbe@103 177 COMMENT ON COLUMN "member"."last_login" IS 'Timestamp of last login';
jbe@387 178 COMMENT ON COLUMN "member"."last_delegation_check" IS 'Timestamp of last delegation check (i.e. confirmation of all unit and area delegations)';
jbe@10 179 COMMENT ON COLUMN "member"."login" IS 'Login name';
jbe@10 180 COMMENT ON COLUMN "member"."password" IS 'Password (preferably as crypto-hash, depending on the frontend or access layer)';
jbe@440 181 COMMENT ON COLUMN "member"."authority" IS 'NULL if LiquidFeedback Core is authoritative for the member account; otherwise a string that indicates the source/authority of the external account (e.g. ''LDAP'' for an LDAP account)';
jbe@440 182 COMMENT ON COLUMN "member"."authority_uid" IS 'Unique identifier (unique per "authority") that allows to identify an external account (e.g. even if the login name changes)';
jbe@440 183 COMMENT ON COLUMN "member"."authority_login" IS 'Login name for external accounts (field is not unique!)';
jbe@99 184 COMMENT ON COLUMN "member"."locked" IS 'Locked members can not log in.';
jbe@184 185 COMMENT ON COLUMN "member"."active" IS 'Memberships, support and votes are taken into account when corresponding members are marked as active. Automatically set to FALSE, if "last_activity" is older than "system_setting"."member_ttl".';
jbe@10 186 COMMENT ON COLUMN "member"."admin" IS 'TRUE for admins, which can administrate other users and setup policies and areas';
jbe@221 187 COMMENT ON COLUMN "member"."lang" IS 'Language code of the preferred language of the member';
jbe@10 188 COMMENT ON COLUMN "member"."notify_email" IS 'Email address where notifications of the system are sent to';
jbe@10 189 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 190 COMMENT ON COLUMN "member"."notify_email_secret" IS 'Secret sent to the address in "notify_email_unconformed"';
jbe@10 191 COMMENT ON COLUMN "member"."notify_email_secret_expiry" IS 'Expiry date/time for "notify_email_secret"';
jbe@55 192 COMMENT ON COLUMN "member"."notify_email_lock_expiry" IS 'Date/time until no further email confirmation mails may be sent (abuse protection)';
jbe@460 193 COMMENT ON COLUMN "member"."disable_notifications" IS 'TRUE if member does not want to receive notifications';
jbe@387 194 COMMENT ON COLUMN "member"."login_recovery_expiry" IS 'Date/time after which another login recovery attempt is allowed';
jbe@387 195 COMMENT ON COLUMN "member"."password_reset_secret" IS 'Secret string sent via e-mail for password recovery';
jbe@387 196 COMMENT ON COLUMN "member"."password_reset_secret_expiry" IS 'Date/time until the password recovery secret is valid, and date/time after which another password recovery attempt is allowed';
jbe@225 197 COMMENT ON COLUMN "member"."name" IS 'Distinct name of the member, may be NULL if account has not been activated yet';
jbe@10 198 COMMENT ON COLUMN "member"."identification" IS 'Optional identification number or code of the member';
jbe@214 199 COMMENT ON COLUMN "member"."authentication" IS 'Information about how this member was authenticated';
jbe@10 200 COMMENT ON COLUMN "member"."organizational_unit" IS 'Branch or division of the organization the member belongs to';
jbe@10 201 COMMENT ON COLUMN "member"."internal_posts" IS 'Posts (offices) of the member inside the organization';
jbe@10 202 COMMENT ON COLUMN "member"."realname" IS 'Real name of the member, may be identical with "name"';
jbe@10 203 COMMENT ON COLUMN "member"."email" IS 'Published email address of the member; not used for system notifications';
jbe@10 204 COMMENT ON COLUMN "member"."external_memberships" IS 'Other organizations the member is involved in';
jbe@10 205 COMMENT ON COLUMN "member"."external_posts" IS 'Posts (offices) outside the organization';
jbe@159 206 COMMENT ON COLUMN "member"."formatting_engine" IS 'Allows different formatting engines (i.e. wiki formats) to be used for "member"."statement"';
jbe@207 207 COMMENT ON COLUMN "member"."statement" IS 'Freely chosen text of the member for his/her profile';
jbe@7 208
jbe@7 209
jbe@13 210 CREATE TABLE "member_history" (
jbe@13 211 "id" SERIAL8 PRIMARY KEY,
jbe@13 212 "member_id" INT4 NOT NULL REFERENCES "member" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@13 213 "until" TIMESTAMPTZ NOT NULL DEFAULT now(),
jbe@42 214 "active" BOOLEAN NOT NULL,
jbe@13 215 "name" TEXT NOT NULL );
jbe@45 216 CREATE INDEX "member_history_member_id_idx" ON "member_history" ("member_id");
jbe@13 217
jbe@57 218 COMMENT ON TABLE "member_history" IS 'Filled by trigger; keeps information about old names and active flag of members';
jbe@13 219
jbe@13 220 COMMENT ON COLUMN "member_history"."id" IS 'Primary key, which can be used to sort entries correctly (and time warp resistant)';
jbe@57 221 COMMENT ON COLUMN "member_history"."until" IS 'Timestamp until the data was valid';
jbe@13 222
jbe@13 223
jbe@159 224 CREATE TABLE "rendered_member_statement" (
jbe@159 225 PRIMARY KEY ("member_id", "format"),
jbe@461 226 "member_id" INT4 REFERENCES "member" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@159 227 "format" TEXT,
jbe@159 228 "content" TEXT NOT NULL );
jbe@159 229
jbe@159 230 COMMENT ON TABLE "rendered_member_statement" IS 'This table may be used by frontends to cache "rendered" member statements (e.g. HTML output generated from wiki text)';
jbe@9 231
jbe@9 232
jbe@9 233 CREATE TABLE "setting" (
jbe@9 234 PRIMARY KEY ("member_id", "key"),
jbe@9 235 "member_id" INT4 REFERENCES "member" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@9 236 "key" TEXT NOT NULL,
jbe@9 237 "value" TEXT NOT NULL );
jbe@9 238 CREATE INDEX "setting_key_idx" ON "setting" ("key");
jbe@9 239
jbe@38 240 COMMENT ON TABLE "setting" IS 'Place to store a frontend specific setting for members as a string';
jbe@9 241
jbe@9 242 COMMENT ON COLUMN "setting"."key" IS 'Name of the setting, preceded by a frontend specific prefix';
jbe@9 243
jbe@9 244
jbe@16 245 CREATE TABLE "setting_map" (
jbe@16 246 PRIMARY KEY ("member_id", "key", "subkey"),
jbe@16 247 "member_id" INT4 REFERENCES "member" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@16 248 "key" TEXT NOT NULL,
jbe@16 249 "subkey" TEXT NOT NULL,
jbe@16 250 "value" TEXT NOT NULL );
jbe@16 251 CREATE INDEX "setting_map_key_idx" ON "setting_map" ("key");
jbe@16 252
jbe@23 253 COMMENT ON TABLE "setting_map" IS 'Place to store a frontend specific setting for members as a map of key value pairs';
jbe@16 254
jbe@16 255 COMMENT ON COLUMN "setting_map"."key" IS 'Name of the setting, preceded by a frontend specific prefix';
jbe@16 256 COMMENT ON COLUMN "setting_map"."subkey" IS 'Key of a map entry';
jbe@16 257 COMMENT ON COLUMN "setting_map"."value" IS 'Value of a map entry';
jbe@16 258
jbe@16 259
jbe@23 260 CREATE TABLE "member_relation_setting" (
jbe@23 261 PRIMARY KEY ("member_id", "key", "other_member_id"),
jbe@23 262 "member_id" INT4 REFERENCES "member" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@23 263 "key" TEXT NOT NULL,
jbe@23 264 "other_member_id" INT4 REFERENCES "member" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@23 265 "value" TEXT NOT NULL );
jbe@23 266
jbe@38 267 COMMENT ON TABLE "member_relation_setting" IS 'Place to store a frontend specific setting related to relations between members as a string';
jbe@23 268
jbe@23 269
jbe@7 270 CREATE TYPE "member_image_type" AS ENUM ('photo', 'avatar');
jbe@7 271
jbe@7 272 COMMENT ON TYPE "member_image_type" IS 'Types of images for a member';
jbe@7 273
jbe@7 274
jbe@7 275 CREATE TABLE "member_image" (
jbe@7 276 PRIMARY KEY ("member_id", "image_type", "scaled"),
jbe@7 277 "member_id" INT4 REFERENCES "member" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@7 278 "image_type" "member_image_type",
jbe@7 279 "scaled" BOOLEAN,
jbe@7 280 "content_type" TEXT,
jbe@7 281 "data" BYTEA NOT NULL );
jbe@7 282
jbe@7 283 COMMENT ON TABLE "member_image" IS 'Images of members';
jbe@7 284
jbe@7 285 COMMENT ON COLUMN "member_image"."scaled" IS 'FALSE for original image, TRUE for scaled version of the image';
jbe@0 286
jbe@0 287
jbe@4 288 CREATE TABLE "member_count" (
jbe@341 289 "calculated" TIMESTAMPTZ NOT NULL DEFAULT now(),
jbe@5 290 "total_count" INT4 NOT NULL );
jbe@4 291
jbe@5 292 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 293
jbe@5 294 COMMENT ON COLUMN "member_count"."calculated" IS 'timestamp indicating when the total member count and area member counts were calculated';
jbe@5 295 COMMENT ON COLUMN "member_count"."total_count" IS 'Total count of active(!) members';
jbe@4 296
jbe@4 297
jbe@0 298 CREATE TABLE "contact" (
jbe@0 299 PRIMARY KEY ("member_id", "other_member_id"),
jbe@0 300 "member_id" INT4 REFERENCES "member" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@0 301 "other_member_id" INT4 REFERENCES "member" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@11 302 "public" BOOLEAN NOT NULL DEFAULT FALSE,
jbe@11 303 CONSTRAINT "cant_save_yourself_as_contact"
jbe@11 304 CHECK ("member_id" != "other_member_id") );
jbe@113 305 CREATE INDEX "contact_other_member_id_idx" ON "contact" ("other_member_id");
jbe@0 306
jbe@0 307 COMMENT ON TABLE "contact" IS 'Contact lists';
jbe@0 308
jbe@0 309 COMMENT ON COLUMN "contact"."member_id" IS 'Member having the contact list';
jbe@0 310 COMMENT ON COLUMN "contact"."other_member_id" IS 'Member referenced in the contact list';
jbe@0 311 COMMENT ON COLUMN "contact"."public" IS 'TRUE = display contact publically';
jbe@0 312
jbe@0 313
jbe@113 314 CREATE TABLE "ignored_member" (
jbe@113 315 PRIMARY KEY ("member_id", "other_member_id"),
jbe@113 316 "member_id" INT4 REFERENCES "member" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@113 317 "other_member_id" INT4 REFERENCES "member" ("id") ON DELETE CASCADE ON UPDATE CASCADE );
jbe@113 318 CREATE INDEX "ignored_member_other_member_id_idx" ON "ignored_member" ("other_member_id");
jbe@113 319
jbe@113 320 COMMENT ON TABLE "ignored_member" IS 'Possibility to filter other members';
jbe@113 321
jbe@113 322 COMMENT ON COLUMN "ignored_member"."member_id" IS 'Member ignoring someone';
jbe@113 323 COMMENT ON COLUMN "ignored_member"."other_member_id" IS 'Member being ignored';
jbe@113 324
jbe@113 325
jbe@220 326 CREATE TABLE "session" (
jbe@220 327 "ident" TEXT PRIMARY KEY,
jbe@220 328 "additional_secret" TEXT,
jbe@220 329 "expiry" TIMESTAMPTZ NOT NULL DEFAULT now() + '24 hours',
jbe@461 330 "member_id" INT4 REFERENCES "member" ("id") ON DELETE SET NULL,
jbe@440 331 "authority" TEXT,
jbe@440 332 "authority_uid" TEXT,
jbe@440 333 "authority_login" TEXT,
jbe@387 334 "needs_delegation_check" BOOLEAN NOT NULL DEFAULT FALSE,
jbe@220 335 "lang" TEXT );
jbe@220 336 CREATE INDEX "session_expiry_idx" ON "session" ("expiry");
jbe@220 337
jbe@220 338 COMMENT ON TABLE "session" IS 'Sessions, i.e. for a web-frontend or API layer';
jbe@220 339
jbe@220 340 COMMENT ON COLUMN "session"."ident" IS 'Secret session identifier (i.e. random string)';
jbe@220 341 COMMENT ON COLUMN "session"."additional_secret" IS 'Additional field to store a secret, which can be used against CSRF attacks';
jbe@220 342 COMMENT ON COLUMN "session"."member_id" IS 'Reference to member, who is logged in';
jbe@440 343 COMMENT ON COLUMN "session"."authority" IS 'Temporary store for "member"."authority" during member account creation';
jbe@440 344 COMMENT ON COLUMN "session"."authority_uid" IS 'Temporary store for "member"."authority_uid" during member account creation';
jbe@440 345 COMMENT ON COLUMN "session"."authority_login" IS 'Temporary store for "member"."authority_login" during member account creation';
jbe@387 346 COMMENT ON COLUMN "session"."needs_delegation_check" IS 'Set to TRUE, if member must perform a delegation check to proceed with login; see column "last_delegation_check" in "member" table';
jbe@220 347 COMMENT ON COLUMN "session"."lang" IS 'Language code of the selected language';
jbe@220 348
jbe@220 349
jbe@424 350 CREATE TYPE "defeat_strength" AS ENUM ('simple', 'tuple');
jbe@424 351
jbe@424 352 COMMENT ON TYPE "defeat_strength" IS 'How pairwise defeats are measured for the Schulze method: ''simple'' = only the number of winning votes, ''tuple'' = primarily the number of winning votes, secondarily the number of losing votes';
jbe@424 353
jbe@424 354
jbe@424 355 CREATE TYPE "tie_breaking" AS ENUM ('simple', 'variant1', 'variant2');
jbe@424 356
jbe@424 357 COMMENT ON TYPE "tie_breaking" IS 'Tie-breaker for the Schulze method: ''simple'' = only initiative ids are used, ''variant1'' = use initiative ids in variant 1 for tie breaking of the links (TBRL) and sequentially forbid shared links, ''variant2'' = use initiative ids in variant 2 for tie breaking of the links (TBRL) and sequentially forbid shared links';
jbe@424 358
jbe@424 359
jbe@0 360 CREATE TABLE "policy" (
jbe@0 361 "id" SERIAL4 PRIMARY KEY,
jbe@9 362 "index" INT4 NOT NULL,
jbe@0 363 "active" BOOLEAN NOT NULL DEFAULT TRUE,
jbe@0 364 "name" TEXT NOT NULL UNIQUE,
jbe@0 365 "description" TEXT NOT NULL DEFAULT '',
jbe@261 366 "polling" BOOLEAN NOT NULL DEFAULT FALSE,
jbe@447 367 "min_admission_time" INTERVAL,
jbe@447 368 "max_admission_time" INTERVAL,
jbe@261 369 "discussion_time" INTERVAL,
jbe@261 370 "verification_time" INTERVAL,
jbe@261 371 "voting_time" INTERVAL,
jbe@292 372 "issue_quorum_num" INT4,
jbe@292 373 "issue_quorum_den" INT4,
jbe@0 374 "initiative_quorum_num" INT4 NOT NULL,
jbe@10 375 "initiative_quorum_den" INT4 NOT NULL,
jbe@424 376 "defeat_strength" "defeat_strength" NOT NULL DEFAULT 'tuple',
jbe@424 377 "tie_breaking" "tie_breaking" NOT NULL DEFAULT 'variant1',
jbe@167 378 "direct_majority_num" INT4 NOT NULL DEFAULT 1,
jbe@167 379 "direct_majority_den" INT4 NOT NULL DEFAULT 2,
jbe@167 380 "direct_majority_strict" BOOLEAN NOT NULL DEFAULT TRUE,
jbe@167 381 "direct_majority_positive" INT4 NOT NULL DEFAULT 0,
jbe@167 382 "direct_majority_non_negative" INT4 NOT NULL DEFAULT 0,
jbe@167 383 "indirect_majority_num" INT4 NOT NULL DEFAULT 1,
jbe@167 384 "indirect_majority_den" INT4 NOT NULL DEFAULT 2,
jbe@167 385 "indirect_majority_strict" BOOLEAN NOT NULL DEFAULT TRUE,
jbe@167 386 "indirect_majority_positive" INT4 NOT NULL DEFAULT 0,
jbe@167 387 "indirect_majority_non_negative" INT4 NOT NULL DEFAULT 0,
jbe@429 388 "no_reverse_beat_path" BOOLEAN NOT NULL DEFAULT FALSE,
jbe@260 389 "no_multistage_majority" BOOLEAN NOT NULL DEFAULT FALSE,
jbe@260 390 CONSTRAINT "timing" CHECK (
jbe@261 391 ( "polling" = FALSE AND
jbe@447 392 "min_admission_time" NOTNULL AND "max_admission_time" NOTNULL AND
jbe@454 393 "min_admission_time" <= "max_admission_time" AND
jbe@447 394 "discussion_time" NOTNULL AND
jbe@447 395 "verification_time" NOTNULL AND
jbe@447 396 "voting_time" NOTNULL ) OR
jbe@261 397 ( "polling" = TRUE AND
jbe@447 398 "min_admission_time" ISNULL AND "max_admission_time" ISNULL AND
jbe@447 399 "discussion_time" NOTNULL AND
jbe@447 400 "verification_time" NOTNULL AND
jbe@447 401 "voting_time" NOTNULL ) OR
jbe@447 402 ( "polling" = TRUE AND
jbe@447 403 "min_admission_time" ISNULL AND "max_admission_time" ISNULL AND
jbe@447 404 "discussion_time" ISNULL AND
jbe@447 405 "verification_time" ISNULL AND
jbe@447 406 "voting_time" ISNULL ) ),
jbe@292 407 CONSTRAINT "issue_quorum_if_and_only_if_not_polling" CHECK (
jbe@447 408 "polling" = ("issue_quorum_num" ISNULL) AND
jbe@447 409 "polling" = ("issue_quorum_den" ISNULL) ),
jbe@429 410 CONSTRAINT "no_reverse_beat_path_requires_tuple_defeat_strength" CHECK (
jbe@429 411 "defeat_strength" = 'tuple'::"defeat_strength" OR
jbe@429 412 "no_reverse_beat_path" = FALSE ) );
jbe@0 413 CREATE INDEX "policy_active_idx" ON "policy" ("active");
jbe@0 414
jbe@0 415 COMMENT ON TABLE "policy" IS 'Policies for a particular proceeding type (timelimits, quorum)';
jbe@0 416
jbe@9 417 COMMENT ON COLUMN "policy"."index" IS 'Determines the order in listings';
jbe@0 418 COMMENT ON COLUMN "policy"."active" IS 'TRUE = policy can be used for new issues';
jbe@447 419 COMMENT ON COLUMN "policy"."polling" IS 'TRUE = special policy for non-user-generated issues without issue quorum, where certain initiatives (those having the "polling" flag set) do not need to pass the initiative quorum; "min_admission_time" and "max_admission_time" MUST be set to NULL, the other timings may be set to NULL altogether, allowing individual timing for those issues';
jbe@447 420 COMMENT ON COLUMN "policy"."min_admission_time" IS 'Minimum duration of issue state ''admission''; Minimum time an issue stays open';
jbe@447 421 COMMENT ON COLUMN "policy"."max_admission_time" IS 'Maximum duration of issue state ''admission''; Maximum time an issue stays open without being "accepted"';
jbe@207 422 COMMENT ON COLUMN "policy"."discussion_time" IS 'Duration of issue state ''discussion''; Regular time until an issue is "half_frozen" after being "accepted"';
jbe@207 423 COMMENT ON COLUMN "policy"."verification_time" IS 'Duration of issue state ''verification''; Regular time until an issue is "fully_frozen" (e.g. entering issue state ''voting'') after being "half_frozen"';
jbe@207 424 COMMENT ON COLUMN "policy"."voting_time" IS 'Duration of issue state ''voting''; Time after an issue is "fully_frozen" but not "closed" (duration of issue state ''voting'')';
jbe@207 425 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" and enter issue state ''discussion''';
jbe@207 426 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" and enter issue state ''discussion''';
jbe@10 427 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 428 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@428 429 COMMENT ON COLUMN "policy"."defeat_strength" IS 'How pairwise defeats are measured for the Schulze method; see type "defeat_strength"; ''tuple'' is the recommended setting';
jbe@428 430 COMMENT ON COLUMN "policy"."tie_breaking" IS 'Tie-breaker for the Schulze method; see type "tie_breaking"; ''variant1'' or ''variant2'' are recommended';
jbe@167 431 COMMENT ON COLUMN "policy"."direct_majority_num" IS 'Numerator of fraction of neccessary direct majority for initiatives to be attainable as winner';
jbe@167 432 COMMENT ON COLUMN "policy"."direct_majority_den" IS 'Denominator of fraction of neccessary direct majority for initaitives to be attainable as winner';
jbe@167 433 COMMENT ON COLUMN "policy"."direct_majority_strict" IS 'If TRUE, then the direct majority must be strictly greater than "direct_majority_num"/"direct_majority_den", otherwise it may also be equal.';
jbe@167 434 COMMENT ON COLUMN "policy"."direct_majority_positive" IS 'Absolute number of "positive_votes" neccessary for an initiative to be attainable as winner';
jbe@167 435 COMMENT ON COLUMN "policy"."direct_majority_non_negative" IS 'Absolute number of sum of "positive_votes" and abstentions neccessary for an initiative to be attainable as winner';
jbe@167 436 COMMENT ON COLUMN "policy"."indirect_majority_num" IS 'Numerator of fraction of neccessary indirect majority (through beat path) for initiatives to be attainable as winner';
jbe@167 437 COMMENT ON COLUMN "policy"."indirect_majority_den" IS 'Denominator of fraction of neccessary indirect majority (through beat path) for initiatives to be attainable as winner';
jbe@167 438 COMMENT ON COLUMN "policy"."indirect_majority_strict" IS 'If TRUE, then the indirect majority must be strictly greater than "indirect_majority_num"/"indirect_majority_den", otherwise it may also be equal.';
jbe@167 439 COMMENT ON COLUMN "policy"."indirect_majority_positive" IS 'Absolute number of votes in favor of the winner neccessary in a beat path to the status quo for an initaitive to be attainable as winner';
jbe@167 440 COMMENT ON COLUMN "policy"."indirect_majority_non_negative" IS 'Absolute number of sum of votes in favor and abstentions in a beat path to the status quo for an initiative to be attainable as winner';
jbe@429 441 COMMENT ON COLUMN "policy"."no_reverse_beat_path" IS 'EXPERIMENTAL FEATURE: Causes initiatives with "reverse_beat_path" flag to not be "eligible", thus disallowing them to be winner. See comment on column "initiative"."reverse_beat_path". This option ensures both that a winning initiative is never tied in a (weak) condorcet paradox with the status quo and a winning initiative always beats the status quo directly with a simple majority.';
jbe@429 442 COMMENT ON COLUMN "policy"."no_multistage_majority" IS 'EXPERIMENTAL FEATURE: Causes initiatives with "multistage_majority" flag to not be "eligible", thus disallowing them to be winner. See comment on column "initiative"."multistage_majority". This disqualifies initiatives which could cause an instable result. An instable result in this meaning is a result such that repeating the ballot with same preferences but with the winner of the first ballot as status quo would lead to a different winner in the second ballot. If there are no direct majorities required for the winner, or if in direct comparison only simple majorities are required and "no_reverse_beat_path" is true, then results are always stable and this flag does not have any effect on the winner (but still affects the "eligible" flag of an "initiative").';
jbe@0 443
jbe@0 444
jbe@97 445 CREATE TABLE "unit" (
jbe@97 446 "id" SERIAL4 PRIMARY KEY,
jbe@97 447 "parent_id" INT4 REFERENCES "unit" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@97 448 "active" BOOLEAN NOT NULL DEFAULT TRUE,
jbe@97 449 "name" TEXT NOT NULL,
jbe@97 450 "description" TEXT NOT NULL DEFAULT '',
jbe@444 451 "external_reference" TEXT,
jbe@97 452 "member_count" INT4,
jbe@97 453 "text_search_data" TSVECTOR );
jbe@97 454 CREATE INDEX "unit_root_idx" ON "unit" ("id") WHERE "parent_id" ISNULL;
jbe@97 455 CREATE INDEX "unit_parent_id_idx" ON "unit" ("parent_id");
jbe@97 456 CREATE INDEX "unit_active_idx" ON "unit" ("active");
jbe@97 457 CREATE INDEX "unit_text_search_data_idx" ON "unit" USING gin ("text_search_data");
jbe@97 458 CREATE TRIGGER "update_text_search_data"
jbe@97 459 BEFORE INSERT OR UPDATE ON "unit"
jbe@97 460 FOR EACH ROW EXECUTE PROCEDURE
jbe@97 461 tsvector_update_trigger('text_search_data', 'pg_catalog.simple',
jbe@97 462 "name", "description" );
jbe@97 463
jbe@97 464 COMMENT ON TABLE "unit" IS 'Organizational units organized as trees; Delegations are not inherited through these trees.';
jbe@97 465
jbe@444 466 COMMENT ON COLUMN "unit"."parent_id" IS 'Parent id of tree node; Multiple roots allowed';
jbe@444 467 COMMENT ON COLUMN "unit"."active" IS 'TRUE means new issues can be created in areas of this unit';
jbe@444 468 COMMENT ON COLUMN "unit"."external_reference" IS 'Opaque data field to store an external reference';
jbe@444 469 COMMENT ON COLUMN "unit"."member_count" IS 'Count of members as determined by column "voting_right" in table "privilege"';
jbe@97 470
jbe@97 471
jbe@465 472 CREATE TABLE "subscription" (
jbe@465 473 PRIMARY KEY ("member_id", "unit_id"),
jbe@465 474 "member_id" INT4 REFERENCES "member" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@465 475 "unit_id" INT4 REFERENCES "unit" ("id") ON DELETE CASCADE ON UPDATE CASCADE );
jbe@465 476 CREATE INDEX "subscription_unit_id_idx" ON "subscription" ("unit_id");
jbe@465 477
jbe@465 478 COMMENT ON TABLE "subscription" IS 'An entry in this table denotes that the member wishes to receive notifications regardless of his/her privileges in the given unit';
jbe@465 479
jbe@465 480
jbe@203 481 CREATE TABLE "unit_setting" (
jbe@203 482 PRIMARY KEY ("member_id", "key", "unit_id"),
jbe@203 483 "member_id" INT4 REFERENCES "member" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@203 484 "key" TEXT NOT NULL,
jbe@203 485 "unit_id" INT4 REFERENCES "unit" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@203 486 "value" TEXT NOT NULL );
jbe@203 487
jbe@203 488 COMMENT ON TABLE "unit_setting" IS 'Place for frontend to store unit specific settings of members as strings';
jbe@203 489
jbe@203 490
jbe@0 491 CREATE TABLE "area" (
jbe@0 492 "id" SERIAL4 PRIMARY KEY,
jbe@97 493 "unit_id" INT4 NOT NULL REFERENCES "unit" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@0 494 "active" BOOLEAN NOT NULL DEFAULT TRUE,
jbe@0 495 "name" TEXT NOT NULL,
jbe@4 496 "description" TEXT NOT NULL DEFAULT '',
jbe@444 497 "external_reference" TEXT,
jbe@5 498 "direct_member_count" INT4,
jbe@5 499 "member_weight" INT4,
jbe@7 500 "text_search_data" TSVECTOR );
jbe@97 501 CREATE INDEX "area_unit_id_idx" ON "area" ("unit_id");
jbe@0 502 CREATE INDEX "area_active_idx" ON "area" ("active");
jbe@8 503 CREATE INDEX "area_text_search_data_idx" ON "area" USING gin ("text_search_data");
jbe@7 504 CREATE TRIGGER "update_text_search_data"
jbe@7 505 BEFORE INSERT OR UPDATE ON "area"
jbe@7 506 FOR EACH ROW EXECUTE PROCEDURE
jbe@7 507 tsvector_update_trigger('text_search_data', 'pg_catalog.simple',
jbe@7 508 "name", "description" );
jbe@0 509
jbe@0 510 COMMENT ON TABLE "area" IS 'Subject areas';
jbe@0 511
jbe@5 512 COMMENT ON COLUMN "area"."active" IS 'TRUE means new issues can be created in this area';
jbe@444 513 COMMENT ON COLUMN "area"."external_reference" IS 'Opaque data field to store an external reference';
jbe@5 514 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 515 COMMENT ON COLUMN "area"."member_weight" IS 'Same as "direct_member_count" but respecting delegations';
jbe@0 516
jbe@0 517
jbe@465 518 CREATE TABLE "ignored_area" (
jbe@465 519 PRIMARY KEY ("member_id", "area_id"),
jbe@465 520 "member_id" INT4 REFERENCES "unit" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@465 521 "area_id" INT4 REFERENCES "area" ("id") ON DELETE CASCADE ON UPDATE CASCADE );
jbe@465 522 CREATE INDEX "ignored_area_area_id_idx" ON "ignored_area" ("area_id");
jbe@465 523
jbe@465 524 COMMENT ON TABLE "ignored_area" IS 'An entry in this table denotes that the member does not wish to receive notifications for the given subject area unless he/she declared interested in a particular issue';
jbe@465 525
jbe@465 526
jbe@23 527 CREATE TABLE "area_setting" (
jbe@23 528 PRIMARY KEY ("member_id", "key", "area_id"),
jbe@23 529 "member_id" INT4 REFERENCES "member" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@23 530 "key" TEXT NOT NULL,
jbe@23 531 "area_id" INT4 REFERENCES "area" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@23 532 "value" TEXT NOT NULL );
jbe@23 533
jbe@23 534 COMMENT ON TABLE "area_setting" IS 'Place for frontend to store area specific settings of members as strings';
jbe@23 535
jbe@23 536
jbe@9 537 CREATE TABLE "allowed_policy" (
jbe@9 538 PRIMARY KEY ("area_id", "policy_id"),
jbe@9 539 "area_id" INT4 REFERENCES "area" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@9 540 "policy_id" INT4 NOT NULL REFERENCES "policy" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@9 541 "default_policy" BOOLEAN NOT NULL DEFAULT FALSE );
jbe@9 542 CREATE UNIQUE INDEX "allowed_policy_one_default_per_area_idx" ON "allowed_policy" ("area_id") WHERE "default_policy";
jbe@9 543
jbe@9 544 COMMENT ON TABLE "allowed_policy" IS 'Selects which policies can be used in each area';
jbe@9 545
jbe@9 546 COMMENT ON COLUMN "allowed_policy"."default_policy" IS 'One policy per area can be set as default.';
jbe@9 547
jbe@9 548
jbe@21 549 CREATE TYPE "snapshot_event" AS ENUM ('periodic', 'end_of_admission', 'half_freeze', 'full_freeze');
jbe@21 550
jbe@21 551 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 552
jbe@8 553
jbe@112 554 CREATE TYPE "issue_state" AS ENUM (
jbe@112 555 'admission', 'discussion', 'verification', 'voting',
jbe@389 556 'canceled_by_admin',
jbe@113 557 'canceled_revoked_before_accepted',
jbe@113 558 'canceled_issue_not_accepted',
jbe@113 559 'canceled_after_revocation_during_discussion',
jbe@113 560 'canceled_after_revocation_during_verification',
jbe@113 561 'canceled_no_initiative_admitted',
jbe@112 562 'finished_without_winner', 'finished_with_winner');
jbe@111 563
jbe@111 564 COMMENT ON TYPE "issue_state" IS 'State of issues';
jbe@111 565
jbe@111 566
jbe@0 567 CREATE TABLE "issue" (
jbe@0 568 "id" SERIAL4 PRIMARY KEY,
jbe@0 569 "area_id" INT4 NOT NULL REFERENCES "area" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@0 570 "policy_id" INT4 NOT NULL REFERENCES "policy" ("id") ON DELETE RESTRICT ON UPDATE CASCADE,
jbe@389 571 "admin_notice" TEXT,
jbe@444 572 "external_reference" TEXT,
jbe@111 573 "state" "issue_state" NOT NULL DEFAULT 'admission',
jbe@328 574 "phase_finished" TIMESTAMPTZ,
jbe@0 575 "created" TIMESTAMPTZ NOT NULL DEFAULT now(),
jbe@0 576 "accepted" TIMESTAMPTZ,
jbe@3 577 "half_frozen" TIMESTAMPTZ,
jbe@3 578 "fully_frozen" TIMESTAMPTZ,
jbe@0 579 "closed" TIMESTAMPTZ,
jbe@59 580 "cleaned" TIMESTAMPTZ,
jbe@447 581 "min_admission_time" INTERVAL,
jbe@447 582 "max_admission_time" INTERVAL,
jbe@22 583 "discussion_time" INTERVAL NOT NULL,
jbe@22 584 "verification_time" INTERVAL NOT NULL,
jbe@22 585 "voting_time" INTERVAL NOT NULL,
jbe@0 586 "snapshot" TIMESTAMPTZ,
jbe@8 587 "latest_snapshot_event" "snapshot_event",
jbe@0 588 "population" INT4,
jbe@4 589 "voter_count" INT4,
jbe@170 590 "status_quo_schulze_rank" INT4,
jbe@291 591 CONSTRAINT "admission_time_not_null_unless_instantly_accepted" CHECK (
jbe@447 592 ("min_admission_time" NOTNULL) = ("max_admission_time" NOTNULL) AND
jbe@452 593 ("min_admission_time" NOTNULL OR ("accepted" NOTNULL AND "accepted" = "created")) ),
jbe@340 594 CONSTRAINT "valid_state" CHECK (
jbe@340 595 (
jbe@340 596 ("accepted" ISNULL AND "half_frozen" ISNULL AND "fully_frozen" ISNULL ) OR
jbe@340 597 ("accepted" NOTNULL AND "half_frozen" ISNULL AND "fully_frozen" ISNULL ) OR
jbe@340 598 ("accepted" NOTNULL AND "half_frozen" NOTNULL AND "fully_frozen" ISNULL ) OR
jbe@340 599 ("accepted" NOTNULL AND "half_frozen" NOTNULL AND "fully_frozen" NOTNULL)
jbe@340 600 ) AND (
jbe@340 601 ("state" = 'admission' AND "closed" ISNULL AND "accepted" ISNULL) OR
jbe@340 602 ("state" = 'discussion' AND "closed" ISNULL AND "accepted" NOTNULL AND "half_frozen" ISNULL) OR
jbe@340 603 ("state" = 'verification' AND "closed" ISNULL AND "half_frozen" NOTNULL AND "fully_frozen" ISNULL) OR
jbe@340 604 ("state" = 'voting' AND "closed" ISNULL AND "fully_frozen" NOTNULL) OR
jbe@389 605 ("state" = 'canceled_by_admin' AND "closed" NOTNULL) OR
jbe@340 606 ("state" = 'canceled_revoked_before_accepted' AND "closed" NOTNULL AND "accepted" ISNULL) OR
jbe@340 607 ("state" = 'canceled_issue_not_accepted' AND "closed" NOTNULL AND "accepted" ISNULL) OR
jbe@340 608 ("state" = 'canceled_after_revocation_during_discussion' AND "closed" NOTNULL AND "half_frozen" ISNULL) OR
jbe@340 609 ("state" = 'canceled_after_revocation_during_verification' AND "closed" NOTNULL AND "fully_frozen" ISNULL) OR
jbe@340 610 ("state" = 'canceled_no_initiative_admitted' AND "closed" NOTNULL AND "fully_frozen" NOTNULL AND "closed" = "fully_frozen") OR
jbe@340 611 ("state" = 'finished_without_winner' AND "closed" NOTNULL AND "fully_frozen" NOTNULL AND "closed" != "fully_frozen") OR
jbe@340 612 ("state" = 'finished_with_winner' AND "closed" NOTNULL AND "fully_frozen" NOTNULL AND "closed" != "fully_frozen")
jbe@111 613 )),
jbe@328 614 CONSTRAINT "phase_finished_only_when_not_closed" CHECK (
jbe@328 615 "phase_finished" ISNULL OR "closed" ISNULL ),
jbe@3 616 CONSTRAINT "state_change_order" CHECK (
jbe@10 617 "created" <= "accepted" AND
jbe@10 618 "accepted" <= "half_frozen" AND
jbe@10 619 "half_frozen" <= "fully_frozen" AND
jbe@3 620 "fully_frozen" <= "closed" ),
jbe@61 621 CONSTRAINT "only_closed_issues_may_be_cleaned" CHECK (
jbe@61 622 "cleaned" ISNULL OR "closed" NOTNULL ),
jbe@10 623 CONSTRAINT "last_snapshot_on_full_freeze"
jbe@10 624 CHECK ("snapshot" = "fully_frozen"), -- NOTE: snapshot can be set, while frozen is NULL yet
jbe@10 625 CONSTRAINT "freeze_requires_snapshot"
jbe@10 626 CHECK ("fully_frozen" ISNULL OR "snapshot" NOTNULL),
jbe@10 627 CONSTRAINT "set_both_or_none_of_snapshot_and_latest_snapshot_event"
jbe@447 628 CHECK (("snapshot" NOTNULL) = ("latest_snapshot_event" NOTNULL)) );
jbe@0 629 CREATE INDEX "issue_area_id_idx" ON "issue" ("area_id");
jbe@0 630 CREATE INDEX "issue_policy_id_idx" ON "issue" ("policy_id");
jbe@16 631 CREATE INDEX "issue_created_idx" ON "issue" ("created");
jbe@16 632 CREATE INDEX "issue_accepted_idx" ON "issue" ("accepted");
jbe@16 633 CREATE INDEX "issue_half_frozen_idx" ON "issue" ("half_frozen");
jbe@16 634 CREATE INDEX "issue_fully_frozen_idx" ON "issue" ("fully_frozen");
jbe@16 635 CREATE INDEX "issue_closed_idx" ON "issue" ("closed");
jbe@0 636 CREATE INDEX "issue_created_idx_open" ON "issue" ("created") WHERE "closed" ISNULL;
jbe@16 637 CREATE INDEX "issue_closed_idx_canceled" ON "issue" ("closed") WHERE "fully_frozen" ISNULL;
jbe@0 638
jbe@0 639 COMMENT ON TABLE "issue" IS 'Groups of initiatives';
jbe@0 640
jbe@389 641 COMMENT ON COLUMN "issue"."admin_notice" IS 'Public notice by admin to explain manual interventions, or to announce corrections';
jbe@444 642 COMMENT ON COLUMN "issue"."external_reference" IS 'Opaque data field to store an external reference';
jbe@328 643 COMMENT ON COLUMN "issue"."phase_finished" IS 'Set to a value NOTNULL, if the current phase has finished, but calculations are pending; No changes in this issue shall be made by the frontend or API when this value is set';
jbe@170 644 COMMENT ON COLUMN "issue"."accepted" IS 'Point in time, when one initiative of issue reached the "issue_quorum"';
jbe@170 645 COMMENT ON COLUMN "issue"."half_frozen" IS 'Point in time, when "discussion_time" has elapsed; 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@170 646 COMMENT ON COLUMN "issue"."fully_frozen" IS 'Point in time, when "verification_time" has elapsed and voting has started; 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@447 647 COMMENT ON COLUMN "issue"."closed" IS 'Point in time, when "max_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@170 648 COMMENT ON COLUMN "issue"."cleaned" IS 'Point in time, when discussion data and votes had been deleted';
jbe@447 649 COMMENT ON COLUMN "issue"."min_admission_time" IS 'Copied from "policy" table at creation of issue';
jbe@447 650 COMMENT ON COLUMN "issue"."max_admission_time" IS 'Copied from "policy" table at creation of issue';
jbe@170 651 COMMENT ON COLUMN "issue"."discussion_time" IS 'Copied from "policy" table at creation of issue';
jbe@170 652 COMMENT ON COLUMN "issue"."verification_time" IS 'Copied from "policy" table at creation of issue';
jbe@170 653 COMMENT ON COLUMN "issue"."voting_time" IS 'Copied from "policy" table at creation of issue';
jbe@170 654 COMMENT ON COLUMN "issue"."snapshot" IS 'Point in time, when snapshot tables have been updated and "population" and *_count values were precalculated';
jbe@170 655 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@170 656 COMMENT ON COLUMN "issue"."population" IS 'Sum of "weight" column in table "direct_population_snapshot"';
jbe@170 657 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@170 658 COMMENT ON COLUMN "issue"."status_quo_schulze_rank" IS 'Schulze rank of status quo, as calculated by "calculate_ranks" function';
jbe@0 659
jbe@0 660
jbe@410 661 CREATE TABLE "issue_order_in_admission_state" (
jbe@400 662 "id" INT8 PRIMARY KEY, --REFERENCES "issue" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@410 663 "order_in_area" INT4,
jbe@410 664 "order_in_unit" INT4 );
jbe@410 665
jbe@410 666 COMMENT ON TABLE "issue_order_in_admission_state" IS 'Ordering information for issues that are not stored in the "issue" table to avoid locking of multiple issues at once; Filled/updated by "lf_update_issue_order"';
jbe@410 667
jbe@410 668 COMMENT ON COLUMN "issue_order_in_admission_state"."id" IS 'References "issue" ("id") but has no referential integrity trigger associated, due to performance/locking issues';
jbe@410 669 COMMENT ON COLUMN "issue_order_in_admission_state"."order_in_area" IS 'Order of issues in admission state within a single area; NULL values sort last';
jbe@410 670 COMMENT ON COLUMN "issue_order_in_admission_state"."order_in_unit" IS 'Order of issues in admission state within all areas of a unit; NULL values sort last';
jbe@0 671
jbe@0 672
jbe@23 673 CREATE TABLE "issue_setting" (
jbe@23 674 PRIMARY KEY ("member_id", "key", "issue_id"),
jbe@23 675 "member_id" INT4 REFERENCES "member" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@23 676 "key" TEXT NOT NULL,
jbe@23 677 "issue_id" INT4 REFERENCES "issue" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@23 678 "value" TEXT NOT NULL );
jbe@23 679
jbe@23 680 COMMENT ON TABLE "issue_setting" IS 'Place for frontend to store issue specific settings of members as strings';
jbe@23 681
jbe@23 682
jbe@0 683 CREATE TABLE "initiative" (
jbe@0 684 UNIQUE ("issue_id", "id"), -- index needed for foreign-key on table "vote"
jbe@0 685 "issue_id" INT4 NOT NULL REFERENCES "issue" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@0 686 "id" SERIAL4 PRIMARY KEY,
jbe@0 687 "name" TEXT NOT NULL,
jbe@261 688 "polling" BOOLEAN NOT NULL DEFAULT FALSE,
jbe@0 689 "created" TIMESTAMPTZ NOT NULL DEFAULT now(),
jbe@0 690 "revoked" TIMESTAMPTZ,
jbe@112 691 "revoked_by_member_id" INT4 REFERENCES "member" ("id") ON DELETE RESTRICT ON UPDATE CASCADE,
jbe@14 692 "suggested_initiative_id" INT4 REFERENCES "initiative" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@444 693 "external_reference" TEXT,
jbe@0 694 "admitted" BOOLEAN,
jbe@0 695 "supporter_count" INT4,
jbe@0 696 "informed_supporter_count" INT4,
jbe@0 697 "satisfied_supporter_count" INT4,
jbe@0 698 "satisfied_informed_supporter_count" INT4,
jbe@313 699 "harmonic_weight" NUMERIC(12, 3),
jbe@352 700 "final_suggestion_order_calculated" BOOLEAN NOT NULL DEFAULT FALSE,
jbe@414 701 "first_preference_votes" INT4,
jbe@0 702 "positive_votes" INT4,
jbe@0 703 "negative_votes" INT4,
jbe@167 704 "direct_majority" BOOLEAN,
jbe@167 705 "indirect_majority" BOOLEAN,
jbe@170 706 "schulze_rank" INT4,
jbe@167 707 "better_than_status_quo" BOOLEAN,
jbe@167 708 "worse_than_status_quo" BOOLEAN,
jbe@429 709 "reverse_beat_path" BOOLEAN,
jbe@154 710 "multistage_majority" BOOLEAN,
jbe@154 711 "eligible" BOOLEAN,
jbe@126 712 "winner" BOOLEAN,
jbe@0 713 "rank" INT4,
jbe@7 714 "text_search_data" TSVECTOR,
jbe@112 715 CONSTRAINT "all_or_none_of_revoked_and_revoked_by_member_id_must_be_null"
jbe@447 716 CHECK (("revoked" NOTNULL) = ("revoked_by_member_id" NOTNULL)),
jbe@14 717 CONSTRAINT "non_revoked_initiatives_cant_suggest_other"
jbe@14 718 CHECK ("revoked" NOTNULL OR "suggested_initiative_id" ISNULL),
jbe@0 719 CONSTRAINT "revoked_initiatives_cant_be_admitted"
jbe@0 720 CHECK ("revoked" ISNULL OR "admitted" ISNULL),
jbe@128 721 CONSTRAINT "non_admitted_initiatives_cant_contain_voting_results" CHECK (
jbe@128 722 ( "admitted" NOTNULL AND "admitted" = TRUE ) OR
jbe@414 723 ( "first_preference_votes" ISNULL AND
jbe@414 724 "positive_votes" ISNULL AND "negative_votes" ISNULL AND
jbe@167 725 "direct_majority" ISNULL AND "indirect_majority" ISNULL AND
jbe@173 726 "schulze_rank" ISNULL AND
jbe@167 727 "better_than_status_quo" ISNULL AND "worse_than_status_quo" ISNULL AND
jbe@429 728 "reverse_beat_path" ISNULL AND "multistage_majority" ISNULL AND
jbe@173 729 "eligible" ISNULL AND "winner" ISNULL AND "rank" ISNULL ) ),
jbe@173 730 CONSTRAINT "better_excludes_worse" CHECK (NOT ("better_than_status_quo" AND "worse_than_status_quo")),
jbe@175 731 CONSTRAINT "minimum_requirement_to_be_eligible" CHECK (
jbe@175 732 "eligible" = FALSE OR
jbe@175 733 ("direct_majority" AND "indirect_majority" AND "better_than_status_quo") ),
jbe@175 734 CONSTRAINT "winner_must_be_eligible" CHECK ("winner"=FALSE OR "eligible"=TRUE),
jbe@175 735 CONSTRAINT "winner_must_have_first_rank" CHECK ("winner"=FALSE OR "rank"=1),
jbe@176 736 CONSTRAINT "eligible_at_first_rank_is_winner" CHECK ("eligible"=FALSE OR "rank"!=1 OR "winner"=TRUE),
jbe@173 737 CONSTRAINT "unique_rank_per_issue" UNIQUE ("issue_id", "rank") );
jbe@16 738 CREATE INDEX "initiative_created_idx" ON "initiative" ("created");
jbe@16 739 CREATE INDEX "initiative_revoked_idx" ON "initiative" ("revoked");
jbe@8 740 CREATE INDEX "initiative_text_search_data_idx" ON "initiative" USING gin ("text_search_data");
jbe@7 741 CREATE TRIGGER "update_text_search_data"
jbe@7 742 BEFORE INSERT OR UPDATE ON "initiative"
jbe@7 743 FOR EACH ROW EXECUTE PROCEDURE
jbe@450 744 tsvector_update_trigger('text_search_data', 'pg_catalog.simple', "name");
jbe@0 745
jbe@10 746 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 747
jbe@289 748 COMMENT ON COLUMN "initiative"."polling" IS 'Initiative does not need to pass the initiative quorum (see "policy"."polling")';
jbe@210 749 COMMENT ON COLUMN "initiative"."revoked" IS 'Point in time, when one initiator decided to revoke the initiative';
jbe@210 750 COMMENT ON COLUMN "initiative"."revoked_by_member_id" IS 'Member, who decided to revoke the initiative';
jbe@444 751 COMMENT ON COLUMN "initiative"."external_reference" IS 'Opaque data field to store an external reference';
jbe@210 752 COMMENT ON COLUMN "initiative"."admitted" IS 'TRUE, if initiative reaches the "initiative_quorum" when freezing the issue';
jbe@0 753 COMMENT ON COLUMN "initiative"."supporter_count" IS 'Calculated from table "direct_supporter_snapshot"';
jbe@0 754 COMMENT ON COLUMN "initiative"."informed_supporter_count" IS 'Calculated from table "direct_supporter_snapshot"';
jbe@0 755 COMMENT ON COLUMN "initiative"."satisfied_supporter_count" IS 'Calculated from table "direct_supporter_snapshot"';
jbe@0 756 COMMENT ON COLUMN "initiative"."satisfied_informed_supporter_count" IS 'Calculated from table "direct_supporter_snapshot"';
jbe@320 757 COMMENT ON COLUMN "initiative"."harmonic_weight" IS 'Indicates the relevancy of the initiative, calculated from the potential supporters weighted with the harmonic series to avoid a large number of clones affecting other initiative''s sorting positions too much; shall be used as secondary sorting key after "admitted" as primary sorting key';
jbe@352 758 COMMENT ON COLUMN "initiative"."final_suggestion_order_calculated" IS 'Set to TRUE, when "proportional_order" of suggestions has been calculated the last time';
jbe@414 759 COMMENT ON COLUMN "initiative"."first_preference_votes" IS 'Number of direct and delegating voters who ranked this initiative as their first choice';
jbe@414 760 COMMENT ON COLUMN "initiative"."positive_votes" IS 'Number of direct and delegating voters who ranked this initiative better than the status quo';
jbe@414 761 COMMENT ON COLUMN "initiative"."negative_votes" IS 'Number of direct and delegating voters who ranked this initiative worse than the status quo';
jbe@210 762 COMMENT ON COLUMN "initiative"."direct_majority" IS 'TRUE, if "positive_votes"/("positive_votes"+"negative_votes") is strictly greater or greater-equal than "direct_majority_num"/"direct_majority_den", and "positive_votes" is greater-equal than "direct_majority_positive", and ("positive_votes"+abstentions) is greater-equal than "direct_majority_non_negative"';
jbe@210 763 COMMENT ON COLUMN "initiative"."indirect_majority" IS 'Same as "direct_majority", but also considering indirect beat paths';
jbe@411 764 COMMENT ON COLUMN "initiative"."schulze_rank" IS 'Schulze-Ranking';
jbe@411 765 COMMENT ON COLUMN "initiative"."better_than_status_quo" IS 'TRUE, if initiative has a schulze-ranking better than the status quo';
jbe@411 766 COMMENT ON COLUMN "initiative"."worse_than_status_quo" IS 'TRUE, if initiative has a schulze-ranking worse than the status quo (DEPRECATED, since schulze-ranking is unique per issue; use "better_than_status_quo"=FALSE)';
jbe@429 767 COMMENT ON COLUMN "initiative"."reverse_beat_path" IS 'TRUE, if there is a beat path (may include ties) from this initiative to the status quo; set to NULL if "policy"."defeat_strength" is set to ''simple''';
jbe@210 768 COMMENT ON COLUMN "initiative"."multistage_majority" IS 'TRUE, if either (a) this initiative has no better rank than the status quo, or (b) there exists a better ranked initiative X, which directly beats this initiative, and either more voters prefer X to this initiative than voters preferring X to the status quo or less voters prefer this initiative to X than voters preferring the status quo to X';
jbe@429 769 COMMENT ON COLUMN "initiative"."eligible" IS 'Initiative has a "direct_majority" and an "indirect_majority", is "better_than_status_quo" and depending on selected policy the initiative has no "reverse_beat_path" or "multistage_majority"';
jbe@411 770 COMMENT ON COLUMN "initiative"."winner" IS 'Winner is the "eligible" initiative with best "schulze_rank"';
jbe@210 771 COMMENT ON COLUMN "initiative"."rank" IS 'Unique ranking for all "admitted" initiatives per issue; lower rank is better; a winner always has rank 1, but rank 1 does not imply that an initiative is winner; initiatives with "direct_majority" AND "indirect_majority" always have a better (lower) rank than other initiatives';
jbe@0 772
jbe@0 773
jbe@61 774 CREATE TABLE "battle" (
jbe@126 775 "issue_id" INT4 NOT NULL,
jbe@61 776 "winning_initiative_id" INT4,
jbe@61 777 FOREIGN KEY ("issue_id", "winning_initiative_id") REFERENCES "initiative" ("issue_id", "id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@61 778 "losing_initiative_id" INT4,
jbe@61 779 FOREIGN KEY ("issue_id", "losing_initiative_id") REFERENCES "initiative" ("issue_id", "id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@126 780 "count" INT4 NOT NULL,
jbe@126 781 CONSTRAINT "initiative_ids_not_equal" CHECK (
jbe@126 782 "winning_initiative_id" != "losing_initiative_id" OR
jbe@126 783 ( ("winning_initiative_id" NOTNULL AND "losing_initiative_id" ISNULL) OR
jbe@126 784 ("winning_initiative_id" ISNULL AND "losing_initiative_id" NOTNULL) ) ) );
jbe@126 785 CREATE UNIQUE INDEX "battle_winning_losing_idx" ON "battle" ("issue_id", "winning_initiative_id", "losing_initiative_id");
jbe@126 786 CREATE UNIQUE INDEX "battle_winning_null_idx" ON "battle" ("issue_id", "winning_initiative_id") WHERE "losing_initiative_id" ISNULL;
jbe@126 787 CREATE UNIQUE INDEX "battle_null_losing_idx" ON "battle" ("issue_id", "losing_initiative_id") WHERE "winning_initiative_id" ISNULL;
jbe@126 788
jbe@126 789 COMMENT ON TABLE "battle" IS 'Number of members preferring one initiative to another; Filled by "battle_view" when closing an issue; NULL as initiative_id denotes virtual "status-quo" initiative';
jbe@61 790
jbe@61 791
jbe@113 792 CREATE TABLE "ignored_initiative" (
jbe@465 793 PRIMARY KEY ("member_id", "initiative_id"),
jbe@465 794 "member_id" INT4 REFERENCES "member" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@465 795 "initiative_id" INT4 REFERENCES "initiative" ("id") ON DELETE CASCADE ON UPDATE CASCADE );
jbe@465 796 CREATE INDEX "ignored_initiative_initiative_id_idx" ON "ignored_initiative" ("initiative_id");
jbe@113 797
jbe@113 798 COMMENT ON TABLE "ignored_initiative" IS 'Possibility to filter initiatives';
jbe@113 799
jbe@113 800
jbe@23 801 CREATE TABLE "initiative_setting" (
jbe@23 802 PRIMARY KEY ("member_id", "key", "initiative_id"),
jbe@23 803 "member_id" INT4 REFERENCES "member" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@23 804 "key" TEXT NOT NULL,
jbe@23 805 "initiative_id" INT4 REFERENCES "initiative" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@23 806 "value" TEXT NOT NULL );
jbe@23 807
jbe@23 808 COMMENT ON TABLE "initiative_setting" IS 'Place for frontend to store initiative specific settings of members as strings';
jbe@23 809
jbe@23 810
jbe@0 811 CREATE TABLE "draft" (
jbe@0 812 UNIQUE ("initiative_id", "id"), -- index needed for foreign-key on table "supporter"
jbe@0 813 "initiative_id" INT4 NOT NULL REFERENCES "initiative" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@0 814 "id" SERIAL8 PRIMARY KEY,
jbe@0 815 "created" TIMESTAMPTZ NOT NULL DEFAULT now(),
jbe@0 816 "author_id" INT4 NOT NULL REFERENCES "member" ("id") ON DELETE RESTRICT ON UPDATE CASCADE,
jbe@9 817 "formatting_engine" TEXT,
jbe@7 818 "content" TEXT NOT NULL,
jbe@444 819 "external_reference" TEXT,
jbe@7 820 "text_search_data" TSVECTOR );
jbe@16 821 CREATE INDEX "draft_created_idx" ON "draft" ("created");
jbe@9 822 CREATE INDEX "draft_author_id_created_idx" ON "draft" ("author_id", "created");
jbe@8 823 CREATE INDEX "draft_text_search_data_idx" ON "draft" USING gin ("text_search_data");
jbe@7 824 CREATE TRIGGER "update_text_search_data"
jbe@7 825 BEFORE INSERT OR UPDATE ON "draft"
jbe@7 826 FOR EACH ROW EXECUTE PROCEDURE
jbe@7 827 tsvector_update_trigger('text_search_data', 'pg_catalog.simple', "content");
jbe@0 828
jbe@10 829 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 830
jbe@444 831 COMMENT ON COLUMN "draft"."formatting_engine" IS 'Allows different formatting engines (i.e. wiki formats) to be used';
jbe@444 832 COMMENT ON COLUMN "draft"."content" IS 'Text of the draft in a format depending on the field "formatting_engine"';
jbe@444 833 COMMENT ON COLUMN "draft"."external_reference" IS 'Opaque data field to store an external reference';
jbe@9 834
jbe@0 835
jbe@63 836 CREATE TABLE "rendered_draft" (
jbe@63 837 PRIMARY KEY ("draft_id", "format"),
jbe@63 838 "draft_id" INT8 REFERENCES "draft" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@63 839 "format" TEXT,
jbe@63 840 "content" TEXT NOT NULL );
jbe@63 841
jbe@63 842 COMMENT ON TABLE "rendered_draft" IS 'This table may be used by frontends to cache "rendered" drafts (e.g. HTML output generated from wiki text)';
jbe@63 843
jbe@63 844
jbe@0 845 CREATE TABLE "suggestion" (
jbe@0 846 UNIQUE ("initiative_id", "id"), -- index needed for foreign-key on table "opinion"
jbe@0 847 "initiative_id" INT4 NOT NULL REFERENCES "initiative" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@0 848 "id" SERIAL8 PRIMARY KEY,
jbe@160 849 "draft_id" INT8 NOT NULL,
jbe@160 850 FOREIGN KEY ("initiative_id", "draft_id") REFERENCES "draft" ("initiative_id", "id") ON DELETE NO ACTION ON UPDATE CASCADE,
jbe@0 851 "created" TIMESTAMPTZ NOT NULL DEFAULT now(),
jbe@0 852 "author_id" INT4 NOT NULL REFERENCES "member" ("id") ON DELETE RESTRICT ON UPDATE CASCADE,
jbe@0 853 "name" TEXT NOT NULL,
jbe@159 854 "formatting_engine" TEXT,
jbe@159 855 "content" TEXT NOT NULL DEFAULT '',
jbe@444 856 "external_reference" TEXT,
jbe@7 857 "text_search_data" TSVECTOR,
jbe@0 858 "minus2_unfulfilled_count" INT4,
jbe@0 859 "minus2_fulfilled_count" INT4,
jbe@0 860 "minus1_unfulfilled_count" INT4,
jbe@0 861 "minus1_fulfilled_count" INT4,
jbe@0 862 "plus1_unfulfilled_count" INT4,
jbe@0 863 "plus1_fulfilled_count" INT4,
jbe@0 864 "plus2_unfulfilled_count" INT4,
jbe@352 865 "plus2_fulfilled_count" INT4,
jbe@352 866 "proportional_order" INT4 );
jbe@16 867 CREATE INDEX "suggestion_created_idx" ON "suggestion" ("created");
jbe@9 868 CREATE INDEX "suggestion_author_id_created_idx" ON "suggestion" ("author_id", "created");
jbe@8 869 CREATE INDEX "suggestion_text_search_data_idx" ON "suggestion" USING gin ("text_search_data");
jbe@7 870 CREATE TRIGGER "update_text_search_data"
jbe@7 871 BEFORE INSERT OR UPDATE ON "suggestion"
jbe@7 872 FOR EACH ROW EXECUTE PROCEDURE
jbe@7 873 tsvector_update_trigger('text_search_data', 'pg_catalog.simple',
jbe@159 874 "name", "content");
jbe@0 875
jbe@10 876 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 877
jbe@160 878 COMMENT ON COLUMN "suggestion"."draft_id" IS 'Draft, which the author has seen when composing the suggestion; should always be set by a frontend, but defaults to current draft of the initiative (implemented by trigger "default_for_draft_id")';
jbe@444 879 COMMENT ON COLUMN "suggestion"."external_reference" IS 'Opaque data field to store an external reference';
jbe@0 880 COMMENT ON COLUMN "suggestion"."minus2_unfulfilled_count" IS 'Calculated from table "direct_supporter_snapshot", not requiring informed supporters';
jbe@0 881 COMMENT ON COLUMN "suggestion"."minus2_fulfilled_count" IS 'Calculated from table "direct_supporter_snapshot", not requiring informed supporters';
jbe@0 882 COMMENT ON COLUMN "suggestion"."minus1_unfulfilled_count" IS 'Calculated from table "direct_supporter_snapshot", not requiring informed supporters';
jbe@0 883 COMMENT ON COLUMN "suggestion"."minus1_fulfilled_count" IS 'Calculated from table "direct_supporter_snapshot", not requiring informed supporters';
jbe@0 884 COMMENT ON COLUMN "suggestion"."plus1_unfulfilled_count" IS 'Calculated from table "direct_supporter_snapshot", not requiring informed supporters';
jbe@0 885 COMMENT ON COLUMN "suggestion"."plus1_fulfilled_count" IS 'Calculated from table "direct_supporter_snapshot", not requiring informed supporters';
jbe@0 886 COMMENT ON COLUMN "suggestion"."plus2_unfulfilled_count" IS 'Calculated from table "direct_supporter_snapshot", not requiring informed supporters';
jbe@0 887 COMMENT ON COLUMN "suggestion"."plus2_fulfilled_count" IS 'Calculated from table "direct_supporter_snapshot", not requiring informed supporters';
jbe@378 888 COMMENT ON COLUMN "suggestion"."proportional_order" IS 'To be used for sorting suggestions within an initiative; NULL values sort last; updated by "lf_update_suggestion_order"';
jbe@0 889
jbe@0 890
jbe@159 891 CREATE TABLE "rendered_suggestion" (
jbe@159 892 PRIMARY KEY ("suggestion_id", "format"),
jbe@159 893 "suggestion_id" INT8 REFERENCES "suggestion" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@159 894 "format" TEXT,
jbe@159 895 "content" TEXT NOT NULL );
jbe@159 896
jbe@159 897 COMMENT ON TABLE "rendered_suggestion" IS 'This table may be used by frontends to cache "rendered" drafts (e.g. HTML output generated from wiki text)';
jbe@159 898
jbe@159 899
jbe@23 900 CREATE TABLE "suggestion_setting" (
jbe@23 901 PRIMARY KEY ("member_id", "key", "suggestion_id"),
jbe@23 902 "member_id" INT4 REFERENCES "member" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@23 903 "key" TEXT NOT NULL,
jbe@23 904 "suggestion_id" INT8 REFERENCES "suggestion" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@23 905 "value" TEXT NOT NULL );
jbe@23 906
jbe@23 907 COMMENT ON TABLE "suggestion_setting" IS 'Place for frontend to store suggestion specific settings of members as strings';
jbe@23 908
jbe@23 909
jbe@97 910 CREATE TABLE "privilege" (
jbe@97 911 PRIMARY KEY ("unit_id", "member_id"),
jbe@97 912 "unit_id" INT4 REFERENCES "unit" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@97 913 "member_id" INT4 REFERENCES "member" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@97 914 "admin_manager" BOOLEAN NOT NULL DEFAULT FALSE,
jbe@97 915 "unit_manager" BOOLEAN NOT NULL DEFAULT FALSE,
jbe@97 916 "area_manager" BOOLEAN NOT NULL DEFAULT FALSE,
jbe@261 917 "member_manager" BOOLEAN NOT NULL DEFAULT FALSE,
jbe@261 918 "initiative_right" BOOLEAN NOT NULL DEFAULT TRUE,
jbe@261 919 "voting_right" BOOLEAN NOT NULL DEFAULT TRUE,
jbe@261 920 "polling_right" BOOLEAN NOT NULL DEFAULT FALSE );
jbe@97 921
jbe@97 922 COMMENT ON TABLE "privilege" IS 'Members rights related to each unit';
jbe@97 923
jbe@289 924 COMMENT ON COLUMN "privilege"."admin_manager" IS 'Grant/revoke any privileges to/from other members';
jbe@289 925 COMMENT ON COLUMN "privilege"."unit_manager" IS 'Create and disable sub units';
jbe@289 926 COMMENT ON COLUMN "privilege"."area_manager" IS 'Create and disable areas and set area parameters';
jbe@289 927 COMMENT ON COLUMN "privilege"."member_manager" IS 'Adding/removing members from the unit, granting or revoking "initiative_right" and "voting_right"';
jbe@289 928 COMMENT ON COLUMN "privilege"."initiative_right" IS 'Right to create an initiative';
jbe@289 929 COMMENT ON COLUMN "privilege"."voting_right" IS 'Right to support initiatives, create and rate suggestions, and to vote';
jbe@289 930 COMMENT ON COLUMN "privilege"."polling_right" IS 'Right to create issues with policies having the "policy"."polling" flag set, and to add initiatives having the "initiative"."polling" flag set to those issues';
jbe@97 931
jbe@97 932
jbe@0 933 CREATE TABLE "membership" (
jbe@0 934 PRIMARY KEY ("area_id", "member_id"),
jbe@0 935 "area_id" INT4 REFERENCES "area" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@169 936 "member_id" INT4 REFERENCES "member" ("id") ON DELETE CASCADE ON UPDATE CASCADE );
jbe@0 937 CREATE INDEX "membership_member_id_idx" ON "membership" ("member_id");
jbe@0 938
jbe@0 939 COMMENT ON TABLE "membership" IS 'Interest of members in topic areas';
jbe@0 940
jbe@0 941
jbe@0 942 CREATE TABLE "interest" (
jbe@0 943 PRIMARY KEY ("issue_id", "member_id"),
jbe@0 944 "issue_id" INT4 REFERENCES "issue" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@148 945 "member_id" INT4 REFERENCES "member" ("id") ON DELETE CASCADE ON UPDATE CASCADE );
jbe@0 946 CREATE INDEX "interest_member_id_idx" ON "interest" ("member_id");
jbe@0 947
jbe@10 948 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 949
jbe@0 950
jbe@0 951 CREATE TABLE "initiator" (
jbe@0 952 PRIMARY KEY ("initiative_id", "member_id"),
jbe@0 953 "initiative_id" INT4 REFERENCES "initiative" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@0 954 "member_id" INT4 REFERENCES "member" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@14 955 "accepted" BOOLEAN );
jbe@0 956 CREATE INDEX "initiator_member_id_idx" ON "initiator" ("member_id");
jbe@0 957
jbe@10 958 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 959
jbe@14 960 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 961
jbe@0 962
jbe@0 963 CREATE TABLE "supporter" (
jbe@0 964 "issue_id" INT4 NOT NULL,
jbe@0 965 PRIMARY KEY ("initiative_id", "member_id"),
jbe@0 966 "initiative_id" INT4,
jbe@0 967 "member_id" INT4,
jbe@0 968 "draft_id" INT8 NOT NULL,
jbe@10 969 FOREIGN KEY ("issue_id", "member_id") REFERENCES "interest" ("issue_id", "member_id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@160 970 FOREIGN KEY ("initiative_id", "draft_id") REFERENCES "draft" ("initiative_id", "id") ON DELETE NO ACTION ON UPDATE CASCADE );
jbe@0 971 CREATE INDEX "supporter_member_id_idx" ON "supporter" ("member_id");
jbe@0 972
jbe@10 973 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 974
jbe@207 975 COMMENT ON COLUMN "supporter"."issue_id" IS 'WARNING: No index: For selections use column "initiative_id" and join via table "initiative" where neccessary';
jbe@160 976 COMMENT ON COLUMN "supporter"."draft_id" IS 'Latest seen draft; should always be set by a frontend, but defaults to current draft of the initiative (implemented by trigger "default_for_draft_id")';
jbe@84 977
jbe@0 978
jbe@0 979 CREATE TABLE "opinion" (
jbe@0 980 "initiative_id" INT4 NOT NULL,
jbe@0 981 PRIMARY KEY ("suggestion_id", "member_id"),
jbe@0 982 "suggestion_id" INT8,
jbe@0 983 "member_id" INT4,
jbe@0 984 "degree" INT2 NOT NULL CHECK ("degree" >= -2 AND "degree" <= 2 AND "degree" != 0),
jbe@0 985 "fulfilled" BOOLEAN NOT NULL DEFAULT FALSE,
jbe@42 986 FOREIGN KEY ("initiative_id", "suggestion_id") REFERENCES "suggestion" ("initiative_id", "id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@0 987 FOREIGN KEY ("initiative_id", "member_id") REFERENCES "supporter" ("initiative_id", "member_id") ON DELETE CASCADE ON UPDATE CASCADE );
jbe@10 988 CREATE INDEX "opinion_member_id_initiative_id_idx" ON "opinion" ("member_id", "initiative_id");
jbe@0 989
jbe@10 990 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 991
jbe@0 992 COMMENT ON COLUMN "opinion"."degree" IS '2 = fulfillment required for support; 1 = fulfillment desired; -1 = fulfillment unwanted; -2 = fulfillment cancels support';
jbe@0 993
jbe@0 994
jbe@97 995 CREATE TYPE "delegation_scope" AS ENUM ('unit', 'area', 'issue');
jbe@97 996
jbe@97 997 COMMENT ON TYPE "delegation_scope" IS 'Scope for delegations: ''unit'', ''area'', or ''issue'' (order is relevant)';
jbe@10 998
jbe@10 999
jbe@0 1000 CREATE TABLE "delegation" (
jbe@0 1001 "id" SERIAL8 PRIMARY KEY,
jbe@0 1002 "truster_id" INT4 NOT NULL REFERENCES "member" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@86 1003 "trustee_id" INT4 REFERENCES "member" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@10 1004 "scope" "delegation_scope" NOT NULL,
jbe@97 1005 "unit_id" INT4 REFERENCES "unit" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@0 1006 "area_id" INT4 REFERENCES "area" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@0 1007 "issue_id" INT4 REFERENCES "issue" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@0 1008 CONSTRAINT "cant_delegate_to_yourself" CHECK ("truster_id" != "trustee_id"),
jbe@97 1009 CONSTRAINT "no_unit_delegation_to_null"
jbe@97 1010 CHECK ("trustee_id" NOTNULL OR "scope" != 'unit'),
jbe@10 1011 CONSTRAINT "area_id_and_issue_id_set_according_to_scope" CHECK (
jbe@97 1012 ("scope" = 'unit' AND "unit_id" NOTNULL AND "area_id" ISNULL AND "issue_id" ISNULL ) OR
jbe@97 1013 ("scope" = 'area' AND "unit_id" ISNULL AND "area_id" NOTNULL AND "issue_id" ISNULL ) OR
jbe@97 1014 ("scope" = 'issue' AND "unit_id" ISNULL AND "area_id" ISNULL AND "issue_id" NOTNULL) ),
jbe@97 1015 UNIQUE ("unit_id", "truster_id"),
jbe@74 1016 UNIQUE ("area_id", "truster_id"),
jbe@74 1017 UNIQUE ("issue_id", "truster_id") );
jbe@0 1018 CREATE INDEX "delegation_truster_id_idx" ON "delegation" ("truster_id");
jbe@0 1019 CREATE INDEX "delegation_trustee_id_idx" ON "delegation" ("trustee_id");
jbe@0 1020
jbe@0 1021 COMMENT ON TABLE "delegation" IS 'Delegation of vote-weight to other members';
jbe@0 1022
jbe@97 1023 COMMENT ON COLUMN "delegation"."unit_id" IS 'Reference to unit, if delegation is unit-wide, otherwise NULL';
jbe@0 1024 COMMENT ON COLUMN "delegation"."area_id" IS 'Reference to area, if delegation is area-wide, otherwise NULL';
jbe@0 1025 COMMENT ON COLUMN "delegation"."issue_id" IS 'Reference to issue, if delegation is issue-wide, otherwise NULL';
jbe@0 1026
jbe@0 1027
jbe@0 1028 CREATE TABLE "direct_population_snapshot" (
jbe@0 1029 PRIMARY KEY ("issue_id", "event", "member_id"),
jbe@0 1030 "issue_id" INT4 REFERENCES "issue" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@0 1031 "event" "snapshot_event",
jbe@45 1032 "member_id" INT4 REFERENCES "member" ("id") ON DELETE RESTRICT ON UPDATE RESTRICT,
jbe@54 1033 "weight" INT4 );
jbe@0 1034 CREATE INDEX "direct_population_snapshot_member_id_idx" ON "direct_population_snapshot" ("member_id");
jbe@0 1035
jbe@389 1036 COMMENT ON TABLE "direct_population_snapshot" IS 'Snapshot of active members having either a "membership" in the "area" or an "interest" in the "issue"; for corrections refer to column "issue_notice" of "issue" table';
jbe@0 1037
jbe@148 1038 COMMENT ON COLUMN "direct_population_snapshot"."event" IS 'Reason for snapshot, see "snapshot_event" type for details';
jbe@148 1039 COMMENT ON COLUMN "direct_population_snapshot"."weight" IS 'Weight of member (1 or higher) according to "delegating_population_snapshot"';
jbe@0 1040
jbe@0 1041
jbe@0 1042 CREATE TABLE "delegating_population_snapshot" (
jbe@0 1043 PRIMARY KEY ("issue_id", "event", "member_id"),
jbe@0 1044 "issue_id" INT4 REFERENCES "issue" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@0 1045 "event" "snapshot_event",
jbe@45 1046 "member_id" INT4 REFERENCES "member" ("id") ON DELETE RESTRICT ON UPDATE RESTRICT,
jbe@8 1047 "weight" INT4,
jbe@10 1048 "scope" "delegation_scope" NOT NULL,
jbe@0 1049 "delegate_member_ids" INT4[] NOT NULL );
jbe@0 1050 CREATE INDEX "delegating_population_snapshot_member_id_idx" ON "delegating_population_snapshot" ("member_id");
jbe@0 1051
jbe@389 1052 COMMENT ON TABLE "direct_population_snapshot" IS 'Delegations increasing the weight of entries in the "direct_population_snapshot" table; for corrections refer to column "issue_notice" of "issue" table';
jbe@0 1053
jbe@0 1054 COMMENT ON COLUMN "delegating_population_snapshot"."event" IS 'Reason for snapshot, see "snapshot_event" type for details';
jbe@0 1055 COMMENT ON COLUMN "delegating_population_snapshot"."member_id" IS 'Delegating member';
jbe@8 1056 COMMENT ON COLUMN "delegating_population_snapshot"."weight" IS 'Intermediate weight';
jbe@0 1057 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 1058
jbe@0 1059
jbe@0 1060 CREATE TABLE "direct_interest_snapshot" (
jbe@0 1061 PRIMARY KEY ("issue_id", "event", "member_id"),
jbe@0 1062 "issue_id" INT4 REFERENCES "issue" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@0 1063 "event" "snapshot_event",
jbe@45 1064 "member_id" INT4 REFERENCES "member" ("id") ON DELETE RESTRICT ON UPDATE RESTRICT,
jbe@144 1065 "weight" INT4 );
jbe@0 1066 CREATE INDEX "direct_interest_snapshot_member_id_idx" ON "direct_interest_snapshot" ("member_id");
jbe@0 1067
jbe@389 1068 COMMENT ON TABLE "direct_interest_snapshot" IS 'Snapshot of active members having an "interest" in the "issue"; for corrections refer to column "issue_notice" of "issue" table';
jbe@0 1069
jbe@0 1070 COMMENT ON COLUMN "direct_interest_snapshot"."event" IS 'Reason for snapshot, see "snapshot_event" type for details';
jbe@0 1071 COMMENT ON COLUMN "direct_interest_snapshot"."weight" IS 'Weight of member (1 or higher) according to "delegating_interest_snapshot"';
jbe@0 1072
jbe@0 1073
jbe@0 1074 CREATE TABLE "delegating_interest_snapshot" (
jbe@0 1075 PRIMARY KEY ("issue_id", "event", "member_id"),
jbe@0 1076 "issue_id" INT4 REFERENCES "issue" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@0 1077 "event" "snapshot_event",
jbe@45 1078 "member_id" INT4 REFERENCES "member" ("id") ON DELETE RESTRICT ON UPDATE RESTRICT,
jbe@8 1079 "weight" INT4,
jbe@10 1080 "scope" "delegation_scope" NOT NULL,
jbe@0 1081 "delegate_member_ids" INT4[] NOT NULL );
jbe@0 1082 CREATE INDEX "delegating_interest_snapshot_member_id_idx" ON "delegating_interest_snapshot" ("member_id");
jbe@0 1083
jbe@389 1084 COMMENT ON TABLE "delegating_interest_snapshot" IS 'Delegations increasing the weight of entries in the "direct_interest_snapshot" table; for corrections refer to column "issue_notice" of "issue" table';
jbe@0 1085
jbe@0 1086 COMMENT ON COLUMN "delegating_interest_snapshot"."event" IS 'Reason for snapshot, see "snapshot_event" type for details';
jbe@0 1087 COMMENT ON COLUMN "delegating_interest_snapshot"."member_id" IS 'Delegating member';
jbe@8 1088 COMMENT ON COLUMN "delegating_interest_snapshot"."weight" IS 'Intermediate weight';
jbe@0 1089 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 1090
jbe@0 1091
jbe@0 1092 CREATE TABLE "direct_supporter_snapshot" (
jbe@0 1093 "issue_id" INT4 NOT NULL,
jbe@0 1094 PRIMARY KEY ("initiative_id", "event", "member_id"),
jbe@0 1095 "initiative_id" INT4,
jbe@0 1096 "event" "snapshot_event",
jbe@45 1097 "member_id" INT4 REFERENCES "member" ("id") ON DELETE RESTRICT ON UPDATE RESTRICT,
jbe@204 1098 "draft_id" INT8 NOT NULL,
jbe@0 1099 "informed" BOOLEAN NOT NULL,
jbe@0 1100 "satisfied" BOOLEAN NOT NULL,
jbe@0 1101 FOREIGN KEY ("issue_id", "initiative_id") REFERENCES "initiative" ("issue_id", "id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@204 1102 FOREIGN KEY ("initiative_id", "draft_id") REFERENCES "draft" ("initiative_id", "id") ON DELETE NO ACTION ON UPDATE CASCADE,
jbe@0 1103 FOREIGN KEY ("issue_id", "event", "member_id") REFERENCES "direct_interest_snapshot" ("issue_id", "event", "member_id") ON DELETE CASCADE ON UPDATE CASCADE );
jbe@0 1104 CREATE INDEX "direct_supporter_snapshot_member_id_idx" ON "direct_supporter_snapshot" ("member_id");
jbe@0 1105
jbe@389 1106 COMMENT ON TABLE "direct_supporter_snapshot" IS 'Snapshot of supporters of initiatives (weight is stored in "direct_interest_snapshot"); for corrections refer to column "issue_notice" of "issue" table';
jbe@0 1107
jbe@207 1108 COMMENT ON COLUMN "direct_supporter_snapshot"."issue_id" IS 'WARNING: No index: For selections use column "initiative_id" and join via table "initiative" where neccessary';
jbe@0 1109 COMMENT ON COLUMN "direct_supporter_snapshot"."event" IS 'Reason for snapshot, see "snapshot_event" type for details';
jbe@0 1110 COMMENT ON COLUMN "direct_supporter_snapshot"."informed" IS 'Supporter has seen the latest draft of the initiative';
jbe@0 1111 COMMENT ON COLUMN "direct_supporter_snapshot"."satisfied" IS 'Supporter has no "critical_opinion"s';
jbe@0 1112
jbe@0 1113
jbe@113 1114 CREATE TABLE "non_voter" (
jbe@113 1115 PRIMARY KEY ("issue_id", "member_id"),
jbe@113 1116 "issue_id" INT4 REFERENCES "issue" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@113 1117 "member_id" INT4 REFERENCES "member" ("id") ON DELETE CASCADE ON UPDATE CASCADE );
jbe@113 1118 CREATE INDEX "non_voter_member_id_idx" ON "non_voter" ("member_id");
jbe@113 1119
jbe@113 1120 COMMENT ON TABLE "non_voter" IS 'Members who decided to not vote directly on an issue';
jbe@113 1121
jbe@113 1122
jbe@0 1123 CREATE TABLE "direct_voter" (
jbe@0 1124 PRIMARY KEY ("issue_id", "member_id"),
jbe@0 1125 "issue_id" INT4 REFERENCES "issue" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@45 1126 "member_id" INT4 REFERENCES "member" ("id") ON DELETE RESTRICT ON UPDATE RESTRICT,
jbe@285 1127 "weight" INT4,
jbe@285 1128 "comment_changed" TIMESTAMPTZ,
jbe@285 1129 "formatting_engine" TEXT,
jbe@285 1130 "comment" TEXT,
jbe@285 1131 "text_search_data" TSVECTOR );
jbe@0 1132 CREATE INDEX "direct_voter_member_id_idx" ON "direct_voter" ("member_id");
jbe@285 1133 CREATE INDEX "direct_voter_text_search_data_idx" ON "direct_voter" USING gin ("text_search_data");
jbe@285 1134 CREATE TRIGGER "update_text_search_data"
jbe@285 1135 BEFORE INSERT OR UPDATE ON "direct_voter"
jbe@285 1136 FOR EACH ROW EXECUTE PROCEDURE
jbe@285 1137 tsvector_update_trigger('text_search_data', 'pg_catalog.simple', "comment");
jbe@0 1138
jbe@389 1139 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; for corrections refer to column "issue_notice" of "issue" table';
jbe@0 1140
jbe@285 1141 COMMENT ON COLUMN "direct_voter"."weight" IS 'Weight of member (1 or higher) according to "delegating_voter" table';
jbe@285 1142 COMMENT ON COLUMN "direct_voter"."comment_changed" IS 'Shall be set on comment change, to indicate a comment being modified after voting has been finished; Automatically set to NULL after voting phase; Automatically set to NULL by trigger, if "comment" is set to NULL';
jbe@285 1143 COMMENT ON COLUMN "direct_voter"."formatting_engine" IS 'Allows different formatting engines (i.e. wiki formats) to be used for "direct_voter"."comment"; Automatically set to NULL by trigger, if "comment" is set to NULL';
jbe@285 1144 COMMENT ON COLUMN "direct_voter"."comment" IS 'Is to be set or updated by the frontend, if comment was inserted or updated AFTER the issue has been closed. Otherwise it shall be set to NULL.';
jbe@285 1145
jbe@285 1146
jbe@285 1147 CREATE TABLE "rendered_voter_comment" (
jbe@285 1148 PRIMARY KEY ("issue_id", "member_id", "format"),
jbe@285 1149 FOREIGN KEY ("issue_id", "member_id")
jbe@285 1150 REFERENCES "direct_voter" ("issue_id", "member_id")
jbe@285 1151 ON DELETE CASCADE ON UPDATE CASCADE,
jbe@285 1152 "issue_id" INT4,
jbe@285 1153 "member_id" INT4,
jbe@285 1154 "format" TEXT,
jbe@285 1155 "content" TEXT NOT NULL );
jbe@285 1156
jbe@285 1157 COMMENT ON TABLE "rendered_voter_comment" IS 'This table may be used by frontends to cache "rendered" voter comments (e.g. HTML output generated from wiki text)';
jbe@0 1158
jbe@0 1159
jbe@0 1160 CREATE TABLE "delegating_voter" (
jbe@0 1161 PRIMARY KEY ("issue_id", "member_id"),
jbe@0 1162 "issue_id" INT4 REFERENCES "issue" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@45 1163 "member_id" INT4 REFERENCES "member" ("id") ON DELETE RESTRICT ON UPDATE RESTRICT,
jbe@8 1164 "weight" INT4,
jbe@10 1165 "scope" "delegation_scope" NOT NULL,
jbe@0 1166 "delegate_member_ids" INT4[] NOT NULL );
jbe@52 1167 CREATE INDEX "delegating_voter_member_id_idx" ON "delegating_voter" ("member_id");
jbe@0 1168
jbe@389 1169 COMMENT ON TABLE "delegating_voter" IS 'Delegations increasing the weight of entries in the "direct_voter" table; for corrections refer to column "issue_notice" of "issue" table';
jbe@0 1170
jbe@0 1171 COMMENT ON COLUMN "delegating_voter"."member_id" IS 'Delegating member';
jbe@8 1172 COMMENT ON COLUMN "delegating_voter"."weight" IS 'Intermediate weight';
jbe@0 1173 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 1174
jbe@0 1175
jbe@0 1176 CREATE TABLE "vote" (
jbe@0 1177 "issue_id" INT4 NOT NULL,
jbe@0 1178 PRIMARY KEY ("initiative_id", "member_id"),
jbe@0 1179 "initiative_id" INT4,
jbe@0 1180 "member_id" INT4,
jbe@414 1181 "grade" INT4 NOT NULL,
jbe@414 1182 "first_preference" BOOLEAN,
jbe@0 1183 FOREIGN KEY ("issue_id", "initiative_id") REFERENCES "initiative" ("issue_id", "id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@414 1184 FOREIGN KEY ("issue_id", "member_id") REFERENCES "direct_voter" ("issue_id", "member_id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@414 1185 CONSTRAINT "first_preference_flag_only_set_on_positive_grades"
jbe@414 1186 CHECK ("grade" > 0 OR "first_preference" ISNULL) );
jbe@0 1187 CREATE INDEX "vote_member_id_idx" ON "vote" ("member_id");
jbe@0 1188
jbe@389 1189 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; for corrections refer to column "issue_notice" of "issue" table';
jbe@0 1190
jbe@414 1191 COMMENT ON COLUMN "vote"."issue_id" IS 'WARNING: No index: For selections use column "initiative_id" and join via table "initiative" where neccessary';
jbe@414 1192 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@414 1193 COMMENT ON COLUMN "vote"."first_preference" IS 'Value is automatically set after voting is finished. For positive grades, this value is set to true for the highest (i.e. best) grade.';
jbe@0 1194
jbe@0 1195
jbe@112 1196 CREATE TYPE "event_type" AS ENUM (
jbe@112 1197 'issue_state_changed',
jbe@112 1198 'initiative_created_in_new_issue',
jbe@112 1199 'initiative_created_in_existing_issue',
jbe@112 1200 'initiative_revoked',
jbe@112 1201 'new_draft_created',
jbe@112 1202 'suggestion_created');
jbe@112 1203
jbe@112 1204 COMMENT ON TYPE "event_type" IS 'Type used for column "event" of table "event"';
jbe@112 1205
jbe@112 1206
jbe@112 1207 CREATE TABLE "event" (
jbe@112 1208 "id" SERIAL8 PRIMARY KEY,
jbe@112 1209 "occurrence" TIMESTAMPTZ NOT NULL DEFAULT now(),
jbe@112 1210 "event" "event_type" NOT NULL,
jbe@112 1211 "member_id" INT4 REFERENCES "member" ("id") ON DELETE RESTRICT ON UPDATE CASCADE,
jbe@112 1212 "issue_id" INT4 REFERENCES "issue" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@328 1213 "state" "issue_state",
jbe@112 1214 "initiative_id" INT4,
jbe@112 1215 "draft_id" INT8,
jbe@112 1216 "suggestion_id" INT8,
jbe@112 1217 FOREIGN KEY ("issue_id", "initiative_id")
jbe@112 1218 REFERENCES "initiative" ("issue_id", "id")
jbe@112 1219 ON DELETE CASCADE ON UPDATE CASCADE,
jbe@112 1220 FOREIGN KEY ("initiative_id", "draft_id")
jbe@112 1221 REFERENCES "draft" ("initiative_id", "id")
jbe@112 1222 ON DELETE CASCADE ON UPDATE CASCADE,
jbe@112 1223 FOREIGN KEY ("initiative_id", "suggestion_id")
jbe@112 1224 REFERENCES "suggestion" ("initiative_id", "id")
jbe@112 1225 ON DELETE CASCADE ON UPDATE CASCADE,
jbe@451 1226 CONSTRAINT "null_constr_for_issue_state_changed" CHECK (
jbe@112 1227 "event" != 'issue_state_changed' OR (
jbe@112 1228 "member_id" ISNULL AND
jbe@112 1229 "issue_id" NOTNULL AND
jbe@113 1230 "state" NOTNULL AND
jbe@112 1231 "initiative_id" ISNULL AND
jbe@112 1232 "draft_id" ISNULL AND
jbe@112 1233 "suggestion_id" ISNULL )),
jbe@451 1234 CONSTRAINT "null_constr_for_initiative_creation_or_revocation_or_new_draft" CHECK (
jbe@112 1235 "event" NOT IN (
jbe@112 1236 'initiative_created_in_new_issue',
jbe@112 1237 'initiative_created_in_existing_issue',
jbe@112 1238 'initiative_revoked',
jbe@112 1239 'new_draft_created'
jbe@112 1240 ) OR (
jbe@112 1241 "member_id" NOTNULL AND
jbe@112 1242 "issue_id" NOTNULL AND
jbe@113 1243 "state" NOTNULL AND
jbe@112 1244 "initiative_id" NOTNULL AND
jbe@112 1245 "draft_id" NOTNULL AND
jbe@112 1246 "suggestion_id" ISNULL )),
jbe@451 1247 CONSTRAINT "null_constr_for_suggestion_creation" CHECK (
jbe@112 1248 "event" != 'suggestion_created' OR (
jbe@112 1249 "member_id" NOTNULL AND
jbe@112 1250 "issue_id" NOTNULL AND
jbe@113 1251 "state" NOTNULL AND
jbe@112 1252 "initiative_id" NOTNULL AND
jbe@112 1253 "draft_id" ISNULL AND
jbe@112 1254 "suggestion_id" NOTNULL )) );
jbe@223 1255 CREATE INDEX "event_occurrence_idx" ON "event" ("occurrence");
jbe@112 1256
jbe@112 1257 COMMENT ON TABLE "event" IS 'Event table, automatically filled by triggers';
jbe@112 1258
jbe@114 1259 COMMENT ON COLUMN "event"."occurrence" IS 'Point in time, when event occurred';
jbe@114 1260 COMMENT ON COLUMN "event"."event" IS 'Type of event (see TYPE "event_type")';
jbe@114 1261 COMMENT ON COLUMN "event"."member_id" IS 'Member who caused the event, if applicable';
jbe@114 1262 COMMENT ON COLUMN "event"."state" IS 'If issue_id is set: state of affected issue; If state changed: new state';
jbe@114 1263
jbe@112 1264
jbe@222 1265 CREATE TABLE "notification_sent" (
jbe@222 1266 "event_id" INT8 NOT NULL );
jbe@222 1267 CREATE UNIQUE INDEX "notification_sent_singleton_idx" ON "notification_sent" ((1));
jbe@222 1268
jbe@222 1269 COMMENT ON TABLE "notification_sent" IS 'This table stores one row with the last event_id, for which notifications have been sent out';
jbe@222 1270 COMMENT ON INDEX "notification_sent_singleton_idx" IS 'This index ensures that "notification_sent" only contains one row maximum.';
jbe@222 1271
jbe@222 1272
jbe@486 1273 CREATE TABLE "initiative_notification_sent" (
jbe@486 1274 PRIMARY KEY ("member_id", "initiative_id"),
jbe@486 1275 "member_id" INT4 REFERENCES "member" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@486 1276 "initiative_id" INT4 REFERENCES "initiative" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@486 1277 "last_draft_id" INT8 NOT NULL,
jbe@495 1278 "last_suggestion_id" INT8 );
jbe@486 1279 CREATE INDEX "initiative_notification_sent_initiative_idx" ON "initiative_notification_sent" ("initiative_id");
jbe@486 1280
jbe@486 1281
jbe@496 1282 CREATE TABLE "newsletter" (
jbe@496 1283 "id" SERIAL4 PRIMARY KEY,
jbe@496 1284 "published" TIMESTAMPTZ NOT NULL,
jbe@496 1285 "unit_id" INT4 REFERENCES "unit" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@496 1286 "include_all_members" BOOLEAN NOT NULL,
jbe@496 1287 "sent" TIMESTAMPTZ,
jbe@496 1288 "subject" TEXT NOT NULL,
jbe@496 1289 "content" TEXT NOT NULL );
jbe@496 1290 CREATE INDEX "newsletter_unit_id_idx" ON "newsletter" ("unit_id", "published");
jbe@496 1291 CREATE INDEX "newsletter_all_units_published_idx" ON "newsletter" ("published") WHERE "unit_id" ISNULL;
jbe@496 1292 CREATE INDEX "newsletter_published_idx" ON "newsletter" ("published");
jbe@496 1293
jbe@496 1294
jbe@112 1295
jbe@112 1296 ----------------------------------------------
jbe@112 1297 -- Writing of history entries and event log --
jbe@112 1298 ----------------------------------------------
jbe@13 1299
jbe@181 1300
jbe@13 1301 CREATE FUNCTION "write_member_history_trigger"()
jbe@13 1302 RETURNS TRIGGER
jbe@13 1303 LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@13 1304 BEGIN
jbe@42 1305 IF
jbe@230 1306 ( NEW."active" != OLD."active" OR
jbe@230 1307 NEW."name" != OLD."name" ) AND
jbe@230 1308 OLD."activated" NOTNULL
jbe@42 1309 THEN
jbe@42 1310 INSERT INTO "member_history"
jbe@57 1311 ("member_id", "active", "name")
jbe@57 1312 VALUES (NEW."id", OLD."active", OLD."name");
jbe@13 1313 END IF;
jbe@13 1314 RETURN NULL;
jbe@13 1315 END;
jbe@13 1316 $$;
jbe@13 1317
jbe@13 1318 CREATE TRIGGER "write_member_history"
jbe@13 1319 AFTER UPDATE ON "member" FOR EACH ROW EXECUTE PROCEDURE
jbe@13 1320 "write_member_history_trigger"();
jbe@13 1321
jbe@13 1322 COMMENT ON FUNCTION "write_member_history_trigger"() IS 'Implementation of trigger "write_member_history" on table "member"';
jbe@57 1323 COMMENT ON TRIGGER "write_member_history" ON "member" IS 'When changing certain fields of a member, create a history entry in "member_history" table';
jbe@13 1324
jbe@13 1325
jbe@112 1326 CREATE FUNCTION "write_event_issue_state_changed_trigger"()
jbe@112 1327 RETURNS TRIGGER
jbe@112 1328 LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@112 1329 BEGIN
jbe@328 1330 IF NEW."state" != OLD."state" THEN
jbe@112 1331 INSERT INTO "event" ("event", "issue_id", "state")
jbe@112 1332 VALUES ('issue_state_changed', NEW."id", NEW."state");
jbe@112 1333 END IF;
jbe@112 1334 RETURN NULL;
jbe@112 1335 END;
jbe@112 1336 $$;
jbe@112 1337
jbe@112 1338 CREATE TRIGGER "write_event_issue_state_changed"
jbe@112 1339 AFTER UPDATE ON "issue" FOR EACH ROW EXECUTE PROCEDURE
jbe@112 1340 "write_event_issue_state_changed_trigger"();
jbe@112 1341
jbe@112 1342 COMMENT ON FUNCTION "write_event_issue_state_changed_trigger"() IS 'Implementation of trigger "write_event_issue_state_changed" on table "issue"';
jbe@112 1343 COMMENT ON TRIGGER "write_event_issue_state_changed" ON "issue" IS 'Create entry in "event" table on "state" change';
jbe@112 1344
jbe@112 1345
jbe@112 1346 CREATE FUNCTION "write_event_initiative_or_draft_created_trigger"()
jbe@112 1347 RETURNS TRIGGER
jbe@112 1348 LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@112 1349 DECLARE
jbe@112 1350 "initiative_row" "initiative"%ROWTYPE;
jbe@113 1351 "issue_row" "issue"%ROWTYPE;
jbe@112 1352 "event_v" "event_type";
jbe@112 1353 BEGIN
jbe@112 1354 SELECT * INTO "initiative_row" FROM "initiative"
jbe@112 1355 WHERE "id" = NEW."initiative_id";
jbe@113 1356 SELECT * INTO "issue_row" FROM "issue"
jbe@113 1357 WHERE "id" = "initiative_row"."issue_id";
jbe@112 1358 IF EXISTS (
jbe@112 1359 SELECT NULL FROM "draft"
jbe@112 1360 WHERE "initiative_id" = NEW."initiative_id"
jbe@112 1361 AND "id" != NEW."id"
jbe@112 1362 ) THEN
jbe@112 1363 "event_v" := 'new_draft_created';
jbe@112 1364 ELSE
jbe@112 1365 IF EXISTS (
jbe@112 1366 SELECT NULL FROM "initiative"
jbe@112 1367 WHERE "issue_id" = "initiative_row"."issue_id"
jbe@112 1368 AND "id" != "initiative_row"."id"
jbe@112 1369 ) THEN
jbe@112 1370 "event_v" := 'initiative_created_in_existing_issue';
jbe@112 1371 ELSE
jbe@112 1372 "event_v" := 'initiative_created_in_new_issue';
jbe@112 1373 END IF;
jbe@112 1374 END IF;
jbe@112 1375 INSERT INTO "event" (
jbe@112 1376 "event", "member_id",
jbe@113 1377 "issue_id", "state", "initiative_id", "draft_id"
jbe@112 1378 ) VALUES (
jbe@112 1379 "event_v",
jbe@112 1380 NEW."author_id",
jbe@112 1381 "initiative_row"."issue_id",
jbe@113 1382 "issue_row"."state",
jbe@112 1383 "initiative_row"."id",
jbe@112 1384 NEW."id" );
jbe@112 1385 RETURN NULL;
jbe@112 1386 END;
jbe@112 1387 $$;
jbe@112 1388
jbe@112 1389 CREATE TRIGGER "write_event_initiative_or_draft_created"
jbe@112 1390 AFTER INSERT ON "draft" FOR EACH ROW EXECUTE PROCEDURE
jbe@112 1391 "write_event_initiative_or_draft_created_trigger"();
jbe@112 1392
jbe@112 1393 COMMENT ON FUNCTION "write_event_initiative_or_draft_created_trigger"() IS 'Implementation of trigger "write_event_initiative_or_draft_created" on table "issue"';
jbe@112 1394 COMMENT ON TRIGGER "write_event_initiative_or_draft_created" ON "draft" IS 'Create entry in "event" table on draft creation';
jbe@112 1395
jbe@112 1396
jbe@112 1397 CREATE FUNCTION "write_event_initiative_revoked_trigger"()
jbe@112 1398 RETURNS TRIGGER
jbe@112 1399 LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@113 1400 DECLARE
jbe@231 1401 "issue_row" "issue"%ROWTYPE;
jbe@231 1402 "draft_id_v" "draft"."id"%TYPE;
jbe@112 1403 BEGIN
jbe@112 1404 IF OLD."revoked" ISNULL AND NEW."revoked" NOTNULL THEN
jbe@231 1405 SELECT * INTO "issue_row" FROM "issue"
jbe@231 1406 WHERE "id" = NEW."issue_id";
jbe@231 1407 SELECT "id" INTO "draft_id_v" FROM "current_draft"
jbe@231 1408 WHERE "initiative_id" = NEW."id";
jbe@112 1409 INSERT INTO "event" (
jbe@231 1410 "event", "member_id", "issue_id", "state", "initiative_id", "draft_id"
jbe@112 1411 ) VALUES (
jbe@112 1412 'initiative_revoked',
jbe@112 1413 NEW."revoked_by_member_id",
jbe@112 1414 NEW."issue_id",
jbe@113 1415 "issue_row"."state",
jbe@231 1416 NEW."id",
jbe@231 1417 "draft_id_v");
jbe@112 1418 END IF;
jbe@112 1419 RETURN NULL;
jbe@112 1420 END;
jbe@112 1421 $$;
jbe@112 1422
jbe@112 1423 CREATE TRIGGER "write_event_initiative_revoked"
jbe@112 1424 AFTER UPDATE ON "initiative" FOR EACH ROW EXECUTE PROCEDURE
jbe@112 1425 "write_event_initiative_revoked_trigger"();
jbe@112 1426
jbe@112 1427 COMMENT ON FUNCTION "write_event_initiative_revoked_trigger"() IS 'Implementation of trigger "write_event_initiative_revoked" on table "issue"';
jbe@112 1428 COMMENT ON TRIGGER "write_event_initiative_revoked" ON "initiative" IS 'Create entry in "event" table, when an initiative is revoked';
jbe@112 1429
jbe@112 1430
jbe@112 1431 CREATE FUNCTION "write_event_suggestion_created_trigger"()
jbe@112 1432 RETURNS TRIGGER
jbe@112 1433 LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@112 1434 DECLARE
jbe@112 1435 "initiative_row" "initiative"%ROWTYPE;
jbe@113 1436 "issue_row" "issue"%ROWTYPE;
jbe@112 1437 BEGIN
jbe@112 1438 SELECT * INTO "initiative_row" FROM "initiative"
jbe@112 1439 WHERE "id" = NEW."initiative_id";
jbe@113 1440 SELECT * INTO "issue_row" FROM "issue"
jbe@113 1441 WHERE "id" = "initiative_row"."issue_id";
jbe@112 1442 INSERT INTO "event" (
jbe@112 1443 "event", "member_id",
jbe@113 1444 "issue_id", "state", "initiative_id", "suggestion_id"
jbe@112 1445 ) VALUES (
jbe@112 1446 'suggestion_created',
jbe@112 1447 NEW."author_id",
jbe@112 1448 "initiative_row"."issue_id",
jbe@113 1449 "issue_row"."state",
jbe@112 1450 "initiative_row"."id",
jbe@112 1451 NEW."id" );
jbe@112 1452 RETURN NULL;
jbe@112 1453 END;
jbe@112 1454 $$;
jbe@112 1455
jbe@112 1456 CREATE TRIGGER "write_event_suggestion_created"
jbe@112 1457 AFTER INSERT ON "suggestion" FOR EACH ROW EXECUTE PROCEDURE
jbe@112 1458 "write_event_suggestion_created_trigger"();
jbe@112 1459
jbe@112 1460 COMMENT ON FUNCTION "write_event_suggestion_created_trigger"() IS 'Implementation of trigger "write_event_suggestion_created" on table "issue"';
jbe@112 1461 COMMENT ON TRIGGER "write_event_suggestion_created" ON "suggestion" IS 'Create entry in "event" table on suggestion creation';
jbe@112 1462
jbe@112 1463
jbe@13 1464
jbe@0 1465 ----------------------------
jbe@0 1466 -- Additional constraints --
jbe@0 1467 ----------------------------
jbe@0 1468
jbe@0 1469
jbe@0 1470 CREATE FUNCTION "issue_requires_first_initiative_trigger"()
jbe@0 1471 RETURNS TRIGGER
jbe@0 1472 LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@0 1473 BEGIN
jbe@0 1474 IF NOT EXISTS (
jbe@0 1475 SELECT NULL FROM "initiative" WHERE "issue_id" = NEW."id"
jbe@0 1476 ) THEN
jbe@463 1477 RAISE EXCEPTION 'Cannot create issue without an initial initiative.' USING
jbe@463 1478 ERRCODE = 'integrity_constraint_violation',
jbe@463 1479 HINT = 'Create issue, initiative, and draft within the same transaction.';
jbe@0 1480 END IF;
jbe@0 1481 RETURN NULL;
jbe@0 1482 END;
jbe@0 1483 $$;
jbe@0 1484
jbe@0 1485 CREATE CONSTRAINT TRIGGER "issue_requires_first_initiative"
jbe@0 1486 AFTER INSERT OR UPDATE ON "issue" DEFERRABLE INITIALLY DEFERRED
jbe@0 1487 FOR EACH ROW EXECUTE PROCEDURE
jbe@0 1488 "issue_requires_first_initiative_trigger"();
jbe@0 1489
jbe@0 1490 COMMENT ON FUNCTION "issue_requires_first_initiative_trigger"() IS 'Implementation of trigger "issue_requires_first_initiative" on table "issue"';
jbe@0 1491 COMMENT ON TRIGGER "issue_requires_first_initiative" ON "issue" IS 'Ensure that new issues have at least one initiative';
jbe@0 1492
jbe@0 1493
jbe@0 1494 CREATE FUNCTION "last_initiative_deletes_issue_trigger"()
jbe@0 1495 RETURNS TRIGGER
jbe@0 1496 LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@0 1497 DECLARE
jbe@0 1498 "reference_lost" BOOLEAN;
jbe@0 1499 BEGIN
jbe@0 1500 IF TG_OP = 'DELETE' THEN
jbe@0 1501 "reference_lost" := TRUE;
jbe@0 1502 ELSE
jbe@0 1503 "reference_lost" := NEW."issue_id" != OLD."issue_id";
jbe@0 1504 END IF;
jbe@0 1505 IF
jbe@0 1506 "reference_lost" AND NOT EXISTS (
jbe@0 1507 SELECT NULL FROM "initiative" WHERE "issue_id" = OLD."issue_id"
jbe@0 1508 )
jbe@0 1509 THEN
jbe@0 1510 DELETE FROM "issue" WHERE "id" = OLD."issue_id";
jbe@0 1511 END IF;
jbe@0 1512 RETURN NULL;
jbe@0 1513 END;
jbe@0 1514 $$;
jbe@0 1515
jbe@0 1516 CREATE CONSTRAINT TRIGGER "last_initiative_deletes_issue"
jbe@0 1517 AFTER UPDATE OR DELETE ON "initiative" DEFERRABLE INITIALLY DEFERRED
jbe@0 1518 FOR EACH ROW EXECUTE PROCEDURE
jbe@0 1519 "last_initiative_deletes_issue_trigger"();
jbe@0 1520
jbe@0 1521 COMMENT ON FUNCTION "last_initiative_deletes_issue_trigger"() IS 'Implementation of trigger "last_initiative_deletes_issue" on table "initiative"';
jbe@0 1522 COMMENT ON TRIGGER "last_initiative_deletes_issue" ON "initiative" IS 'Removing the last initiative of an issue deletes the issue';
jbe@0 1523
jbe@0 1524
jbe@0 1525 CREATE FUNCTION "initiative_requires_first_draft_trigger"()
jbe@0 1526 RETURNS TRIGGER
jbe@0 1527 LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@0 1528 BEGIN
jbe@0 1529 IF NOT EXISTS (
jbe@0 1530 SELECT NULL FROM "draft" WHERE "initiative_id" = NEW."id"
jbe@0 1531 ) THEN
jbe@463 1532 RAISE EXCEPTION 'Cannot create initiative without an initial draft.' USING
jbe@463 1533 ERRCODE = 'integrity_constraint_violation',
jbe@463 1534 HINT = 'Create issue, initiative and draft within the same transaction.';
jbe@0 1535 END IF;
jbe@0 1536 RETURN NULL;
jbe@0 1537 END;
jbe@0 1538 $$;
jbe@0 1539
jbe@0 1540 CREATE CONSTRAINT TRIGGER "initiative_requires_first_draft"
jbe@0 1541 AFTER INSERT OR UPDATE ON "initiative" DEFERRABLE INITIALLY DEFERRED
jbe@0 1542 FOR EACH ROW EXECUTE PROCEDURE
jbe@0 1543 "initiative_requires_first_draft_trigger"();
jbe@0 1544
jbe@0 1545 COMMENT ON FUNCTION "initiative_requires_first_draft_trigger"() IS 'Implementation of trigger "initiative_requires_first_draft" on table "initiative"';
jbe@0 1546 COMMENT ON TRIGGER "initiative_requires_first_draft" ON "initiative" IS 'Ensure that new initiatives have at least one draft';
jbe@0 1547
jbe@0 1548
jbe@0 1549 CREATE FUNCTION "last_draft_deletes_initiative_trigger"()
jbe@0 1550 RETURNS TRIGGER
jbe@0 1551 LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@0 1552 DECLARE
jbe@0 1553 "reference_lost" BOOLEAN;
jbe@0 1554 BEGIN
jbe@0 1555 IF TG_OP = 'DELETE' THEN
jbe@0 1556 "reference_lost" := TRUE;
jbe@0 1557 ELSE
jbe@0 1558 "reference_lost" := NEW."initiative_id" != OLD."initiative_id";
jbe@0 1559 END IF;
jbe@0 1560 IF
jbe@0 1561 "reference_lost" AND NOT EXISTS (
jbe@0 1562 SELECT NULL FROM "draft" WHERE "initiative_id" = OLD."initiative_id"
jbe@0 1563 )
jbe@0 1564 THEN
jbe@0 1565 DELETE FROM "initiative" WHERE "id" = OLD."initiative_id";
jbe@0 1566 END IF;
jbe@0 1567 RETURN NULL;
jbe@0 1568 END;
jbe@0 1569 $$;
jbe@0 1570
jbe@0 1571 CREATE CONSTRAINT TRIGGER "last_draft_deletes_initiative"
jbe@0 1572 AFTER UPDATE OR DELETE ON "draft" DEFERRABLE INITIALLY DEFERRED
jbe@0 1573 FOR EACH ROW EXECUTE PROCEDURE
jbe@0 1574 "last_draft_deletes_initiative_trigger"();
jbe@0 1575
jbe@0 1576 COMMENT ON FUNCTION "last_draft_deletes_initiative_trigger"() IS 'Implementation of trigger "last_draft_deletes_initiative" on table "draft"';
jbe@0 1577 COMMENT ON TRIGGER "last_draft_deletes_initiative" ON "draft" IS 'Removing the last draft of an initiative deletes the initiative';
jbe@0 1578
jbe@0 1579
jbe@0 1580 CREATE FUNCTION "suggestion_requires_first_opinion_trigger"()
jbe@0 1581 RETURNS TRIGGER
jbe@0 1582 LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@0 1583 BEGIN
jbe@0 1584 IF NOT EXISTS (
jbe@0 1585 SELECT NULL FROM "opinion" WHERE "suggestion_id" = NEW."id"
jbe@0 1586 ) THEN
jbe@463 1587 RAISE EXCEPTION 'Cannot create a suggestion without an opinion.' USING
jbe@463 1588 ERRCODE = 'integrity_constraint_violation',
jbe@463 1589 HINT = 'Create suggestion and opinion within the same transaction.';
jbe@0 1590 END IF;
jbe@0 1591 RETURN NULL;
jbe@0 1592 END;
jbe@0 1593 $$;
jbe@0 1594
jbe@0 1595 CREATE CONSTRAINT TRIGGER "suggestion_requires_first_opinion"
jbe@0 1596 AFTER INSERT OR UPDATE ON "suggestion" DEFERRABLE INITIALLY DEFERRED
jbe@0 1597 FOR EACH ROW EXECUTE PROCEDURE
jbe@0 1598 "suggestion_requires_first_opinion_trigger"();
jbe@0 1599
jbe@0 1600 COMMENT ON FUNCTION "suggestion_requires_first_opinion_trigger"() IS 'Implementation of trigger "suggestion_requires_first_opinion" on table "suggestion"';
jbe@0 1601 COMMENT ON TRIGGER "suggestion_requires_first_opinion" ON "suggestion" IS 'Ensure that new suggestions have at least one opinion';
jbe@0 1602
jbe@0 1603
jbe@0 1604 CREATE FUNCTION "last_opinion_deletes_suggestion_trigger"()
jbe@0 1605 RETURNS TRIGGER
jbe@0 1606 LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@0 1607 DECLARE
jbe@0 1608 "reference_lost" BOOLEAN;
jbe@0 1609 BEGIN
jbe@0 1610 IF TG_OP = 'DELETE' THEN
jbe@0 1611 "reference_lost" := TRUE;
jbe@0 1612 ELSE
jbe@0 1613 "reference_lost" := NEW."suggestion_id" != OLD."suggestion_id";
jbe@0 1614 END IF;
jbe@0 1615 IF
jbe@0 1616 "reference_lost" AND NOT EXISTS (
jbe@0 1617 SELECT NULL FROM "opinion" WHERE "suggestion_id" = OLD."suggestion_id"
jbe@0 1618 )
jbe@0 1619 THEN
jbe@0 1620 DELETE FROM "suggestion" WHERE "id" = OLD."suggestion_id";
jbe@0 1621 END IF;
jbe@0 1622 RETURN NULL;
jbe@0 1623 END;
jbe@0 1624 $$;
jbe@0 1625
jbe@0 1626 CREATE CONSTRAINT TRIGGER "last_opinion_deletes_suggestion"
jbe@0 1627 AFTER UPDATE OR DELETE ON "opinion" DEFERRABLE INITIALLY DEFERRED
jbe@0 1628 FOR EACH ROW EXECUTE PROCEDURE
jbe@0 1629 "last_opinion_deletes_suggestion_trigger"();
jbe@0 1630
jbe@0 1631 COMMENT ON FUNCTION "last_opinion_deletes_suggestion_trigger"() IS 'Implementation of trigger "last_opinion_deletes_suggestion" on table "opinion"';
jbe@0 1632 COMMENT ON TRIGGER "last_opinion_deletes_suggestion" ON "opinion" IS 'Removing the last opinion of a suggestion deletes the suggestion';
jbe@0 1633
jbe@0 1634
jbe@284 1635 CREATE FUNCTION "non_voter_deletes_direct_voter_trigger"()
jbe@284 1636 RETURNS TRIGGER
jbe@284 1637 LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@284 1638 BEGIN
jbe@284 1639 DELETE FROM "direct_voter"
jbe@284 1640 WHERE "issue_id" = NEW."issue_id" AND "member_id" = NEW."member_id";
jbe@284 1641 RETURN NULL;
jbe@284 1642 END;
jbe@284 1643 $$;
jbe@284 1644
jbe@284 1645 CREATE TRIGGER "non_voter_deletes_direct_voter"
jbe@284 1646 AFTER INSERT OR UPDATE ON "non_voter"
jbe@284 1647 FOR EACH ROW EXECUTE PROCEDURE
jbe@284 1648 "non_voter_deletes_direct_voter_trigger"();
jbe@284 1649
jbe@284 1650 COMMENT ON FUNCTION "non_voter_deletes_direct_voter_trigger"() IS 'Implementation of trigger "non_voter_deletes_direct_voter" on table "non_voter"';
jbe@284 1651 COMMENT ON TRIGGER "non_voter_deletes_direct_voter" ON "non_voter" IS 'An entry in the "non_voter" table deletes an entry in the "direct_voter" table (and vice versa due to trigger "direct_voter_deletes_non_voter" on table "direct_voter")';
jbe@284 1652
jbe@284 1653
jbe@284 1654 CREATE FUNCTION "direct_voter_deletes_non_voter_trigger"()
jbe@284 1655 RETURNS TRIGGER
jbe@284 1656 LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@284 1657 BEGIN
jbe@284 1658 DELETE FROM "non_voter"
jbe@284 1659 WHERE "issue_id" = NEW."issue_id" AND "member_id" = NEW."member_id";
jbe@284 1660 RETURN NULL;
jbe@284 1661 END;
jbe@284 1662 $$;
jbe@284 1663
jbe@284 1664 CREATE TRIGGER "direct_voter_deletes_non_voter"
jbe@284 1665 AFTER INSERT OR UPDATE ON "direct_voter"
jbe@284 1666 FOR EACH ROW EXECUTE PROCEDURE
jbe@284 1667 "direct_voter_deletes_non_voter_trigger"();
jbe@284 1668
jbe@284 1669 COMMENT ON FUNCTION "direct_voter_deletes_non_voter_trigger"() IS 'Implementation of trigger "direct_voter_deletes_non_voter" on table "direct_voter"';
jbe@284 1670 COMMENT ON TRIGGER "direct_voter_deletes_non_voter" ON "direct_voter" IS 'An entry in the "direct_voter" table deletes an entry in the "non_voter" table (and vice versa due to trigger "non_voter_deletes_direct_voter" on table "non_voter")';
jbe@284 1671
jbe@284 1672
jbe@285 1673 CREATE FUNCTION "voter_comment_fields_only_set_when_voter_comment_is_set_trigger"()
jbe@285 1674 RETURNS TRIGGER
jbe@285 1675 LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@285 1676 BEGIN
jbe@285 1677 IF NEW."comment" ISNULL THEN
jbe@285 1678 NEW."comment_changed" := NULL;
jbe@285 1679 NEW."formatting_engine" := NULL;
jbe@285 1680 END IF;
jbe@285 1681 RETURN NEW;
jbe@285 1682 END;
jbe@285 1683 $$;
jbe@285 1684
jbe@285 1685 CREATE TRIGGER "voter_comment_fields_only_set_when_voter_comment_is_set"
jbe@285 1686 BEFORE INSERT OR UPDATE ON "direct_voter"
jbe@285 1687 FOR EACH ROW EXECUTE PROCEDURE
jbe@285 1688 "voter_comment_fields_only_set_when_voter_comment_is_set_trigger"();
jbe@285 1689
jbe@285 1690 COMMENT ON FUNCTION "voter_comment_fields_only_set_when_voter_comment_is_set_trigger"() IS 'Implementation of trigger "voter_comment_fields_only_set_when_voter_comment_is_set" ON table "direct_voter"';
jbe@285 1691 COMMENT ON TRIGGER "voter_comment_fields_only_set_when_voter_comment_is_set" ON "direct_voter" IS 'If "comment" is set to NULL, then other comment related fields are also set to NULL.';
jbe@285 1692
jbe@0 1693
jbe@20 1694 ---------------------------------------------------------------
jbe@333 1695 -- Ensure that votes are not modified when issues are closed --
jbe@20 1696 ---------------------------------------------------------------
jbe@20 1697
jbe@20 1698 -- NOTE: Frontends should ensure this anyway, but in case of programming
jbe@20 1699 -- errors the following triggers ensure data integrity.
jbe@20 1700
jbe@20 1701
jbe@20 1702 CREATE FUNCTION "forbid_changes_on_closed_issue_trigger"()
jbe@20 1703 RETURNS TRIGGER
jbe@20 1704 LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@20 1705 DECLARE
jbe@336 1706 "issue_id_v" "issue"."id"%TYPE;
jbe@336 1707 "issue_row" "issue"%ROWTYPE;
jbe@20 1708 BEGIN
jbe@383 1709 IF EXISTS (
jbe@385 1710 SELECT NULL FROM "temporary_transaction_data"
jbe@385 1711 WHERE "txid" = txid_current()
jbe@383 1712 AND "key" = 'override_protection_triggers'
jbe@383 1713 AND "value" = TRUE::TEXT
jbe@383 1714 ) THEN
jbe@383 1715 RETURN NULL;
jbe@383 1716 END IF;
jbe@32 1717 IF TG_OP = 'DELETE' THEN
jbe@32 1718 "issue_id_v" := OLD."issue_id";
jbe@32 1719 ELSE
jbe@32 1720 "issue_id_v" := NEW."issue_id";
jbe@32 1721 END IF;
jbe@20 1722 SELECT INTO "issue_row" * FROM "issue"
jbe@32 1723 WHERE "id" = "issue_id_v" FOR SHARE;
jbe@383 1724 IF (
jbe@383 1725 "issue_row"."closed" NOTNULL OR (
jbe@383 1726 "issue_row"."state" = 'voting' AND
jbe@383 1727 "issue_row"."phase_finished" NOTNULL
jbe@383 1728 )
jbe@383 1729 ) THEN
jbe@332 1730 IF
jbe@332 1731 TG_RELID = 'direct_voter'::regclass AND
jbe@332 1732 TG_OP = 'UPDATE'
jbe@332 1733 THEN
jbe@332 1734 IF
jbe@332 1735 OLD."issue_id" = NEW."issue_id" AND
jbe@332 1736 OLD."member_id" = NEW."member_id" AND
jbe@332 1737 OLD."weight" = NEW."weight"
jbe@332 1738 THEN
jbe@332 1739 RETURN NULL; -- allows changing of voter comment
jbe@332 1740 END IF;
jbe@332 1741 END IF;
jbe@463 1742 RAISE EXCEPTION 'Tried to modify data after voting has been closed.' USING
jbe@463 1743 ERRCODE = 'integrity_constraint_violation';
jbe@20 1744 END IF;
jbe@20 1745 RETURN NULL;
jbe@20 1746 END;
jbe@20 1747 $$;
jbe@20 1748
jbe@20 1749 CREATE TRIGGER "forbid_changes_on_closed_issue"
jbe@20 1750 AFTER INSERT OR UPDATE OR DELETE ON "direct_voter"
jbe@20 1751 FOR EACH ROW EXECUTE PROCEDURE
jbe@20 1752 "forbid_changes_on_closed_issue_trigger"();
jbe@20 1753
jbe@20 1754 CREATE TRIGGER "forbid_changes_on_closed_issue"
jbe@20 1755 AFTER INSERT OR UPDATE OR DELETE ON "delegating_voter"
jbe@20 1756 FOR EACH ROW EXECUTE PROCEDURE
jbe@20 1757 "forbid_changes_on_closed_issue_trigger"();
jbe@20 1758
jbe@20 1759 CREATE TRIGGER "forbid_changes_on_closed_issue"
jbe@20 1760 AFTER INSERT OR UPDATE OR DELETE ON "vote"
jbe@20 1761 FOR EACH ROW EXECUTE PROCEDURE
jbe@20 1762 "forbid_changes_on_closed_issue_trigger"();
jbe@20 1763
jbe@20 1764 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 1765 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 1766 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 1767 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 1768
jbe@20 1769
jbe@20 1770
jbe@0 1771 --------------------------------------------------------------------
jbe@0 1772 -- Auto-retrieval of fields only needed for referential integrity --
jbe@0 1773 --------------------------------------------------------------------
jbe@0 1774
jbe@20 1775
jbe@0 1776 CREATE FUNCTION "autofill_issue_id_trigger"()
jbe@0 1777 RETURNS TRIGGER
jbe@0 1778 LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@0 1779 BEGIN
jbe@0 1780 IF NEW."issue_id" ISNULL THEN
jbe@0 1781 SELECT "issue_id" INTO NEW."issue_id"
jbe@0 1782 FROM "initiative" WHERE "id" = NEW."initiative_id";
jbe@0 1783 END IF;
jbe@0 1784 RETURN NEW;
jbe@0 1785 END;
jbe@0 1786 $$;
jbe@0 1787
jbe@0 1788 CREATE TRIGGER "autofill_issue_id" BEFORE INSERT ON "supporter"
jbe@0 1789 FOR EACH ROW EXECUTE PROCEDURE "autofill_issue_id_trigger"();
jbe@0 1790
jbe@0 1791 CREATE TRIGGER "autofill_issue_id" BEFORE INSERT ON "vote"
jbe@0 1792 FOR EACH ROW EXECUTE PROCEDURE "autofill_issue_id_trigger"();
jbe@0 1793
jbe@0 1794 COMMENT ON FUNCTION "autofill_issue_id_trigger"() IS 'Implementation of triggers "autofill_issue_id" on tables "supporter" and "vote"';
jbe@0 1795 COMMENT ON TRIGGER "autofill_issue_id" ON "supporter" IS 'Set "issue_id" field automatically, if NULL';
jbe@0 1796 COMMENT ON TRIGGER "autofill_issue_id" ON "vote" IS 'Set "issue_id" field automatically, if NULL';
jbe@0 1797
jbe@0 1798
jbe@0 1799 CREATE FUNCTION "autofill_initiative_id_trigger"()
jbe@0 1800 RETURNS TRIGGER
jbe@0 1801 LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@0 1802 BEGIN
jbe@0 1803 IF NEW."initiative_id" ISNULL THEN
jbe@0 1804 SELECT "initiative_id" INTO NEW."initiative_id"
jbe@0 1805 FROM "suggestion" WHERE "id" = NEW."suggestion_id";
jbe@0 1806 END IF;
jbe@0 1807 RETURN NEW;
jbe@0 1808 END;
jbe@0 1809 $$;
jbe@0 1810
jbe@0 1811 CREATE TRIGGER "autofill_initiative_id" BEFORE INSERT ON "opinion"
jbe@0 1812 FOR EACH ROW EXECUTE PROCEDURE "autofill_initiative_id_trigger"();
jbe@0 1813
jbe@0 1814 COMMENT ON FUNCTION "autofill_initiative_id_trigger"() IS 'Implementation of trigger "autofill_initiative_id" on table "opinion"';
jbe@0 1815 COMMENT ON TRIGGER "autofill_initiative_id" ON "opinion" IS 'Set "initiative_id" field automatically, if NULL';
jbe@0 1816
jbe@0 1817
jbe@0 1818
jbe@4 1819 -----------------------------------------------------
jbe@4 1820 -- Automatic calculation of certain default values --
jbe@4 1821 -----------------------------------------------------
jbe@0 1822
jbe@22 1823
jbe@22 1824 CREATE FUNCTION "copy_timings_trigger"()
jbe@22 1825 RETURNS TRIGGER
jbe@22 1826 LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@22 1827 DECLARE
jbe@22 1828 "policy_row" "policy"%ROWTYPE;
jbe@22 1829 BEGIN
jbe@22 1830 SELECT * INTO "policy_row" FROM "policy"
jbe@22 1831 WHERE "id" = NEW."policy_id";
jbe@447 1832 IF NEW."min_admission_time" ISNULL THEN
jbe@447 1833 NEW."min_admission_time" := "policy_row"."min_admission_time";
jbe@447 1834 END IF;
jbe@447 1835 IF NEW."max_admission_time" ISNULL THEN
jbe@447 1836 NEW."max_admission_time" := "policy_row"."max_admission_time";
jbe@22 1837 END IF;
jbe@22 1838 IF NEW."discussion_time" ISNULL THEN
jbe@22 1839 NEW."discussion_time" := "policy_row"."discussion_time";
jbe@22 1840 END IF;
jbe@22 1841 IF NEW."verification_time" ISNULL THEN
jbe@22 1842 NEW."verification_time" := "policy_row"."verification_time";
jbe@22 1843 END IF;
jbe@22 1844 IF NEW."voting_time" ISNULL THEN
jbe@22 1845 NEW."voting_time" := "policy_row"."voting_time";
jbe@22 1846 END IF;
jbe@22 1847 RETURN NEW;
jbe@22 1848 END;
jbe@22 1849 $$;
jbe@22 1850
jbe@22 1851 CREATE TRIGGER "copy_timings" BEFORE INSERT OR UPDATE ON "issue"
jbe@22 1852 FOR EACH ROW EXECUTE PROCEDURE "copy_timings_trigger"();
jbe@22 1853
jbe@22 1854 COMMENT ON FUNCTION "copy_timings_trigger"() IS 'Implementation of trigger "copy_timings" on table "issue"';
jbe@22 1855 COMMENT ON TRIGGER "copy_timings" ON "issue" IS 'If timing fields are NULL, copy values from policy.';
jbe@22 1856
jbe@22 1857
jbe@160 1858 CREATE FUNCTION "default_for_draft_id_trigger"()
jbe@2 1859 RETURNS TRIGGER
jbe@2 1860 LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@2 1861 BEGIN
jbe@2 1862 IF NEW."draft_id" ISNULL THEN
jbe@2 1863 SELECT "id" INTO NEW."draft_id" FROM "current_draft"
jbe@2 1864 WHERE "initiative_id" = NEW."initiative_id";
jbe@2 1865 END IF;
jbe@2 1866 RETURN NEW;
jbe@2 1867 END;
jbe@2 1868 $$;
jbe@2 1869
jbe@160 1870 CREATE TRIGGER "default_for_draft_id" BEFORE INSERT OR UPDATE ON "suggestion"
jbe@160 1871 FOR EACH ROW EXECUTE PROCEDURE "default_for_draft_id_trigger"();
jbe@2 1872 CREATE TRIGGER "default_for_draft_id" BEFORE INSERT OR UPDATE ON "supporter"
jbe@160 1873 FOR EACH ROW EXECUTE PROCEDURE "default_for_draft_id_trigger"();
jbe@160 1874
jbe@160 1875 COMMENT ON FUNCTION "default_for_draft_id_trigger"() IS 'Implementation of trigger "default_for_draft" on tables "supporter" and "suggestion"';
jbe@160 1876 COMMENT ON TRIGGER "default_for_draft_id" ON "suggestion" IS 'If "draft_id" is NULL, then use the current draft of the initiative as default';
jbe@160 1877 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 1878
jbe@2 1879
jbe@0 1880
jbe@0 1881 ----------------------------------------
jbe@0 1882 -- Automatic creation of dependencies --
jbe@0 1883 ----------------------------------------
jbe@0 1884
jbe@22 1885
jbe@0 1886 CREATE FUNCTION "autocreate_interest_trigger"()
jbe@0 1887 RETURNS TRIGGER
jbe@0 1888 LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@0 1889 BEGIN
jbe@0 1890 IF NOT EXISTS (
jbe@0 1891 SELECT NULL FROM "initiative" JOIN "interest"
jbe@0 1892 ON "initiative"."issue_id" = "interest"."issue_id"
jbe@0 1893 WHERE "initiative"."id" = NEW."initiative_id"
jbe@0 1894 AND "interest"."member_id" = NEW."member_id"
jbe@0 1895 ) THEN
jbe@0 1896 BEGIN
jbe@0 1897 INSERT INTO "interest" ("issue_id", "member_id")
jbe@0 1898 SELECT "issue_id", NEW."member_id"
jbe@0 1899 FROM "initiative" WHERE "id" = NEW."initiative_id";
jbe@0 1900 EXCEPTION WHEN unique_violation THEN END;
jbe@0 1901 END IF;
jbe@0 1902 RETURN NEW;
jbe@0 1903 END;
jbe@0 1904 $$;
jbe@0 1905
jbe@0 1906 CREATE TRIGGER "autocreate_interest" BEFORE INSERT ON "supporter"
jbe@0 1907 FOR EACH ROW EXECUTE PROCEDURE "autocreate_interest_trigger"();
jbe@0 1908
jbe@0 1909 COMMENT ON FUNCTION "autocreate_interest_trigger"() IS 'Implementation of trigger "autocreate_interest" on table "supporter"';
jbe@0 1910 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 1911
jbe@0 1912
jbe@0 1913 CREATE FUNCTION "autocreate_supporter_trigger"()
jbe@0 1914 RETURNS TRIGGER
jbe@0 1915 LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@0 1916 BEGIN
jbe@0 1917 IF NOT EXISTS (
jbe@0 1918 SELECT NULL FROM "suggestion" JOIN "supporter"
jbe@0 1919 ON "suggestion"."initiative_id" = "supporter"."initiative_id"
jbe@0 1920 WHERE "suggestion"."id" = NEW."suggestion_id"
jbe@0 1921 AND "supporter"."member_id" = NEW."member_id"
jbe@0 1922 ) THEN
jbe@0 1923 BEGIN
jbe@0 1924 INSERT INTO "supporter" ("initiative_id", "member_id")
jbe@0 1925 SELECT "initiative_id", NEW."member_id"
jbe@0 1926 FROM "suggestion" WHERE "id" = NEW."suggestion_id";
jbe@0 1927 EXCEPTION WHEN unique_violation THEN END;
jbe@0 1928 END IF;
jbe@0 1929 RETURN NEW;
jbe@0 1930 END;
jbe@0 1931 $$;
jbe@0 1932
jbe@0 1933 CREATE TRIGGER "autocreate_supporter" BEFORE INSERT ON "opinion"
jbe@0 1934 FOR EACH ROW EXECUTE PROCEDURE "autocreate_supporter_trigger"();
jbe@0 1935
jbe@0 1936 COMMENT ON FUNCTION "autocreate_supporter_trigger"() IS 'Implementation of trigger "autocreate_supporter" on table "opinion"';
jbe@0 1937 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 1938
jbe@0 1939
jbe@0 1940
jbe@0 1941 ------------------------------------------
jbe@0 1942 -- Views and helper functions for views --
jbe@0 1943 ------------------------------------------
jbe@0 1944
jbe@5 1945
jbe@97 1946 CREATE VIEW "unit_delegation" AS
jbe@97 1947 SELECT
jbe@97 1948 "unit"."id" AS "unit_id",
jbe@97 1949 "delegation"."id",
jbe@97 1950 "delegation"."truster_id",
jbe@97 1951 "delegation"."trustee_id",
jbe@97 1952 "delegation"."scope"
jbe@97 1953 FROM "unit"
jbe@97 1954 JOIN "delegation"
jbe@97 1955 ON "delegation"."unit_id" = "unit"."id"
jbe@97 1956 JOIN "member"
jbe@97 1957 ON "delegation"."truster_id" = "member"."id"
jbe@97 1958 JOIN "privilege"
jbe@97 1959 ON "delegation"."unit_id" = "privilege"."unit_id"
jbe@97 1960 AND "delegation"."truster_id" = "privilege"."member_id"
jbe@97 1961 WHERE "member"."active" AND "privilege"."voting_right";
jbe@97 1962
jbe@97 1963 COMMENT ON VIEW "unit_delegation" IS 'Unit delegations where trusters are active and have voting right';
jbe@5 1964
jbe@5 1965
jbe@5 1966 CREATE VIEW "area_delegation" AS
jbe@70 1967 SELECT DISTINCT ON ("area"."id", "delegation"."truster_id")
jbe@70 1968 "area"."id" AS "area_id",
jbe@70 1969 "delegation"."id",
jbe@70 1970 "delegation"."truster_id",
jbe@70 1971 "delegation"."trustee_id",
jbe@70 1972 "delegation"."scope"
jbe@97 1973 FROM "area"
jbe@97 1974 JOIN "delegation"
jbe@97 1975 ON "delegation"."unit_id" = "area"."unit_id"
jbe@97 1976 OR "delegation"."area_id" = "area"."id"
jbe@97 1977 JOIN "member"
jbe@97 1978 ON "delegation"."truster_id" = "member"."id"
jbe@97 1979 JOIN "privilege"
jbe@97 1980 ON "area"."unit_id" = "privilege"."unit_id"
jbe@97 1981 AND "delegation"."truster_id" = "privilege"."member_id"
jbe@97 1982 WHERE "member"."active" AND "privilege"."voting_right"
jbe@70 1983 ORDER BY
jbe@70 1984 "area"."id",
jbe@70 1985 "delegation"."truster_id",
jbe@70 1986 "delegation"."scope" DESC;
jbe@70 1987
jbe@97 1988 COMMENT ON VIEW "area_delegation" IS 'Area delegations where trusters are active and have voting right';
jbe@5 1989
jbe@5 1990
jbe@5 1991 CREATE VIEW "issue_delegation" AS
jbe@70 1992 SELECT DISTINCT ON ("issue"."id", "delegation"."truster_id")
jbe@70 1993 "issue"."id" AS "issue_id",
jbe@70 1994 "delegation"."id",
jbe@70 1995 "delegation"."truster_id",
jbe@70 1996 "delegation"."trustee_id",
jbe@70 1997 "delegation"."scope"
jbe@97 1998 FROM "issue"
jbe@97 1999 JOIN "area"
jbe@97 2000 ON "area"."id" = "issue"."area_id"
jbe@97 2001 JOIN "delegation"
jbe@97 2002 ON "delegation"."unit_id" = "area"."unit_id"
jbe@97 2003 OR "delegation"."area_id" = "area"."id"
jbe@97 2004 OR "delegation"."issue_id" = "issue"."id"
jbe@97 2005 JOIN "member"
jbe@97 2006 ON "delegation"."truster_id" = "member"."id"
jbe@97 2007 JOIN "privilege"
jbe@97 2008 ON "area"."unit_id" = "privilege"."unit_id"
jbe@97 2009 AND "delegation"."truster_id" = "privilege"."member_id"
jbe@97 2010 WHERE "member"."active" AND "privilege"."voting_right"
jbe@70 2011 ORDER BY
jbe@70 2012 "issue"."id",
jbe@70 2013 "delegation"."truster_id",
jbe@70 2014 "delegation"."scope" DESC;
jbe@70 2015
jbe@97 2016 COMMENT ON VIEW "issue_delegation" IS 'Issue delegations where trusters are active and have voting right';
jbe@5 2017
jbe@5 2018
jbe@5 2019 CREATE FUNCTION "membership_weight_with_skipping"
jbe@5 2020 ( "area_id_p" "area"."id"%TYPE,
jbe@5 2021 "member_id_p" "member"."id"%TYPE,
jbe@5 2022 "skip_member_ids_p" INT4[] ) -- "member"."id"%TYPE[]
jbe@5 2023 RETURNS INT4
jbe@5 2024 LANGUAGE 'plpgsql' STABLE AS $$
jbe@5 2025 DECLARE
jbe@5 2026 "sum_v" INT4;
jbe@5 2027 "delegation_row" "area_delegation"%ROWTYPE;
jbe@5 2028 BEGIN
jbe@5 2029 "sum_v" := 1;
jbe@5 2030 FOR "delegation_row" IN
jbe@5 2031 SELECT "area_delegation".*
jbe@5 2032 FROM "area_delegation" LEFT JOIN "membership"
jbe@5 2033 ON "membership"."area_id" = "area_id_p"
jbe@5 2034 AND "membership"."member_id" = "area_delegation"."truster_id"
jbe@5 2035 WHERE "area_delegation"."area_id" = "area_id_p"
jbe@5 2036 AND "area_delegation"."trustee_id" = "member_id_p"
jbe@5 2037 AND "membership"."member_id" ISNULL
jbe@5 2038 LOOP
jbe@5 2039 IF NOT
jbe@5 2040 "skip_member_ids_p" @> ARRAY["delegation_row"."truster_id"]
jbe@5 2041 THEN
jbe@5 2042 "sum_v" := "sum_v" + "membership_weight_with_skipping"(
jbe@5 2043 "area_id_p",
jbe@5 2044 "delegation_row"."truster_id",
jbe@5 2045 "skip_member_ids_p" || "delegation_row"."truster_id"
jbe@5 2046 );
jbe@5 2047 END IF;
jbe@5 2048 END LOOP;
jbe@5 2049 RETURN "sum_v";
jbe@5 2050 END;
jbe@5 2051 $$;
jbe@5 2052
jbe@8 2053 COMMENT ON FUNCTION "membership_weight_with_skipping"
jbe@8 2054 ( "area"."id"%TYPE,
jbe@8 2055 "member"."id"%TYPE,
jbe@8 2056 INT4[] )
jbe@8 2057 IS 'Helper function for "membership_weight" function';
jbe@8 2058
jbe@8 2059
jbe@5 2060 CREATE FUNCTION "membership_weight"
jbe@5 2061 ( "area_id_p" "area"."id"%TYPE,
jbe@5 2062 "member_id_p" "member"."id"%TYPE ) -- "member"."id"%TYPE[]
jbe@5 2063 RETURNS INT4
jbe@5 2064 LANGUAGE 'plpgsql' STABLE AS $$
jbe@5 2065 BEGIN
jbe@5 2066 RETURN "membership_weight_with_skipping"(
jbe@5 2067 "area_id_p",
jbe@5 2068 "member_id_p",
jbe@5 2069 ARRAY["member_id_p"]
jbe@5 2070 );
jbe@5 2071 END;
jbe@5 2072 $$;
jbe@5 2073
jbe@8 2074 COMMENT ON FUNCTION "membership_weight"
jbe@8 2075 ( "area"."id"%TYPE,
jbe@8 2076 "member"."id"%TYPE )
jbe@8 2077 IS 'Calculates the potential voting weight of a member in a given area';
jbe@8 2078
jbe@5 2079
jbe@4 2080 CREATE VIEW "member_count_view" AS
jbe@5 2081 SELECT count(1) AS "total_count" FROM "member" WHERE "active";
jbe@4 2082
jbe@4 2083 COMMENT ON VIEW "member_count_view" IS 'View used to update "member_count" table';
jbe@4 2084
jbe@4 2085
jbe@97 2086 CREATE VIEW "unit_member_count" AS
jbe@97 2087 SELECT
jbe@97 2088 "unit"."id" AS "unit_id",
jbe@248 2089 count("member"."id") AS "member_count"
jbe@97 2090 FROM "unit"
jbe@97 2091 LEFT JOIN "privilege"
jbe@97 2092 ON "privilege"."unit_id" = "unit"."id"
jbe@97 2093 AND "privilege"."voting_right"
jbe@97 2094 LEFT JOIN "member"
jbe@97 2095 ON "member"."id" = "privilege"."member_id"
jbe@97 2096 AND "member"."active"
jbe@97 2097 GROUP BY "unit"."id";
jbe@97 2098
jbe@97 2099 COMMENT ON VIEW "unit_member_count" IS 'View used to update "member_count" column of "unit" table';
jbe@97 2100
jbe@97 2101
jbe@4 2102 CREATE VIEW "area_member_count" AS
jbe@5 2103 SELECT
jbe@5 2104 "area"."id" AS "area_id",
jbe@5 2105 count("member"."id") AS "direct_member_count",
jbe@5 2106 coalesce(
jbe@5 2107 sum(
jbe@5 2108 CASE WHEN "member"."id" NOTNULL THEN
jbe@5 2109 "membership_weight"("area"."id", "member"."id")
jbe@5 2110 ELSE 0 END
jbe@5 2111 )
jbe@169 2112 ) AS "member_weight"
jbe@4 2113 FROM "area"
jbe@4 2114 LEFT JOIN "membership"
jbe@4 2115 ON "area"."id" = "membership"."area_id"
jbe@97 2116 LEFT JOIN "privilege"
jbe@97 2117 ON "privilege"."unit_id" = "area"."unit_id"
jbe@97 2118 AND "privilege"."member_id" = "membership"."member_id"
jbe@97 2119 AND "privilege"."voting_right"
jbe@4 2120 LEFT JOIN "member"
jbe@97 2121 ON "member"."id" = "privilege"."member_id" -- NOTE: no membership here!
jbe@4 2122 AND "member"."active"
jbe@4 2123 GROUP BY "area"."id";
jbe@4 2124
jbe@169 2125 COMMENT ON VIEW "area_member_count" IS 'View used to update "direct_member_count" and "member_weight" columns of table "area"';
jbe@4 2126
jbe@4 2127
jbe@9 2128 CREATE VIEW "opening_draft" AS
jbe@9 2129 SELECT "draft".* FROM (
jbe@9 2130 SELECT
jbe@9 2131 "initiative"."id" AS "initiative_id",
jbe@9 2132 min("draft"."id") AS "draft_id"
jbe@9 2133 FROM "initiative" JOIN "draft"
jbe@9 2134 ON "initiative"."id" = "draft"."initiative_id"
jbe@9 2135 GROUP BY "initiative"."id"
jbe@9 2136 ) AS "subquery"
jbe@9 2137 JOIN "draft" ON "subquery"."draft_id" = "draft"."id";
jbe@9 2138
jbe@9 2139 COMMENT ON VIEW "opening_draft" IS 'First drafts of all initiatives';
jbe@9 2140
jbe@9 2141
jbe@0 2142 CREATE VIEW "current_draft" AS
jbe@0 2143 SELECT "draft".* FROM (
jbe@0 2144 SELECT
jbe@0 2145 "initiative"."id" AS "initiative_id",
jbe@0 2146 max("draft"."id") AS "draft_id"
jbe@0 2147 FROM "initiative" JOIN "draft"
jbe@0 2148 ON "initiative"."id" = "draft"."initiative_id"
jbe@0 2149 GROUP BY "initiative"."id"
jbe@0 2150 ) AS "subquery"
jbe@0 2151 JOIN "draft" ON "subquery"."draft_id" = "draft"."id";
jbe@0 2152
jbe@0 2153 COMMENT ON VIEW "current_draft" IS 'All latest drafts for each initiative';
jbe@0 2154
jbe@0 2155
jbe@0 2156 CREATE VIEW "critical_opinion" AS
jbe@0 2157 SELECT * FROM "opinion"
jbe@0 2158 WHERE ("degree" = 2 AND "fulfilled" = FALSE)
jbe@0 2159 OR ("degree" = -2 AND "fulfilled" = TRUE);
jbe@0 2160
jbe@0 2161 COMMENT ON VIEW "critical_opinion" IS 'Opinions currently causing dissatisfaction';
jbe@0 2162
jbe@0 2163
jbe@392 2164 CREATE VIEW "issue_supporter_in_admission_state" AS
jbe@466 2165 SELECT DISTINCT -- TODO: DISTINCT needed?
jbe@410 2166 "area"."unit_id",
jbe@392 2167 "issue"."area_id",
jbe@392 2168 "issue"."id" AS "issue_id",
jbe@392 2169 "supporter"."member_id",
jbe@392 2170 "direct_interest_snapshot"."weight"
jbe@392 2171 FROM "issue"
jbe@410 2172 JOIN "area" ON "area"."id" = "issue"."area_id"
jbe@392 2173 JOIN "supporter" ON "supporter"."issue_id" = "issue"."id"
jbe@392 2174 JOIN "direct_interest_snapshot"
jbe@392 2175 ON "direct_interest_snapshot"."issue_id" = "issue"."id"
jbe@392 2176 AND "direct_interest_snapshot"."event" = "issue"."latest_snapshot_event"
jbe@392 2177 AND "direct_interest_snapshot"."member_id" = "supporter"."member_id"
jbe@392 2178 WHERE "issue"."state" = 'admission'::"issue_state";
jbe@392 2179
jbe@392 2180 COMMENT ON VIEW "issue_supporter_in_admission_state" IS 'Helper view for "lf_update_issue_order" to allow a (proportional) ordering of issues within an area';
jbe@392 2181
jbe@392 2182
jbe@352 2183 CREATE VIEW "initiative_suggestion_order_calculation" AS
jbe@352 2184 SELECT
jbe@352 2185 "initiative"."id" AS "initiative_id",
jbe@352 2186 ("issue"."closed" NOTNULL OR "issue"."fully_frozen" NOTNULL) AS "final"
jbe@352 2187 FROM "initiative" JOIN "issue"
jbe@352 2188 ON "initiative"."issue_id" = "issue"."id"
jbe@352 2189 WHERE ("issue"."closed" ISNULL AND "issue"."fully_frozen" ISNULL)
jbe@352 2190 OR ("initiative"."final_suggestion_order_calculated" = FALSE);
jbe@352 2191
jbe@352 2192 COMMENT ON VIEW "initiative_suggestion_order_calculation" IS 'Initiatives, where the "proportional_order" of its suggestions has to be calculated';
jbe@352 2193
jbe@360 2194 COMMENT ON COLUMN "initiative_suggestion_order_calculation"."final" IS 'Set to TRUE, if the issue is fully frozen or closed, and the calculation has to be done only once for one last time';
jbe@352 2195
jbe@352 2196
jbe@352 2197 CREATE VIEW "individual_suggestion_ranking" AS
jbe@352 2198 SELECT
jbe@352 2199 "opinion"."initiative_id",
jbe@352 2200 "opinion"."member_id",
jbe@352 2201 "direct_interest_snapshot"."weight",
jbe@352 2202 CASE WHEN
jbe@352 2203 ("opinion"."degree" = 2 AND "opinion"."fulfilled" = FALSE) OR
jbe@352 2204 ("opinion"."degree" = -2 AND "opinion"."fulfilled" = TRUE)
jbe@352 2205 THEN 1 ELSE
jbe@352 2206 CASE WHEN
jbe@352 2207 ("opinion"."degree" = 1 AND "opinion"."fulfilled" = FALSE) OR
jbe@352 2208 ("opinion"."degree" = -1 AND "opinion"."fulfilled" = TRUE)
jbe@352 2209 THEN 2 ELSE
jbe@352 2210 CASE WHEN
jbe@352 2211 ("opinion"."degree" = 2 AND "opinion"."fulfilled" = TRUE) OR
jbe@352 2212 ("opinion"."degree" = -2 AND "opinion"."fulfilled" = FALSE)
jbe@352 2213 THEN 3 ELSE 4 END
jbe@352 2214 END
jbe@352 2215 END AS "preference",
jbe@352 2216 "opinion"."suggestion_id"
jbe@352 2217 FROM "opinion"
jbe@352 2218 JOIN "initiative" ON "initiative"."id" = "opinion"."initiative_id"
jbe@352 2219 JOIN "issue" ON "issue"."id" = "initiative"."issue_id"
jbe@352 2220 JOIN "direct_interest_snapshot"
jbe@352 2221 ON "direct_interest_snapshot"."issue_id" = "issue"."id"
jbe@352 2222 AND "direct_interest_snapshot"."event" = "issue"."latest_snapshot_event"
jbe@352 2223 AND "direct_interest_snapshot"."member_id" = "opinion"."member_id";
jbe@352 2224
jbe@352 2225 COMMENT ON VIEW "individual_suggestion_ranking" IS 'Helper view for "lf_update_suggestion_order" to allow a proportional ordering of suggestions within an initiative';
jbe@352 2226
jbe@352 2227
jbe@126 2228 CREATE VIEW "battle_participant" AS
jbe@126 2229 SELECT "initiative"."id", "initiative"."issue_id"
jbe@126 2230 FROM "issue" JOIN "initiative"
jbe@126 2231 ON "issue"."id" = "initiative"."issue_id"
jbe@126 2232 WHERE "initiative"."admitted"
jbe@126 2233 UNION ALL
jbe@126 2234 SELECT NULL, "id" AS "issue_id"
jbe@126 2235 FROM "issue";
jbe@126 2236
jbe@126 2237 COMMENT ON VIEW "battle_participant" IS 'Helper view for "battle_view" containing admitted initiatives plus virtual "status-quo" initiative denoted by NULL reference';
jbe@126 2238
jbe@126 2239
jbe@61 2240 CREATE VIEW "battle_view" AS
jbe@0 2241 SELECT
jbe@0 2242 "issue"."id" AS "issue_id",
jbe@10 2243 "winning_initiative"."id" AS "winning_initiative_id",
jbe@10 2244 "losing_initiative"."id" AS "losing_initiative_id",
jbe@0 2245 sum(
jbe@0 2246 CASE WHEN
jbe@0 2247 coalesce("better_vote"."grade", 0) >
jbe@0 2248 coalesce("worse_vote"."grade", 0)
jbe@0 2249 THEN "direct_voter"."weight" ELSE 0 END
jbe@0 2250 ) AS "count"
jbe@0 2251 FROM "issue"
jbe@0 2252 LEFT JOIN "direct_voter"
jbe@0 2253 ON "issue"."id" = "direct_voter"."issue_id"
jbe@126 2254 JOIN "battle_participant" AS "winning_initiative"
jbe@10 2255 ON "issue"."id" = "winning_initiative"."issue_id"
jbe@126 2256 JOIN "battle_participant" AS "losing_initiative"
jbe@10 2257 ON "issue"."id" = "losing_initiative"."issue_id"
jbe@0 2258 LEFT JOIN "vote" AS "better_vote"
jbe@10 2259 ON "direct_voter"."member_id" = "better_vote"."member_id"
jbe@10 2260 AND "winning_initiative"."id" = "better_vote"."initiative_id"
jbe@0 2261 LEFT JOIN "vote" AS "worse_vote"
jbe@10 2262 ON "direct_voter"."member_id" = "worse_vote"."member_id"
jbe@10 2263 AND "losing_initiative"."id" = "worse_vote"."initiative_id"
jbe@328 2264 WHERE "issue"."state" = 'voting'
jbe@328 2265 AND "issue"."phase_finished" NOTNULL
jbe@126 2266 AND (
jbe@126 2267 "winning_initiative"."id" != "losing_initiative"."id" OR
jbe@126 2268 ( ("winning_initiative"."id" NOTNULL AND "losing_initiative"."id" ISNULL) OR
jbe@126 2269 ("winning_initiative"."id" ISNULL AND "losing_initiative"."id" NOTNULL) ) )
jbe@0 2270 GROUP BY
jbe@0 2271 "issue"."id",
jbe@10 2272 "winning_initiative"."id",
jbe@10 2273 "losing_initiative"."id";
jbe@0 2274
jbe@126 2275 COMMENT ON VIEW "battle_view" IS 'Number of members preferring one initiative (or status-quo) to another initiative (or status-quo); Used to fill "battle" table';
jbe@1 2276
jbe@1 2277
jbe@235 2278 CREATE VIEW "expired_session" AS
jbe@235 2279 SELECT * FROM "session" WHERE now() > "expiry";
jbe@235 2280
jbe@235 2281 CREATE RULE "delete" AS ON DELETE TO "expired_session" DO INSTEAD
jbe@235 2282 DELETE FROM "session" WHERE "ident" = OLD."ident";
jbe@235 2283
jbe@235 2284 COMMENT ON VIEW "expired_session" IS 'View containing all expired sessions where DELETE is possible';
jbe@235 2285 COMMENT ON RULE "delete" ON "expired_session" IS 'Rule allowing DELETE on rows in "expired_session" view, i.e. DELETE FROM "expired_session"';
jbe@235 2286
jbe@235 2287
jbe@0 2288 CREATE VIEW "open_issue" AS
jbe@0 2289 SELECT * FROM "issue" WHERE "closed" ISNULL;
jbe@0 2290
jbe@0 2291 COMMENT ON VIEW "open_issue" IS 'All open issues';
jbe@0 2292
jbe@0 2293
jbe@9 2294 CREATE VIEW "member_contingent" AS
jbe@9 2295 SELECT
jbe@9 2296 "member"."id" AS "member_id",
jbe@293 2297 "contingent"."polling",
jbe@9 2298 "contingent"."time_frame",
jbe@9 2299 CASE WHEN "contingent"."text_entry_limit" NOTNULL THEN
jbe@9 2300 (
jbe@9 2301 SELECT count(1) FROM "draft"
jbe@293 2302 JOIN "initiative" ON "initiative"."id" = "draft"."initiative_id"
jbe@9 2303 WHERE "draft"."author_id" = "member"."id"
jbe@293 2304 AND "initiative"."polling" = "contingent"."polling"
jbe@9 2305 AND "draft"."created" > now() - "contingent"."time_frame"
jbe@9 2306 ) + (
jbe@9 2307 SELECT count(1) FROM "suggestion"
jbe@293 2308 JOIN "initiative" ON "initiative"."id" = "suggestion"."initiative_id"
jbe@9 2309 WHERE "suggestion"."author_id" = "member"."id"
jbe@293 2310 AND "contingent"."polling" = FALSE
jbe@9 2311 AND "suggestion"."created" > now() - "contingent"."time_frame"
jbe@9 2312 )
jbe@9 2313 ELSE NULL END AS "text_entry_count",
jbe@9 2314 "contingent"."text_entry_limit",
jbe@9 2315 CASE WHEN "contingent"."initiative_limit" NOTNULL THEN (
jbe@293 2316 SELECT count(1) FROM "opening_draft" AS "draft"
jbe@293 2317 JOIN "initiative" ON "initiative"."id" = "draft"."initiative_id"
jbe@293 2318 WHERE "draft"."author_id" = "member"."id"
jbe@293 2319 AND "initiative"."polling" = "contingent"."polling"
jbe@293 2320 AND "draft"."created" > now() - "contingent"."time_frame"
jbe@9 2321 ) ELSE NULL END AS "initiative_count",
jbe@9 2322 "contingent"."initiative_limit"
jbe@9 2323 FROM "member" CROSS JOIN "contingent";
jbe@9 2324
jbe@9 2325 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 2326
jbe@9 2327 COMMENT ON COLUMN "member_contingent"."text_entry_count" IS 'Only calculated when "text_entry_limit" is not null in the same row';
jbe@9 2328 COMMENT ON COLUMN "member_contingent"."initiative_count" IS 'Only calculated when "initiative_limit" is not null in the same row';
jbe@9 2329
jbe@9 2330
jbe@9 2331 CREATE VIEW "member_contingent_left" AS
jbe@9 2332 SELECT
jbe@9 2333 "member_id",
jbe@293 2334 "polling",
jbe@9 2335 max("text_entry_limit" - "text_entry_count") AS "text_entries_left",
jbe@9 2336 max("initiative_limit" - "initiative_count") AS "initiatives_left"
jbe@293 2337 FROM "member_contingent" GROUP BY "member_id", "polling";
jbe@9 2338
jbe@9 2339 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 2340
jbe@9 2341
jbe@113 2342 CREATE VIEW "event_seen_by_member" AS
jbe@113 2343 SELECT
jbe@113 2344 "member"."id" AS "seen_by_member_id",
jbe@113 2345 CASE WHEN "event"."state" IN (
jbe@113 2346 'voting',
jbe@113 2347 'finished_without_winner',
jbe@113 2348 'finished_with_winner'
jbe@113 2349 ) THEN
jbe@113 2350 'voting'::"notify_level"
jbe@113 2351 ELSE
jbe@113 2352 CASE WHEN "event"."state" IN (
jbe@113 2353 'verification',
jbe@113 2354 'canceled_after_revocation_during_verification',
jbe@113 2355 'canceled_no_initiative_admitted'
jbe@113 2356 ) THEN
jbe@113 2357 'verification'::"notify_level"
jbe@113 2358 ELSE
jbe@113 2359 CASE WHEN "event"."state" IN (
jbe@113 2360 'discussion',
jbe@113 2361 'canceled_after_revocation_during_discussion'
jbe@113 2362 ) THEN
jbe@113 2363 'discussion'::"notify_level"
jbe@113 2364 ELSE
jbe@113 2365 'all'::"notify_level"
jbe@113 2366 END
jbe@113 2367 END
jbe@113 2368 END AS "notify_level",
jbe@113 2369 "event".*
jbe@113 2370 FROM "member" CROSS JOIN "event"
jbe@113 2371 LEFT JOIN "issue"
jbe@113 2372 ON "event"."issue_id" = "issue"."id"
jbe@113 2373 LEFT JOIN "membership"
jbe@113 2374 ON "member"."id" = "membership"."member_id"
jbe@113 2375 AND "issue"."area_id" = "membership"."area_id"
jbe@113 2376 LEFT JOIN "interest"
jbe@113 2377 ON "member"."id" = "interest"."member_id"
jbe@113 2378 AND "event"."issue_id" = "interest"."issue_id"
jbe@113 2379 LEFT JOIN "ignored_member"
jbe@113 2380 ON "member"."id" = "ignored_member"."member_id"
jbe@113 2381 AND "event"."member_id" = "ignored_member"."other_member_id"
jbe@113 2382 LEFT JOIN "ignored_initiative"
jbe@113 2383 ON "member"."id" = "ignored_initiative"."member_id"
jbe@113 2384 AND "event"."initiative_id" = "ignored_initiative"."initiative_id"
jbe@113 2385 WHERE (
jbe@113 2386 "interest"."member_id" NOTNULL OR
jbe@113 2387 ( "membership"."member_id" NOTNULL AND
jbe@113 2388 "event"."event" IN (
jbe@113 2389 'issue_state_changed',
jbe@113 2390 'initiative_created_in_new_issue',
jbe@113 2391 'initiative_created_in_existing_issue',
jbe@113 2392 'initiative_revoked' ) ) )
jbe@113 2393 AND "ignored_member"."member_id" ISNULL
jbe@113 2394 AND "ignored_initiative"."member_id" ISNULL;
jbe@113 2395
jbe@222 2396 COMMENT ON VIEW "event_seen_by_member" IS 'Events as seen by a member, depending on its memberships, interests and support, but ignoring members "notify_level"';
jbe@222 2397
jbe@222 2398
jbe@473 2399 CREATE VIEW "updated_initiative" AS
jbe@473 2400 SELECT
jbe@486 2401 "supporter"."member_id" AS "seen_by_member_id",
jbe@477 2402 TRUE AS "supported",
jbe@477 2403 EXISTS (
jbe@477 2404 SELECT NULL FROM "draft"
jbe@477 2405 WHERE "draft"."initiative_id" = "initiative"."id"
jbe@477 2406 AND "draft"."id" > "supporter"."draft_id"
jbe@477 2407 ) AS "new_draft",
jbe@477 2408 ( SELECT count(1) FROM "suggestion"
jbe@488 2409 LEFT JOIN "opinion" ON
jbe@488 2410 "opinion"."member_id" = "supporter"."member_id" AND
jbe@488 2411 "opinion"."suggestion_id" = "suggestion"."id"
jbe@477 2412 WHERE "suggestion"."initiative_id" = "initiative"."id"
jbe@488 2413 AND "opinion"."member_id" ISNULL
jbe@477 2414 AND COALESCE(
jbe@486 2415 "suggestion"."id" > "sent"."last_suggestion_id",
jbe@477 2416 TRUE
jbe@477 2417 )
jbe@477 2418 ) AS "new_suggestion_count",
jbe@477 2419 FALSE AS "featured",
jbe@477 2420 NOT EXISTS (
jbe@477 2421 SELECT NULL FROM "initiative" AS "better_initiative"
jbe@477 2422 WHERE
jbe@484 2423 "better_initiative"."issue_id" = "initiative"."issue_id"
jbe@484 2424 AND
jbe@484 2425 ( COALESCE("better_initiative"."harmonic_weight", -1),
jbe@484 2426 -"better_initiative"."id" ) >
jbe@484 2427 ( COALESCE("initiative"."harmonic_weight", -1),
jbe@485 2428 -"initiative"."id" )
jbe@477 2429 ) AS "leading",
jbe@473 2430 "initiative".*
jbe@486 2431 FROM "supporter" JOIN "initiative"
jbe@486 2432 ON "supporter"."initiative_id" = "initiative"."id"
jbe@486 2433 LEFT JOIN "initiative_notification_sent" AS "sent"
jbe@486 2434 ON "sent"."member_id" = "supporter"."member_id"
jbe@486 2435 AND "sent"."initiative_id" = "initiative"."id"
jbe@473 2436 JOIN "issue" ON "issue"."id" = "initiative"."issue_id"
jbe@480 2437 WHERE "issue"."state" IN ('admission', 'discussion')
jbe@473 2438 AND (
jbe@473 2439 EXISTS (
jbe@473 2440 SELECT NULL FROM "draft"
jbe@473 2441 WHERE "draft"."initiative_id" = "initiative"."id"
jbe@473 2442 AND "draft"."id" > "supporter"."draft_id"
jbe@473 2443 ) OR EXISTS (
jbe@473 2444 SELECT NULL FROM "suggestion"
jbe@487 2445 LEFT JOIN "opinion" ON
jbe@487 2446 "opinion"."member_id" = "supporter"."member_id" AND
jbe@487 2447 "opinion"."suggestion_id" = "suggestion"."id"
jbe@473 2448 WHERE "suggestion"."initiative_id" = "initiative"."id"
jbe@487 2449 AND "opinion"."member_id" ISNULL
jbe@473 2450 AND COALESCE(
jbe@486 2451 "suggestion"."id" > "sent"."last_suggestion_id",
jbe@473 2452 TRUE
jbe@473 2453 )
jbe@473 2454 )
jbe@473 2455 );
jbe@473 2456
jbe@474 2457 CREATE FUNCTION "featured_initiative"
jbe@474 2458 ( "member_id_p" "member"."id"%TYPE,
jbe@474 2459 "area_id_p" "area"."id"%TYPE )
jbe@474 2460 RETURNS SETOF "initiative"
jbe@474 2461 LANGUAGE 'plpgsql' STABLE AS $$
jbe@474 2462 DECLARE
jbe@482 2463 "member_row" "member"%ROWTYPE;
jbe@474 2464 "member_id_v" "member"."id"%TYPE;
jbe@474 2465 "seed_v" TEXT;
jbe@474 2466 "result_row" "initiative"%ROWTYPE;
jbe@474 2467 "match_v" BOOLEAN;
jbe@474 2468 "initiative_id_ary" INT4[]; --"initiative"."id"%TYPE[]
jbe@474 2469 BEGIN
jbe@482 2470 SELECT INTO "member_row" * FROM "member" WHERE "id" = "member_id_p";
jbe@474 2471 "initiative_id_ary" := '{}';
jbe@474 2472 LOOP
jbe@474 2473 "match_v" := FALSE;
jbe@474 2474 FOR "member_id_v", "seed_v" IN
jbe@474 2475 SELECT * FROM (
jbe@474 2476 SELECT DISTINCT
jbe@474 2477 "supporter"."member_id",
jbe@482 2478 md5("member_id_p" || '-' || "member_row"."notification_counter" || '-' || "area_id_p" || '-' || "supporter"."member_id") AS "seed"
jbe@474 2479 FROM "supporter"
jbe@474 2480 JOIN "member" ON "member"."id" = "supporter"."member_id"
jbe@474 2481 JOIN "initiative" ON "initiative"."id" = "supporter"."initiative_id"
jbe@474 2482 JOIN "issue" ON "issue"."id" = "initiative"."issue_id"
jbe@474 2483 WHERE "supporter"."member_id" != "member_id_p"
jbe@474 2484 AND "issue"."area_id" = "area_id_p"
jbe@474 2485 AND "issue"."state" IN ('admission', 'discussion', 'verification')
jbe@474 2486 ) AS "subquery"
jbe@474 2487 ORDER BY "seed"
jbe@474 2488 LOOP
jbe@476 2489 SELECT "initiative".* INTO "result_row"
jbe@476 2490 FROM "initiative"
jbe@474 2491 JOIN "issue" ON "issue"."id" = "initiative"."issue_id"
jbe@474 2492 JOIN "supporter" ON "supporter"."initiative_id" = "initiative"."id"
jbe@474 2493 LEFT JOIN "supporter" AS "self_support" ON
jbe@474 2494 "self_support"."initiative_id" = "initiative"."id" AND
jbe@474 2495 "self_support"."member_id" = "member_id_p"
jbe@474 2496 WHERE "supporter"."member_id" = "member_id_v"
jbe@474 2497 AND "issue"."area_id" = "area_id_p"
jbe@474 2498 AND "issue"."state" IN ('admission', 'discussion', 'verification')
jbe@474 2499 AND "self_support"."member_id" ISNULL
jbe@476 2500 AND NOT "initiative_id_ary" @> ARRAY["initiative"."id"]
jbe@474 2501 ORDER BY md5("seed_v" || '-' || "initiative"."id")
jbe@476 2502 LIMIT 1;
jbe@476 2503 IF FOUND THEN
jbe@476 2504 "match_v" := TRUE;
jbe@476 2505 "initiative_id_ary" := "initiative_id_ary" || "result_row"."id";
jbe@476 2506 RETURN NEXT "result_row";
jbe@486 2507 IF array_length("initiative_id_ary", 1) >= "member_row"."notification_sample_size" THEN
jbe@476 2508 RETURN;
jbe@474 2509 END IF;
jbe@476 2510 END IF;
jbe@474 2511 END LOOP;
jbe@474 2512 EXIT WHEN NOT "match_v";
jbe@474 2513 END LOOP;
jbe@474 2514 RETURN;
jbe@474 2515 END;
jbe@474 2516 $$;
jbe@474 2517
jbe@474 2518 CREATE VIEW "updated_or_featured_initiative" AS
jbe@474 2519 SELECT * FROM "updated_initiative"
jbe@474 2520 UNION ALL
jbe@474 2521 SELECT
jbe@474 2522 "member"."id" AS "seen_by_member_id",
jbe@477 2523 FALSE AS "supported",
jbe@489 2524 EXISTS (
jbe@489 2525 SELECT NULL FROM "draft"
jbe@489 2526 WHERE "draft"."initiative_id" = "initiative"."id"
jbe@489 2527 AND COALESCE(
jbe@489 2528 "draft"."id" > "sent"."last_draft_id",
jbe@489 2529 TRUE
jbe@489 2530 )
jbe@489 2531 ) AS "new_draft",
jbe@489 2532 ( SELECT count(1) FROM "suggestion"
jbe@489 2533 WHERE "suggestion"."initiative_id" = "initiative"."id"
jbe@489 2534 AND COALESCE(
jbe@489 2535 "suggestion"."id" > "sent"."last_suggestion_id",
jbe@489 2536 TRUE
jbe@489 2537 )
jbe@489 2538 ) AS "new_suggestion_count",
jbe@477 2539 TRUE AS "featured",
jbe@477 2540 NOT EXISTS (
jbe@477 2541 SELECT NULL FROM "initiative" AS "better_initiative"
jbe@477 2542 WHERE
jbe@484 2543 "better_initiative"."issue_id" = "initiative"."issue_id"
jbe@484 2544 AND
jbe@484 2545 ( COALESCE("better_initiative"."harmonic_weight", -1),
jbe@484 2546 -"better_initiative"."id" ) >
jbe@484 2547 ( COALESCE("initiative"."harmonic_weight", -1),
jbe@485 2548 -"initiative"."id" )
jbe@477 2549 ) AS "leading",
jbe@477 2550 "initiative".*
jbe@474 2551 FROM "member" CROSS JOIN "area"
jbe@477 2552 CROSS JOIN LATERAL
jbe@489 2553 "featured_initiative"("member"."id", "area"."id") AS "initiative"
jbe@489 2554 LEFT JOIN "initiative_notification_sent" AS "sent"
jbe@489 2555 ON "sent"."member_id" = "member"."id"
jbe@489 2556 AND "sent"."initiative_id" = "initiative"."id";
jbe@474 2557
jbe@474 2558 CREATE VIEW "leading_complement_initiative" AS
jbe@477 2559 SELECT * FROM (
jbe@477 2560 SELECT DISTINCT ON ("seen_by_member_id", "initiative"."issue_id")
jbe@489 2561 "uf_initiative"."seen_by_member_id",
jbe@489 2562 "supporter"."member_id" NOTNULL AS "supported",
jbe@489 2563 CASE WHEN "supporter"."member_id" NOTNULL THEN FALSE ELSE
jbe@489 2564 EXISTS (
jbe@489 2565 SELECT NULL FROM "draft"
jbe@489 2566 WHERE "draft"."initiative_id" = "initiative"."id"
jbe@489 2567 AND COALESCE(
jbe@489 2568 "draft"."id" > "sent"."last_draft_id",
jbe@489 2569 TRUE
jbe@489 2570 )
jbe@489 2571 )
jbe@489 2572 END AS "new_draft",
jbe@489 2573 CASE WHEN "supporter"."member_id" NOTNULL THEN 0 ELSE
jbe@489 2574 ( SELECT count(1) FROM "suggestion"
jbe@489 2575 WHERE "suggestion"."initiative_id" = "initiative"."id"
jbe@489 2576 AND COALESCE(
jbe@489 2577 "suggestion"."id" > "sent"."last_suggestion_id",
jbe@489 2578 TRUE
jbe@489 2579 )
jbe@489 2580 )
jbe@489 2581 END AS "new_suggestion_count",
jbe@477 2582 FALSE AS "featured",
jbe@477 2583 TRUE AS "leading",
jbe@477 2584 "initiative".*
jbe@489 2585 FROM "updated_or_featured_initiative" AS "uf_initiative"
jbe@489 2586 JOIN "initiative" ON
jbe@489 2587 "uf_initiative"."issue_id" = "initiative"."issue_id"
jbe@489 2588 LEFT JOIN "supporter" ON
jbe@489 2589 "supporter"."member_id" = "uf_initiative"."seen_by_member_id" AND
jbe@489 2590 "supporter"."initiative_id" = "initiative"."id"
jbe@489 2591 LEFT JOIN "initiative_notification_sent" AS "sent"
jbe@489 2592 ON "sent"."member_id" = "uf_initiative"."seen_by_member_id"
jbe@489 2593 AND "sent"."initiative_id" = "initiative"."id"
jbe@477 2594 ORDER BY
jbe@477 2595 "seen_by_member_id",
jbe@477 2596 "initiative"."issue_id",
jbe@477 2597 "initiative"."harmonic_weight" DESC,
jbe@477 2598 "initiative"."id"
jbe@477 2599 ) AS "subquery"
jbe@477 2600 WHERE NOT EXISTS (
jbe@477 2601 SELECT NULL FROM "updated_or_featured_initiative" AS "other"
jbe@477 2602 WHERE "other"."seen_by_member_id" = "subquery"."seen_by_member_id"
jbe@477 2603 AND "other"."id" = "subquery"."id"
jbe@477 2604 );
jbe@474 2605
jbe@490 2606 CREATE VIEW "unfiltered_initiative_for_notification" AS
jbe@474 2607 SELECT * FROM "updated_or_featured_initiative"
jbe@477 2608 UNION ALL
jbe@474 2609 SELECT * FROM "leading_complement_initiative";
jbe@474 2610
jbe@490 2611 CREATE VIEW "initiative_for_notification" AS
jbe@490 2612 SELECT "initiative1".*
jbe@490 2613 FROM "unfiltered_initiative_for_notification" "initiative1"
jbe@490 2614 JOIN "issue" AS "issue1" ON "initiative1"."issue_id" = "issue1"."id"
jbe@490 2615 WHERE EXISTS (
jbe@490 2616 SELECT NULL
jbe@490 2617 FROM "unfiltered_initiative_for_notification" "initiative2"
jbe@490 2618 JOIN "issue" AS "issue2" ON "initiative2"."issue_id" = "issue2"."id"
jbe@490 2619 WHERE "initiative1"."seen_by_member_id" = "initiative2"."seen_by_member_id"
jbe@490 2620 AND "issue1"."area_id" = "issue2"."area_id"
jbe@490 2621 AND ( "initiative2"."new_draft" OR "initiative2"."new_suggestion_count" > 0 )
jbe@490 2622 );
jbe@490 2623
jbe@497 2624 CREATE VIEW "newsletter_to_send" AS
jbe@497 2625 SELECT
jbe@497 2626 "newsletter"."id" AS "newsletter_id",
jbe@497 2627 "member"."id" AS "member_id"
jbe@497 2628 FROM "newsletter" CROSS JOIN "member"
jbe@497 2629 LEFT JOIN "privilege" ON
jbe@497 2630 "privilege"."member_id" = "member"."id" AND
jbe@497 2631 "privilege"."unit_id" = "newsletter"."unit_id" AND
jbe@497 2632 "privilege"."voting_right" = TRUE
jbe@497 2633 LEFT JOIN "subscription" ON
jbe@497 2634 "subscription"."member_id" = "member"."id" AND
jbe@497 2635 "subscription"."unit_id" = "newsletter"."unit_id"
jbe@498 2636 WHERE "newsletter"."published" <= now()
jbe@497 2637 AND "newsletter"."sent" ISNULL
jbe@497 2638 AND "member"."locked" = FALSE
jbe@497 2639 AND (
jbe@497 2640 "member"."disable_notifications" = FALSE OR
jbe@497 2641 "newsletter"."include_all_members" = TRUE )
jbe@497 2642 AND (
jbe@497 2643 "newsletter"."unit_id" ISNULL OR
jbe@497 2644 "privilege"."member_id" NOTNULL OR
jbe@497 2645 "subscription"."member_id" NOTNULL );
jbe@497 2646
jbe@473 2647
jbe@0 2648
jbe@242 2649 ------------------------------------------------------
jbe@242 2650 -- Row set returning function for delegation chains --
jbe@242 2651 ------------------------------------------------------
jbe@5 2652
jbe@5 2653
jbe@5 2654 CREATE TYPE "delegation_chain_loop_tag" AS ENUM
jbe@5 2655 ('first', 'intermediate', 'last', 'repetition');
jbe@5 2656
jbe@5 2657 COMMENT ON TYPE "delegation_chain_loop_tag" IS 'Type for loop tags in "delegation_chain_row" type';
jbe@5 2658
jbe@5 2659
jbe@5 2660 CREATE TYPE "delegation_chain_row" AS (
jbe@5 2661 "index" INT4,
jbe@5 2662 "member_id" INT4,
jbe@97 2663 "member_valid" BOOLEAN,
jbe@5 2664 "participation" BOOLEAN,
jbe@5 2665 "overridden" BOOLEAN,
jbe@5 2666 "scope_in" "delegation_scope",
jbe@5 2667 "scope_out" "delegation_scope",
jbe@86 2668 "disabled_out" BOOLEAN,
jbe@5 2669 "loop" "delegation_chain_loop_tag" );
jbe@5 2670
jbe@243 2671 COMMENT ON TYPE "delegation_chain_row" IS 'Type of rows returned by "delegation_chain" function';
jbe@5 2672
jbe@5 2673 COMMENT ON COLUMN "delegation_chain_row"."index" IS 'Index starting with 0 and counting up';
jbe@5 2674 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 2675 COMMENT ON COLUMN "delegation_chain_row"."overridden" IS 'True, if an entry with lower index has "participation" set to true';
jbe@5 2676 COMMENT ON COLUMN "delegation_chain_row"."scope_in" IS 'Scope of used incoming delegation';
jbe@5 2677 COMMENT ON COLUMN "delegation_chain_row"."scope_out" IS 'Scope of used outgoing delegation';
jbe@86 2678 COMMENT ON COLUMN "delegation_chain_row"."disabled_out" IS 'Outgoing delegation is explicitly disabled by a delegation with trustee_id set to NULL';
jbe@5 2679 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 2680
jbe@5 2681
jbe@242 2682 CREATE FUNCTION "delegation_chain_for_closed_issue"
jbe@242 2683 ( "member_id_p" "member"."id"%TYPE,
jbe@242 2684 "issue_id_p" "issue"."id"%TYPE )
jbe@242 2685 RETURNS SETOF "delegation_chain_row"
jbe@242 2686 LANGUAGE 'plpgsql' STABLE AS $$
jbe@242 2687 DECLARE
jbe@242 2688 "output_row" "delegation_chain_row";
jbe@242 2689 "direct_voter_row" "direct_voter"%ROWTYPE;
jbe@242 2690 "delegating_voter_row" "delegating_voter"%ROWTYPE;
jbe@242 2691 BEGIN
jbe@242 2692 "output_row"."index" := 0;
jbe@242 2693 "output_row"."member_id" := "member_id_p";
jbe@242 2694 "output_row"."member_valid" := TRUE;
jbe@242 2695 "output_row"."participation" := FALSE;
jbe@242 2696 "output_row"."overridden" := FALSE;
jbe@242 2697 "output_row"."disabled_out" := FALSE;
jbe@242 2698 LOOP
jbe@242 2699 SELECT INTO "direct_voter_row" * FROM "direct_voter"
jbe@242 2700 WHERE "issue_id" = "issue_id_p"
jbe@242 2701 AND "member_id" = "output_row"."member_id";
jbe@242 2702 IF "direct_voter_row"."member_id" NOTNULL THEN
jbe@242 2703 "output_row"."participation" := TRUE;
jbe@242 2704 "output_row"."scope_out" := NULL;
jbe@242 2705 "output_row"."disabled_out" := NULL;
jbe@242 2706 RETURN NEXT "output_row";
jbe@242 2707 RETURN;
jbe@242 2708 END IF;
jbe@242 2709 SELECT INTO "delegating_voter_row" * FROM "delegating_voter"
jbe@242 2710 WHERE "issue_id" = "issue_id_p"
jbe@242 2711 AND "member_id" = "output_row"."member_id";
jbe@242 2712 IF "delegating_voter_row"."member_id" ISNULL THEN
jbe@242 2713 RETURN;
jbe@242 2714 END IF;
jbe@242 2715 "output_row"."scope_out" := "delegating_voter_row"."scope";
jbe@242 2716 RETURN NEXT "output_row";
jbe@242 2717 "output_row"."member_id" := "delegating_voter_row"."delegate_member_ids"[1];
jbe@242 2718 "output_row"."scope_in" := "output_row"."scope_out";
jbe@242 2719 END LOOP;
jbe@242 2720 END;
jbe@242 2721 $$;
jbe@242 2722
jbe@242 2723 COMMENT ON FUNCTION "delegation_chain_for_closed_issue"
jbe@242 2724 ( "member"."id"%TYPE,
jbe@242 2725 "member"."id"%TYPE )
jbe@242 2726 IS 'Helper function for "delegation_chain" function, handling the special case of closed issues after voting';
jbe@242 2727
jbe@242 2728
jbe@5 2729 CREATE FUNCTION "delegation_chain"
jbe@5 2730 ( "member_id_p" "member"."id"%TYPE,
jbe@97 2731 "unit_id_p" "unit"."id"%TYPE,
jbe@5 2732 "area_id_p" "area"."id"%TYPE,
jbe@5 2733 "issue_id_p" "issue"."id"%TYPE,
jbe@255 2734 "simulate_trustee_id_p" "member"."id"%TYPE DEFAULT NULL,
jbe@255 2735 "simulate_default_p" BOOLEAN DEFAULT FALSE )
jbe@5 2736 RETURNS SETOF "delegation_chain_row"
jbe@5 2737 LANGUAGE 'plpgsql' STABLE AS $$
jbe@5 2738 DECLARE
jbe@97 2739 "scope_v" "delegation_scope";
jbe@97 2740 "unit_id_v" "unit"."id"%TYPE;
jbe@97 2741 "area_id_v" "area"."id"%TYPE;
jbe@241 2742 "issue_row" "issue"%ROWTYPE;
jbe@5 2743 "visited_member_ids" INT4[]; -- "member"."id"%TYPE[]
jbe@5 2744 "loop_member_id_v" "member"."id"%TYPE;
jbe@5 2745 "output_row" "delegation_chain_row";
jbe@5 2746 "output_rows" "delegation_chain_row"[];
jbe@255 2747 "simulate_v" BOOLEAN;
jbe@255 2748 "simulate_here_v" BOOLEAN;
jbe@5 2749 "delegation_row" "delegation"%ROWTYPE;
jbe@5 2750 "row_count" INT4;
jbe@5 2751 "i" INT4;
jbe@5 2752 "loop_v" BOOLEAN;
jbe@5 2753 BEGIN
jbe@255 2754 IF "simulate_trustee_id_p" NOTNULL AND "simulate_default_p" THEN
jbe@255 2755 RAISE EXCEPTION 'Both "simulate_trustee_id_p" is set, and "simulate_default_p" is true';
jbe@255 2756 END IF;
jbe@255 2757 IF "simulate_trustee_id_p" NOTNULL OR "simulate_default_p" THEN
jbe@255 2758 "simulate_v" := TRUE;
jbe@255 2759 ELSE
jbe@255 2760 "simulate_v" := FALSE;
jbe@255 2761 END IF;
jbe@97 2762 IF
jbe@97 2763 "unit_id_p" NOTNULL AND
jbe@97 2764 "area_id_p" ISNULL AND
jbe@97 2765 "issue_id_p" ISNULL
jbe@97 2766 THEN
jbe@97 2767 "scope_v" := 'unit';
jbe@97 2768 "unit_id_v" := "unit_id_p";
jbe@97 2769 ELSIF
jbe@97 2770 "unit_id_p" ISNULL AND
jbe@97 2771 "area_id_p" NOTNULL AND
jbe@97 2772 "issue_id_p" ISNULL
jbe@97 2773 THEN
jbe@97 2774 "scope_v" := 'area';
jbe@97 2775 "area_id_v" := "area_id_p";
jbe@97 2776 SELECT "unit_id" INTO "unit_id_v"
jbe@97 2777 FROM "area" WHERE "id" = "area_id_v";
jbe@97 2778 ELSIF
jbe@97 2779 "unit_id_p" ISNULL AND
jbe@97 2780 "area_id_p" ISNULL AND
jbe@97 2781 "issue_id_p" NOTNULL
jbe@97 2782 THEN
jbe@242 2783 SELECT INTO "issue_row" * FROM "issue" WHERE "id" = "issue_id_p";
jbe@242 2784 IF "issue_row"."id" ISNULL THEN
jbe@242 2785 RETURN;
jbe@242 2786 END IF;
jbe@242 2787 IF "issue_row"."closed" NOTNULL THEN
jbe@255 2788 IF "simulate_v" THEN
jbe@242 2789 RAISE EXCEPTION 'Tried to simulate delegation chain for closed issue.';
jbe@242 2790 END IF;
jbe@242 2791 FOR "output_row" IN
jbe@242 2792 SELECT * FROM
jbe@242 2793 "delegation_chain_for_closed_issue"("member_id_p", "issue_id_p")
jbe@242 2794 LOOP
jbe@242 2795 RETURN NEXT "output_row";
jbe@242 2796 END LOOP;
jbe@242 2797 RETURN;
jbe@242 2798 END IF;
jbe@97 2799 "scope_v" := 'issue';
jbe@97 2800 SELECT "area_id" INTO "area_id_v"
jbe@97 2801 FROM "issue" WHERE "id" = "issue_id_p";
jbe@97 2802 SELECT "unit_id" INTO "unit_id_v"
jbe@97 2803 FROM "area" WHERE "id" = "area_id_v";
jbe@97 2804 ELSE
jbe@97 2805 RAISE EXCEPTION 'Exactly one of unit_id_p, area_id_p, or issue_id_p must be NOTNULL.';
jbe@97 2806 END IF;
jbe@5 2807 "visited_member_ids" := '{}';
jbe@5 2808 "loop_member_id_v" := NULL;
jbe@5 2809 "output_rows" := '{}';
jbe@5 2810 "output_row"."index" := 0;
jbe@5 2811 "output_row"."member_id" := "member_id_p";
jbe@97 2812 "output_row"."member_valid" := TRUE;
jbe@5 2813 "output_row"."participation" := FALSE;
jbe@5 2814 "output_row"."overridden" := FALSE;
jbe@86 2815 "output_row"."disabled_out" := FALSE;
jbe@5 2816 "output_row"."scope_out" := NULL;
jbe@5 2817 LOOP
jbe@5 2818 IF "visited_member_ids" @> ARRAY["output_row"."member_id"] THEN
jbe@5 2819 "loop_member_id_v" := "output_row"."member_id";
jbe@5 2820 ELSE
jbe@5 2821 "visited_member_ids" :=
jbe@5 2822 "visited_member_ids" || "output_row"."member_id";
jbe@5 2823 END IF;
jbe@241 2824 IF "output_row"."participation" ISNULL THEN
jbe@241 2825 "output_row"."overridden" := NULL;
jbe@241 2826 ELSIF "output_row"."participation" THEN
jbe@5 2827 "output_row"."overridden" := TRUE;
jbe@5 2828 END IF;
jbe@5 2829 "output_row"."scope_in" := "output_row"."scope_out";
jbe@255 2830 "output_row"."member_valid" := EXISTS (
jbe@97 2831 SELECT NULL FROM "member" JOIN "privilege"
jbe@97 2832 ON "privilege"."member_id" = "member"."id"
jbe@97 2833 AND "privilege"."unit_id" = "unit_id_v"
jbe@97 2834 WHERE "id" = "output_row"."member_id"
jbe@97 2835 AND "member"."active" AND "privilege"."voting_right"
jbe@255 2836 );
jbe@255 2837 "simulate_here_v" := (
jbe@255 2838 "simulate_v" AND
jbe@255 2839 "output_row"."member_id" = "member_id_p"
jbe@255 2840 );
jbe@255 2841 "delegation_row" := ROW(NULL);
jbe@255 2842 IF "output_row"."member_valid" OR "simulate_here_v" THEN
jbe@97 2843 IF "scope_v" = 'unit' THEN
jbe@255 2844 IF NOT "simulate_here_v" THEN
jbe@255 2845 SELECT * INTO "delegation_row" FROM "delegation"
jbe@255 2846 WHERE "truster_id" = "output_row"."member_id"
jbe@255 2847 AND "unit_id" = "unit_id_v";
jbe@255 2848 END IF;
jbe@97 2849 ELSIF "scope_v" = 'area' THEN
jbe@5 2850 "output_row"."participation" := EXISTS (
jbe@5 2851 SELECT NULL FROM "membership"
jbe@5 2852 WHERE "area_id" = "area_id_p"
jbe@5 2853 AND "member_id" = "output_row"."member_id"
jbe@5 2854 );
jbe@255 2855 IF "simulate_here_v" THEN
jbe@255 2856 IF "simulate_trustee_id_p" ISNULL THEN
jbe@255 2857 SELECT * INTO "delegation_row" FROM "delegation"
jbe@255 2858 WHERE "truster_id" = "output_row"."member_id"
jbe@255 2859 AND "unit_id" = "unit_id_v";
jbe@255 2860 END IF;
jbe@255 2861 ELSE
jbe@255 2862 SELECT * INTO "delegation_row" FROM "delegation"
jbe@255 2863 WHERE "truster_id" = "output_row"."member_id"
jbe@255 2864 AND (
jbe@255 2865 "unit_id" = "unit_id_v" OR
jbe@255 2866 "area_id" = "area_id_v"
jbe@255 2867 )
jbe@255 2868 ORDER BY "scope" DESC;
jbe@255 2869 END IF;
jbe@97 2870 ELSIF "scope_v" = 'issue' THEN
jbe@241 2871 IF "issue_row"."fully_frozen" ISNULL THEN
jbe@241 2872 "output_row"."participation" := EXISTS (
jbe@241 2873 SELECT NULL FROM "interest"
jbe@241 2874 WHERE "issue_id" = "issue_id_p"
jbe@241 2875 AND "member_id" = "output_row"."member_id"
jbe@241 2876 );
jbe@241 2877 ELSE
jbe@241 2878 IF "output_row"."member_id" = "member_id_p" THEN
jbe@241 2879 "output_row"."participation" := EXISTS (
jbe@241 2880 SELECT NULL FROM "direct_voter"
jbe@241 2881 WHERE "issue_id" = "issue_id_p"
jbe@241 2882 AND "member_id" = "output_row"."member_id"
jbe@241 2883 );
jbe@241 2884 ELSE
jbe@241 2885 "output_row"."participation" := NULL;
jbe@241 2886 END IF;
jbe@241 2887 END IF;
jbe@255 2888 IF "simulate_here_v" THEN
jbe@255 2889 IF "simulate_trustee_id_p" ISNULL THEN
jbe@255 2890 SELECT * INTO "delegation_row" FROM "delegation"
jbe@255 2891 WHERE "truster_id" = "output_row"."member_id"
jbe@255 2892 AND (
jbe@255 2893 "unit_id" = "unit_id_v" OR
jbe@255 2894 "area_id" = "area_id_v"
jbe@255 2895 )
jbe@255 2896 ORDER BY "scope" DESC;
jbe@255 2897 END IF;
jbe@255 2898 ELSE
jbe@255 2899 SELECT * INTO "delegation_row" FROM "delegation"
jbe@255 2900 WHERE "truster_id" = "output_row"."member_id"
jbe@255 2901 AND (
jbe@255 2902 "unit_id" = "unit_id_v" OR
jbe@255 2903 "area_id" = "area_id_v" OR
jbe@255 2904 "issue_id" = "issue_id_p"
jbe@255 2905 )
jbe@255 2906 ORDER BY "scope" DESC;
jbe@255 2907 END IF;
jbe@5 2908 END IF;
jbe@5 2909 ELSE
jbe@5 2910 "output_row"."participation" := FALSE;
jbe@5 2911 END IF;
jbe@255 2912 IF "simulate_here_v" AND "simulate_trustee_id_p" NOTNULL THEN
jbe@97 2913 "output_row"."scope_out" := "scope_v";
jbe@5 2914 "output_rows" := "output_rows" || "output_row";
jbe@5 2915 "output_row"."member_id" := "simulate_trustee_id_p";
jbe@5 2916 ELSIF "delegation_row"."trustee_id" NOTNULL THEN
jbe@10 2917 "output_row"."scope_out" := "delegation_row"."scope";
jbe@5 2918 "output_rows" := "output_rows" || "output_row";
jbe@5 2919 "output_row"."member_id" := "delegation_row"."trustee_id";
jbe@86 2920 ELSIF "delegation_row"."scope" NOTNULL THEN
jbe@86 2921 "output_row"."scope_out" := "delegation_row"."scope";
jbe@86 2922 "output_row"."disabled_out" := TRUE;
jbe@86 2923 "output_rows" := "output_rows" || "output_row";
jbe@86 2924 EXIT;
jbe@5 2925 ELSE
jbe@5 2926 "output_row"."scope_out" := NULL;
jbe@5 2927 "output_rows" := "output_rows" || "output_row";
jbe@5 2928 EXIT;
jbe@5 2929 END IF;
jbe@5 2930 EXIT WHEN "loop_member_id_v" NOTNULL;
jbe@5 2931 "output_row"."index" := "output_row"."index" + 1;
jbe@5 2932 END LOOP;
jbe@5 2933 "row_count" := array_upper("output_rows", 1);
jbe@5 2934 "i" := 1;
jbe@5 2935 "loop_v" := FALSE;
jbe@5 2936 LOOP
jbe@5 2937 "output_row" := "output_rows"["i"];
jbe@98 2938 EXIT WHEN "output_row" ISNULL; -- NOTE: ISNULL and NOT ... NOTNULL produce different results!
jbe@5 2939 IF "loop_v" THEN
jbe@5 2940 IF "i" + 1 = "row_count" THEN
jbe@5 2941 "output_row"."loop" := 'last';
jbe@5 2942 ELSIF "i" = "row_count" THEN
jbe@5 2943 "output_row"."loop" := 'repetition';
jbe@5 2944 ELSE
jbe@5 2945 "output_row"."loop" := 'intermediate';
jbe@5 2946 END IF;
jbe@5 2947 ELSIF "output_row"."member_id" = "loop_member_id_v" THEN
jbe@5 2948 "output_row"."loop" := 'first';
jbe@5 2949 "loop_v" := TRUE;
jbe@5 2950 END IF;
jbe@97 2951 IF "scope_v" = 'unit' THEN
jbe@5 2952 "output_row"."participation" := NULL;
jbe@5 2953 END IF;
jbe@5 2954 RETURN NEXT "output_row";
jbe@5 2955 "i" := "i" + 1;
jbe@5 2956 END LOOP;
jbe@5 2957 RETURN;
jbe@5 2958 END;
jbe@5 2959 $$;
jbe@5 2960
jbe@5 2961 COMMENT ON FUNCTION "delegation_chain"
jbe@5 2962 ( "member"."id"%TYPE,
jbe@97 2963 "unit"."id"%TYPE,
jbe@5 2964 "area"."id"%TYPE,
jbe@5 2965 "issue"."id"%TYPE,
jbe@255 2966 "member"."id"%TYPE,
jbe@255 2967 BOOLEAN )
jbe@242 2968 IS 'Shows a delegation chain for unit, area, or issue; See "delegation_chain_row" type for more information';
jbe@242 2969
jbe@242 2970
jbe@242 2971
jbe@242 2972 ---------------------------------------------------------
jbe@242 2973 -- Single row returning function for delegation chains --
jbe@242 2974 ---------------------------------------------------------
jbe@242 2975
jbe@242 2976
jbe@242 2977 CREATE TYPE "delegation_info_loop_type" AS ENUM
jbe@242 2978 ('own', 'first', 'first_ellipsis', 'other', 'other_ellipsis');
jbe@240 2979
jbe@243 2980 COMMENT ON TYPE "delegation_info_loop_type" IS 'Type of "delegation_loop" in "delegation_info_type"; ''own'' means loop to self, ''first'' means loop to first trustee, ''first_ellipsis'' means loop to ellipsis after first trustee, ''other'' means loop to other trustee, ''other_ellipsis'' means loop to ellipsis after other trustee''';
jbe@243 2981
jbe@243 2982
jbe@240 2983 CREATE TYPE "delegation_info_type" AS (
jbe@242 2984 "own_participation" BOOLEAN,
jbe@242 2985 "own_delegation_scope" "delegation_scope",
jbe@242 2986 "first_trustee_id" INT4,
jbe@240 2987 "first_trustee_participation" BOOLEAN,
jbe@242 2988 "first_trustee_ellipsis" BOOLEAN,
jbe@242 2989 "other_trustee_id" INT4,
jbe@240 2990 "other_trustee_participation" BOOLEAN,
jbe@242 2991 "other_trustee_ellipsis" BOOLEAN,
jbe@253 2992 "delegation_loop" "delegation_info_loop_type",
jbe@253 2993 "participating_member_id" INT4 );
jbe@240 2994
jbe@243 2995 COMMENT ON TYPE "delegation_info_type" IS 'Type of result returned by "delegation_info" function; For meaning of "participation" check comment on "delegation_chain_row" type';
jbe@243 2996
jbe@243 2997 COMMENT ON COLUMN "delegation_info_type"."own_participation" IS 'Member is directly participating';
jbe@243 2998 COMMENT ON COLUMN "delegation_info_type"."own_delegation_scope" IS 'Delegation scope of member';
jbe@243 2999 COMMENT ON COLUMN "delegation_info_type"."first_trustee_id" IS 'Direct trustee of member';
jbe@243 3000 COMMENT ON COLUMN "delegation_info_type"."first_trustee_participation" IS 'Direct trustee of member is participating';
jbe@243 3001 COMMENT ON COLUMN "delegation_info_type"."first_trustee_ellipsis" IS 'Ellipsis in delegation chain after "first_trustee"';
jbe@243 3002 COMMENT ON COLUMN "delegation_info_type"."other_trustee_id" IS 'Another relevant trustee (due to participation)';
jbe@243 3003 COMMENT ON COLUMN "delegation_info_type"."other_trustee_participation" IS 'Another trustee is participating (redundant field: if "other_trustee_id" is set, then "other_trustee_participation" is always TRUE, else "other_trustee_participation" is NULL)';
jbe@243 3004 COMMENT ON COLUMN "delegation_info_type"."other_trustee_ellipsis" IS 'Ellipsis in delegation chain after "other_trustee"';
jbe@243 3005 COMMENT ON COLUMN "delegation_info_type"."delegation_loop" IS 'Non-NULL value, if delegation chain contains a circle; See comment on "delegation_info_loop_type" for details';
jbe@253 3006 COMMENT ON COLUMN "delegation_info_type"."participating_member_id" IS 'First participating member in delegation chain';
jbe@243 3007
jbe@243 3008
jbe@240 3009 CREATE FUNCTION "delegation_info"
jbe@242 3010 ( "member_id_p" "member"."id"%TYPE,
jbe@242 3011 "unit_id_p" "unit"."id"%TYPE,
jbe@242 3012 "area_id_p" "area"."id"%TYPE,
jbe@242 3013 "issue_id_p" "issue"."id"%TYPE,
jbe@255 3014 "simulate_trustee_id_p" "member"."id"%TYPE DEFAULT NULL,
jbe@255 3015 "simulate_default_p" BOOLEAN DEFAULT FALSE )
jbe@240 3016 RETURNS "delegation_info_type"
jbe@240 3017 LANGUAGE 'plpgsql' STABLE AS $$
jbe@240 3018 DECLARE
jbe@242 3019 "current_row" "delegation_chain_row";
jbe@242 3020 "result" "delegation_info_type";
jbe@240 3021 BEGIN
jbe@242 3022 "result"."own_participation" := FALSE;
jbe@242 3023 FOR "current_row" IN
jbe@242 3024 SELECT * FROM "delegation_chain"(
jbe@242 3025 "member_id_p",
jbe@242 3026 "unit_id_p", "area_id_p", "issue_id_p",
jbe@255 3027 "simulate_trustee_id_p", "simulate_default_p")
jbe@242 3028 LOOP
jbe@253 3029 IF
jbe@253 3030 "result"."participating_member_id" ISNULL AND
jbe@253 3031 "current_row"."participation"
jbe@253 3032 THEN
jbe@253 3033 "result"."participating_member_id" := "current_row"."member_id";
jbe@253 3034 END IF;
jbe@242 3035 IF "current_row"."member_id" = "member_id_p" THEN
jbe@242 3036 "result"."own_participation" := "current_row"."participation";
jbe@242 3037 "result"."own_delegation_scope" := "current_row"."scope_out";
jbe@242 3038 IF "current_row"."loop" = 'first' THEN
jbe@242 3039 "result"."delegation_loop" := 'own';
jbe@242 3040 END IF;
jbe@242 3041 ELSIF
jbe@242 3042 "current_row"."member_valid" AND
jbe@242 3043 ( "current_row"."loop" ISNULL OR
jbe@242 3044 "current_row"."loop" != 'repetition' )
jbe@242 3045 THEN
jbe@242 3046 IF "result"."first_trustee_id" ISNULL THEN
jbe@242 3047 "result"."first_trustee_id" := "current_row"."member_id";
jbe@242 3048 "result"."first_trustee_participation" := "current_row"."participation";
jbe@242 3049 "result"."first_trustee_ellipsis" := FALSE;
jbe@242 3050 IF "current_row"."loop" = 'first' THEN
jbe@242 3051 "result"."delegation_loop" := 'first';
jbe@242 3052 END IF;
jbe@242 3053 ELSIF "result"."other_trustee_id" ISNULL THEN
jbe@247 3054 IF "current_row"."participation" AND NOT "current_row"."overridden" THEN
jbe@242 3055 "result"."other_trustee_id" := "current_row"."member_id";
jbe@242 3056 "result"."other_trustee_participation" := TRUE;
jbe@242 3057 "result"."other_trustee_ellipsis" := FALSE;
jbe@242 3058 IF "current_row"."loop" = 'first' THEN
jbe@242 3059 "result"."delegation_loop" := 'other';
jbe@240 3060 END IF;
jbe@240 3061 ELSE
jbe@242 3062 "result"."first_trustee_ellipsis" := TRUE;
jbe@242 3063 IF "current_row"."loop" = 'first' THEN
jbe@242 3064 "result"."delegation_loop" := 'first_ellipsis';
jbe@242 3065 END IF;
jbe@242 3066 END IF;
jbe@242 3067 ELSE
jbe@242 3068 "result"."other_trustee_ellipsis" := TRUE;
jbe@242 3069 IF "current_row"."loop" = 'first' THEN
jbe@242 3070 "result"."delegation_loop" := 'other_ellipsis';
jbe@240 3071 END IF;
jbe@240 3072 END IF;
jbe@240 3073 END IF;
jbe@242 3074 END LOOP;
jbe@240 3075 RETURN "result";
jbe@240 3076 END;
jbe@240 3077 $$;
jbe@240 3078
jbe@243 3079 COMMENT ON FUNCTION "delegation_info"
jbe@243 3080 ( "member"."id"%TYPE,
jbe@243 3081 "unit"."id"%TYPE,
jbe@243 3082 "area"."id"%TYPE,
jbe@243 3083 "issue"."id"%TYPE,
jbe@255 3084 "member"."id"%TYPE,
jbe@255 3085 BOOLEAN )
jbe@243 3086 IS 'Notable information about a delegation chain for unit, area, or issue; See "delegation_info_type" for more information';
jbe@243 3087
jbe@240 3088
jbe@240 3089
jbe@333 3090 ---------------------------
jbe@333 3091 -- Transaction isolation --
jbe@333 3092 ---------------------------
jbe@333 3093
jbe@344 3094
jbe@333 3095 CREATE FUNCTION "require_transaction_isolation"()
jbe@333 3096 RETURNS VOID
jbe@333 3097 LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@333 3098 BEGIN
jbe@333 3099 IF
jbe@333 3100 current_setting('transaction_isolation') NOT IN
jbe@333 3101 ('repeatable read', 'serializable')
jbe@333 3102 THEN
jbe@463 3103 RAISE EXCEPTION 'Insufficient transaction isolation level' USING
jbe@463 3104 HINT = 'Consider using SET TRANSACTION ISOLATION LEVEL REPEATABLE READ.';
jbe@333 3105 END IF;
jbe@333 3106 RETURN;
jbe@333 3107 END;
jbe@333 3108 $$;
jbe@333 3109
jbe@344 3110 COMMENT ON FUNCTION "require_transaction_isolation"() IS 'Throws an exception, if transaction isolation level is too low to provide a consistent snapshot';
jbe@344 3111
jbe@333 3112
jbe@333 3113 CREATE FUNCTION "dont_require_transaction_isolation"()
jbe@333 3114 RETURNS VOID
jbe@333 3115 LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@333 3116 BEGIN
jbe@333 3117 IF
jbe@333 3118 current_setting('transaction_isolation') IN
jbe@333 3119 ('repeatable read', 'serializable')
jbe@333 3120 THEN
jbe@333 3121 RAISE WARNING 'Unneccessary transaction isolation level: %',
jbe@333 3122 current_setting('transaction_isolation');
jbe@333 3123 END IF;
jbe@333 3124 RETURN;
jbe@333 3125 END;
jbe@333 3126 $$;
jbe@333 3127
jbe@344 3128 COMMENT ON FUNCTION "dont_require_transaction_isolation"() IS 'Raises a warning, if transaction isolation level is higher than READ COMMITTED';
jbe@344 3129
jbe@333 3130
jbe@333 3131
jbe@491 3132 -------------------------
jbe@491 3133 -- Notification system --
jbe@491 3134 -------------------------
jbe@491 3135
jbe@491 3136 CREATE FUNCTION "get_initiatives_for_notification"
jbe@491 3137 ( "member_id_p" "member"."id"%TYPE )
jbe@491 3138 RETURNS SETOF "initiative_for_notification"
jbe@491 3139 LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@491 3140 DECLARE
jbe@491 3141 "result_row" "initiative_for_notification"%ROWTYPE;
jbe@491 3142 "last_draft_id_v" "draft"."id"%TYPE;
jbe@491 3143 "last_suggestion_id_v" "suggestion"."id"%TYPE;
jbe@491 3144 BEGIN
jbe@491 3145 PERFORM "require_transaction_isolation"();
jbe@491 3146 PERFORM NULL FROM "member" WHERE "id" = "member_id_p" FOR UPDATE;
jbe@491 3147 FOR "result_row" IN
jbe@491 3148 SELECT * FROM "initiative_for_notification"
jbe@491 3149 WHERE "seen_by_member_id" = "member_id_p"
jbe@491 3150 LOOP
jbe@491 3151 SELECT "id" INTO "last_draft_id_v" FROM "draft"
jbe@491 3152 WHERE "draft"."initiative_id" = "result_row"."id"
jbe@491 3153 ORDER BY "id" DESC LIMIT 1;
jbe@491 3154 SELECT "id" INTO "last_suggestion_id_v" FROM "suggestion"
jbe@491 3155 WHERE "suggestion"."initiative_id" = "result_row"."id"
jbe@491 3156 ORDER BY "id" DESC LIMIT 1;
jbe@491 3157 INSERT INTO "initiative_notification_sent"
jbe@491 3158 ("member_id", "initiative_id", "last_draft_id", "last_suggestion_id")
jbe@491 3159 VALUES (
jbe@491 3160 "member_id_p",
jbe@491 3161 "result_row"."id",
jbe@493 3162 "last_draft_id_v",
jbe@493 3163 "last_suggestion_id_v" )
jbe@491 3164 ON CONFLICT ("member_id", "initiative_id") DO UPDATE SET
jbe@491 3165 "last_draft_id" = CASE
jbe@494 3166 WHEN "initiative_notification_sent"."last_draft_id" > "last_draft_id_v"
jbe@494 3167 THEN "initiative_notification_sent"."last_draft_id"
jbe@491 3168 ELSE "last_draft_id_v"
jbe@491 3169 END,
jbe@491 3170 "last_suggestion_id" = CASE
jbe@494 3171 WHEN "initiative_notification_sent"."last_suggestion_id" > "last_suggestion_id_v"
jbe@494 3172 THEN "initiative_notification_sent"."last_suggestion_id"
jbe@491 3173 ELSE "last_suggestion_id_v"
jbe@491 3174 END;
jbe@491 3175 RETURN NEXT "result_row";
jbe@491 3176 END LOOP;
jbe@491 3177 DELETE FROM "initiative_notification_sent"
jbe@491 3178 USING "initiative", "issue"
jbe@491 3179 WHERE "initiative_notification_sent"."member_id" = "member_id_p"
jbe@491 3180 AND "initiative"."id" = "initiative_notification_sent"."initiative_id"
jbe@491 3181 AND "issue"."id" = "initiative"."issue_id"
jbe@491 3182 AND ( "issue"."closed" NOTNULL OR "issue"."fully_frozen" NOTNULL );
jbe@491 3183 UPDATE "member" SET "notification_counter" = "notification_counter" + 1
jbe@491 3184 WHERE "id" = "member_id_p";
jbe@491 3185 RETURN;
jbe@491 3186 END;
jbe@491 3187 $$;
jbe@491 3188
jbe@491 3189
jbe@491 3190
jbe@103 3191 ------------------------------------------------------------------------
jbe@103 3192 -- Regular tasks, except calculcation of snapshots and voting results --
jbe@103 3193 ------------------------------------------------------------------------
jbe@103 3194
jbe@333 3195
jbe@184 3196 CREATE FUNCTION "check_activity"()
jbe@103 3197 RETURNS VOID
jbe@103 3198 LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@104 3199 DECLARE
jbe@104 3200 "system_setting_row" "system_setting"%ROWTYPE;
jbe@103 3201 BEGIN
jbe@333 3202 PERFORM "dont_require_transaction_isolation"();
jbe@104 3203 SELECT * INTO "system_setting_row" FROM "system_setting";
jbe@104 3204 IF "system_setting_row"."member_ttl" NOTNULL THEN
jbe@104 3205 UPDATE "member" SET "active" = FALSE
jbe@104 3206 WHERE "active" = TRUE
jbe@184 3207 AND "last_activity" < (now() - "system_setting_row"."member_ttl")::DATE;
jbe@104 3208 END IF;
jbe@103 3209 RETURN;
jbe@103 3210 END;
jbe@103 3211 $$;
jbe@103 3212
jbe@184 3213 COMMENT ON FUNCTION "check_activity"() IS 'Deactivates members when "last_activity" is older than "system_setting"."member_ttl".';
jbe@103 3214
jbe@4 3215
jbe@4 3216 CREATE FUNCTION "calculate_member_counts"()
jbe@4 3217 RETURNS VOID
jbe@4 3218 LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@4 3219 BEGIN
jbe@333 3220 PERFORM "require_transaction_isolation"();
jbe@4 3221 DELETE FROM "member_count";
jbe@5 3222 INSERT INTO "member_count" ("total_count")
jbe@5 3223 SELECT "total_count" FROM "member_count_view";
jbe@97 3224 UPDATE "unit" SET "member_count" = "view"."member_count"
jbe@97 3225 FROM "unit_member_count" AS "view"
jbe@97 3226 WHERE "view"."unit_id" = "unit"."id";
jbe@5 3227 UPDATE "area" SET
jbe@5 3228 "direct_member_count" = "view"."direct_member_count",
jbe@169 3229 "member_weight" = "view"."member_weight"
jbe@5 3230 FROM "area_member_count" AS "view"
jbe@5 3231 WHERE "view"."area_id" = "area"."id";
jbe@4 3232 RETURN;
jbe@4 3233 END;
jbe@4 3234 $$;
jbe@4 3235
jbe@4 3236 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 3237
jbe@4 3238
jbe@4 3239
jbe@327 3240 ------------------------------------
jbe@327 3241 -- Calculation of harmonic weight --
jbe@327 3242 ------------------------------------
jbe@310 3243
jbe@312 3244
jbe@310 3245 CREATE VIEW "remaining_harmonic_supporter_weight" AS
jbe@310 3246 SELECT
jbe@310 3247 "direct_interest_snapshot"."issue_id",
jbe@310 3248 "direct_interest_snapshot"."event",
jbe@310 3249 "direct_interest_snapshot"."member_id",
jbe@310 3250 "direct_interest_snapshot"."weight" AS "weight_num",
jbe@310 3251 count("initiative"."id") AS "weight_den"
jbe@312 3252 FROM "issue"
jbe@312 3253 JOIN "direct_interest_snapshot"
jbe@312 3254 ON "issue"."id" = "direct_interest_snapshot"."issue_id"
jbe@312 3255 AND "issue"."latest_snapshot_event" = "direct_interest_snapshot"."event"
jbe@327 3256 JOIN "initiative"
jbe@327 3257 ON "issue"."id" = "initiative"."issue_id"
jbe@327 3258 AND "initiative"."harmonic_weight" ISNULL
jbe@310 3259 JOIN "direct_supporter_snapshot"
jbe@327 3260 ON "initiative"."id" = "direct_supporter_snapshot"."initiative_id"
jbe@310 3261 AND "direct_interest_snapshot"."event" = "direct_supporter_snapshot"."event"
jbe@310 3262 AND "direct_interest_snapshot"."member_id" = "direct_supporter_snapshot"."member_id"
jbe@321 3263 AND (
jbe@321 3264 "direct_supporter_snapshot"."satisfied" = TRUE OR
jbe@321 3265 coalesce("initiative"."admitted", FALSE) = FALSE
jbe@321 3266 )
jbe@310 3267 GROUP BY
jbe@310 3268 "direct_interest_snapshot"."issue_id",
jbe@310 3269 "direct_interest_snapshot"."event",
jbe@310 3270 "direct_interest_snapshot"."member_id",
jbe@310 3271 "direct_interest_snapshot"."weight";
jbe@310 3272
jbe@310 3273 COMMENT ON VIEW "remaining_harmonic_supporter_weight" IS 'Helper view for function "set_harmonic_initiative_weights"';
jbe@310 3274
jbe@310 3275
jbe@310 3276 CREATE VIEW "remaining_harmonic_initiative_weight_summands" AS
jbe@310 3277 SELECT
jbe@310 3278 "initiative"."issue_id",
jbe@310 3279 "initiative"."id" AS "initiative_id",
jbe@320 3280 "initiative"."admitted",
jbe@310 3281 sum("remaining_harmonic_supporter_weight"."weight_num") AS "weight_num",
jbe@310 3282 "remaining_harmonic_supporter_weight"."weight_den"
jbe@310 3283 FROM "remaining_harmonic_supporter_weight"
jbe@327 3284 JOIN "initiative"
jbe@327 3285 ON "remaining_harmonic_supporter_weight"."issue_id" = "initiative"."issue_id"
jbe@327 3286 AND "initiative"."harmonic_weight" ISNULL
jbe@310 3287 JOIN "direct_supporter_snapshot"
jbe@327 3288 ON "initiative"."id" = "direct_supporter_snapshot"."initiative_id"
jbe@310 3289 AND "remaining_harmonic_supporter_weight"."event" = "direct_supporter_snapshot"."event"
jbe@310 3290 AND "remaining_harmonic_supporter_weight"."member_id" = "direct_supporter_snapshot"."member_id"
jbe@321 3291 AND (
jbe@321 3292 "direct_supporter_snapshot"."satisfied" = TRUE OR
jbe@321 3293 coalesce("initiative"."admitted", FALSE) = FALSE
jbe@321 3294 )
jbe@310 3295 GROUP BY
jbe@310 3296 "initiative"."issue_id",
jbe@310 3297 "initiative"."id",
jbe@320 3298 "initiative"."admitted",
jbe@310 3299 "remaining_harmonic_supporter_weight"."weight_den";
jbe@310 3300
jbe@310 3301 COMMENT ON VIEW "remaining_harmonic_initiative_weight_summands" IS 'Helper view for function "set_harmonic_initiative_weights"';
jbe@310 3302
jbe@310 3303
jbe@349 3304 CREATE VIEW "remaining_harmonic_initiative_weight_dummies" AS
jbe@349 3305 SELECT
jbe@349 3306 "issue_id",
jbe@349 3307 "id" AS "initiative_id",
jbe@349 3308 "admitted",
jbe@349 3309 0 AS "weight_num",
jbe@349 3310 1 AS "weight_den"
jbe@349 3311 FROM "initiative"
jbe@349 3312 WHERE "harmonic_weight" ISNULL;
jbe@349 3313
jbe@349 3314 COMMENT ON VIEW "remaining_harmonic_initiative_weight_dummies" IS 'Helper view for function "set_harmonic_initiative_weights" providing dummy weights of zero value, which are needed for corner cases where there are no supporters for an initiative at all';
jbe@349 3315
jbe@349 3316
jbe@310 3317 CREATE FUNCTION "set_harmonic_initiative_weights"
jbe@310 3318 ( "issue_id_p" "issue"."id"%TYPE )
jbe@310 3319 RETURNS VOID
jbe@310 3320 LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@310 3321 DECLARE
jbe@310 3322 "weight_row" "remaining_harmonic_initiative_weight_summands"%ROWTYPE;
jbe@310 3323 "i" INT4;
jbe@310 3324 "count_v" INT4;
jbe@310 3325 "summand_v" FLOAT;
jbe@310 3326 "id_ary" INT4[];
jbe@310 3327 "weight_ary" FLOAT[];
jbe@310 3328 "min_weight_v" FLOAT;
jbe@310 3329 BEGIN
jbe@333 3330 PERFORM "require_transaction_isolation"();
jbe@312 3331 UPDATE "initiative" SET "harmonic_weight" = NULL
jbe@312 3332 WHERE "issue_id" = "issue_id_p";
jbe@310 3333 LOOP
jbe@310 3334 "min_weight_v" := NULL;
jbe@310 3335 "i" := 0;
jbe@310 3336 "count_v" := 0;
jbe@310 3337 FOR "weight_row" IN
jbe@310 3338 SELECT * FROM "remaining_harmonic_initiative_weight_summands"
jbe@310 3339 WHERE "issue_id" = "issue_id_p"
jbe@320 3340 AND (
jbe@320 3341 coalesce("admitted", FALSE) = FALSE OR NOT EXISTS (
jbe@320 3342 SELECT NULL FROM "initiative"
jbe@320 3343 WHERE "issue_id" = "issue_id_p"
jbe@320 3344 AND "harmonic_weight" ISNULL
jbe@320 3345 AND coalesce("admitted", FALSE) = FALSE
jbe@320 3346 )
jbe@320 3347 )
jbe@349 3348 UNION ALL -- needed for corner cases
jbe@349 3349 SELECT * FROM "remaining_harmonic_initiative_weight_dummies"
jbe@349 3350 WHERE "issue_id" = "issue_id_p"
jbe@349 3351 AND (
jbe@349 3352 coalesce("admitted", FALSE) = FALSE OR NOT EXISTS (
jbe@349 3353 SELECT NULL FROM "initiative"
jbe@349 3354 WHERE "issue_id" = "issue_id_p"
jbe@349 3355 AND "harmonic_weight" ISNULL
jbe@349 3356 AND coalesce("admitted", FALSE) = FALSE
jbe@349 3357 )
jbe@349 3358 )
jbe@310 3359 ORDER BY "initiative_id" DESC, "weight_den" DESC
jbe@320 3360 -- NOTE: non-admitted initiatives placed first (at last positions),
jbe@320 3361 -- latest initiatives treated worse in case of tie
jbe@310 3362 LOOP
jbe@310 3363 "summand_v" := "weight_row"."weight_num"::FLOAT / "weight_row"."weight_den"::FLOAT;
jbe@310 3364 IF "i" = 0 OR "weight_row"."initiative_id" != "id_ary"["i"] THEN
jbe@310 3365 "i" := "i" + 1;
jbe@310 3366 "count_v" := "i";
jbe@310 3367 "id_ary"["i"] := "weight_row"."initiative_id";
jbe@310 3368 "weight_ary"["i"] := "summand_v";
jbe@310 3369 ELSE
jbe@310 3370 "weight_ary"["i"] := "weight_ary"["i"] + "summand_v";
jbe@310 3371 END IF;
jbe@310 3372 END LOOP;
jbe@310 3373 EXIT WHEN "count_v" = 0;
jbe@310 3374 "i" := 1;
jbe@310 3375 LOOP
jbe@313 3376 "weight_ary"["i"] := "weight_ary"["i"]::NUMERIC(18,9)::NUMERIC(12,3);
jbe@310 3377 IF "min_weight_v" ISNULL OR "weight_ary"["i"] < "min_weight_v" THEN
jbe@310 3378 "min_weight_v" := "weight_ary"["i"];
jbe@310 3379 END IF;
jbe@310 3380 "i" := "i" + 1;
jbe@310 3381 EXIT WHEN "i" > "count_v";
jbe@310 3382 END LOOP;
jbe@310 3383 "i" := 1;
jbe@310 3384 LOOP
jbe@310 3385 IF "weight_ary"["i"] = "min_weight_v" THEN
jbe@310 3386 UPDATE "initiative" SET "harmonic_weight" = "min_weight_v"
jbe@310 3387 WHERE "id" = "id_ary"["i"];
jbe@310 3388 EXIT;
jbe@310 3389 END IF;
jbe@310 3390 "i" := "i" + 1;
jbe@310 3391 END LOOP;
jbe@310 3392 END LOOP;
jbe@316 3393 UPDATE "initiative" SET "harmonic_weight" = 0
jbe@316 3394 WHERE "issue_id" = "issue_id_p" AND "harmonic_weight" ISNULL;
jbe@310 3395 END;
jbe@310 3396 $$;
jbe@310 3397
jbe@310 3398 COMMENT ON FUNCTION "set_harmonic_initiative_weights"
jbe@310 3399 ( "issue"."id"%TYPE )
jbe@310 3400 IS 'Calculates and sets "harmonic_weight" of initiatives in a given issue';
jbe@310 3401
jbe@310 3402
jbe@312 3403
jbe@0 3404 ------------------------------
jbe@0 3405 -- Calculation of snapshots --
jbe@0 3406 ------------------------------
jbe@0 3407
jbe@312 3408
jbe@0 3409 CREATE FUNCTION "weight_of_added_delegations_for_population_snapshot"
jbe@0 3410 ( "issue_id_p" "issue"."id"%TYPE,
jbe@0 3411 "member_id_p" "member"."id"%TYPE,
jbe@0 3412 "delegate_member_ids_p" "delegating_population_snapshot"."delegate_member_ids"%TYPE )
jbe@0 3413 RETURNS "direct_population_snapshot"."weight"%TYPE
jbe@0 3414 LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@0 3415 DECLARE
jbe@0 3416 "issue_delegation_row" "issue_delegation"%ROWTYPE;
jbe@0 3417 "delegate_member_ids_v" "delegating_population_snapshot"."delegate_member_ids"%TYPE;
jbe@0 3418 "weight_v" INT4;
jbe@8 3419 "sub_weight_v" INT4;
jbe@0 3420 BEGIN
jbe@336 3421 PERFORM "require_transaction_isolation"();
jbe@0 3422 "weight_v" := 0;
jbe@0 3423 FOR "issue_delegation_row" IN
jbe@0 3424 SELECT * FROM "issue_delegation"
jbe@0 3425 WHERE "trustee_id" = "member_id_p"
jbe@0 3426 AND "issue_id" = "issue_id_p"
jbe@0 3427 LOOP
jbe@0 3428 IF NOT EXISTS (
jbe@0 3429 SELECT NULL FROM "direct_population_snapshot"
jbe@0 3430 WHERE "issue_id" = "issue_id_p"
jbe@0 3431 AND "event" = 'periodic'
jbe@0 3432 AND "member_id" = "issue_delegation_row"."truster_id"
jbe@0 3433 ) AND NOT EXISTS (
jbe@0 3434 SELECT NULL FROM "delegating_population_snapshot"
jbe@0 3435 WHERE "issue_id" = "issue_id_p"
jbe@0 3436 AND "event" = 'periodic'
jbe@0 3437 AND "member_id" = "issue_delegation_row"."truster_id"
jbe@0 3438 ) THEN
jbe@0 3439 "delegate_member_ids_v" :=
jbe@0 3440 "member_id_p" || "delegate_member_ids_p";
jbe@10 3441 INSERT INTO "delegating_population_snapshot" (
jbe@10 3442 "issue_id",
jbe@10 3443 "event",
jbe@10 3444 "member_id",
jbe@10 3445 "scope",
jbe@10 3446 "delegate_member_ids"
jbe@10 3447 ) VALUES (
jbe@0 3448 "issue_id_p",
jbe@0 3449 'periodic',
jbe@0 3450 "issue_delegation_row"."truster_id",
jbe@10 3451 "issue_delegation_row"."scope",
jbe@0 3452 "delegate_member_ids_v"
jbe@0 3453 );
jbe@8 3454 "sub_weight_v" := 1 +
jbe@0 3455 "weight_of_added_delegations_for_population_snapshot"(
jbe@0 3456 "issue_id_p",
jbe@0 3457 "issue_delegation_row"."truster_id",
jbe@0 3458 "delegate_member_ids_v"
jbe@0 3459 );
jbe@8 3460 UPDATE "delegating_population_snapshot"
jbe@8 3461 SET "weight" = "sub_weight_v"
jbe@8 3462 WHERE "issue_id" = "issue_id_p"
jbe@8 3463 AND "event" = 'periodic'
jbe@8 3464 AND "member_id" = "issue_delegation_row"."truster_id";
jbe@8 3465 "weight_v" := "weight_v" + "sub_weight_v";
jbe@0 3466 END IF;
jbe@0 3467 END LOOP;
jbe@0 3468 RETURN "weight_v";
jbe@0 3469 END;
jbe@0 3470 $$;
jbe@0 3471
jbe@0 3472 COMMENT ON FUNCTION "weight_of_added_delegations_for_population_snapshot"
jbe@0 3473 ( "issue"."id"%TYPE,
jbe@0 3474 "member"."id"%TYPE,
jbe@0 3475 "delegating_population_snapshot"."delegate_member_ids"%TYPE )
jbe@0 3476 IS 'Helper function for "create_population_snapshot" function';
jbe@0 3477
jbe@0 3478
jbe@0 3479 CREATE FUNCTION "create_population_snapshot"
jbe@0 3480 ( "issue_id_p" "issue"."id"%TYPE )
jbe@0 3481 RETURNS VOID
jbe@0 3482 LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@0 3483 DECLARE
jbe@0 3484 "member_id_v" "member"."id"%TYPE;
jbe@0 3485 BEGIN
jbe@336 3486 PERFORM "require_transaction_isolation"();
jbe@0 3487 DELETE FROM "direct_population_snapshot"
jbe@0 3488 WHERE "issue_id" = "issue_id_p"
jbe@0 3489 AND "event" = 'periodic';
jbe@0 3490 DELETE FROM "delegating_population_snapshot"
jbe@0 3491 WHERE "issue_id" = "issue_id_p"
jbe@0 3492 AND "event" = 'periodic';
jbe@0 3493 INSERT INTO "direct_population_snapshot"
jbe@54 3494 ("issue_id", "event", "member_id")
jbe@54 3495 SELECT
jbe@54 3496 "issue_id_p" AS "issue_id",
jbe@54 3497 'periodic'::"snapshot_event" AS "event",
jbe@54 3498 "member"."id" AS "member_id"
jbe@54 3499 FROM "issue"
jbe@54 3500 JOIN "area" ON "issue"."area_id" = "area"."id"
jbe@54 3501 JOIN "membership" ON "area"."id" = "membership"."area_id"
jbe@54 3502 JOIN "member" ON "membership"."member_id" = "member"."id"
jbe@97 3503 JOIN "privilege"
jbe@97 3504 ON "privilege"."unit_id" = "area"."unit_id"
jbe@97 3505 AND "privilege"."member_id" = "member"."id"
jbe@54 3506 WHERE "issue"."id" = "issue_id_p"
jbe@97 3507 AND "member"."active" AND "privilege"."voting_right"
jbe@54 3508 UNION
jbe@54 3509 SELECT
jbe@54 3510 "issue_id_p" AS "issue_id",
jbe@54 3511 'periodic'::"snapshot_event" AS "event",
jbe@54 3512 "member"."id" AS "member_id"
jbe@97 3513 FROM "issue"
jbe@97 3514 JOIN "area" ON "issue"."area_id" = "area"."id"
jbe@97 3515 JOIN "interest" ON "issue"."id" = "interest"."issue_id"
jbe@97 3516 JOIN "member" ON "interest"."member_id" = "member"."id"
jbe@97 3517 JOIN "privilege"
jbe@97 3518 ON "privilege"."unit_id" = "area"."unit_id"
jbe@97 3519 AND "privilege"."member_id" = "member"."id"
jbe@97 3520 WHERE "issue"."id" = "issue_id_p"
jbe@97 3521 AND "member"."active" AND "privilege"."voting_right";
jbe@0 3522 FOR "member_id_v" IN
jbe@0 3523 SELECT "member_id" FROM "direct_population_snapshot"
jbe@0 3524 WHERE "issue_id" = "issue_id_p"
jbe@0 3525 AND "event" = 'periodic'
jbe@0 3526 LOOP
jbe@0 3527 UPDATE "direct_population_snapshot" SET
jbe@0 3528 "weight" = 1 +
jbe@0 3529 "weight_of_added_delegations_for_population_snapshot"(
jbe@0 3530 "issue_id_p",
jbe@0 3531 "member_id_v",
jbe@0 3532 '{}'
jbe@0 3533 )
jbe@0 3534 WHERE "issue_id" = "issue_id_p"
jbe@0 3535 AND "event" = 'periodic'
jbe@0 3536 AND "member_id" = "member_id_v";
jbe@0 3537 END LOOP;
jbe@0 3538 RETURN;
jbe@0 3539 END;
jbe@0 3540 $$;
jbe@0 3541
jbe@0 3542 COMMENT ON FUNCTION "create_population_snapshot"
jbe@67 3543 ( "issue"."id"%TYPE )
jbe@0 3544 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 3545
jbe@0 3546
jbe@0 3547 CREATE FUNCTION "weight_of_added_delegations_for_interest_snapshot"
jbe@0 3548 ( "issue_id_p" "issue"."id"%TYPE,
jbe@0 3549 "member_id_p" "member"."id"%TYPE,
jbe@0 3550 "delegate_member_ids_p" "delegating_interest_snapshot"."delegate_member_ids"%TYPE )
jbe@0 3551 RETURNS "direct_interest_snapshot"."weight"%TYPE
jbe@0 3552 LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@0 3553 DECLARE
jbe@0 3554 "issue_delegation_row" "issue_delegation"%ROWTYPE;
jbe@0 3555 "delegate_member_ids_v" "delegating_interest_snapshot"."delegate_member_ids"%TYPE;
jbe@0 3556 "weight_v" INT4;
jbe@8 3557 "sub_weight_v" INT4;
jbe@0 3558 BEGIN
jbe@336 3559 PERFORM "require_transaction_isolation"();
jbe@0 3560 "weight_v" := 0;
jbe@0 3561 FOR "issue_delegation_row" IN
jbe@0 3562 SELECT * FROM "issue_delegation"
jbe@0 3563 WHERE "trustee_id" = "member_id_p"
jbe@0 3564 AND "issue_id" = "issue_id_p"
jbe@0 3565 LOOP
jbe@0 3566 IF NOT EXISTS (
jbe@0 3567 SELECT NULL FROM "direct_interest_snapshot"
jbe@0 3568 WHERE "issue_id" = "issue_id_p"
jbe@0 3569 AND "event" = 'periodic'
jbe@0 3570 AND "member_id" = "issue_delegation_row"."truster_id"
jbe@0 3571 ) AND NOT EXISTS (
jbe@0 3572 SELECT NULL FROM "delegating_interest_snapshot"
jbe@0 3573 WHERE "issue_id" = "issue_id_p"
jbe@0 3574 AND "event" = 'periodic'
jbe@0 3575 AND "member_id" = "issue_delegation_row"."truster_id"
jbe@0 3576 ) THEN
jbe@0 3577 "delegate_member_ids_v" :=
jbe@0 3578 "member_id_p" || "delegate_member_ids_p";
jbe@10 3579 INSERT INTO "delegating_interest_snapshot" (
jbe@10 3580 "issue_id",
jbe@10 3581 "event",
jbe@10 3582 "member_id",
jbe@10 3583 "scope",
jbe@10 3584 "delegate_member_ids"
jbe@10 3585 ) VALUES (
jbe@0 3586 "issue_id_p",
jbe@0 3587 'periodic',
jbe@0 3588 "issue_delegation_row"."truster_id",
jbe@10 3589 "issue_delegation_row"."scope",
jbe@0 3590 "delegate_member_ids_v"
jbe@0 3591 );
jbe@8 3592 "sub_weight_v" := 1 +
jbe@0 3593 "weight_of_added_delegations_for_interest_snapshot"(
jbe@0 3594 "issue_id_p",
jbe@0 3595 "issue_delegation_row"."truster_id",
jbe@0 3596 "delegate_member_ids_v"
jbe@0 3597 );
jbe@8 3598 UPDATE "delegating_interest_snapshot"
jbe@8 3599 SET "weight" = "sub_weight_v"
jbe@8 3600 WHERE "issue_id" = "issue_id_p"
jbe@8 3601 AND "event" = 'periodic'
jbe@8 3602 AND "member_id" = "issue_delegation_row"."truster_id";
jbe@8 3603 "weight_v" := "weight_v" + "sub_weight_v";
jbe@0 3604 END IF;
jbe@0 3605 END LOOP;
jbe@0 3606 RETURN "weight_v";
jbe@0 3607 END;
jbe@0 3608 $$;
jbe@0 3609
jbe@0 3610 COMMENT ON FUNCTION "weight_of_added_delegations_for_interest_snapshot"
jbe@0 3611 ( "issue"."id"%TYPE,
jbe@0 3612 "member"."id"%TYPE,
jbe@0 3613 "delegating_interest_snapshot"."delegate_member_ids"%TYPE )
jbe@0 3614 IS 'Helper function for "create_interest_snapshot" function';
jbe@0 3615
jbe@0 3616
jbe@0 3617 CREATE FUNCTION "create_interest_snapshot"
jbe@0 3618 ( "issue_id_p" "issue"."id"%TYPE )
jbe@0 3619 RETURNS VOID
jbe@0 3620 LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@0 3621 DECLARE
jbe@0 3622 "member_id_v" "member"."id"%TYPE;
jbe@0 3623 BEGIN
jbe@336 3624 PERFORM "require_transaction_isolation"();
jbe@0 3625 DELETE FROM "direct_interest_snapshot"
jbe@0 3626 WHERE "issue_id" = "issue_id_p"
jbe@0 3627 AND "event" = 'periodic';
jbe@0 3628 DELETE FROM "delegating_interest_snapshot"
jbe@0 3629 WHERE "issue_id" = "issue_id_p"
jbe@0 3630 AND "event" = 'periodic';
jbe@0 3631 DELETE FROM "direct_supporter_snapshot"
jbe@325 3632 USING "initiative" -- NOTE: due to missing index on issue_id
jbe@325 3633 WHERE "initiative"."issue_id" = "issue_id_p"
jbe@325 3634 AND "direct_supporter_snapshot"."initiative_id" = "initiative"."id"
jbe@325 3635 AND "direct_supporter_snapshot"."event" = 'periodic';
jbe@0 3636 INSERT INTO "direct_interest_snapshot"
jbe@144 3637 ("issue_id", "event", "member_id")
jbe@0 3638 SELECT
jbe@0 3639 "issue_id_p" AS "issue_id",
jbe@0 3640 'periodic' AS "event",
jbe@144 3641 "member"."id" AS "member_id"
jbe@97 3642 FROM "issue"
jbe@97 3643 JOIN "area" ON "issue"."area_id" = "area"."id"
jbe@97 3644 JOIN "interest" ON "issue"."id" = "interest"."issue_id"
jbe@97 3645 JOIN "member" ON "interest"."member_id" = "member"."id"
jbe@97 3646 JOIN "privilege"
jbe@97 3647 ON "privilege"."unit_id" = "area"."unit_id"
jbe@97 3648 AND "privilege"."member_id" = "member"."id"
jbe@97 3649 WHERE "issue"."id" = "issue_id_p"
jbe@97 3650 AND "member"."active" AND "privilege"."voting_right";
jbe@0 3651 FOR "member_id_v" IN
jbe@0 3652 SELECT "member_id" FROM "direct_interest_snapshot"
jbe@0 3653 WHERE "issue_id" = "issue_id_p"
jbe@0 3654 AND "event" = 'periodic'
jbe@0 3655 LOOP
jbe@0 3656 UPDATE "direct_interest_snapshot" SET
jbe@0 3657 "weight" = 1 +
jbe@0 3658 "weight_of_added_delegations_for_interest_snapshot"(
jbe@0 3659 "issue_id_p",
jbe@0 3660 "member_id_v",
jbe@0 3661 '{}'
jbe@0 3662 )
jbe@0 3663 WHERE "issue_id" = "issue_id_p"
jbe@0 3664 AND "event" = 'periodic'
jbe@0 3665 AND "member_id" = "member_id_v";
jbe@0 3666 END LOOP;
jbe@0 3667 INSERT INTO "direct_supporter_snapshot"
jbe@0 3668 ( "issue_id", "initiative_id", "event", "member_id",
jbe@204 3669 "draft_id", "informed", "satisfied" )
jbe@0 3670 SELECT
jbe@96 3671 "issue_id_p" AS "issue_id",
jbe@96 3672 "initiative"."id" AS "initiative_id",
jbe@96 3673 'periodic' AS "event",
jbe@96 3674 "supporter"."member_id" AS "member_id",
jbe@204 3675 "supporter"."draft_id" AS "draft_id",
jbe@0 3676 "supporter"."draft_id" = "current_draft"."id" AS "informed",
jbe@0 3677 NOT EXISTS (
jbe@0 3678 SELECT NULL FROM "critical_opinion"
jbe@0 3679 WHERE "initiative_id" = "initiative"."id"
jbe@96 3680 AND "member_id" = "supporter"."member_id"
jbe@0 3681 ) AS "satisfied"
jbe@96 3682 FROM "initiative"
jbe@96 3683 JOIN "supporter"
jbe@0 3684 ON "supporter"."initiative_id" = "initiative"."id"
jbe@0 3685 JOIN "current_draft"
jbe@0 3686 ON "initiative"."id" = "current_draft"."initiative_id"
jbe@0 3687 JOIN "direct_interest_snapshot"
jbe@96 3688 ON "supporter"."member_id" = "direct_interest_snapshot"."member_id"
jbe@0 3689 AND "initiative"."issue_id" = "direct_interest_snapshot"."issue_id"
jbe@3 3690 AND "event" = 'periodic'
jbe@96 3691 WHERE "initiative"."issue_id" = "issue_id_p";
jbe@0 3692 RETURN;
jbe@0 3693 END;
jbe@0 3694 $$;
jbe@0 3695
jbe@0 3696 COMMENT ON FUNCTION "create_interest_snapshot"
jbe@0 3697 ( "issue"."id"%TYPE )
jbe@0 3698 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 3699
jbe@0 3700
jbe@0 3701 CREATE FUNCTION "create_snapshot"
jbe@0 3702 ( "issue_id_p" "issue"."id"%TYPE )
jbe@0 3703 RETURNS VOID
jbe@0 3704 LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@0 3705 DECLARE
jbe@0 3706 "initiative_id_v" "initiative"."id"%TYPE;
jbe@0 3707 "suggestion_id_v" "suggestion"."id"%TYPE;
jbe@0 3708 BEGIN
jbe@333 3709 PERFORM "require_transaction_isolation"();
jbe@0 3710 PERFORM "create_population_snapshot"("issue_id_p");
jbe@0 3711 PERFORM "create_interest_snapshot"("issue_id_p");
jbe@0 3712 UPDATE "issue" SET
jbe@331 3713 "snapshot" = coalesce("phase_finished", now()),
jbe@8 3714 "latest_snapshot_event" = 'periodic',
jbe@0 3715 "population" = (
jbe@0 3716 SELECT coalesce(sum("weight"), 0)
jbe@0 3717 FROM "direct_population_snapshot"
jbe@0 3718 WHERE "issue_id" = "issue_id_p"
jbe@0 3719 AND "event" = 'periodic'
jbe@0 3720 )
jbe@0 3721 WHERE "id" = "issue_id_p";
jbe@0 3722 FOR "initiative_id_v" IN
jbe@0 3723 SELECT "id" FROM "initiative" WHERE "issue_id" = "issue_id_p"
jbe@0 3724 LOOP
jbe@0 3725 UPDATE "initiative" SET
jbe@0 3726 "supporter_count" = (
jbe@0 3727 SELECT coalesce(sum("di"."weight"), 0)
jbe@0 3728 FROM "direct_interest_snapshot" AS "di"
jbe@0 3729 JOIN "direct_supporter_snapshot" AS "ds"
jbe@0 3730 ON "di"."member_id" = "ds"."member_id"
jbe@0 3731 WHERE "di"."issue_id" = "issue_id_p"
jbe@0 3732 AND "di"."event" = 'periodic'
jbe@0 3733 AND "ds"."initiative_id" = "initiative_id_v"
jbe@0 3734 AND "ds"."event" = 'periodic'
jbe@0 3735 ),
jbe@0 3736 "informed_supporter_count" = (
jbe@0 3737 SELECT coalesce(sum("di"."weight"), 0)
jbe@0 3738 FROM "direct_interest_snapshot" AS "di"
jbe@0 3739 JOIN "direct_supporter_snapshot" AS "ds"
jbe@0 3740 ON "di"."member_id" = "ds"."member_id"
jbe@0 3741 WHERE "di"."issue_id" = "issue_id_p"
jbe@0 3742 AND "di"."event" = 'periodic'
jbe@0 3743 AND "ds"."initiative_id" = "initiative_id_v"
jbe@0 3744 AND "ds"."event" = 'periodic'
jbe@0 3745 AND "ds"."informed"
jbe@0 3746 ),
jbe@0 3747 "satisfied_supporter_count" = (
jbe@0 3748 SELECT coalesce(sum("di"."weight"), 0)
jbe@0 3749 FROM "direct_interest_snapshot" AS "di"
jbe@0 3750 JOIN "direct_supporter_snapshot" AS "ds"
jbe@0 3751 ON "di"."member_id" = "ds"."member_id"
jbe@0 3752 WHERE "di"."issue_id" = "issue_id_p"
jbe@0 3753 AND "di"."event" = 'periodic'
jbe@0 3754 AND "ds"."initiative_id" = "initiative_id_v"
jbe@0 3755 AND "ds"."event" = 'periodic'
jbe@0 3756 AND "ds"."satisfied"
jbe@0 3757 ),
jbe@0 3758 "satisfied_informed_supporter_count" = (
jbe@0 3759 SELECT coalesce(sum("di"."weight"), 0)
jbe@0 3760 FROM "direct_interest_snapshot" AS "di"
jbe@0 3761 JOIN "direct_supporter_snapshot" AS "ds"
jbe@0 3762 ON "di"."member_id" = "ds"."member_id"
jbe@0 3763 WHERE "di"."issue_id" = "issue_id_p"
jbe@0 3764 AND "di"."event" = 'periodic'
jbe@0 3765 AND "ds"."initiative_id" = "initiative_id_v"
jbe@0 3766 AND "ds"."event" = 'periodic'
jbe@0 3767 AND "ds"."informed"
jbe@0 3768 AND "ds"."satisfied"
jbe@0 3769 )
jbe@0 3770 WHERE "id" = "initiative_id_v";
jbe@0 3771 FOR "suggestion_id_v" IN
jbe@0 3772 SELECT "id" FROM "suggestion"
jbe@0 3773 WHERE "initiative_id" = "initiative_id_v"
jbe@0 3774 LOOP
jbe@0 3775 UPDATE "suggestion" SET
jbe@0 3776 "minus2_unfulfilled_count" = (
jbe@0 3777 SELECT coalesce(sum("snapshot"."weight"), 0)
jbe@36 3778 FROM "issue" CROSS JOIN "opinion"
jbe@36 3779 JOIN "direct_interest_snapshot" AS "snapshot"
jbe@36 3780 ON "snapshot"."issue_id" = "issue"."id"
jbe@36 3781 AND "snapshot"."event" = "issue"."latest_snapshot_event"
jbe@36 3782 AND "snapshot"."member_id" = "opinion"."member_id"
jbe@36 3783 WHERE "issue"."id" = "issue_id_p"
jbe@36 3784 AND "opinion"."suggestion_id" = "suggestion_id_v"
jbe@0 3785 AND "opinion"."degree" = -2
jbe@0 3786 AND "opinion"."fulfilled" = FALSE
jbe@0 3787 ),
jbe@0 3788 "minus2_fulfilled_count" = (
jbe@0 3789 SELECT coalesce(sum("snapshot"."weight"), 0)
jbe@36 3790 FROM "issue" CROSS JOIN "opinion"
jbe@36 3791 JOIN "direct_interest_snapshot" AS "snapshot"
jbe@36 3792 ON "snapshot"."issue_id" = "issue"."id"
jbe@36 3793 AND "snapshot"."event" = "issue"."latest_snapshot_event"
jbe@36 3794 AND "snapshot"."member_id" = "opinion"."member_id"
jbe@36 3795 WHERE "issue"."id" = "issue_id_p"
jbe@36 3796 AND "opinion"."suggestion_id" = "suggestion_id_v"
jbe@0 3797 AND "opinion"."degree" = -2
jbe@0 3798 AND "opinion"."fulfilled" = TRUE
jbe@0 3799 ),
jbe@0 3800 "minus1_unfulfilled_count" = (
jbe@0 3801 SELECT coalesce(sum("snapshot"."weight"), 0)
jbe@36 3802 FROM "issue" CROSS JOIN "opinion"
jbe@36 3803 JOIN "direct_interest_snapshot" AS "snapshot"
jbe@36 3804 ON "snapshot"."issue_id" = "issue"."id"
jbe@36 3805 AND "snapshot"."event" = "issue"."latest_snapshot_event"
jbe@36 3806 AND "snapshot"."member_id" = "opinion"."member_id"
jbe@36 3807 WHERE "issue"."id" = "issue_id_p"
jbe@36 3808 AND "opinion"."suggestion_id" = "suggestion_id_v"
jbe@0 3809 AND "opinion"."degree" = -1
jbe@0 3810 AND "opinion"."fulfilled" = FALSE
jbe@0 3811 ),
jbe@0 3812 "minus1_fulfilled_count" = (
jbe@0 3813 SELECT coalesce(sum("snapshot"."weight"), 0)
jbe@36 3814 FROM "issue" CROSS JOIN "opinion"
jbe@36 3815 JOIN "direct_interest_snapshot" AS "snapshot"
jbe@36 3816 ON "snapshot"."issue_id" = "issue"."id"
jbe@36 3817 AND "snapshot"."event" = "issue"."latest_snapshot_event"
jbe@36 3818 AND "snapshot"."member_id" = "opinion"."member_id"
jbe@36 3819 WHERE "issue"."id" = "issue_id_p"
jbe@36 3820 AND "opinion"."suggestion_id" = "suggestion_id_v"
jbe@0 3821 AND "opinion"."degree" = -1
jbe@0 3822 AND "opinion"."fulfilled" = TRUE
jbe@0 3823 ),
jbe@0 3824 "plus1_unfulfilled_count" = (
jbe@0 3825 SELECT coalesce(sum("snapshot"."weight"), 0)
jbe@36 3826 FROM "issue" CROSS JOIN "opinion"
jbe@36 3827 JOIN "direct_interest_snapshot" AS "snapshot"
jbe@36 3828 ON "snapshot"."issue_id" = "issue"."id"
jbe@36 3829 AND "snapshot"."event" = "issue"."latest_snapshot_event"
jbe@36 3830 AND "snapshot"."member_id" = "opinion"."member_id"
jbe@36 3831 WHERE "issue"."id" = "issue_id_p"
jbe@36 3832 AND "opinion"."suggestion_id" = "suggestion_id_v"
jbe@0 3833 AND "opinion"."degree" = 1
jbe@0 3834 AND "opinion"."fulfilled" = FALSE
jbe@0 3835 ),
jbe@0 3836 "plus1_fulfilled_count" = (
jbe@0 3837 SELECT coalesce(sum("snapshot"."weight"), 0)
jbe@36 3838 FROM "issue" CROSS JOIN "opinion"
jbe@36 3839 JOIN "direct_interest_snapshot" AS "snapshot"
jbe@36 3840 ON "snapshot"."issue_id" = "issue"."id"
jbe@36 3841 AND "snapshot"."event" = "issue"."latest_snapshot_event"
jbe@36 3842 AND "snapshot"."member_id" = "opinion"."member_id"
jbe@36 3843 WHERE "issue"."id" = "issue_id_p"
jbe@36 3844 AND "opinion"."suggestion_id" = "suggestion_id_v"
jbe@0 3845 AND "opinion"."degree" = 1
jbe@0 3846 AND "opinion"."fulfilled" = TRUE
jbe@0 3847 ),
jbe@0 3848 "plus2_unfulfilled_count" = (
jbe@0 3849 SELECT coalesce(sum("snapshot"."weight"), 0)
jbe@36 3850 FROM "issue" CROSS JOIN "opinion"
jbe@36 3851 JOIN "direct_interest_snapshot" AS "snapshot"
jbe@36 3852 ON "snapshot"."issue_id" = "issue"."id"
jbe@36 3853 AND "snapshot"."event" = "issue"."latest_snapshot_event"
jbe@36 3854 AND "snapshot"."member_id" = "opinion"."member_id"
jbe@36 3855 WHERE "issue"."id" = "issue_id_p"
jbe@36 3856 AND "opinion"."suggestion_id" = "suggestion_id_v"
jbe@0 3857 AND "opinion"."degree" = 2
jbe@0 3858 AND "opinion"."fulfilled" = FALSE
jbe@0 3859 ),
jbe@0 3860 "plus2_fulfilled_count" = (
jbe@0 3861 SELECT coalesce(sum("snapshot"."weight"), 0)
jbe@36 3862 FROM "issue" CROSS JOIN "opinion"
jbe@36 3863 JOIN "direct_interest_snapshot" AS "snapshot"
jbe@36 3864 ON "snapshot"."issue_id" = "issue"."id"
jbe@36 3865 AND "snapshot"."event" = "issue"."latest_snapshot_event"
jbe@36 3866 AND "snapshot"."member_id" = "opinion"."member_id"
jbe@36 3867 WHERE "issue"."id" = "issue_id_p"
jbe@36 3868 AND "opinion"."suggestion_id" = "suggestion_id_v"
jbe@0 3869 AND "opinion"."degree" = 2
jbe@0 3870 AND "opinion"."fulfilled" = TRUE
jbe@0 3871 )
jbe@0 3872 WHERE "suggestion"."id" = "suggestion_id_v";
jbe@0 3873 END LOOP;
jbe@0 3874 END LOOP;
jbe@0 3875 RETURN;
jbe@0 3876 END;
jbe@0 3877 $$;
jbe@0 3878
jbe@0 3879 COMMENT ON FUNCTION "create_snapshot"
jbe@0 3880 ( "issue"."id"%TYPE )
jbe@0 3881 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 3882
jbe@0 3883
jbe@0 3884 CREATE FUNCTION "set_snapshot_event"
jbe@0 3885 ( "issue_id_p" "issue"."id"%TYPE,
jbe@0 3886 "event_p" "snapshot_event" )
jbe@0 3887 RETURNS VOID
jbe@0 3888 LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@21 3889 DECLARE
jbe@21 3890 "event_v" "issue"."latest_snapshot_event"%TYPE;
jbe@0 3891 BEGIN
jbe@333 3892 PERFORM "require_transaction_isolation"();
jbe@21 3893 SELECT "latest_snapshot_event" INTO "event_v" FROM "issue"
jbe@21 3894 WHERE "id" = "issue_id_p" FOR UPDATE;
jbe@8 3895 UPDATE "issue" SET "latest_snapshot_event" = "event_p"
jbe@8 3896 WHERE "id" = "issue_id_p";
jbe@3 3897 UPDATE "direct_population_snapshot" SET "event" = "event_p"
jbe@21 3898 WHERE "issue_id" = "issue_id_p" AND "event" = "event_v";
jbe@3 3899 UPDATE "delegating_population_snapshot" SET "event" = "event_p"
jbe@21 3900 WHERE "issue_id" = "issue_id_p" AND "event" = "event_v";
jbe@3 3901 UPDATE "direct_interest_snapshot" SET "event" = "event_p"
jbe@21 3902 WHERE "issue_id" = "issue_id_p" AND "event" = "event_v";
jbe@3 3903 UPDATE "delegating_interest_snapshot" SET "event" = "event_p"
jbe@21 3904 WHERE "issue_id" = "issue_id_p" AND "event" = "event_v";
jbe@3 3905 UPDATE "direct_supporter_snapshot" SET "event" = "event_p"
jbe@325 3906 FROM "initiative" -- NOTE: due to missing index on issue_id
jbe@325 3907 WHERE "initiative"."issue_id" = "issue_id_p"
jbe@325 3908 AND "direct_supporter_snapshot"."initiative_id" = "initiative"."id"
jbe@325 3909 AND "direct_supporter_snapshot"."event" = "event_v";
jbe@0 3910 RETURN;
jbe@0 3911 END;
jbe@0 3912 $$;
jbe@0 3913
jbe@0 3914 COMMENT ON FUNCTION "set_snapshot_event"
jbe@0 3915 ( "issue"."id"%TYPE,
jbe@0 3916 "snapshot_event" )
jbe@0 3917 IS 'Change "event" attribute of the previous ''periodic'' snapshot';
jbe@0 3918
jbe@0 3919
jbe@0 3920
jbe@0 3921 -----------------------
jbe@0 3922 -- Counting of votes --
jbe@0 3923 -----------------------
jbe@0 3924
jbe@0 3925
jbe@5 3926 CREATE FUNCTION "weight_of_added_vote_delegations"
jbe@0 3927 ( "issue_id_p" "issue"."id"%TYPE,
jbe@0 3928 "member_id_p" "member"."id"%TYPE,
jbe@0 3929 "delegate_member_ids_p" "delegating_voter"."delegate_member_ids"%TYPE )
jbe@0 3930 RETURNS "direct_voter"."weight"%TYPE
jbe@0 3931 LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@0 3932 DECLARE
jbe@0 3933 "issue_delegation_row" "issue_delegation"%ROWTYPE;
jbe@0 3934 "delegate_member_ids_v" "delegating_voter"."delegate_member_ids"%TYPE;
jbe@0 3935 "weight_v" INT4;
jbe@8 3936 "sub_weight_v" INT4;
jbe@0 3937 BEGIN
jbe@336 3938 PERFORM "require_transaction_isolation"();
jbe@0 3939 "weight_v" := 0;
jbe@0 3940 FOR "issue_delegation_row" IN
jbe@0 3941 SELECT * FROM "issue_delegation"
jbe@0 3942 WHERE "trustee_id" = "member_id_p"
jbe@0 3943 AND "issue_id" = "issue_id_p"
jbe@0 3944 LOOP
jbe@0 3945 IF NOT EXISTS (
jbe@0 3946 SELECT NULL FROM "direct_voter"
jbe@0 3947 WHERE "member_id" = "issue_delegation_row"."truster_id"
jbe@0 3948 AND "issue_id" = "issue_id_p"
jbe@0 3949 ) AND NOT EXISTS (
jbe@0 3950 SELECT NULL FROM "delegating_voter"
jbe@0 3951 WHERE "member_id" = "issue_delegation_row"."truster_id"
jbe@0 3952 AND "issue_id" = "issue_id_p"
jbe@0 3953 ) THEN
jbe@0 3954 "delegate_member_ids_v" :=
jbe@0 3955 "member_id_p" || "delegate_member_ids_p";
jbe@10 3956 INSERT INTO "delegating_voter" (
jbe@10 3957 "issue_id",
jbe@10 3958 "member_id",
jbe@10 3959 "scope",
jbe@10 3960 "delegate_member_ids"
jbe@10 3961 ) VALUES (
jbe@5 3962 "issue_id_p",
jbe@5 3963 "issue_delegation_row"."truster_id",
jbe@10 3964 "issue_delegation_row"."scope",
jbe@5 3965 "delegate_member_ids_v"
jbe@5 3966 );
jbe@8 3967 "sub_weight_v" := 1 +
jbe@8 3968 "weight_of_added_vote_delegations"(
jbe@8 3969 "issue_id_p",
jbe@8 3970 "issue_delegation_row"."truster_id",
jbe@8 3971 "delegate_member_ids_v"
jbe@8 3972 );
jbe@8 3973 UPDATE "delegating_voter"
jbe@8 3974 SET "weight" = "sub_weight_v"
jbe@8 3975 WHERE "issue_id" = "issue_id_p"
jbe@8 3976 AND "member_id" = "issue_delegation_row"."truster_id";
jbe@8 3977 "weight_v" := "weight_v" + "sub_weight_v";
jbe@0 3978 END IF;
jbe@0 3979 END LOOP;
jbe@0 3980 RETURN "weight_v";
jbe@0 3981 END;
jbe@0 3982 $$;
jbe@0 3983
jbe@5 3984 COMMENT ON FUNCTION "weight_of_added_vote_delegations"
jbe@0 3985 ( "issue"."id"%TYPE,
jbe@0 3986 "member"."id"%TYPE,
jbe@0 3987 "delegating_voter"."delegate_member_ids"%TYPE )
jbe@0 3988 IS 'Helper function for "add_vote_delegations" function';
jbe@0 3989
jbe@0 3990
jbe@0 3991 CREATE FUNCTION "add_vote_delegations"
jbe@0 3992 ( "issue_id_p" "issue"."id"%TYPE )
jbe@0 3993 RETURNS VOID
jbe@0 3994 LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@0 3995 DECLARE
jbe@0 3996 "member_id_v" "member"."id"%TYPE;
jbe@0 3997 BEGIN
jbe@336 3998 PERFORM "require_transaction_isolation"();
jbe@0 3999 FOR "member_id_v" IN
jbe@0 4000 SELECT "member_id" FROM "direct_voter"
jbe@0 4001 WHERE "issue_id" = "issue_id_p"
jbe@0 4002 LOOP
jbe@0 4003 UPDATE "direct_voter" SET
jbe@5 4004 "weight" = "weight" + "weight_of_added_vote_delegations"(
jbe@0 4005 "issue_id_p",
jbe@0 4006 "member_id_v",
jbe@0 4007 '{}'
jbe@0 4008 )
jbe@0 4009 WHERE "member_id" = "member_id_v"
jbe@0 4010 AND "issue_id" = "issue_id_p";
jbe@0 4011 END LOOP;
jbe@0 4012 RETURN;
jbe@0 4013 END;
jbe@0 4014 $$;
jbe@0 4015
jbe@0 4016 COMMENT ON FUNCTION "add_vote_delegations"
jbe@0 4017 ( "issue_id_p" "issue"."id"%TYPE )
jbe@0 4018 IS 'Helper function for "close_voting" function';
jbe@0 4019
jbe@0 4020
jbe@0 4021 CREATE FUNCTION "close_voting"("issue_id_p" "issue"."id"%TYPE)
jbe@0 4022 RETURNS VOID
jbe@0 4023 LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@0 4024 DECLARE
jbe@97 4025 "area_id_v" "area"."id"%TYPE;
jbe@97 4026 "unit_id_v" "unit"."id"%TYPE;
jbe@0 4027 "member_id_v" "member"."id"%TYPE;
jbe@0 4028 BEGIN
jbe@333 4029 PERFORM "require_transaction_isolation"();
jbe@129 4030 SELECT "area_id" INTO "area_id_v" FROM "issue" WHERE "id" = "issue_id_p";
jbe@129 4031 SELECT "unit_id" INTO "unit_id_v" FROM "area" WHERE "id" = "area_id_v";
jbe@383 4032 -- override protection triggers:
jbe@385 4033 INSERT INTO "temporary_transaction_data" ("key", "value")
jbe@385 4034 VALUES ('override_protection_triggers', TRUE::TEXT);
jbe@285 4035 -- delete timestamp of voting comment:
jbe@285 4036 UPDATE "direct_voter" SET "comment_changed" = NULL
jbe@285 4037 WHERE "issue_id" = "issue_id_p";
jbe@169 4038 -- delete delegating votes (in cases of manual reset of issue state):
jbe@0 4039 DELETE FROM "delegating_voter"
jbe@0 4040 WHERE "issue_id" = "issue_id_p";
jbe@169 4041 -- delete votes from non-privileged voters:
jbe@97 4042 DELETE FROM "direct_voter"
jbe@97 4043 USING (
jbe@97 4044 SELECT
jbe@97 4045 "direct_voter"."member_id"
jbe@97 4046 FROM "direct_voter"
jbe@97 4047 JOIN "member" ON "direct_voter"."member_id" = "member"."id"
jbe@97 4048 LEFT JOIN "privilege"
jbe@97 4049 ON "privilege"."unit_id" = "unit_id_v"
jbe@97 4050 AND "privilege"."member_id" = "direct_voter"."member_id"
jbe@97 4051 WHERE "direct_voter"."issue_id" = "issue_id_p" AND (
jbe@97 4052 "member"."active" = FALSE OR
jbe@97 4053 "privilege"."voting_right" ISNULL OR
jbe@97 4054 "privilege"."voting_right" = FALSE
jbe@97 4055 )
jbe@97 4056 ) AS "subquery"
jbe@97 4057 WHERE "direct_voter"."issue_id" = "issue_id_p"
jbe@97 4058 AND "direct_voter"."member_id" = "subquery"."member_id";
jbe@169 4059 -- consider delegations:
jbe@0 4060 UPDATE "direct_voter" SET "weight" = 1
jbe@0 4061 WHERE "issue_id" = "issue_id_p";
jbe@0 4062 PERFORM "add_vote_delegations"("issue_id_p");
jbe@414 4063 -- mark first preferences:
jbe@414 4064 UPDATE "vote" SET "first_preference" = "subquery"."first_preference"
jbe@414 4065 FROM (
jbe@414 4066 SELECT
jbe@414 4067 "vote"."initiative_id",
jbe@414 4068 "vote"."member_id",
jbe@414 4069 CASE WHEN "vote"."grade" > 0 THEN
jbe@414 4070 CASE WHEN "vote"."grade" = max("agg"."grade") THEN TRUE ELSE FALSE END
jbe@414 4071 ELSE NULL
jbe@414 4072 END AS "first_preference"
jbe@415 4073 FROM "vote"
jbe@415 4074 JOIN "initiative" -- NOTE: due to missing index on issue_id
jbe@415 4075 ON "vote"."issue_id" = "initiative"."issue_id"
jbe@415 4076 JOIN "vote" AS "agg"
jbe@415 4077 ON "initiative"."id" = "agg"."initiative_id"
jbe@415 4078 AND "vote"."member_id" = "agg"."member_id"
jbe@433 4079 GROUP BY "vote"."initiative_id", "vote"."member_id", "vote"."grade"
jbe@414 4080 ) AS "subquery"
jbe@414 4081 WHERE "vote"."issue_id" = "issue_id_p"
jbe@414 4082 AND "vote"."initiative_id" = "subquery"."initiative_id"
jbe@414 4083 AND "vote"."member_id" = "subquery"."member_id";
jbe@385 4084 -- finish overriding protection triggers (avoids garbage):
jbe@385 4085 DELETE FROM "temporary_transaction_data"
jbe@385 4086 WHERE "key" = 'override_protection_triggers';
jbe@137 4087 -- materialize battle_view:
jbe@61 4088 -- NOTE: "closed" column of issue must be set at this point
jbe@61 4089 DELETE FROM "battle" WHERE "issue_id" = "issue_id_p";
jbe@61 4090 INSERT INTO "battle" (
jbe@61 4091 "issue_id",
jbe@61 4092 "winning_initiative_id", "losing_initiative_id",
jbe@61 4093 "count"
jbe@61 4094 ) SELECT
jbe@61 4095 "issue_id",
jbe@61 4096 "winning_initiative_id", "losing_initiative_id",
jbe@61 4097 "count"
jbe@61 4098 FROM "battle_view" WHERE "issue_id" = "issue_id_p";
jbe@331 4099 -- set voter count:
jbe@331 4100 UPDATE "issue" SET
jbe@331 4101 "voter_count" = (
jbe@331 4102 SELECT coalesce(sum("weight"), 0)
jbe@331 4103 FROM "direct_voter" WHERE "issue_id" = "issue_id_p"
jbe@331 4104 )
jbe@331 4105 WHERE "id" = "issue_id_p";
jbe@437 4106 -- copy "positive_votes" and "negative_votes" from "battle" table:
jbe@437 4107 -- NOTE: "first_preference_votes" is set to a default of 0 at this step
jbe@437 4108 UPDATE "initiative" SET
jbe@437 4109 "first_preference_votes" = 0,
jbe@437 4110 "positive_votes" = "battle_win"."count",
jbe@437 4111 "negative_votes" = "battle_lose"."count"
jbe@437 4112 FROM "battle" AS "battle_win", "battle" AS "battle_lose"
jbe@437 4113 WHERE
jbe@437 4114 "battle_win"."issue_id" = "issue_id_p" AND
jbe@437 4115 "battle_win"."winning_initiative_id" = "initiative"."id" AND
jbe@437 4116 "battle_win"."losing_initiative_id" ISNULL AND
jbe@437 4117 "battle_lose"."issue_id" = "issue_id_p" AND
jbe@437 4118 "battle_lose"."losing_initiative_id" = "initiative"."id" AND
jbe@437 4119 "battle_lose"."winning_initiative_id" ISNULL;
jbe@414 4120 -- calculate "first_preference_votes":
jbe@437 4121 -- NOTE: will only set values not equal to zero
jbe@437 4122 UPDATE "initiative" SET "first_preference_votes" = "subquery"."sum"
jbe@414 4123 FROM (
jbe@414 4124 SELECT "vote"."initiative_id", sum("direct_voter"."weight")
jbe@414 4125 FROM "vote" JOIN "direct_voter"
jbe@414 4126 ON "vote"."issue_id" = "direct_voter"."issue_id"
jbe@414 4127 AND "vote"."member_id" = "direct_voter"."member_id"
jbe@414 4128 WHERE "vote"."first_preference"
jbe@414 4129 GROUP BY "vote"."initiative_id"
jbe@414 4130 ) AS "subquery"
jbe@414 4131 WHERE "initiative"."issue_id" = "issue_id_p"
jbe@414 4132 AND "initiative"."admitted"
jbe@414 4133 AND "initiative"."id" = "subquery"."initiative_id";
jbe@0 4134 END;
jbe@0 4135 $$;
jbe@0 4136
jbe@0 4137 COMMENT ON FUNCTION "close_voting"
jbe@0 4138 ( "issue"."id"%TYPE )
jbe@0 4139 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 4140
jbe@0 4141
jbe@30 4142 CREATE FUNCTION "defeat_strength"
jbe@424 4143 ( "positive_votes_p" INT4,
jbe@424 4144 "negative_votes_p" INT4,
jbe@424 4145 "defeat_strength_p" "defeat_strength" )
jbe@30 4146 RETURNS INT8
jbe@30 4147 LANGUAGE 'plpgsql' IMMUTABLE AS $$
jbe@30 4148 BEGIN
jbe@424 4149 IF "defeat_strength_p" = 'simple'::"defeat_strength" THEN
jbe@424 4150 IF "positive_votes_p" > "negative_votes_p" THEN
jbe@424 4151 RETURN "positive_votes_p";
jbe@424 4152 ELSE
jbe@424 4153 RETURN 0;
jbe@424 4154 END IF;
jbe@30 4155 ELSE
jbe@424 4156 IF "positive_votes_p" > "negative_votes_p" THEN
jbe@424 4157 RETURN ("positive_votes_p"::INT8 << 31) - "negative_votes_p"::INT8;
jbe@424 4158 ELSIF "positive_votes_p" = "negative_votes_p" THEN
jbe@424 4159 RETURN 0;
jbe@424 4160 ELSE
jbe@424 4161 RETURN -1;
jbe@424 4162 END IF;
jbe@30 4163 END IF;
jbe@30 4164 END;
jbe@30 4165 $$;
jbe@30 4166
jbe@425 4167 COMMENT ON FUNCTION "defeat_strength"(INT4, INT4, "defeat_strength") IS 'Calculates defeat strength (INT8!) according to the "defeat_strength" option (see comment on type "defeat_strength")';
jbe@30 4168
jbe@30 4169
jbe@423 4170 CREATE FUNCTION "secondary_link_strength"
jbe@426 4171 ( "initiative1_ord_p" INT4,
jbe@426 4172 "initiative2_ord_p" INT4,
jbe@424 4173 "tie_breaking_p" "tie_breaking" )
jbe@423 4174 RETURNS INT8
jbe@423 4175 LANGUAGE 'plpgsql' IMMUTABLE AS $$
jbe@423 4176 BEGIN
jbe@426 4177 IF "initiative1_ord_p" = "initiative2_ord_p" THEN
jbe@423 4178 RAISE EXCEPTION 'Identical initiative ids passed to "secondary_link_strength" function (should not happen)';
jbe@423 4179 END IF;
jbe@423 4180 RETURN (
jbe@426 4181 CASE WHEN "tie_breaking_p" = 'simple'::"tie_breaking" THEN
jbe@426 4182 0
jbe@424 4183 ELSE
jbe@426 4184 CASE WHEN "initiative1_ord_p" < "initiative2_ord_p" THEN
jbe@426 4185 1::INT8 << 62
jbe@426 4186 ELSE 0 END
jbe@426 4187 +
jbe@426 4188 CASE WHEN "tie_breaking_p" = 'variant2'::"tie_breaking" THEN
jbe@426 4189 ("initiative2_ord_p"::INT8 << 31) - "initiative1_ord_p"::INT8
jbe@426 4190 ELSE
jbe@426 4191 "initiative2_ord_p"::INT8 - ("initiative1_ord_p"::INT8 << 31)
jbe@426 4192 END
jbe@424 4193 END
jbe@423 4194 );
jbe@423 4195 END;
jbe@423 4196 $$;
jbe@423 4197
jbe@424 4198 COMMENT ON FUNCTION "secondary_link_strength"(INT4, INT4, "tie_breaking") IS 'Calculates a secondary criterion for the defeat strength (tie-breaking of the links)';
jbe@423 4199
jbe@423 4200
jbe@426 4201 CREATE TYPE "link_strength" AS (
jbe@426 4202 "primary" INT8,
jbe@426 4203 "secondary" INT8 );
jbe@426 4204
jbe@428 4205 COMMENT ON TYPE "link_strength" IS 'Type to store the defeat strength of a link between two candidates plus a secondary criterion to create unique link strengths between the candidates (needed for tie-breaking ''variant1'' and ''variant2'')';
jbe@427 4206
jbe@427 4207
jbe@427 4208 CREATE FUNCTION "find_best_paths"("matrix_d" "link_strength"[][])
jbe@427 4209 RETURNS "link_strength"[][]
jbe@427 4210 LANGUAGE 'plpgsql' IMMUTABLE AS $$
jbe@427 4211 DECLARE
jbe@427 4212 "dimension_v" INT4;
jbe@427 4213 "matrix_p" "link_strength"[][];
jbe@427 4214 "i" INT4;
jbe@427 4215 "j" INT4;
jbe@427 4216 "k" INT4;
jbe@427 4217 BEGIN
jbe@427 4218 "dimension_v" := array_upper("matrix_d", 1);
jbe@427 4219 "matrix_p" := "matrix_d";
jbe@427 4220 "i" := 1;
jbe@427 4221 LOOP
jbe@427 4222 "j" := 1;
jbe@427 4223 LOOP
jbe@427 4224 IF "i" != "j" THEN
jbe@427 4225 "k" := 1;
jbe@427 4226 LOOP
jbe@427 4227 IF "i" != "k" AND "j" != "k" THEN
jbe@427 4228 IF "matrix_p"["j"]["i"] < "matrix_p"["i"]["k"] THEN
jbe@427 4229 IF "matrix_p"["j"]["i"] > "matrix_p"["j"]["k"] THEN
jbe@427 4230 "matrix_p"["j"]["k"] := "matrix_p"["j"]["i"];
jbe@427 4231 END IF;
jbe@427 4232 ELSE
jbe@427 4233 IF "matrix_p"["i"]["k"] > "matrix_p"["j"]["k"] THEN
jbe@427 4234 "matrix_p"["j"]["k"] := "matrix_p"["i"]["k"];
jbe@427 4235 END IF;
jbe@427 4236 END IF;
jbe@427 4237 END IF;
jbe@427 4238 EXIT WHEN "k" = "dimension_v";
jbe@427 4239 "k" := "k" + 1;
jbe@427 4240 END LOOP;
jbe@427 4241 END IF;
jbe@427 4242 EXIT WHEN "j" = "dimension_v";
jbe@427 4243 "j" := "j" + 1;
jbe@427 4244 END LOOP;
jbe@427 4245 EXIT WHEN "i" = "dimension_v";
jbe@427 4246 "i" := "i" + 1;
jbe@427 4247 END LOOP;
jbe@427 4248 RETURN "matrix_p";
jbe@427 4249 END;
jbe@427 4250 $$;
jbe@427 4251
jbe@428 4252 COMMENT ON FUNCTION "find_best_paths"("link_strength"[][]) IS 'Computes the strengths of the best beat-paths from a square matrix';
jbe@426 4253
jbe@426 4254
jbe@0 4255 CREATE FUNCTION "calculate_ranks"("issue_id_p" "issue"."id"%TYPE)
jbe@0 4256 RETURNS VOID
jbe@0 4257 LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@0 4258 DECLARE
jbe@427 4259 "issue_row" "issue"%ROWTYPE;
jbe@427 4260 "policy_row" "policy"%ROWTYPE;
jbe@427 4261 "dimension_v" INT4;
jbe@427 4262 "matrix_a" INT4[][]; -- absolute votes
jbe@427 4263 "matrix_d" "link_strength"[][]; -- defeat strength (direct)
jbe@427 4264 "matrix_p" "link_strength"[][]; -- defeat strength (best path)
jbe@427 4265 "matrix_t" "link_strength"[][]; -- defeat strength (tie-breaking)
jbe@427 4266 "matrix_f" BOOLEAN[][]; -- forbidden link (tie-breaking)
jbe@427 4267 "matrix_b" BOOLEAN[][]; -- final order (who beats who)
jbe@427 4268 "i" INT4;
jbe@427 4269 "j" INT4;
jbe@427 4270 "m" INT4;
jbe@427 4271 "n" INT4;
jbe@427 4272 "battle_row" "battle"%ROWTYPE;
jbe@427 4273 "rank_ary" INT4[];
jbe@427 4274 "rank_v" INT4;
jbe@427 4275 "initiative_id_v" "initiative"."id"%TYPE;
jbe@0 4276 BEGIN
jbe@333 4277 PERFORM "require_transaction_isolation"();
jbe@155 4278 SELECT * INTO "issue_row"
jbe@331 4279 FROM "issue" WHERE "id" = "issue_id_p";
jbe@155 4280 SELECT * INTO "policy_row"
jbe@155 4281 FROM "policy" WHERE "id" = "issue_row"."policy_id";
jbe@126 4282 SELECT count(1) INTO "dimension_v"
jbe@126 4283 FROM "battle_participant" WHERE "issue_id" = "issue_id_p";
jbe@428 4284 -- create "matrix_a" with absolute number of votes in pairwise
jbe@170 4285 -- comparison:
jbe@427 4286 "matrix_a" := array_fill(NULL::INT4, ARRAY["dimension_v", "dimension_v"]);
jbe@170 4287 "i" := 1;
jbe@170 4288 "j" := 2;
jbe@170 4289 FOR "battle_row" IN
jbe@170 4290 SELECT * FROM "battle" WHERE "issue_id" = "issue_id_p"
jbe@170 4291 ORDER BY
jbe@411 4292 "winning_initiative_id" NULLS FIRST,
jbe@411 4293 "losing_initiative_id" NULLS FIRST
jbe@170 4294 LOOP
jbe@427 4295 "matrix_a"["i"]["j"] := "battle_row"."count";
jbe@170 4296 IF "j" = "dimension_v" THEN
jbe@170 4297 "i" := "i" + 1;
jbe@170 4298 "j" := 1;
jbe@170 4299 ELSE
jbe@170 4300 "j" := "j" + 1;
jbe@170 4301 IF "j" = "i" THEN
jbe@170 4302 "j" := "j" + 1;
jbe@170 4303 END IF;
jbe@170 4304 END IF;
jbe@170 4305 END LOOP;
jbe@170 4306 IF "i" != "dimension_v" OR "j" != "dimension_v" + 1 THEN
jbe@170 4307 RAISE EXCEPTION 'Wrong battle count (should not happen)';
jbe@170 4308 END IF;
jbe@428 4309 -- store direct defeat strengths in "matrix_d" using "defeat_strength"
jbe@427 4310 -- and "secondary_link_strength" functions:
jbe@427 4311 "matrix_d" := array_fill(NULL::INT8, ARRAY["dimension_v", "dimension_v"]);
jbe@170 4312 "i" := 1;
jbe@170 4313 LOOP
jbe@170 4314 "j" := 1;
jbe@0 4315 LOOP
jbe@170 4316 IF "i" != "j" THEN
jbe@427 4317 "matrix_d"["i"]["j"] := (
jbe@426 4318 "defeat_strength"(
jbe@427 4319 "matrix_a"["i"]["j"],
jbe@427 4320 "matrix_a"["j"]["i"],
jbe@426 4321 "policy_row"."defeat_strength"
jbe@426 4322 ),
jbe@426 4323 "secondary_link_strength"(
jbe@426 4324 "i",
jbe@426 4325 "j",
jbe@426 4326 "policy_row"."tie_breaking"
jbe@426 4327 )
jbe@426 4328 )::"link_strength";
jbe@0 4329 END IF;
jbe@170 4330 EXIT WHEN "j" = "dimension_v";
jbe@170 4331 "j" := "j" + 1;
jbe@0 4332 END LOOP;
jbe@170 4333 EXIT WHEN "i" = "dimension_v";
jbe@170 4334 "i" := "i" + 1;
jbe@170 4335 END LOOP;
jbe@428 4336 -- find best paths:
jbe@427 4337 "matrix_p" := "find_best_paths"("matrix_d");
jbe@428 4338 -- create partial order:
jbe@427 4339 "matrix_b" := array_fill(NULL::BOOLEAN, ARRAY["dimension_v", "dimension_v"]);
jbe@170 4340 "i" := 1;
jbe@170 4341 LOOP
jbe@427 4342 "j" := "i" + 1;
jbe@170 4343 LOOP
jbe@170 4344 IF "i" != "j" THEN
jbe@427 4345 IF "matrix_p"["i"]["j"] > "matrix_p"["j"]["i"] THEN
jbe@427 4346 "matrix_b"["i"]["j"] := TRUE;
jbe@427 4347 "matrix_b"["j"]["i"] := FALSE;
jbe@427 4348 ELSIF "matrix_p"["i"]["j"] < "matrix_p"["j"]["i"] THEN
jbe@427 4349 "matrix_b"["i"]["j"] := FALSE;
jbe@427 4350 "matrix_b"["j"]["i"] := TRUE;
jbe@427 4351 END IF;
jbe@170 4352 END IF;
jbe@170 4353 EXIT WHEN "j" = "dimension_v";
jbe@170 4354 "j" := "j" + 1;
jbe@170 4355 END LOOP;
jbe@427 4356 EXIT WHEN "i" = "dimension_v" - 1;
jbe@170 4357 "i" := "i" + 1;
jbe@170 4358 END LOOP;
jbe@428 4359 -- tie-breaking by forbidding shared weakest links in beat-paths
jbe@428 4360 -- (unless "tie_breaking" is set to 'simple', in which case tie-breaking
jbe@428 4361 -- is performed later by initiative id):
jbe@427 4362 IF "policy_row"."tie_breaking" != 'simple'::"tie_breaking" THEN
jbe@427 4363 "m" := 1;
jbe@427 4364 LOOP
jbe@427 4365 "n" := "m" + 1;
jbe@427 4366 LOOP
jbe@428 4367 -- only process those candidates m and n, which are tied:
jbe@427 4368 IF "matrix_b"["m"]["n"] ISNULL THEN
jbe@428 4369 -- start with beat-paths prior tie-breaking:
jbe@427 4370 "matrix_t" := "matrix_p";
jbe@428 4371 -- start with all links allowed:
jbe@427 4372 "matrix_f" := array_fill(FALSE, ARRAY["dimension_v", "dimension_v"]);
jbe@427 4373 LOOP
jbe@428 4374 -- determine (and forbid) that link that is the weakest link
jbe@428 4375 -- in both the best path from candidate m to candidate n and
jbe@428 4376 -- from candidate n to candidate m:
jbe@427 4377 "i" := 1;
jbe@427 4378 <<forbid_one_link>>
jbe@427 4379 LOOP
jbe@427 4380 "j" := 1;
jbe@427 4381 LOOP
jbe@427 4382 IF "i" != "j" THEN
jbe@427 4383 IF "matrix_d"["i"]["j"] = "matrix_t"["m"]["n"] THEN
jbe@427 4384 "matrix_f"["i"]["j"] := TRUE;
jbe@427 4385 -- exit for performance reasons,
jbe@428 4386 -- as exactly one link will be found:
jbe@427 4387 EXIT forbid_one_link;
jbe@427 4388 END IF;
jbe@427 4389 END IF;
jbe@427 4390 EXIT WHEN "j" = "dimension_v";
jbe@427 4391 "j" := "j" + 1;
jbe@427 4392 END LOOP;
jbe@427 4393 IF "i" = "dimension_v" THEN
jbe@428 4394 RAISE EXCEPTION 'Did not find shared weakest link for tie-breaking (should not happen)';
jbe@427 4395 END IF;
jbe@427 4396 "i" := "i" + 1;
jbe@427 4397 END LOOP;
jbe@428 4398 -- calculate best beat-paths while ignoring forbidden links:
jbe@427 4399 "i" := 1;
jbe@427 4400 LOOP
jbe@427 4401 "j" := 1;
jbe@427 4402 LOOP
jbe@427 4403 IF "i" != "j" THEN
jbe@427 4404 "matrix_t"["i"]["j"] := CASE
jbe@427 4405 WHEN "matrix_f"["i"]["j"]
jbe@431 4406 THEN ((-1::INT8) << 63, 0)::"link_strength" -- worst possible value
jbe@427 4407 ELSE "matrix_d"["i"]["j"] END;
jbe@427 4408 END IF;
jbe@427 4409 EXIT WHEN "j" = "dimension_v";
jbe@427 4410 "j" := "j" + 1;
jbe@427 4411 END LOOP;
jbe@427 4412 EXIT WHEN "i" = "dimension_v";
jbe@427 4413 "i" := "i" + 1;
jbe@427 4414 END LOOP;
jbe@427 4415 "matrix_t" := "find_best_paths"("matrix_t");
jbe@428 4416 -- extend partial order, if tie-breaking was successful:
jbe@427 4417 IF "matrix_t"["m"]["n"] > "matrix_t"["n"]["m"] THEN
jbe@427 4418 "matrix_b"["m"]["n"] := TRUE;
jbe@427 4419 "matrix_b"["n"]["m"] := FALSE;
jbe@427 4420 EXIT;
jbe@427 4421 ELSIF "matrix_t"["m"]["n"] < "matrix_t"["n"]["m"] THEN
jbe@427 4422 "matrix_b"["m"]["n"] := FALSE;
jbe@427 4423 "matrix_b"["n"]["m"] := TRUE;
jbe@427 4424 EXIT;
jbe@427 4425 END IF;
jbe@427 4426 END LOOP;
jbe@427 4427 END IF;
jbe@427 4428 EXIT WHEN "n" = "dimension_v";
jbe@427 4429 "n" := "n" + 1;
jbe@427 4430 END LOOP;
jbe@427 4431 EXIT WHEN "m" = "dimension_v" - 1;
jbe@427 4432 "m" := "m" + 1;
jbe@427 4433 END LOOP;
jbe@427 4434 END IF;
jbe@428 4435 -- store a unique ranking in "rank_ary":
jbe@170 4436 "rank_ary" := array_fill(NULL::INT4, ARRAY["dimension_v"]);
jbe@170 4437 "rank_v" := 1;
jbe@170 4438 LOOP
jbe@0 4439 "i" := 1;
jbe@428 4440 <<assign_next_rank>>
jbe@0 4441 LOOP
jbe@170 4442 IF "rank_ary"["i"] ISNULL THEN
jbe@170 4443 "j" := 1;
jbe@170 4444 LOOP
jbe@170 4445 IF
jbe@170 4446 "i" != "j" AND
jbe@170 4447 "rank_ary"["j"] ISNULL AND
jbe@427 4448 ( "matrix_b"["j"]["i"] OR
jbe@411 4449 -- tie-breaking by "id"
jbe@427 4450 ( "matrix_b"["j"]["i"] ISNULL AND
jbe@411 4451 "j" < "i" ) )
jbe@170 4452 THEN
jbe@170 4453 -- someone else is better
jbe@170 4454 EXIT;
jbe@170 4455 END IF;
jbe@428 4456 IF "j" = "dimension_v" THEN
jbe@170 4457 -- noone is better
jbe@411 4458 "rank_ary"["i"] := "rank_v";
jbe@428 4459 EXIT assign_next_rank;
jbe@170 4460 END IF;
jbe@428 4461 "j" := "j" + 1;
jbe@170 4462 END LOOP;
jbe@170 4463 END IF;
jbe@0 4464 "i" := "i" + 1;
jbe@411 4465 IF "i" > "dimension_v" THEN
jbe@411 4466 RAISE EXCEPTION 'Schulze ranking does not compute (should not happen)';
jbe@411 4467 END IF;
jbe@0 4468 END LOOP;
jbe@411 4469 EXIT WHEN "rank_v" = "dimension_v";
jbe@170 4470 "rank_v" := "rank_v" + 1;
jbe@170 4471 END LOOP;
jbe@170 4472 -- write preliminary results:
jbe@411 4473 "i" := 2; -- omit status quo with "i" = 1
jbe@170 4474 FOR "initiative_id_v" IN
jbe@170 4475 SELECT "id" FROM "initiative"
jbe@170 4476 WHERE "issue_id" = "issue_id_p" AND "admitted"
jbe@170 4477 ORDER BY "id"
jbe@170 4478 LOOP
jbe@170 4479 UPDATE "initiative" SET
jbe@170 4480 "direct_majority" =
jbe@170 4481 CASE WHEN "policy_row"."direct_majority_strict" THEN
jbe@170 4482 "positive_votes" * "policy_row"."direct_majority_den" >
jbe@170 4483 "policy_row"."direct_majority_num" * ("positive_votes"+"negative_votes")
jbe@170 4484 ELSE
jbe@170 4485 "positive_votes" * "policy_row"."direct_majority_den" >=
jbe@170 4486 "policy_row"."direct_majority_num" * ("positive_votes"+"negative_votes")
jbe@170 4487 END
jbe@170 4488 AND "positive_votes" >= "policy_row"."direct_majority_positive"
jbe@170 4489 AND "issue_row"."voter_count"-"negative_votes" >=
jbe@170 4490 "policy_row"."direct_majority_non_negative",
jbe@170 4491 "indirect_majority" =
jbe@170 4492 CASE WHEN "policy_row"."indirect_majority_strict" THEN
jbe@170 4493 "positive_votes" * "policy_row"."indirect_majority_den" >
jbe@170 4494 "policy_row"."indirect_majority_num" * ("positive_votes"+"negative_votes")
jbe@170 4495 ELSE
jbe@170 4496 "positive_votes" * "policy_row"."indirect_majority_den" >=
jbe@170 4497 "policy_row"."indirect_majority_num" * ("positive_votes"+"negative_votes")
jbe@170 4498 END
jbe@170 4499 AND "positive_votes" >= "policy_row"."indirect_majority_positive"
jbe@170 4500 AND "issue_row"."voter_count"-"negative_votes" >=
jbe@170 4501 "policy_row"."indirect_majority_non_negative",
jbe@171 4502 "schulze_rank" = "rank_ary"["i"],
jbe@411 4503 "better_than_status_quo" = "rank_ary"["i"] < "rank_ary"[1],
jbe@411 4504 "worse_than_status_quo" = "rank_ary"["i"] > "rank_ary"[1],
jbe@411 4505 "multistage_majority" = "rank_ary"["i"] >= "rank_ary"[1],
jbe@429 4506 "reverse_beat_path" = CASE WHEN "policy_row"."defeat_strength" = 'simple'::"defeat_strength"
jbe@429 4507 THEN NULL
jbe@429 4508 ELSE "matrix_p"[1]["i"]."primary" >= 0 END,
jbe@216 4509 "eligible" = FALSE,
jbe@250 4510 "winner" = FALSE,
jbe@250 4511 "rank" = NULL -- NOTE: in cases of manual reset of issue state
jbe@170 4512 WHERE "id" = "initiative_id_v";
jbe@170 4513 "i" := "i" + 1;
jbe@170 4514 END LOOP;
jbe@411 4515 IF "i" != "dimension_v" + 1 THEN
jbe@170 4516 RAISE EXCEPTION 'Wrong winner count (should not happen)';
jbe@0 4517 END IF;
jbe@170 4518 -- take indirect majorities into account:
jbe@170 4519 LOOP
jbe@170 4520 UPDATE "initiative" SET "indirect_majority" = TRUE
jbe@139 4521 FROM (
jbe@170 4522 SELECT "new_initiative"."id" AS "initiative_id"
jbe@170 4523 FROM "initiative" "old_initiative"
jbe@170 4524 JOIN "initiative" "new_initiative"
jbe@170 4525 ON "new_initiative"."issue_id" = "issue_id_p"
jbe@170 4526 AND "new_initiative"."indirect_majority" = FALSE
jbe@139 4527 JOIN "battle" "battle_win"
jbe@139 4528 ON "battle_win"."issue_id" = "issue_id_p"
jbe@170 4529 AND "battle_win"."winning_initiative_id" = "new_initiative"."id"
jbe@170 4530 AND "battle_win"."losing_initiative_id" = "old_initiative"."id"
jbe@139 4531 JOIN "battle" "battle_lose"
jbe@139 4532 ON "battle_lose"."issue_id" = "issue_id_p"
jbe@170 4533 AND "battle_lose"."losing_initiative_id" = "new_initiative"."id"
jbe@170 4534 AND "battle_lose"."winning_initiative_id" = "old_initiative"."id"
jbe@170 4535 WHERE "old_initiative"."issue_id" = "issue_id_p"
jbe@170 4536 AND "old_initiative"."indirect_majority" = TRUE
jbe@170 4537 AND CASE WHEN "policy_row"."indirect_majority_strict" THEN
jbe@170 4538 "battle_win"."count" * "policy_row"."indirect_majority_den" >
jbe@170 4539 "policy_row"."indirect_majority_num" *
jbe@170 4540 ("battle_win"."count"+"battle_lose"."count")
jbe@170 4541 ELSE
jbe@170 4542 "battle_win"."count" * "policy_row"."indirect_majority_den" >=
jbe@170 4543 "policy_row"."indirect_majority_num" *
jbe@170 4544 ("battle_win"."count"+"battle_lose"."count")
jbe@170 4545 END
jbe@170 4546 AND "battle_win"."count" >= "policy_row"."indirect_majority_positive"
jbe@170 4547 AND "issue_row"."voter_count"-"battle_lose"."count" >=
jbe@170 4548 "policy_row"."indirect_majority_non_negative"
jbe@139 4549 ) AS "subquery"
jbe@139 4550 WHERE "id" = "subquery"."initiative_id";
jbe@170 4551 EXIT WHEN NOT FOUND;
jbe@170 4552 END LOOP;
jbe@170 4553 -- set "multistage_majority" for remaining matching initiatives:
jbe@216 4554 UPDATE "initiative" SET "multistage_majority" = TRUE
jbe@170 4555 FROM (
jbe@170 4556 SELECT "losing_initiative"."id" AS "initiative_id"
jbe@170 4557 FROM "initiative" "losing_initiative"
jbe@170 4558 JOIN "initiative" "winning_initiative"
jbe@170 4559 ON "winning_initiative"."issue_id" = "issue_id_p"
jbe@170 4560 AND "winning_initiative"."admitted"
jbe@170 4561 JOIN "battle" "battle_win"
jbe@170 4562 ON "battle_win"."issue_id" = "issue_id_p"
jbe@170 4563 AND "battle_win"."winning_initiative_id" = "winning_initiative"."id"
jbe@170 4564 AND "battle_win"."losing_initiative_id" = "losing_initiative"."id"
jbe@170 4565 JOIN "battle" "battle_lose"
jbe@170 4566 ON "battle_lose"."issue_id" = "issue_id_p"
jbe@170 4567 AND "battle_lose"."losing_initiative_id" = "winning_initiative"."id"
jbe@170 4568 AND "battle_lose"."winning_initiative_id" = "losing_initiative"."id"
jbe@170 4569 WHERE "losing_initiative"."issue_id" = "issue_id_p"
jbe@170 4570 AND "losing_initiative"."admitted"
jbe@170 4571 AND "winning_initiative"."schulze_rank" <
jbe@170 4572 "losing_initiative"."schulze_rank"
jbe@170 4573 AND "battle_win"."count" > "battle_lose"."count"
jbe@170 4574 AND (
jbe@170 4575 "battle_win"."count" > "winning_initiative"."positive_votes" OR
jbe@170 4576 "battle_lose"."count" < "losing_initiative"."negative_votes" )
jbe@170 4577 ) AS "subquery"
jbe@170 4578 WHERE "id" = "subquery"."initiative_id";
jbe@170 4579 -- mark eligible initiatives:
jbe@170 4580 UPDATE "initiative" SET "eligible" = TRUE
jbe@171 4581 WHERE "issue_id" = "issue_id_p"
jbe@171 4582 AND "initiative"."direct_majority"
jbe@171 4583 AND "initiative"."indirect_majority"
jbe@171 4584 AND "initiative"."better_than_status_quo"
jbe@171 4585 AND (
jbe@171 4586 "policy_row"."no_multistage_majority" = FALSE OR
jbe@429 4587 "initiative"."multistage_majority" = FALSE )
jbe@429 4588 AND (
jbe@429 4589 "policy_row"."no_reverse_beat_path" = FALSE OR
jbe@429 4590 coalesce("initiative"."reverse_beat_path", FALSE) = FALSE );
jbe@170 4591 -- mark final winner:
jbe@170 4592 UPDATE "initiative" SET "winner" = TRUE
jbe@170 4593 FROM (
jbe@170 4594 SELECT "id" AS "initiative_id"
jbe@170 4595 FROM "initiative"
jbe@170 4596 WHERE "issue_id" = "issue_id_p" AND "eligible"
jbe@217 4597 ORDER BY
jbe@217 4598 "schulze_rank",
jbe@217 4599 "id"
jbe@170 4600 LIMIT 1
jbe@170 4601 ) AS "subquery"
jbe@170 4602 WHERE "id" = "subquery"."initiative_id";
jbe@173 4603 -- write (final) ranks:
jbe@173 4604 "rank_v" := 1;
jbe@173 4605 FOR "initiative_id_v" IN
jbe@173 4606 SELECT "id"
jbe@173 4607 FROM "initiative"
jbe@173 4608 WHERE "issue_id" = "issue_id_p" AND "admitted"
jbe@174 4609 ORDER BY
jbe@174 4610 "winner" DESC,
jbe@217 4611 "eligible" DESC,
jbe@174 4612 "schulze_rank",
jbe@174 4613 "id"
jbe@173 4614 LOOP
jbe@173 4615 UPDATE "initiative" SET "rank" = "rank_v"
jbe@173 4616 WHERE "id" = "initiative_id_v";
jbe@173 4617 "rank_v" := "rank_v" + 1;
jbe@173 4618 END LOOP;
jbe@170 4619 -- set schulze rank of status quo and mark issue as finished:
jbe@111 4620 UPDATE "issue" SET
jbe@411 4621 "status_quo_schulze_rank" = "rank_ary"[1],
jbe@111 4622 "state" =
jbe@139 4623 CASE WHEN EXISTS (
jbe@139 4624 SELECT NULL FROM "initiative"
jbe@139 4625 WHERE "issue_id" = "issue_id_p" AND "winner"
jbe@139 4626 ) THEN
jbe@139 4627 'finished_with_winner'::"issue_state"
jbe@139 4628 ELSE
jbe@121 4629 'finished_without_winner'::"issue_state"
jbe@111 4630 END,
jbe@331 4631 "closed" = "phase_finished",
jbe@331 4632 "phase_finished" = NULL
jbe@0 4633 WHERE "id" = "issue_id_p";
jbe@0 4634 RETURN;
jbe@0 4635 END;
jbe@0 4636 $$;
jbe@0 4637
jbe@0 4638 COMMENT ON FUNCTION "calculate_ranks"
jbe@0 4639 ( "issue"."id"%TYPE )
jbe@0 4640 IS 'Determine ranking (Votes have to be counted first)';
jbe@0 4641
jbe@0 4642
jbe@0 4643
jbe@0 4644 -----------------------------
jbe@0 4645 -- Automatic state changes --
jbe@0 4646 -----------------------------
jbe@0 4647
jbe@0 4648
jbe@331 4649 CREATE TYPE "check_issue_persistence" AS (
jbe@331 4650 "state" "issue_state",
jbe@331 4651 "phase_finished" BOOLEAN,
jbe@331 4652 "issue_revoked" BOOLEAN,
jbe@331 4653 "snapshot_created" BOOLEAN,
jbe@331 4654 "harmonic_weights_set" BOOLEAN,
jbe@331 4655 "closed_voting" BOOLEAN );
jbe@331 4656
jbe@336 4657 COMMENT ON TYPE "check_issue_persistence" IS 'Type of data returned by "check_issue" function, to be passed to subsequent calls of the same function';
jbe@336 4658
jbe@336 4659
jbe@0 4660 CREATE FUNCTION "check_issue"
jbe@331 4661 ( "issue_id_p" "issue"."id"%TYPE,
jbe@331 4662 "persist" "check_issue_persistence" )
jbe@331 4663 RETURNS "check_issue_persistence"
jbe@0 4664 LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@0 4665 DECLARE
jbe@336 4666 "issue_row" "issue"%ROWTYPE;
jbe@336 4667 "policy_row" "policy"%ROWTYPE;
jbe@336 4668 "initiative_row" "initiative"%ROWTYPE;
jbe@336 4669 "state_v" "issue_state";
jbe@0 4670 BEGIN
jbe@333 4671 PERFORM "require_transaction_isolation"();
jbe@331 4672 IF "persist" ISNULL THEN
jbe@331 4673 SELECT * INTO "issue_row" FROM "issue" WHERE "id" = "issue_id_p"
jbe@331 4674 FOR UPDATE;
jbe@331 4675 IF "issue_row"."closed" NOTNULL THEN
jbe@331 4676 RETURN NULL;
jbe@0 4677 END IF;
jbe@331 4678 "persist"."state" := "issue_row"."state";
jbe@331 4679 IF
jbe@331 4680 ( "issue_row"."state" = 'admission' AND now() >=
jbe@447 4681 "issue_row"."created" + "issue_row"."max_admission_time" ) OR
jbe@331 4682 ( "issue_row"."state" = 'discussion' AND now() >=
jbe@331 4683 "issue_row"."accepted" + "issue_row"."discussion_time" ) OR
jbe@331 4684 ( "issue_row"."state" = 'verification' AND now() >=
jbe@331 4685 "issue_row"."half_frozen" + "issue_row"."verification_time" ) OR
jbe@331 4686 ( "issue_row"."state" = 'voting' AND now() >=
jbe@331 4687 "issue_row"."fully_frozen" + "issue_row"."voting_time" )
jbe@331 4688 THEN
jbe@331 4689 "persist"."phase_finished" := TRUE;
jbe@331 4690 ELSE
jbe@331 4691 "persist"."phase_finished" := FALSE;
jbe@0 4692 END IF;
jbe@0 4693 IF
jbe@24 4694 NOT EXISTS (
jbe@24 4695 -- all initiatives are revoked
jbe@24 4696 SELECT NULL FROM "initiative"
jbe@24 4697 WHERE "issue_id" = "issue_id_p" AND "revoked" ISNULL
jbe@24 4698 ) AND (
jbe@111 4699 -- and issue has not been accepted yet
jbe@331 4700 "persist"."state" = 'admission' OR
jbe@331 4701 -- or verification time has elapsed
jbe@331 4702 ( "persist"."state" = 'verification' AND
jbe@331 4703 "persist"."phase_finished" ) OR
jbe@331 4704 -- or no initiatives have been revoked lately
jbe@24 4705 NOT EXISTS (
jbe@24 4706 SELECT NULL FROM "initiative"
jbe@24 4707 WHERE "issue_id" = "issue_id_p"
jbe@24 4708 AND now() < "revoked" + "issue_row"."verification_time"
jbe@24 4709 )
jbe@24 4710 )
jbe@24 4711 THEN
jbe@331 4712 "persist"."issue_revoked" := TRUE;
jbe@331 4713 ELSE
jbe@331 4714 "persist"."issue_revoked" := FALSE;
jbe@24 4715 END IF;
jbe@331 4716 IF "persist"."phase_finished" OR "persist"."issue_revoked" THEN
jbe@331 4717 UPDATE "issue" SET "phase_finished" = now()
jbe@331 4718 WHERE "id" = "issue_row"."id";
jbe@331 4719 RETURN "persist";
jbe@331 4720 ELSIF
jbe@331 4721 "persist"."state" IN ('admission', 'discussion', 'verification')
jbe@3 4722 THEN
jbe@331 4723 RETURN "persist";
jbe@331 4724 ELSE
jbe@331 4725 RETURN NULL;
jbe@322 4726 END IF;
jbe@0 4727 END IF;
jbe@331 4728 IF
jbe@331 4729 "persist"."state" IN ('admission', 'discussion', 'verification') AND
jbe@331 4730 coalesce("persist"."snapshot_created", FALSE) = FALSE
jbe@331 4731 THEN
jbe@331 4732 PERFORM "create_snapshot"("issue_id_p");
jbe@331 4733 "persist"."snapshot_created" = TRUE;
jbe@331 4734 IF "persist"."phase_finished" THEN
jbe@331 4735 IF "persist"."state" = 'admission' THEN
jbe@331 4736 PERFORM "set_snapshot_event"("issue_id_p", 'end_of_admission');
jbe@331 4737 ELSIF "persist"."state" = 'discussion' THEN
jbe@331 4738 PERFORM "set_snapshot_event"("issue_id_p", 'half_freeze');
jbe@331 4739 ELSIF "persist"."state" = 'verification' THEN
jbe@331 4740 PERFORM "set_snapshot_event"("issue_id_p", 'full_freeze');
jbe@336 4741 SELECT * INTO "issue_row" FROM "issue" WHERE "id" = "issue_id_p";
jbe@336 4742 SELECT * INTO "policy_row" FROM "policy"
jbe@336 4743 WHERE "id" = "issue_row"."policy_id";
jbe@336 4744 FOR "initiative_row" IN
jbe@336 4745 SELECT * FROM "initiative"
jbe@336 4746 WHERE "issue_id" = "issue_id_p" AND "revoked" ISNULL
jbe@336 4747 FOR UPDATE
jbe@336 4748 LOOP
jbe@336 4749 IF
jbe@336 4750 "initiative_row"."polling" OR (
jbe@336 4751 "initiative_row"."satisfied_supporter_count" > 0 AND
jbe@336 4752 "initiative_row"."satisfied_supporter_count" *
jbe@336 4753 "policy_row"."initiative_quorum_den" >=
jbe@336 4754 "issue_row"."population" * "policy_row"."initiative_quorum_num"
jbe@336 4755 )
jbe@336 4756 THEN
jbe@336 4757 UPDATE "initiative" SET "admitted" = TRUE
jbe@336 4758 WHERE "id" = "initiative_row"."id";
jbe@336 4759 ELSE
jbe@336 4760 UPDATE "initiative" SET "admitted" = FALSE
jbe@336 4761 WHERE "id" = "initiative_row"."id";
jbe@336 4762 END IF;
jbe@336 4763 END LOOP;
jbe@331 4764 END IF;
jbe@331 4765 END IF;
jbe@331 4766 RETURN "persist";
jbe@331 4767 END IF;
jbe@331 4768 IF
jbe@331 4769 "persist"."state" IN ('admission', 'discussion', 'verification') AND
jbe@331 4770 coalesce("persist"."harmonic_weights_set", FALSE) = FALSE
jbe@331 4771 THEN
jbe@331 4772 PERFORM "set_harmonic_initiative_weights"("issue_id_p");
jbe@331 4773 "persist"."harmonic_weights_set" = TRUE;
jbe@332 4774 IF
jbe@332 4775 "persist"."phase_finished" OR
jbe@332 4776 "persist"."issue_revoked" OR
jbe@332 4777 "persist"."state" = 'admission'
jbe@332 4778 THEN
jbe@331 4779 RETURN "persist";
jbe@331 4780 ELSE
jbe@331 4781 RETURN NULL;
jbe@331 4782 END IF;
jbe@331 4783 END IF;
jbe@331 4784 IF "persist"."issue_revoked" THEN
jbe@331 4785 IF "persist"."state" = 'admission' THEN
jbe@331 4786 "state_v" := 'canceled_revoked_before_accepted';
jbe@331 4787 ELSIF "persist"."state" = 'discussion' THEN
jbe@331 4788 "state_v" := 'canceled_after_revocation_during_discussion';
jbe@331 4789 ELSIF "persist"."state" = 'verification' THEN
jbe@331 4790 "state_v" := 'canceled_after_revocation_during_verification';
jbe@331 4791 END IF;
jbe@331 4792 UPDATE "issue" SET
jbe@331 4793 "state" = "state_v",
jbe@331 4794 "closed" = "phase_finished",
jbe@331 4795 "phase_finished" = NULL
jbe@332 4796 WHERE "id" = "issue_id_p";
jbe@331 4797 RETURN NULL;
jbe@331 4798 END IF;
jbe@331 4799 IF "persist"."state" = 'admission' THEN
jbe@336 4800 SELECT * INTO "issue_row" FROM "issue" WHERE "id" = "issue_id_p"
jbe@336 4801 FOR UPDATE;
jbe@336 4802 SELECT * INTO "policy_row"
jbe@336 4803 FROM "policy" WHERE "id" = "issue_row"."policy_id";
jbe@447 4804 IF
jbe@447 4805 ( now() >=
jbe@447 4806 "issue_row"."created" + "issue_row"."min_admission_time" ) AND
jbe@447 4807 EXISTS (
jbe@447 4808 SELECT NULL FROM "initiative"
jbe@447 4809 WHERE "issue_id" = "issue_id_p"
jbe@447 4810 AND "supporter_count" > 0
jbe@447 4811 AND "supporter_count" * "policy_row"."issue_quorum_den"
jbe@447 4812 >= "issue_row"."population" * "policy_row"."issue_quorum_num"
jbe@447 4813 )
jbe@447 4814 THEN
jbe@336 4815 UPDATE "issue" SET
jbe@336 4816 "state" = 'discussion',
jbe@336 4817 "accepted" = coalesce("phase_finished", now()),
jbe@336 4818 "phase_finished" = NULL
jbe@336 4819 WHERE "id" = "issue_id_p";
jbe@336 4820 ELSIF "issue_row"."phase_finished" NOTNULL THEN
jbe@336 4821 UPDATE "issue" SET
jbe@336 4822 "state" = 'canceled_issue_not_accepted',
jbe@336 4823 "closed" = "phase_finished",
jbe@336 4824 "phase_finished" = NULL
jbe@336 4825 WHERE "id" = "issue_id_p";
jbe@336 4826 END IF;
jbe@331 4827 RETURN NULL;
jbe@331 4828 END IF;
jbe@332 4829 IF "persist"."phase_finished" THEN
jbe@443 4830 IF "persist"."state" = 'discussion' THEN
jbe@332 4831 UPDATE "issue" SET
jbe@332 4832 "state" = 'verification',
jbe@332 4833 "half_frozen" = "phase_finished",
jbe@332 4834 "phase_finished" = NULL
jbe@332 4835 WHERE "id" = "issue_id_p";
jbe@332 4836 RETURN NULL;
jbe@332 4837 END IF;
jbe@332 4838 IF "persist"."state" = 'verification' THEN
jbe@336 4839 SELECT * INTO "issue_row" FROM "issue" WHERE "id" = "issue_id_p"
jbe@336 4840 FOR UPDATE;
jbe@336 4841 SELECT * INTO "policy_row" FROM "policy"
jbe@336 4842 WHERE "id" = "issue_row"."policy_id";
jbe@336 4843 IF EXISTS (
jbe@336 4844 SELECT NULL FROM "initiative"
jbe@336 4845 WHERE "issue_id" = "issue_id_p" AND "admitted" = TRUE
jbe@336 4846 ) THEN
jbe@336 4847 UPDATE "issue" SET
jbe@343 4848 "state" = 'voting',
jbe@343 4849 "fully_frozen" = "phase_finished",
jbe@336 4850 "phase_finished" = NULL
jbe@336 4851 WHERE "id" = "issue_id_p";
jbe@336 4852 ELSE
jbe@336 4853 UPDATE "issue" SET
jbe@343 4854 "state" = 'canceled_no_initiative_admitted',
jbe@343 4855 "fully_frozen" = "phase_finished",
jbe@343 4856 "closed" = "phase_finished",
jbe@343 4857 "phase_finished" = NULL
jbe@336 4858 WHERE "id" = "issue_id_p";
jbe@336 4859 -- NOTE: The following DELETE statements have effect only when
jbe@336 4860 -- issue state has been manipulated
jbe@336 4861 DELETE FROM "direct_voter" WHERE "issue_id" = "issue_id_p";
jbe@336 4862 DELETE FROM "delegating_voter" WHERE "issue_id" = "issue_id_p";
jbe@336 4863 DELETE FROM "battle" WHERE "issue_id" = "issue_id_p";
jbe@336 4864 END IF;
jbe@332 4865 RETURN NULL;
jbe@332 4866 END IF;
jbe@332 4867 IF "persist"."state" = 'voting' THEN
jbe@332 4868 IF coalesce("persist"."closed_voting", FALSE) = FALSE THEN
jbe@332 4869 PERFORM "close_voting"("issue_id_p");
jbe@332 4870 "persist"."closed_voting" = TRUE;
jbe@332 4871 RETURN "persist";
jbe@332 4872 END IF;
jbe@332 4873 PERFORM "calculate_ranks"("issue_id_p");
jbe@332 4874 RETURN NULL;
jbe@332 4875 END IF;
jbe@331 4876 END IF;
jbe@331 4877 RAISE WARNING 'should not happen';
jbe@331 4878 RETURN NULL;
jbe@0 4879 END;
jbe@0 4880 $$;
jbe@0 4881
jbe@0 4882 COMMENT ON FUNCTION "check_issue"
jbe@331 4883 ( "issue"."id"%TYPE,
jbe@331 4884 "check_issue_persistence" )
jbe@336 4885 IS 'Precalculate supporter counts etc. for a given issue, and check, if status change is required, and perform the status change when necessary; Function must be called multiple times with the previous result as second parameter, until the result is NULL (see source code of function "check_everything")';
jbe@0 4886
jbe@0 4887
jbe@0 4888 CREATE FUNCTION "check_everything"()
jbe@0 4889 RETURNS VOID
jbe@0 4890 LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@0 4891 DECLARE
jbe@0 4892 "issue_id_v" "issue"."id"%TYPE;
jbe@331 4893 "persist_v" "check_issue_persistence";
jbe@0 4894 BEGIN
jbe@333 4895 RAISE WARNING 'Function "check_everything" should only be used for development and debugging purposes';
jbe@235 4896 DELETE FROM "expired_session";
jbe@184 4897 PERFORM "check_activity"();
jbe@4 4898 PERFORM "calculate_member_counts"();
jbe@4 4899 FOR "issue_id_v" IN SELECT "id" FROM "open_issue" LOOP
jbe@331 4900 "persist_v" := NULL;
jbe@331 4901 LOOP
jbe@331 4902 "persist_v" := "check_issue"("issue_id_v", "persist_v");
jbe@331 4903 EXIT WHEN "persist_v" ISNULL;
jbe@331 4904 END LOOP;
jbe@0 4905 END LOOP;
jbe@0 4906 RETURN;
jbe@0 4907 END;
jbe@0 4908 $$;
jbe@0 4909
jbe@336 4910 COMMENT ON FUNCTION "check_everything"() IS 'Amongst other regular tasks this function performs "check_issue" for every open issue. Use this function only for development and debugging purposes, as you may run into locking and/or serialization problems in productive environments.';
jbe@0 4911
jbe@0 4912
jbe@0 4913
jbe@59 4914 ----------------------
jbe@59 4915 -- Deletion of data --
jbe@59 4916 ----------------------
jbe@59 4917
jbe@59 4918
jbe@59 4919 CREATE FUNCTION "clean_issue"("issue_id_p" "issue"."id"%TYPE)
jbe@59 4920 RETURNS VOID
jbe@59 4921 LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@59 4922 BEGIN
jbe@385 4923 IF EXISTS (
jbe@385 4924 SELECT NULL FROM "issue" WHERE "id" = "issue_id_p" AND "cleaned" ISNULL
jbe@385 4925 ) THEN
jbe@385 4926 -- override protection triggers:
jbe@385 4927 INSERT INTO "temporary_transaction_data" ("key", "value")
jbe@385 4928 VALUES ('override_protection_triggers', TRUE::TEXT);
jbe@385 4929 -- clean data:
jbe@59 4930 DELETE FROM "delegating_voter"
jbe@59 4931 WHERE "issue_id" = "issue_id_p";
jbe@59 4932 DELETE FROM "direct_voter"
jbe@59 4933 WHERE "issue_id" = "issue_id_p";
jbe@59 4934 DELETE FROM "delegating_interest_snapshot"
jbe@59 4935 WHERE "issue_id" = "issue_id_p";
jbe@59 4936 DELETE FROM "direct_interest_snapshot"
jbe@59 4937 WHERE "issue_id" = "issue_id_p";
jbe@59 4938 DELETE FROM "delegating_population_snapshot"
jbe@59 4939 WHERE "issue_id" = "issue_id_p";
jbe@59 4940 DELETE FROM "direct_population_snapshot"
jbe@59 4941 WHERE "issue_id" = "issue_id_p";
jbe@113 4942 DELETE FROM "non_voter"
jbe@94 4943 WHERE "issue_id" = "issue_id_p";
jbe@59 4944 DELETE FROM "delegation"
jbe@59 4945 WHERE "issue_id" = "issue_id_p";
jbe@59 4946 DELETE FROM "supporter"
jbe@329 4947 USING "initiative" -- NOTE: due to missing index on issue_id
jbe@325 4948 WHERE "initiative"."issue_id" = "issue_id_p"
jbe@325 4949 AND "supporter"."initiative_id" = "initiative_id";
jbe@385 4950 -- mark issue as cleaned:
jbe@385 4951 UPDATE "issue" SET "cleaned" = now() WHERE "id" = "issue_id_p";
jbe@385 4952 -- finish overriding protection triggers (avoids garbage):
jbe@385 4953 DELETE FROM "temporary_transaction_data"
jbe@385 4954 WHERE "key" = 'override_protection_triggers';
jbe@59 4955 END IF;
jbe@59 4956 RETURN;
jbe@59 4957 END;
jbe@59 4958 $$;
jbe@59 4959
jbe@59 4960 COMMENT ON FUNCTION "clean_issue"("issue"."id"%TYPE) IS 'Delete discussion data and votes belonging to an issue';
jbe@8 4961
jbe@8 4962
jbe@54 4963 CREATE FUNCTION "delete_member"("member_id_p" "member"."id"%TYPE)
jbe@8 4964 RETURNS VOID
jbe@8 4965 LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@8 4966 BEGIN
jbe@9 4967 UPDATE "member" SET
jbe@57 4968 "last_login" = NULL,
jbe@387 4969 "last_delegation_check" = NULL,
jbe@45 4970 "login" = NULL,
jbe@11 4971 "password" = NULL,
jbe@441 4972 "authority" = NULL,
jbe@441 4973 "authority_uid" = NULL,
jbe@441 4974 "authority_login" = NULL,
jbe@101 4975 "locked" = TRUE,
jbe@54 4976 "active" = FALSE,
jbe@11 4977 "notify_email" = NULL,
jbe@11 4978 "notify_email_unconfirmed" = NULL,
jbe@11 4979 "notify_email_secret" = NULL,
jbe@11 4980 "notify_email_secret_expiry" = NULL,
jbe@57 4981 "notify_email_lock_expiry" = NULL,
jbe@387 4982 "login_recovery_expiry" = NULL,
jbe@11 4983 "password_reset_secret" = NULL,
jbe@11 4984 "password_reset_secret_expiry" = NULL,
jbe@11 4985 "organizational_unit" = NULL,
jbe@11 4986 "internal_posts" = NULL,
jbe@11 4987 "realname" = NULL,
jbe@11 4988 "birthday" = NULL,
jbe@11 4989 "address" = NULL,
jbe@11 4990 "email" = NULL,
jbe@11 4991 "xmpp_address" = NULL,
jbe@11 4992 "website" = NULL,
jbe@11 4993 "phone" = NULL,
jbe@11 4994 "mobile_phone" = NULL,
jbe@11 4995 "profession" = NULL,
jbe@11 4996 "external_memberships" = NULL,
jbe@11 4997 "external_posts" = NULL,
jbe@45 4998 "statement" = NULL
jbe@45 4999 WHERE "id" = "member_id_p";
jbe@11 5000 -- "text_search_data" is updated by triggers
jbe@45 5001 DELETE FROM "setting" WHERE "member_id" = "member_id_p";
jbe@45 5002 DELETE FROM "setting_map" WHERE "member_id" = "member_id_p";
jbe@45 5003 DELETE FROM "member_relation_setting" WHERE "member_id" = "member_id_p";
jbe@45 5004 DELETE FROM "member_image" WHERE "member_id" = "member_id_p";
jbe@45 5005 DELETE FROM "contact" WHERE "member_id" = "member_id_p";
jbe@113 5006 DELETE FROM "ignored_member" WHERE "member_id" = "member_id_p";
jbe@235 5007 DELETE FROM "session" WHERE "member_id" = "member_id_p";
jbe@45 5008 DELETE FROM "area_setting" WHERE "member_id" = "member_id_p";
jbe@45 5009 DELETE FROM "issue_setting" WHERE "member_id" = "member_id_p";
jbe@113 5010 DELETE FROM "ignored_initiative" WHERE "member_id" = "member_id_p";
jbe@45 5011 DELETE FROM "initiative_setting" WHERE "member_id" = "member_id_p";
jbe@45 5012 DELETE FROM "suggestion_setting" WHERE "member_id" = "member_id_p";
jbe@54 5013 DELETE FROM "membership" WHERE "member_id" = "member_id_p";
jbe@54 5014 DELETE FROM "delegation" WHERE "truster_id" = "member_id_p";
jbe@113 5015 DELETE FROM "non_voter" WHERE "member_id" = "member_id_p";
jbe@57 5016 DELETE FROM "direct_voter" USING "issue"
jbe@57 5017 WHERE "direct_voter"."issue_id" = "issue"."id"
jbe@57 5018 AND "issue"."closed" ISNULL
jbe@57 5019 AND "member_id" = "member_id_p";
jbe@45 5020 RETURN;
jbe@45 5021 END;
jbe@45 5022 $$;
jbe@45 5023
jbe@57 5024 COMMENT ON FUNCTION "delete_member"("member_id_p" "member"."id"%TYPE) IS 'Deactivate member and clear certain settings and data of this member (data protection)';
jbe@45 5025
jbe@45 5026
jbe@45 5027 CREATE FUNCTION "delete_private_data"()
jbe@45 5028 RETURNS VOID
jbe@45 5029 LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@45 5030 BEGIN
jbe@385 5031 DELETE FROM "temporary_transaction_data";
jbe@226 5032 DELETE FROM "member" WHERE "activated" ISNULL;
jbe@50 5033 UPDATE "member" SET
jbe@206 5034 "invite_code" = NULL,
jbe@232 5035 "invite_code_expiry" = NULL,
jbe@228 5036 "admin_comment" = NULL,
jbe@57 5037 "last_login" = NULL,
jbe@387 5038 "last_delegation_check" = NULL,
jbe@50 5039 "login" = NULL,
jbe@50 5040 "password" = NULL,
jbe@441 5041 "authority" = NULL,
jbe@441 5042 "authority_uid" = NULL,
jbe@441 5043 "authority_login" = NULL,
jbe@238 5044 "lang" = NULL,
jbe@50 5045 "notify_email" = NULL,
jbe@50 5046 "notify_email_unconfirmed" = NULL,
jbe@50 5047 "notify_email_secret" = NULL,
jbe@50 5048 "notify_email_secret_expiry" = NULL,
jbe@57 5049 "notify_email_lock_expiry" = NULL,
jbe@238 5050 "notify_level" = NULL,
jbe@387 5051 "login_recovery_expiry" = NULL,
jbe@50 5052 "password_reset_secret" = NULL,
jbe@50 5053 "password_reset_secret_expiry" = NULL,
jbe@50 5054 "organizational_unit" = NULL,
jbe@50 5055 "internal_posts" = NULL,
jbe@50 5056 "realname" = NULL,
jbe@50 5057 "birthday" = NULL,
jbe@50 5058 "address" = NULL,
jbe@50 5059 "email" = NULL,
jbe@50 5060 "xmpp_address" = NULL,
jbe@50 5061 "website" = NULL,
jbe@50 5062 "phone" = NULL,
jbe@50 5063 "mobile_phone" = NULL,
jbe@50 5064 "profession" = NULL,
jbe@50 5065 "external_memberships" = NULL,
jbe@50 5066 "external_posts" = NULL,
jbe@238 5067 "formatting_engine" = NULL,
jbe@50 5068 "statement" = NULL;
jbe@50 5069 -- "text_search_data" is updated by triggers
jbe@50 5070 DELETE FROM "setting";
jbe@50 5071 DELETE FROM "setting_map";
jbe@50 5072 DELETE FROM "member_relation_setting";
jbe@50 5073 DELETE FROM "member_image";
jbe@50 5074 DELETE FROM "contact";
jbe@113 5075 DELETE FROM "ignored_member";
jbe@235 5076 DELETE FROM "session";
jbe@50 5077 DELETE FROM "area_setting";
jbe@50 5078 DELETE FROM "issue_setting";
jbe@113 5079 DELETE FROM "ignored_initiative";
jbe@50 5080 DELETE FROM "initiative_setting";
jbe@50 5081 DELETE FROM "suggestion_setting";
jbe@113 5082 DELETE FROM "non_voter";
jbe@8 5083 DELETE FROM "direct_voter" USING "issue"
jbe@8 5084 WHERE "direct_voter"."issue_id" = "issue"."id"
jbe@8 5085 AND "issue"."closed" ISNULL;
jbe@8 5086 RETURN;
jbe@8 5087 END;
jbe@8 5088 $$;
jbe@8 5089
jbe@273 5090 COMMENT ON FUNCTION "delete_private_data"() IS 'Used by lf_export script. DO NOT USE on productive database, but only on a copy! This function deletes all data which should not be publicly available, and can be used to create a database dump for publication. See source code to see which data is deleted. If you need a different behaviour, copy this function and modify lf_export accordingly, to avoid data-leaks after updating.';
jbe@8 5091
jbe@8 5092
jbe@8 5093
jbe@0 5094 COMMIT;

Impressum / About Us