liquid_feedback_core

annotate core.sql @ 503:986d8f3a6895

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

Impressum / About Us