liquid_feedback_core

annotate core.sql @ 59:fdd1729d7e2a

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

Impressum / About Us