liquid_feedback_core

annotate core.sql @ 536:750b0be5acb6

Work on unit/area/policy events: added column "policy_id" to "event" table, added new "event_type"s
author jbe
date Mon Jun 26 15:54:26 2017 +0200 (2017-06-26)
parents 8b6433096a58
children aa261389c993
rev   line source
jbe@0 1
jbe@0 2 -- NOTE: In PostgreSQL every UNIQUE constraint implies creation of an index
jbe@0 3
jbe@0 4 BEGIN;
jbe@0 5
jbe@532 6 CREATE EXTENSION IF NOT EXISTS latlon; -- load pgLatLon extenstion
jbe@529 7
jbe@5 8 CREATE VIEW "liquid_feedback_version" AS
jbe@532 9 SELECT * FROM (VALUES ('4.0-dev', 4, 0, -1))
jbe@5 10 AS "subquery"("string", "major", "minor", "revision");
jbe@5 11
jbe@0 12
jbe@0 13
jbe@7 14 ----------------------
jbe@7 15 -- Full text search --
jbe@7 16 ----------------------
jbe@7 17
jbe@7 18
jbe@7 19 CREATE FUNCTION "text_search_query"("query_text_p" TEXT)
jbe@7 20 RETURNS TSQUERY
jbe@7 21 LANGUAGE 'plpgsql' IMMUTABLE AS $$
jbe@7 22 BEGIN
jbe@7 23 RETURN plainto_tsquery('pg_catalog.simple', "query_text_p");
jbe@7 24 END;
jbe@7 25 $$;
jbe@7 26
jbe@7 27 COMMENT ON FUNCTION "text_search_query"(TEXT) IS 'Usage: WHERE "text_search_data" @@ "text_search_query"(''<user query>'')';
jbe@7 28
jbe@7 29
jbe@7 30 CREATE FUNCTION "highlight"
jbe@7 31 ( "body_p" TEXT,
jbe@7 32 "query_text_p" TEXT )
jbe@7 33 RETURNS TEXT
jbe@7 34 LANGUAGE 'plpgsql' IMMUTABLE AS $$
jbe@7 35 BEGIN
jbe@7 36 RETURN ts_headline(
jbe@7 37 'pg_catalog.simple',
jbe@8 38 replace(replace("body_p", e'\\', e'\\\\'), '*', e'\\*'),
jbe@7 39 "text_search_query"("query_text_p"),
jbe@7 40 'StartSel=* StopSel=* HighlightAll=TRUE' );
jbe@7 41 END;
jbe@7 42 $$;
jbe@7 43
jbe@7 44 COMMENT ON FUNCTION "highlight"
jbe@7 45 ( "body_p" TEXT,
jbe@7 46 "query_text_p" TEXT )
jbe@7 47 IS 'For a given a user query this function encapsulates all matches with asterisks. Asterisks and backslashes being already present are preceeded with one extra backslash.';
jbe@7 48
jbe@7 49
jbe@7 50
jbe@0 51 -------------------------
jbe@0 52 -- Tables and indicies --
jbe@0 53 -------------------------
jbe@0 54
jbe@8 55
jbe@385 56 CREATE TABLE "temporary_transaction_data" (
jbe@385 57 PRIMARY KEY ("txid", "key"),
jbe@385 58 "txid" INT8 DEFAULT txid_current(),
jbe@383 59 "key" TEXT,
jbe@383 60 "value" TEXT NOT NULL );
jbe@383 61
jbe@385 62 COMMENT ON TABLE "temporary_transaction_data" IS 'Table to store temporary transaction data; shall be emptied before a transaction is committed';
jbe@385 63
jbe@385 64 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 65
jbe@383 66
jbe@104 67 CREATE TABLE "system_setting" (
jbe@532 68 "member_ttl" INTERVAL,
jbe@532 69 "snapshot_retention" 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@532 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@532 76 COMMENT ON COLUMN "system_setting"."snapshot_retention" IS 'Unreferenced snapshots are retained for the given period of time after creation; set to NULL for infinite retention.';
jbe@104 77
jbe@104 78
jbe@111 79 CREATE TABLE "contingent" (
jbe@293 80 PRIMARY KEY ("polling", "time_frame"),
jbe@293 81 "polling" BOOLEAN,
jbe@293 82 "time_frame" INTERVAL,
jbe@111 83 "text_entry_limit" INT4,
jbe@111 84 "initiative_limit" INT4 );
jbe@111 85
jbe@111 86 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 87
jbe@293 88 COMMENT ON COLUMN "contingent"."polling" IS 'Determines if settings are for creating initiatives and new drafts of initiatives with "polling" flag set';
jbe@111 89 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 90 COMMENT ON COLUMN "contingent"."initiative_limit" IS 'Number of new initiatives to be opened by each member within a given time frame';
jbe@111 91
jbe@111 92
jbe@0 93 CREATE TABLE "member" (
jbe@0 94 "id" SERIAL4 PRIMARY KEY,
jbe@13 95 "created" TIMESTAMPTZ NOT NULL DEFAULT now(),
jbe@181 96 "invite_code" TEXT UNIQUE,
jbe@232 97 "invite_code_expiry" TIMESTAMPTZ,
jbe@182 98 "admin_comment" TEXT,
jbe@181 99 "activated" TIMESTAMPTZ,
jbe@184 100 "last_activity" DATE,
jbe@42 101 "last_login" TIMESTAMPTZ,
jbe@387 102 "last_delegation_check" TIMESTAMPTZ,
jbe@45 103 "login" TEXT UNIQUE,
jbe@0 104 "password" TEXT,
jbe@440 105 "authority" TEXT,
jbe@440 106 "authority_uid" TEXT,
jbe@440 107 "authority_login" TEXT,
jbe@99 108 "locked" BOOLEAN NOT NULL DEFAULT FALSE,
jbe@181 109 "active" BOOLEAN NOT NULL DEFAULT FALSE,
jbe@0 110 "admin" BOOLEAN NOT NULL DEFAULT FALSE,
jbe@221 111 "lang" TEXT,
jbe@7 112 "notify_email" TEXT,
jbe@11 113 "notify_email_unconfirmed" TEXT,
jbe@11 114 "notify_email_secret" TEXT UNIQUE,
jbe@11 115 "notify_email_secret_expiry" TIMESTAMPTZ,
jbe@55 116 "notify_email_lock_expiry" TIMESTAMPTZ,
jbe@486 117 "disable_notifications" BOOLEAN NOT NULL DEFAULT FALSE,
jbe@486 118 "notification_counter" INT4 NOT NULL DEFAULT 1,
jbe@486 119 "notification_sample_size" INT4 NOT NULL DEFAULT 3,
jbe@486 120 "notification_dow" INT4 CHECK ("notification_dow" BETWEEN 0 AND 6),
jbe@515 121 "notification_hour" INT4 DEFAULT floor(random() * 24) CHECK ("notification_hour" BETWEEN 0 AND 23),
jbe@504 122 "notification_sent" TIMESTAMP,
jbe@387 123 "login_recovery_expiry" TIMESTAMPTZ,
jbe@11 124 "password_reset_secret" TEXT UNIQUE,
jbe@11 125 "password_reset_secret_expiry" TIMESTAMPTZ,
jbe@225 126 "name" TEXT UNIQUE,
jbe@7 127 "identification" TEXT UNIQUE,
jbe@214 128 "authentication" TEXT,
jbe@532 129 "location" JSONB,
jbe@181 130 "text_search_data" TSVECTOR,
jbe@184 131 CONSTRAINT "active_requires_activated_and_last_activity"
jbe@225 132 CHECK ("active" = FALSE OR ("activated" NOTNULL AND "last_activity" NOTNULL)),
jbe@440 133 CONSTRAINT "authority_requires_uid_and_vice_versa"
jbe@447 134 CHECK (("authority" NOTNULL) = ("authority_uid" NOTNULL)),
jbe@440 135 CONSTRAINT "authority_uid_unique_per_authority"
jbe@440 136 UNIQUE ("authority", "authority_uid"),
jbe@440 137 CONSTRAINT "authority_login_requires_authority"
jbe@440 138 CHECK ("authority" NOTNULL OR "authority_login" ISNULL),
jbe@505 139 CONSTRAINT "notification_dow_requires_notification_hour"
jbe@505 140 CHECK ("notification_dow" ISNULL OR "notification_hour" NOTNULL),
jbe@225 141 CONSTRAINT "name_not_null_if_activated"
jbe@529 142 CHECK ("activated" ISNULL OR "name" NOTNULL) );
jbe@440 143 CREATE INDEX "member_authority_login_idx" ON "member" ("authority_login");
jbe@0 144 CREATE INDEX "member_active_idx" ON "member" ("active");
jbe@532 145 CREATE INDEX "member_location_idx" ON "member" USING gist ((GeoJSON_to_ecluster("location")));
jbe@8 146 CREATE INDEX "member_text_search_data_idx" ON "member" USING gin ("text_search_data");
jbe@7 147 CREATE TRIGGER "update_text_search_data"
jbe@7 148 BEFORE INSERT OR UPDATE ON "member"
jbe@7 149 FOR EACH ROW EXECUTE PROCEDURE
jbe@7 150 tsvector_update_trigger('text_search_data', 'pg_catalog.simple',
jbe@532 151 "name", "identification");
jbe@0 152
jbe@0 153 COMMENT ON TABLE "member" IS 'Users of the system, e.g. members of an organization';
jbe@0 154
jbe@181 155 COMMENT ON COLUMN "member"."created" IS 'Creation of member record and/or invite code';
jbe@181 156 COMMENT ON COLUMN "member"."invite_code" IS 'Optional invite code, to allow a member to initialize his/her account the first time';
jbe@232 157 COMMENT ON COLUMN "member"."invite_code_expiry" IS 'Expiry data/time for "invite_code"';
jbe@182 158 COMMENT ON COLUMN "member"."admin_comment" IS 'Hidden comment for administrative purposes';
jbe@207 159 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 160 COMMENT ON COLUMN "member"."last_activity" IS 'Date of last activity of member; required to be set for "active" members';
jbe@103 161 COMMENT ON COLUMN "member"."last_login" IS 'Timestamp of last login';
jbe@387 162 COMMENT ON COLUMN "member"."last_delegation_check" IS 'Timestamp of last delegation check (i.e. confirmation of all unit and area delegations)';
jbe@10 163 COMMENT ON COLUMN "member"."login" IS 'Login name';
jbe@10 164 COMMENT ON COLUMN "member"."password" IS 'Password (preferably as crypto-hash, depending on the frontend or access layer)';
jbe@440 165 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 166 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 167 COMMENT ON COLUMN "member"."authority_login" IS 'Login name for external accounts (field is not unique!)';
jbe@99 168 COMMENT ON COLUMN "member"."locked" IS 'Locked members can not log in.';
jbe@184 169 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 170 COMMENT ON COLUMN "member"."admin" IS 'TRUE for admins, which can administrate other users and setup policies and areas';
jbe@221 171 COMMENT ON COLUMN "member"."lang" IS 'Language code of the preferred language of the member';
jbe@10 172 COMMENT ON COLUMN "member"."notify_email" IS 'Email address where notifications of the system are sent to';
jbe@10 173 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 174 COMMENT ON COLUMN "member"."notify_email_secret" IS 'Secret sent to the address in "notify_email_unconformed"';
jbe@10 175 COMMENT ON COLUMN "member"."notify_email_secret_expiry" IS 'Expiry date/time for "notify_email_secret"';
jbe@55 176 COMMENT ON COLUMN "member"."notify_email_lock_expiry" IS 'Date/time until no further email confirmation mails may be sent (abuse protection)';
jbe@508 177 COMMENT ON COLUMN "member"."disable_notifications" IS 'TRUE if member does not want to receive notifications';
jbe@508 178 COMMENT ON COLUMN "member"."notification_counter" IS 'Sequential number of next scheduled notification message (used as a seed for pseudo-random initiative selection algorithm)';
jbe@508 179 COMMENT ON COLUMN "member"."notification_sample_size" IS 'Number of featured initiatives per issue in scheduled notification messages';
jbe@508 180 COMMENT ON COLUMN "member"."notification_dow" IS 'Day of week for scheduled notifications (NULL to receive a daily digest)';
jbe@508 181 COMMENT ON COLUMN "member"."notification_hour" IS 'Time of day when scheduled notifications are sent out';
jbe@508 182 COMMENT ON COLUMN "member"."notification_sent" IS 'Timestamp of last scheduled notification mail that has been sent out';
jbe@387 183 COMMENT ON COLUMN "member"."login_recovery_expiry" IS 'Date/time after which another login recovery attempt is allowed';
jbe@387 184 COMMENT ON COLUMN "member"."password_reset_secret" IS 'Secret string sent via e-mail for password recovery';
jbe@387 185 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 186 COMMENT ON COLUMN "member"."name" IS 'Distinct name of the member, may be NULL if account has not been activated yet';
jbe@10 187 COMMENT ON COLUMN "member"."identification" IS 'Optional identification number or code of the member';
jbe@214 188 COMMENT ON COLUMN "member"."authentication" IS 'Information about how this member was authenticated';
jbe@532 189 COMMENT ON COLUMN "member"."location" IS 'Geographic location on earth as GeoJSON object';
jbe@532 190
jbe@532 191
jbe@532 192 CREATE TABLE "member_history" ( -- TODO: redundancy with new "event" table
jbe@13 193 "id" SERIAL8 PRIMARY KEY,
jbe@13 194 "member_id" INT4 NOT NULL REFERENCES "member" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@13 195 "until" TIMESTAMPTZ NOT NULL DEFAULT now(),
jbe@42 196 "active" BOOLEAN NOT NULL,
jbe@13 197 "name" TEXT NOT NULL );
jbe@45 198 CREATE INDEX "member_history_member_id_idx" ON "member_history" ("member_id");
jbe@13 199
jbe@57 200 COMMENT ON TABLE "member_history" IS 'Filled by trigger; keeps information about old names and active flag of members';
jbe@13 201
jbe@13 202 COMMENT ON COLUMN "member_history"."id" IS 'Primary key, which can be used to sort entries correctly (and time warp resistant)';
jbe@57 203 COMMENT ON COLUMN "member_history"."until" IS 'Timestamp until the data was valid';
jbe@13 204
jbe@13 205
jbe@532 206 CREATE TABLE "member_profile" (
jbe@532 207 "member_id" INT4 PRIMARY KEY REFERENCES "member" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@532 208 "formatting_engine" TEXT,
jbe@532 209 "statement" TEXT,
jbe@532 210 "profile" JSONB,
jbe@532 211 "profile_text_data" TEXT,
jbe@532 212 "text_search_data" TSVECTOR );
jbe@532 213 CREATE INDEX "member_profile_text_search_data_idx" ON "member_profile" USING gin ("text_search_data");
jbe@532 214 CREATE TRIGGER "update_text_search_data"
jbe@532 215 BEFORE INSERT OR UPDATE ON "member_profile"
jbe@532 216 FOR EACH ROW EXECUTE PROCEDURE
jbe@532 217 tsvector_update_trigger('text_search_data', 'pg_catalog.simple',
jbe@532 218 'statement', 'profile_text_data');
jbe@532 219
jbe@532 220 COMMENT ON COLUMN "member_profile"."formatting_engine" IS 'Allows different formatting engines (i.e. wiki formats) to be used for "member_profile"."statement"';
jbe@532 221 COMMENT ON COLUMN "member_profile"."statement" IS 'Freely chosen text of the member for his/her profile';
jbe@532 222 COMMENT ON COLUMN "member_profile"."profile" IS 'Additional profile data as JSON document';
jbe@532 223 COMMENT ON COLUMN "member_profile"."profile_text_data" IS 'Text data from "profile" field for full text search';
jbe@532 224
jbe@532 225
jbe@159 226 CREATE TABLE "rendered_member_statement" (
jbe@159 227 PRIMARY KEY ("member_id", "format"),
jbe@461 228 "member_id" INT4 REFERENCES "member" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@159 229 "format" TEXT,
jbe@159 230 "content" TEXT NOT NULL );
jbe@159 231
jbe@159 232 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 233
jbe@9 234
jbe@9 235 CREATE TABLE "setting" (
jbe@9 236 PRIMARY KEY ("member_id", "key"),
jbe@9 237 "member_id" INT4 REFERENCES "member" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@9 238 "key" TEXT NOT NULL,
jbe@9 239 "value" TEXT NOT NULL );
jbe@9 240 CREATE INDEX "setting_key_idx" ON "setting" ("key");
jbe@9 241
jbe@38 242 COMMENT ON TABLE "setting" IS 'Place to store a frontend specific setting for members as a string';
jbe@9 243
jbe@9 244 COMMENT ON COLUMN "setting"."key" IS 'Name of the setting, preceded by a frontend specific prefix';
jbe@9 245
jbe@9 246
jbe@16 247 CREATE TABLE "setting_map" (
jbe@16 248 PRIMARY KEY ("member_id", "key", "subkey"),
jbe@16 249 "member_id" INT4 REFERENCES "member" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@16 250 "key" TEXT NOT NULL,
jbe@16 251 "subkey" TEXT NOT NULL,
jbe@16 252 "value" TEXT NOT NULL );
jbe@16 253 CREATE INDEX "setting_map_key_idx" ON "setting_map" ("key");
jbe@16 254
jbe@23 255 COMMENT ON TABLE "setting_map" IS 'Place to store a frontend specific setting for members as a map of key value pairs';
jbe@16 256
jbe@16 257 COMMENT ON COLUMN "setting_map"."key" IS 'Name of the setting, preceded by a frontend specific prefix';
jbe@16 258 COMMENT ON COLUMN "setting_map"."subkey" IS 'Key of a map entry';
jbe@16 259 COMMENT ON COLUMN "setting_map"."value" IS 'Value of a map entry';
jbe@16 260
jbe@16 261
jbe@23 262 CREATE TABLE "member_relation_setting" (
jbe@23 263 PRIMARY KEY ("member_id", "key", "other_member_id"),
jbe@23 264 "member_id" INT4 REFERENCES "member" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@23 265 "key" TEXT NOT NULL,
jbe@23 266 "other_member_id" INT4 REFERENCES "member" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@23 267 "value" TEXT NOT NULL );
jbe@23 268
jbe@38 269 COMMENT ON TABLE "member_relation_setting" IS 'Place to store a frontend specific setting related to relations between members as a string';
jbe@23 270
jbe@23 271
jbe@7 272 CREATE TYPE "member_image_type" AS ENUM ('photo', 'avatar');
jbe@7 273
jbe@7 274 COMMENT ON TYPE "member_image_type" IS 'Types of images for a member';
jbe@7 275
jbe@7 276
jbe@7 277 CREATE TABLE "member_image" (
jbe@7 278 PRIMARY KEY ("member_id", "image_type", "scaled"),
jbe@7 279 "member_id" INT4 REFERENCES "member" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@7 280 "image_type" "member_image_type",
jbe@7 281 "scaled" BOOLEAN,
jbe@7 282 "content_type" TEXT,
jbe@7 283 "data" BYTEA NOT NULL );
jbe@7 284
jbe@7 285 COMMENT ON TABLE "member_image" IS 'Images of members';
jbe@7 286
jbe@7 287 COMMENT ON COLUMN "member_image"."scaled" IS 'FALSE for original image, TRUE for scaled version of the image';
jbe@0 288
jbe@0 289
jbe@4 290 CREATE TABLE "member_count" (
jbe@341 291 "calculated" TIMESTAMPTZ NOT NULL DEFAULT now(),
jbe@5 292 "total_count" INT4 NOT NULL );
jbe@4 293
jbe@5 294 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 295
jbe@5 296 COMMENT ON COLUMN "member_count"."calculated" IS 'timestamp indicating when the total member count and area member counts were calculated';
jbe@5 297 COMMENT ON COLUMN "member_count"."total_count" IS 'Total count of active(!) members';
jbe@4 298
jbe@4 299
jbe@0 300 CREATE TABLE "contact" (
jbe@0 301 PRIMARY KEY ("member_id", "other_member_id"),
jbe@0 302 "member_id" INT4 REFERENCES "member" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@0 303 "other_member_id" INT4 REFERENCES "member" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@11 304 "public" BOOLEAN NOT NULL DEFAULT FALSE,
jbe@11 305 CONSTRAINT "cant_save_yourself_as_contact"
jbe@11 306 CHECK ("member_id" != "other_member_id") );
jbe@113 307 CREATE INDEX "contact_other_member_id_idx" ON "contact" ("other_member_id");
jbe@0 308
jbe@0 309 COMMENT ON TABLE "contact" IS 'Contact lists';
jbe@0 310
jbe@0 311 COMMENT ON COLUMN "contact"."member_id" IS 'Member having the contact list';
jbe@0 312 COMMENT ON COLUMN "contact"."other_member_id" IS 'Member referenced in the contact list';
jbe@0 313 COMMENT ON COLUMN "contact"."public" IS 'TRUE = display contact publically';
jbe@0 314
jbe@0 315
jbe@113 316 CREATE TABLE "ignored_member" (
jbe@113 317 PRIMARY KEY ("member_id", "other_member_id"),
jbe@113 318 "member_id" INT4 REFERENCES "member" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@113 319 "other_member_id" INT4 REFERENCES "member" ("id") ON DELETE CASCADE ON UPDATE CASCADE );
jbe@113 320 CREATE INDEX "ignored_member_other_member_id_idx" ON "ignored_member" ("other_member_id");
jbe@113 321
jbe@113 322 COMMENT ON TABLE "ignored_member" IS 'Possibility to filter other members';
jbe@113 323
jbe@113 324 COMMENT ON COLUMN "ignored_member"."member_id" IS 'Member ignoring someone';
jbe@113 325 COMMENT ON COLUMN "ignored_member"."other_member_id" IS 'Member being ignored';
jbe@113 326
jbe@113 327
jbe@220 328 CREATE TABLE "session" (
jbe@532 329 UNIQUE ("member_id", "id"), -- index needed for foreign-key on table "token"
jbe@532 330 "id" SERIAL8 PRIMARY KEY,
jbe@532 331 "ident" TEXT NOT NULL UNIQUE,
jbe@220 332 "additional_secret" TEXT,
jbe@532 333 "logout_token" TEXT,
jbe@220 334 "expiry" TIMESTAMPTZ NOT NULL DEFAULT now() + '24 hours',
jbe@461 335 "member_id" INT4 REFERENCES "member" ("id") ON DELETE SET NULL,
jbe@440 336 "authority" TEXT,
jbe@440 337 "authority_uid" TEXT,
jbe@440 338 "authority_login" TEXT,
jbe@387 339 "needs_delegation_check" BOOLEAN NOT NULL DEFAULT FALSE,
jbe@220 340 "lang" TEXT );
jbe@220 341 CREATE INDEX "session_expiry_idx" ON "session" ("expiry");
jbe@220 342
jbe@220 343 COMMENT ON TABLE "session" IS 'Sessions, i.e. for a web-frontend or API layer';
jbe@220 344
jbe@220 345 COMMENT ON COLUMN "session"."ident" IS 'Secret session identifier (i.e. random string)';
jbe@220 346 COMMENT ON COLUMN "session"."additional_secret" IS 'Additional field to store a secret, which can be used against CSRF attacks';
jbe@532 347 COMMENT ON COLUMN "session"."logout_token" IS 'Optional token to authorize logout through external component';
jbe@220 348 COMMENT ON COLUMN "session"."member_id" IS 'Reference to member, who is logged in';
jbe@440 349 COMMENT ON COLUMN "session"."authority" IS 'Temporary store for "member"."authority" during member account creation';
jbe@440 350 COMMENT ON COLUMN "session"."authority_uid" IS 'Temporary store for "member"."authority_uid" during member account creation';
jbe@440 351 COMMENT ON COLUMN "session"."authority_login" IS 'Temporary store for "member"."authority_login" during member account creation';
jbe@387 352 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 353 COMMENT ON COLUMN "session"."lang" IS 'Language code of the selected language';
jbe@220 354
jbe@220 355
jbe@532 356 CREATE TYPE "authflow" AS ENUM ('code', 'token');
jbe@532 357
jbe@532 358 COMMENT ON TYPE "authflow" IS 'OAuth 2.0 flows: ''code'' = Authorization Code flow, ''token'' = Implicit flow';
jbe@532 359
jbe@532 360
jbe@532 361 CREATE TABLE "system_application" (
jbe@532 362 "id" SERIAL4 PRIMARY KEY,
jbe@532 363 "name" TEXT NOT NULL,
jbe@532 364 "client_id" TEXT NOT NULL UNIQUE,
jbe@532 365 "default_redirect_uri" TEXT NOT NULL,
jbe@532 366 "cert_common_name" TEXT,
jbe@532 367 "client_cred_scope" TEXT,
jbe@532 368 "flow" "authflow",
jbe@532 369 "automatic_scope" TEXT,
jbe@532 370 "permitted_scope" TEXT,
jbe@532 371 "forbidden_scope" TEXT );
jbe@532 372
jbe@532 373 COMMENT ON TABLE "system_application" IS 'OAuth 2.0 clients that are registered by the system administrator';
jbe@532 374
jbe@532 375 COMMENT ON COLUMN "system_application"."name" IS 'Human readable name of application';
jbe@532 376 COMMENT ON COLUMN "system_application"."client_id" IS 'OAuth 2.0 "client_id"';
jbe@532 377 COMMENT ON COLUMN "system_application"."cert_common_name" IS 'Value for CN field of TLS client certificate';
jbe@532 378 COMMENT ON COLUMN "system_application"."client_cred_scope" IS 'Space-separated list of scopes; If set, Client Credentials Grant is allowed; value determines scope';
jbe@532 379 COMMENT ON COLUMN "system_application"."flow" IS 'If set to ''code'' or ''token'', then Authorization Code or Implicit flow is allowed respectively';
jbe@532 380 COMMENT ON COLUMN "system_application"."automatic_scope" IS 'Space-separated list of scopes; Automatically granted scope for Authorization Code or Implicit flow';
jbe@532 381 COMMENT ON COLUMN "system_application"."permitted_scope" IS 'Space-separated list of scopes; If set, scope that members may grant to the application is limited to the given value';
jbe@532 382 COMMENT ON COLUMN "system_application"."forbidden_scope" IS 'Space-separated list of scopes that may not be granted to the application by a member';
jbe@532 383
jbe@532 384
jbe@532 385 CREATE TABLE "system_application_redirect_uri" (
jbe@532 386 PRIMARY KEY ("system_application_id", "redirect_uri"),
jbe@532 387 "system_application_id" INT4 REFERENCES "system_application" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@532 388 "redirect_uri" TEXT );
jbe@532 389
jbe@532 390 COMMENT ON TABLE "system_application_redirect_uri" IS 'Additional OAuth 2.0 redirection endpoints, which may be selected through the "redirect_uri" GET parameter';
jbe@532 391
jbe@532 392
jbe@532 393 CREATE TABLE "dynamic_application_scope" (
jbe@532 394 PRIMARY KEY ("redirect_uri", "flow", "scope"),
jbe@532 395 "redirect_uri" TEXT,
jbe@532 396 "flow" TEXT,
jbe@532 397 "scope" TEXT,
jbe@532 398 "expiry" TIMESTAMPTZ NOT NULL DEFAULT now() + '24 hours' );
jbe@532 399 CREATE INDEX "dynamic_application_scope_redirect_uri_scope_idx" ON "dynamic_application_scope" ("redirect_uri", "flow", "scope");
jbe@532 400 CREATE INDEX "dynamic_application_scope_expiry_idx" ON "dynamic_application_scope" ("expiry");
jbe@532 401
jbe@532 402 COMMENT ON TABLE "dynamic_application_scope" IS 'Dynamic OAuth 2.0 client registration data';
jbe@532 403
jbe@532 404 COMMENT ON COLUMN "dynamic_application_scope"."redirect_uri" IS 'Redirection endpoint for which the registration has been done';
jbe@532 405 COMMENT ON COLUMN "dynamic_application_scope"."flow" IS 'OAuth 2.0 flow for which the registration has been done (see also "system_application"."flow")';
jbe@532 406 COMMENT ON COLUMN "dynamic_application_scope"."scope" IS 'Single scope without space characters (use multiple rows for more scopes)';
jbe@532 407 COMMENT ON COLUMN "dynamic_application_scope"."expiry" IS 'Expiry unless renewed';
jbe@532 408
jbe@532 409
jbe@532 410 CREATE TABLE "member_application" (
jbe@532 411 "id" SERIAL4 PRIMARY KEY,
jbe@532 412 UNIQUE ("system_application_id", "member_id"),
jbe@532 413 UNIQUE ("domain", "member_id"),
jbe@532 414 "member_id" INT4 NOT NULL REFERENCES "member" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@532 415 "system_application_id" INT4 REFERENCES "system_application" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@532 416 "domain" TEXT,
jbe@532 417 "session_id" INT8,
jbe@532 418 FOREIGN KEY ("member_id", "session_id") REFERENCES "session" ("member_id", "id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@532 419 "scope" TEXT NOT NULL,
jbe@532 420 CONSTRAINT "system_application_or_domain_but_not_both" CHECK (
jbe@532 421 ("system_application_id" NOTNULL AND "domain" ISNULL) OR
jbe@532 422 ("system_application_id" ISNULL AND "domain" NOTNULL) ) );
jbe@532 423 CREATE INDEX "member_application_member_id_idx" ON "member_application" ("member_id");
jbe@532 424
jbe@532 425 COMMENT ON TABLE "member_application" IS 'Application authorized by a member';
jbe@532 426
jbe@532 427 COMMENT ON COLUMN "member_application"."system_application_id" IS 'If set, then application is a system application';
jbe@532 428 COMMENT ON COLUMN "member_application"."domain" IS 'If set, then application is a dynamically registered OAuth 2.0 client; value is set to client''s domain';
jbe@532 429 COMMENT ON COLUMN "member_application"."session_id" IS 'If set, registration ends with session';
jbe@532 430 COMMENT ON COLUMN "member_application"."scope" IS 'Granted scope as space-separated list of strings';
jbe@532 431
jbe@532 432
jbe@532 433 CREATE TYPE "token_type" AS ENUM ('authorization', 'refresh', 'access');
jbe@532 434
jbe@532 435 COMMENT ON TYPE "token_type" IS 'Types for entries in "token" table';
jbe@532 436
jbe@532 437
jbe@532 438 CREATE TABLE "token" (
jbe@532 439 "id" SERIAL8 PRIMARY KEY,
jbe@532 440 "token" TEXT NOT NULL UNIQUE,
jbe@532 441 "token_type" "token_type" NOT NULL,
jbe@532 442 "authorization_token_id" INT8 REFERENCES "token" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@532 443 "member_id" INT4 NOT NULL REFERENCES "member" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@532 444 "system_application_id" INT4 REFERENCES "system_application" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@532 445 "domain" TEXT,
jbe@532 446 FOREIGN KEY ("member_id", "domain") REFERENCES "member_application" ("member_id", "domain") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@532 447 "session_id" INT8,
jbe@532 448 FOREIGN KEY ("member_id", "session_id") REFERENCES "session" ("member_id", "id") ON DELETE RESTRICT ON UPDATE CASCADE, -- NOTE: deletion through "detach_token_from_session" trigger on table "session"
jbe@532 449 "redirect_uri" TEXT,
jbe@532 450 "redirect_uri_explicit" BOOLEAN,
jbe@532 451 "created" TIMESTAMPTZ NOT NULL DEFAULT now(),
jbe@532 452 "expiry" TIMESTAMPTZ DEFAULT now() + '1 hour',
jbe@532 453 "used" BOOLEAN NOT NULL DEFAULT FALSE,
jbe@532 454 "scope" TEXT NOT NULL,
jbe@532 455 CONSTRAINT "access_token_needs_expiry"
jbe@532 456 CHECK ("token_type" != 'access'::"token_type" OR "expiry" NOTNULL),
jbe@532 457 CONSTRAINT "authorization_token_needs_redirect_uri"
jbe@532 458 CHECK ("token_type" != 'authorization'::"token_type" OR ("redirect_uri" NOTNULL AND "redirect_uri_explicit" NOTNULL) ) );
jbe@532 459 CREATE INDEX "token_member_id_idx" ON "token" ("member_id");
jbe@532 460 CREATE INDEX "token_authorization_token_id_idx" ON "token" ("authorization_token_id");
jbe@532 461 CREATE INDEX "token_expiry_idx" ON "token" ("expiry");
jbe@532 462
jbe@532 463 COMMENT ON TABLE "token" IS 'Issued OAuth 2.0 authorization codes and access/refresh tokens';
jbe@532 464
jbe@532 465 COMMENT ON COLUMN "token"."token" IS 'String secret (the actual token)';
jbe@532 466 COMMENT ON COLUMN "token"."authorization_token_id" IS 'Reference to authorization token if tokens were originally created by Authorization Code flow (allows deletion if code is used twice)';
jbe@532 467 COMMENT ON COLUMN "token"."system_application_id" IS 'If set, then application is a system application';
jbe@532 468 COMMENT ON COLUMN "token"."domain" IS 'If set, then application is a dynamically registered OAuth 2.0 client; value is set to client''s domain';
jbe@532 469 COMMENT ON COLUMN "token"."session_id" IS 'If set, then token is tied to a session; Deletion of session sets value to NULL (via trigger) and removes all scopes without suffix ''_detached''';
jbe@532 470 COMMENT ON COLUMN "token"."redirect_uri" IS 'Authorization codes must be bound to a specific redirect URI';
jbe@532 471 COMMENT ON COLUMN "token"."redirect_uri_explicit" IS 'True if ''redirect_uri'' parameter was explicitly specified during authorization request of the Authorization Code flow (since RFC 6749 requires it to be included in the access token request in this case)';
jbe@532 472 COMMENT ON COLUMN "token"."expiry" IS 'Point in time when code or token expired; In case of "used" authorization codes, authorization code must not be deleted as long as tokens exist which refer to the authorization code';
jbe@532 473 COMMENT ON COLUMN "token"."used" IS 'Can be set to TRUE for authorization codes that have been used (enables deletion of authorization codes that were used twice)';
jbe@532 474 COMMENT ON COLUMN "token"."scope" IS 'Scope as space-separated list of strings (detached scopes are marked with ''_detached'' suffix)';
jbe@532 475
jbe@532 476
jbe@532 477 CREATE TABLE "token_scope" (
jbe@532 478 PRIMARY KEY ("token_id", "index"),
jbe@532 479 "token_id" INT8 REFERENCES "token" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@532 480 "index" INT4,
jbe@532 481 "scope" TEXT NOT NULL );
jbe@532 482
jbe@532 483 COMMENT ON TABLE "token_scope" IS 'Additional scopes for an authorization code if ''scope1'', ''scope2'', etc. parameters were used during Authorization Code flow to request several access and refresh tokens at once';
jbe@532 484
jbe@532 485
jbe@424 486 CREATE TYPE "defeat_strength" AS ENUM ('simple', 'tuple');
jbe@424 487
jbe@424 488 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 489
jbe@424 490
jbe@424 491 CREATE TYPE "tie_breaking" AS ENUM ('simple', 'variant1', 'variant2');
jbe@424 492
jbe@424 493 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 494
jbe@424 495
jbe@0 496 CREATE TABLE "policy" (
jbe@0 497 "id" SERIAL4 PRIMARY KEY,
jbe@9 498 "index" INT4 NOT NULL,
jbe@0 499 "active" BOOLEAN NOT NULL DEFAULT TRUE,
jbe@0 500 "name" TEXT NOT NULL UNIQUE,
jbe@0 501 "description" TEXT NOT NULL DEFAULT '',
jbe@261 502 "polling" BOOLEAN NOT NULL DEFAULT FALSE,
jbe@447 503 "min_admission_time" INTERVAL,
jbe@447 504 "max_admission_time" INTERVAL,
jbe@261 505 "discussion_time" INTERVAL,
jbe@261 506 "verification_time" INTERVAL,
jbe@261 507 "voting_time" INTERVAL,
jbe@532 508 "issue_quorum" INT4 CHECK ("issue_quorum" >= 1),
jbe@532 509 "issue_quorum_num" INT4,
jbe@532 510 "issue_quorum_den" INT4,
jbe@532 511 "initiative_quorum" INT4 NOT NULL CHECK ("initiative_quorum" >= 1),
jbe@0 512 "initiative_quorum_num" INT4 NOT NULL,
jbe@10 513 "initiative_quorum_den" INT4 NOT NULL,
jbe@424 514 "defeat_strength" "defeat_strength" NOT NULL DEFAULT 'tuple',
jbe@424 515 "tie_breaking" "tie_breaking" NOT NULL DEFAULT 'variant1',
jbe@167 516 "direct_majority_num" INT4 NOT NULL DEFAULT 1,
jbe@167 517 "direct_majority_den" INT4 NOT NULL DEFAULT 2,
jbe@167 518 "direct_majority_strict" BOOLEAN NOT NULL DEFAULT TRUE,
jbe@167 519 "direct_majority_positive" INT4 NOT NULL DEFAULT 0,
jbe@167 520 "direct_majority_non_negative" INT4 NOT NULL DEFAULT 0,
jbe@167 521 "indirect_majority_num" INT4 NOT NULL DEFAULT 1,
jbe@167 522 "indirect_majority_den" INT4 NOT NULL DEFAULT 2,
jbe@167 523 "indirect_majority_strict" BOOLEAN NOT NULL DEFAULT TRUE,
jbe@167 524 "indirect_majority_positive" INT4 NOT NULL DEFAULT 0,
jbe@167 525 "indirect_majority_non_negative" INT4 NOT NULL DEFAULT 0,
jbe@429 526 "no_reverse_beat_path" BOOLEAN NOT NULL DEFAULT FALSE,
jbe@260 527 "no_multistage_majority" BOOLEAN NOT NULL DEFAULT FALSE,
jbe@458 528 CONSTRAINT "issue_quorum_if_and_only_if_not_polling" CHECK (
jbe@532 529 "polling" = ("issue_quorum" ISNULL) AND
jbe@532 530 "polling" = ("issue_quorum_num" ISNULL) AND
jbe@532 531 "polling" = ("issue_quorum_den" ISNULL) ),
jbe@528 532 CONSTRAINT "min_admission_time_smaller_than_max_admission_time" CHECK (
jbe@528 533 "min_admission_time" < "max_admission_time" ),
jbe@528 534 CONSTRAINT "timing_null_or_not_null_constraints" CHECK (
jbe@261 535 ( "polling" = FALSE AND
jbe@447 536 "min_admission_time" NOTNULL AND "max_admission_time" NOTNULL AND
jbe@447 537 "discussion_time" NOTNULL AND
jbe@447 538 "verification_time" NOTNULL AND
jbe@447 539 "voting_time" NOTNULL ) OR
jbe@261 540 ( "polling" = TRUE AND
jbe@447 541 "min_admission_time" ISNULL AND "max_admission_time" ISNULL AND
jbe@447 542 "discussion_time" NOTNULL AND
jbe@447 543 "verification_time" NOTNULL AND
jbe@447 544 "voting_time" NOTNULL ) OR
jbe@447 545 ( "polling" = TRUE AND
jbe@447 546 "min_admission_time" ISNULL AND "max_admission_time" ISNULL AND
jbe@447 547 "discussion_time" ISNULL AND
jbe@447 548 "verification_time" ISNULL AND
jbe@447 549 "voting_time" ISNULL ) ),
jbe@429 550 CONSTRAINT "no_reverse_beat_path_requires_tuple_defeat_strength" CHECK (
jbe@429 551 "defeat_strength" = 'tuple'::"defeat_strength" OR
jbe@429 552 "no_reverse_beat_path" = FALSE ) );
jbe@0 553 CREATE INDEX "policy_active_idx" ON "policy" ("active");
jbe@0 554
jbe@0 555 COMMENT ON TABLE "policy" IS 'Policies for a particular proceeding type (timelimits, quorum)';
jbe@0 556
jbe@9 557 COMMENT ON COLUMN "policy"."index" IS 'Determines the order in listings';
jbe@0 558 COMMENT ON COLUMN "policy"."active" IS 'TRUE = policy can be used for new issues';
jbe@447 559 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@528 560 COMMENT ON COLUMN "policy"."min_admission_time" IS 'Minimum duration of issue state ''admission''; Minimum time an issue stays open; Note: should be considerably smaller than "max_admission_time"';
jbe@447 561 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 562 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 563 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 564 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@532 565 COMMENT ON COLUMN "policy"."issue_quorum" IS 'Absolute number of supporters needed by an initiative to be "accepted", i.e. pass from ''admission'' to ''discussion'' state';
jbe@532 566 COMMENT ON COLUMN "policy"."issue_quorum_num" IS 'Numerator of supporter quorum to be reached by an initiative to be "accepted", i.e. pass from ''admission'' to ''discussion'' state (Note: further requirements apply, see quorum columns of "area" table)';
jbe@532 567 COMMENT ON COLUMN "policy"."issue_quorum_den" IS 'Denominator of supporter quorum to be reached by an initiative to be "accepted", i.e. pass from ''admission'' to ''discussion'' state (Note: further requirements apply, see quorum columns of "area" table)';
jbe@532 568 COMMENT ON COLUMN "policy"."initiative_quorum" IS 'Absolute number of satisfied supporters to be reached by an initiative to be "admitted" for voting';
jbe@528 569 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 570 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 571 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 572 COMMENT ON COLUMN "policy"."tie_breaking" IS 'Tie-breaker for the Schulze method; see type "tie_breaking"; ''variant1'' or ''variant2'' are recommended';
jbe@167 573 COMMENT ON COLUMN "policy"."direct_majority_num" IS 'Numerator of fraction of neccessary direct majority for initiatives to be attainable as winner';
jbe@167 574 COMMENT ON COLUMN "policy"."direct_majority_den" IS 'Denominator of fraction of neccessary direct majority for initaitives to be attainable as winner';
jbe@167 575 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 576 COMMENT ON COLUMN "policy"."direct_majority_positive" IS 'Absolute number of "positive_votes" neccessary for an initiative to be attainable as winner';
jbe@167 577 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 578 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 579 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 580 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 581 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 582 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 583 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 584 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 585
jbe@0 586
jbe@97 587 CREATE TABLE "unit" (
jbe@97 588 "id" SERIAL4 PRIMARY KEY,
jbe@97 589 "parent_id" INT4 REFERENCES "unit" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@97 590 "active" BOOLEAN NOT NULL DEFAULT TRUE,
jbe@97 591 "name" TEXT NOT NULL,
jbe@97 592 "description" TEXT NOT NULL DEFAULT '',
jbe@444 593 "external_reference" TEXT,
jbe@97 594 "member_count" INT4,
jbe@532 595 "region" JSONB,
jbe@97 596 "text_search_data" TSVECTOR );
jbe@97 597 CREATE INDEX "unit_root_idx" ON "unit" ("id") WHERE "parent_id" ISNULL;
jbe@97 598 CREATE INDEX "unit_parent_id_idx" ON "unit" ("parent_id");
jbe@97 599 CREATE INDEX "unit_active_idx" ON "unit" ("active");
jbe@532 600 CREATE INDEX "unit_region_idx" ON "unit" USING gist ((GeoJSON_to_ecluster("region")));
jbe@97 601 CREATE INDEX "unit_text_search_data_idx" ON "unit" USING gin ("text_search_data");
jbe@97 602 CREATE TRIGGER "update_text_search_data"
jbe@97 603 BEFORE INSERT OR UPDATE ON "unit"
jbe@97 604 FOR EACH ROW EXECUTE PROCEDURE
jbe@97 605 tsvector_update_trigger('text_search_data', 'pg_catalog.simple',
jbe@97 606 "name", "description" );
jbe@97 607
jbe@97 608 COMMENT ON TABLE "unit" IS 'Organizational units organized as trees; Delegations are not inherited through these trees.';
jbe@97 609
jbe@444 610 COMMENT ON COLUMN "unit"."parent_id" IS 'Parent id of tree node; Multiple roots allowed';
jbe@444 611 COMMENT ON COLUMN "unit"."active" IS 'TRUE means new issues can be created in areas of this unit';
jbe@444 612 COMMENT ON COLUMN "unit"."external_reference" IS 'Opaque data field to store an external reference';
jbe@528 613 COMMENT ON COLUMN "unit"."member_count" IS 'Count of members as determined by column "voting_right" in table "privilege" (only active members counted)';
jbe@528 614 COMMENT ON COLUMN "unit"."region" IS 'Scattered (or hollow) polygon represented as an array of polygons indicating valid coordinates for initiatives of issues with this policy';
jbe@97 615
jbe@97 616
jbe@465 617 CREATE TABLE "subscription" (
jbe@465 618 PRIMARY KEY ("member_id", "unit_id"),
jbe@465 619 "member_id" INT4 REFERENCES "member" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@465 620 "unit_id" INT4 REFERENCES "unit" ("id") ON DELETE CASCADE ON UPDATE CASCADE );
jbe@465 621 CREATE INDEX "subscription_unit_id_idx" ON "subscription" ("unit_id");
jbe@465 622
jbe@465 623 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 624
jbe@465 625
jbe@203 626 CREATE TABLE "unit_setting" (
jbe@203 627 PRIMARY KEY ("member_id", "key", "unit_id"),
jbe@203 628 "member_id" INT4 REFERENCES "member" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@203 629 "key" TEXT NOT NULL,
jbe@203 630 "unit_id" INT4 REFERENCES "unit" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@203 631 "value" TEXT NOT NULL );
jbe@203 632
jbe@203 633 COMMENT ON TABLE "unit_setting" IS 'Place for frontend to store unit specific settings of members as strings';
jbe@203 634
jbe@203 635
jbe@0 636 CREATE TABLE "area" (
jbe@532 637 UNIQUE ("unit_id", "id"), -- index needed for foreign-key on table "event"
jbe@532 638 "id" SERIAL4 PRIMARY KEY,
jbe@457 639 "unit_id" INT4 NOT NULL REFERENCES "unit" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@0 640 "active" BOOLEAN NOT NULL DEFAULT TRUE,
jbe@0 641 "name" TEXT NOT NULL,
jbe@4 642 "description" TEXT NOT NULL DEFAULT '',
jbe@532 643 "quorum_standard" NUMERIC NOT NULL DEFAULT 2 CHECK ("quorum_standard" >= 0),
jbe@532 644 "quorum_issues" NUMERIC NOT NULL DEFAULT 1 CHECK ("quorum_issues" > 0),
jbe@532 645 "quorum_time" INTERVAL NOT NULL DEFAULT '1 day' CHECK ("quorum_time" > '0'::INTERVAL),
jbe@532 646 "quorum_exponent" NUMERIC NOT NULL DEFAULT 0.5 CHECK ("quorum_exponent" BETWEEN 0 AND 1),
jbe@532 647 "quorum_factor" NUMERIC NOT NULL DEFAULT 2 CHECK ("quorum_factor" >= 1),
jbe@532 648 "quorum_den" INT4 CHECK ("quorum_den" > 0),
jbe@532 649 "issue_quorum" INT4,
jbe@444 650 "external_reference" TEXT,
jbe@532 651 "region" JSONB,
jbe@7 652 "text_search_data" TSVECTOR );
jbe@0 653 CREATE INDEX "area_active_idx" ON "area" ("active");
jbe@532 654 CREATE INDEX "area_region_idx" ON "area" USING gist ((GeoJSON_to_ecluster("region")));
jbe@8 655 CREATE INDEX "area_text_search_data_idx" ON "area" USING gin ("text_search_data");
jbe@7 656 CREATE TRIGGER "update_text_search_data"
jbe@7 657 BEFORE INSERT OR UPDATE ON "area"
jbe@7 658 FOR EACH ROW EXECUTE PROCEDURE
jbe@7 659 tsvector_update_trigger('text_search_data', 'pg_catalog.simple',
jbe@7 660 "name", "description" );
jbe@0 661
jbe@0 662 COMMENT ON TABLE "area" IS 'Subject areas';
jbe@0 663
jbe@528 664 COMMENT ON COLUMN "area"."active" IS 'TRUE means new issues can be created in this area';
jbe@532 665 COMMENT ON COLUMN "area"."quorum_standard" IS 'Parameter for dynamic issue quorum: default quorum';
jbe@532 666 COMMENT ON COLUMN "area"."quorum_issues" IS 'Parameter for dynamic issue quorum: number of open issues for default quorum';
jbe@532 667 COMMENT ON COLUMN "area"."quorum_time" IS 'Parameter for dynamic issue quorum: discussion, verification, and voting time of open issues to result in the given default quorum (open issues with shorter time will increase quorum and open issues with longer time will reduce quorum if "quorum_exponent" is greater than zero)';
jbe@532 668 COMMENT ON COLUMN "area"."quorum_exponent" IS 'Parameter for dynamic issue quorum: set to zero to ignore duration of open issues, set to one to fully take duration of open issues into account; defaults to 0.5';
jbe@532 669 COMMENT ON COLUMN "area"."quorum_factor" IS 'Parameter for dynamic issue quorum: factor to increase dynamic quorum when a number of "quorum_issues" issues with "quorum_time" duration of discussion, verification, and voting phase are added to the number of open admitted issues';
jbe@532 670 COMMENT ON COLUMN "area"."quorum_den" IS 'Parameter for dynamic issue quorum: when set, dynamic quorum is multiplied with "issue"."population" and divided by "quorum_den" (and then rounded up)';
jbe@532 671 COMMENT ON COLUMN "area"."issue_quorum" IS 'Additional dynamic issue quorum based on the number of open accepted issues; automatically calculated by function "issue_admission"';
jbe@528 672 COMMENT ON COLUMN "area"."external_reference" IS 'Opaque data field to store an external reference';
jbe@528 673 COMMENT ON COLUMN "area"."region" IS 'Scattered (or hollow) polygon represented as an array of polygons indicating valid coordinates for initiatives of issues with this policy';
jbe@0 674
jbe@0 675
jbe@465 676 CREATE TABLE "ignored_area" (
jbe@465 677 PRIMARY KEY ("member_id", "area_id"),
jbe@465 678 "member_id" INT4 REFERENCES "unit" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@465 679 "area_id" INT4 REFERENCES "area" ("id") ON DELETE CASCADE ON UPDATE CASCADE );
jbe@465 680 CREATE INDEX "ignored_area_area_id_idx" ON "ignored_area" ("area_id");
jbe@465 681
jbe@465 682 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 683
jbe@465 684
jbe@23 685 CREATE TABLE "area_setting" (
jbe@23 686 PRIMARY KEY ("member_id", "key", "area_id"),
jbe@23 687 "member_id" INT4 REFERENCES "member" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@23 688 "key" TEXT NOT NULL,
jbe@23 689 "area_id" INT4 REFERENCES "area" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@23 690 "value" TEXT NOT NULL );
jbe@23 691
jbe@23 692 COMMENT ON TABLE "area_setting" IS 'Place for frontend to store area specific settings of members as strings';
jbe@23 693
jbe@23 694
jbe@9 695 CREATE TABLE "allowed_policy" (
jbe@9 696 PRIMARY KEY ("area_id", "policy_id"),
jbe@9 697 "area_id" INT4 REFERENCES "area" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@9 698 "policy_id" INT4 NOT NULL REFERENCES "policy" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@9 699 "default_policy" BOOLEAN NOT NULL DEFAULT FALSE );
jbe@9 700 CREATE UNIQUE INDEX "allowed_policy_one_default_per_area_idx" ON "allowed_policy" ("area_id") WHERE "default_policy";
jbe@9 701
jbe@9 702 COMMENT ON TABLE "allowed_policy" IS 'Selects which policies can be used in each area';
jbe@9 703
jbe@9 704 COMMENT ON COLUMN "allowed_policy"."default_policy" IS 'One policy per area can be set as default.';
jbe@9 705
jbe@9 706
jbe@528 707 CREATE TABLE "snapshot" (
jbe@532 708 UNIQUE ("issue_id", "id"), -- index needed for foreign-key on table "issue"
jbe@528 709 "id" SERIAL8 PRIMARY KEY,
jbe@532 710 "calculated" TIMESTAMPTZ NOT NULL DEFAULT now(),
jbe@532 711 "population" INT4,
jbe@532 712 "area_id" INT4 NOT NULL REFERENCES "area" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@532 713 "issue_id" INT4 ); -- NOTE: following (cyclic) reference is added later through ALTER command: REFERENCES "issue" ("id") ON DELETE CASCADE ON UPDATE CASCADE
jbe@528 714
jbe@528 715 COMMENT ON TABLE "snapshot" IS 'Point in time when a snapshot of one or more issues (see table "snapshot_issue") and their supporter situation is taken';
jbe@8 716
jbe@8 717
jbe@532 718 CREATE TABLE "snapshot_population" (
jbe@532 719 PRIMARY KEY ("snapshot_id", "member_id"),
jbe@532 720 "snapshot_id" INT8 REFERENCES "snapshot" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@532 721 "member_id" INT4 REFERENCES "member" ("id") ON DELETE RESTRICT ON UPDATE CASCADE );
jbe@532 722
jbe@532 723 COMMENT ON TABLE "snapshot_population" IS 'Members with voting right relevant for a snapshot';
jbe@532 724
jbe@532 725
jbe@112 726 CREATE TYPE "issue_state" AS ENUM (
jbe@112 727 'admission', 'discussion', 'verification', 'voting',
jbe@389 728 'canceled_by_admin',
jbe@113 729 'canceled_revoked_before_accepted',
jbe@113 730 'canceled_issue_not_accepted',
jbe@113 731 'canceled_after_revocation_during_discussion',
jbe@113 732 'canceled_after_revocation_during_verification',
jbe@113 733 'canceled_no_initiative_admitted',
jbe@112 734 'finished_without_winner', 'finished_with_winner');
jbe@111 735
jbe@111 736 COMMENT ON TYPE "issue_state" IS 'State of issues';
jbe@111 737
jbe@111 738
jbe@0 739 CREATE TABLE "issue" (
jbe@532 740 UNIQUE ("area_id", "id"), -- index needed for foreign-key on table "event"
jbe@536 741 UNIQUE ("policy_id", "id"), -- index needed for foreign-key on table "event"
jbe@0 742 "id" SERIAL4 PRIMARY KEY,
jbe@0 743 "area_id" INT4 NOT NULL REFERENCES "area" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@0 744 "policy_id" INT4 NOT NULL REFERENCES "policy" ("id") ON DELETE RESTRICT ON UPDATE CASCADE,
jbe@389 745 "admin_notice" TEXT,
jbe@444 746 "external_reference" TEXT,
jbe@111 747 "state" "issue_state" NOT NULL DEFAULT 'admission',
jbe@328 748 "phase_finished" TIMESTAMPTZ,
jbe@0 749 "created" TIMESTAMPTZ NOT NULL DEFAULT now(),
jbe@0 750 "accepted" TIMESTAMPTZ,
jbe@3 751 "half_frozen" TIMESTAMPTZ,
jbe@3 752 "fully_frozen" TIMESTAMPTZ,
jbe@0 753 "closed" TIMESTAMPTZ,
jbe@59 754 "cleaned" TIMESTAMPTZ,
jbe@447 755 "min_admission_time" INTERVAL,
jbe@447 756 "max_admission_time" INTERVAL,
jbe@22 757 "discussion_time" INTERVAL NOT NULL,
jbe@22 758 "verification_time" INTERVAL NOT NULL,
jbe@22 759 "voting_time" INTERVAL NOT NULL,
jbe@532 760 "calculated" TIMESTAMPTZ, -- NOTE: copy of "calculated" column of latest snapshot, but no referential integrity to avoid overhead
jbe@528 761 "latest_snapshot_id" INT8 REFERENCES "snapshot" ("id") ON DELETE RESTRICT ON UPDATE CASCADE,
jbe@528 762 "admission_snapshot_id" INT8 REFERENCES "snapshot" ("id") ON DELETE SET NULL ON UPDATE CASCADE,
jbe@532 763 "half_freeze_snapshot_id" INT8,
jbe@532 764 FOREIGN KEY ("id", "half_freeze_snapshot_id")
jbe@532 765 REFERENCES "snapshot" ("issue_id", "id") ON DELETE RESTRICT ON UPDATE CASCADE,
jbe@532 766 "full_freeze_snapshot_id" INT8,
jbe@532 767 FOREIGN KEY ("id", "full_freeze_snapshot_id")
jbe@532 768 REFERENCES "snapshot" ("issue_id", "id") ON DELETE RESTRICT ON UPDATE CASCADE,
jbe@0 769 "population" INT4,
jbe@4 770 "voter_count" INT4,
jbe@170 771 "status_quo_schulze_rank" INT4,
jbe@291 772 CONSTRAINT "admission_time_not_null_unless_instantly_accepted" CHECK (
jbe@447 773 ("min_admission_time" NOTNULL) = ("max_admission_time" NOTNULL) AND
jbe@452 774 ("min_admission_time" NOTNULL OR ("accepted" NOTNULL AND "accepted" = "created")) ),
jbe@340 775 CONSTRAINT "valid_state" CHECK (
jbe@340 776 (
jbe@340 777 ("accepted" ISNULL AND "half_frozen" ISNULL AND "fully_frozen" ISNULL ) OR
jbe@340 778 ("accepted" NOTNULL AND "half_frozen" ISNULL AND "fully_frozen" ISNULL ) OR
jbe@340 779 ("accepted" NOTNULL AND "half_frozen" NOTNULL AND "fully_frozen" ISNULL ) OR
jbe@340 780 ("accepted" NOTNULL AND "half_frozen" NOTNULL AND "fully_frozen" NOTNULL)
jbe@340 781 ) AND (
jbe@340 782 ("state" = 'admission' AND "closed" ISNULL AND "accepted" ISNULL) OR
jbe@340 783 ("state" = 'discussion' AND "closed" ISNULL AND "accepted" NOTNULL AND "half_frozen" ISNULL) OR
jbe@340 784 ("state" = 'verification' AND "closed" ISNULL AND "half_frozen" NOTNULL AND "fully_frozen" ISNULL) OR
jbe@340 785 ("state" = 'voting' AND "closed" ISNULL AND "fully_frozen" NOTNULL) OR
jbe@389 786 ("state" = 'canceled_by_admin' AND "closed" NOTNULL) OR
jbe@340 787 ("state" = 'canceled_revoked_before_accepted' AND "closed" NOTNULL AND "accepted" ISNULL) OR
jbe@340 788 ("state" = 'canceled_issue_not_accepted' AND "closed" NOTNULL AND "accepted" ISNULL) OR
jbe@340 789 ("state" = 'canceled_after_revocation_during_discussion' AND "closed" NOTNULL AND "half_frozen" ISNULL) OR
jbe@340 790 ("state" = 'canceled_after_revocation_during_verification' AND "closed" NOTNULL AND "fully_frozen" ISNULL) OR
jbe@340 791 ("state" = 'canceled_no_initiative_admitted' AND "closed" NOTNULL AND "fully_frozen" NOTNULL AND "closed" = "fully_frozen") OR
jbe@340 792 ("state" = 'finished_without_winner' AND "closed" NOTNULL AND "fully_frozen" NOTNULL AND "closed" != "fully_frozen") OR
jbe@340 793 ("state" = 'finished_with_winner' AND "closed" NOTNULL AND "fully_frozen" NOTNULL AND "closed" != "fully_frozen")
jbe@111 794 )),
jbe@328 795 CONSTRAINT "phase_finished_only_when_not_closed" CHECK (
jbe@328 796 "phase_finished" ISNULL OR "closed" ISNULL ),
jbe@3 797 CONSTRAINT "state_change_order" CHECK (
jbe@10 798 "created" <= "accepted" AND
jbe@10 799 "accepted" <= "half_frozen" AND
jbe@10 800 "half_frozen" <= "fully_frozen" AND
jbe@3 801 "fully_frozen" <= "closed" ),
jbe@61 802 CONSTRAINT "only_closed_issues_may_be_cleaned" CHECK (
jbe@61 803 "cleaned" ISNULL OR "closed" NOTNULL ),
jbe@528 804 CONSTRAINT "snapshot_required" CHECK (
jbe@528 805 --("accepted" ISNULL OR "admission_snapshot_id" NOTNULL) AND
jbe@528 806 ("half_frozen" ISNULL OR "half_freeze_snapshot_id" NOTNULL) AND
jbe@528 807 ("fully_frozen" ISNULL OR "full_freeze_snapshot_id" NOTNULL) ) );
jbe@528 808 CREATE INDEX "issue_state_idx" ON "issue" ("state");
jbe@16 809 CREATE INDEX "issue_created_idx" ON "issue" ("created");
jbe@16 810 CREATE INDEX "issue_accepted_idx" ON "issue" ("accepted");
jbe@16 811 CREATE INDEX "issue_half_frozen_idx" ON "issue" ("half_frozen");
jbe@16 812 CREATE INDEX "issue_fully_frozen_idx" ON "issue" ("fully_frozen");
jbe@16 813 CREATE INDEX "issue_closed_idx" ON "issue" ("closed");
jbe@0 814 CREATE INDEX "issue_created_idx_open" ON "issue" ("created") WHERE "closed" ISNULL;
jbe@16 815 CREATE INDEX "issue_closed_idx_canceled" ON "issue" ("closed") WHERE "fully_frozen" ISNULL;
jbe@528 816 CREATE INDEX "issue_latest_snapshot_id" ON "issue" ("latest_snapshot_id");
jbe@528 817 CREATE INDEX "issue_admission_snapshot_id" ON "issue" ("admission_snapshot_id");
jbe@528 818 CREATE INDEX "issue_half_freeze_snapshot_id" ON "issue" ("half_freeze_snapshot_id");
jbe@528 819 CREATE INDEX "issue_full_freeze_snapshot_id" ON "issue" ("full_freeze_snapshot_id");
jbe@0 820
jbe@0 821 COMMENT ON TABLE "issue" IS 'Groups of initiatives';
jbe@0 822
jbe@389 823 COMMENT ON COLUMN "issue"."admin_notice" IS 'Public notice by admin to explain manual interventions, or to announce corrections';
jbe@444 824 COMMENT ON COLUMN "issue"."external_reference" IS 'Opaque data field to store an external reference';
jbe@328 825 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@532 826 COMMENT ON COLUMN "issue"."accepted" IS 'Point in time, when the issue was accepted for further discussion (see columns "issue_quorum_num" and "issue_quorum_den" of table "policy" and quorum columns of table "area")';
jbe@170 827 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 828 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 829 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 830 COMMENT ON COLUMN "issue"."cleaned" IS 'Point in time, when discussion data and votes had been deleted';
jbe@447 831 COMMENT ON COLUMN "issue"."min_admission_time" IS 'Copied from "policy" table at creation of issue';
jbe@447 832 COMMENT ON COLUMN "issue"."max_admission_time" IS 'Copied from "policy" table at creation of issue';
jbe@170 833 COMMENT ON COLUMN "issue"."discussion_time" IS 'Copied from "policy" table at creation of issue';
jbe@170 834 COMMENT ON COLUMN "issue"."verification_time" IS 'Copied from "policy" table at creation of issue';
jbe@170 835 COMMENT ON COLUMN "issue"."voting_time" IS 'Copied from "policy" table at creation of issue';
jbe@532 836 COMMENT ON COLUMN "issue"."calculated" IS 'Point in time, when most recent snapshot and "population" and *_count values were calculated (NOTE: value is equal to "snapshot"."calculated" of snapshot with "id"="issue"."latest_snapshot_id")';
jbe@528 837 COMMENT ON COLUMN "issue"."latest_snapshot_id" IS 'Snapshot id of most recent snapshot';
jbe@528 838 COMMENT ON COLUMN "issue"."admission_snapshot_id" IS 'Snapshot id when issue as accepted or canceled in admission phase';
jbe@528 839 COMMENT ON COLUMN "issue"."half_freeze_snapshot_id" IS 'Snapshot id at end of discussion phase';
jbe@528 840 COMMENT ON COLUMN "issue"."full_freeze_snapshot_id" IS 'Snapshot id at end of verification phase';
jbe@532 841 COMMENT ON COLUMN "issue"."population" IS 'Count of members in "snapshot_population" table with "snapshot_id" equal to "issue"."latest_snapshot_id"';
jbe@170 842 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 843 COMMENT ON COLUMN "issue"."status_quo_schulze_rank" IS 'Schulze rank of status quo, as calculated by "calculate_ranks" function';
jbe@0 844
jbe@0 845
jbe@532 846 ALTER TABLE "snapshot" ADD FOREIGN KEY ("issue_id") REFERENCES "issue" ("id") ON DELETE CASCADE ON UPDATE CASCADE;
jbe@532 847
jbe@532 848
jbe@410 849 CREATE TABLE "issue_order_in_admission_state" (
jbe@532 850 "id" INT8 PRIMARY KEY, -- NOTE: no referential integrity due to performans/locking issues; REFERENCES "issue" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@410 851 "order_in_area" INT4,
jbe@410 852 "order_in_unit" INT4 );
jbe@410 853
jbe@410 854 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 855
jbe@410 856 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 857 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 858 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 859
jbe@0 860
jbe@23 861 CREATE TABLE "issue_setting" (
jbe@23 862 PRIMARY KEY ("member_id", "key", "issue_id"),
jbe@23 863 "member_id" INT4 REFERENCES "member" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@23 864 "key" TEXT NOT NULL,
jbe@23 865 "issue_id" INT4 REFERENCES "issue" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@23 866 "value" TEXT NOT NULL );
jbe@23 867
jbe@23 868 COMMENT ON TABLE "issue_setting" IS 'Place for frontend to store issue specific settings of members as strings';
jbe@23 869
jbe@23 870
jbe@0 871 CREATE TABLE "initiative" (
jbe@0 872 UNIQUE ("issue_id", "id"), -- index needed for foreign-key on table "vote"
jbe@0 873 "issue_id" INT4 NOT NULL REFERENCES "issue" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@0 874 "id" SERIAL4 PRIMARY KEY,
jbe@0 875 "name" TEXT NOT NULL,
jbe@261 876 "polling" BOOLEAN NOT NULL DEFAULT FALSE,
jbe@0 877 "created" TIMESTAMPTZ NOT NULL DEFAULT now(),
jbe@0 878 "revoked" TIMESTAMPTZ,
jbe@112 879 "revoked_by_member_id" INT4 REFERENCES "member" ("id") ON DELETE RESTRICT ON UPDATE CASCADE,
jbe@532 880 "suggested_initiative_id" INT4 REFERENCES "initiative" ("id") ON DELETE SET NULL ON UPDATE CASCADE,
jbe@532 881 "location" JSONB,
jbe@444 882 "external_reference" TEXT,
jbe@0 883 "admitted" BOOLEAN,
jbe@0 884 "supporter_count" INT4,
jbe@0 885 "informed_supporter_count" INT4,
jbe@0 886 "satisfied_supporter_count" INT4,
jbe@0 887 "satisfied_informed_supporter_count" INT4,
jbe@313 888 "harmonic_weight" NUMERIC(12, 3),
jbe@352 889 "final_suggestion_order_calculated" BOOLEAN NOT NULL DEFAULT FALSE,
jbe@414 890 "first_preference_votes" INT4,
jbe@0 891 "positive_votes" INT4,
jbe@0 892 "negative_votes" INT4,
jbe@167 893 "direct_majority" BOOLEAN,
jbe@167 894 "indirect_majority" BOOLEAN,
jbe@170 895 "schulze_rank" INT4,
jbe@167 896 "better_than_status_quo" BOOLEAN,
jbe@167 897 "worse_than_status_quo" BOOLEAN,
jbe@429 898 "reverse_beat_path" BOOLEAN,
jbe@154 899 "multistage_majority" BOOLEAN,
jbe@154 900 "eligible" BOOLEAN,
jbe@126 901 "winner" BOOLEAN,
jbe@0 902 "rank" INT4,
jbe@7 903 "text_search_data" TSVECTOR,
jbe@528 904 "draft_text_search_data" TSVECTOR,
jbe@112 905 CONSTRAINT "all_or_none_of_revoked_and_revoked_by_member_id_must_be_null"
jbe@447 906 CHECK (("revoked" NOTNULL) = ("revoked_by_member_id" NOTNULL)),
jbe@14 907 CONSTRAINT "non_revoked_initiatives_cant_suggest_other"
jbe@14 908 CHECK ("revoked" NOTNULL OR "suggested_initiative_id" ISNULL),
jbe@0 909 CONSTRAINT "revoked_initiatives_cant_be_admitted"
jbe@0 910 CHECK ("revoked" ISNULL OR "admitted" ISNULL),
jbe@128 911 CONSTRAINT "non_admitted_initiatives_cant_contain_voting_results" CHECK (
jbe@128 912 ( "admitted" NOTNULL AND "admitted" = TRUE ) OR
jbe@414 913 ( "first_preference_votes" ISNULL AND
jbe@414 914 "positive_votes" ISNULL AND "negative_votes" ISNULL AND
jbe@167 915 "direct_majority" ISNULL AND "indirect_majority" ISNULL AND
jbe@173 916 "schulze_rank" ISNULL AND
jbe@167 917 "better_than_status_quo" ISNULL AND "worse_than_status_quo" ISNULL AND
jbe@429 918 "reverse_beat_path" ISNULL AND "multistage_majority" ISNULL AND
jbe@173 919 "eligible" ISNULL AND "winner" ISNULL AND "rank" ISNULL ) ),
jbe@173 920 CONSTRAINT "better_excludes_worse" CHECK (NOT ("better_than_status_quo" AND "worse_than_status_quo")),
jbe@175 921 CONSTRAINT "minimum_requirement_to_be_eligible" CHECK (
jbe@175 922 "eligible" = FALSE OR
jbe@175 923 ("direct_majority" AND "indirect_majority" AND "better_than_status_quo") ),
jbe@175 924 CONSTRAINT "winner_must_be_eligible" CHECK ("winner"=FALSE OR "eligible"=TRUE),
jbe@175 925 CONSTRAINT "winner_must_have_first_rank" CHECK ("winner"=FALSE OR "rank"=1),
jbe@176 926 CONSTRAINT "eligible_at_first_rank_is_winner" CHECK ("eligible"=FALSE OR "rank"!=1 OR "winner"=TRUE),
jbe@173 927 CONSTRAINT "unique_rank_per_issue" UNIQUE ("issue_id", "rank") );
jbe@16 928 CREATE INDEX "initiative_created_idx" ON "initiative" ("created");
jbe@16 929 CREATE INDEX "initiative_revoked_idx" ON "initiative" ("revoked");
jbe@532 930 CREATE INDEX "initiative_location_idx" ON "initiative" USING gist ((GeoJSON_to_ecluster("location")));
jbe@8 931 CREATE INDEX "initiative_text_search_data_idx" ON "initiative" USING gin ("text_search_data");
jbe@528 932 CREATE INDEX "initiative_draft_text_search_data_idx" ON "initiative" USING gin ("draft_text_search_data");
jbe@7 933 CREATE TRIGGER "update_text_search_data"
jbe@7 934 BEFORE INSERT OR UPDATE ON "initiative"
jbe@7 935 FOR EACH ROW EXECUTE PROCEDURE
jbe@450 936 tsvector_update_trigger('text_search_data', 'pg_catalog.simple', "name");
jbe@0 937
jbe@10 938 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 939
jbe@289 940 COMMENT ON COLUMN "initiative"."polling" IS 'Initiative does not need to pass the initiative quorum (see "policy"."polling")';
jbe@210 941 COMMENT ON COLUMN "initiative"."revoked" IS 'Point in time, when one initiator decided to revoke the initiative';
jbe@210 942 COMMENT ON COLUMN "initiative"."revoked_by_member_id" IS 'Member, who decided to revoke the initiative';
jbe@532 943 COMMENT ON COLUMN "initiative"."location" IS 'Geographic location of initiative as GeoJSON object (automatically copied from most recent draft)';
jbe@444 944 COMMENT ON COLUMN "initiative"."external_reference" IS 'Opaque data field to store an external reference';
jbe@210 945 COMMENT ON COLUMN "initiative"."admitted" IS 'TRUE, if initiative reaches the "initiative_quorum" when freezing the issue';
jbe@0 946 COMMENT ON COLUMN "initiative"."supporter_count" IS 'Calculated from table "direct_supporter_snapshot"';
jbe@0 947 COMMENT ON COLUMN "initiative"."informed_supporter_count" IS 'Calculated from table "direct_supporter_snapshot"';
jbe@0 948 COMMENT ON COLUMN "initiative"."satisfied_supporter_count" IS 'Calculated from table "direct_supporter_snapshot"';
jbe@0 949 COMMENT ON COLUMN "initiative"."satisfied_informed_supporter_count" IS 'Calculated from table "direct_supporter_snapshot"';
jbe@320 950 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 951 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 952 COMMENT ON COLUMN "initiative"."first_preference_votes" IS 'Number of direct and delegating voters who ranked this initiative as their first choice';
jbe@414 953 COMMENT ON COLUMN "initiative"."positive_votes" IS 'Number of direct and delegating voters who ranked this initiative better than the status quo';
jbe@414 954 COMMENT ON COLUMN "initiative"."negative_votes" IS 'Number of direct and delegating voters who ranked this initiative worse than the status quo';
jbe@210 955 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 956 COMMENT ON COLUMN "initiative"."indirect_majority" IS 'Same as "direct_majority", but also considering indirect beat paths';
jbe@411 957 COMMENT ON COLUMN "initiative"."schulze_rank" IS 'Schulze-Ranking';
jbe@411 958 COMMENT ON COLUMN "initiative"."better_than_status_quo" IS 'TRUE, if initiative has a schulze-ranking better than the status quo';
jbe@411 959 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 960 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 961 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 962 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 963 COMMENT ON COLUMN "initiative"."winner" IS 'Winner is the "eligible" initiative with best "schulze_rank"';
jbe@210 964 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 965
jbe@0 966
jbe@61 967 CREATE TABLE "battle" (
jbe@126 968 "issue_id" INT4 NOT NULL,
jbe@61 969 "winning_initiative_id" INT4,
jbe@61 970 FOREIGN KEY ("issue_id", "winning_initiative_id") REFERENCES "initiative" ("issue_id", "id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@61 971 "losing_initiative_id" INT4,
jbe@61 972 FOREIGN KEY ("issue_id", "losing_initiative_id") REFERENCES "initiative" ("issue_id", "id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@126 973 "count" INT4 NOT NULL,
jbe@126 974 CONSTRAINT "initiative_ids_not_equal" CHECK (
jbe@126 975 "winning_initiative_id" != "losing_initiative_id" OR
jbe@126 976 ( ("winning_initiative_id" NOTNULL AND "losing_initiative_id" ISNULL) OR
jbe@126 977 ("winning_initiative_id" ISNULL AND "losing_initiative_id" NOTNULL) ) ) );
jbe@126 978 CREATE UNIQUE INDEX "battle_winning_losing_idx" ON "battle" ("issue_id", "winning_initiative_id", "losing_initiative_id");
jbe@126 979 CREATE UNIQUE INDEX "battle_winning_null_idx" ON "battle" ("issue_id", "winning_initiative_id") WHERE "losing_initiative_id" ISNULL;
jbe@126 980 CREATE UNIQUE INDEX "battle_null_losing_idx" ON "battle" ("issue_id", "losing_initiative_id") WHERE "winning_initiative_id" ISNULL;
jbe@126 981
jbe@126 982 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 983
jbe@61 984
jbe@113 985 CREATE TABLE "ignored_initiative" (
jbe@465 986 PRIMARY KEY ("member_id", "initiative_id"),
jbe@465 987 "member_id" INT4 REFERENCES "member" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@465 988 "initiative_id" INT4 REFERENCES "initiative" ("id") ON DELETE CASCADE ON UPDATE CASCADE );
jbe@465 989 CREATE INDEX "ignored_initiative_initiative_id_idx" ON "ignored_initiative" ("initiative_id");
jbe@113 990
jbe@509 991 COMMENT ON TABLE "ignored_initiative" IS 'An entry in this table denotes that the member does not wish to receive notifications for the given initiative';
jbe@113 992
jbe@113 993
jbe@23 994 CREATE TABLE "initiative_setting" (
jbe@23 995 PRIMARY KEY ("member_id", "key", "initiative_id"),
jbe@23 996 "member_id" INT4 REFERENCES "member" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@23 997 "key" TEXT NOT NULL,
jbe@23 998 "initiative_id" INT4 REFERENCES "initiative" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@23 999 "value" TEXT NOT NULL );
jbe@23 1000
jbe@23 1001 COMMENT ON TABLE "initiative_setting" IS 'Place for frontend to store initiative specific settings of members as strings';
jbe@23 1002
jbe@23 1003
jbe@0 1004 CREATE TABLE "draft" (
jbe@0 1005 UNIQUE ("initiative_id", "id"), -- index needed for foreign-key on table "supporter"
jbe@0 1006 "initiative_id" INT4 NOT NULL REFERENCES "initiative" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@0 1007 "id" SERIAL8 PRIMARY KEY,
jbe@0 1008 "created" TIMESTAMPTZ NOT NULL DEFAULT now(),
jbe@0 1009 "author_id" INT4 NOT NULL REFERENCES "member" ("id") ON DELETE RESTRICT ON UPDATE CASCADE,
jbe@9 1010 "formatting_engine" TEXT,
jbe@7 1011 "content" TEXT NOT NULL,
jbe@532 1012 "location" JSONB,
jbe@444 1013 "external_reference" TEXT,
jbe@532 1014 "text_search_data" TSVECTOR );
jbe@16 1015 CREATE INDEX "draft_created_idx" ON "draft" ("created");
jbe@9 1016 CREATE INDEX "draft_author_id_created_idx" ON "draft" ("author_id", "created");
jbe@532 1017 CREATE INDEX "draft_location_idx" ON "draft" USING gist ((GeoJSON_to_ecluster("location")));
jbe@8 1018 CREATE INDEX "draft_text_search_data_idx" ON "draft" USING gin ("text_search_data");
jbe@7 1019 CREATE TRIGGER "update_text_search_data"
jbe@7 1020 BEFORE INSERT OR UPDATE ON "draft"
jbe@7 1021 FOR EACH ROW EXECUTE PROCEDURE
jbe@7 1022 tsvector_update_trigger('text_search_data', 'pg_catalog.simple', "content");
jbe@0 1023
jbe@10 1024 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 1025
jbe@444 1026 COMMENT ON COLUMN "draft"."formatting_engine" IS 'Allows different formatting engines (i.e. wiki formats) to be used';
jbe@444 1027 COMMENT ON COLUMN "draft"."content" IS 'Text of the draft in a format depending on the field "formatting_engine"';
jbe@532 1028 COMMENT ON COLUMN "draft"."location" IS 'Geographic location of initiative as GeoJSON object (automatically copied to "initiative" table if draft is most recent)';
jbe@444 1029 COMMENT ON COLUMN "draft"."external_reference" IS 'Opaque data field to store an external reference';
jbe@9 1030
jbe@0 1031
jbe@63 1032 CREATE TABLE "rendered_draft" (
jbe@63 1033 PRIMARY KEY ("draft_id", "format"),
jbe@63 1034 "draft_id" INT8 REFERENCES "draft" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@63 1035 "format" TEXT,
jbe@63 1036 "content" TEXT NOT NULL );
jbe@63 1037
jbe@63 1038 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 1039
jbe@63 1040
jbe@0 1041 CREATE TABLE "suggestion" (
jbe@0 1042 UNIQUE ("initiative_id", "id"), -- index needed for foreign-key on table "opinion"
jbe@0 1043 "initiative_id" INT4 NOT NULL REFERENCES "initiative" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@0 1044 "id" SERIAL8 PRIMARY KEY,
jbe@160 1045 "draft_id" INT8 NOT NULL,
jbe@160 1046 FOREIGN KEY ("initiative_id", "draft_id") REFERENCES "draft" ("initiative_id", "id") ON DELETE NO ACTION ON UPDATE CASCADE,
jbe@0 1047 "created" TIMESTAMPTZ NOT NULL DEFAULT now(),
jbe@0 1048 "author_id" INT4 NOT NULL REFERENCES "member" ("id") ON DELETE RESTRICT ON UPDATE CASCADE,
jbe@0 1049 "name" TEXT NOT NULL,
jbe@159 1050 "formatting_engine" TEXT,
jbe@159 1051 "content" TEXT NOT NULL DEFAULT '',
jbe@532 1052 "location" JSONB,
jbe@444 1053 "external_reference" TEXT,
jbe@7 1054 "text_search_data" TSVECTOR,
jbe@0 1055 "minus2_unfulfilled_count" INT4,
jbe@0 1056 "minus2_fulfilled_count" INT4,
jbe@0 1057 "minus1_unfulfilled_count" INT4,
jbe@0 1058 "minus1_fulfilled_count" INT4,
jbe@0 1059 "plus1_unfulfilled_count" INT4,
jbe@0 1060 "plus1_fulfilled_count" INT4,
jbe@0 1061 "plus2_unfulfilled_count" INT4,
jbe@352 1062 "plus2_fulfilled_count" INT4,
jbe@532 1063 "proportional_order" INT4 );
jbe@16 1064 CREATE INDEX "suggestion_created_idx" ON "suggestion" ("created");
jbe@9 1065 CREATE INDEX "suggestion_author_id_created_idx" ON "suggestion" ("author_id", "created");
jbe@532 1066 CREATE INDEX "suggestion_location_idx" ON "suggestion" USING gist ((GeoJSON_to_ecluster("location")));
jbe@8 1067 CREATE INDEX "suggestion_text_search_data_idx" ON "suggestion" USING gin ("text_search_data");
jbe@7 1068 CREATE TRIGGER "update_text_search_data"
jbe@7 1069 BEFORE INSERT OR UPDATE ON "suggestion"
jbe@7 1070 FOR EACH ROW EXECUTE PROCEDURE
jbe@7 1071 tsvector_update_trigger('text_search_data', 'pg_catalog.simple',
jbe@159 1072 "name", "content");
jbe@0 1073
jbe@10 1074 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 1075
jbe@160 1076 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@532 1077 COMMENT ON COLUMN "suggestion"."location" IS 'Geographic location of suggestion as GeoJSON object';
jbe@444 1078 COMMENT ON COLUMN "suggestion"."external_reference" IS 'Opaque data field to store an external reference';
jbe@0 1079 COMMENT ON COLUMN "suggestion"."minus2_unfulfilled_count" IS 'Calculated from table "direct_supporter_snapshot", not requiring informed supporters';
jbe@0 1080 COMMENT ON COLUMN "suggestion"."minus2_fulfilled_count" IS 'Calculated from table "direct_supporter_snapshot", not requiring informed supporters';
jbe@0 1081 COMMENT ON COLUMN "suggestion"."minus1_unfulfilled_count" IS 'Calculated from table "direct_supporter_snapshot", not requiring informed supporters';
jbe@0 1082 COMMENT ON COLUMN "suggestion"."minus1_fulfilled_count" IS 'Calculated from table "direct_supporter_snapshot", not requiring informed supporters';
jbe@0 1083 COMMENT ON COLUMN "suggestion"."plus1_unfulfilled_count" IS 'Calculated from table "direct_supporter_snapshot", not requiring informed supporters';
jbe@0 1084 COMMENT ON COLUMN "suggestion"."plus1_fulfilled_count" IS 'Calculated from table "direct_supporter_snapshot", not requiring informed supporters';
jbe@0 1085 COMMENT ON COLUMN "suggestion"."plus2_unfulfilled_count" IS 'Calculated from table "direct_supporter_snapshot", not requiring informed supporters';
jbe@0 1086 COMMENT ON COLUMN "suggestion"."plus2_fulfilled_count" IS 'Calculated from table "direct_supporter_snapshot", not requiring informed supporters';
jbe@378 1087 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 1088
jbe@0 1089
jbe@159 1090 CREATE TABLE "rendered_suggestion" (
jbe@159 1091 PRIMARY KEY ("suggestion_id", "format"),
jbe@159 1092 "suggestion_id" INT8 REFERENCES "suggestion" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@159 1093 "format" TEXT,
jbe@159 1094 "content" TEXT NOT NULL );
jbe@159 1095
jbe@159 1096 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 1097
jbe@159 1098
jbe@23 1099 CREATE TABLE "suggestion_setting" (
jbe@23 1100 PRIMARY KEY ("member_id", "key", "suggestion_id"),
jbe@23 1101 "member_id" INT4 REFERENCES "member" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@23 1102 "key" TEXT NOT NULL,
jbe@23 1103 "suggestion_id" INT8 REFERENCES "suggestion" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@23 1104 "value" TEXT NOT NULL );
jbe@23 1105
jbe@23 1106 COMMENT ON TABLE "suggestion_setting" IS 'Place for frontend to store suggestion specific settings of members as strings';
jbe@23 1107
jbe@23 1108
jbe@528 1109 CREATE TABLE "temporary_suggestion_counts" (
jbe@532 1110 "id" INT8 PRIMARY KEY, -- NOTE: no referential integrity due to performance/locking issues; REFERENCES "suggestion" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@528 1111 "minus2_unfulfilled_count" INT4 NOT NULL,
jbe@528 1112 "minus2_fulfilled_count" INT4 NOT NULL,
jbe@528 1113 "minus1_unfulfilled_count" INT4 NOT NULL,
jbe@528 1114 "minus1_fulfilled_count" INT4 NOT NULL,
jbe@528 1115 "plus1_unfulfilled_count" INT4 NOT NULL,
jbe@528 1116 "plus1_fulfilled_count" INT4 NOT NULL,
jbe@528 1117 "plus2_unfulfilled_count" INT4 NOT NULL,
jbe@528 1118 "plus2_fulfilled_count" INT4 NOT NULL );
jbe@528 1119
jbe@528 1120 COMMENT ON TABLE "temporary_suggestion_counts" IS 'Holds certain calculated values (suggestion counts) temporarily until they can be copied into table "suggestion"';
jbe@528 1121
jbe@528 1122 COMMENT ON COLUMN "temporary_suggestion_counts"."id" IS 'References "suggestion" ("id") but has no referential integrity trigger associated, due to performance/locking issues';
jbe@528 1123
jbe@528 1124
jbe@97 1125 CREATE TABLE "privilege" (
jbe@97 1126 PRIMARY KEY ("unit_id", "member_id"),
jbe@97 1127 "unit_id" INT4 REFERENCES "unit" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@97 1128 "member_id" INT4 REFERENCES "member" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@97 1129 "admin_manager" BOOLEAN NOT NULL DEFAULT FALSE,
jbe@97 1130 "unit_manager" BOOLEAN NOT NULL DEFAULT FALSE,
jbe@97 1131 "area_manager" BOOLEAN NOT NULL DEFAULT FALSE,
jbe@261 1132 "member_manager" BOOLEAN NOT NULL DEFAULT FALSE,
jbe@261 1133 "initiative_right" BOOLEAN NOT NULL DEFAULT TRUE,
jbe@261 1134 "voting_right" BOOLEAN NOT NULL DEFAULT TRUE,
jbe@261 1135 "polling_right" BOOLEAN NOT NULL DEFAULT FALSE );
jbe@97 1136
jbe@97 1137 COMMENT ON TABLE "privilege" IS 'Members rights related to each unit';
jbe@97 1138
jbe@289 1139 COMMENT ON COLUMN "privilege"."admin_manager" IS 'Grant/revoke any privileges to/from other members';
jbe@289 1140 COMMENT ON COLUMN "privilege"."unit_manager" IS 'Create and disable sub units';
jbe@289 1141 COMMENT ON COLUMN "privilege"."area_manager" IS 'Create and disable areas and set area parameters';
jbe@289 1142 COMMENT ON COLUMN "privilege"."member_manager" IS 'Adding/removing members from the unit, granting or revoking "initiative_right" and "voting_right"';
jbe@289 1143 COMMENT ON COLUMN "privilege"."initiative_right" IS 'Right to create an initiative';
jbe@289 1144 COMMENT ON COLUMN "privilege"."voting_right" IS 'Right to support initiatives, create and rate suggestions, and to vote';
jbe@289 1145 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 1146
jbe@97 1147
jbe@0 1148 CREATE TABLE "interest" (
jbe@0 1149 PRIMARY KEY ("issue_id", "member_id"),
jbe@0 1150 "issue_id" INT4 REFERENCES "issue" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@532 1151 "member_id" INT4 REFERENCES "member" ("id") ON DELETE RESTRICT ON UPDATE CASCADE );
jbe@0 1152 CREATE INDEX "interest_member_id_idx" ON "interest" ("member_id");
jbe@0 1153
jbe@10 1154 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 1155
jbe@0 1156
jbe@0 1157 CREATE TABLE "initiator" (
jbe@0 1158 PRIMARY KEY ("initiative_id", "member_id"),
jbe@0 1159 "initiative_id" INT4 REFERENCES "initiative" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@532 1160 "member_id" INT4 REFERENCES "member" ("id") ON DELETE RESTRICT ON UPDATE CASCADE,
jbe@14 1161 "accepted" BOOLEAN );
jbe@0 1162 CREATE INDEX "initiator_member_id_idx" ON "initiator" ("member_id");
jbe@0 1163
jbe@10 1164 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 1165
jbe@14 1166 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 1167
jbe@0 1168
jbe@0 1169 CREATE TABLE "supporter" (
jbe@0 1170 "issue_id" INT4 NOT NULL,
jbe@0 1171 PRIMARY KEY ("initiative_id", "member_id"),
jbe@0 1172 "initiative_id" INT4,
jbe@0 1173 "member_id" INT4,
jbe@0 1174 "draft_id" INT8 NOT NULL,
jbe@10 1175 FOREIGN KEY ("issue_id", "member_id") REFERENCES "interest" ("issue_id", "member_id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@160 1176 FOREIGN KEY ("initiative_id", "draft_id") REFERENCES "draft" ("initiative_id", "id") ON DELETE NO ACTION ON UPDATE CASCADE );
jbe@0 1177 CREATE INDEX "supporter_member_id_idx" ON "supporter" ("member_id");
jbe@0 1178
jbe@10 1179 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 1180
jbe@207 1181 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 1182 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 1183
jbe@0 1184
jbe@0 1185 CREATE TABLE "opinion" (
jbe@0 1186 "initiative_id" INT4 NOT NULL,
jbe@0 1187 PRIMARY KEY ("suggestion_id", "member_id"),
jbe@0 1188 "suggestion_id" INT8,
jbe@0 1189 "member_id" INT4,
jbe@0 1190 "degree" INT2 NOT NULL CHECK ("degree" >= -2 AND "degree" <= 2 AND "degree" != 0),
jbe@0 1191 "fulfilled" BOOLEAN NOT NULL DEFAULT FALSE,
jbe@42 1192 FOREIGN KEY ("initiative_id", "suggestion_id") REFERENCES "suggestion" ("initiative_id", "id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@0 1193 FOREIGN KEY ("initiative_id", "member_id") REFERENCES "supporter" ("initiative_id", "member_id") ON DELETE CASCADE ON UPDATE CASCADE );
jbe@10 1194 CREATE INDEX "opinion_member_id_initiative_id_idx" ON "opinion" ("member_id", "initiative_id");
jbe@0 1195
jbe@10 1196 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 1197
jbe@0 1198 COMMENT ON COLUMN "opinion"."degree" IS '2 = fulfillment required for support; 1 = fulfillment desired; -1 = fulfillment unwanted; -2 = fulfillment cancels support';
jbe@0 1199
jbe@0 1200
jbe@97 1201 CREATE TYPE "delegation_scope" AS ENUM ('unit', 'area', 'issue');
jbe@97 1202
jbe@97 1203 COMMENT ON TYPE "delegation_scope" IS 'Scope for delegations: ''unit'', ''area'', or ''issue'' (order is relevant)';
jbe@10 1204
jbe@10 1205
jbe@0 1206 CREATE TABLE "delegation" (
jbe@0 1207 "id" SERIAL8 PRIMARY KEY,
jbe@0 1208 "truster_id" INT4 NOT NULL REFERENCES "member" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@532 1209 "trustee_id" INT4 REFERENCES "member" ("id") ON DELETE RESTRICT ON UPDATE CASCADE,
jbe@10 1210 "scope" "delegation_scope" NOT NULL,
jbe@97 1211 "unit_id" INT4 REFERENCES "unit" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@0 1212 "area_id" INT4 REFERENCES "area" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@0 1213 "issue_id" INT4 REFERENCES "issue" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@0 1214 CONSTRAINT "cant_delegate_to_yourself" CHECK ("truster_id" != "trustee_id"),
jbe@97 1215 CONSTRAINT "no_unit_delegation_to_null"
jbe@97 1216 CHECK ("trustee_id" NOTNULL OR "scope" != 'unit'),
jbe@10 1217 CONSTRAINT "area_id_and_issue_id_set_according_to_scope" CHECK (
jbe@97 1218 ("scope" = 'unit' AND "unit_id" NOTNULL AND "area_id" ISNULL AND "issue_id" ISNULL ) OR
jbe@97 1219 ("scope" = 'area' AND "unit_id" ISNULL AND "area_id" NOTNULL AND "issue_id" ISNULL ) OR
jbe@97 1220 ("scope" = 'issue' AND "unit_id" ISNULL AND "area_id" ISNULL AND "issue_id" NOTNULL) ),
jbe@97 1221 UNIQUE ("unit_id", "truster_id"),
jbe@74 1222 UNIQUE ("area_id", "truster_id"),
jbe@74 1223 UNIQUE ("issue_id", "truster_id") );
jbe@0 1224 CREATE INDEX "delegation_truster_id_idx" ON "delegation" ("truster_id");
jbe@0 1225 CREATE INDEX "delegation_trustee_id_idx" ON "delegation" ("trustee_id");
jbe@0 1226
jbe@0 1227 COMMENT ON TABLE "delegation" IS 'Delegation of vote-weight to other members';
jbe@0 1228
jbe@97 1229 COMMENT ON COLUMN "delegation"."unit_id" IS 'Reference to unit, if delegation is unit-wide, otherwise NULL';
jbe@0 1230 COMMENT ON COLUMN "delegation"."area_id" IS 'Reference to area, if delegation is area-wide, otherwise NULL';
jbe@0 1231 COMMENT ON COLUMN "delegation"."issue_id" IS 'Reference to issue, if delegation is issue-wide, otherwise NULL';
jbe@0 1232
jbe@0 1233
jbe@528 1234 CREATE TABLE "snapshot_issue" (
jbe@528 1235 PRIMARY KEY ("snapshot_id", "issue_id"),
jbe@528 1236 "snapshot_id" INT8 REFERENCES "snapshot" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@532 1237 "issue_id" INT4 REFERENCES "issue" ("id") ON DELETE CASCADE ON UPDATE CASCADE ); -- NOTE: trigger "delete_snapshot_on_partial_delete" will delete whole "snapshot"
jbe@528 1238 CREATE INDEX "snapshot_issue_issue_id_idx" ON "snapshot_issue" ("issue_id");
jbe@528 1239
jbe@528 1240 COMMENT ON TABLE "snapshot_issue" IS 'List of issues included in a snapshot';
jbe@0 1241
jbe@532 1242 COMMENT ON COLUMN "snapshot_issue"."issue_id" IS 'Issue being part of the snapshot; Trigger "delete_snapshot_on_partial_delete" on "snapshot_issue" table will delete snapshot if an issue of the snapshot is deleted.';
jbe@532 1243
jbe@0 1244
jbe@0 1245 CREATE TABLE "direct_interest_snapshot" (
jbe@528 1246 PRIMARY KEY ("snapshot_id", "issue_id", "member_id"),
jbe@528 1247 "snapshot_id" INT8,
jbe@528 1248 "issue_id" INT4,
jbe@528 1249 FOREIGN KEY ("snapshot_id", "issue_id")
jbe@528 1250 REFERENCES "snapshot_issue" ("snapshot_id", "issue_id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@45 1251 "member_id" INT4 REFERENCES "member" ("id") ON DELETE RESTRICT ON UPDATE RESTRICT,
jbe@144 1252 "weight" INT4 );
jbe@0 1253 CREATE INDEX "direct_interest_snapshot_member_id_idx" ON "direct_interest_snapshot" ("member_id");
jbe@0 1254
jbe@389 1255 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 1256
jbe@528 1257 COMMENT ON COLUMN "direct_interest_snapshot"."weight" IS 'Weight of member (1 or higher) according to "delegating_interest_snapshot"';
jbe@0 1258
jbe@0 1259
jbe@0 1260 CREATE TABLE "delegating_interest_snapshot" (
jbe@528 1261 PRIMARY KEY ("snapshot_id", "issue_id", "member_id"),
jbe@528 1262 "snapshot_id" INT8,
jbe@528 1263 "issue_id" INT4,
jbe@528 1264 FOREIGN KEY ("snapshot_id", "issue_id")
jbe@528 1265 REFERENCES "snapshot_issue" ("snapshot_id", "issue_id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@45 1266 "member_id" INT4 REFERENCES "member" ("id") ON DELETE RESTRICT ON UPDATE RESTRICT,
jbe@8 1267 "weight" INT4,
jbe@10 1268 "scope" "delegation_scope" NOT NULL,
jbe@0 1269 "delegate_member_ids" INT4[] NOT NULL );
jbe@0 1270 CREATE INDEX "delegating_interest_snapshot_member_id_idx" ON "delegating_interest_snapshot" ("member_id");
jbe@0 1271
jbe@389 1272 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 1273
jbe@0 1274 COMMENT ON COLUMN "delegating_interest_snapshot"."member_id" IS 'Delegating member';
jbe@8 1275 COMMENT ON COLUMN "delegating_interest_snapshot"."weight" IS 'Intermediate weight';
jbe@0 1276 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 1277
jbe@0 1278
jbe@0 1279 CREATE TABLE "direct_supporter_snapshot" (
jbe@528 1280 PRIMARY KEY ("snapshot_id", "initiative_id", "member_id"),
jbe@528 1281 "snapshot_id" INT8,
jbe@0 1282 "issue_id" INT4 NOT NULL,
jbe@528 1283 FOREIGN KEY ("snapshot_id", "issue_id")
jbe@528 1284 REFERENCES "snapshot_issue" ("snapshot_id", "issue_id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@0 1285 "initiative_id" INT4,
jbe@45 1286 "member_id" INT4 REFERENCES "member" ("id") ON DELETE RESTRICT ON UPDATE RESTRICT,
jbe@204 1287 "draft_id" INT8 NOT NULL,
jbe@0 1288 "informed" BOOLEAN NOT NULL,
jbe@0 1289 "satisfied" BOOLEAN NOT NULL,
jbe@0 1290 FOREIGN KEY ("issue_id", "initiative_id") REFERENCES "initiative" ("issue_id", "id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@204 1291 FOREIGN KEY ("initiative_id", "draft_id") REFERENCES "draft" ("initiative_id", "id") ON DELETE NO ACTION ON UPDATE CASCADE,
jbe@528 1292 FOREIGN KEY ("snapshot_id", "issue_id", "member_id") REFERENCES "direct_interest_snapshot" ("snapshot_id", "issue_id", "member_id") ON DELETE CASCADE ON UPDATE CASCADE );
jbe@0 1293 CREATE INDEX "direct_supporter_snapshot_member_id_idx" ON "direct_supporter_snapshot" ("member_id");
jbe@0 1294
jbe@389 1295 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 1296
jbe@207 1297 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 1298 COMMENT ON COLUMN "direct_supporter_snapshot"."informed" IS 'Supporter has seen the latest draft of the initiative';
jbe@0 1299 COMMENT ON COLUMN "direct_supporter_snapshot"."satisfied" IS 'Supporter has no "critical_opinion"s';
jbe@0 1300
jbe@0 1301
jbe@113 1302 CREATE TABLE "non_voter" (
jbe@528 1303 PRIMARY KEY ("member_id", "issue_id"),
jbe@528 1304 "member_id" INT4 REFERENCES "member" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@528 1305 "issue_id" INT4 REFERENCES "issue" ("id") ON DELETE CASCADE ON UPDATE CASCADE );
jbe@528 1306 CREATE INDEX "non_voter_issue_id_idx" ON "non_voter" ("issue_id");
jbe@113 1307
jbe@113 1308 COMMENT ON TABLE "non_voter" IS 'Members who decided to not vote directly on an issue';
jbe@113 1309
jbe@113 1310
jbe@0 1311 CREATE TABLE "direct_voter" (
jbe@0 1312 PRIMARY KEY ("issue_id", "member_id"),
jbe@0 1313 "issue_id" INT4 REFERENCES "issue" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@45 1314 "member_id" INT4 REFERENCES "member" ("id") ON DELETE RESTRICT ON UPDATE RESTRICT,
jbe@285 1315 "weight" INT4,
jbe@285 1316 "comment_changed" TIMESTAMPTZ,
jbe@285 1317 "formatting_engine" TEXT,
jbe@285 1318 "comment" TEXT,
jbe@285 1319 "text_search_data" TSVECTOR );
jbe@0 1320 CREATE INDEX "direct_voter_member_id_idx" ON "direct_voter" ("member_id");
jbe@285 1321 CREATE INDEX "direct_voter_text_search_data_idx" ON "direct_voter" USING gin ("text_search_data");
jbe@285 1322 CREATE TRIGGER "update_text_search_data"
jbe@285 1323 BEFORE INSERT OR UPDATE ON "direct_voter"
jbe@285 1324 FOR EACH ROW EXECUTE PROCEDURE
jbe@285 1325 tsvector_update_trigger('text_search_data', 'pg_catalog.simple', "comment");
jbe@0 1326
jbe@389 1327 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 1328
jbe@285 1329 COMMENT ON COLUMN "direct_voter"."weight" IS 'Weight of member (1 or higher) according to "delegating_voter" table';
jbe@285 1330 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 1331 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 1332 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 1333
jbe@285 1334
jbe@285 1335 CREATE TABLE "rendered_voter_comment" (
jbe@285 1336 PRIMARY KEY ("issue_id", "member_id", "format"),
jbe@285 1337 FOREIGN KEY ("issue_id", "member_id")
jbe@285 1338 REFERENCES "direct_voter" ("issue_id", "member_id")
jbe@285 1339 ON DELETE CASCADE ON UPDATE CASCADE,
jbe@285 1340 "issue_id" INT4,
jbe@285 1341 "member_id" INT4,
jbe@285 1342 "format" TEXT,
jbe@285 1343 "content" TEXT NOT NULL );
jbe@285 1344
jbe@285 1345 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 1346
jbe@0 1347
jbe@0 1348 CREATE TABLE "delegating_voter" (
jbe@0 1349 PRIMARY KEY ("issue_id", "member_id"),
jbe@0 1350 "issue_id" INT4 REFERENCES "issue" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@45 1351 "member_id" INT4 REFERENCES "member" ("id") ON DELETE RESTRICT ON UPDATE RESTRICT,
jbe@8 1352 "weight" INT4,
jbe@10 1353 "scope" "delegation_scope" NOT NULL,
jbe@0 1354 "delegate_member_ids" INT4[] NOT NULL );
jbe@52 1355 CREATE INDEX "delegating_voter_member_id_idx" ON "delegating_voter" ("member_id");
jbe@0 1356
jbe@389 1357 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 1358
jbe@0 1359 COMMENT ON COLUMN "delegating_voter"."member_id" IS 'Delegating member';
jbe@8 1360 COMMENT ON COLUMN "delegating_voter"."weight" IS 'Intermediate weight';
jbe@0 1361 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 1362
jbe@0 1363
jbe@0 1364 CREATE TABLE "vote" (
jbe@0 1365 "issue_id" INT4 NOT NULL,
jbe@0 1366 PRIMARY KEY ("initiative_id", "member_id"),
jbe@0 1367 "initiative_id" INT4,
jbe@0 1368 "member_id" INT4,
jbe@414 1369 "grade" INT4 NOT NULL,
jbe@414 1370 "first_preference" BOOLEAN,
jbe@0 1371 FOREIGN KEY ("issue_id", "initiative_id") REFERENCES "initiative" ("issue_id", "id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@414 1372 FOREIGN KEY ("issue_id", "member_id") REFERENCES "direct_voter" ("issue_id", "member_id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@414 1373 CONSTRAINT "first_preference_flag_only_set_on_positive_grades"
jbe@414 1374 CHECK ("grade" > 0 OR "first_preference" ISNULL) );
jbe@0 1375 CREATE INDEX "vote_member_id_idx" ON "vote" ("member_id");
jbe@0 1376
jbe@389 1377 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 1378
jbe@414 1379 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 1380 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 1381 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 1382
jbe@0 1383
jbe@112 1384 CREATE TYPE "event_type" AS ENUM (
jbe@536 1385 'unit_created',
jbe@536 1386 'unit_updated',
jbe@536 1387 'unit_removed',
jbe@536 1388 'subject_area_created',
jbe@536 1389 'subject_area_updated',
jbe@536 1390 'subject_area_removed',
jbe@536 1391 'policy_created',
jbe@536 1392 'policy_updated',
jbe@536 1393 'policy_removed',
jbe@112 1394 'issue_state_changed',
jbe@112 1395 'initiative_created_in_new_issue',
jbe@112 1396 'initiative_created_in_existing_issue',
jbe@112 1397 'initiative_revoked',
jbe@112 1398 'new_draft_created',
jbe@532 1399 'suggestion_created',
jbe@532 1400 'suggestion_removed',
jbe@532 1401 'member_activated',
jbe@532 1402 'member_removed',
jbe@532 1403 'member_active',
jbe@532 1404 'member_name_updated',
jbe@532 1405 'member_profile_updated',
jbe@532 1406 'member_image_updated',
jbe@532 1407 'interest',
jbe@532 1408 'initiator',
jbe@532 1409 'support',
jbe@532 1410 'support_updated',
jbe@532 1411 'suggestion_rated',
jbe@532 1412 'delegation',
jbe@532 1413 'contact' );
jbe@112 1414
jbe@112 1415 COMMENT ON TYPE "event_type" IS 'Type used for column "event" of table "event"';
jbe@112 1416
jbe@112 1417
jbe@112 1418 CREATE TABLE "event" (
jbe@112 1419 "id" SERIAL8 PRIMARY KEY,
jbe@112 1420 "occurrence" TIMESTAMPTZ NOT NULL DEFAULT now(),
jbe@112 1421 "event" "event_type" NOT NULL,
jbe@112 1422 "member_id" INT4 REFERENCES "member" ("id") ON DELETE RESTRICT ON UPDATE CASCADE,
jbe@532 1423 "other_member_id" INT4 REFERENCES "member" ("id") ON DELETE RESTRICT ON UPDATE CASCADE,
jbe@532 1424 "scope" "delegation_scope",
jbe@532 1425 "unit_id" INT4 REFERENCES "unit" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@532 1426 "area_id" INT4,
jbe@532 1427 FOREIGN KEY ("unit_id", "area_id") REFERENCES "area" ("unit_id", "id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@536 1428 "policy_id" INT4 REFERENCES "policy" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@112 1429 "issue_id" INT4 REFERENCES "issue" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@532 1430 FOREIGN KEY ("area_id", "issue_id") REFERENCES "issue" ("area_id", "id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@536 1431 FOREIGN KEY ("policy_id", "issue_id") REFERENCES "issue" ("policy_id", "id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@328 1432 "state" "issue_state",
jbe@112 1433 "initiative_id" INT4,
jbe@112 1434 "draft_id" INT8,
jbe@112 1435 "suggestion_id" INT8,
jbe@532 1436 "boolean_value" BOOLEAN,
jbe@532 1437 "numeric_value" INT4,
jbe@532 1438 "text_value" TEXT,
jbe@532 1439 "old_text_value" TEXT,
jbe@112 1440 FOREIGN KEY ("issue_id", "initiative_id")
jbe@112 1441 REFERENCES "initiative" ("issue_id", "id")
jbe@112 1442 ON DELETE CASCADE ON UPDATE CASCADE,
jbe@112 1443 FOREIGN KEY ("initiative_id", "draft_id")
jbe@112 1444 REFERENCES "draft" ("initiative_id", "id")
jbe@112 1445 ON DELETE CASCADE ON UPDATE CASCADE,
jbe@532 1446 -- NOTE: no referential integrity for suggestions because those are
jbe@532 1447 -- actually deleted
jbe@532 1448 -- FOREIGN KEY ("initiative_id", "suggestion_id")
jbe@532 1449 -- REFERENCES "suggestion" ("initiative_id", "id")
jbe@532 1450 -- ON DELETE CASCADE ON UPDATE CASCADE,
jbe@532 1451 CONSTRAINT "constr_for_issue_state_changed" CHECK (
jbe@112 1452 "event" != 'issue_state_changed' OR (
jbe@532 1453 "member_id" ISNULL AND
jbe@532 1454 "other_member_id" ISNULL AND
jbe@532 1455 "scope" ISNULL AND
jbe@532 1456 "unit_id" NOTNULL AND
jbe@532 1457 "area_id" NOTNULL AND
jbe@536 1458 "policy_id" NOTNULL AND
jbe@532 1459 "issue_id" NOTNULL AND
jbe@532 1460 "state" NOTNULL AND
jbe@532 1461 "initiative_id" ISNULL AND
jbe@532 1462 "draft_id" ISNULL AND
jbe@532 1463 "suggestion_id" ISNULL AND
jbe@532 1464 "boolean_value" ISNULL AND
jbe@532 1465 "numeric_value" ISNULL AND
jbe@532 1466 "text_value" ISNULL AND
jbe@532 1467 "old_text_value" ISNULL )),
jbe@532 1468 CONSTRAINT "constr_for_initiative_creation_or_revocation_or_new_draft" CHECK (
jbe@112 1469 "event" NOT IN (
jbe@112 1470 'initiative_created_in_new_issue',
jbe@112 1471 'initiative_created_in_existing_issue',
jbe@112 1472 'initiative_revoked',
jbe@112 1473 'new_draft_created'
jbe@112 1474 ) OR (
jbe@532 1475 "member_id" NOTNULL AND
jbe@532 1476 "other_member_id" ISNULL AND
jbe@532 1477 "scope" ISNULL AND
jbe@532 1478 "unit_id" NOTNULL AND
jbe@532 1479 "area_id" NOTNULL AND
jbe@536 1480 "policy_id" NOTNULL AND
jbe@532 1481 "issue_id" NOTNULL AND
jbe@532 1482 "state" NOTNULL AND
jbe@532 1483 "initiative_id" NOTNULL AND
jbe@532 1484 "draft_id" NOTNULL AND
jbe@532 1485 "suggestion_id" ISNULL AND
jbe@532 1486 "boolean_value" ISNULL AND
jbe@532 1487 "numeric_value" ISNULL AND
jbe@532 1488 "text_value" ISNULL AND
jbe@532 1489 "old_text_value" ISNULL )),
jbe@532 1490 CONSTRAINT "constr_for_suggestion_creation" CHECK (
jbe@112 1491 "event" != 'suggestion_created' OR (
jbe@532 1492 "member_id" NOTNULL AND
jbe@532 1493 "other_member_id" ISNULL AND
jbe@532 1494 "scope" ISNULL AND
jbe@532 1495 "unit_id" NOTNULL AND
jbe@532 1496 "area_id" NOTNULL AND
jbe@536 1497 "policy_id" NOTNULL AND
jbe@532 1498 "issue_id" NOTNULL AND
jbe@532 1499 "state" NOTNULL AND
jbe@532 1500 "initiative_id" NOTNULL AND
jbe@532 1501 "draft_id" ISNULL AND
jbe@532 1502 "suggestion_id" NOTNULL AND
jbe@532 1503 "boolean_value" ISNULL AND
jbe@532 1504 "numeric_value" ISNULL AND
jbe@532 1505 "text_value" ISNULL AND
jbe@532 1506 "old_text_value" ISNULL )),
jbe@532 1507 CONSTRAINT "constr_for_suggestion_removal" CHECK (
jbe@532 1508 "event" != 'suggestion_removed' OR (
jbe@532 1509 "member_id" ISNULL AND
jbe@532 1510 "other_member_id" ISNULL AND
jbe@532 1511 "scope" ISNULL AND
jbe@532 1512 "unit_id" NOTNULL AND
jbe@532 1513 "area_id" NOTNULL AND
jbe@536 1514 "policy_id" NOTNULL AND
jbe@532 1515 "issue_id" NOTNULL AND
jbe@532 1516 "state" NOTNULL AND
jbe@532 1517 "initiative_id" NOTNULL AND
jbe@532 1518 "draft_id" ISNULL AND
jbe@532 1519 "suggestion_id" NOTNULL AND
jbe@532 1520 "boolean_value" ISNULL AND
jbe@532 1521 "numeric_value" ISNULL AND
jbe@532 1522 "text_value" ISNULL AND
jbe@532 1523 "old_text_value" ISNULL )),
jbe@532 1524 CONSTRAINT "constr_for_value_less_member_event" CHECK (
jbe@532 1525 "event" NOT IN (
jbe@532 1526 'member_activated',
jbe@532 1527 'member_removed',
jbe@532 1528 'member_profile_updated',
jbe@532 1529 'member_image_updated'
jbe@532 1530 ) OR (
jbe@532 1531 "member_id" NOTNULL AND
jbe@532 1532 "other_member_id" ISNULL AND
jbe@532 1533 "scope" ISNULL AND
jbe@532 1534 "unit_id" ISNULL AND
jbe@532 1535 "area_id" ISNULL AND
jbe@536 1536 "policy_id" ISNULL AND
jbe@532 1537 "issue_id" ISNULL AND
jbe@532 1538 "state" ISNULL AND
jbe@532 1539 "initiative_id" ISNULL AND
jbe@532 1540 "draft_id" ISNULL AND
jbe@532 1541 "suggestion_id" ISNULL AND
jbe@532 1542 "boolean_value" ISNULL AND
jbe@532 1543 "numeric_value" ISNULL AND
jbe@532 1544 "text_value" ISNULL AND
jbe@532 1545 "old_text_value" ISNULL )),
jbe@532 1546 CONSTRAINT "constr_for_member_active" CHECK (
jbe@532 1547 "event" != 'member_active' OR (
jbe@532 1548 "member_id" NOTNULL AND
jbe@532 1549 "other_member_id" ISNULL AND
jbe@532 1550 "scope" ISNULL AND
jbe@532 1551 "unit_id" ISNULL AND
jbe@532 1552 "area_id" ISNULL AND
jbe@536 1553 "policy_id" ISNULL AND
jbe@532 1554 "issue_id" ISNULL AND
jbe@532 1555 "state" ISNULL AND
jbe@532 1556 "initiative_id" ISNULL AND
jbe@532 1557 "draft_id" ISNULL AND
jbe@532 1558 "suggestion_id" ISNULL AND
jbe@532 1559 "boolean_value" NOTNULL AND
jbe@532 1560 "numeric_value" ISNULL AND
jbe@532 1561 "text_value" ISNULL AND
jbe@532 1562 "old_text_value" ISNULL )),
jbe@532 1563 CONSTRAINT "constr_for_member_name_updated" CHECK (
jbe@532 1564 "event" != 'member_name_updated' OR (
jbe@532 1565 "member_id" NOTNULL AND
jbe@532 1566 "other_member_id" ISNULL AND
jbe@532 1567 "scope" ISNULL AND
jbe@532 1568 "unit_id" ISNULL AND
jbe@532 1569 "area_id" ISNULL AND
jbe@536 1570 "policy_id" ISNULL AND
jbe@532 1571 "issue_id" ISNULL AND
jbe@532 1572 "state" ISNULL AND
jbe@532 1573 "initiative_id" ISNULL AND
jbe@532 1574 "draft_id" ISNULL AND
jbe@532 1575 "suggestion_id" ISNULL AND
jbe@532 1576 "boolean_value" ISNULL AND
jbe@532 1577 "numeric_value" ISNULL AND
jbe@532 1578 "text_value" NOTNULL AND
jbe@532 1579 "old_text_value" NOTNULL )),
jbe@532 1580 CONSTRAINT "constr_for_interest" CHECK (
jbe@532 1581 "event" != 'interest' OR (
jbe@532 1582 "member_id" NOTNULL AND
jbe@532 1583 "other_member_id" ISNULL AND
jbe@532 1584 "scope" ISNULL AND
jbe@532 1585 "unit_id" NOTNULL AND
jbe@532 1586 "area_id" NOTNULL AND
jbe@536 1587 "policy_id" NOTNULL AND
jbe@532 1588 "issue_id" NOTNULL AND
jbe@532 1589 "state" NOTNULL AND
jbe@532 1590 "initiative_id" ISNULL AND
jbe@532 1591 "draft_id" ISNULL AND
jbe@532 1592 "suggestion_id" ISNULL AND
jbe@532 1593 "boolean_value" NOTNULL AND
jbe@532 1594 "numeric_value" ISNULL AND
jbe@532 1595 "text_value" ISNULL AND
jbe@532 1596 "old_text_value" ISNULL )),
jbe@532 1597 CONSTRAINT "constr_for_initiator" CHECK (
jbe@532 1598 "event" != 'initiator' OR (
jbe@532 1599 "member_id" NOTNULL AND
jbe@532 1600 "other_member_id" ISNULL AND
jbe@532 1601 "scope" ISNULL AND
jbe@532 1602 "unit_id" NOTNULL AND
jbe@532 1603 "area_id" NOTNULL AND
jbe@536 1604 "policy_id" NOTNULL AND
jbe@532 1605 "issue_id" NOTNULL AND
jbe@532 1606 "state" NOTNULL AND
jbe@532 1607 "initiative_id" NOTNULL AND
jbe@532 1608 "draft_id" ISNULL AND
jbe@532 1609 "suggestion_id" ISNULL AND
jbe@532 1610 "boolean_value" NOTNULL AND
jbe@532 1611 "numeric_value" ISNULL AND
jbe@532 1612 "text_value" ISNULL AND
jbe@532 1613 "old_text_value" ISNULL )),
jbe@532 1614 CONSTRAINT "constr_for_support" CHECK (
jbe@532 1615 "event" != 'support' OR (
jbe@532 1616 "member_id" NOTNULL AND
jbe@532 1617 "other_member_id" ISNULL AND
jbe@532 1618 "scope" ISNULL AND
jbe@532 1619 "unit_id" NOTNULL AND
jbe@532 1620 "area_id" NOTNULL AND
jbe@536 1621 "policy_id" NOTNULL AND
jbe@532 1622 "issue_id" NOTNULL AND
jbe@532 1623 "state" NOTNULL AND
jbe@532 1624 "initiative_id" NOTNULL AND
jbe@532 1625 ("draft_id" NOTNULL) = ("boolean_value" = TRUE) AND
jbe@532 1626 "suggestion_id" ISNULL AND
jbe@532 1627 "boolean_value" NOTNULL AND
jbe@532 1628 "numeric_value" ISNULL AND
jbe@532 1629 "text_value" ISNULL AND
jbe@532 1630 "old_text_value" ISNULL )),
jbe@532 1631 CONSTRAINT "constr_for_support_updated" CHECK (
jbe@532 1632 "event" != 'support_updated' OR (
jbe@532 1633 "member_id" NOTNULL AND
jbe@532 1634 "other_member_id" ISNULL AND
jbe@532 1635 "scope" ISNULL AND
jbe@532 1636 "unit_id" NOTNULL AND
jbe@532 1637 "area_id" NOTNULL AND
jbe@536 1638 "policy_id" NOTNULL AND
jbe@532 1639 "issue_id" NOTNULL AND
jbe@532 1640 "state" NOTNULL AND
jbe@532 1641 "initiative_id" NOTNULL AND
jbe@532 1642 "draft_id" NOTNULL AND
jbe@532 1643 "suggestion_id" ISNULL AND
jbe@532 1644 "boolean_value" ISNULL AND
jbe@532 1645 "numeric_value" ISNULL AND
jbe@532 1646 "text_value" ISNULL AND
jbe@532 1647 "old_text_value" ISNULL )),
jbe@532 1648 CONSTRAINT "constr_for_suggestion_rated" CHECK (
jbe@532 1649 "event" != 'suggestion_rated' OR (
jbe@532 1650 "member_id" NOTNULL AND
jbe@532 1651 "other_member_id" ISNULL AND
jbe@532 1652 "scope" ISNULL AND
jbe@532 1653 "unit_id" NOTNULL AND
jbe@532 1654 "area_id" NOTNULL AND
jbe@536 1655 "policy_id" NOTNULL AND
jbe@532 1656 "issue_id" NOTNULL AND
jbe@532 1657 "state" NOTNULL AND
jbe@532 1658 "initiative_id" NOTNULL AND
jbe@532 1659 "draft_id" ISNULL AND
jbe@532 1660 "suggestion_id" NOTNULL AND
jbe@532 1661 ("boolean_value" NOTNULL) = ("numeric_value" != 0) AND
jbe@532 1662 "numeric_value" NOTNULL AND
jbe@532 1663 "numeric_value" IN (-2, -1, 0, 1, 2) AND
jbe@532 1664 "text_value" ISNULL AND
jbe@532 1665 "old_text_value" ISNULL )),
jbe@532 1666 CONSTRAINT "constr_for_delegation" CHECK (
jbe@532 1667 "event" != 'delegation' OR (
jbe@532 1668 "member_id" NOTNULL AND
jbe@532 1669 ("other_member_id" NOTNULL) OR ("boolean_value" = FALSE) AND
jbe@532 1670 "scope" NOTNULL AND
jbe@532 1671 "unit_id" NOTNULL AND
jbe@532 1672 ("area_id" NOTNULL) = ("scope" != 'unit'::"delegation_scope") AND
jbe@536 1673 "policy_id" ISNULL AND
jbe@532 1674 ("issue_id" NOTNULL) = ("scope" = 'issue'::"delegation_scope") AND
jbe@532 1675 ("state" NOTNULL) = ("scope" = 'issue'::"delegation_scope") AND
jbe@532 1676 "initiative_id" ISNULL AND
jbe@532 1677 "draft_id" ISNULL AND
jbe@532 1678 "suggestion_id" ISNULL AND
jbe@532 1679 "boolean_value" NOTNULL AND
jbe@532 1680 "numeric_value" ISNULL AND
jbe@532 1681 "text_value" ISNULL AND
jbe@532 1682 "old_text_value" ISNULL )),
jbe@532 1683 CONSTRAINT "constr_for_contact" CHECK (
jbe@532 1684 "event" != 'contact' OR (
jbe@532 1685 "member_id" NOTNULL AND
jbe@532 1686 "other_member_id" NOTNULL AND
jbe@532 1687 "scope" ISNULL AND
jbe@532 1688 "unit_id" ISNULL AND
jbe@532 1689 "area_id" ISNULL AND
jbe@536 1690 "policy_id" ISNULL AND
jbe@532 1691 "issue_id" ISNULL AND
jbe@532 1692 "state" ISNULL AND
jbe@532 1693 "initiative_id" ISNULL AND
jbe@532 1694 "draft_id" ISNULL AND
jbe@532 1695 "suggestion_id" ISNULL AND
jbe@532 1696 "boolean_value" NOTNULL AND
jbe@532 1697 "numeric_value" ISNULL AND
jbe@532 1698 "text_value" ISNULL AND
jbe@532 1699 "old_text_value" ISNULL )) );
jbe@223 1700 CREATE INDEX "event_occurrence_idx" ON "event" ("occurrence");
jbe@112 1701
jbe@112 1702 COMMENT ON TABLE "event" IS 'Event table, automatically filled by triggers';
jbe@112 1703
jbe@114 1704 COMMENT ON COLUMN "event"."occurrence" IS 'Point in time, when event occurred';
jbe@114 1705 COMMENT ON COLUMN "event"."event" IS 'Type of event (see TYPE "event_type")';
jbe@114 1706 COMMENT ON COLUMN "event"."member_id" IS 'Member who caused the event, if applicable';
jbe@114 1707 COMMENT ON COLUMN "event"."state" IS 'If issue_id is set: state of affected issue; If state changed: new state';
jbe@114 1708
jbe@112 1709
jbe@534 1710 CREATE TABLE "event_processed" (
jbe@222 1711 "event_id" INT8 NOT NULL );
jbe@534 1712 CREATE UNIQUE INDEX "event_processed_singleton_idx" ON "event_processed" ((1));
jbe@534 1713
jbe@534 1714 COMMENT ON TABLE "event_processed" IS 'This table stores one row with the last event_id, for which event handlers have been executed (e.g. notifications having been sent out)';
jbe@534 1715 COMMENT ON INDEX "event_processed_singleton_idx" IS 'This index ensures that "event_processed" only contains one row maximum.';
jbe@507 1716
jbe@507 1717
jbe@507 1718 CREATE TABLE "notification_initiative_sent" (
jbe@486 1719 PRIMARY KEY ("member_id", "initiative_id"),
jbe@486 1720 "member_id" INT4 REFERENCES "member" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@486 1721 "initiative_id" INT4 REFERENCES "initiative" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@486 1722 "last_draft_id" INT8 NOT NULL,
jbe@495 1723 "last_suggestion_id" INT8 );
jbe@507 1724 CREATE INDEX "notification_initiative_sent_initiative_idx" ON "notification_initiative_sent" ("initiative_id");
jbe@486 1725
jbe@508 1726 COMMENT ON TABLE "notification_initiative_sent" IS 'Information which initiatives have been promoted to a member in a scheduled notification mail';
jbe@508 1727
jbe@508 1728 COMMENT ON COLUMN "notification_initiative_sent"."last_draft_id" IS 'Current (i.e. last) draft_id when initiative had been promoted';
jbe@508 1729 COMMENT ON COLUMN "notification_initiative_sent"."last_suggestion_id" IS 'Current (i.e. last) draft_id when initiative had been promoted';
jbe@508 1730
jbe@486 1731
jbe@496 1732 CREATE TABLE "newsletter" (
jbe@496 1733 "id" SERIAL4 PRIMARY KEY,
jbe@496 1734 "published" TIMESTAMPTZ NOT NULL,
jbe@496 1735 "unit_id" INT4 REFERENCES "unit" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@496 1736 "include_all_members" BOOLEAN NOT NULL,
jbe@496 1737 "sent" TIMESTAMPTZ,
jbe@496 1738 "subject" TEXT NOT NULL,
jbe@496 1739 "content" TEXT NOT NULL );
jbe@496 1740 CREATE INDEX "newsletter_unit_id_idx" ON "newsletter" ("unit_id", "published");
jbe@496 1741 CREATE INDEX "newsletter_all_units_published_idx" ON "newsletter" ("published") WHERE "unit_id" ISNULL;
jbe@496 1742 CREATE INDEX "newsletter_published_idx" ON "newsletter" ("published");
jbe@496 1743
jbe@508 1744 COMMENT ON TABLE "newsletter" IS 'Contains newsletters created by administrators to be sent out and for further reference';
jbe@508 1745
jbe@508 1746 COMMENT ON COLUMN "newsletter"."published" IS 'Timestamp when the newsletter is to be sent out (and made available in the frontend)';
jbe@508 1747 COMMENT ON COLUMN "newsletter"."unit_id" IS 'If set, only members with voting right in the given unit are considered to be recipients';
jbe@508 1748 COMMENT ON COLUMN "newsletter"."include_all_members" IS 'TRUE = include all members regardless of their ''disable_notifications'' setting';
jbe@508 1749 COMMENT ON COLUMN "newsletter"."sent" IS 'Timestamp when the newsletter has been mailed out';
jbe@508 1750 COMMENT ON COLUMN "newsletter"."subject" IS 'Subject line (e.g. to be used for the email)';
jbe@508 1751 COMMENT ON COLUMN "newsletter"."content" IS 'Plain text content of the newsletter';
jbe@222 1752
jbe@222 1753
jbe@112 1754
jbe@112 1755 ----------------------------------------------
jbe@112 1756 -- Writing of history entries and event log --
jbe@112 1757 ----------------------------------------------
jbe@13 1758
jbe@181 1759
jbe@13 1760 CREATE FUNCTION "write_member_history_trigger"()
jbe@13 1761 RETURNS TRIGGER
jbe@13 1762 LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@13 1763 BEGIN
jbe@42 1764 IF
jbe@230 1765 ( NEW."active" != OLD."active" OR
jbe@230 1766 NEW."name" != OLD."name" ) AND
jbe@230 1767 OLD."activated" NOTNULL
jbe@42 1768 THEN
jbe@42 1769 INSERT INTO "member_history"
jbe@57 1770 ("member_id", "active", "name")
jbe@57 1771 VALUES (NEW."id", OLD."active", OLD."name");
jbe@13 1772 END IF;
jbe@13 1773 RETURN NULL;
jbe@13 1774 END;
jbe@13 1775 $$;
jbe@13 1776
jbe@13 1777 CREATE TRIGGER "write_member_history"
jbe@13 1778 AFTER UPDATE ON "member" FOR EACH ROW EXECUTE PROCEDURE
jbe@13 1779 "write_member_history_trigger"();
jbe@13 1780
jbe@13 1781 COMMENT ON FUNCTION "write_member_history_trigger"() IS 'Implementation of trigger "write_member_history" on table "member"';
jbe@57 1782 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 1783
jbe@13 1784
jbe@112 1785 CREATE FUNCTION "write_event_issue_state_changed_trigger"()
jbe@112 1786 RETURNS TRIGGER
jbe@112 1787 LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@532 1788 DECLARE
jbe@532 1789 "area_row" "area"%ROWTYPE;
jbe@112 1790 BEGIN
jbe@328 1791 IF NEW."state" != OLD."state" THEN
jbe@532 1792 SELECT * INTO "area_row" FROM "area" WHERE "id" = NEW."area_id"
jbe@532 1793 FOR SHARE;
jbe@532 1794 INSERT INTO "event" (
jbe@532 1795 "event",
jbe@536 1796 "unit_id", "area_id", "policy_id", "issue_id", "state"
jbe@532 1797 ) VALUES (
jbe@532 1798 'issue_state_changed',
jbe@536 1799 "area_row"."unit_id", NEW."area_id", NEW."policy_id",
jbe@536 1800 NEW."id", NEW."state"
jbe@532 1801 );
jbe@112 1802 END IF;
jbe@112 1803 RETURN NULL;
jbe@112 1804 END;
jbe@112 1805 $$;
jbe@112 1806
jbe@112 1807 CREATE TRIGGER "write_event_issue_state_changed"
jbe@112 1808 AFTER UPDATE ON "issue" FOR EACH ROW EXECUTE PROCEDURE
jbe@112 1809 "write_event_issue_state_changed_trigger"();
jbe@112 1810
jbe@112 1811 COMMENT ON FUNCTION "write_event_issue_state_changed_trigger"() IS 'Implementation of trigger "write_event_issue_state_changed" on table "issue"';
jbe@112 1812 COMMENT ON TRIGGER "write_event_issue_state_changed" ON "issue" IS 'Create entry in "event" table on "state" change';
jbe@112 1813
jbe@112 1814
jbe@112 1815 CREATE FUNCTION "write_event_initiative_or_draft_created_trigger"()
jbe@112 1816 RETURNS TRIGGER
jbe@112 1817 LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@112 1818 DECLARE
jbe@112 1819 "initiative_row" "initiative"%ROWTYPE;
jbe@113 1820 "issue_row" "issue"%ROWTYPE;
jbe@532 1821 "area_row" "area"%ROWTYPE;
jbe@112 1822 "event_v" "event_type";
jbe@112 1823 BEGIN
jbe@112 1824 SELECT * INTO "initiative_row" FROM "initiative"
jbe@532 1825 WHERE "id" = NEW."initiative_id" FOR SHARE;
jbe@113 1826 SELECT * INTO "issue_row" FROM "issue"
jbe@532 1827 WHERE "id" = "initiative_row"."issue_id" FOR SHARE;
jbe@532 1828 SELECT * INTO "area_row" FROM "area"
jbe@532 1829 WHERE "id" = "issue_row"."area_id" FOR SHARE;
jbe@112 1830 IF EXISTS (
jbe@112 1831 SELECT NULL FROM "draft"
jbe@532 1832 WHERE "initiative_id" = NEW."initiative_id" AND "id" != NEW."id"
jbe@532 1833 FOR SHARE
jbe@112 1834 ) THEN
jbe@112 1835 "event_v" := 'new_draft_created';
jbe@112 1836 ELSE
jbe@112 1837 IF EXISTS (
jbe@112 1838 SELECT NULL FROM "initiative"
jbe@112 1839 WHERE "issue_id" = "initiative_row"."issue_id"
jbe@112 1840 AND "id" != "initiative_row"."id"
jbe@532 1841 FOR SHARE
jbe@112 1842 ) THEN
jbe@112 1843 "event_v" := 'initiative_created_in_existing_issue';
jbe@112 1844 ELSE
jbe@112 1845 "event_v" := 'initiative_created_in_new_issue';
jbe@112 1846 END IF;
jbe@112 1847 END IF;
jbe@112 1848 INSERT INTO "event" (
jbe@112 1849 "event", "member_id",
jbe@536 1850 "unit_id", "area_id", "policy_id", "issue_id", "state",
jbe@532 1851 "initiative_id", "draft_id"
jbe@112 1852 ) VALUES (
jbe@532 1853 "event_v", NEW."author_id",
jbe@536 1854 "area_row"."unit_id", "issue_row"."area_id", "issue_row"."policy_id",
jbe@532 1855 "initiative_row"."issue_id", "issue_row"."state",
jbe@532 1856 NEW."initiative_id", NEW."id"
jbe@532 1857 );
jbe@112 1858 RETURN NULL;
jbe@112 1859 END;
jbe@112 1860 $$;
jbe@112 1861
jbe@112 1862 CREATE TRIGGER "write_event_initiative_or_draft_created"
jbe@112 1863 AFTER INSERT ON "draft" FOR EACH ROW EXECUTE PROCEDURE
jbe@112 1864 "write_event_initiative_or_draft_created_trigger"();
jbe@112 1865
jbe@112 1866 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 1867 COMMENT ON TRIGGER "write_event_initiative_or_draft_created" ON "draft" IS 'Create entry in "event" table on draft creation';
jbe@112 1868
jbe@112 1869
jbe@112 1870 CREATE FUNCTION "write_event_initiative_revoked_trigger"()
jbe@112 1871 RETURNS TRIGGER
jbe@112 1872 LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@113 1873 DECLARE
jbe@231 1874 "issue_row" "issue"%ROWTYPE;
jbe@532 1875 "area_row" "area"%ROWTYPE;
jbe@231 1876 "draft_id_v" "draft"."id"%TYPE;
jbe@112 1877 BEGIN
jbe@112 1878 IF OLD."revoked" ISNULL AND NEW."revoked" NOTNULL THEN
jbe@231 1879 SELECT * INTO "issue_row" FROM "issue"
jbe@532 1880 WHERE "id" = NEW."issue_id" FOR SHARE;
jbe@532 1881 SELECT * INTO "area_row" FROM "area"
jbe@532 1882 WHERE "id" = "issue_row"."area_id" FOR SHARE;
jbe@231 1883 SELECT "id" INTO "draft_id_v" FROM "current_draft"
jbe@532 1884 WHERE "initiative_id" = NEW."id" FOR SHARE;
jbe@112 1885 INSERT INTO "event" (
jbe@532 1886 "event", "member_id",
jbe@536 1887 "unit_id", "area_id", "policy_id", "issue_id", "state",
jbe@532 1888 "initiative_id", "draft_id"
jbe@112 1889 ) VALUES (
jbe@532 1890 'initiative_revoked', NEW."revoked_by_member_id",
jbe@532 1891 "area_row"."unit_id", "issue_row"."area_id",
jbe@536 1892 "issue_row"."policy_id",
jbe@532 1893 NEW."issue_id", "issue_row"."state",
jbe@532 1894 NEW."id", "draft_id_v"
jbe@532 1895 );
jbe@112 1896 END IF;
jbe@112 1897 RETURN NULL;
jbe@112 1898 END;
jbe@112 1899 $$;
jbe@112 1900
jbe@112 1901 CREATE TRIGGER "write_event_initiative_revoked"
jbe@112 1902 AFTER UPDATE ON "initiative" FOR EACH ROW EXECUTE PROCEDURE
jbe@112 1903 "write_event_initiative_revoked_trigger"();
jbe@112 1904
jbe@112 1905 COMMENT ON FUNCTION "write_event_initiative_revoked_trigger"() IS 'Implementation of trigger "write_event_initiative_revoked" on table "issue"';
jbe@112 1906 COMMENT ON TRIGGER "write_event_initiative_revoked" ON "initiative" IS 'Create entry in "event" table, when an initiative is revoked';
jbe@112 1907
jbe@112 1908
jbe@112 1909 CREATE FUNCTION "write_event_suggestion_created_trigger"()
jbe@112 1910 RETURNS TRIGGER
jbe@112 1911 LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@112 1912 DECLARE
jbe@112 1913 "initiative_row" "initiative"%ROWTYPE;
jbe@113 1914 "issue_row" "issue"%ROWTYPE;
jbe@532 1915 "area_row" "area"%ROWTYPE;
jbe@112 1916 BEGIN
jbe@112 1917 SELECT * INTO "initiative_row" FROM "initiative"
jbe@532 1918 WHERE "id" = NEW."initiative_id" FOR SHARE;
jbe@113 1919 SELECT * INTO "issue_row" FROM "issue"
jbe@532 1920 WHERE "id" = "initiative_row"."issue_id" FOR SHARE;
jbe@532 1921 SELECT * INTO "area_row" FROM "area"
jbe@532 1922 WHERE "id" = "issue_row"."area_id" FOR SHARE;
jbe@112 1923 INSERT INTO "event" (
jbe@112 1924 "event", "member_id",
jbe@536 1925 "unit_id", "area_id", "policy_id", "issue_id", "state",
jbe@532 1926 "initiative_id", "suggestion_id"
jbe@112 1927 ) VALUES (
jbe@532 1928 'suggestion_created', NEW."author_id",
jbe@536 1929 "area_row"."unit_id", "issue_row"."area_id", "issue_row"."policy_id",
jbe@532 1930 "initiative_row"."issue_id", "issue_row"."state",
jbe@532 1931 NEW."initiative_id", NEW."id"
jbe@532 1932 );
jbe@112 1933 RETURN NULL;
jbe@112 1934 END;
jbe@112 1935 $$;
jbe@112 1936
jbe@112 1937 CREATE TRIGGER "write_event_suggestion_created"
jbe@112 1938 AFTER INSERT ON "suggestion" FOR EACH ROW EXECUTE PROCEDURE
jbe@112 1939 "write_event_suggestion_created_trigger"();
jbe@112 1940
jbe@112 1941 COMMENT ON FUNCTION "write_event_suggestion_created_trigger"() IS 'Implementation of trigger "write_event_suggestion_created" on table "issue"';
jbe@112 1942 COMMENT ON TRIGGER "write_event_suggestion_created" ON "suggestion" IS 'Create entry in "event" table on suggestion creation';
jbe@112 1943
jbe@112 1944
jbe@532 1945 CREATE FUNCTION "write_event_suggestion_removed_trigger"()
jbe@532 1946 RETURNS TRIGGER
jbe@532 1947 LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@532 1948 DECLARE
jbe@532 1949 "initiative_row" "initiative"%ROWTYPE;
jbe@532 1950 "issue_row" "issue"%ROWTYPE;
jbe@532 1951 "area_row" "area"%ROWTYPE;
jbe@532 1952 BEGIN
jbe@532 1953 SELECT * INTO "initiative_row" FROM "initiative"
jbe@532 1954 WHERE "id" = OLD."initiative_id" FOR SHARE;
jbe@532 1955 IF "initiative_row"."id" NOTNULL THEN
jbe@532 1956 SELECT * INTO "issue_row" FROM "issue"
jbe@532 1957 WHERE "id" = "initiative_row"."issue_id" FOR SHARE;
jbe@532 1958 SELECT * INTO "area_row" FROM "area"
jbe@532 1959 WHERE "id" = "issue_row"."area_id" FOR SHARE;
jbe@532 1960 INSERT INTO "event" (
jbe@532 1961 "event",
jbe@536 1962 "unit_id", "area_id", "policy_id", "issue_id", "state",
jbe@532 1963 "initiative_id", "suggestion_id"
jbe@532 1964 ) VALUES (
jbe@532 1965 'suggestion_removed',
jbe@532 1966 "area_row"."unit_id", "issue_row"."area_id",
jbe@536 1967 "issue_row"."policy_id",
jbe@532 1968 "initiative_row"."issue_id", "issue_row"."state",
jbe@532 1969 OLD."initiative_id", OLD."id"
jbe@532 1970 );
jbe@532 1971 END IF;
jbe@532 1972 RETURN NULL;
jbe@532 1973 END;
jbe@532 1974 $$;
jbe@532 1975
jbe@532 1976 CREATE TRIGGER "write_event_suggestion_removed"
jbe@532 1977 AFTER DELETE ON "suggestion" FOR EACH ROW EXECUTE PROCEDURE
jbe@532 1978 "write_event_suggestion_removed_trigger"();
jbe@532 1979
jbe@532 1980 COMMENT ON FUNCTION "write_event_suggestion_removed_trigger"() IS 'Implementation of trigger "write_event_suggestion_removed" on table "issue"';
jbe@532 1981 COMMENT ON TRIGGER "write_event_suggestion_removed" ON "suggestion" IS 'Create entry in "event" table on suggestion creation';
jbe@532 1982
jbe@532 1983
jbe@532 1984 CREATE FUNCTION "write_event_member_trigger"()
jbe@532 1985 RETURNS TRIGGER
jbe@532 1986 LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@532 1987 BEGIN
jbe@532 1988 IF TG_OP = 'INSERT' THEN
jbe@532 1989 IF NEW."activated" NOTNULL THEN
jbe@532 1990 INSERT INTO "event" ("event", "member_id")
jbe@532 1991 VALUES ('member_activated', NEW."id");
jbe@532 1992 END IF;
jbe@532 1993 IF NEW."active" THEN
jbe@532 1994 INSERT INTO "event" ("event", "member_id", "boolean_value")
jbe@532 1995 VALUES ('member_active', NEW."id", TRUE);
jbe@532 1996 END IF;
jbe@532 1997 ELSIF TG_OP = 'UPDATE' THEN
jbe@532 1998 IF OLD."id" != NEW."id" THEN
jbe@532 1999 RAISE EXCEPTION 'Cannot change member ID';
jbe@532 2000 END IF;
jbe@532 2001 IF OLD."name" != NEW."name" THEN
jbe@532 2002 INSERT INTO "event" (
jbe@532 2003 "event", "member_id", "text_value", "old_text_value"
jbe@532 2004 ) VALUES (
jbe@532 2005 'member_name_updated', NEW."id", NEW."name", OLD."name"
jbe@532 2006 );
jbe@532 2007 END IF;
jbe@532 2008 IF OLD."active" != NEW."active" THEN
jbe@532 2009 INSERT INTO "event" ("event", "member_id", "boolean_value") VALUES (
jbe@532 2010 'member_active', NEW."id", NEW."active"
jbe@532 2011 );
jbe@532 2012 END IF;
jbe@532 2013 IF
jbe@532 2014 OLD."activated" NOTNULL AND
jbe@536 2015 -- TODO: NEW."activated" ISNULL? OLD."login" NOTNULL?
jbe@532 2016 NEW."last_login" ISNULL AND
jbe@532 2017 NEW."login" ISNULL AND
jbe@532 2018 NEW."authority_login" ISNULL AND
jbe@532 2019 NEW."locked" = TRUE
jbe@532 2020 THEN
jbe@532 2021 INSERT INTO "event" ("event", "member_id")
jbe@532 2022 VALUES ('member_removed', NEW."id");
jbe@532 2023 END IF;
jbe@532 2024 END IF;
jbe@532 2025 RETURN NULL;
jbe@532 2026 END;
jbe@532 2027 $$;
jbe@532 2028
jbe@532 2029 CREATE TRIGGER "write_event_member"
jbe@532 2030 AFTER INSERT OR UPDATE ON "member" FOR EACH ROW EXECUTE PROCEDURE
jbe@532 2031 "write_event_member_trigger"();
jbe@532 2032
jbe@532 2033 COMMENT ON FUNCTION "write_event_member_trigger"() IS 'Implementation of trigger "write_event_member" on table "member"';
jbe@532 2034 COMMENT ON TRIGGER "write_event_member" ON "member" IS 'Create entries in "event" table on insertion to member table';
jbe@532 2035
jbe@532 2036
jbe@532 2037 CREATE FUNCTION "write_event_member_profile_updated_trigger"()
jbe@532 2038 RETURNS TRIGGER
jbe@532 2039 LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@532 2040 BEGIN
jbe@532 2041 IF TG_OP = 'DELETE' OR TG_OP = 'UPDATE' THEN
jbe@532 2042 IF EXISTS (SELECT NULL FROM "member" WHERE "id" = OLD."member_id") THEN
jbe@532 2043 INSERT INTO "event" ("event", "member_id") VALUES (
jbe@532 2044 'member_profile_updated', OLD."member_id"
jbe@532 2045 );
jbe@532 2046 END IF;
jbe@532 2047 END IF;
jbe@532 2048 IF TG_OP = 'UPDATE' THEN
jbe@532 2049 IF OLD."member_id" = NEW."member_id" THEN
jbe@532 2050 RETURN NULL;
jbe@532 2051 END IF;
jbe@532 2052 END IF;
jbe@532 2053 IF TG_OP = 'INSERT' OR TG_OP = 'UPDATE' THEN
jbe@532 2054 INSERT INTO "event" ("event", "member_id") VALUES (
jbe@532 2055 'member_profile_updated', NEW."member_id"
jbe@532 2056 );
jbe@532 2057 END IF;
jbe@532 2058 RETURN NULL;
jbe@532 2059 END;
jbe@532 2060 $$;
jbe@532 2061
jbe@532 2062 CREATE TRIGGER "write_event_member_profile_updated"
jbe@532 2063 AFTER INSERT OR UPDATE OR DELETE ON "member_profile"
jbe@532 2064 FOR EACH ROW EXECUTE PROCEDURE
jbe@532 2065 "write_event_member_profile_updated_trigger"();
jbe@532 2066
jbe@532 2067 COMMENT ON FUNCTION "write_event_member_profile_updated_trigger"() IS 'Implementation of trigger "write_event_member_profile_updated" on table "member_profile"';
jbe@532 2068 COMMENT ON TRIGGER "write_event_member_profile_updated" ON "member_profile" IS 'Creates entries in "event" table on member profile update';
jbe@532 2069
jbe@532 2070
jbe@532 2071 CREATE FUNCTION "write_event_member_image_updated_trigger"()
jbe@532 2072 RETURNS TRIGGER
jbe@532 2073 LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@532 2074 BEGIN
jbe@532 2075 IF TG_OP = 'DELETE' OR TG_OP = 'UPDATE' THEN
jbe@532 2076 IF NOT OLD."scaled" THEN
jbe@532 2077 IF EXISTS (SELECT NULL FROM "member" WHERE "id" = OLD."member_id") THEN
jbe@532 2078 INSERT INTO "event" ("event", "member_id") VALUES (
jbe@532 2079 'member_image_updated', OLD."member_id"
jbe@532 2080 );
jbe@532 2081 END IF;
jbe@532 2082 END IF;
jbe@532 2083 END IF;
jbe@532 2084 IF TG_OP = 'UPDATE' THEN
jbe@532 2085 IF
jbe@532 2086 OLD."member_id" = NEW."member_id" AND
jbe@532 2087 OLD."scaled" = NEW."scaled"
jbe@532 2088 THEN
jbe@532 2089 RETURN NULL;
jbe@532 2090 END IF;
jbe@532 2091 END IF;
jbe@532 2092 IF TG_OP = 'INSERT' OR TG_OP = 'UPDATE' THEN
jbe@532 2093 IF NOT NEW."scaled" THEN
jbe@532 2094 INSERT INTO "event" ("event", "member_id") VALUES (
jbe@532 2095 'member_image_updated', NEW."member_id"
jbe@532 2096 );
jbe@532 2097 END IF;
jbe@532 2098 END IF;
jbe@532 2099 RETURN NULL;
jbe@532 2100 END;
jbe@532 2101 $$;
jbe@532 2102
jbe@532 2103 CREATE TRIGGER "write_event_member_image_updated"
jbe@532 2104 AFTER INSERT OR UPDATE OR DELETE ON "member_image"
jbe@532 2105 FOR EACH ROW EXECUTE PROCEDURE
jbe@532 2106 "write_event_member_image_updated_trigger"();
jbe@532 2107
jbe@532 2108 COMMENT ON FUNCTION "write_event_member_image_updated_trigger"() IS 'Implementation of trigger "write_event_member_image_updated" on table "member_image"';
jbe@532 2109 COMMENT ON TRIGGER "write_event_member_image_updated" ON "member_image" IS 'Creates entries in "event" table on member image update';
jbe@532 2110
jbe@532 2111
jbe@532 2112 CREATE FUNCTION "write_event_interest_trigger"()
jbe@532 2113 RETURNS TRIGGER
jbe@532 2114 LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@532 2115 DECLARE
jbe@532 2116 "issue_row" "issue"%ROWTYPE;
jbe@532 2117 "area_row" "area"%ROWTYPE;
jbe@532 2118 BEGIN
jbe@532 2119 IF TG_OP = 'UPDATE' THEN
jbe@532 2120 IF OLD = NEW THEN
jbe@532 2121 RETURN NULL;
jbe@532 2122 END IF;
jbe@532 2123 END IF;
jbe@532 2124 IF TG_OP = 'DELETE' OR TG_OP = 'UPDATE' THEN
jbe@532 2125 SELECT * INTO "issue_row" FROM "issue"
jbe@532 2126 WHERE "id" = OLD."issue_id" FOR SHARE;
jbe@532 2127 SELECT * INTO "area_row" FROM "area"
jbe@532 2128 WHERE "id" = "issue_row"."area_id" FOR SHARE;
jbe@532 2129 IF "issue_row"."id" NOTNULL THEN
jbe@532 2130 INSERT INTO "event" (
jbe@532 2131 "event", "member_id",
jbe@536 2132 "unit_id", "area_id", "policy_id", "issue_id", "state",
jbe@532 2133 "boolean_value"
jbe@532 2134 ) VALUES (
jbe@532 2135 'interest', OLD."member_id",
jbe@532 2136 "area_row"."unit_id", "issue_row"."area_id",
jbe@536 2137 "issue_row"."policy_id",
jbe@532 2138 OLD."issue_id", "issue_row"."state",
jbe@532 2139 FALSE
jbe@532 2140 );
jbe@532 2141 END IF;
jbe@532 2142 END IF;
jbe@532 2143 IF TG_OP = 'INSERT' OR TG_OP = 'UPDATE' THEN
jbe@532 2144 SELECT * INTO "issue_row" FROM "issue"
jbe@532 2145 WHERE "id" = NEW."issue_id" FOR SHARE;
jbe@532 2146 SELECT * INTO "area_row" FROM "area"
jbe@532 2147 WHERE "id" = "issue_row"."area_id" FOR SHARE;
jbe@532 2148 INSERT INTO "event" (
jbe@532 2149 "event", "member_id",
jbe@536 2150 "unit_id", "area_id", "policy_id", "issue_id", "state",
jbe@532 2151 "boolean_value"
jbe@532 2152 ) VALUES (
jbe@532 2153 'interest', NEW."member_id",
jbe@532 2154 "area_row"."unit_id", "issue_row"."area_id",
jbe@536 2155 "issue_row"."policy_id",
jbe@532 2156 NEW."issue_id", "issue_row"."state",
jbe@532 2157 TRUE
jbe@532 2158 );
jbe@532 2159 END IF;
jbe@532 2160 RETURN NULL;
jbe@532 2161 END;
jbe@532 2162 $$;
jbe@532 2163
jbe@532 2164 CREATE TRIGGER "write_event_interest"
jbe@532 2165 AFTER INSERT OR UPDATE OR DELETE ON "interest" FOR EACH ROW EXECUTE PROCEDURE
jbe@532 2166 "write_event_interest_trigger"();
jbe@532 2167
jbe@532 2168 COMMENT ON FUNCTION "write_event_interest_trigger"() IS 'Implementation of trigger "write_event_interest_inserted" on table "interest"';
jbe@532 2169 COMMENT ON TRIGGER "write_event_interest" ON "interest" IS 'Create entry in "event" table on adding or removing interest';
jbe@532 2170
jbe@532 2171
jbe@532 2172 CREATE FUNCTION "write_event_initiator_trigger"()
jbe@532 2173 RETURNS TRIGGER
jbe@532 2174 LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@532 2175 DECLARE
jbe@532 2176 "initiative_row" "initiative"%ROWTYPE;
jbe@532 2177 "issue_row" "issue"%ROWTYPE;
jbe@532 2178 "area_row" "area"%ROWTYPE;
jbe@532 2179 BEGIN
jbe@532 2180 IF TG_OP = 'UPDATE' THEN
jbe@532 2181 IF
jbe@532 2182 OLD."initiative_id" = NEW."initiative_id" AND
jbe@532 2183 OLD."member_id" = NEW."member_id" AND
jbe@532 2184 coalesce(OLD."accepted", FALSE) = coalesce(NEW."accepted", FALSE)
jbe@532 2185 THEN
jbe@532 2186 RETURN NULL;
jbe@532 2187 END IF;
jbe@532 2188 END IF;
jbe@532 2189 IF (TG_OP = 'DELETE' OR TG_OP = 'UPDATE') AND NOT "accepted_v" THEN
jbe@532 2190 IF coalesce(OLD."accepted", FALSE) = TRUE THEN
jbe@532 2191 SELECT * INTO "initiative_row" FROM "initiative"
jbe@532 2192 WHERE "id" = OLD."initiative_id" FOR SHARE;
jbe@532 2193 IF "initiative_row"."id" NOTNULL THEN
jbe@532 2194 SELECT * INTO "issue_row" FROM "issue"
jbe@532 2195 WHERE "id" = "initiative_row"."issue_id" FOR SHARE;
jbe@532 2196 SELECT * INTO "area_row" FROM "area"
jbe@532 2197 WHERE "id" = "issue_row"."area_id" FOR SHARE;
jbe@532 2198 INSERT INTO "event" (
jbe@532 2199 "event", "member_id",
jbe@536 2200 "unit_id", "area_id", "policy_id", "issue_id", "state",
jbe@532 2201 "initiative_id", "boolean_value"
jbe@532 2202 ) VALUES (
jbe@532 2203 'initiator', OLD."member_id",
jbe@532 2204 "area_row"."unit_id", "issue_row"."area_id",
jbe@536 2205 "issue_row"."policy_id",
jbe@532 2206 "issue_row"."id", "issue_row"."state",
jbe@532 2207 OLD."initiative_id", FALSE
jbe@532 2208 );
jbe@532 2209 END IF;
jbe@532 2210 END IF;
jbe@532 2211 END IF;
jbe@532 2212 IF TG_OP = 'UPDATE' AND NOT "rejected_v" THEN
jbe@532 2213 IF coalesce(NEW."accepted", FALSE) = TRUE THEN
jbe@532 2214 SELECT * INTO "initiative_row" FROM "initiative"
jbe@532 2215 WHERE "id" = NEW."initiative_id" FOR SHARE;
jbe@532 2216 SELECT * INTO "issue_row" FROM "issue"
jbe@532 2217 WHERE "id" = "initiative_row"."issue_id" FOR SHARE;
jbe@532 2218 SELECT * INTO "area_row" FROM "area"
jbe@532 2219 WHERE "id" = "issue_row"."area_id" FOR SHARE;
jbe@532 2220 INSERT INTO "event" (
jbe@532 2221 "event", "member_id",
jbe@536 2222 "unit_id", "area_id", "policy_id", "issue_id", "state",
jbe@532 2223 "initiative_id", "boolean_value"
jbe@532 2224 ) VALUES (
jbe@532 2225 'initiator', NEW."member_id",
jbe@532 2226 "area_row"."unit_id", "issue_row"."area_id",
jbe@536 2227 "issue_row"."policy_id",
jbe@532 2228 "issue_row"."id", "issue_row"."state",
jbe@532 2229 NEW."initiative_id", TRUE
jbe@532 2230 );
jbe@532 2231 END IF;
jbe@532 2232 END IF;
jbe@532 2233 RETURN NULL;
jbe@532 2234 END;
jbe@532 2235 $$;
jbe@532 2236
jbe@532 2237 CREATE TRIGGER "write_event_initiator"
jbe@532 2238 AFTER UPDATE OR DELETE ON "initiator" FOR EACH ROW EXECUTE PROCEDURE
jbe@532 2239 "write_event_initiator_trigger"();
jbe@532 2240
jbe@532 2241 COMMENT ON FUNCTION "write_event_initiator_trigger"() IS 'Implementation of trigger "write_event_initiator" on table "initiator"';
jbe@532 2242 COMMENT ON TRIGGER "write_event_initiator" ON "initiator" IS 'Create entry in "event" table when accepting or removing initiatorship (NOTE: trigger does not fire on INSERT to avoid events on initiative creation)';
jbe@532 2243
jbe@532 2244
jbe@532 2245 CREATE FUNCTION "write_event_support_trigger"()
jbe@532 2246 RETURNS TRIGGER
jbe@532 2247 LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@532 2248 DECLARE
jbe@532 2249 "issue_row" "issue"%ROWTYPE;
jbe@532 2250 "area_row" "area"%ROWTYPE;
jbe@532 2251 BEGIN
jbe@532 2252 IF TG_OP = 'UPDATE' THEN
jbe@532 2253 IF
jbe@532 2254 OLD."initiative_id" = NEW."initiative_id" AND
jbe@532 2255 OLD."member_id" = NEW."member_id"
jbe@532 2256 THEN
jbe@532 2257 IF OLD."draft_id" != NEW."draft_id" THEN
jbe@532 2258 SELECT * INTO "issue_row" FROM "issue"
jbe@532 2259 WHERE "id" = NEW."issue_id" FOR SHARE;
jbe@532 2260 SELECT * INTO "area_row" FROM "area"
jbe@532 2261 WHERE "id" = "issue_row"."area_id" FOR SHARE;
jbe@532 2262 INSERT INTO "event" (
jbe@532 2263 "event", "member_id",
jbe@536 2264 "unit_id", "area_id", "policy_id", "issue_id", "state",
jbe@532 2265 "initiative_id", "draft_id"
jbe@532 2266 ) VALUES (
jbe@532 2267 'support_updated', NEW."member_id",
jbe@532 2268 "area_row"."unit_id", "issue_row"."area_id",
jbe@536 2269 "issue_row"."policy_id",
jbe@532 2270 "issue_row"."id", "issue_row"."state",
jbe@532 2271 NEW."initiative_id", NEW."draft_id"
jbe@532 2272 );
jbe@532 2273 END IF;
jbe@532 2274 RETURN NULL;
jbe@532 2275 END IF;
jbe@532 2276 END IF;
jbe@532 2277 IF TG_OP = 'DELETE' OR TG_OP = 'UPDATE' THEN
jbe@532 2278 IF EXISTS (
jbe@532 2279 SELECT NULL FROM "initiative" WHERE "id" = OLD."initiative_id"
jbe@532 2280 FOR SHARE
jbe@532 2281 ) THEN
jbe@532 2282 SELECT * INTO "issue_row" FROM "issue"
jbe@532 2283 WHERE "id" = OLD."issue_id" FOR SHARE;
jbe@532 2284 SELECT * INTO "area_row" FROM "area"
jbe@532 2285 WHERE "id" = "issue_row"."area_id" FOR SHARE;
jbe@532 2286 INSERT INTO "event" (
jbe@532 2287 "event", "member_id",
jbe@536 2288 "unit_id", "area_id", "policy_id", "issue_id", "state",
jbe@535 2289 "initiative_id", "boolean_value"
jbe@532 2290 ) VALUES (
jbe@532 2291 'support', OLD."member_id",
jbe@532 2292 "area_row"."unit_id", "issue_row"."area_id",
jbe@536 2293 "issue_row"."policy_id",
jbe@532 2294 "issue_row"."id", "issue_row"."state",
jbe@535 2295 OLD."initiative_id", FALSE
jbe@532 2296 );
jbe@532 2297 END IF;
jbe@532 2298 END IF;
jbe@532 2299 IF TG_OP = 'INSERT' OR TG_OP = 'UPDATE' THEN
jbe@532 2300 SELECT * INTO "issue_row" FROM "issue"
jbe@532 2301 WHERE "id" = NEW."issue_id" FOR SHARE;
jbe@532 2302 SELECT * INTO "area_row" FROM "area"
jbe@532 2303 WHERE "id" = "issue_row"."area_id" FOR SHARE;
jbe@532 2304 INSERT INTO "event" (
jbe@532 2305 "event", "member_id",
jbe@536 2306 "unit_id", "area_id", "policy_id", "issue_id", "state",
jbe@532 2307 "initiative_id", "draft_id", "boolean_value"
jbe@532 2308 ) VALUES (
jbe@532 2309 'support', NEW."member_id",
jbe@532 2310 "area_row"."unit_id", "issue_row"."area_id",
jbe@536 2311 "issue_row"."policy_id",
jbe@532 2312 "issue_row"."id", "issue_row"."state",
jbe@532 2313 NEW."initiative_id", NEW."draft_id", TRUE
jbe@532 2314 );
jbe@532 2315 END IF;
jbe@532 2316 RETURN NULL;
jbe@532 2317 END;
jbe@532 2318 $$;
jbe@532 2319
jbe@532 2320 CREATE TRIGGER "write_event_support"
jbe@532 2321 AFTER INSERT OR UPDATE OR DELETE ON "supporter" FOR EACH ROW EXECUTE PROCEDURE
jbe@532 2322 "write_event_support_trigger"();
jbe@532 2323
jbe@532 2324 COMMENT ON FUNCTION "write_event_support_trigger"() IS 'Implementation of trigger "write_event_support" on table "supporter"';
jbe@532 2325 COMMENT ON TRIGGER "write_event_support" ON "supporter" IS 'Create entry in "event" table when adding, updating, or removing support';
jbe@532 2326
jbe@532 2327
jbe@532 2328 CREATE FUNCTION "write_event_suggestion_rated_trigger"()
jbe@532 2329 RETURNS TRIGGER
jbe@532 2330 LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@532 2331 DECLARE
jbe@532 2332 "same_pkey_v" BOOLEAN = FALSE;
jbe@532 2333 "initiative_row" "initiative"%ROWTYPE;
jbe@532 2334 "issue_row" "issue"%ROWTYPE;
jbe@532 2335 "area_row" "area"%ROWTYPE;
jbe@532 2336 BEGIN
jbe@532 2337 IF TG_OP = 'UPDATE' THEN
jbe@532 2338 IF
jbe@532 2339 OLD."suggestion_id" = NEW."suggestion_id" AND
jbe@532 2340 OLD."member_id" = NEW."member_id"
jbe@532 2341 THEN
jbe@532 2342 IF
jbe@532 2343 OLD."degree" = NEW."degree" AND
jbe@532 2344 OLD."fulfilled" = NEW."fulfilled"
jbe@532 2345 THEN
jbe@532 2346 RETURN NULL;
jbe@532 2347 END IF;
jbe@532 2348 "same_pkey_v" := TRUE;
jbe@532 2349 END IF;
jbe@532 2350 END IF;
jbe@532 2351 IF (TG_OP = 'DELETE' OR TG_OP = 'UPDATE') AND NOT "same_pkey_v" THEN
jbe@532 2352 IF EXISTS (
jbe@532 2353 SELECT NULL FROM "suggestion" WHERE "id" = OLD."suggestion_id"
jbe@532 2354 FOR SHARE
jbe@532 2355 ) THEN
jbe@532 2356 SELECT * INTO "initiative_row" FROM "initiative"
jbe@532 2357 WHERE "id" = OLD."initiative_id" FOR SHARE;
jbe@532 2358 SELECT * INTO "issue_row" FROM "issue"
jbe@532 2359 WHERE "id" = "initiative_row"."issue_id" FOR SHARE;
jbe@532 2360 SELECT * INTO "area_row" FROM "area"
jbe@532 2361 WHERE "id" = "issue_row"."area_id" FOR SHARE;
jbe@532 2362 INSERT INTO "event" (
jbe@532 2363 "event", "member_id",
jbe@536 2364 "unit_id", "area_id", "policy_id", "issue_id", "state",
jbe@532 2365 "initiative_id", "suggestion_id",
jbe@532 2366 "boolean_value", "numeric_value"
jbe@532 2367 ) VALUES (
jbe@532 2368 'suggestion_rated', OLD."member_id",
jbe@532 2369 "area_row"."unit_id", "issue_row"."area_id",
jbe@536 2370 "issue_row"."policy_id",
jbe@532 2371 "initiative_row"."issue_id", "issue_row"."state",
jbe@532 2372 OLD."initiative_id", OLD."suggestion_id",
jbe@532 2373 NULL, 0
jbe@532 2374 );
jbe@532 2375 END IF;
jbe@532 2376 END IF;
jbe@532 2377 IF TG_OP = 'INSERT' OR TG_OP = 'UPDATE' THEN
jbe@532 2378 SELECT * INTO "initiative_row" FROM "initiative"
jbe@532 2379 WHERE "id" = NEW."initiative_id" FOR SHARE;
jbe@532 2380 SELECT * INTO "issue_row" FROM "issue"
jbe@532 2381 WHERE "id" = "initiative_row"."issue_id" FOR SHARE;
jbe@532 2382 SELECT * INTO "area_row" FROM "area"
jbe@532 2383 WHERE "id" = "issue_row"."area_id" FOR SHARE;
jbe@532 2384 INSERT INTO "event" (
jbe@532 2385 "event", "member_id",
jbe@536 2386 "unit_id", "area_id", "policy_id", "issue_id", "state",
jbe@532 2387 "initiative_id", "suggestion_id",
jbe@532 2388 "boolean_value", "numeric_value"
jbe@532 2389 ) VALUES (
jbe@532 2390 'suggestion_rated', NEW."member_id",
jbe@532 2391 "area_row"."unit_id", "issue_row"."area_id",
jbe@536 2392 "issue_row"."policy_id",
jbe@532 2393 "initiative_row"."issue_id", "issue_row"."state",
jbe@532 2394 NEW."initiative_id", NEW."suggestion_id",
jbe@532 2395 NEW."fulfilled", NEW."degree"
jbe@532 2396 );
jbe@532 2397 END IF;
jbe@532 2398 RETURN NULL;
jbe@532 2399 END;
jbe@532 2400 $$;
jbe@532 2401
jbe@532 2402 CREATE TRIGGER "write_event_suggestion_rated"
jbe@532 2403 AFTER INSERT OR UPDATE OR DELETE ON "opinion" FOR EACH ROW EXECUTE PROCEDURE
jbe@532 2404 "write_event_suggestion_rated_trigger"();
jbe@532 2405
jbe@532 2406 COMMENT ON FUNCTION "write_event_suggestion_rated_trigger"() IS 'Implementation of trigger "write_event_suggestion_rated" on table "opinion"';
jbe@532 2407 COMMENT ON TRIGGER "write_event_suggestion_rated" ON "opinion" IS 'Create entry in "event" table when adding, updating, or removing support';
jbe@532 2408
jbe@532 2409
jbe@532 2410 CREATE FUNCTION "write_event_delegation_trigger"()
jbe@532 2411 RETURNS TRIGGER
jbe@532 2412 LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@532 2413 DECLARE
jbe@532 2414 "issue_row" "issue"%ROWTYPE;
jbe@532 2415 "area_row" "area"%ROWTYPE;
jbe@532 2416 BEGIN
jbe@532 2417 IF TG_OP = 'DELETE' THEN
jbe@532 2418 IF EXISTS (
jbe@532 2419 SELECT NULL FROM "member" WHERE "id" = OLD."truster_id"
jbe@532 2420 ) AND (CASE OLD."scope"
jbe@532 2421 WHEN 'unit'::"delegation_scope" THEN EXISTS (
jbe@532 2422 SELECT NULL FROM "unit" WHERE "id" = OLD."unit_id"
jbe@532 2423 )
jbe@532 2424 WHEN 'area'::"delegation_scope" THEN EXISTS (
jbe@532 2425 SELECT NULL FROM "area" WHERE "id" = OLD."area_id"
jbe@532 2426 )
jbe@532 2427 WHEN 'issue'::"delegation_scope" THEN EXISTS (
jbe@532 2428 SELECT NULL FROM "issue" WHERE "id" = OLD."issue_id"
jbe@532 2429 )
jbe@532 2430 END) THEN
jbe@532 2431 SELECT * INTO "issue_row" FROM "issue"
jbe@532 2432 WHERE "id" = OLD."issue_id" FOR SHARE;
jbe@532 2433 SELECT * INTO "area_row" FROM "area"
jbe@532 2434 WHERE "id" = COALESCE(OLD."area_id", "issue_row"."area_id")
jbe@532 2435 FOR SHARE;
jbe@532 2436 INSERT INTO "event" (
jbe@532 2437 "event", "member_id", "scope",
jbe@532 2438 "unit_id", "area_id", "issue_id", "state",
jbe@532 2439 "boolean_value"
jbe@532 2440 ) VALUES (
jbe@532 2441 'delegation', OLD."truster_id", OLD."scope",
jbe@532 2442 COALESCE(OLD."unit_id", "area_row"."unit_id"), "area_row"."id",
jbe@532 2443 OLD."issue_id", "issue_row"."state",
jbe@532 2444 FALSE
jbe@532 2445 );
jbe@532 2446 END IF;
jbe@532 2447 ELSE
jbe@532 2448 SELECT * INTO "issue_row" FROM "issue"
jbe@532 2449 WHERE "id" = NEW."issue_id" FOR SHARE;
jbe@532 2450 SELECT * INTO "area_row" FROM "area"
jbe@532 2451 WHERE "id" = COALESCE(NEW."area_id", "issue_row"."area_id")
jbe@532 2452 FOR SHARE;
jbe@532 2453 INSERT INTO "event" (
jbe@532 2454 "event", "member_id", "other_member_id", "scope",
jbe@532 2455 "unit_id", "area_id", "issue_id", "state",
jbe@532 2456 "boolean_value"
jbe@532 2457 ) VALUES (
jbe@532 2458 'delegation', NEW."truster_id", NEW."trustee_id", NEW."scope",
jbe@532 2459 COALESCE(NEW."unit_id", "area_row"."unit_id"), "area_row"."id",
jbe@532 2460 NEW."issue_id", "issue_row"."state",
jbe@532 2461 TRUE
jbe@532 2462 );
jbe@532 2463 END IF;
jbe@532 2464 RETURN NULL;
jbe@532 2465 END;
jbe@532 2466 $$;
jbe@532 2467
jbe@532 2468 CREATE TRIGGER "write_event_delegation"
jbe@532 2469 AFTER INSERT OR UPDATE OR DELETE ON "delegation" FOR EACH ROW EXECUTE PROCEDURE
jbe@532 2470 "write_event_delegation_trigger"();
jbe@532 2471
jbe@532 2472 COMMENT ON FUNCTION "write_event_delegation_trigger"() IS 'Implementation of trigger "write_event_delegation" on table "delegation"';
jbe@532 2473 COMMENT ON TRIGGER "write_event_delegation" ON "delegation" IS 'Create entry in "event" table when adding, updating, or removing a delegation';
jbe@532 2474
jbe@532 2475
jbe@532 2476 CREATE FUNCTION "write_event_contact_trigger"()
jbe@532 2477 RETURNS TRIGGER
jbe@532 2478 LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@532 2479 BEGIN
jbe@532 2480 IF TG_OP = 'UPDATE' THEN
jbe@532 2481 IF
jbe@532 2482 OLD."member_id" = NEW."member_id" AND
jbe@532 2483 OLD."other_member_id" = NEW."other_member_id" AND
jbe@532 2484 OLD."public" = NEW."public"
jbe@532 2485 THEN
jbe@532 2486 RETURN NULL;
jbe@532 2487 END IF;
jbe@532 2488 END IF;
jbe@532 2489 IF TG_OP = 'DELETE' OR TG_OP = 'UPDATE' THEN
jbe@532 2490 IF OLD."public" THEN
jbe@532 2491 IF EXISTS (
jbe@532 2492 SELECT NULL FROM "member" WHERE "id" = OLD."member_id"
jbe@532 2493 FOR SHARE
jbe@532 2494 ) AND EXISTS (
jbe@532 2495 SELECT NULL FROM "member" WHERE "id" = OLD."other_member_id"
jbe@532 2496 FOR SHARE
jbe@532 2497 ) THEN
jbe@532 2498 INSERT INTO "event" (
jbe@532 2499 "event", "member_id", "other_member_id", "boolean_value"
jbe@532 2500 ) VALUES (
jbe@532 2501 'contact', OLD."member_id", OLD."other_member_id", FALSE
jbe@532 2502 );
jbe@532 2503 END IF;
jbe@532 2504 END IF;
jbe@532 2505 END IF;
jbe@532 2506 IF TG_OP = 'INSERT' OR TG_OP = 'UPDATE' THEN
jbe@532 2507 IF NEW."public" THEN
jbe@532 2508 INSERT INTO "event" (
jbe@532 2509 "event", "member_id", "other_member_id", "boolean_value"
jbe@532 2510 ) VALUES (
jbe@532 2511 'contact', NEW."member_id", NEW."other_member_id", TRUE
jbe@532 2512 );
jbe@532 2513 END IF;
jbe@532 2514 END IF;
jbe@532 2515 RETURN NULL;
jbe@532 2516 END;
jbe@532 2517 $$;
jbe@532 2518
jbe@532 2519 CREATE TRIGGER "write_event_contact"
jbe@532 2520 AFTER INSERT OR UPDATE OR DELETE ON "contact" FOR EACH ROW EXECUTE PROCEDURE
jbe@532 2521 "write_event_contact_trigger"();
jbe@532 2522
jbe@532 2523 COMMENT ON FUNCTION "write_event_contact_trigger"() IS 'Implementation of trigger "write_event_contact" on table "contact"';
jbe@532 2524 COMMENT ON TRIGGER "write_event_contact" ON "contact" IS 'Create entry in "event" table when adding or removing public contacts';
jbe@532 2525
jbe@532 2526
jbe@532 2527 CREATE FUNCTION "send_event_notify_trigger"()
jbe@532 2528 RETURNS TRIGGER
jbe@532 2529 LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@532 2530 BEGIN
jbe@532 2531 EXECUTE 'NOTIFY "event", ''' || NEW."event" || '''';
jbe@532 2532 RETURN NULL;
jbe@532 2533 END;
jbe@532 2534 $$;
jbe@532 2535
jbe@532 2536 CREATE TRIGGER "send_notify"
jbe@532 2537 AFTER INSERT OR UPDATE ON "event" FOR EACH ROW EXECUTE PROCEDURE
jbe@532 2538 "send_event_notify_trigger"();
jbe@532 2539
jbe@532 2540
jbe@13 2541
jbe@0 2542 ----------------------------
jbe@0 2543 -- Additional constraints --
jbe@0 2544 ----------------------------
jbe@0 2545
jbe@0 2546
jbe@532 2547 CREATE FUNCTION "delete_extended_scope_tokens_trigger"()
jbe@532 2548 RETURNS TRIGGER
jbe@532 2549 LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@532 2550 DECLARE
jbe@532 2551 "system_application_row" "system_application"%ROWTYPE;
jbe@532 2552 BEGIN
jbe@532 2553 IF OLD."system_application_id" NOTNULL THEN
jbe@532 2554 SELECT * FROM "system_application" INTO "system_application_row"
jbe@532 2555 WHERE "id" = OLD."system_application_id";
jbe@532 2556 DELETE FROM "token"
jbe@532 2557 WHERE "member_id" = OLD."member_id"
jbe@532 2558 AND "system_application_id" = OLD."system_application_id"
jbe@532 2559 AND NOT COALESCE(
jbe@532 2560 regexp_split_to_array("scope", E'\\s+') <@
jbe@532 2561 regexp_split_to_array(
jbe@532 2562 "system_application_row"."automatic_scope", E'\\s+'
jbe@532 2563 ),
jbe@532 2564 FALSE
jbe@532 2565 );
jbe@532 2566 END IF;
jbe@532 2567 RETURN OLD;
jbe@532 2568 END;
jbe@532 2569 $$;
jbe@532 2570
jbe@532 2571 CREATE TRIGGER "delete_extended_scope_tokens"
jbe@532 2572 BEFORE DELETE ON "member_application" FOR EACH ROW EXECUTE PROCEDURE
jbe@532 2573 "delete_extended_scope_tokens_trigger"();
jbe@532 2574
jbe@532 2575
jbe@532 2576 CREATE FUNCTION "detach_token_from_session_trigger"()
jbe@532 2577 RETURNS TRIGGER
jbe@532 2578 LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@532 2579 BEGIN
jbe@532 2580 UPDATE "token" SET "session_id" = NULL
jbe@532 2581 WHERE "session_id" = OLD."id";
jbe@532 2582 RETURN OLD;
jbe@532 2583 END;
jbe@532 2584 $$;
jbe@532 2585
jbe@532 2586 CREATE TRIGGER "detach_token_from_session"
jbe@532 2587 BEFORE DELETE ON "session" FOR EACH ROW EXECUTE PROCEDURE
jbe@532 2588 "detach_token_from_session_trigger"();
jbe@532 2589
jbe@532 2590
jbe@532 2591 CREATE FUNCTION "delete_non_detached_scope_with_session_trigger"()
jbe@532 2592 RETURNS TRIGGER
jbe@532 2593 LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@532 2594 BEGIN
jbe@532 2595 IF NEW."session_id" ISNULL THEN
jbe@532 2596 SELECT coalesce(string_agg("element", ' '), '') INTO NEW."scope"
jbe@532 2597 FROM unnest(regexp_split_to_array(NEW."scope", E'\\s+')) AS "element"
jbe@532 2598 WHERE "element" LIKE '%_detached';
jbe@532 2599 END IF;
jbe@532 2600 RETURN NEW;
jbe@532 2601 END;
jbe@532 2602 $$;
jbe@532 2603
jbe@532 2604 CREATE TRIGGER "delete_non_detached_scope_with_session"
jbe@532 2605 BEFORE INSERT OR UPDATE ON "token" FOR EACH ROW EXECUTE PROCEDURE
jbe@532 2606 "delete_non_detached_scope_with_session_trigger"();
jbe@532 2607
jbe@532 2608
jbe@532 2609 CREATE FUNCTION "delete_token_with_empty_scope_trigger"()
jbe@532 2610 RETURNS TRIGGER
jbe@532 2611 LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@532 2612 BEGIN
jbe@532 2613 IF NEW."scope" = '' THEN
jbe@532 2614 DELETE FROM "token" WHERE "id" = NEW."id";
jbe@532 2615 END IF;
jbe@532 2616 RETURN NULL;
jbe@532 2617 END;
jbe@532 2618 $$;
jbe@532 2619
jbe@532 2620 CREATE TRIGGER "delete_token_with_empty_scope"
jbe@532 2621 AFTER INSERT OR UPDATE ON "token" FOR EACH ROW EXECUTE PROCEDURE
jbe@532 2622 "delete_token_with_empty_scope_trigger"();
jbe@532 2623
jbe@532 2624
jbe@0 2625 CREATE FUNCTION "issue_requires_first_initiative_trigger"()
jbe@0 2626 RETURNS TRIGGER
jbe@0 2627 LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@0 2628 BEGIN
jbe@0 2629 IF NOT EXISTS (
jbe@0 2630 SELECT NULL FROM "initiative" WHERE "issue_id" = NEW."id"
jbe@0 2631 ) THEN
jbe@463 2632 RAISE EXCEPTION 'Cannot create issue without an initial initiative.' USING
jbe@463 2633 ERRCODE = 'integrity_constraint_violation',
jbe@463 2634 HINT = 'Create issue, initiative, and draft within the same transaction.';
jbe@0 2635 END IF;
jbe@0 2636 RETURN NULL;
jbe@0 2637 END;
jbe@0 2638 $$;
jbe@0 2639
jbe@0 2640 CREATE CONSTRAINT TRIGGER "issue_requires_first_initiative"
jbe@0 2641 AFTER INSERT OR UPDATE ON "issue" DEFERRABLE INITIALLY DEFERRED
jbe@0 2642 FOR EACH ROW EXECUTE PROCEDURE
jbe@0 2643 "issue_requires_first_initiative_trigger"();
jbe@0 2644
jbe@0 2645 COMMENT ON FUNCTION "issue_requires_first_initiative_trigger"() IS 'Implementation of trigger "issue_requires_first_initiative" on table "issue"';
jbe@0 2646 COMMENT ON TRIGGER "issue_requires_first_initiative" ON "issue" IS 'Ensure that new issues have at least one initiative';
jbe@0 2647
jbe@0 2648
jbe@0 2649 CREATE FUNCTION "last_initiative_deletes_issue_trigger"()
jbe@0 2650 RETURNS TRIGGER
jbe@0 2651 LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@0 2652 DECLARE
jbe@0 2653 "reference_lost" BOOLEAN;
jbe@0 2654 BEGIN
jbe@0 2655 IF TG_OP = 'DELETE' THEN
jbe@0 2656 "reference_lost" := TRUE;
jbe@0 2657 ELSE
jbe@0 2658 "reference_lost" := NEW."issue_id" != OLD."issue_id";
jbe@0 2659 END IF;
jbe@0 2660 IF
jbe@0 2661 "reference_lost" AND NOT EXISTS (
jbe@0 2662 SELECT NULL FROM "initiative" WHERE "issue_id" = OLD."issue_id"
jbe@0 2663 )
jbe@0 2664 THEN
jbe@0 2665 DELETE FROM "issue" WHERE "id" = OLD."issue_id";
jbe@0 2666 END IF;
jbe@0 2667 RETURN NULL;
jbe@0 2668 END;
jbe@0 2669 $$;
jbe@0 2670
jbe@0 2671 CREATE CONSTRAINT TRIGGER "last_initiative_deletes_issue"
jbe@0 2672 AFTER UPDATE OR DELETE ON "initiative" DEFERRABLE INITIALLY DEFERRED
jbe@0 2673 FOR EACH ROW EXECUTE PROCEDURE
jbe@0 2674 "last_initiative_deletes_issue_trigger"();
jbe@0 2675
jbe@0 2676 COMMENT ON FUNCTION "last_initiative_deletes_issue_trigger"() IS 'Implementation of trigger "last_initiative_deletes_issue" on table "initiative"';
jbe@0 2677 COMMENT ON TRIGGER "last_initiative_deletes_issue" ON "initiative" IS 'Removing the last initiative of an issue deletes the issue';
jbe@0 2678
jbe@0 2679
jbe@0 2680 CREATE FUNCTION "initiative_requires_first_draft_trigger"()
jbe@0 2681 RETURNS TRIGGER
jbe@0 2682 LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@0 2683 BEGIN
jbe@0 2684 IF NOT EXISTS (
jbe@0 2685 SELECT NULL FROM "draft" WHERE "initiative_id" = NEW."id"
jbe@0 2686 ) THEN
jbe@463 2687 RAISE EXCEPTION 'Cannot create initiative without an initial draft.' USING
jbe@463 2688 ERRCODE = 'integrity_constraint_violation',
jbe@463 2689 HINT = 'Create issue, initiative and draft within the same transaction.';
jbe@0 2690 END IF;
jbe@0 2691 RETURN NULL;
jbe@0 2692 END;
jbe@0 2693 $$;
jbe@0 2694
jbe@0 2695 CREATE CONSTRAINT TRIGGER "initiative_requires_first_draft"
jbe@0 2696 AFTER INSERT OR UPDATE ON "initiative" DEFERRABLE INITIALLY DEFERRED
jbe@0 2697 FOR EACH ROW EXECUTE PROCEDURE
jbe@0 2698 "initiative_requires_first_draft_trigger"();
jbe@0 2699
jbe@0 2700 COMMENT ON FUNCTION "initiative_requires_first_draft_trigger"() IS 'Implementation of trigger "initiative_requires_first_draft" on table "initiative"';
jbe@0 2701 COMMENT ON TRIGGER "initiative_requires_first_draft" ON "initiative" IS 'Ensure that new initiatives have at least one draft';
jbe@0 2702
jbe@0 2703
jbe@0 2704 CREATE FUNCTION "last_draft_deletes_initiative_trigger"()
jbe@0 2705 RETURNS TRIGGER
jbe@0 2706 LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@0 2707 DECLARE
jbe@0 2708 "reference_lost" BOOLEAN;
jbe@0 2709 BEGIN
jbe@0 2710 IF TG_OP = 'DELETE' THEN
jbe@0 2711 "reference_lost" := TRUE;
jbe@0 2712 ELSE
jbe@0 2713 "reference_lost" := NEW."initiative_id" != OLD."initiative_id";
jbe@0 2714 END IF;
jbe@0 2715 IF
jbe@0 2716 "reference_lost" AND NOT EXISTS (
jbe@0 2717 SELECT NULL FROM "draft" WHERE "initiative_id" = OLD."initiative_id"
jbe@0 2718 )
jbe@0 2719 THEN
jbe@0 2720 DELETE FROM "initiative" WHERE "id" = OLD."initiative_id";
jbe@0 2721 END IF;
jbe@0 2722 RETURN NULL;
jbe@0 2723 END;
jbe@0 2724 $$;
jbe@0 2725
jbe@0 2726 CREATE CONSTRAINT TRIGGER "last_draft_deletes_initiative"
jbe@0 2727 AFTER UPDATE OR DELETE ON "draft" DEFERRABLE INITIALLY DEFERRED
jbe@0 2728 FOR EACH ROW EXECUTE PROCEDURE
jbe@0 2729 "last_draft_deletes_initiative_trigger"();
jbe@0 2730
jbe@0 2731 COMMENT ON FUNCTION "last_draft_deletes_initiative_trigger"() IS 'Implementation of trigger "last_draft_deletes_initiative" on table "draft"';
jbe@0 2732 COMMENT ON TRIGGER "last_draft_deletes_initiative" ON "draft" IS 'Removing the last draft of an initiative deletes the initiative';
jbe@0 2733
jbe@0 2734
jbe@0 2735 CREATE FUNCTION "suggestion_requires_first_opinion_trigger"()
jbe@0 2736 RETURNS TRIGGER
jbe@0 2737 LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@0 2738 BEGIN
jbe@0 2739 IF NOT EXISTS (
jbe@0 2740 SELECT NULL FROM "opinion" WHERE "suggestion_id" = NEW."id"
jbe@0 2741 ) THEN
jbe@463 2742 RAISE EXCEPTION 'Cannot create a suggestion without an opinion.' USING
jbe@463 2743 ERRCODE = 'integrity_constraint_violation',
jbe@463 2744 HINT = 'Create suggestion and opinion within the same transaction.';
jbe@0 2745 END IF;
jbe@0 2746 RETURN NULL;
jbe@0 2747 END;
jbe@0 2748 $$;
jbe@0 2749
jbe@0 2750 CREATE CONSTRAINT TRIGGER "suggestion_requires_first_opinion"
jbe@0 2751 AFTER INSERT OR UPDATE ON "suggestion" DEFERRABLE INITIALLY DEFERRED
jbe@0 2752 FOR EACH ROW EXECUTE PROCEDURE
jbe@0 2753 "suggestion_requires_first_opinion_trigger"();
jbe@0 2754
jbe@0 2755 COMMENT ON FUNCTION "suggestion_requires_first_opinion_trigger"() IS 'Implementation of trigger "suggestion_requires_first_opinion" on table "suggestion"';
jbe@0 2756 COMMENT ON TRIGGER "suggestion_requires_first_opinion" ON "suggestion" IS 'Ensure that new suggestions have at least one opinion';
jbe@0 2757
jbe@0 2758
jbe@0 2759 CREATE FUNCTION "last_opinion_deletes_suggestion_trigger"()
jbe@0 2760 RETURNS TRIGGER
jbe@0 2761 LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@0 2762 DECLARE
jbe@0 2763 "reference_lost" BOOLEAN;
jbe@0 2764 BEGIN
jbe@0 2765 IF TG_OP = 'DELETE' THEN
jbe@0 2766 "reference_lost" := TRUE;
jbe@0 2767 ELSE
jbe@0 2768 "reference_lost" := NEW."suggestion_id" != OLD."suggestion_id";
jbe@0 2769 END IF;
jbe@0 2770 IF
jbe@0 2771 "reference_lost" AND NOT EXISTS (
jbe@0 2772 SELECT NULL FROM "opinion" WHERE "suggestion_id" = OLD."suggestion_id"
jbe@0 2773 )
jbe@0 2774 THEN
jbe@0 2775 DELETE FROM "suggestion" WHERE "id" = OLD."suggestion_id";
jbe@0 2776 END IF;
jbe@0 2777 RETURN NULL;
jbe@0 2778 END;
jbe@0 2779 $$;
jbe@0 2780
jbe@0 2781 CREATE CONSTRAINT TRIGGER "last_opinion_deletes_suggestion"
jbe@0 2782 AFTER UPDATE OR DELETE ON "opinion" DEFERRABLE INITIALLY DEFERRED
jbe@0 2783 FOR EACH ROW EXECUTE PROCEDURE
jbe@0 2784 "last_opinion_deletes_suggestion_trigger"();
jbe@0 2785
jbe@0 2786 COMMENT ON FUNCTION "last_opinion_deletes_suggestion_trigger"() IS 'Implementation of trigger "last_opinion_deletes_suggestion" on table "opinion"';
jbe@0 2787 COMMENT ON TRIGGER "last_opinion_deletes_suggestion" ON "opinion" IS 'Removing the last opinion of a suggestion deletes the suggestion';
jbe@0 2788
jbe@0 2789
jbe@284 2790 CREATE FUNCTION "non_voter_deletes_direct_voter_trigger"()
jbe@284 2791 RETURNS TRIGGER
jbe@284 2792 LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@284 2793 BEGIN
jbe@284 2794 DELETE FROM "direct_voter"
jbe@284 2795 WHERE "issue_id" = NEW."issue_id" AND "member_id" = NEW."member_id";
jbe@284 2796 RETURN NULL;
jbe@284 2797 END;
jbe@284 2798 $$;
jbe@284 2799
jbe@284 2800 CREATE TRIGGER "non_voter_deletes_direct_voter"
jbe@284 2801 AFTER INSERT OR UPDATE ON "non_voter"
jbe@284 2802 FOR EACH ROW EXECUTE PROCEDURE
jbe@284 2803 "non_voter_deletes_direct_voter_trigger"();
jbe@284 2804
jbe@284 2805 COMMENT ON FUNCTION "non_voter_deletes_direct_voter_trigger"() IS 'Implementation of trigger "non_voter_deletes_direct_voter" on table "non_voter"';
jbe@284 2806 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 2807
jbe@284 2808
jbe@284 2809 CREATE FUNCTION "direct_voter_deletes_non_voter_trigger"()
jbe@284 2810 RETURNS TRIGGER
jbe@284 2811 LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@284 2812 BEGIN
jbe@284 2813 DELETE FROM "non_voter"
jbe@284 2814 WHERE "issue_id" = NEW."issue_id" AND "member_id" = NEW."member_id";
jbe@284 2815 RETURN NULL;
jbe@284 2816 END;
jbe@284 2817 $$;
jbe@284 2818
jbe@284 2819 CREATE TRIGGER "direct_voter_deletes_non_voter"
jbe@284 2820 AFTER INSERT OR UPDATE ON "direct_voter"
jbe@284 2821 FOR EACH ROW EXECUTE PROCEDURE
jbe@284 2822 "direct_voter_deletes_non_voter_trigger"();
jbe@284 2823
jbe@284 2824 COMMENT ON FUNCTION "direct_voter_deletes_non_voter_trigger"() IS 'Implementation of trigger "direct_voter_deletes_non_voter" on table "direct_voter"';
jbe@284 2825 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 2826
jbe@284 2827
jbe@285 2828 CREATE FUNCTION "voter_comment_fields_only_set_when_voter_comment_is_set_trigger"()
jbe@285 2829 RETURNS TRIGGER
jbe@285 2830 LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@285 2831 BEGIN
jbe@285 2832 IF NEW."comment" ISNULL THEN
jbe@285 2833 NEW."comment_changed" := NULL;
jbe@285 2834 NEW."formatting_engine" := NULL;
jbe@285 2835 END IF;
jbe@285 2836 RETURN NEW;
jbe@285 2837 END;
jbe@285 2838 $$;
jbe@285 2839
jbe@285 2840 CREATE TRIGGER "voter_comment_fields_only_set_when_voter_comment_is_set"
jbe@285 2841 BEFORE INSERT OR UPDATE ON "direct_voter"
jbe@285 2842 FOR EACH ROW EXECUTE PROCEDURE
jbe@285 2843 "voter_comment_fields_only_set_when_voter_comment_is_set_trigger"();
jbe@285 2844
jbe@285 2845 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 2846 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 2847
jbe@0 2848
jbe@528 2849
jbe@528 2850 ---------------------------------
jbe@528 2851 -- Delete incomplete snapshots --
jbe@528 2852 ---------------------------------
jbe@528 2853
jbe@528 2854
jbe@528 2855 CREATE FUNCTION "delete_snapshot_on_partial_delete_trigger"()
jbe@528 2856 RETURNS TRIGGER
jbe@528 2857 LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@528 2858 BEGIN
jbe@532 2859 IF TG_OP = 'UPDATE' THEN
jbe@532 2860 IF
jbe@532 2861 OLD."snapshot_id" = NEW."snapshot_id" AND
jbe@532 2862 OLD."issue_id" = NEW."issue_id"
jbe@532 2863 THEN
jbe@532 2864 RETURN NULL;
jbe@532 2865 END IF;
jbe@532 2866 END IF;
jbe@528 2867 DELETE FROM "snapshot" WHERE "id" = OLD."snapshot_id";
jbe@528 2868 RETURN NULL;
jbe@528 2869 END;
jbe@528 2870 $$;
jbe@528 2871
jbe@528 2872 CREATE TRIGGER "delete_snapshot_on_partial_delete"
jbe@532 2873 AFTER UPDATE OR DELETE ON "snapshot_issue"
jbe@528 2874 FOR EACH ROW EXECUTE PROCEDURE
jbe@528 2875 "delete_snapshot_on_partial_delete_trigger"();
jbe@528 2876
jbe@528 2877 COMMENT ON FUNCTION "delete_snapshot_on_partial_delete_trigger"() IS 'Implementation of trigger "delete_snapshot_on_partial_delete" on table "snapshot_issue"';
jbe@528 2878 COMMENT ON TRIGGER "delete_snapshot_on_partial_delete" ON "snapshot_issue" IS 'Deletes whole snapshot if one issue is deleted from the snapshot';
jbe@528 2879
jbe@528 2880
jbe@528 2881
jbe@20 2882 ---------------------------------------------------------------
jbe@333 2883 -- Ensure that votes are not modified when issues are closed --
jbe@20 2884 ---------------------------------------------------------------
jbe@20 2885
jbe@20 2886 -- NOTE: Frontends should ensure this anyway, but in case of programming
jbe@532 2887 -- errors the following triggers ensure data integrity.
jbe@20 2888
jbe@20 2889
jbe@20 2890 CREATE FUNCTION "forbid_changes_on_closed_issue_trigger"()
jbe@20 2891 RETURNS TRIGGER
jbe@20 2892 LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@20 2893 DECLARE
jbe@336 2894 "issue_id_v" "issue"."id"%TYPE;
jbe@336 2895 "issue_row" "issue"%ROWTYPE;
jbe@20 2896 BEGIN
jbe@383 2897 IF EXISTS (
jbe@385 2898 SELECT NULL FROM "temporary_transaction_data"
jbe@385 2899 WHERE "txid" = txid_current()
jbe@383 2900 AND "key" = 'override_protection_triggers'
jbe@383 2901 AND "value" = TRUE::TEXT
jbe@383 2902 ) THEN
jbe@383 2903 RETURN NULL;
jbe@383 2904 END IF;
jbe@32 2905 IF TG_OP = 'DELETE' THEN
jbe@32 2906 "issue_id_v" := OLD."issue_id";
jbe@32 2907 ELSE
jbe@32 2908 "issue_id_v" := NEW."issue_id";
jbe@32 2909 END IF;
jbe@20 2910 SELECT INTO "issue_row" * FROM "issue"
jbe@32 2911 WHERE "id" = "issue_id_v" FOR SHARE;
jbe@383 2912 IF (
jbe@383 2913 "issue_row"."closed" NOTNULL OR (
jbe@383 2914 "issue_row"."state" = 'voting' AND
jbe@383 2915 "issue_row"."phase_finished" NOTNULL
jbe@383 2916 )
jbe@383 2917 ) THEN
jbe@332 2918 IF
jbe@332 2919 TG_RELID = 'direct_voter'::regclass AND
jbe@332 2920 TG_OP = 'UPDATE'
jbe@332 2921 THEN
jbe@332 2922 IF
jbe@332 2923 OLD."issue_id" = NEW."issue_id" AND
jbe@332 2924 OLD."member_id" = NEW."member_id" AND
jbe@332 2925 OLD."weight" = NEW."weight"
jbe@332 2926 THEN
jbe@332 2927 RETURN NULL; -- allows changing of voter comment
jbe@332 2928 END IF;
jbe@332 2929 END IF;
jbe@463 2930 RAISE EXCEPTION 'Tried to modify data after voting has been closed.' USING
jbe@463 2931 ERRCODE = 'integrity_constraint_violation';
jbe@20 2932 END IF;
jbe@20 2933 RETURN NULL;
jbe@20 2934 END;
jbe@20 2935 $$;
jbe@20 2936
jbe@20 2937 CREATE TRIGGER "forbid_changes_on_closed_issue"
jbe@20 2938 AFTER INSERT OR UPDATE OR DELETE ON "direct_voter"
jbe@20 2939 FOR EACH ROW EXECUTE PROCEDURE
jbe@20 2940 "forbid_changes_on_closed_issue_trigger"();
jbe@20 2941
jbe@20 2942 CREATE TRIGGER "forbid_changes_on_closed_issue"
jbe@20 2943 AFTER INSERT OR UPDATE OR DELETE ON "delegating_voter"
jbe@20 2944 FOR EACH ROW EXECUTE PROCEDURE
jbe@20 2945 "forbid_changes_on_closed_issue_trigger"();
jbe@20 2946
jbe@20 2947 CREATE TRIGGER "forbid_changes_on_closed_issue"
jbe@20 2948 AFTER INSERT OR UPDATE OR DELETE ON "vote"
jbe@20 2949 FOR EACH ROW EXECUTE PROCEDURE
jbe@20 2950 "forbid_changes_on_closed_issue_trigger"();
jbe@20 2951
jbe@20 2952 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 2953 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 2954 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 2955 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 2956
jbe@20 2957
jbe@20 2958
jbe@0 2959 --------------------------------------------------------------------
jbe@0 2960 -- Auto-retrieval of fields only needed for referential integrity --
jbe@0 2961 --------------------------------------------------------------------
jbe@0 2962
jbe@20 2963
jbe@0 2964 CREATE FUNCTION "autofill_issue_id_trigger"()
jbe@0 2965 RETURNS TRIGGER
jbe@0 2966 LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@0 2967 BEGIN
jbe@0 2968 IF NEW."issue_id" ISNULL THEN
jbe@0 2969 SELECT "issue_id" INTO NEW."issue_id"
jbe@0 2970 FROM "initiative" WHERE "id" = NEW."initiative_id";
jbe@0 2971 END IF;
jbe@0 2972 RETURN NEW;
jbe@0 2973 END;
jbe@0 2974 $$;
jbe@0 2975
jbe@0 2976 CREATE TRIGGER "autofill_issue_id" BEFORE INSERT ON "supporter"
jbe@0 2977 FOR EACH ROW EXECUTE PROCEDURE "autofill_issue_id_trigger"();
jbe@0 2978
jbe@0 2979 CREATE TRIGGER "autofill_issue_id" BEFORE INSERT ON "vote"
jbe@0 2980 FOR EACH ROW EXECUTE PROCEDURE "autofill_issue_id_trigger"();
jbe@0 2981
jbe@0 2982 COMMENT ON FUNCTION "autofill_issue_id_trigger"() IS 'Implementation of triggers "autofill_issue_id" on tables "supporter" and "vote"';
jbe@0 2983 COMMENT ON TRIGGER "autofill_issue_id" ON "supporter" IS 'Set "issue_id" field automatically, if NULL';
jbe@0 2984 COMMENT ON TRIGGER "autofill_issue_id" ON "vote" IS 'Set "issue_id" field automatically, if NULL';
jbe@0 2985
jbe@0 2986
jbe@0 2987 CREATE FUNCTION "autofill_initiative_id_trigger"()
jbe@0 2988 RETURNS TRIGGER
jbe@0 2989 LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@0 2990 BEGIN
jbe@0 2991 IF NEW."initiative_id" ISNULL THEN
jbe@0 2992 SELECT "initiative_id" INTO NEW."initiative_id"
jbe@0 2993 FROM "suggestion" WHERE "id" = NEW."suggestion_id";
jbe@0 2994 END IF;
jbe@0 2995 RETURN NEW;
jbe@0 2996 END;
jbe@0 2997 $$;
jbe@0 2998
jbe@0 2999 CREATE TRIGGER "autofill_initiative_id" BEFORE INSERT ON "opinion"
jbe@0 3000 FOR EACH ROW EXECUTE PROCEDURE "autofill_initiative_id_trigger"();
jbe@0 3001
jbe@0 3002 COMMENT ON FUNCTION "autofill_initiative_id_trigger"() IS 'Implementation of trigger "autofill_initiative_id" on table "opinion"';
jbe@0 3003 COMMENT ON TRIGGER "autofill_initiative_id" ON "opinion" IS 'Set "initiative_id" field automatically, if NULL';
jbe@0 3004
jbe@0 3005
jbe@0 3006
jbe@528 3007 -------------------------------------------------------
jbe@528 3008 -- Automatic copying of values for indexing purposes --
jbe@528 3009 -------------------------------------------------------
jbe@528 3010
jbe@528 3011
jbe@528 3012 CREATE FUNCTION "copy_current_draft_data"
jbe@528 3013 ("initiative_id_p" "initiative"."id"%TYPE )
jbe@528 3014 RETURNS VOID
jbe@528 3015 LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@528 3016 BEGIN
jbe@528 3017 PERFORM NULL FROM "initiative" WHERE "id" = "initiative_id_p"
jbe@528 3018 FOR UPDATE;
jbe@528 3019 UPDATE "initiative" SET
jbe@532 3020 "location" = "draft"."location",
jbe@528 3021 "draft_text_search_data" = "draft"."text_search_data"
jbe@528 3022 FROM "current_draft" AS "draft"
jbe@528 3023 WHERE "initiative"."id" = "initiative_id_p"
jbe@528 3024 AND "draft"."initiative_id" = "initiative_id_p";
jbe@528 3025 END;
jbe@528 3026 $$;
jbe@528 3027
jbe@528 3028 COMMENT ON FUNCTION "copy_current_draft_data"
jbe@528 3029 ( "initiative"."id"%TYPE )
jbe@528 3030 IS 'Helper function for function "copy_current_draft_data_trigger"';
jbe@528 3031
jbe@528 3032
jbe@528 3033 CREATE FUNCTION "copy_current_draft_data_trigger"()
jbe@528 3034 RETURNS TRIGGER
jbe@528 3035 LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@528 3036 BEGIN
jbe@528 3037 IF TG_OP='DELETE' THEN
jbe@528 3038 PERFORM "copy_current_draft_data"(OLD."initiative_id");
jbe@528 3039 ELSE
jbe@528 3040 IF TG_OP='UPDATE' THEN
jbe@528 3041 IF COALESCE(OLD."inititiave_id" != NEW."initiative_id", TRUE) THEN
jbe@528 3042 PERFORM "copy_current_draft_data"(OLD."initiative_id");
jbe@528 3043 END IF;
jbe@528 3044 END IF;
jbe@528 3045 PERFORM "copy_current_draft_data"(NEW."initiative_id");
jbe@528 3046 END IF;
jbe@528 3047 RETURN NULL;
jbe@528 3048 END;
jbe@528 3049 $$;
jbe@528 3050
jbe@528 3051 CREATE TRIGGER "copy_current_draft_data"
jbe@528 3052 AFTER INSERT OR UPDATE OR DELETE ON "draft"
jbe@528 3053 FOR EACH ROW EXECUTE PROCEDURE
jbe@528 3054 "copy_current_draft_data_trigger"();
jbe@528 3055
jbe@528 3056 COMMENT ON FUNCTION "copy_current_draft_data_trigger"() IS 'Implementation of trigger "copy_current_draft_data" on table "draft"';
jbe@528 3057 COMMENT ON TRIGGER "copy_current_draft_data" ON "draft" IS 'Copy certain fields from most recent "draft" to "initiative"';
jbe@528 3058
jbe@528 3059
jbe@528 3060
jbe@4 3061 -----------------------------------------------------
jbe@4 3062 -- Automatic calculation of certain default values --
jbe@4 3063 -----------------------------------------------------
jbe@0 3064
jbe@22 3065
jbe@22 3066 CREATE FUNCTION "copy_timings_trigger"()
jbe@22 3067 RETURNS TRIGGER
jbe@22 3068 LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@22 3069 DECLARE
jbe@22 3070 "policy_row" "policy"%ROWTYPE;
jbe@22 3071 BEGIN
jbe@22 3072 SELECT * INTO "policy_row" FROM "policy"
jbe@22 3073 WHERE "id" = NEW."policy_id";
jbe@447 3074 IF NEW."min_admission_time" ISNULL THEN
jbe@447 3075 NEW."min_admission_time" := "policy_row"."min_admission_time";
jbe@447 3076 END IF;
jbe@447 3077 IF NEW."max_admission_time" ISNULL THEN
jbe@447 3078 NEW."max_admission_time" := "policy_row"."max_admission_time";
jbe@22 3079 END IF;
jbe@22 3080 IF NEW."discussion_time" ISNULL THEN
jbe@22 3081 NEW."discussion_time" := "policy_row"."discussion_time";
jbe@22 3082 END IF;
jbe@22 3083 IF NEW."verification_time" ISNULL THEN
jbe@22 3084 NEW."verification_time" := "policy_row"."verification_time";
jbe@22 3085 END IF;
jbe@22 3086 IF NEW."voting_time" ISNULL THEN
jbe@22 3087 NEW."voting_time" := "policy_row"."voting_time";
jbe@22 3088 END IF;
jbe@22 3089 RETURN NEW;
jbe@22 3090 END;
jbe@22 3091 $$;
jbe@22 3092
jbe@22 3093 CREATE TRIGGER "copy_timings" BEFORE INSERT OR UPDATE ON "issue"
jbe@22 3094 FOR EACH ROW EXECUTE PROCEDURE "copy_timings_trigger"();
jbe@22 3095
jbe@22 3096 COMMENT ON FUNCTION "copy_timings_trigger"() IS 'Implementation of trigger "copy_timings" on table "issue"';
jbe@22 3097 COMMENT ON TRIGGER "copy_timings" ON "issue" IS 'If timing fields are NULL, copy values from policy.';
jbe@22 3098
jbe@22 3099
jbe@160 3100 CREATE FUNCTION "default_for_draft_id_trigger"()
jbe@2 3101 RETURNS TRIGGER
jbe@2 3102 LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@2 3103 BEGIN
jbe@2 3104 IF NEW."draft_id" ISNULL THEN
jbe@2 3105 SELECT "id" INTO NEW."draft_id" FROM "current_draft"
jbe@2 3106 WHERE "initiative_id" = NEW."initiative_id";
jbe@2 3107 END IF;
jbe@2 3108 RETURN NEW;
jbe@2 3109 END;
jbe@2 3110 $$;
jbe@2 3111
jbe@160 3112 CREATE TRIGGER "default_for_draft_id" BEFORE INSERT OR UPDATE ON "suggestion"
jbe@160 3113 FOR EACH ROW EXECUTE PROCEDURE "default_for_draft_id_trigger"();
jbe@2 3114 CREATE TRIGGER "default_for_draft_id" BEFORE INSERT OR UPDATE ON "supporter"
jbe@160 3115 FOR EACH ROW EXECUTE PROCEDURE "default_for_draft_id_trigger"();
jbe@160 3116
jbe@160 3117 COMMENT ON FUNCTION "default_for_draft_id_trigger"() IS 'Implementation of trigger "default_for_draft" on tables "supporter" and "suggestion"';
jbe@160 3118 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 3119 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 3120
jbe@2 3121
jbe@0 3122
jbe@0 3123 ----------------------------------------
jbe@0 3124 -- Automatic creation of dependencies --
jbe@0 3125 ----------------------------------------
jbe@0 3126
jbe@22 3127
jbe@0 3128 CREATE FUNCTION "autocreate_interest_trigger"()
jbe@0 3129 RETURNS TRIGGER
jbe@0 3130 LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@0 3131 BEGIN
jbe@0 3132 IF NOT EXISTS (
jbe@0 3133 SELECT NULL FROM "initiative" JOIN "interest"
jbe@0 3134 ON "initiative"."issue_id" = "interest"."issue_id"
jbe@0 3135 WHERE "initiative"."id" = NEW."initiative_id"
jbe@0 3136 AND "interest"."member_id" = NEW."member_id"
jbe@0 3137 ) THEN
jbe@0 3138 BEGIN
jbe@0 3139 INSERT INTO "interest" ("issue_id", "member_id")
jbe@0 3140 SELECT "issue_id", NEW."member_id"
jbe@0 3141 FROM "initiative" WHERE "id" = NEW."initiative_id";
jbe@0 3142 EXCEPTION WHEN unique_violation THEN END;
jbe@0 3143 END IF;
jbe@0 3144 RETURN NEW;
jbe@0 3145 END;
jbe@0 3146 $$;
jbe@0 3147
jbe@0 3148 CREATE TRIGGER "autocreate_interest" BEFORE INSERT ON "supporter"
jbe@0 3149 FOR EACH ROW EXECUTE PROCEDURE "autocreate_interest_trigger"();
jbe@0 3150
jbe@0 3151 COMMENT ON FUNCTION "autocreate_interest_trigger"() IS 'Implementation of trigger "autocreate_interest" on table "supporter"';
jbe@0 3152 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 3153
jbe@0 3154
jbe@0 3155 CREATE FUNCTION "autocreate_supporter_trigger"()
jbe@0 3156 RETURNS TRIGGER
jbe@0 3157 LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@0 3158 BEGIN
jbe@0 3159 IF NOT EXISTS (
jbe@0 3160 SELECT NULL FROM "suggestion" JOIN "supporter"
jbe@0 3161 ON "suggestion"."initiative_id" = "supporter"."initiative_id"
jbe@0 3162 WHERE "suggestion"."id" = NEW."suggestion_id"
jbe@0 3163 AND "supporter"."member_id" = NEW."member_id"
jbe@0 3164 ) THEN
jbe@0 3165 BEGIN
jbe@0 3166 INSERT INTO "supporter" ("initiative_id", "member_id")
jbe@0 3167 SELECT "initiative_id", NEW."member_id"
jbe@0 3168 FROM "suggestion" WHERE "id" = NEW."suggestion_id";
jbe@0 3169 EXCEPTION WHEN unique_violation THEN END;
jbe@0 3170 END IF;
jbe@0 3171 RETURN NEW;
jbe@0 3172 END;
jbe@0 3173 $$;
jbe@0 3174
jbe@0 3175 CREATE TRIGGER "autocreate_supporter" BEFORE INSERT ON "opinion"
jbe@0 3176 FOR EACH ROW EXECUTE PROCEDURE "autocreate_supporter_trigger"();
jbe@0 3177
jbe@0 3178 COMMENT ON FUNCTION "autocreate_supporter_trigger"() IS 'Implementation of trigger "autocreate_supporter" on table "opinion"';
jbe@0 3179 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 3180
jbe@0 3181
jbe@0 3182
jbe@0 3183 ------------------------------------------
jbe@0 3184 -- Views and helper functions for views --
jbe@0 3185 ------------------------------------------
jbe@0 3186
jbe@5 3187
jbe@524 3188 CREATE VIEW "member_eligible_to_be_notified" AS
jbe@524 3189 SELECT * FROM "member"
jbe@524 3190 WHERE "activated" NOTNULL AND "locked" = FALSE;
jbe@524 3191
jbe@524 3192 COMMENT ON VIEW "member_eligible_to_be_notified" IS 'Filtered "member" table containing only activated and non-locked members (used as helper view for "member_to_notify" and "newsletter_to_send")';
jbe@524 3193
jbe@524 3194
jbe@524 3195 CREATE VIEW "member_to_notify" AS
jbe@524 3196 SELECT * FROM "member_eligible_to_be_notified"
jbe@524 3197 WHERE "disable_notifications" = FALSE;
jbe@524 3198
jbe@524 3199 COMMENT ON VIEW "member_to_notify" IS 'Filtered "member" table containing only members that are eligible to and wish to receive notifications; NOTE: "notify_email" may still be NULL and might need to be checked by frontend (this allows other means of messaging)';
jbe@524 3200
jbe@524 3201
jbe@532 3202 CREATE VIEW "area_quorum" AS
jbe@532 3203 SELECT
jbe@532 3204 "area"."id" AS "area_id",
jbe@532 3205 ceil(
jbe@532 3206 "area"."quorum_standard"::FLOAT8 * "quorum_factor"::FLOAT8 ^ (
jbe@532 3207 coalesce(
jbe@532 3208 ( SELECT sum(
jbe@532 3209 ( extract(epoch from "area"."quorum_time")::FLOAT8 /
jbe@532 3210 extract(epoch from
jbe@532 3211 ("issue"."accepted"-"issue"."created") +
jbe@532 3212 "issue"."discussion_time" +
jbe@532 3213 "issue"."verification_time" +
jbe@532 3214 "issue"."voting_time"
jbe@532 3215 )::FLOAT8
jbe@532 3216 ) ^ "area"."quorum_exponent"::FLOAT8
jbe@532 3217 )
jbe@532 3218 FROM "issue" JOIN "policy"
jbe@532 3219 ON "issue"."policy_id" = "policy"."id"
jbe@532 3220 WHERE "issue"."area_id" = "area"."id"
jbe@532 3221 AND "issue"."accepted" NOTNULL
jbe@532 3222 AND "issue"."closed" ISNULL
jbe@532 3223 AND "policy"."polling" = FALSE
jbe@532 3224 )::FLOAT8, 0::FLOAT8
jbe@532 3225 ) / "area"."quorum_issues"::FLOAT8 - 1::FLOAT8
jbe@532 3226 ) * CASE WHEN "area"."quorum_den" ISNULL THEN 1 ELSE (
jbe@532 3227 SELECT "snapshot"."population"
jbe@532 3228 FROM "snapshot"
jbe@532 3229 WHERE "snapshot"."area_id" = "area"."id"
jbe@532 3230 AND "snapshot"."issue_id" ISNULL
jbe@532 3231 ORDER BY "snapshot"."id" DESC
jbe@532 3232 LIMIT 1
jbe@532 3233 ) END / coalesce("area"."quorum_den", 1)
jbe@532 3234
jbe@532 3235 )::INT4 AS "issue_quorum"
jbe@532 3236 FROM "area";
jbe@532 3237
jbe@532 3238 COMMENT ON VIEW "area_quorum" IS 'Area-based quorum considering number of open (accepted) issues';
jbe@532 3239
jbe@532 3240
jbe@532 3241 CREATE VIEW "area_with_unaccepted_issues" AS
jbe@532 3242 SELECT DISTINCT ON ("area"."id") "area".*
jbe@532 3243 FROM "area" JOIN "issue" ON "area"."id" = "issue"."area_id"
jbe@532 3244 WHERE "issue"."state" = 'admission';
jbe@532 3245
jbe@532 3246 COMMENT ON VIEW "area_with_unaccepted_issues" IS 'All areas with unaccepted open issues (needed for issue admission system)';
jbe@457 3247
jbe@457 3248
jbe@457 3249 CREATE VIEW "issue_for_admission" AS
jbe@532 3250 SELECT DISTINCT ON ("issue"."area_id")
jbe@457 3251 "issue".*,
jbe@457 3252 max("initiative"."supporter_count") AS "max_supporter_count"
jbe@457 3253 FROM "issue"
jbe@528 3254 JOIN "policy" ON "issue"."policy_id" = "policy"."id"
jbe@457 3255 JOIN "initiative" ON "issue"."id" = "initiative"."issue_id"
jbe@457 3256 JOIN "area" ON "issue"."area_id" = "area"."id"
jbe@457 3257 WHERE "issue"."state" = 'admission'::"issue_state"
jbe@528 3258 AND now() >= "issue"."created" + "issue"."min_admission_time"
jbe@528 3259 AND "initiative"."supporter_count" >= "policy"."issue_quorum"
jbe@532 3260 AND "initiative"."supporter_count" * "policy"."issue_quorum_den" >=
jbe@532 3261 "issue"."population" * "policy"."issue_quorum_num"
jbe@532 3262 AND "initiative"."supporter_count" >= "area"."issue_quorum"
jbe@528 3263 AND "initiative"."revoked" ISNULL
jbe@457 3264 GROUP BY "issue"."id"
jbe@532 3265 ORDER BY "issue"."area_id", "max_supporter_count" DESC, "issue"."id";
jbe@532 3266
jbe@532 3267 COMMENT ON VIEW "issue_for_admission" IS 'Contains up to 1 issue per area eligible to pass from ''admission'' to ''discussion'' state; needs to be recalculated after admitting the issue in this view';
jbe@457 3268
jbe@457 3269
jbe@97 3270 CREATE VIEW "unit_delegation" AS
jbe@97 3271 SELECT
jbe@97 3272 "unit"."id" AS "unit_id",
jbe@97 3273 "delegation"."id",
jbe@97 3274 "delegation"."truster_id",
jbe@97 3275 "delegation"."trustee_id",
jbe@97 3276 "delegation"."scope"
jbe@97 3277 FROM "unit"
jbe@97 3278 JOIN "delegation"
jbe@97 3279 ON "delegation"."unit_id" = "unit"."id"
jbe@97 3280 JOIN "member"
jbe@97 3281 ON "delegation"."truster_id" = "member"."id"
jbe@97 3282 JOIN "privilege"
jbe@97 3283 ON "delegation"."unit_id" = "privilege"."unit_id"
jbe@97 3284 AND "delegation"."truster_id" = "privilege"."member_id"
jbe@97 3285 WHERE "member"."active" AND "privilege"."voting_right";
jbe@97 3286
jbe@97 3287 COMMENT ON VIEW "unit_delegation" IS 'Unit delegations where trusters are active and have voting right';
jbe@5 3288
jbe@5 3289
jbe@5 3290 CREATE VIEW "area_delegation" AS
jbe@70 3291 SELECT DISTINCT ON ("area"."id", "delegation"."truster_id")
jbe@70 3292 "area"."id" AS "area_id",
jbe@70 3293 "delegation"."id",
jbe@70 3294 "delegation"."truster_id",
jbe@70 3295 "delegation"."trustee_id",
jbe@70 3296 "delegation"."scope"
jbe@97 3297 FROM "area"
jbe@97 3298 JOIN "delegation"
jbe@97 3299 ON "delegation"."unit_id" = "area"."unit_id"
jbe@97 3300 OR "delegation"."area_id" = "area"."id"
jbe@97 3301 JOIN "member"
jbe@97 3302 ON "delegation"."truster_id" = "member"."id"
jbe@97 3303 JOIN "privilege"
jbe@97 3304 ON "area"."unit_id" = "privilege"."unit_id"
jbe@97 3305 AND "delegation"."truster_id" = "privilege"."member_id"
jbe@97 3306 WHERE "member"."active" AND "privilege"."voting_right"
jbe@70 3307 ORDER BY
jbe@70 3308 "area"."id",
jbe@70 3309 "delegation"."truster_id",
jbe@70 3310 "delegation"."scope" DESC;
jbe@70 3311
jbe@97 3312 COMMENT ON VIEW "area_delegation" IS 'Area delegations where trusters are active and have voting right';
jbe@5 3313
jbe@5 3314
jbe@5 3315 CREATE VIEW "issue_delegation" AS
jbe@70 3316 SELECT DISTINCT ON ("issue"."id", "delegation"."truster_id")
jbe@70 3317 "issue"."id" AS "issue_id",
jbe@70 3318 "delegation"."id",
jbe@70 3319 "delegation"."truster_id",
jbe@70 3320 "delegation"."trustee_id",
jbe@70 3321 "delegation"."scope"
jbe@97 3322 FROM "issue"
jbe@97 3323 JOIN "area"
jbe@97 3324 ON "area"."id" = "issue"."area_id"
jbe@97 3325 JOIN "delegation"
jbe@97 3326 ON "delegation"."unit_id" = "area"."unit_id"
jbe@97 3327 OR "delegation"."area_id" = "area"."id"
jbe@97 3328 OR "delegation"."issue_id" = "issue"."id"
jbe@97 3329 JOIN "member"
jbe@97 3330 ON "delegation"."truster_id" = "member"."id"
jbe@97 3331 JOIN "privilege"
jbe@97 3332 ON "area"."unit_id" = "privilege"."unit_id"
jbe@97 3333 AND "delegation"."truster_id" = "privilege"."member_id"
jbe@97 3334 WHERE "member"."active" AND "privilege"."voting_right"
jbe@70 3335 ORDER BY
jbe@70 3336 "issue"."id",
jbe@70 3337 "delegation"."truster_id",
jbe@70 3338 "delegation"."scope" DESC;
jbe@70 3339
jbe@97 3340 COMMENT ON VIEW "issue_delegation" IS 'Issue delegations where trusters are active and have voting right';
jbe@5 3341
jbe@5 3342
jbe@4 3343 CREATE VIEW "member_count_view" AS
jbe@5 3344 SELECT count(1) AS "total_count" FROM "member" WHERE "active";
jbe@4 3345
jbe@4 3346 COMMENT ON VIEW "member_count_view" IS 'View used to update "member_count" table';
jbe@4 3347
jbe@4 3348
jbe@532 3349 CREATE VIEW "unit_member" AS
jbe@532 3350 SELECT
jbe@532 3351 "unit"."id" AS "unit_id",
jbe@532 3352 "member"."id" AS "member_id"
jbe@532 3353 FROM "privilege"
jbe@532 3354 JOIN "unit" ON "unit_id" = "privilege"."unit_id"
jbe@532 3355 JOIN "member" ON "member"."id" = "privilege"."member_id"
jbe@532 3356 WHERE "privilege"."voting_right" AND "member"."active";
jbe@532 3357
jbe@532 3358 COMMENT ON VIEW "unit_member" IS 'Active members with voting right in a unit';
jbe@532 3359
jbe@532 3360
jbe@97 3361 CREATE VIEW "unit_member_count" AS
jbe@97 3362 SELECT
jbe@97 3363 "unit"."id" AS "unit_id",
jbe@532 3364 count("unit_member"."member_id") AS "member_count"
jbe@532 3365 FROM "unit" LEFT JOIN "unit_member"
jbe@532 3366 ON "unit"."id" = "unit_member"."unit_id"
jbe@97 3367 GROUP BY "unit"."id";
jbe@97 3368
jbe@97 3369 COMMENT ON VIEW "unit_member_count" IS 'View used to update "member_count" column of "unit" table';
jbe@97 3370
jbe@97 3371
jbe@9 3372 CREATE VIEW "opening_draft" AS
jbe@528 3373 SELECT DISTINCT ON ("initiative_id") * FROM "draft"
jbe@528 3374 ORDER BY "initiative_id", "id";
jbe@9 3375
jbe@9 3376 COMMENT ON VIEW "opening_draft" IS 'First drafts of all initiatives';
jbe@9 3377
jbe@9 3378
jbe@0 3379 CREATE VIEW "current_draft" AS
jbe@528 3380 SELECT DISTINCT ON ("initiative_id") * FROM "draft"
jbe@528 3381 ORDER BY "initiative_id", "id" DESC;
jbe@0 3382
jbe@0 3383 COMMENT ON VIEW "current_draft" IS 'All latest drafts for each initiative';
jbe@0 3384
jbe@0 3385
jbe@0 3386 CREATE VIEW "critical_opinion" AS
jbe@0 3387 SELECT * FROM "opinion"
jbe@0 3388 WHERE ("degree" = 2 AND "fulfilled" = FALSE)
jbe@0 3389 OR ("degree" = -2 AND "fulfilled" = TRUE);
jbe@0 3390
jbe@0 3391 COMMENT ON VIEW "critical_opinion" IS 'Opinions currently causing dissatisfaction';
jbe@0 3392
jbe@0 3393
jbe@392 3394 CREATE VIEW "issue_supporter_in_admission_state" AS
jbe@528 3395 SELECT
jbe@410 3396 "area"."unit_id",
jbe@392 3397 "issue"."area_id",
jbe@392 3398 "issue"."id" AS "issue_id",
jbe@392 3399 "supporter"."member_id",
jbe@392 3400 "direct_interest_snapshot"."weight"
jbe@392 3401 FROM "issue"
jbe@410 3402 JOIN "area" ON "area"."id" = "issue"."area_id"
jbe@392 3403 JOIN "supporter" ON "supporter"."issue_id" = "issue"."id"
jbe@392 3404 JOIN "direct_interest_snapshot"
jbe@528 3405 ON "direct_interest_snapshot"."snapshot_id" = "issue"."latest_snapshot_id"
jbe@528 3406 AND "direct_interest_snapshot"."issue_id" = "issue"."id"
jbe@392 3407 AND "direct_interest_snapshot"."member_id" = "supporter"."member_id"
jbe@392 3408 WHERE "issue"."state" = 'admission'::"issue_state";
jbe@392 3409
jbe@392 3410 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 3411
jbe@392 3412
jbe@352 3413 CREATE VIEW "initiative_suggestion_order_calculation" AS
jbe@352 3414 SELECT
jbe@352 3415 "initiative"."id" AS "initiative_id",
jbe@352 3416 ("issue"."closed" NOTNULL OR "issue"."fully_frozen" NOTNULL) AS "final"
jbe@352 3417 FROM "initiative" JOIN "issue"
jbe@352 3418 ON "initiative"."issue_id" = "issue"."id"
jbe@352 3419 WHERE ("issue"."closed" ISNULL AND "issue"."fully_frozen" ISNULL)
jbe@352 3420 OR ("initiative"."final_suggestion_order_calculated" = FALSE);
jbe@352 3421
jbe@352 3422 COMMENT ON VIEW "initiative_suggestion_order_calculation" IS 'Initiatives, where the "proportional_order" of its suggestions has to be calculated';
jbe@352 3423
jbe@360 3424 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 3425
jbe@352 3426
jbe@352 3427 CREATE VIEW "individual_suggestion_ranking" AS
jbe@352 3428 SELECT
jbe@352 3429 "opinion"."initiative_id",
jbe@352 3430 "opinion"."member_id",
jbe@352 3431 "direct_interest_snapshot"."weight",
jbe@352 3432 CASE WHEN
jbe@352 3433 ("opinion"."degree" = 2 AND "opinion"."fulfilled" = FALSE) OR
jbe@352 3434 ("opinion"."degree" = -2 AND "opinion"."fulfilled" = TRUE)
jbe@352 3435 THEN 1 ELSE
jbe@352 3436 CASE WHEN
jbe@352 3437 ("opinion"."degree" = 1 AND "opinion"."fulfilled" = FALSE) OR
jbe@352 3438 ("opinion"."degree" = -1 AND "opinion"."fulfilled" = TRUE)
jbe@352 3439 THEN 2 ELSE
jbe@352 3440 CASE WHEN
jbe@352 3441 ("opinion"."degree" = 2 AND "opinion"."fulfilled" = TRUE) OR
jbe@352 3442 ("opinion"."degree" = -2 AND "opinion"."fulfilled" = FALSE)
jbe@352 3443 THEN 3 ELSE 4 END
jbe@352 3444 END
jbe@352 3445 END AS "preference",
jbe@352 3446 "opinion"."suggestion_id"
jbe@352 3447 FROM "opinion"
jbe@352 3448 JOIN "initiative" ON "initiative"."id" = "opinion"."initiative_id"
jbe@352 3449 JOIN "issue" ON "issue"."id" = "initiative"."issue_id"
jbe@352 3450 JOIN "direct_interest_snapshot"
jbe@528 3451 ON "direct_interest_snapshot"."snapshot_id" = "issue"."latest_snapshot_id"
jbe@528 3452 AND "direct_interest_snapshot"."issue_id" = "issue"."id"
jbe@352 3453 AND "direct_interest_snapshot"."member_id" = "opinion"."member_id";
jbe@352 3454
jbe@352 3455 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 3456
jbe@352 3457
jbe@126 3458 CREATE VIEW "battle_participant" AS
jbe@126 3459 SELECT "initiative"."id", "initiative"."issue_id"
jbe@126 3460 FROM "issue" JOIN "initiative"
jbe@126 3461 ON "issue"."id" = "initiative"."issue_id"
jbe@126 3462 WHERE "initiative"."admitted"
jbe@126 3463 UNION ALL
jbe@126 3464 SELECT NULL, "id" AS "issue_id"
jbe@126 3465 FROM "issue";
jbe@126 3466
jbe@126 3467 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 3468
jbe@126 3469
jbe@61 3470 CREATE VIEW "battle_view" AS
jbe@0 3471 SELECT
jbe@0 3472 "issue"."id" AS "issue_id",
jbe@10 3473 "winning_initiative"."id" AS "winning_initiative_id",
jbe@10 3474 "losing_initiative"."id" AS "losing_initiative_id",
jbe@0 3475 sum(
jbe@0 3476 CASE WHEN
jbe@0 3477 coalesce("better_vote"."grade", 0) >
jbe@0 3478 coalesce("worse_vote"."grade", 0)
jbe@0 3479 THEN "direct_voter"."weight" ELSE 0 END
jbe@0 3480 ) AS "count"
jbe@0 3481 FROM "issue"
jbe@0 3482 LEFT JOIN "direct_voter"
jbe@0 3483 ON "issue"."id" = "direct_voter"."issue_id"
jbe@126 3484 JOIN "battle_participant" AS "winning_initiative"
jbe@10 3485 ON "issue"."id" = "winning_initiative"."issue_id"
jbe@126 3486 JOIN "battle_participant" AS "losing_initiative"
jbe@10 3487 ON "issue"."id" = "losing_initiative"."issue_id"
jbe@0 3488 LEFT JOIN "vote" AS "better_vote"
jbe@10 3489 ON "direct_voter"."member_id" = "better_vote"."member_id"
jbe@10 3490 AND "winning_initiative"."id" = "better_vote"."initiative_id"
jbe@0 3491 LEFT JOIN "vote" AS "worse_vote"
jbe@10 3492 ON "direct_voter"."member_id" = "worse_vote"."member_id"
jbe@10 3493 AND "losing_initiative"."id" = "worse_vote"."initiative_id"
jbe@328 3494 WHERE "issue"."state" = 'voting'
jbe@328 3495 AND "issue"."phase_finished" NOTNULL
jbe@126 3496 AND (
jbe@126 3497 "winning_initiative"."id" != "losing_initiative"."id" OR
jbe@126 3498 ( ("winning_initiative"."id" NOTNULL AND "losing_initiative"."id" ISNULL) OR
jbe@126 3499 ("winning_initiative"."id" ISNULL AND "losing_initiative"."id" NOTNULL) ) )
jbe@0 3500 GROUP BY
jbe@0 3501 "issue"."id",
jbe@10 3502 "winning_initiative"."id",
jbe@10 3503 "losing_initiative"."id";
jbe@0 3504
jbe@126 3505 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 3506
jbe@1 3507
jbe@235 3508 CREATE VIEW "expired_session" AS
jbe@235 3509 SELECT * FROM "session" WHERE now() > "expiry";
jbe@235 3510
jbe@235 3511 CREATE RULE "delete" AS ON DELETE TO "expired_session" DO INSTEAD
jbe@532 3512 DELETE FROM "session" WHERE "id" = OLD."id";
jbe@235 3513
jbe@235 3514 COMMENT ON VIEW "expired_session" IS 'View containing all expired sessions where DELETE is possible';
jbe@235 3515 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 3516
jbe@235 3517
jbe@532 3518 CREATE VIEW "expired_token" AS
jbe@532 3519 SELECT * FROM "token" WHERE now() > "expiry" AND NOT (
jbe@532 3520 "token_type" = 'authorization' AND "used" AND EXISTS (
jbe@532 3521 SELECT NULL FROM "token" AS "other"
jbe@532 3522 WHERE "other"."authorization_token_id" = "id" ) );
jbe@532 3523
jbe@532 3524 CREATE RULE "delete" AS ON DELETE TO "expired_token" DO INSTEAD
jbe@532 3525 DELETE FROM "token" WHERE "id" = OLD."id";
jbe@532 3526
jbe@532 3527 COMMENT ON VIEW "expired_token" IS 'View containing all expired tokens where DELETE is possible; Note that used authorization codes must not be deleted if still referred to by other tokens';
jbe@532 3528
jbe@532 3529
jbe@532 3530 CREATE VIEW "unused_snapshot" AS
jbe@532 3531 SELECT "snapshot".* FROM "snapshot"
jbe@532 3532 LEFT JOIN "issue"
jbe@532 3533 ON "snapshot"."id" = "issue"."latest_snapshot_id"
jbe@532 3534 OR "snapshot"."id" = "issue"."admission_snapshot_id"
jbe@532 3535 OR "snapshot"."id" = "issue"."half_freeze_snapshot_id"
jbe@532 3536 OR "snapshot"."id" = "issue"."full_freeze_snapshot_id"
jbe@532 3537 WHERE "issue"."id" ISNULL;
jbe@532 3538
jbe@532 3539 CREATE RULE "delete" AS ON DELETE TO "unused_snapshot" DO INSTEAD
jbe@532 3540 DELETE FROM "snapshot" WHERE "id" = OLD."id";
jbe@532 3541
jbe@532 3542 COMMENT ON VIEW "unused_snapshot" IS 'Snapshots that are not referenced by any issue (either as latest snapshot or as snapshot at phase/state change)';
jbe@532 3543
jbe@532 3544
jbe@532 3545 CREATE VIEW "expired_snapshot" AS
jbe@532 3546 SELECT "unused_snapshot".* FROM "unused_snapshot" CROSS JOIN "system_setting"
jbe@532 3547 WHERE "unused_snapshot"."calculated" <
jbe@532 3548 now() - "system_setting"."snapshot_retention";
jbe@532 3549
jbe@532 3550 CREATE RULE "delete" AS ON DELETE TO "expired_snapshot" DO INSTEAD
jbe@532 3551 DELETE FROM "snapshot" WHERE "id" = OLD."id";
jbe@532 3552
jbe@532 3553 COMMENT ON VIEW "expired_snapshot" IS 'Contains "unused_snapshot"s that are older than "system_setting"."snapshot_retention" (for deletion)';
jbe@532 3554
jbe@532 3555
jbe@0 3556 CREATE VIEW "open_issue" AS
jbe@0 3557 SELECT * FROM "issue" WHERE "closed" ISNULL;
jbe@0 3558
jbe@0 3559 COMMENT ON VIEW "open_issue" IS 'All open issues';
jbe@0 3560
jbe@0 3561
jbe@9 3562 CREATE VIEW "member_contingent" AS
jbe@9 3563 SELECT
jbe@9 3564 "member"."id" AS "member_id",
jbe@293 3565 "contingent"."polling",
jbe@9 3566 "contingent"."time_frame",
jbe@9 3567 CASE WHEN "contingent"."text_entry_limit" NOTNULL THEN
jbe@9 3568 (
jbe@9 3569 SELECT count(1) FROM "draft"
jbe@293 3570 JOIN "initiative" ON "initiative"."id" = "draft"."initiative_id"
jbe@9 3571 WHERE "draft"."author_id" = "member"."id"
jbe@293 3572 AND "initiative"."polling" = "contingent"."polling"
jbe@9 3573 AND "draft"."created" > now() - "contingent"."time_frame"
jbe@9 3574 ) + (
jbe@9 3575 SELECT count(1) FROM "suggestion"
jbe@293 3576 JOIN "initiative" ON "initiative"."id" = "suggestion"."initiative_id"
jbe@9 3577 WHERE "suggestion"."author_id" = "member"."id"
jbe@293 3578 AND "contingent"."polling" = FALSE
jbe@9 3579 AND "suggestion"."created" > now() - "contingent"."time_frame"
jbe@9 3580 )
jbe@9 3581 ELSE NULL END AS "text_entry_count",
jbe@9 3582 "contingent"."text_entry_limit",
jbe@9 3583 CASE WHEN "contingent"."initiative_limit" NOTNULL THEN (
jbe@293 3584 SELECT count(1) FROM "opening_draft" AS "draft"
jbe@293 3585 JOIN "initiative" ON "initiative"."id" = "draft"."initiative_id"
jbe@293 3586 WHERE "draft"."author_id" = "member"."id"
jbe@293 3587 AND "initiative"."polling" = "contingent"."polling"
jbe@293 3588 AND "draft"."created" > now() - "contingent"."time_frame"
jbe@9 3589 ) ELSE NULL END AS "initiative_count",
jbe@9 3590 "contingent"."initiative_limit"
jbe@9 3591 FROM "member" CROSS JOIN "contingent";
jbe@9 3592
jbe@9 3593 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 3594
jbe@9 3595 COMMENT ON COLUMN "member_contingent"."text_entry_count" IS 'Only calculated when "text_entry_limit" is not null in the same row';
jbe@9 3596 COMMENT ON COLUMN "member_contingent"."initiative_count" IS 'Only calculated when "initiative_limit" is not null in the same row';
jbe@9 3597
jbe@9 3598
jbe@9 3599 CREATE VIEW "member_contingent_left" AS
jbe@9 3600 SELECT
jbe@9 3601 "member_id",
jbe@293 3602 "polling",
jbe@9 3603 max("text_entry_limit" - "text_entry_count") AS "text_entries_left",
jbe@9 3604 max("initiative_limit" - "initiative_count") AS "initiatives_left"
jbe@293 3605 FROM "member_contingent" GROUP BY "member_id", "polling";
jbe@9 3606
jbe@9 3607 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 3608
jbe@9 3609
jbe@499 3610 CREATE VIEW "event_for_notification" AS
jbe@113 3611 SELECT
jbe@499 3612 "member"."id" AS "recipient_id",
jbe@113 3613 "event".*
jbe@113 3614 FROM "member" CROSS JOIN "event"
jbe@499 3615 JOIN "issue" ON "issue"."id" = "event"."issue_id"
jbe@499 3616 JOIN "area" ON "area"."id" = "issue"."area_id"
jbe@499 3617 LEFT JOIN "privilege" ON
jbe@499 3618 "privilege"."member_id" = "member"."id" AND
jbe@499 3619 "privilege"."unit_id" = "area"."unit_id" AND
jbe@499 3620 "privilege"."voting_right" = TRUE
jbe@499 3621 LEFT JOIN "subscription" ON
jbe@499 3622 "subscription"."member_id" = "member"."id" AND
jbe@499 3623 "subscription"."unit_id" = "area"."unit_id"
jbe@499 3624 LEFT JOIN "ignored_area" ON
jbe@499 3625 "ignored_area"."member_id" = "member"."id" AND
jbe@499 3626 "ignored_area"."area_id" = "issue"."area_id"
jbe@499 3627 LEFT JOIN "interest" ON
jbe@499 3628 "interest"."member_id" = "member"."id" AND
jbe@499 3629 "interest"."issue_id" = "event"."issue_id"
jbe@499 3630 LEFT JOIN "supporter" ON
jbe@499 3631 "supporter"."member_id" = "member"."id" AND
jbe@499 3632 "supporter"."initiative_id" = "event"."initiative_id"
jbe@499 3633 WHERE ("privilege"."member_id" NOTNULL OR "subscription"."member_id" NOTNULL)
jbe@499 3634 AND ("ignored_area"."member_id" ISNULL OR "interest"."member_id" NOTNULL)
jbe@499 3635 AND (
jbe@499 3636 "event"."event" = 'issue_state_changed'::"event_type" OR
jbe@499 3637 ( "event"."event" = 'initiative_revoked'::"event_type" AND
jbe@499 3638 "supporter"."member_id" NOTNULL ) );
jbe@499 3639
jbe@508 3640 COMMENT ON VIEW "event_for_notification" IS 'Entries of the "event" table which are of interest for a particular notification mail recipient';
jbe@508 3641
jbe@508 3642 COMMENT ON COLUMN "event_for_notification"."recipient_id" IS 'member_id of the recipient of a notification mail';
jbe@222 3643
jbe@222 3644
jbe@473 3645 CREATE VIEW "updated_initiative" AS
jbe@113 3646 SELECT
jbe@499 3647 "supporter"."member_id" AS "recipient_id",
jbe@477 3648 FALSE AS "featured",
jbe@499 3649 "supporter"."initiative_id"
jbe@499 3650 FROM "supporter"
jbe@499 3651 JOIN "initiative" ON "supporter"."initiative_id" = "initiative"."id"
jbe@473 3652 JOIN "issue" ON "issue"."id" = "initiative"."issue_id"
jbe@507 3653 LEFT JOIN "notification_initiative_sent" AS "sent" ON
jbe@499 3654 "sent"."member_id" = "supporter"."member_id" AND
jbe@499 3655 "sent"."initiative_id" = "supporter"."initiative_id"
jbe@499 3656 LEFT JOIN "ignored_initiative" ON
jbe@499 3657 "ignored_initiative"."member_id" = "supporter"."member_id" AND
jbe@499 3658 "ignored_initiative"."initiative_id" = "supporter"."initiative_id"
jbe@480 3659 WHERE "issue"."state" IN ('admission', 'discussion')
jbe@503 3660 AND "initiative"."revoked" ISNULL
jbe@499 3661 AND "ignored_initiative"."member_id" ISNULL
jbe@473 3662 AND (
jbe@473 3663 EXISTS (
jbe@473 3664 SELECT NULL FROM "draft"
jbe@499 3665 LEFT JOIN "ignored_member" ON
jbe@499 3666 "ignored_member"."member_id" = "supporter"."member_id" AND
jbe@499 3667 "ignored_member"."other_member_id" = "draft"."author_id"
jbe@499 3668 WHERE "draft"."initiative_id" = "supporter"."initiative_id"
jbe@473 3669 AND "draft"."id" > "supporter"."draft_id"
jbe@499 3670 AND "ignored_member"."member_id" ISNULL
jbe@473 3671 ) OR EXISTS (
jbe@473 3672 SELECT NULL FROM "suggestion"
jbe@487 3673 LEFT JOIN "opinion" ON
jbe@487 3674 "opinion"."member_id" = "supporter"."member_id" AND
jbe@487 3675 "opinion"."suggestion_id" = "suggestion"."id"
jbe@499 3676 LEFT JOIN "ignored_member" ON
jbe@499 3677 "ignored_member"."member_id" = "supporter"."member_id" AND
jbe@499 3678 "ignored_member"."other_member_id" = "suggestion"."author_id"
jbe@499 3679 WHERE "suggestion"."initiative_id" = "supporter"."initiative_id"
jbe@487 3680 AND "opinion"."member_id" ISNULL
jbe@499 3681 AND COALESCE("suggestion"."id" > "sent"."last_suggestion_id", TRUE)
jbe@499 3682 AND "ignored_member"."member_id" ISNULL
jbe@473 3683 )
jbe@473 3684 );
jbe@473 3685
jbe@508 3686 COMMENT ON VIEW "updated_initiative" IS 'Helper view for view "updated_or_featured_initiative"';
jbe@508 3687
jbe@508 3688
jbe@474 3689 CREATE FUNCTION "featured_initiative"
jbe@499 3690 ( "recipient_id_p" "member"."id"%TYPE,
jbe@499 3691 "area_id_p" "area"."id"%TYPE )
jbe@499 3692 RETURNS SETOF "initiative"."id"%TYPE
jbe@474 3693 LANGUAGE 'plpgsql' STABLE AS $$
jbe@474 3694 DECLARE
jbe@499 3695 "counter_v" "member"."notification_counter"%TYPE;
jbe@499 3696 "sample_size_v" "member"."notification_sample_size"%TYPE;
jbe@499 3697 "initiative_id_ary" INT4[]; --"initiative"."id"%TYPE[]
jbe@499 3698 "match_v" BOOLEAN;
jbe@474 3699 "member_id_v" "member"."id"%TYPE;
jbe@474 3700 "seed_v" TEXT;
jbe@499 3701 "initiative_id_v" "initiative"."id"%TYPE;
jbe@474 3702 BEGIN
jbe@499 3703 SELECT "notification_counter", "notification_sample_size"
jbe@499 3704 INTO "counter_v", "sample_size_v"
jbe@499 3705 FROM "member" WHERE "id" = "recipient_id_p";
jbe@520 3706 IF COALESCE("sample_size_v" <= 0, TRUE) THEN
jbe@520 3707 RETURN;
jbe@520 3708 END IF;
jbe@474 3709 "initiative_id_ary" := '{}';
jbe@474 3710 LOOP
jbe@474 3711 "match_v" := FALSE;
jbe@474 3712 FOR "member_id_v", "seed_v" IN
jbe@474 3713 SELECT * FROM (
jbe@474 3714 SELECT DISTINCT
jbe@474 3715 "supporter"."member_id",
jbe@499 3716 md5(
jbe@499 3717 "recipient_id_p" || '-' ||
jbe@499 3718 "counter_v" || '-' ||
jbe@499 3719 "area_id_p" || '-' ||
jbe@499 3720 "supporter"."member_id"
jbe@499 3721 ) AS "seed"
jbe@474 3722 FROM "supporter"
jbe@474 3723 JOIN "initiative" ON "initiative"."id" = "supporter"."initiative_id"
jbe@474 3724 JOIN "issue" ON "issue"."id" = "initiative"."issue_id"
jbe@499 3725 WHERE "supporter"."member_id" != "recipient_id_p"
jbe@474 3726 AND "issue"."area_id" = "area_id_p"
jbe@474 3727 AND "issue"."state" IN ('admission', 'discussion', 'verification')
jbe@474 3728 ) AS "subquery"
jbe@474 3729 ORDER BY "seed"
jbe@474 3730 LOOP
jbe@499 3731 SELECT "initiative"."id" INTO "initiative_id_v"
jbe@476 3732 FROM "initiative"
jbe@474 3733 JOIN "issue" ON "issue"."id" = "initiative"."issue_id"
jbe@499 3734 JOIN "area" ON "area"."id" = "issue"."area_id"
jbe@474 3735 JOIN "supporter" ON "supporter"."initiative_id" = "initiative"."id"
jbe@474 3736 LEFT JOIN "supporter" AS "self_support" ON
jbe@474 3737 "self_support"."initiative_id" = "initiative"."id" AND
jbe@499 3738 "self_support"."member_id" = "recipient_id_p"
jbe@499 3739 LEFT JOIN "privilege" ON
jbe@499 3740 "privilege"."member_id" = "recipient_id_p" AND
jbe@499 3741 "privilege"."unit_id" = "area"."unit_id" AND
jbe@499 3742 "privilege"."voting_right" = TRUE
jbe@499 3743 LEFT JOIN "subscription" ON
jbe@499 3744 "subscription"."member_id" = "recipient_id_p" AND
jbe@499 3745 "subscription"."unit_id" = "area"."unit_id"
jbe@499 3746 LEFT JOIN "ignored_initiative" ON
jbe@499 3747 "ignored_initiative"."member_id" = "recipient_id_p" AND
jbe@499 3748 "ignored_initiative"."initiative_id" = "initiative"."id"
jbe@474 3749 WHERE "supporter"."member_id" = "member_id_v"
jbe@474 3750 AND "issue"."area_id" = "area_id_p"
jbe@474 3751 AND "issue"."state" IN ('admission', 'discussion', 'verification')
jbe@503 3752 AND "initiative"."revoked" ISNULL
jbe@474 3753 AND "self_support"."member_id" ISNULL
jbe@476 3754 AND NOT "initiative_id_ary" @> ARRAY["initiative"."id"]
jbe@499 3755 AND (
jbe@499 3756 "privilege"."member_id" NOTNULL OR
jbe@499 3757 "subscription"."member_id" NOTNULL )
jbe@499 3758 AND "ignored_initiative"."member_id" ISNULL
jbe@499 3759 AND NOT EXISTS (
jbe@499 3760 SELECT NULL FROM "draft"
jbe@499 3761 JOIN "ignored_member" ON
jbe@499 3762 "ignored_member"."member_id" = "recipient_id_p" AND
jbe@499 3763 "ignored_member"."other_member_id" = "draft"."author_id"
jbe@499 3764 WHERE "draft"."initiative_id" = "initiative"."id"
jbe@499 3765 )
jbe@474 3766 ORDER BY md5("seed_v" || '-' || "initiative"."id")
jbe@476 3767 LIMIT 1;
jbe@476 3768 IF FOUND THEN
jbe@476 3769 "match_v" := TRUE;
jbe@499 3770 RETURN NEXT "initiative_id_v";
jbe@499 3771 IF array_length("initiative_id_ary", 1) + 1 >= "sample_size_v" THEN
jbe@476 3772 RETURN;
jbe@474 3773 END IF;
jbe@499 3774 "initiative_id_ary" := "initiative_id_ary" || "initiative_id_v";
jbe@476 3775 END IF;
jbe@474 3776 END LOOP;
jbe@474 3777 EXIT WHEN NOT "match_v";
jbe@474 3778 END LOOP;
jbe@474 3779 RETURN;
jbe@474 3780 END;
jbe@474 3781 $$;
jbe@474 3782
jbe@508 3783 COMMENT ON FUNCTION "featured_initiative"
jbe@508 3784 ( "recipient_id_p" "member"."id"%TYPE,
jbe@508 3785 "area_id_p" "area"."id"%TYPE )
jbe@508 3786 IS 'Helper function for view "updated_or_featured_initiative"';
jbe@508 3787
jbe@508 3788
jbe@474 3789 CREATE VIEW "updated_or_featured_initiative" AS
jbe@474 3790 SELECT
jbe@499 3791 "subquery".*,
jbe@477 3792 NOT EXISTS (
jbe@477 3793 SELECT NULL FROM "initiative" AS "better_initiative"
jbe@499 3794 WHERE "better_initiative"."issue_id" = "initiative"."issue_id"
jbe@484 3795 AND
jbe@502 3796 ( COALESCE("better_initiative"."supporter_count", -1),
jbe@484 3797 -"better_initiative"."id" ) >
jbe@502 3798 ( COALESCE("initiative"."supporter_count", -1),
jbe@485 3799 -"initiative"."id" )
jbe@499 3800 ) AS "leading"
jbe@499 3801 FROM (
jbe@499 3802 SELECT * FROM "updated_initiative"
jbe@499 3803 UNION ALL
jbe@499 3804 SELECT
jbe@499 3805 "member"."id" AS "recipient_id",
jbe@499 3806 TRUE AS "featured",
jbe@499 3807 "featured_initiative_id" AS "initiative_id"
jbe@499 3808 FROM "member" CROSS JOIN "area"
jbe@499 3809 CROSS JOIN LATERAL
jbe@499 3810 "featured_initiative"("member"."id", "area"."id") AS "featured_initiative_id"
jbe@499 3811 JOIN "initiative" ON "initiative"."id" = "featured_initiative_id"
jbe@499 3812 ) AS "subquery"
jbe@499 3813 JOIN "initiative" ON "initiative"."id" = "subquery"."initiative_id";
jbe@474 3814
jbe@508 3815 COMMENT ON VIEW "updated_or_featured_initiative" IS 'Initiatives to be included in a scheduled notification mail because (a) they have been updated or (b) they are featured';
jbe@508 3816
jbe@508 3817 COMMENT ON COLUMN "updated_or_featured_initiative"."recipient_id" IS '"id" of the member who receives the notification mail';
jbe@508 3818 COMMENT ON COLUMN "updated_or_featured_initiative"."featured" IS 'TRUE if the initiative has been included because it was selected by the "featured_initiative" algorithm (see source of function "featured_initiative")';
jbe@508 3819 COMMENT ON COLUMN "updated_or_featured_initiative"."initiative_id" IS '"id" of the initiative to be included in the notification mail';
jbe@508 3820 COMMENT ON COLUMN "updated_or_featured_initiative"."leading" IS 'TRUE if the initiative has the highest "supporter_count" in the issue';
jbe@508 3821
jbe@508 3822
jbe@474 3823 CREATE VIEW "leading_complement_initiative" AS
jbe@477 3824 SELECT * FROM (
jbe@499 3825 SELECT DISTINCT ON ("uf_initiative"."recipient_id", "initiative"."issue_id")
jbe@499 3826 "uf_initiative"."recipient_id",
jbe@477 3827 FALSE AS "featured",
jbe@499 3828 "uf_initiative"."initiative_id",
jbe@499 3829 TRUE AS "leading"
jbe@489 3830 FROM "updated_or_featured_initiative" AS "uf_initiative"
jbe@499 3831 JOIN "initiative" AS "uf_initiative_full" ON
jbe@499 3832 "uf_initiative_full"."id" = "uf_initiative"."initiative_id"
jbe@489 3833 JOIN "initiative" ON
jbe@499 3834 "initiative"."issue_id" = "uf_initiative_full"."issue_id"
jbe@503 3835 WHERE "initiative"."revoked" ISNULL
jbe@477 3836 ORDER BY
jbe@499 3837 "uf_initiative"."recipient_id",
jbe@477 3838 "initiative"."issue_id",
jbe@502 3839 "initiative"."supporter_count" DESC,
jbe@477 3840 "initiative"."id"
jbe@477 3841 ) AS "subquery"
jbe@477 3842 WHERE NOT EXISTS (
jbe@477 3843 SELECT NULL FROM "updated_or_featured_initiative" AS "other"
jbe@499 3844 WHERE "other"."recipient_id" = "subquery"."recipient_id"
jbe@499 3845 AND "other"."initiative_id" = "subquery"."initiative_id"
jbe@477 3846 );
jbe@474 3847
jbe@508 3848 COMMENT ON VIEW "leading_complement_initiative" IS 'Helper view for view "unfiltered_initiative_for_notification" in order to always include the most supported initiative of an issue';
jbe@508 3849 COMMENT ON COLUMN "leading_complement_initiative"."featured" IS 'Always FALSE in this view';
jbe@508 3850 COMMENT ON COLUMN "leading_complement_initiative"."initiative_id" IS '"id" of the initiative to be included in the notification mail';
jbe@508 3851 COMMENT ON COLUMN "leading_complement_initiative"."leading" IS 'Always TRUE in this view';
jbe@508 3852
jbe@508 3853
jbe@490 3854 CREATE VIEW "unfiltered_initiative_for_notification" AS
jbe@499 3855 SELECT
jbe@499 3856 "subquery".*,
jbe@499 3857 "supporter"."member_id" NOTNULL AS "supported",
jbe@499 3858 CASE WHEN "supporter"."member_id" NOTNULL THEN
jbe@499 3859 EXISTS (
jbe@499 3860 SELECT NULL FROM "draft"
jbe@499 3861 WHERE "draft"."initiative_id" = "subquery"."initiative_id"
jbe@499 3862 AND "draft"."id" > "supporter"."draft_id"
jbe@499 3863 )
jbe@222 3864 ELSE
jbe@499 3865 EXISTS (
jbe@499 3866 SELECT NULL FROM "draft"
jbe@499 3867 WHERE "draft"."initiative_id" = "subquery"."initiative_id"
jbe@499 3868 AND COALESCE("draft"."id" > "sent"."last_draft_id", TRUE)
jbe@499 3869 )
jbe@499 3870 END AS "new_draft",
jbe@499 3871 CASE WHEN "supporter"."member_id" NOTNULL THEN
jbe@499 3872 ( SELECT count(1) FROM "suggestion"
jbe@499 3873 LEFT JOIN "opinion" ON
jbe@499 3874 "opinion"."member_id" = "supporter"."member_id" AND
jbe@499 3875 "opinion"."suggestion_id" = "suggestion"."id"
jbe@499 3876 WHERE "suggestion"."initiative_id" = "subquery"."initiative_id"
jbe@499 3877 AND "opinion"."member_id" ISNULL
jbe@499 3878 AND COALESCE("suggestion"."id" > "sent"."last_suggestion_id", TRUE)
jbe@499 3879 )
jbe@499 3880 ELSE
jbe@499 3881 ( SELECT count(1) FROM "suggestion"
jbe@499 3882 WHERE "suggestion"."initiative_id" = "subquery"."initiative_id"
jbe@499 3883 AND COALESCE("suggestion"."id" > "sent"."last_suggestion_id", TRUE)
jbe@499 3884 )
jbe@499 3885 END AS "new_suggestion_count"
jbe@499 3886 FROM (
jbe@499 3887 SELECT * FROM "updated_or_featured_initiative"
jbe@499 3888 UNION ALL
jbe@499 3889 SELECT * FROM "leading_complement_initiative"
jbe@499 3890 ) AS "subquery"
jbe@499 3891 LEFT JOIN "supporter" ON
jbe@499 3892 "supporter"."member_id" = "subquery"."recipient_id" AND
jbe@499 3893 "supporter"."initiative_id" = "subquery"."initiative_id"
jbe@507 3894 LEFT JOIN "notification_initiative_sent" AS "sent" ON
jbe@499 3895 "sent"."member_id" = "subquery"."recipient_id" AND
jbe@499 3896 "sent"."initiative_id" = "subquery"."initiative_id";
jbe@474 3897
jbe@508 3898 COMMENT ON VIEW "unfiltered_initiative_for_notification" IS 'Helper view which simply combines the views "updated_or_featured_initiative" and "leading_complement_initiative" and adds columns "supported", "new_draft", and "new_suggestion_count';
jbe@508 3899
jbe@508 3900 COMMENT ON COLUMN "unfiltered_initiative_for_notification"."supported" IS 'TRUE if initiative is supported by the recipient';
jbe@508 3901 COMMENT ON COLUMN "unfiltered_initiative_for_notification"."new_draft" IS 'TRUE if a new draft exists (using the "draft_id" column of the "supporter" table in case of "supported" initiatives and the "last_draft_id" column of the "notification_initiative_sent" table in all other cases)';
jbe@508 3902 COMMENT ON COLUMN "unfiltered_initiative_for_notification"."new_suggestion_count" IS 'Number of new suggestions (using the "last_suggestion_id" column of the "notification_initiative_sent" table while ignoring suggestions with an "opinion")';
jbe@508 3903
jbe@508 3904
jbe@490 3905 CREATE VIEW "initiative_for_notification" AS
jbe@499 3906 SELECT "unfiltered1".*
jbe@499 3907 FROM "unfiltered_initiative_for_notification" "unfiltered1"
jbe@499 3908 JOIN "initiative" AS "initiative1" ON
jbe@499 3909 "initiative1"."id" = "unfiltered1"."initiative_id"
jbe@499 3910 JOIN "issue" AS "issue1" ON "issue1"."id" = "initiative1"."issue_id"
jbe@490 3911 WHERE EXISTS (
jbe@490 3912 SELECT NULL
jbe@499 3913 FROM "unfiltered_initiative_for_notification" "unfiltered2"
jbe@499 3914 JOIN "initiative" AS "initiative2" ON
jbe@499 3915 "initiative2"."id" = "unfiltered2"."initiative_id"
jbe@499 3916 JOIN "issue" AS "issue2" ON "issue2"."id" = "initiative2"."issue_id"
jbe@499 3917 WHERE "unfiltered1"."recipient_id" = "unfiltered2"."recipient_id"
jbe@490 3918 AND "issue1"."area_id" = "issue2"."area_id"
jbe@499 3919 AND ("unfiltered2"."new_draft" OR "unfiltered2"."new_suggestion_count" > 0 )
jbe@490 3920 );
jbe@490 3921
jbe@508 3922 COMMENT ON VIEW "initiative_for_notification" IS 'Initiatives to be included in a scheduled notification mail';
jbe@508 3923
jbe@508 3924 COMMENT ON COLUMN "initiative_for_notification"."recipient_id" IS '"id" of the member who receives the notification mail';
jbe@508 3925 COMMENT ON COLUMN "initiative_for_notification"."featured" IS 'TRUE if the initiative has been included because it was selected by the "featured_initiative" algorithm (see source of function "featured_initiative")';
jbe@508 3926 COMMENT ON COLUMN "initiative_for_notification"."initiative_id" IS '"id" of the initiative to be included in the notification mail';
jbe@508 3927 COMMENT ON COLUMN "initiative_for_notification"."leading" IS 'TRUE if the initiative has the highest "supporter_count" in the issue';
jbe@508 3928 COMMENT ON COLUMN "initiative_for_notification"."supported" IS 'TRUE if initiative is supported by the recipient';
jbe@508 3929 COMMENT ON COLUMN "initiative_for_notification"."new_draft" IS 'TRUE if a new draft exists (using the "draft_id" column of the "supporter" table in case of "supported" initiatives and the "last_draft_id" column of the "notification_initiative_sent" table in all other cases)';
jbe@508 3930 COMMENT ON COLUMN "initiative_for_notification"."new_suggestion_count" IS 'Number of new suggestions (using the "last_suggestion_id" column of the "notification_initiative_sent" table while ignoring suggestions with an "opinion")';
jbe@508 3931
jbe@508 3932
jbe@504 3933 CREATE VIEW "scheduled_notification_to_send" AS
jbe@505 3934 SELECT * FROM (
jbe@505 3935 SELECT
jbe@505 3936 "id" AS "recipient_id",
jbe@505 3937 now() - CASE WHEN "notification_dow" ISNULL THEN
jbe@505 3938 ( "notification_sent"::DATE + CASE
jbe@505 3939 WHEN EXTRACT(HOUR FROM "notification_sent") < "notification_hour"
jbe@505 3940 THEN 0 ELSE 1 END
jbe@505 3941 )::TIMESTAMP + '1 hour'::INTERVAL * "notification_hour"
jbe@222 3942 ELSE
jbe@505 3943 ( "notification_sent"::DATE +
jbe@505 3944 ( 7 + "notification_dow" -
jbe@505 3945 EXTRACT(DOW FROM
jbe@505 3946 ( "notification_sent"::DATE + CASE
jbe@505 3947 WHEN EXTRACT(HOUR FROM "notification_sent") < "notification_hour"
jbe@505 3948 THEN 0 ELSE 1 END
jbe@505 3949 )::TIMESTAMP + '1 hour'::INTERVAL * "notification_hour"
jbe@505 3950 )::INTEGER
jbe@505 3951 ) % 7 +
jbe@505 3952 CASE
jbe@505 3953 WHEN EXTRACT(HOUR FROM "notification_sent") < "notification_hour"
jbe@505 3954 THEN 0 ELSE 1
jbe@505 3955 END
jbe@505 3956 )::TIMESTAMP + '1 hour'::INTERVAL * "notification_hour"
jbe@505 3957 END AS "pending"
jbe@505 3958 FROM (
jbe@505 3959 SELECT
jbe@505 3960 "id",
jbe@505 3961 COALESCE("notification_sent", "activated") AS "notification_sent",
jbe@505 3962 "notification_dow",
jbe@505 3963 "notification_hour"
jbe@524 3964 FROM "member_to_notify"
jbe@524 3965 WHERE "notification_hour" NOTNULL
jbe@505 3966 ) AS "subquery1"
jbe@505 3967 ) AS "subquery2"
jbe@505 3968 WHERE "pending" > '0'::INTERVAL;
jbe@504 3969
jbe@508 3970 COMMENT ON VIEW "scheduled_notification_to_send" IS 'Set of members where a scheduled notification mail is pending';
jbe@508 3971
jbe@508 3972 COMMENT ON COLUMN "scheduled_notification_to_send"."recipient_id" IS '"id" of the member who needs to receive a notification mail';
jbe@508 3973 COMMENT ON COLUMN "scheduled_notification_to_send"."pending" IS 'Duration for which the notification mail has already been pending';
jbe@508 3974
jbe@508 3975
jbe@497 3976 CREATE VIEW "newsletter_to_send" AS
jbe@497 3977 SELECT
jbe@499 3978 "member"."id" AS "recipient_id",
jbe@514 3979 "newsletter"."id" AS "newsletter_id",
jbe@514 3980 "newsletter"."published"
jbe@524 3981 FROM "newsletter" CROSS JOIN "member_eligible_to_be_notified" AS "member"
jbe@497 3982 LEFT JOIN "privilege" ON
jbe@497 3983 "privilege"."member_id" = "member"."id" AND
jbe@497 3984 "privilege"."unit_id" = "newsletter"."unit_id" AND
jbe@497 3985 "privilege"."voting_right" = TRUE
jbe@497 3986 LEFT JOIN "subscription" ON
jbe@497 3987 "subscription"."member_id" = "member"."id" AND
jbe@497 3988 "subscription"."unit_id" = "newsletter"."unit_id"
jbe@498 3989 WHERE "newsletter"."published" <= now()
jbe@497 3990 AND "newsletter"."sent" ISNULL
jbe@113 3991 AND (
jbe@497 3992 "member"."disable_notifications" = FALSE OR
jbe@497 3993 "newsletter"."include_all_members" = TRUE )
jbe@497 3994 AND (
jbe@497 3995 "newsletter"."unit_id" ISNULL OR
jbe@497 3996 "privilege"."member_id" NOTNULL OR
jbe@497 3997 "subscription"."member_id" NOTNULL );
jbe@497 3998
jbe@508 3999 COMMENT ON VIEW "newsletter_to_send" IS 'List of "newsletter_id"s for each member that are due to be sent out';
jbe@508 4000
jbe@514 4001 COMMENT ON COLUMN "newsletter"."published" IS 'Timestamp when the newsletter was supposed to be sent out (can be used for ordering)';
jbe@113 4002
jbe@113 4003
jbe@0 4004
jbe@242 4005 ------------------------------------------------------
jbe@242 4006 -- Row set returning function for delegation chains --
jbe@242 4007 ------------------------------------------------------
jbe@5 4008
jbe@5 4009
jbe@5 4010 CREATE TYPE "delegation_chain_loop_tag" AS ENUM
jbe@5 4011 ('first', 'intermediate', 'last', 'repetition');
jbe@5 4012
jbe@5 4013 COMMENT ON TYPE "delegation_chain_loop_tag" IS 'Type for loop tags in "delegation_chain_row" type';
jbe@5 4014
jbe@5 4015
jbe@5 4016 CREATE TYPE "delegation_chain_row" AS (
jbe@5 4017 "index" INT4,
jbe@5 4018 "member_id" INT4,
jbe@97 4019 "member_valid" BOOLEAN,
jbe@5 4020 "participation" BOOLEAN,
jbe@5 4021 "overridden" BOOLEAN,
jbe@5 4022 "scope_in" "delegation_scope",
jbe@5 4023 "scope_out" "delegation_scope",
jbe@86 4024 "disabled_out" BOOLEAN,
jbe@5 4025 "loop" "delegation_chain_loop_tag" );
jbe@5 4026
jbe@243 4027 COMMENT ON TYPE "delegation_chain_row" IS 'Type of rows returned by "delegation_chain" function';
jbe@5 4028
jbe@5 4029 COMMENT ON COLUMN "delegation_chain_row"."index" IS 'Index starting with 0 and counting up';
jbe@532 4030 COMMENT ON COLUMN "delegation_chain_row"."participation" IS 'In case of delegation chains for issues: interest; for area and global delegation chains: always null';
jbe@5 4031 COMMENT ON COLUMN "delegation_chain_row"."overridden" IS 'True, if an entry with lower index has "participation" set to true';
jbe@5 4032 COMMENT ON COLUMN "delegation_chain_row"."scope_in" IS 'Scope of used incoming delegation';
jbe@5 4033 COMMENT ON COLUMN "delegation_chain_row"."scope_out" IS 'Scope of used outgoing delegation';
jbe@86 4034 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 4035 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 4036
jbe@5 4037
jbe@242 4038 CREATE FUNCTION "delegation_chain_for_closed_issue"
jbe@242 4039 ( "member_id_p" "member"."id"%TYPE,
jbe@242 4040 "issue_id_p" "issue"."id"%TYPE )
jbe@242 4041 RETURNS SETOF "delegation_chain_row"
jbe@242 4042 LANGUAGE 'plpgsql' STABLE AS $$
jbe@242 4043 DECLARE
jbe@242 4044 "output_row" "delegation_chain_row";
jbe@242 4045 "direct_voter_row" "direct_voter"%ROWTYPE;
jbe@242 4046 "delegating_voter_row" "delegating_voter"%ROWTYPE;
jbe@242 4047 BEGIN
jbe@242 4048 "output_row"."index" := 0;
jbe@242 4049 "output_row"."member_id" := "member_id_p";
jbe@242 4050 "output_row"."member_valid" := TRUE;
jbe@242 4051 "output_row"."participation" := FALSE;
jbe@242 4052 "output_row"."overridden" := FALSE;
jbe@242 4053 "output_row"."disabled_out" := FALSE;
jbe@242 4054 LOOP
jbe@242 4055 SELECT INTO "direct_voter_row" * FROM "direct_voter"
jbe@242 4056 WHERE "issue_id" = "issue_id_p"
jbe@242 4057 AND "member_id" = "output_row"."member_id";
jbe@242 4058 IF "direct_voter_row"."member_id" NOTNULL THEN
jbe@242 4059 "output_row"."participation" := TRUE;
jbe@242 4060 "output_row"."scope_out" := NULL;
jbe@242 4061 "output_row"."disabled_out" := NULL;
jbe@242 4062 RETURN NEXT "output_row";
jbe@242 4063 RETURN;
jbe@242 4064 END IF;
jbe@242 4065 SELECT INTO "delegating_voter_row" * FROM "delegating_voter"
jbe@242 4066 WHERE "issue_id" = "issue_id_p"
jbe@242 4067 AND "member_id" = "output_row"."member_id";
jbe@242 4068 IF "delegating_voter_row"."member_id" ISNULL THEN
jbe@242 4069 RETURN;
jbe@242 4070 END IF;
jbe@242 4071 "output_row"."scope_out" := "delegating_voter_row"."scope";
jbe@242 4072 RETURN NEXT "output_row";
jbe@242 4073 "output_row"."member_id" := "delegating_voter_row"."delegate_member_ids"[1];
jbe@242 4074 "output_row"."scope_in" := "output_row"."scope_out";
jbe@242 4075 END LOOP;
jbe@242 4076 END;
jbe@242 4077 $$;
jbe@242 4078
jbe@242 4079 COMMENT ON FUNCTION "delegation_chain_for_closed_issue"
jbe@242 4080 ( "member"."id"%TYPE,
jbe@242 4081 "member"."id"%TYPE )
jbe@242 4082 IS 'Helper function for "delegation_chain" function, handling the special case of closed issues after voting';
jbe@242 4083
jbe@242 4084
jbe@5 4085 CREATE FUNCTION "delegation_chain"
jbe@5 4086 ( "member_id_p" "member"."id"%TYPE,
jbe@97 4087 "unit_id_p" "unit"."id"%TYPE,
jbe@5 4088 "area_id_p" "area"."id"%TYPE,
jbe@5 4089 "issue_id_p" "issue"."id"%TYPE,
jbe@255 4090 "simulate_trustee_id_p" "member"."id"%TYPE DEFAULT NULL,
jbe@255 4091 "simulate_default_p" BOOLEAN DEFAULT FALSE )
jbe@5 4092 RETURNS SETOF "delegation_chain_row"
jbe@5 4093 LANGUAGE 'plpgsql' STABLE AS $$
jbe@5 4094 DECLARE
jbe@97 4095 "scope_v" "delegation_scope";
jbe@97 4096 "unit_id_v" "unit"."id"%TYPE;
jbe@97 4097 "area_id_v" "area"."id"%TYPE;
jbe@241 4098 "issue_row" "issue"%ROWTYPE;
jbe@5 4099 "visited_member_ids" INT4[]; -- "member"."id"%TYPE[]
jbe@5 4100 "loop_member_id_v" "member"."id"%TYPE;
jbe@5 4101 "output_row" "delegation_chain_row";
jbe@5 4102 "output_rows" "delegation_chain_row"[];
jbe@255 4103 "simulate_v" BOOLEAN;
jbe@255 4104 "simulate_here_v" BOOLEAN;
jbe@5 4105 "delegation_row" "delegation"%ROWTYPE;
jbe@5 4106 "row_count" INT4;
jbe@5 4107 "i" INT4;
jbe@5 4108 "loop_v" BOOLEAN;
jbe@5 4109 BEGIN
jbe@255 4110 IF "simulate_trustee_id_p" NOTNULL AND "simulate_default_p" THEN
jbe@255 4111 RAISE EXCEPTION 'Both "simulate_trustee_id_p" is set, and "simulate_default_p" is true';
jbe@255 4112 END IF;
jbe@255 4113 IF "simulate_trustee_id_p" NOTNULL OR "simulate_default_p" THEN
jbe@255 4114 "simulate_v" := TRUE;
jbe@255 4115 ELSE
jbe@255 4116 "simulate_v" := FALSE;
jbe@255 4117 END IF;
jbe@97 4118 IF
jbe@97 4119 "unit_id_p" NOTNULL AND
jbe@97 4120 "area_id_p" ISNULL AND
jbe@97 4121 "issue_id_p" ISNULL
jbe@97 4122 THEN
jbe@97 4123 "scope_v" := 'unit';
jbe@97 4124 "unit_id_v" := "unit_id_p";
jbe@97 4125 ELSIF
jbe@97 4126 "unit_id_p" ISNULL AND
jbe@97 4127 "area_id_p" NOTNULL AND
jbe@97 4128 "issue_id_p" ISNULL
jbe@97 4129 THEN
jbe@97 4130 "scope_v" := 'area';
jbe@97 4131 "area_id_v" := "area_id_p";
jbe@97 4132 SELECT "unit_id" INTO "unit_id_v"
jbe@97 4133 FROM "area" WHERE "id" = "area_id_v";
jbe@97 4134 ELSIF
jbe@97 4135 "unit_id_p" ISNULL AND
jbe@97 4136 "area_id_p" ISNULL AND
jbe@97 4137 "issue_id_p" NOTNULL
jbe@97 4138 THEN
jbe@242 4139 SELECT INTO "issue_row" * FROM "issue" WHERE "id" = "issue_id_p";
jbe@242 4140 IF "issue_row"."id" ISNULL THEN
jbe@242 4141 RETURN;
jbe@242 4142 END IF;
jbe@242 4143 IF "issue_row"."closed" NOTNULL THEN
jbe@255 4144 IF "simulate_v" THEN
jbe@242 4145 RAISE EXCEPTION 'Tried to simulate delegation chain for closed issue.';
jbe@242 4146 END IF;
jbe@242 4147 FOR "output_row" IN
jbe@242 4148 SELECT * FROM
jbe@242 4149 "delegation_chain_for_closed_issue"("member_id_p", "issue_id_p")
jbe@242 4150 LOOP
jbe@242 4151 RETURN NEXT "output_row";
jbe@242 4152 END LOOP;
jbe@242 4153 RETURN;
jbe@242 4154 END IF;
jbe@97 4155 "scope_v" := 'issue';
jbe@97 4156 SELECT "area_id" INTO "area_id_v"
jbe@97 4157 FROM "issue" WHERE "id" = "issue_id_p";
jbe@97 4158 SELECT "unit_id" INTO "unit_id_v"
jbe@97 4159 FROM "area" WHERE "id" = "area_id_v";
jbe@97 4160 ELSE
jbe@97 4161 RAISE EXCEPTION 'Exactly one of unit_id_p, area_id_p, or issue_id_p must be NOTNULL.';
jbe@97 4162 END IF;
jbe@5 4163 "visited_member_ids" := '{}';
jbe@5 4164 "loop_member_id_v" := NULL;
jbe@5 4165 "output_rows" := '{}';
jbe@5 4166 "output_row"."index" := 0;
jbe@5 4167 "output_row"."member_id" := "member_id_p";
jbe@97 4168 "output_row"."member_valid" := TRUE;
jbe@5 4169 "output_row"."participation" := FALSE;
jbe@5 4170 "output_row"."overridden" := FALSE;
jbe@86 4171 "output_row"."disabled_out" := FALSE;
jbe@5 4172 "output_row"."scope_out" := NULL;
jbe@5 4173 LOOP
jbe@5 4174 IF "visited_member_ids" @> ARRAY["output_row"."member_id"] THEN
jbe@5 4175 "loop_member_id_v" := "output_row"."member_id";
jbe@5 4176 ELSE
jbe@5 4177 "visited_member_ids" :=
jbe@5 4178 "visited_member_ids" || "output_row"."member_id";
jbe@5 4179 END IF;
jbe@241 4180 IF "output_row"."participation" ISNULL THEN
jbe@241 4181 "output_row"."overridden" := NULL;
jbe@241 4182 ELSIF "output_row"."participation" THEN
jbe@5 4183 "output_row"."overridden" := TRUE;
jbe@5 4184 END IF;
jbe@5 4185 "output_row"."scope_in" := "output_row"."scope_out";
jbe@255 4186 "output_row"."member_valid" := EXISTS (
jbe@97 4187 SELECT NULL FROM "member" JOIN "privilege"
jbe@97 4188 ON "privilege"."member_id" = "member"."id"
jbe@97 4189 AND "privilege"."unit_id" = "unit_id_v"
jbe@97 4190 WHERE "id" = "output_row"."member_id"
jbe@97 4191 AND "member"."active" AND "privilege"."voting_right"
jbe@255 4192 );
jbe@255 4193 "simulate_here_v" := (
jbe@255 4194 "simulate_v" AND
jbe@255 4195 "output_row"."member_id" = "member_id_p"
jbe@255 4196 );
jbe@255 4197 "delegation_row" := ROW(NULL);
jbe@255 4198 IF "output_row"."member_valid" OR "simulate_here_v" THEN
jbe@97 4199 IF "scope_v" = 'unit' THEN
jbe@255 4200 IF NOT "simulate_here_v" THEN
jbe@255 4201 SELECT * INTO "delegation_row" FROM "delegation"
jbe@255 4202 WHERE "truster_id" = "output_row"."member_id"
jbe@255 4203 AND "unit_id" = "unit_id_v";
jbe@255 4204 END IF;
jbe@97 4205 ELSIF "scope_v" = 'area' THEN
jbe@255 4206 IF "simulate_here_v" THEN
jbe@255 4207 IF "simulate_trustee_id_p" ISNULL THEN
jbe@255 4208 SELECT * INTO "delegation_row" FROM "delegation"
jbe@255 4209 WHERE "truster_id" = "output_row"."member_id"
jbe@255 4210 AND "unit_id" = "unit_id_v";
jbe@255 4211 END IF;
jbe@255 4212 ELSE
jbe@255 4213 SELECT * INTO "delegation_row" FROM "delegation"
jbe@255 4214 WHERE "truster_id" = "output_row"."member_id"
jbe@255 4215 AND (
jbe@255 4216 "unit_id" = "unit_id_v" OR
jbe@255 4217 "area_id" = "area_id_v"
jbe@255 4218 )
jbe@255 4219 ORDER BY "scope" DESC;
jbe@255 4220 END IF;
jbe@97 4221 ELSIF "scope_v" = 'issue' THEN
jbe@241 4222 IF "issue_row"."fully_frozen" ISNULL THEN
jbe@241 4223 "output_row"."participation" := EXISTS (
jbe@241 4224 SELECT NULL FROM "interest"
jbe@241 4225 WHERE "issue_id" = "issue_id_p"
jbe@241 4226 AND "member_id" = "output_row"."member_id"
jbe@241 4227 );
jbe@241 4228 ELSE
jbe@241 4229 IF "output_row"."member_id" = "member_id_p" THEN
jbe@241 4230 "output_row"."participation" := EXISTS (
jbe@241 4231 SELECT NULL FROM "direct_voter"
jbe@241 4232 WHERE "issue_id" = "issue_id_p"
jbe@241 4233 AND "member_id" = "output_row"."member_id"
jbe@241 4234 );
jbe@241 4235 ELSE
jbe@241 4236 "output_row"."participation" := NULL;
jbe@241 4237 END IF;
jbe@241 4238 END IF;
jbe@255 4239 IF "simulate_here_v" THEN
jbe@255 4240 IF "simulate_trustee_id_p" ISNULL THEN
jbe@255 4241 SELECT * INTO "delegation_row" FROM "delegation"
jbe@255 4242 WHERE "truster_id" = "output_row"."member_id"
jbe@255 4243 AND (
jbe@255 4244 "unit_id" = "unit_id_v" OR
jbe@255 4245 "area_id" = "area_id_v"
jbe@255 4246 )
jbe@255 4247 ORDER BY "scope" DESC;
jbe@255 4248 END IF;
jbe@255 4249 ELSE
jbe@255 4250 SELECT * INTO "delegation_row" FROM "delegation"
jbe@255 4251 WHERE "truster_id" = "output_row"."member_id"
jbe@255 4252 AND (
jbe@255 4253 "unit_id" = "unit_id_v" OR
jbe@255 4254 "area_id" = "area_id_v" OR
jbe@255 4255 "issue_id" = "issue_id_p"
jbe@255 4256 )
jbe@255 4257 ORDER BY "scope" DESC;
jbe@255 4258 END IF;
jbe@5 4259 END IF;
jbe@5 4260 ELSE
jbe@5 4261 "output_row"."participation" := FALSE;
jbe@5 4262 END IF;
jbe@255 4263 IF "simulate_here_v" AND "simulate_trustee_id_p" NOTNULL THEN
jbe@97 4264 "output_row"."scope_out" := "scope_v";
jbe@5 4265 "output_rows" := "output_rows" || "output_row";
jbe@5 4266 "output_row"."member_id" := "simulate_trustee_id_p";
jbe@5 4267 ELSIF "delegation_row"."trustee_id" NOTNULL THEN
jbe@10 4268 "output_row"."scope_out" := "delegation_row"."scope";
jbe@5 4269 "output_rows" := "output_rows" || "output_row";
jbe@5 4270 "output_row"."member_id" := "delegation_row"."trustee_id";
jbe@86 4271 ELSIF "delegation_row"."scope" NOTNULL THEN
jbe@86 4272 "output_row"."scope_out" := "delegation_row"."scope";
jbe@86 4273 "output_row"."disabled_out" := TRUE;
jbe@86 4274 "output_rows" := "output_rows" || "output_row";
jbe@86 4275 EXIT;
jbe@5 4276 ELSE
jbe@5 4277 "output_row"."scope_out" := NULL;
jbe@5 4278 "output_rows" := "output_rows" || "output_row";
jbe@5 4279 EXIT;
jbe@5 4280 END IF;
jbe@5 4281 EXIT WHEN "loop_member_id_v" NOTNULL;
jbe@5 4282 "output_row"."index" := "output_row"."index" + 1;
jbe@5 4283 END LOOP;
jbe@5 4284 "row_count" := array_upper("output_rows", 1);
jbe@5 4285 "i" := 1;
jbe@5 4286 "loop_v" := FALSE;
jbe@5 4287 LOOP
jbe@5 4288 "output_row" := "output_rows"["i"];
jbe@98 4289 EXIT WHEN "output_row" ISNULL; -- NOTE: ISNULL and NOT ... NOTNULL produce different results!
jbe@5 4290 IF "loop_v" THEN
jbe@5 4291 IF "i" + 1 = "row_count" THEN
jbe@5 4292 "output_row"."loop" := 'last';
jbe@5 4293 ELSIF "i" = "row_count" THEN
jbe@5 4294 "output_row"."loop" := 'repetition';
jbe@5 4295 ELSE
jbe@5 4296 "output_row"."loop" := 'intermediate';
jbe@5 4297 END IF;
jbe@5 4298 ELSIF "output_row"."member_id" = "loop_member_id_v" THEN
jbe@5 4299 "output_row"."loop" := 'first';
jbe@5 4300 "loop_v" := TRUE;
jbe@5 4301 END IF;
jbe@97 4302 IF "scope_v" = 'unit' THEN
jbe@5 4303 "output_row"."participation" := NULL;
jbe@5 4304 END IF;
jbe@5 4305 RETURN NEXT "output_row";
jbe@5 4306 "i" := "i" + 1;
jbe@5 4307 END LOOP;
jbe@5 4308 RETURN;
jbe@5 4309 END;
jbe@5 4310 $$;
jbe@5 4311
jbe@5 4312 COMMENT ON FUNCTION "delegation_chain"
jbe@5 4313 ( "member"."id"%TYPE,
jbe@97 4314 "unit"."id"%TYPE,
jbe@5 4315 "area"."id"%TYPE,
jbe@5 4316 "issue"."id"%TYPE,
jbe@255 4317 "member"."id"%TYPE,
jbe@255 4318 BOOLEAN )
jbe@242 4319 IS 'Shows a delegation chain for unit, area, or issue; See "delegation_chain_row" type for more information';
jbe@242 4320
jbe@242 4321
jbe@242 4322
jbe@242 4323 ---------------------------------------------------------
jbe@242 4324 -- Single row returning function for delegation chains --
jbe@242 4325 ---------------------------------------------------------
jbe@242 4326
jbe@242 4327
jbe@242 4328 CREATE TYPE "delegation_info_loop_type" AS ENUM
jbe@242 4329 ('own', 'first', 'first_ellipsis', 'other', 'other_ellipsis');
jbe@240 4330
jbe@243 4331 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 4332
jbe@243 4333
jbe@240 4334 CREATE TYPE "delegation_info_type" AS (
jbe@242 4335 "own_participation" BOOLEAN,
jbe@242 4336 "own_delegation_scope" "delegation_scope",
jbe@242 4337 "first_trustee_id" INT4,
jbe@240 4338 "first_trustee_participation" BOOLEAN,
jbe@242 4339 "first_trustee_ellipsis" BOOLEAN,
jbe@242 4340 "other_trustee_id" INT4,
jbe@240 4341 "other_trustee_participation" BOOLEAN,
jbe@242 4342 "other_trustee_ellipsis" BOOLEAN,
jbe@253 4343 "delegation_loop" "delegation_info_loop_type",
jbe@253 4344 "participating_member_id" INT4 );
jbe@240 4345
jbe@243 4346 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 4347
jbe@243 4348 COMMENT ON COLUMN "delegation_info_type"."own_participation" IS 'Member is directly participating';
jbe@243 4349 COMMENT ON COLUMN "delegation_info_type"."own_delegation_scope" IS 'Delegation scope of member';
jbe@243 4350 COMMENT ON COLUMN "delegation_info_type"."first_trustee_id" IS 'Direct trustee of member';
jbe@243 4351 COMMENT ON COLUMN "delegation_info_type"."first_trustee_participation" IS 'Direct trustee of member is participating';
jbe@243 4352 COMMENT ON COLUMN "delegation_info_type"."first_trustee_ellipsis" IS 'Ellipsis in delegation chain after "first_trustee"';
jbe@243 4353 COMMENT ON COLUMN "delegation_info_type"."other_trustee_id" IS 'Another relevant trustee (due to participation)';
jbe@243 4354 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 4355 COMMENT ON COLUMN "delegation_info_type"."other_trustee_ellipsis" IS 'Ellipsis in delegation chain after "other_trustee"';
jbe@243 4356 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 4357 COMMENT ON COLUMN "delegation_info_type"."participating_member_id" IS 'First participating member in delegation chain';
jbe@243 4358
jbe@243 4359
jbe@240 4360 CREATE FUNCTION "delegation_info"
jbe@242 4361 ( "member_id_p" "member"."id"%TYPE,
jbe@242 4362 "unit_id_p" "unit"."id"%TYPE,
jbe@242 4363 "area_id_p" "area"."id"%TYPE,
jbe@242 4364 "issue_id_p" "issue"."id"%TYPE,
jbe@255 4365 "simulate_trustee_id_p" "member"."id"%TYPE DEFAULT NULL,
jbe@255 4366 "simulate_default_p" BOOLEAN DEFAULT FALSE )
jbe@240 4367 RETURNS "delegation_info_type"
jbe@240 4368 LANGUAGE 'plpgsql' STABLE AS $$
jbe@240 4369 DECLARE
jbe@242 4370 "current_row" "delegation_chain_row";
jbe@242 4371 "result" "delegation_info_type";
jbe@240 4372 BEGIN
jbe@242 4373 "result"."own_participation" := FALSE;
jbe@242 4374 FOR "current_row" IN
jbe@242 4375 SELECT * FROM "delegation_chain"(
jbe@242 4376 "member_id_p",
jbe@242 4377 "unit_id_p", "area_id_p", "issue_id_p",
jbe@255 4378 "simulate_trustee_id_p", "simulate_default_p")
jbe@242 4379 LOOP
jbe@253 4380 IF
jbe@253 4381 "result"."participating_member_id" ISNULL AND
jbe@253 4382 "current_row"."participation"
jbe@253 4383 THEN
jbe@253 4384 "result"."participating_member_id" := "current_row"."member_id";
jbe@253 4385 END IF;
jbe@242 4386 IF "current_row"."member_id" = "member_id_p" THEN
jbe@242 4387 "result"."own_participation" := "current_row"."participation";
jbe@242 4388 "result"."own_delegation_scope" := "current_row"."scope_out";
jbe@242 4389 IF "current_row"."loop" = 'first' THEN
jbe@242 4390 "result"."delegation_loop" := 'own';
jbe@242 4391 END IF;
jbe@242 4392 ELSIF
jbe@242 4393 "current_row"."member_valid" AND
jbe@242 4394 ( "current_row"."loop" ISNULL OR
jbe@242 4395 "current_row"."loop" != 'repetition' )
jbe@242 4396 THEN
jbe@242 4397 IF "result"."first_trustee_id" ISNULL THEN
jbe@242 4398 "result"."first_trustee_id" := "current_row"."member_id";
jbe@242 4399 "result"."first_trustee_participation" := "current_row"."participation";
jbe@242 4400 "result"."first_trustee_ellipsis" := FALSE;
jbe@242 4401 IF "current_row"."loop" = 'first' THEN
jbe@242 4402 "result"."delegation_loop" := 'first';
jbe@242 4403 END IF;
jbe@242 4404 ELSIF "result"."other_trustee_id" ISNULL THEN
jbe@247 4405 IF "current_row"."participation" AND NOT "current_row"."overridden" THEN
jbe@242 4406 "result"."other_trustee_id" := "current_row"."member_id";
jbe@242 4407 "result"."other_trustee_participation" := TRUE;
jbe@242 4408 "result"."other_trustee_ellipsis" := FALSE;
jbe@242 4409 IF "current_row"."loop" = 'first' THEN
jbe@242 4410 "result"."delegation_loop" := 'other';
jbe@240 4411 END IF;
jbe@240 4412 ELSE
jbe@242 4413 "result"."first_trustee_ellipsis" := TRUE;
jbe@242 4414 IF "current_row"."loop" = 'first' THEN
jbe@242 4415 "result"."delegation_loop" := 'first_ellipsis';
jbe@242 4416 END IF;
jbe@242 4417 END IF;
jbe@242 4418 ELSE
jbe@242 4419 "result"."other_trustee_ellipsis" := TRUE;
jbe@242 4420 IF "current_row"."loop" = 'first' THEN
jbe@242 4421 "result"."delegation_loop" := 'other_ellipsis';
jbe@240 4422 END IF;
jbe@240 4423 END IF;
jbe@240 4424 END IF;
jbe@242 4425 END LOOP;
jbe@240 4426 RETURN "result";
jbe@240 4427 END;
jbe@240 4428 $$;
jbe@240 4429
jbe@243 4430 COMMENT ON FUNCTION "delegation_info"
jbe@243 4431 ( "member"."id"%TYPE,
jbe@243 4432 "unit"."id"%TYPE,
jbe@243 4433 "area"."id"%TYPE,
jbe@243 4434 "issue"."id"%TYPE,
jbe@255 4435 "member"."id"%TYPE,
jbe@255 4436 BOOLEAN )
jbe@243 4437 IS 'Notable information about a delegation chain for unit, area, or issue; See "delegation_info_type" for more information';
jbe@243 4438
jbe@240 4439
jbe@240 4440
jbe@333 4441 ---------------------------
jbe@333 4442 -- Transaction isolation --
jbe@333 4443 ---------------------------
jbe@333 4444
jbe@344 4445
jbe@333 4446 CREATE FUNCTION "require_transaction_isolation"()
jbe@333 4447 RETURNS VOID
jbe@333 4448 LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@333 4449 BEGIN
jbe@333 4450 IF
jbe@333 4451 current_setting('transaction_isolation') NOT IN
jbe@333 4452 ('repeatable read', 'serializable')
jbe@333 4453 THEN
jbe@463 4454 RAISE EXCEPTION 'Insufficient transaction isolation level' USING
jbe@463 4455 HINT = 'Consider using SET TRANSACTION ISOLATION LEVEL REPEATABLE READ.';
jbe@333 4456 END IF;
jbe@333 4457 RETURN;
jbe@333 4458 END;
jbe@333 4459 $$;
jbe@333 4460
jbe@344 4461 COMMENT ON FUNCTION "require_transaction_isolation"() IS 'Throws an exception, if transaction isolation level is too low to provide a consistent snapshot';
jbe@344 4462
jbe@333 4463
jbe@333 4464 CREATE FUNCTION "dont_require_transaction_isolation"()
jbe@333 4465 RETURNS VOID
jbe@333 4466 LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@333 4467 BEGIN
jbe@333 4468 IF
jbe@333 4469 current_setting('transaction_isolation') IN
jbe@333 4470 ('repeatable read', 'serializable')
jbe@333 4471 THEN
jbe@333 4472 RAISE WARNING 'Unneccessary transaction isolation level: %',
jbe@333 4473 current_setting('transaction_isolation');
jbe@333 4474 END IF;
jbe@333 4475 RETURN;
jbe@333 4476 END;
jbe@333 4477 $$;
jbe@333 4478
jbe@344 4479 COMMENT ON FUNCTION "dont_require_transaction_isolation"() IS 'Raises a warning, if transaction isolation level is higher than READ COMMITTED';
jbe@344 4480
jbe@333 4481
jbe@333 4482
jbe@491 4483 -------------------------
jbe@491 4484 -- Notification system --
jbe@491 4485 -------------------------
jbe@491 4486
jbe@491 4487 CREATE FUNCTION "get_initiatives_for_notification"
jbe@501 4488 ( "recipient_id_p" "member"."id"%TYPE )
jbe@491 4489 RETURNS SETOF "initiative_for_notification"
jbe@491 4490 LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@491 4491 DECLARE
jbe@491 4492 "result_row" "initiative_for_notification"%ROWTYPE;
jbe@491 4493 "last_draft_id_v" "draft"."id"%TYPE;
jbe@491 4494 "last_suggestion_id_v" "suggestion"."id"%TYPE;
jbe@491 4495 BEGIN
jbe@491 4496 PERFORM "require_transaction_isolation"();
jbe@501 4497 PERFORM NULL FROM "member" WHERE "id" = "recipient_id_p" FOR UPDATE;
jbe@491 4498 FOR "result_row" IN
jbe@491 4499 SELECT * FROM "initiative_for_notification"
jbe@501 4500 WHERE "recipient_id" = "recipient_id_p"
jbe@491 4501 LOOP
jbe@491 4502 SELECT "id" INTO "last_draft_id_v" FROM "draft"
jbe@499 4503 WHERE "draft"."initiative_id" = "result_row"."initiative_id"
jbe@491 4504 ORDER BY "id" DESC LIMIT 1;
jbe@491 4505 SELECT "id" INTO "last_suggestion_id_v" FROM "suggestion"
jbe@499 4506 WHERE "suggestion"."initiative_id" = "result_row"."initiative_id"
jbe@491 4507 ORDER BY "id" DESC LIMIT 1;
jbe@507 4508 INSERT INTO "notification_initiative_sent"
jbe@491 4509 ("member_id", "initiative_id", "last_draft_id", "last_suggestion_id")
jbe@491 4510 VALUES (
jbe@501 4511 "recipient_id_p",
jbe@499 4512 "result_row"."initiative_id",
jbe@493 4513 "last_draft_id_v",
jbe@493 4514 "last_suggestion_id_v" )
jbe@491 4515 ON CONFLICT ("member_id", "initiative_id") DO UPDATE SET
jbe@517 4516 "last_draft_id" = "last_draft_id_v",
jbe@517 4517 "last_suggestion_id" = "last_suggestion_id_v";
jbe@491 4518 RETURN NEXT "result_row";
jbe@491 4519 END LOOP;
jbe@507 4520 DELETE FROM "notification_initiative_sent"
jbe@491 4521 USING "initiative", "issue"
jbe@507 4522 WHERE "notification_initiative_sent"."member_id" = "recipient_id_p"
jbe@507 4523 AND "initiative"."id" = "notification_initiative_sent"."initiative_id"
jbe@491 4524 AND "issue"."id" = "initiative"."issue_id"
jbe@491 4525 AND ( "issue"."closed" NOTNULL OR "issue"."fully_frozen" NOTNULL );
jbe@505 4526 UPDATE "member" SET
jbe@506 4527 "notification_counter" = "notification_counter" + 1,
jbe@505 4528 "notification_sent" = now()
jbe@501 4529 WHERE "id" = "recipient_id_p";
jbe@491 4530 RETURN;
jbe@491 4531 END;
jbe@491 4532 $$;
jbe@491 4533
jbe@511 4534 COMMENT ON FUNCTION "get_initiatives_for_notification"
jbe@511 4535 ( "member"."id"%TYPE )
jbe@511 4536 IS 'Returns rows from view "initiative_for_notification" for a given recipient while updating table "notification_initiative_sent" and columns "notification_counter" and "notification_sent" of "member" table';
jbe@511 4537
jbe@491 4538
jbe@491 4539
jbe@103 4540 ------------------------------------------------------------------------
jbe@103 4541 -- Regular tasks, except calculcation of snapshots and voting results --
jbe@103 4542 ------------------------------------------------------------------------
jbe@103 4543
jbe@333 4544
jbe@184 4545 CREATE FUNCTION "check_activity"()
jbe@103 4546 RETURNS VOID
jbe@103 4547 LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@104 4548 DECLARE
jbe@104 4549 "system_setting_row" "system_setting"%ROWTYPE;
jbe@103 4550 BEGIN
jbe@333 4551 PERFORM "dont_require_transaction_isolation"();
jbe@104 4552 SELECT * INTO "system_setting_row" FROM "system_setting";
jbe@104 4553 IF "system_setting_row"."member_ttl" NOTNULL THEN
jbe@104 4554 UPDATE "member" SET "active" = FALSE
jbe@104 4555 WHERE "active" = TRUE
jbe@184 4556 AND "last_activity" < (now() - "system_setting_row"."member_ttl")::DATE;
jbe@104 4557 END IF;
jbe@103 4558 RETURN;
jbe@103 4559 END;
jbe@103 4560 $$;
jbe@103 4561
jbe@184 4562 COMMENT ON FUNCTION "check_activity"() IS 'Deactivates members when "last_activity" is older than "system_setting"."member_ttl".';
jbe@103 4563
jbe@4 4564
jbe@4 4565 CREATE FUNCTION "calculate_member_counts"()
jbe@4 4566 RETURNS VOID
jbe@4 4567 LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@4 4568 BEGIN
jbe@333 4569 PERFORM "require_transaction_isolation"();
jbe@4 4570 DELETE FROM "member_count";
jbe@5 4571 INSERT INTO "member_count" ("total_count")
jbe@5 4572 SELECT "total_count" FROM "member_count_view";
jbe@97 4573 UPDATE "unit" SET "member_count" = "view"."member_count"
jbe@97 4574 FROM "unit_member_count" AS "view"
jbe@97 4575 WHERE "view"."unit_id" = "unit"."id";
jbe@4 4576 RETURN;
jbe@4 4577 END;
jbe@4 4578 $$;
jbe@4 4579
jbe@532 4580 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 "unit_member_count"';
jbe@532 4581
jbe@532 4582
jbe@532 4583 CREATE FUNCTION "calculate_area_quorum"()
jbe@532 4584 RETURNS VOID
jbe@532 4585 LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@532 4586 BEGIN
jbe@532 4587 PERFORM "dont_require_transaction_isolation"();
jbe@532 4588 UPDATE "area" SET "issue_quorum" = "view"."issue_quorum"
jbe@532 4589 FROM "area_quorum" AS "view"
jbe@532 4590 WHERE "view"."area_id" = "area"."id";
jbe@532 4591 RETURN;
jbe@532 4592 END;
jbe@532 4593 $$;
jbe@532 4594
jbe@532 4595 COMMENT ON FUNCTION "calculate_area_quorum"() IS 'Calculate column "issue_quorum" in table "area" from view "area_quorum"';
jbe@4 4596
jbe@4 4597
jbe@4 4598
jbe@327 4599 ------------------------------------
jbe@327 4600 -- Calculation of harmonic weight --
jbe@327 4601 ------------------------------------
jbe@310 4602
jbe@312 4603
jbe@310 4604 CREATE VIEW "remaining_harmonic_supporter_weight" AS
jbe@310 4605 SELECT
jbe@528 4606 "direct_interest_snapshot"."snapshot_id",
jbe@310 4607 "direct_interest_snapshot"."issue_id",
jbe@310 4608 "direct_interest_snapshot"."member_id",
jbe@310 4609 "direct_interest_snapshot"."weight" AS "weight_num",
jbe@310 4610 count("initiative"."id") AS "weight_den"
jbe@312 4611 FROM "issue"
jbe@312 4612 JOIN "direct_interest_snapshot"
jbe@528 4613 ON "issue"."latest_snapshot_id" = "direct_interest_snapshot"."snapshot_id"
jbe@528 4614 AND "issue"."id" = "direct_interest_snapshot"."issue_id"
jbe@327 4615 JOIN "initiative"
jbe@327 4616 ON "issue"."id" = "initiative"."issue_id"
jbe@327 4617 AND "initiative"."harmonic_weight" ISNULL
jbe@310 4618 JOIN "direct_supporter_snapshot"
jbe@528 4619 ON "issue"."latest_snapshot_id" = "direct_supporter_snapshot"."snapshot_id"
jbe@528 4620 AND "initiative"."id" = "direct_supporter_snapshot"."initiative_id"
jbe@310 4621 AND "direct_interest_snapshot"."member_id" = "direct_supporter_snapshot"."member_id"
jbe@321 4622 AND (
jbe@321 4623 "direct_supporter_snapshot"."satisfied" = TRUE OR
jbe@321 4624 coalesce("initiative"."admitted", FALSE) = FALSE
jbe@321 4625 )
jbe@310 4626 GROUP BY
jbe@528 4627 "direct_interest_snapshot"."snapshot_id",
jbe@310 4628 "direct_interest_snapshot"."issue_id",
jbe@310 4629 "direct_interest_snapshot"."member_id",
jbe@310 4630 "direct_interest_snapshot"."weight";
jbe@310 4631
jbe@310 4632 COMMENT ON VIEW "remaining_harmonic_supporter_weight" IS 'Helper view for function "set_harmonic_initiative_weights"';
jbe@310 4633
jbe@310 4634
jbe@310 4635 CREATE VIEW "remaining_harmonic_initiative_weight_summands" AS
jbe@310 4636 SELECT
jbe@310 4637 "initiative"."issue_id",
jbe@310 4638 "initiative"."id" AS "initiative_id",
jbe@320 4639 "initiative"."admitted",
jbe@310 4640 sum("remaining_harmonic_supporter_weight"."weight_num") AS "weight_num",
jbe@310 4641 "remaining_harmonic_supporter_weight"."weight_den"
jbe@310 4642 FROM "remaining_harmonic_supporter_weight"
jbe@327 4643 JOIN "initiative"
jbe@327 4644 ON "remaining_harmonic_supporter_weight"."issue_id" = "initiative"."issue_id"
jbe@327 4645 AND "initiative"."harmonic_weight" ISNULL
jbe@310 4646 JOIN "direct_supporter_snapshot"
jbe@528 4647 ON "remaining_harmonic_supporter_weight"."snapshot_id" = "direct_supporter_snapshot"."snapshot_id"
jbe@528 4648 AND "initiative"."id" = "direct_supporter_snapshot"."initiative_id"
jbe@310 4649 AND "remaining_harmonic_supporter_weight"."member_id" = "direct_supporter_snapshot"."member_id"
jbe@321 4650 AND (
jbe@321 4651 "direct_supporter_snapshot"."satisfied" = TRUE OR
jbe@321 4652 coalesce("initiative"."admitted", FALSE) = FALSE
jbe@321 4653 )
jbe@310 4654 GROUP BY
jbe@310 4655 "initiative"."issue_id",
jbe@310 4656 "initiative"."id",
jbe@320 4657 "initiative"."admitted",
jbe@310 4658 "remaining_harmonic_supporter_weight"."weight_den";
jbe@310 4659
jbe@310 4660 COMMENT ON VIEW "remaining_harmonic_initiative_weight_summands" IS 'Helper view for function "set_harmonic_initiative_weights"';
jbe@310 4661
jbe@310 4662
jbe@349 4663 CREATE VIEW "remaining_harmonic_initiative_weight_dummies" AS
jbe@349 4664 SELECT
jbe@349 4665 "issue_id",
jbe@349 4666 "id" AS "initiative_id",
jbe@349 4667 "admitted",
jbe@349 4668 0 AS "weight_num",
jbe@349 4669 1 AS "weight_den"
jbe@349 4670 FROM "initiative"
jbe@349 4671 WHERE "harmonic_weight" ISNULL;
jbe@349 4672
jbe@349 4673 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 4674
jbe@349 4675
jbe@310 4676 CREATE FUNCTION "set_harmonic_initiative_weights"
jbe@310 4677 ( "issue_id_p" "issue"."id"%TYPE )
jbe@310 4678 RETURNS VOID
jbe@310 4679 LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@310 4680 DECLARE
jbe@310 4681 "weight_row" "remaining_harmonic_initiative_weight_summands"%ROWTYPE;
jbe@310 4682 "i" INT4;
jbe@310 4683 "count_v" INT4;
jbe@310 4684 "summand_v" FLOAT;
jbe@310 4685 "id_ary" INT4[];
jbe@310 4686 "weight_ary" FLOAT[];
jbe@310 4687 "min_weight_v" FLOAT;
jbe@310 4688 BEGIN
jbe@333 4689 PERFORM "require_transaction_isolation"();
jbe@312 4690 UPDATE "initiative" SET "harmonic_weight" = NULL
jbe@312 4691 WHERE "issue_id" = "issue_id_p";
jbe@310 4692 LOOP
jbe@310 4693 "min_weight_v" := NULL;
jbe@310 4694 "i" := 0;
jbe@310 4695 "count_v" := 0;
jbe@310 4696 FOR "weight_row" IN
jbe@310 4697 SELECT * FROM "remaining_harmonic_initiative_weight_summands"
jbe@310 4698 WHERE "issue_id" = "issue_id_p"
jbe@320 4699 AND (
jbe@320 4700 coalesce("admitted", FALSE) = FALSE OR NOT EXISTS (
jbe@320 4701 SELECT NULL FROM "initiative"
jbe@320 4702 WHERE "issue_id" = "issue_id_p"
jbe@320 4703 AND "harmonic_weight" ISNULL
jbe@320 4704 AND coalesce("admitted", FALSE) = FALSE
jbe@320 4705 )
jbe@320 4706 )
jbe@349 4707 UNION ALL -- needed for corner cases
jbe@349 4708 SELECT * FROM "remaining_harmonic_initiative_weight_dummies"
jbe@349 4709 WHERE "issue_id" = "issue_id_p"
jbe@349 4710 AND (
jbe@349 4711 coalesce("admitted", FALSE) = FALSE OR NOT EXISTS (
jbe@349 4712 SELECT NULL FROM "initiative"
jbe@349 4713 WHERE "issue_id" = "issue_id_p"
jbe@349 4714 AND "harmonic_weight" ISNULL
jbe@349 4715 AND coalesce("admitted", FALSE) = FALSE
jbe@349 4716 )
jbe@349 4717 )
jbe@310 4718 ORDER BY "initiative_id" DESC, "weight_den" DESC
jbe@320 4719 -- NOTE: non-admitted initiatives placed first (at last positions),
jbe@320 4720 -- latest initiatives treated worse in case of tie
jbe@310 4721 LOOP
jbe@310 4722 "summand_v" := "weight_row"."weight_num"::FLOAT / "weight_row"."weight_den"::FLOAT;
jbe@310 4723 IF "i" = 0 OR "weight_row"."initiative_id" != "id_ary"["i"] THEN
jbe@310 4724 "i" := "i" + 1;
jbe@310 4725 "count_v" := "i";
jbe@310 4726 "id_ary"["i"] := "weight_row"."initiative_id";
jbe@310 4727 "weight_ary"["i"] := "summand_v";
jbe@310 4728 ELSE
jbe@310 4729 "weight_ary"["i"] := "weight_ary"["i"] + "summand_v";
jbe@310 4730 END IF;
jbe@310 4731 END LOOP;
jbe@310 4732 EXIT WHEN "count_v" = 0;
jbe@310 4733 "i" := 1;
jbe@310 4734 LOOP
jbe@313 4735 "weight_ary"["i"] := "weight_ary"["i"]::NUMERIC(18,9)::NUMERIC(12,3);
jbe@310 4736 IF "min_weight_v" ISNULL OR "weight_ary"["i"] < "min_weight_v" THEN
jbe@310 4737 "min_weight_v" := "weight_ary"["i"];
jbe@310 4738 END IF;
jbe@310 4739 "i" := "i" + 1;
jbe@310 4740 EXIT WHEN "i" > "count_v";
jbe@310 4741 END LOOP;
jbe@310 4742 "i" := 1;
jbe@310 4743 LOOP
jbe@310 4744 IF "weight_ary"["i"] = "min_weight_v" THEN
jbe@310 4745 UPDATE "initiative" SET "harmonic_weight" = "min_weight_v"
jbe@310 4746 WHERE "id" = "id_ary"["i"];
jbe@310 4747 EXIT;
jbe@310 4748 END IF;
jbe@310 4749 "i" := "i" + 1;
jbe@310 4750 END LOOP;
jbe@310 4751 END LOOP;
jbe@316 4752 UPDATE "initiative" SET "harmonic_weight" = 0
jbe@316 4753 WHERE "issue_id" = "issue_id_p" AND "harmonic_weight" ISNULL;
jbe@310 4754 END;
jbe@310 4755 $$;
jbe@310 4756
jbe@310 4757 COMMENT ON FUNCTION "set_harmonic_initiative_weights"
jbe@310 4758 ( "issue"."id"%TYPE )
jbe@310 4759 IS 'Calculates and sets "harmonic_weight" of initiatives in a given issue';
jbe@310 4760
jbe@310 4761
jbe@312 4762
jbe@0 4763 ------------------------------
jbe@0 4764 -- Calculation of snapshots --
jbe@0 4765 ------------------------------
jbe@0 4766
jbe@312 4767
jbe@528 4768 CREATE FUNCTION "weight_of_added_delegations_for_snapshot"
jbe@528 4769 ( "snapshot_id_p" "snapshot"."id"%TYPE,
jbe@528 4770 "issue_id_p" "issue"."id"%TYPE,
jbe@0 4771 "member_id_p" "member"."id"%TYPE,
jbe@0 4772 "delegate_member_ids_p" "delegating_interest_snapshot"."delegate_member_ids"%TYPE )
jbe@0 4773 RETURNS "direct_interest_snapshot"."weight"%TYPE
jbe@0 4774 LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@0 4775 DECLARE
jbe@0 4776 "issue_delegation_row" "issue_delegation"%ROWTYPE;
jbe@0 4777 "delegate_member_ids_v" "delegating_interest_snapshot"."delegate_member_ids"%TYPE;
jbe@0 4778 "weight_v" INT4;
jbe@8 4779 "sub_weight_v" INT4;
jbe@0 4780 BEGIN
jbe@336 4781 PERFORM "require_transaction_isolation"();
jbe@0 4782 "weight_v" := 0;
jbe@0 4783 FOR "issue_delegation_row" IN
jbe@0 4784 SELECT * FROM "issue_delegation"
jbe@0 4785 WHERE "trustee_id" = "member_id_p"
jbe@0 4786 AND "issue_id" = "issue_id_p"
jbe@0 4787 LOOP
jbe@0 4788 IF NOT EXISTS (
jbe@0 4789 SELECT NULL FROM "direct_interest_snapshot"
jbe@528 4790 WHERE "snapshot_id" = "snapshot_id_p"
jbe@528 4791 AND "issue_id" = "issue_id_p"
jbe@0 4792 AND "member_id" = "issue_delegation_row"."truster_id"
jbe@0 4793 ) AND NOT EXISTS (
jbe@0 4794 SELECT NULL FROM "delegating_interest_snapshot"
jbe@528 4795 WHERE "snapshot_id" = "snapshot_id_p"
jbe@528 4796 AND "issue_id" = "issue_id_p"
jbe@0 4797 AND "member_id" = "issue_delegation_row"."truster_id"
jbe@0 4798 ) THEN
jbe@0 4799 "delegate_member_ids_v" :=
jbe@0 4800 "member_id_p" || "delegate_member_ids_p";
jbe@10 4801 INSERT INTO "delegating_interest_snapshot" (
jbe@528 4802 "snapshot_id",
jbe@10 4803 "issue_id",
jbe@10 4804 "member_id",
jbe@10 4805 "scope",
jbe@10 4806 "delegate_member_ids"
jbe@10 4807 ) VALUES (
jbe@528 4808 "snapshot_id_p",
jbe@0 4809 "issue_id_p",
jbe@0 4810 "issue_delegation_row"."truster_id",
jbe@10 4811 "issue_delegation_row"."scope",
jbe@0 4812 "delegate_member_ids_v"
jbe@0 4813 );
jbe@8 4814 "sub_weight_v" := 1 +
jbe@528 4815 "weight_of_added_delegations_for_snapshot"(
jbe@528 4816 "snapshot_id_p",
jbe@0 4817 "issue_id_p",
jbe@0 4818 "issue_delegation_row"."truster_id",
jbe@0 4819 "delegate_member_ids_v"
jbe@0 4820 );
jbe@8 4821 UPDATE "delegating_interest_snapshot"
jbe@8 4822 SET "weight" = "sub_weight_v"
jbe@528 4823 WHERE "snapshot_id" = "snapshot_id_p"
jbe@528 4824 AND "issue_id" = "issue_id_p"
jbe@8 4825 AND "member_id" = "issue_delegation_row"."truster_id";
jbe@8 4826 "weight_v" := "weight_v" + "sub_weight_v";
jbe@0 4827 END IF;
jbe@0 4828 END LOOP;
jbe@0 4829 RETURN "weight_v";
jbe@0 4830 END;
jbe@0 4831 $$;
jbe@0 4832
jbe@528 4833 COMMENT ON FUNCTION "weight_of_added_delegations_for_snapshot"
jbe@528 4834 ( "snapshot"."id"%TYPE,
jbe@528 4835 "issue"."id"%TYPE,
jbe@0 4836 "member"."id"%TYPE,
jbe@0 4837 "delegating_interest_snapshot"."delegate_member_ids"%TYPE )
jbe@528 4838 IS 'Helper function for "fill_snapshot" function';
jbe@528 4839
jbe@528 4840
jbe@528 4841 CREATE FUNCTION "take_snapshot"
jbe@532 4842 ( "issue_id_p" "issue"."id"%TYPE,
jbe@532 4843 "area_id_p" "area"."id"%TYPE = NULL )
jbe@528 4844 RETURNS "snapshot"."id"%TYPE
jbe@0 4845 LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@0 4846 DECLARE
jbe@532 4847 "area_id_v" "area"."id"%TYPE;
jbe@532 4848 "unit_id_v" "unit"."id"%TYPE;
jbe@528 4849 "snapshot_id_v" "snapshot"."id"%TYPE;
jbe@528 4850 "issue_id_v" "issue"."id"%TYPE;
jbe@528 4851 "member_id_v" "member"."id"%TYPE;
jbe@0 4852 BEGIN
jbe@532 4853 IF "issue_id_p" NOTNULL AND "area_id_p" NOTNULL THEN
jbe@532 4854 RAISE EXCEPTION 'One of "issue_id_p" and "area_id_p" must be NULL';
jbe@532 4855 END IF;
jbe@336 4856 PERFORM "require_transaction_isolation"();
jbe@532 4857 IF "issue_id_p" ISNULL THEN
jbe@532 4858 "area_id_v" := "area_id_p";
jbe@532 4859 ELSE
jbe@532 4860 SELECT "area_id" INTO "area_id_v"
jbe@532 4861 FROM "issue" WHERE "id" = "issue_id_p";
jbe@532 4862 END IF;
jbe@532 4863 SELECT "unit_id" INTO "unit_id_v" FROM "area" WHERE "id" = "area_id_p";
jbe@532 4864 INSERT INTO "snapshot" ("area_id", "issue_id")
jbe@532 4865 VALUES ("area_id_v", "issue_id_p")
jbe@528 4866 RETURNING "id" INTO "snapshot_id_v";
jbe@532 4867 INSERT INTO "snapshot_population" ("snapshot_id", "member_id")
jbe@532 4868 SELECT "snapshot_id_v", "member_id"
jbe@532 4869 FROM "unit_member" WHERE "unit_id" = "unit_id_v";
jbe@532 4870 UPDATE "snapshot" SET
jbe@532 4871 "population" = (
jbe@532 4872 SELECT count(1) FROM "snapshot_population"
jbe@532 4873 WHERE "snapshot_id" = "snapshot_id_v"
jbe@532 4874 ) WHERE "id" = "snapshot_id_v";
jbe@528 4875 FOR "issue_id_v" IN
jbe@528 4876 SELECT "id" FROM "issue"
jbe@528 4877 WHERE CASE WHEN "issue_id_p" ISNULL THEN
jbe@532 4878 "area_id" = "area_id_p" AND
jbe@528 4879 "state" = 'admission'
jbe@528 4880 ELSE
jbe@528 4881 "id" = "issue_id_p"
jbe@528 4882 END
jbe@0 4883 LOOP
jbe@528 4884 INSERT INTO "snapshot_issue" ("snapshot_id", "issue_id")
jbe@528 4885 VALUES ("snapshot_id_v", "issue_id_v");
jbe@528 4886 INSERT INTO "direct_interest_snapshot"
jbe@528 4887 ("snapshot_id", "issue_id", "member_id")
jbe@528 4888 SELECT
jbe@528 4889 "snapshot_id_v" AS "snapshot_id",
jbe@528 4890 "issue_id_v" AS "issue_id",
jbe@528 4891 "member"."id" AS "member_id"
jbe@528 4892 FROM "issue"
jbe@528 4893 JOIN "area" ON "issue"."area_id" = "area"."id"
jbe@528 4894 JOIN "interest" ON "issue"."id" = "interest"."issue_id"
jbe@528 4895 JOIN "member" ON "interest"."member_id" = "member"."id"
jbe@528 4896 JOIN "privilege"
jbe@528 4897 ON "privilege"."unit_id" = "area"."unit_id"
jbe@528 4898 AND "privilege"."member_id" = "member"."id"
jbe@528 4899 WHERE "issue"."id" = "issue_id_v"
jbe@528 4900 AND "member"."active" AND "privilege"."voting_right";
jbe@528 4901 FOR "member_id_v" IN
jbe@528 4902 SELECT "member_id" FROM "direct_interest_snapshot"
jbe@528 4903 WHERE "snapshot_id" = "snapshot_id_v"
jbe@528 4904 AND "issue_id" = "issue_id_v"
jbe@528 4905 LOOP
jbe@528 4906 UPDATE "direct_interest_snapshot" SET
jbe@528 4907 "weight" = 1 +
jbe@528 4908 "weight_of_added_delegations_for_snapshot"(
jbe@528 4909 "snapshot_id_v",
jbe@528 4910 "issue_id_v",
jbe@528 4911 "member_id_v",
jbe@528 4912 '{}'
jbe@528 4913 )
jbe@528 4914 WHERE "snapshot_id" = "snapshot_id_v"
jbe@528 4915 AND "issue_id" = "issue_id_v"
jbe@528 4916 AND "member_id" = "member_id_v";
jbe@528 4917 END LOOP;
jbe@528 4918 INSERT INTO "direct_supporter_snapshot"
jbe@528 4919 ( "snapshot_id", "issue_id", "initiative_id", "member_id",
jbe@528 4920 "draft_id", "informed", "satisfied" )
jbe@528 4921 SELECT
jbe@528 4922 "snapshot_id_v" AS "snapshot_id",
jbe@528 4923 "issue_id_v" AS "issue_id",
jbe@528 4924 "initiative"."id" AS "initiative_id",
jbe@528 4925 "supporter"."member_id" AS "member_id",
jbe@528 4926 "supporter"."draft_id" AS "draft_id",
jbe@528 4927 "supporter"."draft_id" = "current_draft"."id" AS "informed",
jbe@528 4928 NOT EXISTS (
jbe@528 4929 SELECT NULL FROM "critical_opinion"
jbe@528 4930 WHERE "initiative_id" = "initiative"."id"
jbe@528 4931 AND "member_id" = "supporter"."member_id"
jbe@528 4932 ) AS "satisfied"
jbe@528 4933 FROM "initiative"
jbe@528 4934 JOIN "supporter"
jbe@528 4935 ON "supporter"."initiative_id" = "initiative"."id"
jbe@528 4936 JOIN "current_draft"
jbe@528 4937 ON "initiative"."id" = "current_draft"."initiative_id"
jbe@528 4938 JOIN "direct_interest_snapshot"
jbe@528 4939 ON "snapshot_id_v" = "direct_interest_snapshot"."snapshot_id"
jbe@528 4940 AND "supporter"."member_id" = "direct_interest_snapshot"."member_id"
jbe@528 4941 AND "initiative"."issue_id" = "direct_interest_snapshot"."issue_id"
jbe@528 4942 WHERE "initiative"."issue_id" = "issue_id_v";
jbe@528 4943 DELETE FROM "temporary_suggestion_counts";
jbe@528 4944 INSERT INTO "temporary_suggestion_counts"
jbe@528 4945 ( "id",
jbe@528 4946 "minus2_unfulfilled_count", "minus2_fulfilled_count",
jbe@528 4947 "minus1_unfulfilled_count", "minus1_fulfilled_count",
jbe@528 4948 "plus1_unfulfilled_count", "plus1_fulfilled_count",
jbe@528 4949 "plus2_unfulfilled_count", "plus2_fulfilled_count" )
jbe@528 4950 SELECT
jbe@528 4951 "suggestion"."id",
jbe@528 4952 ( SELECT coalesce(sum("di"."weight"), 0)
jbe@528 4953 FROM "opinion" JOIN "direct_interest_snapshot" AS "di"
jbe@528 4954 ON "di"."snapshot_id" = "snapshot_id_v"
jbe@528 4955 AND "di"."issue_id" = "issue_id_v"
jbe@528 4956 AND "di"."member_id" = "opinion"."member_id"
jbe@528 4957 WHERE "opinion"."suggestion_id" = "suggestion"."id"
jbe@528 4958 AND "opinion"."degree" = -2
jbe@528 4959 AND "opinion"."fulfilled" = FALSE
jbe@528 4960 ) AS "minus2_unfulfilled_count",
jbe@528 4961 ( SELECT coalesce(sum("di"."weight"), 0)
jbe@528 4962 FROM "opinion" JOIN "direct_interest_snapshot" AS "di"
jbe@528 4963 ON "di"."snapshot_id" = "snapshot_id_v"
jbe@528 4964 AND "di"."issue_id" = "issue_id_v"
jbe@528 4965 AND "di"."member_id" = "opinion"."member_id"
jbe@528 4966 WHERE "opinion"."suggestion_id" = "suggestion"."id"
jbe@528 4967 AND "opinion"."degree" = -2
jbe@528 4968 AND "opinion"."fulfilled" = TRUE
jbe@528 4969 ) AS "minus2_fulfilled_count",
jbe@528 4970 ( SELECT coalesce(sum("di"."weight"), 0)
jbe@528 4971 FROM "opinion" JOIN "direct_interest_snapshot" AS "di"
jbe@528 4972 ON "di"."snapshot_id" = "snapshot_id_v"
jbe@528 4973 AND "di"."issue_id" = "issue_id_v"
jbe@528 4974 AND "di"."member_id" = "opinion"."member_id"
jbe@528 4975 WHERE "opinion"."suggestion_id" = "suggestion"."id"
jbe@528 4976 AND "opinion"."degree" = -1
jbe@528 4977 AND "opinion"."fulfilled" = FALSE
jbe@528 4978 ) AS "minus1_unfulfilled_count",
jbe@528 4979 ( SELECT coalesce(sum("di"."weight"), 0)
jbe@528 4980 FROM "opinion" JOIN "direct_interest_snapshot" AS "di"
jbe@528 4981 ON "di"."snapshot_id" = "snapshot_id_v"
jbe@528 4982 AND "di"."issue_id" = "issue_id_v"
jbe@528 4983 AND "di"."member_id" = "opinion"."member_id"
jbe@528 4984 WHERE "opinion"."suggestion_id" = "suggestion"."id"
jbe@528 4985 AND "opinion"."degree" = -1
jbe@528 4986 AND "opinion"."fulfilled" = TRUE
jbe@528 4987 ) AS "minus1_fulfilled_count",
jbe@528 4988 ( SELECT coalesce(sum("di"."weight"), 0)
jbe@528 4989 FROM "opinion" JOIN "direct_interest_snapshot" AS "di"
jbe@528 4990 ON "di"."snapshot_id" = "snapshot_id_v"
jbe@528 4991 AND "di"."issue_id" = "issue_id_v"
jbe@528 4992 AND "di"."member_id" = "opinion"."member_id"
jbe@528 4993 WHERE "opinion"."suggestion_id" = "suggestion"."id"
jbe@528 4994 AND "opinion"."degree" = 1
jbe@528 4995 AND "opinion"."fulfilled" = FALSE
jbe@528 4996 ) AS "plus1_unfulfilled_count",
jbe@528 4997 ( SELECT coalesce(sum("di"."weight"), 0)
jbe@528 4998 FROM "opinion" JOIN "direct_interest_snapshot" AS "di"
jbe@528 4999 ON "di"."snapshot_id" = "snapshot_id_v"
jbe@528 5000 AND "di"."issue_id" = "issue_id_v"
jbe@528 5001 AND "di"."member_id" = "opinion"."member_id"
jbe@528 5002 WHERE "opinion"."suggestion_id" = "suggestion"."id"
jbe@528 5003 AND "opinion"."degree" = 1
jbe@528 5004 AND "opinion"."fulfilled" = TRUE
jbe@528 5005 ) AS "plus1_fulfilled_count",
jbe@528 5006 ( SELECT coalesce(sum("di"."weight"), 0)
jbe@528 5007 FROM "opinion" JOIN "direct_interest_snapshot" AS "di"
jbe@528 5008 ON "di"."snapshot_id" = "snapshot_id_v"
jbe@528 5009 AND "di"."issue_id" = "issue_id_v"
jbe@528 5010 AND "di"."member_id" = "opinion"."member_id"
jbe@528 5011 WHERE "opinion"."suggestion_id" = "suggestion"."id"
jbe@528 5012 AND "opinion"."degree" = 2
jbe@528 5013 AND "opinion"."fulfilled" = FALSE
jbe@528 5014 ) AS "plus2_unfulfilled_count",
jbe@528 5015 ( SELECT coalesce(sum("di"."weight"), 0)
jbe@528 5016 FROM "opinion" JOIN "direct_interest_snapshot" AS "di"
jbe@528 5017 ON "di"."snapshot_id" = "snapshot_id_v"
jbe@528 5018 AND "di"."issue_id" = "issue_id_v"
jbe@528 5019 AND "di"."member_id" = "opinion"."member_id"
jbe@528 5020 WHERE "opinion"."suggestion_id" = "suggestion"."id"
jbe@528 5021 AND "opinion"."degree" = 2
jbe@528 5022 AND "opinion"."fulfilled" = TRUE
jbe@528 5023 ) AS "plus2_fulfilled_count"
jbe@528 5024 FROM "suggestion" JOIN "initiative"
jbe@528 5025 ON "suggestion"."initiative_id" = "initiative"."id"
jbe@528 5026 WHERE "initiative"."issue_id" = "issue_id_v";
jbe@0 5027 END LOOP;
jbe@528 5028 RETURN "snapshot_id_v";
jbe@0 5029 END;
jbe@0 5030 $$;
jbe@0 5031
jbe@528 5032 COMMENT ON FUNCTION "take_snapshot"
jbe@532 5033 ( "issue"."id"%TYPE,
jbe@532 5034 "area"."id"%TYPE )
jbe@532 5035 IS 'This function creates a new interest/supporter snapshot of a particular issue, or, if the first argument is NULL, for all issues in ''admission'' phase of the area given as second argument. It must be executed with TRANSACTION ISOLATION LEVEL REPEATABLE READ. The snapshot must later be finished by calling "finish_snapshot" for every issue.';
jbe@528 5036
jbe@528 5037
jbe@528 5038 CREATE FUNCTION "finish_snapshot"
jbe@0 5039 ( "issue_id_p" "issue"."id"%TYPE )
jbe@0 5040 RETURNS VOID
jbe@0 5041 LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@0 5042 DECLARE
jbe@528 5043 "snapshot_id_v" "snapshot"."id"%TYPE;
jbe@0 5044 BEGIN
jbe@532 5045 -- NOTE: function does not require snapshot isolation but we don't call
jbe@532 5046 -- "dont_require_snapshot_isolation" here because this function is
jbe@532 5047 -- also invoked by "check_issue"
jbe@528 5048 LOCK TABLE "snapshot" IN EXCLUSIVE MODE;
jbe@528 5049 SELECT "id" INTO "snapshot_id_v" FROM "snapshot"
jbe@528 5050 ORDER BY "id" DESC LIMIT 1;
jbe@0 5051 UPDATE "issue" SET
jbe@532 5052 "calculated" = "snapshot"."calculated",
jbe@528 5053 "latest_snapshot_id" = "snapshot_id_v",
jbe@532 5054 "population" = "snapshot"."population"
jbe@532 5055 FROM "snapshot"
jbe@532 5056 WHERE "issue"."id" = "issue_id_p"
jbe@532 5057 AND "snapshot"."id" = "snapshot_id_v";
jbe@528 5058 UPDATE "initiative" SET
jbe@528 5059 "supporter_count" = (
jbe@528 5060 SELECT coalesce(sum("di"."weight"), 0)
jbe@528 5061 FROM "direct_interest_snapshot" AS "di"
jbe@528 5062 JOIN "direct_supporter_snapshot" AS "ds"
jbe@528 5063 ON "di"."member_id" = "ds"."member_id"
jbe@528 5064 WHERE "di"."snapshot_id" = "snapshot_id_v"
jbe@528 5065 AND "di"."issue_id" = "issue_id_p"
jbe@528 5066 AND "ds"."snapshot_id" = "snapshot_id_v"
jbe@528 5067 AND "ds"."initiative_id" = "initiative"."id"
jbe@528 5068 ),
jbe@528 5069 "informed_supporter_count" = (
jbe@528 5070 SELECT coalesce(sum("di"."weight"), 0)
jbe@528 5071 FROM "direct_interest_snapshot" AS "di"
jbe@528 5072 JOIN "direct_supporter_snapshot" AS "ds"
jbe@528 5073 ON "di"."member_id" = "ds"."member_id"
jbe@528 5074 WHERE "di"."snapshot_id" = "snapshot_id_v"
jbe@528 5075 AND "di"."issue_id" = "issue_id_p"
jbe@528 5076 AND "ds"."snapshot_id" = "snapshot_id_v"
jbe@528 5077 AND "ds"."initiative_id" = "initiative"."id"
jbe@528 5078 AND "ds"."informed"
jbe@528 5079 ),
jbe@528 5080 "satisfied_supporter_count" = (
jbe@528 5081 SELECT coalesce(sum("di"."weight"), 0)
jbe@528 5082 FROM "direct_interest_snapshot" AS "di"
jbe@528 5083 JOIN "direct_supporter_snapshot" AS "ds"
jbe@528 5084 ON "di"."member_id" = "ds"."member_id"
jbe@528 5085 WHERE "di"."snapshot_id" = "snapshot_id_v"
jbe@528 5086 AND "di"."issue_id" = "issue_id_p"
jbe@528 5087 AND "ds"."snapshot_id" = "snapshot_id_v"
jbe@528 5088 AND "ds"."initiative_id" = "initiative"."id"
jbe@528 5089 AND "ds"."satisfied"
jbe@528 5090 ),
jbe@528 5091 "satisfied_informed_supporter_count" = (
jbe@528 5092 SELECT coalesce(sum("di"."weight"), 0)
jbe@528 5093 FROM "direct_interest_snapshot" AS "di"
jbe@528 5094 JOIN "direct_supporter_snapshot" AS "ds"
jbe@528 5095 ON "di"."member_id" = "ds"."member_id"
jbe@528 5096 WHERE "di"."snapshot_id" = "snapshot_id_v"
jbe@528 5097 AND "di"."issue_id" = "issue_id_p"
jbe@528 5098 AND "ds"."snapshot_id" = "snapshot_id_v"
jbe@528 5099 AND "ds"."initiative_id" = "initiative"."id"
jbe@528 5100 AND "ds"."informed"
jbe@528 5101 AND "ds"."satisfied"
jbe@528 5102 )
jbe@528 5103 WHERE "issue_id" = "issue_id_p";
jbe@528 5104 UPDATE "suggestion" SET
jbe@528 5105 "minus2_unfulfilled_count" = "temp"."minus2_unfulfilled_count",
jbe@528 5106 "minus2_fulfilled_count" = "temp"."minus2_fulfilled_count",
jbe@528 5107 "minus1_unfulfilled_count" = "temp"."minus1_unfulfilled_count",
jbe@528 5108 "minus1_fulfilled_count" = "temp"."minus1_fulfilled_count",
jbe@528 5109 "plus1_unfulfilled_count" = "temp"."plus1_unfulfilled_count",
jbe@528 5110 "plus1_fulfilled_count" = "temp"."plus1_fulfilled_count",
jbe@528 5111 "plus2_unfulfilled_count" = "temp"."plus2_unfulfilled_count",
jbe@528 5112 "plus2_fulfilled_count" = "temp"."plus2_fulfilled_count"
jbe@528 5113 FROM "temporary_suggestion_counts" AS "temp", "initiative"
jbe@528 5114 WHERE "temp"."id" = "suggestion"."id"
jbe@528 5115 AND "initiative"."issue_id" = "issue_id_p"
jbe@528 5116 AND "suggestion"."initiative_id" = "initiative"."id";
jbe@528 5117 DELETE FROM "temporary_suggestion_counts";
jbe@0 5118 RETURN;
jbe@0 5119 END;
jbe@0 5120 $$;
jbe@0 5121
jbe@528 5122 COMMENT ON FUNCTION "finish_snapshot"
jbe@0 5123 ( "issue"."id"%TYPE )
jbe@528 5124 IS 'After calling "take_snapshot", this function "finish_snapshot" needs to be called for every issue in the snapshot (separate function calls keep locking time minimal)';
jbe@0 5125
jbe@0 5126
jbe@0 5127
jbe@0 5128 -----------------------
jbe@0 5129 -- Counting of votes --
jbe@0 5130 -----------------------
jbe@0 5131
jbe@0 5132
jbe@5 5133 CREATE FUNCTION "weight_of_added_vote_delegations"
jbe@0 5134 ( "issue_id_p" "issue"."id"%TYPE,
jbe@0 5135 "member_id_p" "member"."id"%TYPE,
jbe@0 5136 "delegate_member_ids_p" "delegating_voter"."delegate_member_ids"%TYPE )
jbe@0 5137 RETURNS "direct_voter"."weight"%TYPE
jbe@0 5138 LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@0 5139 DECLARE
jbe@0 5140 "issue_delegation_row" "issue_delegation"%ROWTYPE;
jbe@0 5141 "delegate_member_ids_v" "delegating_voter"."delegate_member_ids"%TYPE;
jbe@0 5142 "weight_v" INT4;
jbe@8 5143 "sub_weight_v" INT4;
jbe@0 5144 BEGIN
jbe@336 5145 PERFORM "require_transaction_isolation"();
jbe@0 5146 "weight_v" := 0;
jbe@0 5147 FOR "issue_delegation_row" IN
jbe@0 5148 SELECT * FROM "issue_delegation"
jbe@0 5149 WHERE "trustee_id" = "member_id_p"
jbe@0 5150 AND "issue_id" = "issue_id_p"
jbe@0 5151 LOOP
jbe@0 5152 IF NOT EXISTS (
jbe@0 5153 SELECT NULL FROM "direct_voter"
jbe@0 5154 WHERE "member_id" = "issue_delegation_row"."truster_id"
jbe@0 5155 AND "issue_id" = "issue_id_p"
jbe@0 5156 ) AND NOT EXISTS (
jbe@0 5157 SELECT NULL FROM "delegating_voter"
jbe@0 5158 WHERE "member_id" = "issue_delegation_row"."truster_id"
jbe@0 5159 AND "issue_id" = "issue_id_p"
jbe@0 5160 ) THEN
jbe@0 5161 "delegate_member_ids_v" :=
jbe@0 5162 "member_id_p" || "delegate_member_ids_p";
jbe@10 5163 INSERT INTO "delegating_voter" (
jbe@10 5164 "issue_id",
jbe@10 5165 "member_id",
jbe@10 5166 "scope",
jbe@10 5167 "delegate_member_ids"
jbe@10 5168 ) VALUES (
jbe@5 5169 "issue_id_p",
jbe@5 5170 "issue_delegation_row"."truster_id",
jbe@10 5171 "issue_delegation_row"."scope",
jbe@5 5172 "delegate_member_ids_v"
jbe@5 5173 );
jbe@8 5174 "sub_weight_v" := 1 +
jbe@8 5175 "weight_of_added_vote_delegations"(
jbe@8 5176 "issue_id_p",
jbe@8 5177 "issue_delegation_row"."truster_id",
jbe@8 5178 "delegate_member_ids_v"
jbe@8 5179 );
jbe@8 5180 UPDATE "delegating_voter"
jbe@8 5181 SET "weight" = "sub_weight_v"
jbe@8 5182 WHERE "issue_id" = "issue_id_p"
jbe@8 5183 AND "member_id" = "issue_delegation_row"."truster_id";
jbe@8 5184 "weight_v" := "weight_v" + "sub_weight_v";
jbe@0 5185 END IF;
jbe@0 5186 END LOOP;
jbe@0 5187 RETURN "weight_v";
jbe@0 5188 END;
jbe@0 5189 $$;
jbe@0 5190
jbe@5 5191 COMMENT ON FUNCTION "weight_of_added_vote_delegations"
jbe@0 5192 ( "issue"."id"%TYPE,
jbe@0 5193 "member"."id"%TYPE,
jbe@0 5194 "delegating_voter"."delegate_member_ids"%TYPE )
jbe@0 5195 IS 'Helper function for "add_vote_delegations" function';
jbe@0 5196
jbe@0 5197
jbe@0 5198 CREATE FUNCTION "add_vote_delegations"
jbe@0 5199 ( "issue_id_p" "issue"."id"%TYPE )
jbe@0 5200 RETURNS VOID
jbe@0 5201 LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@0 5202 DECLARE
jbe@0 5203 "member_id_v" "member"."id"%TYPE;
jbe@0 5204 BEGIN
jbe@336 5205 PERFORM "require_transaction_isolation"();
jbe@0 5206 FOR "member_id_v" IN
jbe@0 5207 SELECT "member_id" FROM "direct_voter"
jbe@0 5208 WHERE "issue_id" = "issue_id_p"
jbe@0 5209 LOOP
jbe@0 5210 UPDATE "direct_voter" SET
jbe@5 5211 "weight" = "weight" + "weight_of_added_vote_delegations"(
jbe@0 5212 "issue_id_p",
jbe@0 5213 "member_id_v",
jbe@0 5214 '{}'
jbe@0 5215 )
jbe@0 5216 WHERE "member_id" = "member_id_v"
jbe@0 5217 AND "issue_id" = "issue_id_p";
jbe@0 5218 END LOOP;
jbe@0 5219 RETURN;
jbe@0 5220 END;
jbe@0 5221 $$;
jbe@0 5222
jbe@0 5223 COMMENT ON FUNCTION "add_vote_delegations"
jbe@0 5224 ( "issue_id_p" "issue"."id"%TYPE )
jbe@0 5225 IS 'Helper function for "close_voting" function';
jbe@0 5226
jbe@0 5227
jbe@0 5228 CREATE FUNCTION "close_voting"("issue_id_p" "issue"."id"%TYPE)
jbe@0 5229 RETURNS VOID
jbe@0 5230 LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@0 5231 DECLARE
jbe@97 5232 "area_id_v" "area"."id"%TYPE;
jbe@97 5233 "unit_id_v" "unit"."id"%TYPE;
jbe@0 5234 "member_id_v" "member"."id"%TYPE;
jbe@0 5235 BEGIN
jbe@333 5236 PERFORM "require_transaction_isolation"();
jbe@129 5237 SELECT "area_id" INTO "area_id_v" FROM "issue" WHERE "id" = "issue_id_p";
jbe@129 5238 SELECT "unit_id" INTO "unit_id_v" FROM "area" WHERE "id" = "area_id_v";
jbe@383 5239 -- override protection triggers:
jbe@385 5240 INSERT INTO "temporary_transaction_data" ("key", "value")
jbe@385 5241 VALUES ('override_protection_triggers', TRUE::TEXT);
jbe@285 5242 -- delete timestamp of voting comment:
jbe@285 5243 UPDATE "direct_voter" SET "comment_changed" = NULL
jbe@285 5244 WHERE "issue_id" = "issue_id_p";
jbe@169 5245 -- delete delegating votes (in cases of manual reset of issue state):
jbe@0 5246 DELETE FROM "delegating_voter"
jbe@0 5247 WHERE "issue_id" = "issue_id_p";
jbe@169 5248 -- delete votes from non-privileged voters:
jbe@97 5249 DELETE FROM "direct_voter"
jbe@97 5250 USING (
jbe@97 5251 SELECT
jbe@97 5252 "direct_voter"."member_id"
jbe@97 5253 FROM "direct_voter"
jbe@97 5254 JOIN "member" ON "direct_voter"."member_id" = "member"."id"
jbe@97 5255 LEFT JOIN "privilege"
jbe@97 5256 ON "privilege"."unit_id" = "unit_id_v"
jbe@97 5257 AND "privilege"."member_id" = "direct_voter"."member_id"
jbe@97 5258 WHERE "direct_voter"."issue_id" = "issue_id_p" AND (
jbe@97 5259 "member"."active" = FALSE OR
jbe@97 5260 "privilege"."voting_right" ISNULL OR
jbe@97 5261 "privilege"."voting_right" = FALSE
jbe@97 5262 )
jbe@97 5263 ) AS "subquery"
jbe@97 5264 WHERE "direct_voter"."issue_id" = "issue_id_p"
jbe@97 5265 AND "direct_voter"."member_id" = "subquery"."member_id";
jbe@169 5266 -- consider delegations:
jbe@0 5267 UPDATE "direct_voter" SET "weight" = 1
jbe@0 5268 WHERE "issue_id" = "issue_id_p";
jbe@0 5269 PERFORM "add_vote_delegations"("issue_id_p");
jbe@414 5270 -- mark first preferences:
jbe@414 5271 UPDATE "vote" SET "first_preference" = "subquery"."first_preference"
jbe@414 5272 FROM (
jbe@414 5273 SELECT
jbe@414 5274 "vote"."initiative_id",
jbe@414 5275 "vote"."member_id",
jbe@414 5276 CASE WHEN "vote"."grade" > 0 THEN
jbe@414 5277 CASE WHEN "vote"."grade" = max("agg"."grade") THEN TRUE ELSE FALSE END
jbe@414 5278 ELSE NULL
jbe@414 5279 END AS "first_preference"
jbe@415 5280 FROM "vote"
jbe@415 5281 JOIN "initiative" -- NOTE: due to missing index on issue_id
jbe@415 5282 ON "vote"."issue_id" = "initiative"."issue_id"
jbe@415 5283 JOIN "vote" AS "agg"
jbe@415 5284 ON "initiative"."id" = "agg"."initiative_id"
jbe@415 5285 AND "vote"."member_id" = "agg"."member_id"
jbe@433 5286 GROUP BY "vote"."initiative_id", "vote"."member_id", "vote"."grade"
jbe@414 5287 ) AS "subquery"
jbe@414 5288 WHERE "vote"."issue_id" = "issue_id_p"
jbe@414 5289 AND "vote"."initiative_id" = "subquery"."initiative_id"
jbe@414 5290 AND "vote"."member_id" = "subquery"."member_id";
jbe@385 5291 -- finish overriding protection triggers (avoids garbage):
jbe@385 5292 DELETE FROM "temporary_transaction_data"
jbe@385 5293 WHERE "key" = 'override_protection_triggers';
jbe@137 5294 -- materialize battle_view:
jbe@61 5295 -- NOTE: "closed" column of issue must be set at this point
jbe@61 5296 DELETE FROM "battle" WHERE "issue_id" = "issue_id_p";
jbe@61 5297 INSERT INTO "battle" (
jbe@61 5298 "issue_id",
jbe@61 5299 "winning_initiative_id", "losing_initiative_id",
jbe@61 5300 "count"
jbe@61 5301 ) SELECT
jbe@61 5302 "issue_id",
jbe@61 5303 "winning_initiative_id", "losing_initiative_id",
jbe@61 5304 "count"
jbe@61 5305 FROM "battle_view" WHERE "issue_id" = "issue_id_p";
jbe@331 5306 -- set voter count:
jbe@331 5307 UPDATE "issue" SET
jbe@331 5308 "voter_count" = (
jbe@331 5309 SELECT coalesce(sum("weight"), 0)
jbe@331 5310 FROM "direct_voter" WHERE "issue_id" = "issue_id_p"
jbe@331 5311 )
jbe@331 5312 WHERE "id" = "issue_id_p";
jbe@437 5313 -- copy "positive_votes" and "negative_votes" from "battle" table:
jbe@437 5314 -- NOTE: "first_preference_votes" is set to a default of 0 at this step
jbe@437 5315 UPDATE "initiative" SET
jbe@437 5316 "first_preference_votes" = 0,
jbe@437 5317 "positive_votes" = "battle_win"."count",
jbe@437 5318 "negative_votes" = "battle_lose"."count"
jbe@437 5319 FROM "battle" AS "battle_win", "battle" AS "battle_lose"
jbe@437 5320 WHERE
jbe@437 5321 "battle_win"."issue_id" = "issue_id_p" AND
jbe@437 5322 "battle_win"."winning_initiative_id" = "initiative"."id" AND
jbe@437 5323 "battle_win"."losing_initiative_id" ISNULL AND
jbe@437 5324 "battle_lose"."issue_id" = "issue_id_p" AND
jbe@437 5325 "battle_lose"."losing_initiative_id" = "initiative"."id" AND
jbe@437 5326 "battle_lose"."winning_initiative_id" ISNULL;
jbe@414 5327 -- calculate "first_preference_votes":
jbe@437 5328 -- NOTE: will only set values not equal to zero
jbe@437 5329 UPDATE "initiative" SET "first_preference_votes" = "subquery"."sum"
jbe@414 5330 FROM (
jbe@414 5331 SELECT "vote"."initiative_id", sum("direct_voter"."weight")
jbe@414 5332 FROM "vote" JOIN "direct_voter"
jbe@414 5333 ON "vote"."issue_id" = "direct_voter"."issue_id"
jbe@414 5334 AND "vote"."member_id" = "direct_voter"."member_id"
jbe@414 5335 WHERE "vote"."first_preference"
jbe@414 5336 GROUP BY "vote"."initiative_id"
jbe@414 5337 ) AS "subquery"
jbe@414 5338 WHERE "initiative"."issue_id" = "issue_id_p"
jbe@414 5339 AND "initiative"."admitted"
jbe@414 5340 AND "initiative"."id" = "subquery"."initiative_id";
jbe@0 5341 END;
jbe@0 5342 $$;
jbe@0 5343
jbe@0 5344 COMMENT ON FUNCTION "close_voting"
jbe@0 5345 ( "issue"."id"%TYPE )
jbe@0 5346 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 5347
jbe@0 5348
jbe@30 5349 CREATE FUNCTION "defeat_strength"
jbe@424 5350 ( "positive_votes_p" INT4,
jbe@424 5351 "negative_votes_p" INT4,
jbe@424 5352 "defeat_strength_p" "defeat_strength" )
jbe@30 5353 RETURNS INT8
jbe@30 5354 LANGUAGE 'plpgsql' IMMUTABLE AS $$
jbe@30 5355 BEGIN
jbe@424 5356 IF "defeat_strength_p" = 'simple'::"defeat_strength" THEN
jbe@424 5357 IF "positive_votes_p" > "negative_votes_p" THEN
jbe@424 5358 RETURN "positive_votes_p";
jbe@424 5359 ELSE
jbe@424 5360 RETURN 0;
jbe@424 5361 END IF;
jbe@30 5362 ELSE
jbe@424 5363 IF "positive_votes_p" > "negative_votes_p" THEN
jbe@424 5364 RETURN ("positive_votes_p"::INT8 << 31) - "negative_votes_p"::INT8;
jbe@424 5365 ELSIF "positive_votes_p" = "negative_votes_p" THEN
jbe@424 5366 RETURN 0;
jbe@424 5367 ELSE
jbe@424 5368 RETURN -1;
jbe@424 5369 END IF;
jbe@30 5370 END IF;
jbe@30 5371 END;
jbe@30 5372 $$;
jbe@30 5373
jbe@425 5374 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 5375
jbe@30 5376
jbe@423 5377 CREATE FUNCTION "secondary_link_strength"
jbe@426 5378 ( "initiative1_ord_p" INT4,
jbe@426 5379 "initiative2_ord_p" INT4,
jbe@424 5380 "tie_breaking_p" "tie_breaking" )
jbe@423 5381 RETURNS INT8
jbe@423 5382 LANGUAGE 'plpgsql' IMMUTABLE AS $$
jbe@423 5383 BEGIN
jbe@426 5384 IF "initiative1_ord_p" = "initiative2_ord_p" THEN
jbe@423 5385 RAISE EXCEPTION 'Identical initiative ids passed to "secondary_link_strength" function (should not happen)';
jbe@423 5386 END IF;
jbe@423 5387 RETURN (
jbe@426 5388 CASE WHEN "tie_breaking_p" = 'simple'::"tie_breaking" THEN
jbe@426 5389 0
jbe@424 5390 ELSE
jbe@426 5391 CASE WHEN "initiative1_ord_p" < "initiative2_ord_p" THEN
jbe@426 5392 1::INT8 << 62
jbe@426 5393 ELSE 0 END
jbe@426 5394 +
jbe@426 5395 CASE WHEN "tie_breaking_p" = 'variant2'::"tie_breaking" THEN
jbe@426 5396 ("initiative2_ord_p"::INT8 << 31) - "initiative1_ord_p"::INT8
jbe@426 5397 ELSE
jbe@426 5398 "initiative2_ord_p"::INT8 - ("initiative1_ord_p"::INT8 << 31)
jbe@426 5399 END
jbe@424 5400 END
jbe@423 5401 );
jbe@423 5402 END;
jbe@423 5403 $$;
jbe@423 5404
jbe@424 5405 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 5406
jbe@423 5407
jbe@426 5408 CREATE TYPE "link_strength" AS (
jbe@426 5409 "primary" INT8,
jbe@426 5410 "secondary" INT8 );
jbe@426 5411
jbe@428 5412 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 5413
jbe@427 5414
jbe@427 5415 CREATE FUNCTION "find_best_paths"("matrix_d" "link_strength"[][])
jbe@427 5416 RETURNS "link_strength"[][]
jbe@427 5417 LANGUAGE 'plpgsql' IMMUTABLE AS $$
jbe@427 5418 DECLARE
jbe@427 5419 "dimension_v" INT4;
jbe@427 5420 "matrix_p" "link_strength"[][];
jbe@427 5421 "i" INT4;
jbe@427 5422 "j" INT4;
jbe@427 5423 "k" INT4;
jbe@427 5424 BEGIN
jbe@427 5425 "dimension_v" := array_upper("matrix_d", 1);
jbe@427 5426 "matrix_p" := "matrix_d";
jbe@427 5427 "i" := 1;
jbe@427 5428 LOOP
jbe@427 5429 "j" := 1;
jbe@427 5430 LOOP
jbe@427 5431 IF "i" != "j" THEN
jbe@427 5432 "k" := 1;
jbe@427 5433 LOOP
jbe@427 5434 IF "i" != "k" AND "j" != "k" THEN
jbe@427 5435 IF "matrix_p"["j"]["i"] < "matrix_p"["i"]["k"] THEN
jbe@427 5436 IF "matrix_p"["j"]["i"] > "matrix_p"["j"]["k"] THEN
jbe@427 5437 "matrix_p"["j"]["k"] := "matrix_p"["j"]["i"];
jbe@427 5438 END IF;
jbe@427 5439 ELSE
jbe@427 5440 IF "matrix_p"["i"]["k"] > "matrix_p"["j"]["k"] THEN
jbe@427 5441 "matrix_p"["j"]["k"] := "matrix_p"["i"]["k"];
jbe@427 5442 END IF;
jbe@427 5443 END IF;
jbe@427 5444 END IF;
jbe@427 5445 EXIT WHEN "k" = "dimension_v";
jbe@427 5446 "k" := "k" + 1;
jbe@427 5447 END LOOP;
jbe@427 5448 END IF;
jbe@427 5449 EXIT WHEN "j" = "dimension_v";
jbe@427 5450 "j" := "j" + 1;
jbe@427 5451 END LOOP;
jbe@427 5452 EXIT WHEN "i" = "dimension_v";
jbe@427 5453 "i" := "i" + 1;
jbe@427 5454 END LOOP;
jbe@427 5455 RETURN "matrix_p";
jbe@427 5456 END;
jbe@427 5457 $$;
jbe@427 5458
jbe@428 5459 COMMENT ON FUNCTION "find_best_paths"("link_strength"[][]) IS 'Computes the strengths of the best beat-paths from a square matrix';
jbe@426 5460
jbe@426 5461
jbe@0 5462 CREATE FUNCTION "calculate_ranks"("issue_id_p" "issue"."id"%TYPE)
jbe@0 5463 RETURNS VOID
jbe@0 5464 LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@0 5465 DECLARE
jbe@427 5466 "issue_row" "issue"%ROWTYPE;
jbe@427 5467 "policy_row" "policy"%ROWTYPE;
jbe@427 5468 "dimension_v" INT4;
jbe@427 5469 "matrix_a" INT4[][]; -- absolute votes
jbe@427 5470 "matrix_d" "link_strength"[][]; -- defeat strength (direct)
jbe@427 5471 "matrix_p" "link_strength"[][]; -- defeat strength (best path)
jbe@427 5472 "matrix_t" "link_strength"[][]; -- defeat strength (tie-breaking)
jbe@427 5473 "matrix_f" BOOLEAN[][]; -- forbidden link (tie-breaking)
jbe@427 5474 "matrix_b" BOOLEAN[][]; -- final order (who beats who)
jbe@427 5475 "i" INT4;
jbe@427 5476 "j" INT4;
jbe@427 5477 "m" INT4;
jbe@427 5478 "n" INT4;
jbe@427 5479 "battle_row" "battle"%ROWTYPE;
jbe@427 5480 "rank_ary" INT4[];
jbe@427 5481 "rank_v" INT4;
jbe@427 5482 "initiative_id_v" "initiative"."id"%TYPE;
jbe@0 5483 BEGIN
jbe@333 5484 PERFORM "require_transaction_isolation"();
jbe@155 5485 SELECT * INTO "issue_row"
jbe@331 5486 FROM "issue" WHERE "id" = "issue_id_p";
jbe@155 5487 SELECT * INTO "policy_row"
jbe@155 5488 FROM "policy" WHERE "id" = "issue_row"."policy_id";
jbe@126 5489 SELECT count(1) INTO "dimension_v"
jbe@126 5490 FROM "battle_participant" WHERE "issue_id" = "issue_id_p";
jbe@428 5491 -- create "matrix_a" with absolute number of votes in pairwise
jbe@170 5492 -- comparison:
jbe@427 5493 "matrix_a" := array_fill(NULL::INT4, ARRAY["dimension_v", "dimension_v"]);
jbe@170 5494 "i" := 1;
jbe@170 5495 "j" := 2;
jbe@170 5496 FOR "battle_row" IN
jbe@170 5497 SELECT * FROM "battle" WHERE "issue_id" = "issue_id_p"
jbe@170 5498 ORDER BY
jbe@411 5499 "winning_initiative_id" NULLS FIRST,
jbe@411 5500 "losing_initiative_id" NULLS FIRST
jbe@170 5501 LOOP
jbe@427 5502 "matrix_a"["i"]["j"] := "battle_row"."count";
jbe@170 5503 IF "j" = "dimension_v" THEN
jbe@170 5504 "i" := "i" + 1;
jbe@170 5505 "j" := 1;
jbe@170 5506 ELSE
jbe@170 5507 "j" := "j" + 1;
jbe@170 5508 IF "j" = "i" THEN
jbe@170 5509 "j" := "j" + 1;
jbe@170 5510 END IF;
jbe@170 5511 END IF;
jbe@170 5512 END LOOP;
jbe@170 5513 IF "i" != "dimension_v" OR "j" != "dimension_v" + 1 THEN
jbe@170 5514 RAISE EXCEPTION 'Wrong battle count (should not happen)';
jbe@170 5515 END IF;
jbe@428 5516 -- store direct defeat strengths in "matrix_d" using "defeat_strength"
jbe@427 5517 -- and "secondary_link_strength" functions:
jbe@427 5518 "matrix_d" := array_fill(NULL::INT8, ARRAY["dimension_v", "dimension_v"]);
jbe@170 5519 "i" := 1;
jbe@170 5520 LOOP
jbe@170 5521 "j" := 1;
jbe@0 5522 LOOP
jbe@170 5523 IF "i" != "j" THEN
jbe@427 5524 "matrix_d"["i"]["j"] := (
jbe@426 5525 "defeat_strength"(
jbe@427 5526 "matrix_a"["i"]["j"],
jbe@427 5527 "matrix_a"["j"]["i"],
jbe@426 5528 "policy_row"."defeat_strength"
jbe@426 5529 ),
jbe@426 5530 "secondary_link_strength"(
jbe@426 5531 "i",
jbe@426 5532 "j",
jbe@426 5533 "policy_row"."tie_breaking"
jbe@426 5534 )
jbe@426 5535 )::"link_strength";
jbe@0 5536 END IF;
jbe@170 5537 EXIT WHEN "j" = "dimension_v";
jbe@170 5538 "j" := "j" + 1;
jbe@0 5539 END LOOP;
jbe@170 5540 EXIT WHEN "i" = "dimension_v";
jbe@170 5541 "i" := "i" + 1;
jbe@170 5542 END LOOP;
jbe@428 5543 -- find best paths:
jbe@427 5544 "matrix_p" := "find_best_paths"("matrix_d");
jbe@428 5545 -- create partial order:
jbe@427 5546 "matrix_b" := array_fill(NULL::BOOLEAN, ARRAY["dimension_v", "dimension_v"]);
jbe@170 5547 "i" := 1;
jbe@170 5548 LOOP
jbe@427 5549 "j" := "i" + 1;
jbe@170 5550 LOOP
jbe@170 5551 IF "i" != "j" THEN
jbe@427 5552 IF "matrix_p"["i"]["j"] > "matrix_p"["j"]["i"] THEN
jbe@427 5553 "matrix_b"["i"]["j"] := TRUE;
jbe@427 5554 "matrix_b"["j"]["i"] := FALSE;
jbe@427 5555 ELSIF "matrix_p"["i"]["j"] < "matrix_p"["j"]["i"] THEN
jbe@427 5556 "matrix_b"["i"]["j"] := FALSE;
jbe@427 5557 "matrix_b"["j"]["i"] := TRUE;
jbe@427 5558 END IF;
jbe@170 5559 END IF;
jbe@170 5560 EXIT WHEN "j" = "dimension_v";
jbe@170 5561 "j" := "j" + 1;
jbe@170 5562 END LOOP;
jbe@427 5563 EXIT WHEN "i" = "dimension_v" - 1;
jbe@170 5564 "i" := "i" + 1;
jbe@170 5565 END LOOP;
jbe@428 5566 -- tie-breaking by forbidding shared weakest links in beat-paths
jbe@428 5567 -- (unless "tie_breaking" is set to 'simple', in which case tie-breaking
jbe@428 5568 -- is performed later by initiative id):
jbe@427 5569 IF "policy_row"."tie_breaking" != 'simple'::"tie_breaking" THEN
jbe@427 5570 "m" := 1;
jbe@427 5571 LOOP
jbe@427 5572 "n" := "m" + 1;
jbe@427 5573 LOOP
jbe@428 5574 -- only process those candidates m and n, which are tied:
jbe@427 5575 IF "matrix_b"["m"]["n"] ISNULL THEN
jbe@428 5576 -- start with beat-paths prior tie-breaking:
jbe@427 5577 "matrix_t" := "matrix_p";
jbe@428 5578 -- start with all links allowed:
jbe@427 5579 "matrix_f" := array_fill(FALSE, ARRAY["dimension_v", "dimension_v"]);
jbe@427 5580 LOOP
jbe@428 5581 -- determine (and forbid) that link that is the weakest link
jbe@428 5582 -- in both the best path from candidate m to candidate n and
jbe@428 5583 -- from candidate n to candidate m:
jbe@427 5584 "i" := 1;
jbe@427 5585 <<forbid_one_link>>
jbe@427 5586 LOOP
jbe@427 5587 "j" := 1;
jbe@427 5588 LOOP
jbe@427 5589 IF "i" != "j" THEN
jbe@427 5590 IF "matrix_d"["i"]["j"] = "matrix_t"["m"]["n"] THEN
jbe@427 5591 "matrix_f"["i"]["j"] := TRUE;
jbe@427 5592 -- exit for performance reasons,
jbe@428 5593 -- as exactly one link will be found:
jbe@427 5594 EXIT forbid_one_link;
jbe@427 5595 END IF;
jbe@427 5596 END IF;
jbe@427 5597 EXIT WHEN "j" = "dimension_v";
jbe@427 5598 "j" := "j" + 1;
jbe@427 5599 END LOOP;
jbe@427 5600 IF "i" = "dimension_v" THEN
jbe@428 5601 RAISE EXCEPTION 'Did not find shared weakest link for tie-breaking (should not happen)';
jbe@427 5602 END IF;
jbe@427 5603 "i" := "i" + 1;
jbe@427 5604 END LOOP;
jbe@428 5605 -- calculate best beat-paths while ignoring forbidden links:
jbe@427 5606 "i" := 1;
jbe@427 5607 LOOP
jbe@427 5608 "j" := 1;
jbe@427 5609 LOOP
jbe@427 5610 IF "i" != "j" THEN
jbe@427 5611 "matrix_t"["i"]["j"] := CASE
jbe@427 5612 WHEN "matrix_f"["i"]["j"]
jbe@431 5613 THEN ((-1::INT8) << 63, 0)::"link_strength" -- worst possible value
jbe@427 5614 ELSE "matrix_d"["i"]["j"] END;
jbe@427 5615 END IF;
jbe@427 5616 EXIT WHEN "j" = "dimension_v";
jbe@427 5617 "j" := "j" + 1;
jbe@427 5618 END LOOP;
jbe@427 5619 EXIT WHEN "i" = "dimension_v";
jbe@427 5620 "i" := "i" + 1;
jbe@427 5621 END LOOP;
jbe@427 5622 "matrix_t" := "find_best_paths"("matrix_t");
jbe@428 5623 -- extend partial order, if tie-breaking was successful:
jbe@427 5624 IF "matrix_t"["m"]["n"] > "matrix_t"["n"]["m"] THEN
jbe@427 5625 "matrix_b"["m"]["n"] := TRUE;
jbe@427 5626 "matrix_b"["n"]["m"] := FALSE;
jbe@427 5627 EXIT;
jbe@427 5628 ELSIF "matrix_t"["m"]["n"] < "matrix_t"["n"]["m"] THEN
jbe@427 5629 "matrix_b"["m"]["n"] := FALSE;
jbe@427 5630 "matrix_b"["n"]["m"] := TRUE;
jbe@427 5631 EXIT;
jbe@427 5632 END IF;
jbe@427 5633 END LOOP;
jbe@427 5634 END IF;
jbe@427 5635 EXIT WHEN "n" = "dimension_v";
jbe@427 5636 "n" := "n" + 1;
jbe@427 5637 END LOOP;
jbe@427 5638 EXIT WHEN "m" = "dimension_v" - 1;
jbe@427 5639 "m" := "m" + 1;
jbe@427 5640 END LOOP;
jbe@427 5641 END IF;
jbe@428 5642 -- store a unique ranking in "rank_ary":
jbe@170 5643 "rank_ary" := array_fill(NULL::INT4, ARRAY["dimension_v"]);
jbe@170 5644 "rank_v" := 1;
jbe@170 5645 LOOP
jbe@0 5646 "i" := 1;
jbe@428 5647 <<assign_next_rank>>
jbe@0 5648 LOOP
jbe@170 5649 IF "rank_ary"["i"] ISNULL THEN
jbe@170 5650 "j" := 1;
jbe@170 5651 LOOP
jbe@170 5652 IF
jbe@170 5653 "i" != "j" AND
jbe@170 5654 "rank_ary"["j"] ISNULL AND
jbe@427 5655 ( "matrix_b"["j"]["i"] OR
jbe@411 5656 -- tie-breaking by "id"
jbe@427 5657 ( "matrix_b"["j"]["i"] ISNULL AND
jbe@411 5658 "j" < "i" ) )
jbe@170 5659 THEN
jbe@170 5660 -- someone else is better
jbe@170 5661 EXIT;
jbe@170 5662 END IF;
jbe@428 5663 IF "j" = "dimension_v" THEN
jbe@170 5664 -- noone is better
jbe@411 5665 "rank_ary"["i"] := "rank_v";
jbe@428 5666 EXIT assign_next_rank;
jbe@170 5667 END IF;
jbe@428 5668 "j" := "j" + 1;
jbe@170 5669 END LOOP;
jbe@170 5670 END IF;
jbe@0 5671 "i" := "i" + 1;
jbe@411 5672 IF "i" > "dimension_v" THEN
jbe@411 5673 RAISE EXCEPTION 'Schulze ranking does not compute (should not happen)';
jbe@411 5674 END IF;
jbe@0 5675 END LOOP;
jbe@411 5676 EXIT WHEN "rank_v" = "dimension_v";
jbe@170 5677 "rank_v" := "rank_v" + 1;
jbe@170 5678 END LOOP;
jbe@170 5679 -- write preliminary results:
jbe@411 5680 "i" := 2; -- omit status quo with "i" = 1
jbe@170 5681 FOR "initiative_id_v" IN
jbe@170 5682 SELECT "id" FROM "initiative"
jbe@170 5683 WHERE "issue_id" = "issue_id_p" AND "admitted"
jbe@170 5684 ORDER BY "id"
jbe@170 5685 LOOP
jbe@170 5686 UPDATE "initiative" SET
jbe@170 5687 "direct_majority" =
jbe@170 5688 CASE WHEN "policy_row"."direct_majority_strict" THEN
jbe@170 5689 "positive_votes" * "policy_row"."direct_majority_den" >
jbe@170 5690 "policy_row"."direct_majority_num" * ("positive_votes"+"negative_votes")
jbe@170 5691 ELSE
jbe@170 5692 "positive_votes" * "policy_row"."direct_majority_den" >=
jbe@170 5693 "policy_row"."direct_majority_num" * ("positive_votes"+"negative_votes")
jbe@170 5694 END
jbe@170 5695 AND "positive_votes" >= "policy_row"."direct_majority_positive"
jbe@170 5696 AND "issue_row"."voter_count"-"negative_votes" >=
jbe@170 5697 "policy_row"."direct_majority_non_negative",
jbe@170 5698 "indirect_majority" =
jbe@170 5699 CASE WHEN "policy_row"."indirect_majority_strict" THEN
jbe@170 5700 "positive_votes" * "policy_row"."indirect_majority_den" >
jbe@170 5701 "policy_row"."indirect_majority_num" * ("positive_votes"+"negative_votes")
jbe@170 5702 ELSE
jbe@170 5703 "positive_votes" * "policy_row"."indirect_majority_den" >=
jbe@170 5704 "policy_row"."indirect_majority_num" * ("positive_votes"+"negative_votes")
jbe@170 5705 END
jbe@170 5706 AND "positive_votes" >= "policy_row"."indirect_majority_positive"
jbe@170 5707 AND "issue_row"."voter_count"-"negative_votes" >=
jbe@170 5708 "policy_row"."indirect_majority_non_negative",
jbe@171 5709 "schulze_rank" = "rank_ary"["i"],
jbe@411 5710 "better_than_status_quo" = "rank_ary"["i"] < "rank_ary"[1],
jbe@411 5711 "worse_than_status_quo" = "rank_ary"["i"] > "rank_ary"[1],
jbe@411 5712 "multistage_majority" = "rank_ary"["i"] >= "rank_ary"[1],
jbe@429 5713 "reverse_beat_path" = CASE WHEN "policy_row"."defeat_strength" = 'simple'::"defeat_strength"
jbe@429 5714 THEN NULL
jbe@429 5715 ELSE "matrix_p"[1]["i"]."primary" >= 0 END,
jbe@216 5716 "eligible" = FALSE,
jbe@250 5717 "winner" = FALSE,
jbe@250 5718 "rank" = NULL -- NOTE: in cases of manual reset of issue state
jbe@170 5719 WHERE "id" = "initiative_id_v";
jbe@170 5720 "i" := "i" + 1;
jbe@170 5721 END LOOP;
jbe@411 5722 IF "i" != "dimension_v" + 1 THEN
jbe@170 5723 RAISE EXCEPTION 'Wrong winner count (should not happen)';
jbe@0 5724 END IF;
jbe@170 5725 -- take indirect majorities into account:
jbe@170 5726 LOOP
jbe@170 5727 UPDATE "initiative" SET "indirect_majority" = TRUE
jbe@139 5728 FROM (
jbe@170 5729 SELECT "new_initiative"."id" AS "initiative_id"
jbe@170 5730 FROM "initiative" "old_initiative"
jbe@170 5731 JOIN "initiative" "new_initiative"
jbe@170 5732 ON "new_initiative"."issue_id" = "issue_id_p"
jbe@170 5733 AND "new_initiative"."indirect_majority" = FALSE
jbe@139 5734 JOIN "battle" "battle_win"
jbe@139 5735 ON "battle_win"."issue_id" = "issue_id_p"
jbe@170 5736 AND "battle_win"."winning_initiative_id" = "new_initiative"."id"
jbe@170 5737 AND "battle_win"."losing_initiative_id" = "old_initiative"."id"
jbe@139 5738 JOIN "battle" "battle_lose"
jbe@139 5739 ON "battle_lose"."issue_id" = "issue_id_p"
jbe@170 5740 AND "battle_lose"."losing_initiative_id" = "new_initiative"."id"
jbe@170 5741 AND "battle_lose"."winning_initiative_id" = "old_initiative"."id"
jbe@170 5742 WHERE "old_initiative"."issue_id" = "issue_id_p"
jbe@170 5743 AND "old_initiative"."indirect_majority" = TRUE
jbe@170 5744 AND CASE WHEN "policy_row"."indirect_majority_strict" THEN
jbe@170 5745 "battle_win"."count" * "policy_row"."indirect_majority_den" >
jbe@170 5746 "policy_row"."indirect_majority_num" *
jbe@170 5747 ("battle_win"."count"+"battle_lose"."count")
jbe@170 5748 ELSE
jbe@170 5749 "battle_win"."count" * "policy_row"."indirect_majority_den" >=
jbe@170 5750 "policy_row"."indirect_majority_num" *
jbe@170 5751 ("battle_win"."count"+"battle_lose"."count")
jbe@170 5752 END
jbe@170 5753 AND "battle_win"."count" >= "policy_row"."indirect_majority_positive"
jbe@170 5754 AND "issue_row"."voter_count"-"battle_lose"."count" >=
jbe@170 5755 "policy_row"."indirect_majority_non_negative"
jbe@139 5756 ) AS "subquery"
jbe@139 5757 WHERE "id" = "subquery"."initiative_id";
jbe@170 5758 EXIT WHEN NOT FOUND;
jbe@170 5759 END LOOP;
jbe@170 5760 -- set "multistage_majority" for remaining matching initiatives:
jbe@216 5761 UPDATE "initiative" SET "multistage_majority" = TRUE
jbe@170 5762 FROM (
jbe@170 5763 SELECT "losing_initiative"."id" AS "initiative_id"
jbe@170 5764 FROM "initiative" "losing_initiative"
jbe@170 5765 JOIN "initiative" "winning_initiative"
jbe@170 5766 ON "winning_initiative"."issue_id" = "issue_id_p"
jbe@170 5767 AND "winning_initiative"."admitted"
jbe@170 5768 JOIN "battle" "battle_win"
jbe@170 5769 ON "battle_win"."issue_id" = "issue_id_p"
jbe@170 5770 AND "battle_win"."winning_initiative_id" = "winning_initiative"."id"
jbe@170 5771 AND "battle_win"."losing_initiative_id" = "losing_initiative"."id"
jbe@170 5772 JOIN "battle" "battle_lose"
jbe@170 5773 ON "battle_lose"."issue_id" = "issue_id_p"
jbe@170 5774 AND "battle_lose"."losing_initiative_id" = "winning_initiative"."id"
jbe@170 5775 AND "battle_lose"."winning_initiative_id" = "losing_initiative"."id"
jbe@170 5776 WHERE "losing_initiative"."issue_id" = "issue_id_p"
jbe@170 5777 AND "losing_initiative"."admitted"
jbe@170 5778 AND "winning_initiative"."schulze_rank" <
jbe@170 5779 "losing_initiative"."schulze_rank"
jbe@170 5780 AND "battle_win"."count" > "battle_lose"."count"
jbe@170 5781 AND (
jbe@170 5782 "battle_win"."count" > "winning_initiative"."positive_votes" OR
jbe@170 5783 "battle_lose"."count" < "losing_initiative"."negative_votes" )
jbe@170 5784 ) AS "subquery"
jbe@170 5785 WHERE "id" = "subquery"."initiative_id";
jbe@170 5786 -- mark eligible initiatives:
jbe@170 5787 UPDATE "initiative" SET "eligible" = TRUE
jbe@171 5788 WHERE "issue_id" = "issue_id_p"
jbe@171 5789 AND "initiative"."direct_majority"
jbe@171 5790 AND "initiative"."indirect_majority"
jbe@171 5791 AND "initiative"."better_than_status_quo"
jbe@171 5792 AND (
jbe@171 5793 "policy_row"."no_multistage_majority" = FALSE OR
jbe@429 5794 "initiative"."multistage_majority" = FALSE )
jbe@429 5795 AND (
jbe@429 5796 "policy_row"."no_reverse_beat_path" = FALSE OR
jbe@429 5797 coalesce("initiative"."reverse_beat_path", FALSE) = FALSE );
jbe@170 5798 -- mark final winner:
jbe@170 5799 UPDATE "initiative" SET "winner" = TRUE
jbe@170 5800 FROM (
jbe@170 5801 SELECT "id" AS "initiative_id"
jbe@170 5802 FROM "initiative"
jbe@170 5803 WHERE "issue_id" = "issue_id_p" AND "eligible"
jbe@217 5804 ORDER BY
jbe@217 5805 "schulze_rank",
jbe@217 5806 "id"
jbe@170 5807 LIMIT 1
jbe@170 5808 ) AS "subquery"
jbe@170 5809 WHERE "id" = "subquery"."initiative_id";
jbe@173 5810 -- write (final) ranks:
jbe@173 5811 "rank_v" := 1;
jbe@173 5812 FOR "initiative_id_v" IN
jbe@173 5813 SELECT "id"
jbe@173 5814 FROM "initiative"
jbe@173 5815 WHERE "issue_id" = "issue_id_p" AND "admitted"
jbe@174 5816 ORDER BY
jbe@174 5817 "winner" DESC,
jbe@217 5818 "eligible" DESC,
jbe@174 5819 "schulze_rank",
jbe@174 5820 "id"
jbe@173 5821 LOOP
jbe@173 5822 UPDATE "initiative" SET "rank" = "rank_v"
jbe@173 5823 WHERE "id" = "initiative_id_v";
jbe@173 5824 "rank_v" := "rank_v" + 1;
jbe@173 5825 END LOOP;
jbe@170 5826 -- set schulze rank of status quo and mark issue as finished:
jbe@111 5827 UPDATE "issue" SET
jbe@411 5828 "status_quo_schulze_rank" = "rank_ary"[1],
jbe@111 5829 "state" =
jbe@139 5830 CASE WHEN EXISTS (
jbe@139 5831 SELECT NULL FROM "initiative"
jbe@139 5832 WHERE "issue_id" = "issue_id_p" AND "winner"
jbe@139 5833 ) THEN
jbe@139 5834 'finished_with_winner'::"issue_state"
jbe@139 5835 ELSE
jbe@121 5836 'finished_without_winner'::"issue_state"
jbe@111 5837 END,
jbe@331 5838 "closed" = "phase_finished",
jbe@331 5839 "phase_finished" = NULL
jbe@0 5840 WHERE "id" = "issue_id_p";
jbe@0 5841 RETURN;
jbe@0 5842 END;
jbe@0 5843 $$;
jbe@0 5844
jbe@0 5845 COMMENT ON FUNCTION "calculate_ranks"
jbe@0 5846 ( "issue"."id"%TYPE )
jbe@0 5847 IS 'Determine ranking (Votes have to be counted first)';
jbe@0 5848
jbe@0 5849
jbe@0 5850
jbe@0 5851 -----------------------------
jbe@0 5852 -- Automatic state changes --
jbe@0 5853 -----------------------------
jbe@0 5854
jbe@0 5855
jbe@532 5856 CREATE FUNCTION "issue_admission"
jbe@532 5857 ( "area_id_p" "area"."id"%TYPE )
jbe@528 5858 RETURNS BOOLEAN
jbe@528 5859 LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@528 5860 DECLARE
jbe@528 5861 "issue_id_v" "issue"."id"%TYPE;
jbe@528 5862 BEGIN
jbe@528 5863 PERFORM "dont_require_transaction_isolation"();
jbe@528 5864 LOCK TABLE "snapshot" IN EXCLUSIVE MODE;
jbe@532 5865 UPDATE "area" SET "issue_quorum" = "view"."issue_quorum"
jbe@532 5866 FROM "area_quorum" AS "view"
jbe@532 5867 WHERE "area"."id" = "view"."area_id"
jbe@532 5868 AND "area"."id" = "area_id_p";
jbe@532 5869 SELECT "id" INTO "issue_id_v" FROM "issue_for_admission"
jbe@532 5870 WHERE "area_id" = "area_id_p";
jbe@528 5871 IF "issue_id_v" ISNULL THEN RETURN FALSE; END IF;
jbe@528 5872 UPDATE "issue" SET
jbe@528 5873 "admission_snapshot_id" = "latest_snapshot_id",
jbe@528 5874 "state" = 'discussion',
jbe@528 5875 "accepted" = now(),
jbe@528 5876 "phase_finished" = NULL
jbe@528 5877 WHERE "id" = "issue_id_v";
jbe@528 5878 RETURN TRUE;
jbe@528 5879 END;
jbe@528 5880 $$;
jbe@528 5881
jbe@532 5882 COMMENT ON FUNCTION "issue_admission"
jbe@532 5883 ( "area"."id"%TYPE )
jbe@532 5884 IS 'Checks if an issue in the area can be admitted for further discussion; returns TRUE on success in which case the function must be called again until it returns FALSE';
jbe@528 5885
jbe@528 5886
jbe@331 5887 CREATE TYPE "check_issue_persistence" AS (
jbe@331 5888 "state" "issue_state",
jbe@331 5889 "phase_finished" BOOLEAN,
jbe@331 5890 "issue_revoked" BOOLEAN,
jbe@331 5891 "snapshot_created" BOOLEAN,
jbe@331 5892 "harmonic_weights_set" BOOLEAN,
jbe@331 5893 "closed_voting" BOOLEAN );
jbe@331 5894
jbe@336 5895 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 5896
jbe@336 5897
jbe@0 5898 CREATE FUNCTION "check_issue"
jbe@331 5899 ( "issue_id_p" "issue"."id"%TYPE,
jbe@331 5900 "persist" "check_issue_persistence" )
jbe@331 5901 RETURNS "check_issue_persistence"
jbe@0 5902 LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@0 5903 DECLARE
jbe@528 5904 "issue_row" "issue"%ROWTYPE;
jbe@528 5905 "last_calculated_v" "snapshot"."calculated"%TYPE;
jbe@528 5906 "policy_row" "policy"%ROWTYPE;
jbe@528 5907 "initiative_row" "initiative"%ROWTYPE;
jbe@528 5908 "state_v" "issue_state";
jbe@0 5909 BEGIN
jbe@333 5910 PERFORM "require_transaction_isolation"();
jbe@331 5911 IF "persist" ISNULL THEN
jbe@331 5912 SELECT * INTO "issue_row" FROM "issue" WHERE "id" = "issue_id_p"
jbe@331 5913 FOR UPDATE;
jbe@528 5914 SELECT "calculated" INTO "last_calculated_v"
jbe@528 5915 FROM "snapshot" JOIN "snapshot_issue"
jbe@528 5916 ON "snapshot"."id" = "snapshot_issue"."snapshot_id"
jbe@528 5917 WHERE "snapshot_issue"."issue_id" = "issue_id_p";
jbe@331 5918 IF "issue_row"."closed" NOTNULL THEN
jbe@331 5919 RETURN NULL;
jbe@0 5920 END IF;
jbe@331 5921 "persist"."state" := "issue_row"."state";
jbe@331 5922 IF
jbe@528 5923 ( "issue_row"."state" = 'admission' AND "last_calculated_v" >=
jbe@447 5924 "issue_row"."created" + "issue_row"."max_admission_time" ) OR
jbe@331 5925 ( "issue_row"."state" = 'discussion' AND now() >=
jbe@331 5926 "issue_row"."accepted" + "issue_row"."discussion_time" ) OR
jbe@331 5927 ( "issue_row"."state" = 'verification' AND now() >=
jbe@331 5928 "issue_row"."half_frozen" + "issue_row"."verification_time" ) OR
jbe@331 5929 ( "issue_row"."state" = 'voting' AND now() >=
jbe@331 5930 "issue_row"."fully_frozen" + "issue_row"."voting_time" )
jbe@331 5931 THEN
jbe@331 5932 "persist"."phase_finished" := TRUE;
jbe@331 5933 ELSE
jbe@331 5934 "persist"."phase_finished" := FALSE;
jbe@0 5935 END IF;
jbe@0 5936 IF
jbe@24 5937 NOT EXISTS (
jbe@24 5938 -- all initiatives are revoked
jbe@24 5939 SELECT NULL FROM "initiative"
jbe@24 5940 WHERE "issue_id" = "issue_id_p" AND "revoked" ISNULL
jbe@24 5941 ) AND (
jbe@111 5942 -- and issue has not been accepted yet
jbe@331 5943 "persist"."state" = 'admission' OR
jbe@331 5944 -- or verification time has elapsed
jbe@331 5945 ( "persist"."state" = 'verification' AND
jbe@331 5946 "persist"."phase_finished" ) OR
jbe@331 5947 -- or no initiatives have been revoked lately
jbe@24 5948 NOT EXISTS (
jbe@24 5949 SELECT NULL FROM "initiative"
jbe@24 5950 WHERE "issue_id" = "issue_id_p"
jbe@24 5951 AND now() < "revoked" + "issue_row"."verification_time"
jbe@24 5952 )
jbe@24 5953 )
jbe@24 5954 THEN
jbe@331 5955 "persist"."issue_revoked" := TRUE;
jbe@331 5956 ELSE
jbe@331 5957 "persist"."issue_revoked" := FALSE;
jbe@24 5958 END IF;
jbe@331 5959 IF "persist"."phase_finished" OR "persist"."issue_revoked" THEN
jbe@331 5960 UPDATE "issue" SET "phase_finished" = now()
jbe@331 5961 WHERE "id" = "issue_row"."id";
jbe@331 5962 RETURN "persist";
jbe@331 5963 ELSIF
jbe@331 5964 "persist"."state" IN ('admission', 'discussion', 'verification')
jbe@3 5965 THEN
jbe@331 5966 RETURN "persist";
jbe@331 5967 ELSE
jbe@331 5968 RETURN NULL;
jbe@322 5969 END IF;
jbe@0 5970 END IF;
jbe@331 5971 IF
jbe@331 5972 "persist"."state" IN ('admission', 'discussion', 'verification') AND
jbe@331 5973 coalesce("persist"."snapshot_created", FALSE) = FALSE
jbe@331 5974 THEN
jbe@528 5975 IF "persist"."state" != 'admission' THEN
jbe@528 5976 PERFORM "take_snapshot"("issue_id_p");
jbe@528 5977 PERFORM "finish_snapshot"("issue_id_p");
jbe@528 5978 END IF;
jbe@331 5979 "persist"."snapshot_created" = TRUE;
jbe@331 5980 IF "persist"."phase_finished" THEN
jbe@331 5981 IF "persist"."state" = 'admission' THEN
jbe@528 5982 UPDATE "issue" SET "admission_snapshot_id" = "latest_snapshot_id";
jbe@331 5983 ELSIF "persist"."state" = 'discussion' THEN
jbe@528 5984 UPDATE "issue" SET "half_freeze_snapshot_id" = "latest_snapshot_id";
jbe@331 5985 ELSIF "persist"."state" = 'verification' THEN
jbe@528 5986 UPDATE "issue" SET "full_freeze_snapshot_id" = "latest_snapshot_id";
jbe@336 5987 SELECT * INTO "issue_row" FROM "issue" WHERE "id" = "issue_id_p";
jbe@336 5988 SELECT * INTO "policy_row" FROM "policy"
jbe@336 5989 WHERE "id" = "issue_row"."policy_id";
jbe@336 5990 FOR "initiative_row" IN
jbe@336 5991 SELECT * FROM "initiative"
jbe@336 5992 WHERE "issue_id" = "issue_id_p" AND "revoked" ISNULL
jbe@336 5993 FOR UPDATE
jbe@336 5994 LOOP
jbe@336 5995 IF
jbe@336 5996 "initiative_row"."polling" OR (
jbe@532 5997 "initiative_row"."satisfied_supporter_count" >
jbe@532 5998 "policy_row"."initiative_quorum" AND
jbe@336 5999 "initiative_row"."satisfied_supporter_count" *
jbe@336 6000 "policy_row"."initiative_quorum_den" >=
jbe@336 6001 "issue_row"."population" * "policy_row"."initiative_quorum_num"
jbe@336 6002 )
jbe@336 6003 THEN
jbe@336 6004 UPDATE "initiative" SET "admitted" = TRUE
jbe@336 6005 WHERE "id" = "initiative_row"."id";
jbe@336 6006 ELSE
jbe@336 6007 UPDATE "initiative" SET "admitted" = FALSE
jbe@336 6008 WHERE "id" = "initiative_row"."id";
jbe@336 6009 END IF;
jbe@336 6010 END LOOP;
jbe@331 6011 END IF;
jbe@331 6012 END IF;
jbe@331 6013 RETURN "persist";
jbe@331 6014 END IF;
jbe@331 6015 IF
jbe@331 6016 "persist"."state" IN ('admission', 'discussion', 'verification') AND
jbe@331 6017 coalesce("persist"."harmonic_weights_set", FALSE) = FALSE
jbe@331 6018 THEN
jbe@331 6019 PERFORM "set_harmonic_initiative_weights"("issue_id_p");
jbe@331 6020 "persist"."harmonic_weights_set" = TRUE;
jbe@332 6021 IF
jbe@332 6022 "persist"."phase_finished" OR
jbe@332 6023 "persist"."issue_revoked" OR
jbe@332 6024 "persist"."state" = 'admission'
jbe@332 6025 THEN
jbe@331 6026 RETURN "persist";
jbe@331 6027 ELSE
jbe@331 6028 RETURN NULL;
jbe@331 6029 END IF;
jbe@331 6030 END IF;
jbe@331 6031 IF "persist"."issue_revoked" THEN
jbe@331 6032 IF "persist"."state" = 'admission' THEN
jbe@331 6033 "state_v" := 'canceled_revoked_before_accepted';
jbe@331 6034 ELSIF "persist"."state" = 'discussion' THEN
jbe@331 6035 "state_v" := 'canceled_after_revocation_during_discussion';
jbe@331 6036 ELSIF "persist"."state" = 'verification' THEN
jbe@331 6037 "state_v" := 'canceled_after_revocation_during_verification';
jbe@331 6038 END IF;
jbe@331 6039 UPDATE "issue" SET
jbe@331 6040 "state" = "state_v",
jbe@331 6041 "closed" = "phase_finished",
jbe@331 6042 "phase_finished" = NULL
jbe@332 6043 WHERE "id" = "issue_id_p";
jbe@331 6044 RETURN NULL;
jbe@331 6045 END IF;
jbe@331 6046 IF "persist"."state" = 'admission' THEN
jbe@336 6047 SELECT * INTO "issue_row" FROM "issue" WHERE "id" = "issue_id_p"
jbe@336 6048 FOR UPDATE;
jbe@528 6049 IF "issue_row"."phase_finished" NOTNULL THEN
jbe@336 6050 UPDATE "issue" SET
jbe@336 6051 "state" = 'canceled_issue_not_accepted',
jbe@336 6052 "closed" = "phase_finished",
jbe@336 6053 "phase_finished" = NULL
jbe@336 6054 WHERE "id" = "issue_id_p";
jbe@336 6055 END IF;
jbe@331 6056 RETURN NULL;
jbe@331 6057 END IF;
jbe@332 6058 IF "persist"."phase_finished" THEN
jbe@443 6059 IF "persist"."state" = 'discussion' THEN
jbe@332 6060 UPDATE "issue" SET
jbe@332 6061 "state" = 'verification',
jbe@332 6062 "half_frozen" = "phase_finished",
jbe@332 6063 "phase_finished" = NULL
jbe@332 6064 WHERE "id" = "issue_id_p";
jbe@332 6065 RETURN NULL;
jbe@332 6066 END IF;
jbe@332 6067 IF "persist"."state" = 'verification' THEN
jbe@336 6068 SELECT * INTO "issue_row" FROM "issue" WHERE "id" = "issue_id_p"
jbe@336 6069 FOR UPDATE;
jbe@336 6070 SELECT * INTO "policy_row" FROM "policy"
jbe@336 6071 WHERE "id" = "issue_row"."policy_id";
jbe@336 6072 IF EXISTS (
jbe@336 6073 SELECT NULL FROM "initiative"
jbe@336 6074 WHERE "issue_id" = "issue_id_p" AND "admitted" = TRUE
jbe@336 6075 ) THEN
jbe@336 6076 UPDATE "issue" SET
jbe@343 6077 "state" = 'voting',
jbe@343 6078 "fully_frozen" = "phase_finished",
jbe@336 6079 "phase_finished" = NULL
jbe@336 6080 WHERE "id" = "issue_id_p";
jbe@336 6081 ELSE
jbe@336 6082 UPDATE "issue" SET
jbe@343 6083 "state" = 'canceled_no_initiative_admitted',
jbe@343 6084 "fully_frozen" = "phase_finished",
jbe@343 6085 "closed" = "phase_finished",
jbe@343 6086 "phase_finished" = NULL
jbe@336 6087 WHERE "id" = "issue_id_p";
jbe@336 6088 -- NOTE: The following DELETE statements have effect only when
jbe@336 6089 -- issue state has been manipulated
jbe@336 6090 DELETE FROM "direct_voter" WHERE "issue_id" = "issue_id_p";
jbe@336 6091 DELETE FROM "delegating_voter" WHERE "issue_id" = "issue_id_p";
jbe@336 6092 DELETE FROM "battle" WHERE "issue_id" = "issue_id_p";
jbe@336 6093 END IF;
jbe@332 6094 RETURN NULL;
jbe@332 6095 END IF;
jbe@332 6096 IF "persist"."state" = 'voting' THEN
jbe@332 6097 IF coalesce("persist"."closed_voting", FALSE) = FALSE THEN
jbe@332 6098 PERFORM "close_voting"("issue_id_p");
jbe@332 6099 "persist"."closed_voting" = TRUE;
jbe@332 6100 RETURN "persist";
jbe@332 6101 END IF;
jbe@332 6102 PERFORM "calculate_ranks"("issue_id_p");
jbe@332 6103 RETURN NULL;
jbe@332 6104 END IF;
jbe@331 6105 END IF;
jbe@331 6106 RAISE WARNING 'should not happen';
jbe@331 6107 RETURN NULL;
jbe@0 6108 END;
jbe@0 6109 $$;
jbe@0 6110
jbe@0 6111 COMMENT ON FUNCTION "check_issue"
jbe@331 6112 ( "issue"."id"%TYPE,
jbe@331 6113 "check_issue_persistence" )
jbe@336 6114 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 6115
jbe@0 6116
jbe@0 6117 CREATE FUNCTION "check_everything"()
jbe@0 6118 RETURNS VOID
jbe@0 6119 LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@0 6120 DECLARE
jbe@532 6121 "area_id_v" "area"."id"%TYPE;
jbe@528 6122 "snapshot_id_v" "snapshot"."id"%TYPE;
jbe@528 6123 "issue_id_v" "issue"."id"%TYPE;
jbe@528 6124 "persist_v" "check_issue_persistence";
jbe@0 6125 BEGIN
jbe@333 6126 RAISE WARNING 'Function "check_everything" should only be used for development and debugging purposes';
jbe@235 6127 DELETE FROM "expired_session";
jbe@532 6128 DELETE FROM "expired_token";
jbe@532 6129 DELETE FROM "expired_snapshot";
jbe@184 6130 PERFORM "check_activity"();
jbe@4 6131 PERFORM "calculate_member_counts"();
jbe@532 6132 FOR "area_id_v" IN SELECT "id" FROM "area_with_unaccepted_issues" LOOP
jbe@532 6133 SELECT "take_snapshot"(NULL, "area_id_v") INTO "snapshot_id_v";
jbe@532 6134 PERFORM "finish_snapshot"("issue_id") FROM "snapshot_issue"
jbe@532 6135 WHERE "snapshot_id" = "snapshot_id_v";
jbe@532 6136 LOOP
jbe@532 6137 EXIT WHEN "issue_admission"("area_id_v") = FALSE;
jbe@532 6138 END LOOP;
jbe@528 6139 END LOOP;
jbe@4 6140 FOR "issue_id_v" IN SELECT "id" FROM "open_issue" LOOP
jbe@331 6141 "persist_v" := NULL;
jbe@331 6142 LOOP
jbe@331 6143 "persist_v" := "check_issue"("issue_id_v", "persist_v");
jbe@331 6144 EXIT WHEN "persist_v" ISNULL;
jbe@331 6145 END LOOP;
jbe@0 6146 END LOOP;
jbe@0 6147 RETURN;
jbe@0 6148 END;
jbe@0 6149 $$;
jbe@0 6150
jbe@532 6151 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. For production, use lf_update binary instead';
jbe@0 6152
jbe@0 6153
jbe@0 6154
jbe@59 6155 ----------------------
jbe@59 6156 -- Deletion of data --
jbe@59 6157 ----------------------
jbe@59 6158
jbe@59 6159
jbe@59 6160 CREATE FUNCTION "clean_issue"("issue_id_p" "issue"."id"%TYPE)
jbe@59 6161 RETURNS VOID
jbe@59 6162 LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@59 6163 BEGIN
jbe@385 6164 IF EXISTS (
jbe@385 6165 SELECT NULL FROM "issue" WHERE "id" = "issue_id_p" AND "cleaned" ISNULL
jbe@385 6166 ) THEN
jbe@385 6167 -- override protection triggers:
jbe@385 6168 INSERT INTO "temporary_transaction_data" ("key", "value")
jbe@385 6169 VALUES ('override_protection_triggers', TRUE::TEXT);
jbe@385 6170 -- clean data:
jbe@59 6171 DELETE FROM "delegating_voter"
jbe@59 6172 WHERE "issue_id" = "issue_id_p";
jbe@59 6173 DELETE FROM "direct_voter"
jbe@59 6174 WHERE "issue_id" = "issue_id_p";
jbe@59 6175 DELETE FROM "delegating_interest_snapshot"
jbe@59 6176 WHERE "issue_id" = "issue_id_p";
jbe@59 6177 DELETE FROM "direct_interest_snapshot"
jbe@59 6178 WHERE "issue_id" = "issue_id_p";
jbe@113 6179 DELETE FROM "non_voter"
jbe@94 6180 WHERE "issue_id" = "issue_id_p";
jbe@59 6181 DELETE FROM "delegation"
jbe@59 6182 WHERE "issue_id" = "issue_id_p";
jbe@59 6183 DELETE FROM "supporter"
jbe@329 6184 USING "initiative" -- NOTE: due to missing index on issue_id
jbe@325 6185 WHERE "initiative"."issue_id" = "issue_id_p"
jbe@325 6186 AND "supporter"."initiative_id" = "initiative_id";
jbe@385 6187 -- mark issue as cleaned:
jbe@385 6188 UPDATE "issue" SET "cleaned" = now() WHERE "id" = "issue_id_p";
jbe@385 6189 -- finish overriding protection triggers (avoids garbage):
jbe@385 6190 DELETE FROM "temporary_transaction_data"
jbe@385 6191 WHERE "key" = 'override_protection_triggers';
jbe@59 6192 END IF;
jbe@59 6193 RETURN;
jbe@59 6194 END;
jbe@59 6195 $$;
jbe@59 6196
jbe@59 6197 COMMENT ON FUNCTION "clean_issue"("issue"."id"%TYPE) IS 'Delete discussion data and votes belonging to an issue';
jbe@8 6198
jbe@8 6199
jbe@54 6200 CREATE FUNCTION "delete_member"("member_id_p" "member"."id"%TYPE)
jbe@8 6201 RETURNS VOID
jbe@8 6202 LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@8 6203 BEGIN
jbe@9 6204 UPDATE "member" SET
jbe@57 6205 "last_login" = NULL,
jbe@387 6206 "last_delegation_check" = NULL,
jbe@45 6207 "login" = NULL,
jbe@11 6208 "password" = NULL,
jbe@441 6209 "authority" = NULL,
jbe@441 6210 "authority_uid" = NULL,
jbe@441 6211 "authority_login" = NULL,
jbe@101 6212 "locked" = TRUE,
jbe@54 6213 "active" = FALSE,
jbe@11 6214 "notify_email" = NULL,
jbe@11 6215 "notify_email_unconfirmed" = NULL,
jbe@11 6216 "notify_email_secret" = NULL,
jbe@11 6217 "notify_email_secret_expiry" = NULL,
jbe@57 6218 "notify_email_lock_expiry" = NULL,
jbe@522 6219 "disable_notifications" = TRUE,
jbe@522 6220 "notification_counter" = DEFAULT,
jbe@522 6221 "notification_sample_size" = 0,
jbe@499 6222 "notification_dow" = NULL,
jbe@499 6223 "notification_hour" = NULL,
jbe@387 6224 "login_recovery_expiry" = NULL,
jbe@11 6225 "password_reset_secret" = NULL,
jbe@11 6226 "password_reset_secret_expiry" = NULL,
jbe@532 6227 "location" = NULL
jbe@45 6228 WHERE "id" = "member_id_p";
jbe@11 6229 -- "text_search_data" is updated by triggers
jbe@45 6230 DELETE FROM "setting" WHERE "member_id" = "member_id_p";
jbe@45 6231 DELETE FROM "setting_map" WHERE "member_id" = "member_id_p";
jbe@45 6232 DELETE FROM "member_relation_setting" WHERE "member_id" = "member_id_p";
jbe@45 6233 DELETE FROM "member_image" WHERE "member_id" = "member_id_p";
jbe@45 6234 DELETE FROM "contact" WHERE "member_id" = "member_id_p";
jbe@113 6235 DELETE FROM "ignored_member" WHERE "member_id" = "member_id_p";
jbe@235 6236 DELETE FROM "session" WHERE "member_id" = "member_id_p";
jbe@45 6237 DELETE FROM "area_setting" WHERE "member_id" = "member_id_p";
jbe@45 6238 DELETE FROM "issue_setting" WHERE "member_id" = "member_id_p";
jbe@113 6239 DELETE FROM "ignored_initiative" WHERE "member_id" = "member_id_p";
jbe@45 6240 DELETE FROM "initiative_setting" WHERE "member_id" = "member_id_p";
jbe@45 6241 DELETE FROM "suggestion_setting" WHERE "member_id" = "member_id_p";
jbe@54 6242 DELETE FROM "delegation" WHERE "truster_id" = "member_id_p";
jbe@113 6243 DELETE FROM "non_voter" WHERE "member_id" = "member_id_p";
jbe@57 6244 DELETE FROM "direct_voter" USING "issue"
jbe@57 6245 WHERE "direct_voter"."issue_id" = "issue"."id"
jbe@57 6246 AND "issue"."closed" ISNULL
jbe@57 6247 AND "member_id" = "member_id_p";
jbe@45 6248 RETURN;
jbe@45 6249 END;
jbe@45 6250 $$;
jbe@45 6251
jbe@57 6252 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 6253
jbe@45 6254
jbe@45 6255 CREATE FUNCTION "delete_private_data"()
jbe@45 6256 RETURNS VOID
jbe@45 6257 LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@45 6258 BEGIN
jbe@385 6259 DELETE FROM "temporary_transaction_data";
jbe@226 6260 DELETE FROM "member" WHERE "activated" ISNULL;
jbe@50 6261 UPDATE "member" SET
jbe@206 6262 "invite_code" = NULL,
jbe@232 6263 "invite_code_expiry" = NULL,
jbe@228 6264 "admin_comment" = NULL,
jbe@57 6265 "last_login" = NULL,
jbe@387 6266 "last_delegation_check" = NULL,
jbe@50 6267 "login" = NULL,
jbe@50 6268 "password" = NULL,
jbe@441 6269 "authority" = NULL,
jbe@441 6270 "authority_uid" = NULL,
jbe@441 6271 "authority_login" = NULL,
jbe@238 6272 "lang" = NULL,
jbe@50 6273 "notify_email" = NULL,
jbe@50 6274 "notify_email_unconfirmed" = NULL,
jbe@50 6275 "notify_email_secret" = NULL,
jbe@50 6276 "notify_email_secret_expiry" = NULL,
jbe@57 6277 "notify_email_lock_expiry" = NULL,
jbe@522 6278 "disable_notifications" = TRUE,
jbe@522 6279 "notification_counter" = DEFAULT,
jbe@522 6280 "notification_sample_size" = 0,
jbe@499 6281 "notification_dow" = NULL,
jbe@499 6282 "notification_hour" = NULL,
jbe@387 6283 "login_recovery_expiry" = NULL,
jbe@50 6284 "password_reset_secret" = NULL,
jbe@50 6285 "password_reset_secret_expiry" = NULL,
jbe@532 6286 "location" = NULL;
jbe@50 6287 -- "text_search_data" is updated by triggers
jbe@50 6288 DELETE FROM "setting";
jbe@50 6289 DELETE FROM "setting_map";
jbe@50 6290 DELETE FROM "member_relation_setting";
jbe@50 6291 DELETE FROM "member_image";
jbe@50 6292 DELETE FROM "contact";
jbe@113 6293 DELETE FROM "ignored_member";
jbe@235 6294 DELETE FROM "session";
jbe@50 6295 DELETE FROM "area_setting";
jbe@50 6296 DELETE FROM "issue_setting";
jbe@113 6297 DELETE FROM "ignored_initiative";
jbe@50 6298 DELETE FROM "initiative_setting";
jbe@50 6299 DELETE FROM "suggestion_setting";
jbe@113 6300 DELETE FROM "non_voter";
jbe@8 6301 DELETE FROM "direct_voter" USING "issue"
jbe@8 6302 WHERE "direct_voter"."issue_id" = "issue"."id"
jbe@8 6303 AND "issue"."closed" ISNULL;
jbe@8 6304 RETURN;
jbe@8 6305 END;
jbe@8 6306 $$;
jbe@8 6307
jbe@273 6308 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 6309
jbe@8 6310
jbe@8 6311
jbe@0 6312 COMMIT;

Impressum / About Us