liquid_feedback_core

annotate core.sql @ 72:dcaa1525c388

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

Impressum / About Us