liquid_feedback_core

annotate core.sql @ 86:3a86196ed0bf

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

Impressum / About Us