liquid_feedback_core

annotate core.sql @ 11:015825e225ca

Version beta12

Changed function delete_private_data() to delete more data, including tables setting and member_image, for better data protection

Bugfix related to linux-sh in shell script lf_export

New fields password_reset_secret and password_reset_secret_expiry in member table

UNIQUE constraint for notify_email_secret and password_reset_secret fields in member table

Database constraint forbids adding yourself as a contact
author jbe
date Sat Jan 02 12:00:00 2010 +0100 (2010-01-02)
parents effdd7a04ea7
children a67c1cd4facf
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@11 9 SELECT * FROM (VALUES ('beta12', NULL, NULL, NULL))
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@0 58 "login" TEXT NOT NULL UNIQUE,
jbe@0 59 "password" TEXT,
jbe@0 60 "active" BOOLEAN NOT NULL DEFAULT TRUE,
jbe@0 61 "admin" BOOLEAN NOT NULL DEFAULT FALSE,
jbe@7 62 "notify_email" TEXT,
jbe@11 63 "notify_email_unconfirmed" TEXT,
jbe@11 64 "notify_email_secret" TEXT UNIQUE,
jbe@11 65 "notify_email_secret_expiry" TIMESTAMPTZ,
jbe@11 66 "password_reset_secret" TEXT UNIQUE,
jbe@11 67 "password_reset_secret_expiry" TIMESTAMPTZ,
jbe@7 68 "name" TEXT NOT NULL UNIQUE,
jbe@7 69 "identification" TEXT UNIQUE,
jbe@7 70 "organizational_unit" TEXT,
jbe@7 71 "internal_posts" TEXT,
jbe@7 72 "realname" TEXT,
jbe@7 73 "birthday" DATE,
jbe@7 74 "address" TEXT,
jbe@7 75 "email" TEXT,
jbe@7 76 "xmpp_address" TEXT,
jbe@7 77 "website" TEXT,
jbe@7 78 "phone" TEXT,
jbe@7 79 "mobile_phone" TEXT,
jbe@7 80 "profession" TEXT,
jbe@7 81 "external_memberships" TEXT,
jbe@7 82 "external_posts" TEXT,
jbe@7 83 "statement" TEXT,
jbe@10 84 "text_search_data" TSVECTOR );
jbe@0 85 CREATE INDEX "member_active_idx" ON "member" ("active");
jbe@8 86 CREATE INDEX "member_text_search_data_idx" ON "member" USING gin ("text_search_data");
jbe@7 87 CREATE TRIGGER "update_text_search_data"
jbe@7 88 BEFORE INSERT OR UPDATE ON "member"
jbe@7 89 FOR EACH ROW EXECUTE PROCEDURE
jbe@7 90 tsvector_update_trigger('text_search_data', 'pg_catalog.simple',
jbe@7 91 "name", "identification", "organizational_unit", "internal_posts",
jbe@7 92 "realname", "external_memberships", "external_posts", "statement" );
jbe@0 93
jbe@0 94 COMMENT ON TABLE "member" IS 'Users of the system, e.g. members of an organization';
jbe@0 95
jbe@10 96 COMMENT ON COLUMN "member"."login" IS 'Login name';
jbe@10 97 COMMENT ON COLUMN "member"."password" IS 'Password (preferably as crypto-hash, depending on the frontend or access layer)';
jbe@10 98 COMMENT ON COLUMN "member"."active" IS 'Inactive members can not login and their supports/votes are not counted by the system.';
jbe@10 99 COMMENT ON COLUMN "member"."admin" IS 'TRUE for admins, which can administrate other users and setup policies and areas';
jbe@10 100 COMMENT ON COLUMN "member"."notify_email" IS 'Email address where notifications of the system are sent to';
jbe@10 101 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 102 COMMENT ON COLUMN "member"."notify_email_secret" IS 'Secret sent to the address in "notify_email_unconformed"';
jbe@10 103 COMMENT ON COLUMN "member"."notify_email_secret_expiry" IS 'Expiry date/time for "notify_email_secret"';
jbe@10 104 COMMENT ON COLUMN "member"."name" IS 'Distinct name of the member';
jbe@10 105 COMMENT ON COLUMN "member"."identification" IS 'Optional identification number or code of the member';
jbe@10 106 COMMENT ON COLUMN "member"."organizational_unit" IS 'Branch or division of the organization the member belongs to';
jbe@10 107 COMMENT ON COLUMN "member"."internal_posts" IS 'Posts (offices) of the member inside the organization';
jbe@10 108 COMMENT ON COLUMN "member"."realname" IS 'Real name of the member, may be identical with "name"';
jbe@10 109 COMMENT ON COLUMN "member"."email" IS 'Published email address of the member; not used for system notifications';
jbe@10 110 COMMENT ON COLUMN "member"."external_memberships" IS 'Other organizations the member is involved in';
jbe@10 111 COMMENT ON COLUMN "member"."external_posts" IS 'Posts (offices) outside the organization';
jbe@10 112 COMMENT ON COLUMN "member"."statement" IS 'Freely chosen text of the member for his homepage within the system';
jbe@7 113
jbe@7 114
jbe@9 115 CREATE TABLE "invite_code" (
jbe@9 116 "code" TEXT PRIMARY KEY,
jbe@9 117 "created" TIMESTAMPTZ NOT NULL DEFAULT now(),
jbe@9 118 "used" TIMESTAMPTZ,
jbe@9 119 "member_id" INT4 UNIQUE REFERENCES "member" ("id") ON DELETE SET NULL ON UPDATE CASCADE,
jbe@9 120 "comment" TEXT,
jbe@9 121 CONSTRAINT "only_used_codes_may_refer_to_member" CHECK ("used" NOTNULL OR "member_id" ISNULL) );
jbe@9 122
jbe@9 123 COMMENT ON TABLE "invite_code" IS 'Invite codes can be used once to create a new member account.';
jbe@9 124
jbe@9 125 COMMENT ON COLUMN "invite_code"."code" IS 'Secret code';
jbe@9 126 COMMENT ON COLUMN "invite_code"."created" IS 'Time of creation of the secret code';
jbe@9 127 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 128 COMMENT ON COLUMN "invite_code"."member_id" IS 'References the member whose account was created with this code';
jbe@9 129 COMMENT ON COLUMN "invite_code"."comment" IS 'Comment on the code, which is to be used for administrative reasons only';
jbe@9 130
jbe@9 131
jbe@9 132 CREATE TABLE "setting" (
jbe@9 133 PRIMARY KEY ("member_id", "key"),
jbe@9 134 "member_id" INT4 REFERENCES "member" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@9 135 "key" TEXT NOT NULL,
jbe@9 136 "value" TEXT NOT NULL );
jbe@9 137 CREATE INDEX "setting_key_idx" ON "setting" ("key");
jbe@9 138
jbe@9 139 COMMENT ON TABLE "setting" IS 'Place to store frontend specific member settings';
jbe@9 140
jbe@9 141 COMMENT ON COLUMN "setting"."key" IS 'Name of the setting, preceded by a frontend specific prefix';
jbe@9 142
jbe@9 143
jbe@7 144 CREATE TYPE "member_image_type" AS ENUM ('photo', 'avatar');
jbe@7 145
jbe@7 146 COMMENT ON TYPE "member_image_type" IS 'Types of images for a member';
jbe@7 147
jbe@7 148
jbe@7 149 CREATE TABLE "member_image" (
jbe@7 150 PRIMARY KEY ("member_id", "image_type", "scaled"),
jbe@7 151 "member_id" INT4 REFERENCES "member" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@7 152 "image_type" "member_image_type",
jbe@7 153 "scaled" BOOLEAN,
jbe@7 154 "content_type" TEXT,
jbe@7 155 "data" BYTEA NOT NULL );
jbe@7 156
jbe@7 157 COMMENT ON TABLE "member_image" IS 'Images of members';
jbe@7 158
jbe@7 159 COMMENT ON COLUMN "member_image"."scaled" IS 'FALSE for original image, TRUE for scaled version of the image';
jbe@0 160
jbe@0 161
jbe@4 162 CREATE TABLE "member_count" (
jbe@5 163 "calculated" TIMESTAMPTZ NOT NULL DEFAULT NOW(),
jbe@5 164 "total_count" INT4 NOT NULL );
jbe@4 165
jbe@5 166 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 167
jbe@5 168 COMMENT ON COLUMN "member_count"."calculated" IS 'timestamp indicating when the total member count and area member counts were calculated';
jbe@5 169 COMMENT ON COLUMN "member_count"."total_count" IS 'Total count of active(!) members';
jbe@4 170
jbe@4 171
jbe@0 172 CREATE TABLE "contact" (
jbe@0 173 PRIMARY KEY ("member_id", "other_member_id"),
jbe@0 174 "member_id" INT4 REFERENCES "member" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@0 175 "other_member_id" INT4 REFERENCES "member" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@11 176 "public" BOOLEAN NOT NULL DEFAULT FALSE,
jbe@11 177 CONSTRAINT "cant_save_yourself_as_contact"
jbe@11 178 CHECK ("member_id" != "other_member_id") );
jbe@0 179
jbe@0 180 COMMENT ON TABLE "contact" IS 'Contact lists';
jbe@0 181
jbe@0 182 COMMENT ON COLUMN "contact"."member_id" IS 'Member having the contact list';
jbe@0 183 COMMENT ON COLUMN "contact"."other_member_id" IS 'Member referenced in the contact list';
jbe@0 184 COMMENT ON COLUMN "contact"."public" IS 'TRUE = display contact publically';
jbe@0 185
jbe@0 186
jbe@0 187 CREATE TABLE "session" (
jbe@0 188 "ident" TEXT PRIMARY KEY,
jbe@0 189 "additional_secret" TEXT,
jbe@0 190 "expiry" TIMESTAMPTZ NOT NULL DEFAULT now() + '24 hours',
jbe@0 191 "member_id" INT8 REFERENCES "member" ("id") ON DELETE SET NULL,
jbe@0 192 "lang" TEXT );
jbe@0 193 CREATE INDEX "session_expiry_idx" ON "session" ("expiry");
jbe@0 194
jbe@0 195 COMMENT ON TABLE "session" IS 'Sessions, i.e. for a web-frontend';
jbe@0 196
jbe@0 197 COMMENT ON COLUMN "session"."ident" IS 'Secret session identifier (i.e. random string)';
jbe@0 198 COMMENT ON COLUMN "session"."additional_secret" IS 'Additional field to store a secret, which can be used against CSRF attacks';
jbe@0 199 COMMENT ON COLUMN "session"."member_id" IS 'Reference to member, who is logged in';
jbe@0 200 COMMENT ON COLUMN "session"."lang" IS 'Language code of the selected language';
jbe@0 201
jbe@0 202
jbe@0 203 CREATE TABLE "policy" (
jbe@0 204 "id" SERIAL4 PRIMARY KEY,
jbe@9 205 "index" INT4 NOT NULL,
jbe@0 206 "active" BOOLEAN NOT NULL DEFAULT TRUE,
jbe@0 207 "name" TEXT NOT NULL UNIQUE,
jbe@0 208 "description" TEXT NOT NULL DEFAULT '',
jbe@0 209 "admission_time" INTERVAL NOT NULL,
jbe@0 210 "discussion_time" INTERVAL NOT NULL,
jbe@3 211 "verification_time" INTERVAL NOT NULL,
jbe@0 212 "voting_time" INTERVAL NOT NULL,
jbe@0 213 "issue_quorum_num" INT4 NOT NULL,
jbe@0 214 "issue_quorum_den" INT4 NOT NULL,
jbe@0 215 "initiative_quorum_num" INT4 NOT NULL,
jbe@10 216 "initiative_quorum_den" INT4 NOT NULL,
jbe@10 217 "majority_num" INT4 NOT NULL DEFAULT 1,
jbe@10 218 "majority_den" INT4 NOT NULL DEFAULT 2,
jbe@10 219 "majority_strict" BOOLEAN NOT NULL DEFAULT TRUE );
jbe@0 220 CREATE INDEX "policy_active_idx" ON "policy" ("active");
jbe@0 221
jbe@0 222 COMMENT ON TABLE "policy" IS 'Policies for a particular proceeding type (timelimits, quorum)';
jbe@0 223
jbe@9 224 COMMENT ON COLUMN "policy"."index" IS 'Determines the order in listings';
jbe@0 225 COMMENT ON COLUMN "policy"."active" IS 'TRUE = policy can be used for new issues';
jbe@0 226 COMMENT ON COLUMN "policy"."admission_time" IS 'Maximum time an issue stays open without being "accepted"';
jbe@3 227 COMMENT ON COLUMN "policy"."discussion_time" IS 'Regular time until an issue is "half_frozen" after being "accepted"';
jbe@3 228 COMMENT ON COLUMN "policy"."verification_time" IS 'Regular time until an issue is "fully_frozen" after being "half_frozen"';
jbe@3 229 COMMENT ON COLUMN "policy"."voting_time" IS 'Time after an issue is "fully_frozen" but not "closed"';
jbe@10 230 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 231 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 232 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 233 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 234 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 235 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 236 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 237
jbe@0 238
jbe@0 239 CREATE TABLE "area" (
jbe@0 240 "id" SERIAL4 PRIMARY KEY,
jbe@0 241 "active" BOOLEAN NOT NULL DEFAULT TRUE,
jbe@0 242 "name" TEXT NOT NULL,
jbe@4 243 "description" TEXT NOT NULL DEFAULT '',
jbe@5 244 "direct_member_count" INT4,
jbe@5 245 "member_weight" INT4,
jbe@7 246 "autoreject_weight" INT4,
jbe@7 247 "text_search_data" TSVECTOR );
jbe@0 248 CREATE INDEX "area_active_idx" ON "area" ("active");
jbe@8 249 CREATE INDEX "area_text_search_data_idx" ON "area" USING gin ("text_search_data");
jbe@7 250 CREATE TRIGGER "update_text_search_data"
jbe@7 251 BEFORE INSERT OR UPDATE ON "area"
jbe@7 252 FOR EACH ROW EXECUTE PROCEDURE
jbe@7 253 tsvector_update_trigger('text_search_data', 'pg_catalog.simple',
jbe@7 254 "name", "description" );
jbe@0 255
jbe@0 256 COMMENT ON TABLE "area" IS 'Subject areas';
jbe@0 257
jbe@5 258 COMMENT ON COLUMN "area"."active" IS 'TRUE means new issues can be created in this area';
jbe@5 259 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 260 COMMENT ON COLUMN "area"."member_weight" IS 'Same as "direct_member_count" but respecting delegations';
jbe@5 261 COMMENT ON COLUMN "area"."autoreject_weight" IS 'Sum of weight of members using the autoreject feature';
jbe@0 262
jbe@0 263
jbe@9 264 CREATE TABLE "allowed_policy" (
jbe@9 265 PRIMARY KEY ("area_id", "policy_id"),
jbe@9 266 "area_id" INT4 REFERENCES "area" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@9 267 "policy_id" INT4 NOT NULL REFERENCES "policy" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@9 268 "default_policy" BOOLEAN NOT NULL DEFAULT FALSE );
jbe@9 269 CREATE UNIQUE INDEX "allowed_policy_one_default_per_area_idx" ON "allowed_policy" ("area_id") WHERE "default_policy";
jbe@9 270
jbe@9 271 COMMENT ON TABLE "allowed_policy" IS 'Selects which policies can be used in each area';
jbe@9 272
jbe@9 273 COMMENT ON COLUMN "allowed_policy"."default_policy" IS 'One policy per area can be set as default.';
jbe@9 274
jbe@9 275
jbe@8 276 CREATE TYPE "snapshot_event" AS ENUM ('periodic', 'end_of_admission', 'start_of_voting');
jbe@8 277
jbe@8 278 COMMENT ON TYPE "snapshot_event" IS 'Reason for snapshots: ''periodic'' = due to periodic recalculation, ''end_of_admission'' = saved state at end of admission period, ''start_of_voting'' = saved state at end of verification period';
jbe@8 279
jbe@8 280
jbe@0 281 CREATE TABLE "issue" (
jbe@0 282 "id" SERIAL4 PRIMARY KEY,
jbe@0 283 "area_id" INT4 NOT NULL REFERENCES "area" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@0 284 "policy_id" INT4 NOT NULL REFERENCES "policy" ("id") ON DELETE RESTRICT ON UPDATE CASCADE,
jbe@0 285 "created" TIMESTAMPTZ NOT NULL DEFAULT now(),
jbe@0 286 "accepted" TIMESTAMPTZ,
jbe@3 287 "half_frozen" TIMESTAMPTZ,
jbe@3 288 "fully_frozen" TIMESTAMPTZ,
jbe@0 289 "closed" TIMESTAMPTZ,
jbe@0 290 "ranks_available" BOOLEAN NOT NULL DEFAULT FALSE,
jbe@0 291 "snapshot" TIMESTAMPTZ,
jbe@8 292 "latest_snapshot_event" "snapshot_event",
jbe@0 293 "population" INT4,
jbe@0 294 "vote_now" INT4,
jbe@0 295 "vote_later" INT4,
jbe@4 296 "voter_count" INT4,
jbe@0 297 CONSTRAINT "valid_state" CHECK (
jbe@3 298 ("accepted" ISNULL AND "half_frozen" ISNULL AND "fully_frozen" ISNULL AND "closed" ISNULL AND "ranks_available" = FALSE) OR
jbe@3 299 ("accepted" ISNULL AND "half_frozen" ISNULL AND "fully_frozen" ISNULL AND "closed" NOTNULL AND "ranks_available" = FALSE) OR
jbe@3 300 ("accepted" NOTNULL AND "half_frozen" ISNULL AND "fully_frozen" ISNULL AND "closed" ISNULL AND "ranks_available" = FALSE) OR
jbe@3 301 ("accepted" NOTNULL AND "half_frozen" NOTNULL AND "fully_frozen" ISNULL AND "closed" ISNULL AND "ranks_available" = FALSE) OR
jbe@3 302 ("accepted" NOTNULL AND "half_frozen" NOTNULL AND "fully_frozen" NOTNULL AND "closed" ISNULL AND "ranks_available" = FALSE) OR
jbe@3 303 ("accepted" NOTNULL AND "half_frozen" NOTNULL AND "fully_frozen" NOTNULL AND "closed" NOTNULL AND "ranks_available" = FALSE) OR
jbe@3 304 ("accepted" NOTNULL AND "half_frozen" NOTNULL AND "fully_frozen" NOTNULL AND "closed" NOTNULL AND "ranks_available" = TRUE) ),
jbe@3 305 CONSTRAINT "state_change_order" CHECK (
jbe@10 306 "created" <= "accepted" AND
jbe@10 307 "accepted" <= "half_frozen" AND
jbe@10 308 "half_frozen" <= "fully_frozen" AND
jbe@3 309 "fully_frozen" <= "closed" ),
jbe@10 310 CONSTRAINT "last_snapshot_on_full_freeze"
jbe@10 311 CHECK ("snapshot" = "fully_frozen"), -- NOTE: snapshot can be set, while frozen is NULL yet
jbe@10 312 CONSTRAINT "freeze_requires_snapshot"
jbe@10 313 CHECK ("fully_frozen" ISNULL OR "snapshot" NOTNULL),
jbe@10 314 CONSTRAINT "set_both_or_none_of_snapshot_and_latest_snapshot_event"
jbe@10 315 CHECK ("snapshot" NOTNULL = "latest_snapshot_event" NOTNULL) );
jbe@0 316 CREATE INDEX "issue_area_id_idx" ON "issue" ("area_id");
jbe@0 317 CREATE INDEX "issue_policy_id_idx" ON "issue" ("policy_id");
jbe@0 318 CREATE INDEX "issue_created_idx_open" ON "issue" ("created") WHERE "closed" ISNULL;
jbe@0 319
jbe@0 320 COMMENT ON TABLE "issue" IS 'Groups of initiatives';
jbe@0 321
jbe@8 322 COMMENT ON COLUMN "issue"."accepted" IS 'Point in time, when one initiative of issue reached the "issue_quorum"';
jbe@10 323 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 324 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 325 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 326 COMMENT ON COLUMN "issue"."ranks_available" IS 'TRUE = ranks have been calculated';
jbe@8 327 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 328 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 329 COMMENT ON COLUMN "issue"."population" IS 'Sum of "weight" column in table "direct_population_snapshot"';
jbe@8 330 COMMENT ON COLUMN "issue"."vote_now" IS 'Number of votes in favor of voting now, as calculated from table "direct_interest_snapshot"';
jbe@8 331 COMMENT ON COLUMN "issue"."vote_later" IS 'Number of votes against voting now, as calculated from table "direct_interest_snapshot"';
jbe@8 332 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 333
jbe@0 334
jbe@0 335 CREATE TABLE "initiative" (
jbe@0 336 UNIQUE ("issue_id", "id"), -- index needed for foreign-key on table "vote"
jbe@0 337 "issue_id" INT4 NOT NULL REFERENCES "issue" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@0 338 "id" SERIAL4 PRIMARY KEY,
jbe@0 339 "name" TEXT NOT NULL,
jbe@8 340 "discussion_url" TEXT,
jbe@0 341 "created" TIMESTAMPTZ NOT NULL DEFAULT now(),
jbe@0 342 "revoked" TIMESTAMPTZ,
jbe@0 343 "admitted" BOOLEAN,
jbe@0 344 "supporter_count" INT4,
jbe@0 345 "informed_supporter_count" INT4,
jbe@0 346 "satisfied_supporter_count" INT4,
jbe@0 347 "satisfied_informed_supporter_count" INT4,
jbe@0 348 "positive_votes" INT4,
jbe@0 349 "negative_votes" INT4,
jbe@10 350 "agreed" BOOLEAN,
jbe@0 351 "rank" INT4,
jbe@7 352 "text_search_data" TSVECTOR,
jbe@0 353 CONSTRAINT "revoked_initiatives_cant_be_admitted"
jbe@0 354 CHECK ("revoked" ISNULL OR "admitted" ISNULL),
jbe@0 355 CONSTRAINT "non_admitted_initiatives_cant_contain_voting_results"
jbe@10 356 CHECK (("admitted" NOTNULL AND "admitted" = TRUE) OR ("positive_votes" ISNULL AND "negative_votes" ISNULL AND "agreed" ISNULL)),
jbe@10 357 CONSTRAINT "all_or_none_of_positive_votes_negative_votes_and_agreed_must_be_null"
jbe@10 358 CHECK ("positive_votes" NOTNULL = "negative_votes" NOTNULL AND "positive_votes" NOTNULL = "agreed" NOTNULL),
jbe@10 359 CONSTRAINT "non_agreed_initiatives_cant_get_a_rank"
jbe@10 360 CHECK (("agreed" NOTNULL AND "agreed" = TRUE) OR "rank" ISNULL) );
jbe@8 361 CREATE INDEX "initiative_text_search_data_idx" ON "initiative" USING gin ("text_search_data");
jbe@7 362 CREATE TRIGGER "update_text_search_data"
jbe@7 363 BEFORE INSERT OR UPDATE ON "initiative"
jbe@7 364 FOR EACH ROW EXECUTE PROCEDURE
jbe@8 365 tsvector_update_trigger('text_search_data', 'pg_catalog.simple',
jbe@8 366 "name", "discussion_url");
jbe@0 367
jbe@10 368 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 369
jbe@8 370 COMMENT ON COLUMN "initiative"."discussion_url" IS 'URL pointing to a discussion platform for this initiative';
jbe@0 371 COMMENT ON COLUMN "initiative"."revoked" IS 'Point in time, when one initiator decided to revoke the initiative';
jbe@10 372 COMMENT ON COLUMN "initiative"."admitted" IS 'TRUE, if initiative reaches the "initiative_quorum" when freezing the issue';
jbe@0 373 COMMENT ON COLUMN "initiative"."supporter_count" IS 'Calculated from table "direct_supporter_snapshot"';
jbe@0 374 COMMENT ON COLUMN "initiative"."informed_supporter_count" IS 'Calculated from table "direct_supporter_snapshot"';
jbe@0 375 COMMENT ON COLUMN "initiative"."satisfied_supporter_count" IS 'Calculated from table "direct_supporter_snapshot"';
jbe@0 376 COMMENT ON COLUMN "initiative"."satisfied_informed_supporter_count" IS 'Calculated from table "direct_supporter_snapshot"';
jbe@0 377 COMMENT ON COLUMN "initiative"."positive_votes" IS 'Calculated from table "direct_voter"';
jbe@0 378 COMMENT ON COLUMN "initiative"."negative_votes" IS 'Calculated from table "direct_voter"';
jbe@10 379 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 380 COMMENT ON COLUMN "initiative"."rank" IS 'Rank of approved initiatives (winner is 1), calculated from table "direct_voter"';
jbe@0 381
jbe@0 382
jbe@0 383 CREATE TABLE "draft" (
jbe@0 384 UNIQUE ("initiative_id", "id"), -- index needed for foreign-key on table "supporter"
jbe@0 385 "initiative_id" INT4 NOT NULL REFERENCES "initiative" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@0 386 "id" SERIAL8 PRIMARY KEY,
jbe@0 387 "created" TIMESTAMPTZ NOT NULL DEFAULT now(),
jbe@0 388 "author_id" INT4 NOT NULL REFERENCES "member" ("id") ON DELETE RESTRICT ON UPDATE CASCADE,
jbe@9 389 "formatting_engine" TEXT,
jbe@7 390 "content" TEXT NOT NULL,
jbe@7 391 "text_search_data" TSVECTOR );
jbe@9 392 CREATE INDEX "draft_author_id_created_idx" ON "draft" ("author_id", "created");
jbe@8 393 CREATE INDEX "draft_text_search_data_idx" ON "draft" USING gin ("text_search_data");
jbe@7 394 CREATE TRIGGER "update_text_search_data"
jbe@7 395 BEFORE INSERT OR UPDATE ON "draft"
jbe@7 396 FOR EACH ROW EXECUTE PROCEDURE
jbe@7 397 tsvector_update_trigger('text_search_data', 'pg_catalog.simple', "content");
jbe@0 398
jbe@10 399 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 400
jbe@9 401 COMMENT ON COLUMN "draft"."formatting_engine" IS 'Allows different formatting engines (i.e. wiki formats) to be used';
jbe@9 402 COMMENT ON COLUMN "draft"."content" IS 'Text of the draft in a format depending on the field "formatting_engine"';
jbe@9 403
jbe@0 404
jbe@0 405 CREATE TABLE "suggestion" (
jbe@0 406 UNIQUE ("initiative_id", "id"), -- index needed for foreign-key on table "opinion"
jbe@0 407 "initiative_id" INT4 NOT NULL REFERENCES "initiative" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@0 408 "id" SERIAL8 PRIMARY KEY,
jbe@0 409 "created" TIMESTAMPTZ NOT NULL DEFAULT now(),
jbe@0 410 "author_id" INT4 NOT NULL REFERENCES "member" ("id") ON DELETE RESTRICT ON UPDATE CASCADE,
jbe@0 411 "name" TEXT NOT NULL,
jbe@0 412 "description" TEXT NOT NULL DEFAULT '',
jbe@7 413 "text_search_data" TSVECTOR,
jbe@0 414 "minus2_unfulfilled_count" INT4,
jbe@0 415 "minus2_fulfilled_count" INT4,
jbe@0 416 "minus1_unfulfilled_count" INT4,
jbe@0 417 "minus1_fulfilled_count" INT4,
jbe@0 418 "plus1_unfulfilled_count" INT4,
jbe@0 419 "plus1_fulfilled_count" INT4,
jbe@0 420 "plus2_unfulfilled_count" INT4,
jbe@0 421 "plus2_fulfilled_count" INT4 );
jbe@9 422 CREATE INDEX "suggestion_author_id_created_idx" ON "suggestion" ("author_id", "created");
jbe@8 423 CREATE INDEX "suggestion_text_search_data_idx" ON "suggestion" USING gin ("text_search_data");
jbe@7 424 CREATE TRIGGER "update_text_search_data"
jbe@7 425 BEFORE INSERT OR UPDATE ON "suggestion"
jbe@7 426 FOR EACH ROW EXECUTE PROCEDURE
jbe@7 427 tsvector_update_trigger('text_search_data', 'pg_catalog.simple',
jbe@7 428 "name", "description");
jbe@0 429
jbe@10 430 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 431
jbe@0 432 COMMENT ON COLUMN "suggestion"."minus2_unfulfilled_count" IS 'Calculated from table "direct_supporter_snapshot", not requiring informed supporters';
jbe@0 433 COMMENT ON COLUMN "suggestion"."minus2_fulfilled_count" IS 'Calculated from table "direct_supporter_snapshot", not requiring informed supporters';
jbe@0 434 COMMENT ON COLUMN "suggestion"."minus1_unfulfilled_count" IS 'Calculated from table "direct_supporter_snapshot", not requiring informed supporters';
jbe@0 435 COMMENT ON COLUMN "suggestion"."minus1_fulfilled_count" IS 'Calculated from table "direct_supporter_snapshot", not requiring informed supporters';
jbe@0 436 COMMENT ON COLUMN "suggestion"."plus1_unfulfilled_count" IS 'Calculated from table "direct_supporter_snapshot", not requiring informed supporters';
jbe@0 437 COMMENT ON COLUMN "suggestion"."plus1_fulfilled_count" IS 'Calculated from table "direct_supporter_snapshot", not requiring informed supporters';
jbe@0 438 COMMENT ON COLUMN "suggestion"."plus2_unfulfilled_count" IS 'Calculated from table "direct_supporter_snapshot", not requiring informed supporters';
jbe@0 439 COMMENT ON COLUMN "suggestion"."plus2_fulfilled_count" IS 'Calculated from table "direct_supporter_snapshot", not requiring informed supporters';
jbe@0 440
jbe@0 441
jbe@0 442 CREATE TABLE "membership" (
jbe@0 443 PRIMARY KEY ("area_id", "member_id"),
jbe@0 444 "area_id" INT4 REFERENCES "area" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@0 445 "member_id" INT4 REFERENCES "member" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@0 446 "autoreject" BOOLEAN NOT NULL DEFAULT FALSE );
jbe@0 447 CREATE INDEX "membership_member_id_idx" ON "membership" ("member_id");
jbe@0 448
jbe@0 449 COMMENT ON TABLE "membership" IS 'Interest of members in topic areas';
jbe@0 450
jbe@0 451 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 452
jbe@0 453
jbe@0 454 CREATE TABLE "interest" (
jbe@0 455 PRIMARY KEY ("issue_id", "member_id"),
jbe@0 456 "issue_id" INT4 REFERENCES "issue" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@0 457 "member_id" INT4 REFERENCES "member" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@0 458 "autoreject" BOOLEAN NOT NULL,
jbe@0 459 "voting_requested" BOOLEAN );
jbe@0 460 CREATE INDEX "interest_member_id_idx" ON "interest" ("member_id");
jbe@0 461
jbe@10 462 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 463
jbe@0 464 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 465 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 466
jbe@0 467
jbe@0 468 CREATE TABLE "initiator" (
jbe@0 469 PRIMARY KEY ("initiative_id", "member_id"),
jbe@0 470 "initiative_id" INT4 REFERENCES "initiative" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@0 471 "member_id" INT4 REFERENCES "member" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@0 472 "accepted" BOOLEAN NOT NULL DEFAULT TRUE );
jbe@0 473 CREATE INDEX "initiator_member_id_idx" ON "initiator" ("member_id");
jbe@0 474
jbe@10 475 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 476
jbe@0 477 COMMENT ON COLUMN "initiator"."accepted" IS 'If "accepted" = FALSE, then the member was invited to be a co-initiator, but has not answered yet.';
jbe@0 478
jbe@0 479
jbe@0 480 CREATE TABLE "supporter" (
jbe@0 481 "issue_id" INT4 NOT NULL,
jbe@0 482 PRIMARY KEY ("initiative_id", "member_id"),
jbe@0 483 "initiative_id" INT4,
jbe@0 484 "member_id" INT4,
jbe@0 485 "draft_id" INT8 NOT NULL,
jbe@10 486 FOREIGN KEY ("issue_id", "member_id") REFERENCES "interest" ("issue_id", "member_id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@0 487 FOREIGN KEY ("initiative_id", "draft_id") REFERENCES "draft" ("initiative_id", "id") ON DELETE CASCADE ON UPDATE CASCADE );
jbe@0 488 CREATE INDEX "supporter_member_id_idx" ON "supporter" ("member_id");
jbe@0 489
jbe@10 490 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 491
jbe@2 492 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 493
jbe@0 494
jbe@0 495 CREATE TABLE "opinion" (
jbe@0 496 "initiative_id" INT4 NOT NULL,
jbe@0 497 PRIMARY KEY ("suggestion_id", "member_id"),
jbe@0 498 "suggestion_id" INT8,
jbe@0 499 "member_id" INT4,
jbe@0 500 "degree" INT2 NOT NULL CHECK ("degree" >= -2 AND "degree" <= 2 AND "degree" != 0),
jbe@0 501 "fulfilled" BOOLEAN NOT NULL DEFAULT FALSE,
jbe@0 502 FOREIGN KEY ("initiative_id", "suggestion_id") REFERENCES "suggestion" ("initiative_id", "id") ON DELETE RESTRICT ON UPDATE CASCADE,
jbe@0 503 FOREIGN KEY ("initiative_id", "member_id") REFERENCES "supporter" ("initiative_id", "member_id") ON DELETE CASCADE ON UPDATE CASCADE );
jbe@10 504 CREATE INDEX "opinion_member_id_initiative_id_idx" ON "opinion" ("member_id", "initiative_id");
jbe@0 505
jbe@10 506 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 507
jbe@0 508 COMMENT ON COLUMN "opinion"."degree" IS '2 = fulfillment required for support; 1 = fulfillment desired; -1 = fulfillment unwanted; -2 = fulfillment cancels support';
jbe@0 509
jbe@0 510
jbe@10 511 CREATE TYPE "delegation_scope" AS ENUM ('global', 'area', 'issue');
jbe@10 512
jbe@10 513 COMMENT ON TYPE "delegation_scope" IS 'Scope for delegations: ''global'', ''area'', or ''issue'' (order is relevant)';
jbe@10 514
jbe@10 515
jbe@0 516 CREATE TABLE "delegation" (
jbe@0 517 "id" SERIAL8 PRIMARY KEY,
jbe@0 518 "truster_id" INT4 NOT NULL REFERENCES "member" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@0 519 "trustee_id" INT4 NOT NULL REFERENCES "member" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@10 520 "scope" "delegation_scope" NOT NULL,
jbe@0 521 "area_id" INT4 REFERENCES "area" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@0 522 "issue_id" INT4 REFERENCES "issue" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@0 523 CONSTRAINT "cant_delegate_to_yourself" CHECK ("truster_id" != "trustee_id"),
jbe@10 524 CONSTRAINT "area_id_and_issue_id_set_according_to_scope" CHECK (
jbe@10 525 ("scope" = 'global' AND "area_id" ISNULL AND "issue_id" ISNULL ) OR
jbe@10 526 ("scope" = 'area' AND "area_id" NOTNULL AND "issue_id" ISNULL ) OR
jbe@10 527 ("scope" = 'issue' AND "area_id" ISNULL AND "issue_id" NOTNULL) ),
jbe@0 528 UNIQUE ("area_id", "truster_id", "trustee_id"),
jbe@0 529 UNIQUE ("issue_id", "truster_id", "trustee_id") );
jbe@10 530 CREATE UNIQUE INDEX "delegation_global_truster_id_trustee_id_unique_idx"
jbe@10 531 ON "delegation" ("truster_id", "trustee_id") WHERE "scope" = 'global';
jbe@0 532 CREATE INDEX "delegation_truster_id_idx" ON "delegation" ("truster_id");
jbe@0 533 CREATE INDEX "delegation_trustee_id_idx" ON "delegation" ("trustee_id");
jbe@0 534
jbe@0 535 COMMENT ON TABLE "delegation" IS 'Delegation of vote-weight to other members';
jbe@0 536
jbe@0 537 COMMENT ON COLUMN "delegation"."area_id" IS 'Reference to area, if delegation is area-wide, otherwise NULL';
jbe@0 538 COMMENT ON COLUMN "delegation"."issue_id" IS 'Reference to issue, if delegation is issue-wide, otherwise NULL';
jbe@0 539
jbe@0 540
jbe@0 541 CREATE TABLE "direct_population_snapshot" (
jbe@0 542 PRIMARY KEY ("issue_id", "event", "member_id"),
jbe@0 543 "issue_id" INT4 REFERENCES "issue" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@0 544 "event" "snapshot_event",
jbe@0 545 "member_id" INT4 REFERENCES "member" ("id") ON DELETE RESTRICT ON UPDATE CASCADE,
jbe@0 546 "weight" INT4,
jbe@0 547 "interest_exists" BOOLEAN NOT NULL );
jbe@0 548 CREATE INDEX "direct_population_snapshot_member_id_idx" ON "direct_population_snapshot" ("member_id");
jbe@0 549
jbe@0 550 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 551
jbe@0 552 COMMENT ON COLUMN "direct_population_snapshot"."event" IS 'Reason for snapshot, see "snapshot_event" type for details';
jbe@0 553 COMMENT ON COLUMN "direct_population_snapshot"."weight" IS 'Weight of member (1 or higher) according to "delegating_population_snapshot"';
jbe@0 554 COMMENT ON COLUMN "direct_population_snapshot"."interest_exists" IS 'TRUE if entry is due to interest in issue, FALSE if entry is only due to membership in area';
jbe@0 555
jbe@0 556
jbe@0 557 CREATE TABLE "delegating_population_snapshot" (
jbe@0 558 PRIMARY KEY ("issue_id", "event", "member_id"),
jbe@0 559 "issue_id" INT4 REFERENCES "issue" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@0 560 "event" "snapshot_event",
jbe@0 561 "member_id" INT4 REFERENCES "member" ("id") ON DELETE RESTRICT ON UPDATE CASCADE,
jbe@8 562 "weight" INT4,
jbe@10 563 "scope" "delegation_scope" NOT NULL,
jbe@0 564 "delegate_member_ids" INT4[] NOT NULL );
jbe@0 565 CREATE INDEX "delegating_population_snapshot_member_id_idx" ON "delegating_population_snapshot" ("member_id");
jbe@0 566
jbe@0 567 COMMENT ON TABLE "direct_population_snapshot" IS 'Delegations increasing the weight of entries in the "direct_population_snapshot" table';
jbe@0 568
jbe@0 569 COMMENT ON COLUMN "delegating_population_snapshot"."event" IS 'Reason for snapshot, see "snapshot_event" type for details';
jbe@0 570 COMMENT ON COLUMN "delegating_population_snapshot"."member_id" IS 'Delegating member';
jbe@8 571 COMMENT ON COLUMN "delegating_population_snapshot"."weight" IS 'Intermediate weight';
jbe@0 572 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 573
jbe@0 574
jbe@0 575 CREATE TABLE "direct_interest_snapshot" (
jbe@0 576 PRIMARY KEY ("issue_id", "event", "member_id"),
jbe@0 577 "issue_id" INT4 REFERENCES "issue" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@0 578 "event" "snapshot_event",
jbe@0 579 "member_id" INT4 REFERENCES "member" ("id") ON DELETE RESTRICT ON UPDATE CASCADE,
jbe@0 580 "weight" INT4,
jbe@0 581 "voting_requested" BOOLEAN );
jbe@0 582 CREATE INDEX "direct_interest_snapshot_member_id_idx" ON "direct_interest_snapshot" ("member_id");
jbe@0 583
jbe@0 584 COMMENT ON TABLE "direct_interest_snapshot" IS 'Snapshot of active members having an "interest" in the "issue"';
jbe@0 585
jbe@0 586 COMMENT ON COLUMN "direct_interest_snapshot"."event" IS 'Reason for snapshot, see "snapshot_event" type for details';
jbe@0 587 COMMENT ON COLUMN "direct_interest_snapshot"."weight" IS 'Weight of member (1 or higher) according to "delegating_interest_snapshot"';
jbe@0 588 COMMENT ON COLUMN "direct_interest_snapshot"."voting_requested" IS 'Copied from column "voting_requested" of table "interest"';
jbe@0 589
jbe@0 590
jbe@0 591 CREATE TABLE "delegating_interest_snapshot" (
jbe@0 592 PRIMARY KEY ("issue_id", "event", "member_id"),
jbe@0 593 "issue_id" INT4 REFERENCES "issue" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@0 594 "event" "snapshot_event",
jbe@0 595 "member_id" INT4 REFERENCES "member" ("id") ON DELETE RESTRICT ON UPDATE CASCADE,
jbe@8 596 "weight" INT4,
jbe@10 597 "scope" "delegation_scope" NOT NULL,
jbe@0 598 "delegate_member_ids" INT4[] NOT NULL );
jbe@0 599 CREATE INDEX "delegating_interest_snapshot_member_id_idx" ON "delegating_interest_snapshot" ("member_id");
jbe@0 600
jbe@0 601 COMMENT ON TABLE "delegating_interest_snapshot" IS 'Delegations increasing the weight of entries in the "direct_interest_snapshot" table';
jbe@0 602
jbe@0 603 COMMENT ON COLUMN "delegating_interest_snapshot"."event" IS 'Reason for snapshot, see "snapshot_event" type for details';
jbe@0 604 COMMENT ON COLUMN "delegating_interest_snapshot"."member_id" IS 'Delegating member';
jbe@8 605 COMMENT ON COLUMN "delegating_interest_snapshot"."weight" IS 'Intermediate weight';
jbe@0 606 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 607
jbe@0 608
jbe@0 609 CREATE TABLE "direct_supporter_snapshot" (
jbe@0 610 "issue_id" INT4 NOT NULL,
jbe@0 611 PRIMARY KEY ("initiative_id", "event", "member_id"),
jbe@0 612 "initiative_id" INT4,
jbe@0 613 "event" "snapshot_event",
jbe@0 614 "member_id" INT4 REFERENCES "member" ("id") ON DELETE RESTRICT ON UPDATE CASCADE,
jbe@0 615 "informed" BOOLEAN NOT NULL,
jbe@0 616 "satisfied" BOOLEAN NOT NULL,
jbe@0 617 FOREIGN KEY ("issue_id", "initiative_id") REFERENCES "initiative" ("issue_id", "id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@0 618 FOREIGN KEY ("issue_id", "event", "member_id") REFERENCES "direct_interest_snapshot" ("issue_id", "event", "member_id") ON DELETE CASCADE ON UPDATE CASCADE );
jbe@0 619 CREATE INDEX "direct_supporter_snapshot_member_id_idx" ON "direct_supporter_snapshot" ("member_id");
jbe@0 620
jbe@8 621 COMMENT ON TABLE "direct_supporter_snapshot" IS 'Snapshot of supporters of initiatives (weight is stored in "direct_interest_snapshot")';
jbe@0 622
jbe@0 623 COMMENT ON COLUMN "direct_supporter_snapshot"."event" IS 'Reason for snapshot, see "snapshot_event" type for details';
jbe@0 624 COMMENT ON COLUMN "direct_supporter_snapshot"."informed" IS 'Supporter has seen the latest draft of the initiative';
jbe@0 625 COMMENT ON COLUMN "direct_supporter_snapshot"."satisfied" IS 'Supporter has no "critical_opinion"s';
jbe@0 626
jbe@0 627
jbe@0 628 CREATE TABLE "direct_voter" (
jbe@0 629 PRIMARY KEY ("issue_id", "member_id"),
jbe@0 630 "issue_id" INT4 REFERENCES "issue" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@0 631 "member_id" INT4 REFERENCES "member" ("id") ON DELETE RESTRICT ON UPDATE CASCADE,
jbe@0 632 "weight" INT4,
jbe@0 633 "autoreject" BOOLEAN NOT NULL DEFAULT FALSE );
jbe@0 634 CREATE INDEX "direct_voter_member_id_idx" ON "direct_voter" ("member_id");
jbe@0 635
jbe@10 636 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 637
jbe@0 638 COMMENT ON COLUMN "direct_voter"."weight" IS 'Weight of member (1 or higher) according to "delegating_voter" table';
jbe@0 639 COMMENT ON COLUMN "direct_voter"."autoreject" IS 'Votes were inserted due to "autoreject" feature';
jbe@0 640
jbe@0 641
jbe@0 642 CREATE TABLE "delegating_voter" (
jbe@0 643 PRIMARY KEY ("issue_id", "member_id"),
jbe@0 644 "issue_id" INT4 REFERENCES "issue" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@0 645 "member_id" INT4 REFERENCES "member" ("id") ON DELETE RESTRICT ON UPDATE CASCADE,
jbe@8 646 "weight" INT4,
jbe@10 647 "scope" "delegation_scope" NOT NULL,
jbe@0 648 "delegate_member_ids" INT4[] NOT NULL );
jbe@0 649 CREATE INDEX "delegating_voter_member_id_idx" ON "direct_voter" ("member_id");
jbe@0 650
jbe@0 651 COMMENT ON TABLE "delegating_voter" IS 'Delegations increasing the weight of entries in the "direct_voter" table';
jbe@0 652
jbe@0 653 COMMENT ON COLUMN "delegating_voter"."member_id" IS 'Delegating member';
jbe@8 654 COMMENT ON COLUMN "delegating_voter"."weight" IS 'Intermediate weight';
jbe@0 655 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 656
jbe@0 657
jbe@0 658 CREATE TABLE "vote" (
jbe@0 659 "issue_id" INT4 NOT NULL,
jbe@0 660 PRIMARY KEY ("initiative_id", "member_id"),
jbe@0 661 "initiative_id" INT4,
jbe@0 662 "member_id" INT4,
jbe@0 663 "grade" INT4,
jbe@0 664 FOREIGN KEY ("issue_id", "initiative_id") REFERENCES "initiative" ("issue_id", "id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@0 665 FOREIGN KEY ("issue_id", "member_id") REFERENCES "direct_voter" ("issue_id", "member_id") ON DELETE CASCADE ON UPDATE CASCADE );
jbe@0 666 CREATE INDEX "vote_member_id_idx" ON "vote" ("member_id");
jbe@0 667
jbe@10 668 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 669
jbe@0 670 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 671
jbe@0 672
jbe@9 673 CREATE TABLE "contingent" (
jbe@9 674 "time_frame" INTERVAL PRIMARY KEY,
jbe@9 675 "text_entry_limit" INT4,
jbe@9 676 "initiative_limit" INT4 );
jbe@9 677
jbe@9 678 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 679
jbe@9 680 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 681 COMMENT ON COLUMN "contingent"."initiative_limit" IS 'Number of new initiatives to be opened by each member within a given time frame';
jbe@9 682
jbe@9 683
jbe@0 684
jbe@0 685 ----------------------------
jbe@0 686 -- Additional constraints --
jbe@0 687 ----------------------------
jbe@0 688
jbe@0 689
jbe@0 690 CREATE FUNCTION "issue_requires_first_initiative_trigger"()
jbe@0 691 RETURNS TRIGGER
jbe@0 692 LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@0 693 BEGIN
jbe@0 694 IF NOT EXISTS (
jbe@0 695 SELECT NULL FROM "initiative" WHERE "issue_id" = NEW."id"
jbe@0 696 ) THEN
jbe@0 697 --RAISE 'Cannot create issue without an initial initiative.' USING
jbe@0 698 -- ERRCODE = 'integrity_constraint_violation',
jbe@0 699 -- HINT = 'Create issue, initiative, and draft within the same transaction.';
jbe@0 700 RAISE EXCEPTION 'Cannot create issue without an initial initiative.';
jbe@0 701 END IF;
jbe@0 702 RETURN NULL;
jbe@0 703 END;
jbe@0 704 $$;
jbe@0 705
jbe@0 706 CREATE CONSTRAINT TRIGGER "issue_requires_first_initiative"
jbe@0 707 AFTER INSERT OR UPDATE ON "issue" DEFERRABLE INITIALLY DEFERRED
jbe@0 708 FOR EACH ROW EXECUTE PROCEDURE
jbe@0 709 "issue_requires_first_initiative_trigger"();
jbe@0 710
jbe@0 711 COMMENT ON FUNCTION "issue_requires_first_initiative_trigger"() IS 'Implementation of trigger "issue_requires_first_initiative" on table "issue"';
jbe@0 712 COMMENT ON TRIGGER "issue_requires_first_initiative" ON "issue" IS 'Ensure that new issues have at least one initiative';
jbe@0 713
jbe@0 714
jbe@0 715 CREATE FUNCTION "last_initiative_deletes_issue_trigger"()
jbe@0 716 RETURNS TRIGGER
jbe@0 717 LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@0 718 DECLARE
jbe@0 719 "reference_lost" BOOLEAN;
jbe@0 720 BEGIN
jbe@0 721 IF TG_OP = 'DELETE' THEN
jbe@0 722 "reference_lost" := TRUE;
jbe@0 723 ELSE
jbe@0 724 "reference_lost" := NEW."issue_id" != OLD."issue_id";
jbe@0 725 END IF;
jbe@0 726 IF
jbe@0 727 "reference_lost" AND NOT EXISTS (
jbe@0 728 SELECT NULL FROM "initiative" WHERE "issue_id" = OLD."issue_id"
jbe@0 729 )
jbe@0 730 THEN
jbe@0 731 DELETE FROM "issue" WHERE "id" = OLD."issue_id";
jbe@0 732 END IF;
jbe@0 733 RETURN NULL;
jbe@0 734 END;
jbe@0 735 $$;
jbe@0 736
jbe@0 737 CREATE CONSTRAINT TRIGGER "last_initiative_deletes_issue"
jbe@0 738 AFTER UPDATE OR DELETE ON "initiative" DEFERRABLE INITIALLY DEFERRED
jbe@0 739 FOR EACH ROW EXECUTE PROCEDURE
jbe@0 740 "last_initiative_deletes_issue_trigger"();
jbe@0 741
jbe@0 742 COMMENT ON FUNCTION "last_initiative_deletes_issue_trigger"() IS 'Implementation of trigger "last_initiative_deletes_issue" on table "initiative"';
jbe@0 743 COMMENT ON TRIGGER "last_initiative_deletes_issue" ON "initiative" IS 'Removing the last initiative of an issue deletes the issue';
jbe@0 744
jbe@0 745
jbe@0 746 CREATE FUNCTION "initiative_requires_first_draft_trigger"()
jbe@0 747 RETURNS TRIGGER
jbe@0 748 LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@0 749 BEGIN
jbe@0 750 IF NOT EXISTS (
jbe@0 751 SELECT NULL FROM "draft" WHERE "initiative_id" = NEW."id"
jbe@0 752 ) THEN
jbe@0 753 --RAISE 'Cannot create initiative without an initial draft.' USING
jbe@0 754 -- ERRCODE = 'integrity_constraint_violation',
jbe@0 755 -- HINT = 'Create issue, initiative and draft within the same transaction.';
jbe@0 756 RAISE EXCEPTION 'Cannot create initiative without an initial draft.';
jbe@0 757 END IF;
jbe@0 758 RETURN NULL;
jbe@0 759 END;
jbe@0 760 $$;
jbe@0 761
jbe@0 762 CREATE CONSTRAINT TRIGGER "initiative_requires_first_draft"
jbe@0 763 AFTER INSERT OR UPDATE ON "initiative" DEFERRABLE INITIALLY DEFERRED
jbe@0 764 FOR EACH ROW EXECUTE PROCEDURE
jbe@0 765 "initiative_requires_first_draft_trigger"();
jbe@0 766
jbe@0 767 COMMENT ON FUNCTION "initiative_requires_first_draft_trigger"() IS 'Implementation of trigger "initiative_requires_first_draft" on table "initiative"';
jbe@0 768 COMMENT ON TRIGGER "initiative_requires_first_draft" ON "initiative" IS 'Ensure that new initiatives have at least one draft';
jbe@0 769
jbe@0 770
jbe@0 771 CREATE FUNCTION "last_draft_deletes_initiative_trigger"()
jbe@0 772 RETURNS TRIGGER
jbe@0 773 LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@0 774 DECLARE
jbe@0 775 "reference_lost" BOOLEAN;
jbe@0 776 BEGIN
jbe@0 777 IF TG_OP = 'DELETE' THEN
jbe@0 778 "reference_lost" := TRUE;
jbe@0 779 ELSE
jbe@0 780 "reference_lost" := NEW."initiative_id" != OLD."initiative_id";
jbe@0 781 END IF;
jbe@0 782 IF
jbe@0 783 "reference_lost" AND NOT EXISTS (
jbe@0 784 SELECT NULL FROM "draft" WHERE "initiative_id" = OLD."initiative_id"
jbe@0 785 )
jbe@0 786 THEN
jbe@0 787 DELETE FROM "initiative" WHERE "id" = OLD."initiative_id";
jbe@0 788 END IF;
jbe@0 789 RETURN NULL;
jbe@0 790 END;
jbe@0 791 $$;
jbe@0 792
jbe@0 793 CREATE CONSTRAINT TRIGGER "last_draft_deletes_initiative"
jbe@0 794 AFTER UPDATE OR DELETE ON "draft" DEFERRABLE INITIALLY DEFERRED
jbe@0 795 FOR EACH ROW EXECUTE PROCEDURE
jbe@0 796 "last_draft_deletes_initiative_trigger"();
jbe@0 797
jbe@0 798 COMMENT ON FUNCTION "last_draft_deletes_initiative_trigger"() IS 'Implementation of trigger "last_draft_deletes_initiative" on table "draft"';
jbe@0 799 COMMENT ON TRIGGER "last_draft_deletes_initiative" ON "draft" IS 'Removing the last draft of an initiative deletes the initiative';
jbe@0 800
jbe@0 801
jbe@0 802 CREATE FUNCTION "suggestion_requires_first_opinion_trigger"()
jbe@0 803 RETURNS TRIGGER
jbe@0 804 LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@0 805 BEGIN
jbe@0 806 IF NOT EXISTS (
jbe@0 807 SELECT NULL FROM "opinion" WHERE "suggestion_id" = NEW."id"
jbe@0 808 ) THEN
jbe@0 809 RAISE EXCEPTION 'Cannot create a suggestion without an opinion.';
jbe@0 810 END IF;
jbe@0 811 RETURN NULL;
jbe@0 812 END;
jbe@0 813 $$;
jbe@0 814
jbe@0 815 CREATE CONSTRAINT TRIGGER "suggestion_requires_first_opinion"
jbe@0 816 AFTER INSERT OR UPDATE ON "suggestion" DEFERRABLE INITIALLY DEFERRED
jbe@0 817 FOR EACH ROW EXECUTE PROCEDURE
jbe@0 818 "suggestion_requires_first_opinion_trigger"();
jbe@0 819
jbe@0 820 COMMENT ON FUNCTION "suggestion_requires_first_opinion_trigger"() IS 'Implementation of trigger "suggestion_requires_first_opinion" on table "suggestion"';
jbe@0 821 COMMENT ON TRIGGER "suggestion_requires_first_opinion" ON "suggestion" IS 'Ensure that new suggestions have at least one opinion';
jbe@0 822
jbe@0 823
jbe@0 824 CREATE FUNCTION "last_opinion_deletes_suggestion_trigger"()
jbe@0 825 RETURNS TRIGGER
jbe@0 826 LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@0 827 DECLARE
jbe@0 828 "reference_lost" BOOLEAN;
jbe@0 829 BEGIN
jbe@0 830 IF TG_OP = 'DELETE' THEN
jbe@0 831 "reference_lost" := TRUE;
jbe@0 832 ELSE
jbe@0 833 "reference_lost" := NEW."suggestion_id" != OLD."suggestion_id";
jbe@0 834 END IF;
jbe@0 835 IF
jbe@0 836 "reference_lost" AND NOT EXISTS (
jbe@0 837 SELECT NULL FROM "opinion" WHERE "suggestion_id" = OLD."suggestion_id"
jbe@0 838 )
jbe@0 839 THEN
jbe@0 840 DELETE FROM "suggestion" WHERE "id" = OLD."suggestion_id";
jbe@0 841 END IF;
jbe@0 842 RETURN NULL;
jbe@0 843 END;
jbe@0 844 $$;
jbe@0 845
jbe@0 846 CREATE CONSTRAINT TRIGGER "last_opinion_deletes_suggestion"
jbe@0 847 AFTER UPDATE OR DELETE ON "opinion" DEFERRABLE INITIALLY DEFERRED
jbe@0 848 FOR EACH ROW EXECUTE PROCEDURE
jbe@0 849 "last_opinion_deletes_suggestion_trigger"();
jbe@0 850
jbe@0 851 COMMENT ON FUNCTION "last_opinion_deletes_suggestion_trigger"() IS 'Implementation of trigger "last_opinion_deletes_suggestion" on table "opinion"';
jbe@0 852 COMMENT ON TRIGGER "last_opinion_deletes_suggestion" ON "opinion" IS 'Removing the last opinion of a suggestion deletes the suggestion';
jbe@0 853
jbe@0 854
jbe@0 855
jbe@0 856 --------------------------------------------------------------------
jbe@0 857 -- Auto-retrieval of fields only needed for referential integrity --
jbe@0 858 --------------------------------------------------------------------
jbe@0 859
jbe@0 860 CREATE FUNCTION "autofill_issue_id_trigger"()
jbe@0 861 RETURNS TRIGGER
jbe@0 862 LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@0 863 BEGIN
jbe@0 864 IF NEW."issue_id" ISNULL THEN
jbe@0 865 SELECT "issue_id" INTO NEW."issue_id"
jbe@0 866 FROM "initiative" WHERE "id" = NEW."initiative_id";
jbe@0 867 END IF;
jbe@0 868 RETURN NEW;
jbe@0 869 END;
jbe@0 870 $$;
jbe@0 871
jbe@0 872 CREATE TRIGGER "autofill_issue_id" BEFORE INSERT ON "supporter"
jbe@0 873 FOR EACH ROW EXECUTE PROCEDURE "autofill_issue_id_trigger"();
jbe@0 874
jbe@0 875 CREATE TRIGGER "autofill_issue_id" BEFORE INSERT ON "vote"
jbe@0 876 FOR EACH ROW EXECUTE PROCEDURE "autofill_issue_id_trigger"();
jbe@0 877
jbe@0 878 COMMENT ON FUNCTION "autofill_issue_id_trigger"() IS 'Implementation of triggers "autofill_issue_id" on tables "supporter" and "vote"';
jbe@0 879 COMMENT ON TRIGGER "autofill_issue_id" ON "supporter" IS 'Set "issue_id" field automatically, if NULL';
jbe@0 880 COMMENT ON TRIGGER "autofill_issue_id" ON "vote" IS 'Set "issue_id" field automatically, if NULL';
jbe@0 881
jbe@0 882
jbe@0 883 CREATE FUNCTION "autofill_initiative_id_trigger"()
jbe@0 884 RETURNS TRIGGER
jbe@0 885 LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@0 886 BEGIN
jbe@0 887 IF NEW."initiative_id" ISNULL THEN
jbe@0 888 SELECT "initiative_id" INTO NEW."initiative_id"
jbe@0 889 FROM "suggestion" WHERE "id" = NEW."suggestion_id";
jbe@0 890 END IF;
jbe@0 891 RETURN NEW;
jbe@0 892 END;
jbe@0 893 $$;
jbe@0 894
jbe@0 895 CREATE TRIGGER "autofill_initiative_id" BEFORE INSERT ON "opinion"
jbe@0 896 FOR EACH ROW EXECUTE PROCEDURE "autofill_initiative_id_trigger"();
jbe@0 897
jbe@0 898 COMMENT ON FUNCTION "autofill_initiative_id_trigger"() IS 'Implementation of trigger "autofill_initiative_id" on table "opinion"';
jbe@0 899 COMMENT ON TRIGGER "autofill_initiative_id" ON "opinion" IS 'Set "initiative_id" field automatically, if NULL';
jbe@0 900
jbe@0 901
jbe@0 902
jbe@4 903 -----------------------------------------------------
jbe@4 904 -- Automatic calculation of certain default values --
jbe@4 905 -----------------------------------------------------
jbe@0 906
jbe@0 907 CREATE FUNCTION "copy_autoreject_trigger"()
jbe@0 908 RETURNS TRIGGER
jbe@0 909 LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@0 910 BEGIN
jbe@0 911 IF NEW."autoreject" ISNULL THEN
jbe@0 912 SELECT "membership"."autoreject" INTO NEW."autoreject"
jbe@0 913 FROM "issue" JOIN "membership"
jbe@0 914 ON "issue"."area_id" = "membership"."area_id"
jbe@0 915 WHERE "issue"."id" = NEW."issue_id"
jbe@0 916 AND "membership"."member_id" = NEW."member_id";
jbe@0 917 END IF;
jbe@0 918 IF NEW."autoreject" ISNULL THEN
jbe@0 919 NEW."autoreject" := FALSE;
jbe@0 920 END IF;
jbe@0 921 RETURN NEW;
jbe@0 922 END;
jbe@0 923 $$;
jbe@0 924
jbe@0 925 CREATE TRIGGER "copy_autoreject" BEFORE INSERT OR UPDATE ON "interest"
jbe@0 926 FOR EACH ROW EXECUTE PROCEDURE "copy_autoreject_trigger"();
jbe@0 927
jbe@0 928 COMMENT ON FUNCTION "copy_autoreject_trigger"() IS 'Implementation of trigger "copy_autoreject" on table "interest"';
jbe@0 929 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 930
jbe@0 931
jbe@2 932 CREATE FUNCTION "supporter_default_for_draft_id_trigger"()
jbe@2 933 RETURNS TRIGGER
jbe@2 934 LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@2 935 BEGIN
jbe@2 936 IF NEW."draft_id" ISNULL THEN
jbe@2 937 SELECT "id" INTO NEW."draft_id" FROM "current_draft"
jbe@2 938 WHERE "initiative_id" = NEW."initiative_id";
jbe@2 939 END IF;
jbe@2 940 RETURN NEW;
jbe@2 941 END;
jbe@2 942 $$;
jbe@2 943
jbe@2 944 CREATE TRIGGER "default_for_draft_id" BEFORE INSERT OR UPDATE ON "supporter"
jbe@2 945 FOR EACH ROW EXECUTE PROCEDURE "supporter_default_for_draft_id_trigger"();
jbe@2 946
jbe@2 947 COMMENT ON FUNCTION "supporter_default_for_draft_id_trigger"() IS 'Implementation of trigger "default_for_draft" on table "supporter"';
jbe@2 948 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 949
jbe@2 950
jbe@0 951
jbe@0 952 ----------------------------------------
jbe@0 953 -- Automatic creation of dependencies --
jbe@0 954 ----------------------------------------
jbe@0 955
jbe@0 956 CREATE FUNCTION "autocreate_interest_trigger"()
jbe@0 957 RETURNS TRIGGER
jbe@0 958 LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@0 959 BEGIN
jbe@0 960 IF NOT EXISTS (
jbe@0 961 SELECT NULL FROM "initiative" JOIN "interest"
jbe@0 962 ON "initiative"."issue_id" = "interest"."issue_id"
jbe@0 963 WHERE "initiative"."id" = NEW."initiative_id"
jbe@0 964 AND "interest"."member_id" = NEW."member_id"
jbe@0 965 ) THEN
jbe@0 966 BEGIN
jbe@0 967 INSERT INTO "interest" ("issue_id", "member_id")
jbe@0 968 SELECT "issue_id", NEW."member_id"
jbe@0 969 FROM "initiative" WHERE "id" = NEW."initiative_id";
jbe@0 970 EXCEPTION WHEN unique_violation THEN END;
jbe@0 971 END IF;
jbe@0 972 RETURN NEW;
jbe@0 973 END;
jbe@0 974 $$;
jbe@0 975
jbe@0 976 CREATE TRIGGER "autocreate_interest" BEFORE INSERT ON "supporter"
jbe@0 977 FOR EACH ROW EXECUTE PROCEDURE "autocreate_interest_trigger"();
jbe@0 978
jbe@0 979 COMMENT ON FUNCTION "autocreate_interest_trigger"() IS 'Implementation of trigger "autocreate_interest" on table "supporter"';
jbe@0 980 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 981
jbe@0 982
jbe@0 983 CREATE FUNCTION "autocreate_supporter_trigger"()
jbe@0 984 RETURNS TRIGGER
jbe@0 985 LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@0 986 BEGIN
jbe@0 987 IF NOT EXISTS (
jbe@0 988 SELECT NULL FROM "suggestion" JOIN "supporter"
jbe@0 989 ON "suggestion"."initiative_id" = "supporter"."initiative_id"
jbe@0 990 WHERE "suggestion"."id" = NEW."suggestion_id"
jbe@0 991 AND "supporter"."member_id" = NEW."member_id"
jbe@0 992 ) THEN
jbe@0 993 BEGIN
jbe@0 994 INSERT INTO "supporter" ("initiative_id", "member_id")
jbe@0 995 SELECT "initiative_id", NEW."member_id"
jbe@0 996 FROM "suggestion" WHERE "id" = NEW."suggestion_id";
jbe@0 997 EXCEPTION WHEN unique_violation THEN END;
jbe@0 998 END IF;
jbe@0 999 RETURN NEW;
jbe@0 1000 END;
jbe@0 1001 $$;
jbe@0 1002
jbe@0 1003 CREATE TRIGGER "autocreate_supporter" BEFORE INSERT ON "opinion"
jbe@0 1004 FOR EACH ROW EXECUTE PROCEDURE "autocreate_supporter_trigger"();
jbe@0 1005
jbe@0 1006 COMMENT ON FUNCTION "autocreate_supporter_trigger"() IS 'Implementation of trigger "autocreate_supporter" on table "opinion"';
jbe@0 1007 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 1008
jbe@0 1009
jbe@0 1010
jbe@0 1011 ------------------------------------------
jbe@0 1012 -- Views and helper functions for views --
jbe@0 1013 ------------------------------------------
jbe@0 1014
jbe@5 1015
jbe@5 1016 CREATE VIEW "global_delegation" AS
jbe@5 1017 SELECT
jbe@5 1018 "delegation"."id",
jbe@5 1019 "delegation"."truster_id",
jbe@5 1020 "delegation"."trustee_id"
jbe@5 1021 FROM "delegation" JOIN "member"
jbe@5 1022 ON "delegation"."trustee_id" = "member"."id"
jbe@10 1023 WHERE "delegation"."scope" = 'global' AND "member"."active";
jbe@5 1024
jbe@5 1025 COMMENT ON VIEW "global_delegation" IS 'Global delegations to active members';
jbe@5 1026
jbe@5 1027
jbe@5 1028 CREATE VIEW "area_delegation" AS
jbe@5 1029 SELECT "subquery".* FROM (
jbe@5 1030 SELECT DISTINCT ON ("area"."id", "delegation"."truster_id")
jbe@10 1031 "area"."id" AS "area_id",
jbe@10 1032 "delegation"."id",
jbe@10 1033 "delegation"."truster_id",
jbe@10 1034 "delegation"."trustee_id",
jbe@10 1035 "delegation"."scope"
jbe@5 1036 FROM "area" JOIN "delegation"
jbe@10 1037 ON "delegation"."scope" = 'global'
jbe@10 1038 OR "delegation"."area_id" = "area"."id"
jbe@5 1039 ORDER BY
jbe@5 1040 "area"."id",
jbe@5 1041 "delegation"."truster_id",
jbe@10 1042 "delegation"."scope" DESC
jbe@5 1043 ) AS "subquery"
jbe@5 1044 JOIN "member" ON "subquery"."trustee_id" = "member"."id"
jbe@5 1045 WHERE "member"."active";
jbe@5 1046
jbe@5 1047 COMMENT ON VIEW "area_delegation" IS 'Active delegations for areas';
jbe@5 1048
jbe@5 1049
jbe@5 1050 CREATE VIEW "issue_delegation" AS
jbe@5 1051 SELECT "subquery".* FROM (
jbe@5 1052 SELECT DISTINCT ON ("issue"."id", "delegation"."truster_id")
jbe@10 1053 "issue"."id" AS "issue_id",
jbe@10 1054 "delegation"."id",
jbe@10 1055 "delegation"."truster_id",
jbe@10 1056 "delegation"."trustee_id",
jbe@10 1057 "delegation"."scope"
jbe@5 1058 FROM "issue" JOIN "delegation"
jbe@10 1059 ON "delegation"."scope" = 'global'
jbe@10 1060 OR "delegation"."area_id" = "issue"."area_id"
jbe@10 1061 OR "delegation"."issue_id" = "issue"."id"
jbe@5 1062 ORDER BY
jbe@5 1063 "issue"."id",
jbe@5 1064 "delegation"."truster_id",
jbe@10 1065 "delegation"."scope" DESC
jbe@5 1066 ) AS "subquery"
jbe@5 1067 JOIN "member" ON "subquery"."trustee_id" = "member"."id"
jbe@5 1068 WHERE "member"."active";
jbe@5 1069
jbe@5 1070 COMMENT ON VIEW "issue_delegation" IS 'Active delegations for issues';
jbe@5 1071
jbe@5 1072
jbe@5 1073 CREATE FUNCTION "membership_weight_with_skipping"
jbe@5 1074 ( "area_id_p" "area"."id"%TYPE,
jbe@5 1075 "member_id_p" "member"."id"%TYPE,
jbe@5 1076 "skip_member_ids_p" INT4[] ) -- "member"."id"%TYPE[]
jbe@5 1077 RETURNS INT4
jbe@5 1078 LANGUAGE 'plpgsql' STABLE AS $$
jbe@5 1079 DECLARE
jbe@5 1080 "sum_v" INT4;
jbe@5 1081 "delegation_row" "area_delegation"%ROWTYPE;
jbe@5 1082 BEGIN
jbe@5 1083 "sum_v" := 1;
jbe@5 1084 FOR "delegation_row" IN
jbe@5 1085 SELECT "area_delegation".*
jbe@5 1086 FROM "area_delegation" LEFT JOIN "membership"
jbe@5 1087 ON "membership"."area_id" = "area_id_p"
jbe@5 1088 AND "membership"."member_id" = "area_delegation"."truster_id"
jbe@5 1089 WHERE "area_delegation"."area_id" = "area_id_p"
jbe@5 1090 AND "area_delegation"."trustee_id" = "member_id_p"
jbe@5 1091 AND "membership"."member_id" ISNULL
jbe@5 1092 LOOP
jbe@5 1093 IF NOT
jbe@5 1094 "skip_member_ids_p" @> ARRAY["delegation_row"."truster_id"]
jbe@5 1095 THEN
jbe@5 1096 "sum_v" := "sum_v" + "membership_weight_with_skipping"(
jbe@5 1097 "area_id_p",
jbe@5 1098 "delegation_row"."truster_id",
jbe@5 1099 "skip_member_ids_p" || "delegation_row"."truster_id"
jbe@5 1100 );
jbe@5 1101 END IF;
jbe@5 1102 END LOOP;
jbe@5 1103 RETURN "sum_v";
jbe@5 1104 END;
jbe@5 1105 $$;
jbe@5 1106
jbe@8 1107 COMMENT ON FUNCTION "membership_weight_with_skipping"
jbe@8 1108 ( "area"."id"%TYPE,
jbe@8 1109 "member"."id"%TYPE,
jbe@8 1110 INT4[] )
jbe@8 1111 IS 'Helper function for "membership_weight" function';
jbe@8 1112
jbe@8 1113
jbe@5 1114 CREATE FUNCTION "membership_weight"
jbe@5 1115 ( "area_id_p" "area"."id"%TYPE,
jbe@5 1116 "member_id_p" "member"."id"%TYPE ) -- "member"."id"%TYPE[]
jbe@5 1117 RETURNS INT4
jbe@5 1118 LANGUAGE 'plpgsql' STABLE AS $$
jbe@5 1119 BEGIN
jbe@5 1120 RETURN "membership_weight_with_skipping"(
jbe@5 1121 "area_id_p",
jbe@5 1122 "member_id_p",
jbe@5 1123 ARRAY["member_id_p"]
jbe@5 1124 );
jbe@5 1125 END;
jbe@5 1126 $$;
jbe@5 1127
jbe@8 1128 COMMENT ON FUNCTION "membership_weight"
jbe@8 1129 ( "area"."id"%TYPE,
jbe@8 1130 "member"."id"%TYPE )
jbe@8 1131 IS 'Calculates the potential voting weight of a member in a given area';
jbe@8 1132
jbe@5 1133
jbe@4 1134 CREATE VIEW "member_count_view" AS
jbe@5 1135 SELECT count(1) AS "total_count" FROM "member" WHERE "active";
jbe@4 1136
jbe@4 1137 COMMENT ON VIEW "member_count_view" IS 'View used to update "member_count" table';
jbe@4 1138
jbe@4 1139
jbe@4 1140 CREATE VIEW "area_member_count" AS
jbe@5 1141 SELECT
jbe@5 1142 "area"."id" AS "area_id",
jbe@5 1143 count("member"."id") AS "direct_member_count",
jbe@5 1144 coalesce(
jbe@5 1145 sum(
jbe@5 1146 CASE WHEN "member"."id" NOTNULL THEN
jbe@5 1147 "membership_weight"("area"."id", "member"."id")
jbe@5 1148 ELSE 0 END
jbe@5 1149 )
jbe@5 1150 ) AS "member_weight",
jbe@5 1151 coalesce(
jbe@5 1152 sum(
jbe@5 1153 CASE WHEN "member"."id" NOTNULL AND "membership"."autoreject" THEN
jbe@5 1154 "membership_weight"("area"."id", "member"."id")
jbe@5 1155 ELSE 0 END
jbe@5 1156 )
jbe@5 1157 ) AS "autoreject_weight"
jbe@4 1158 FROM "area"
jbe@4 1159 LEFT JOIN "membership"
jbe@4 1160 ON "area"."id" = "membership"."area_id"
jbe@4 1161 LEFT JOIN "member"
jbe@4 1162 ON "membership"."member_id" = "member"."id"
jbe@4 1163 AND "member"."active"
jbe@4 1164 GROUP BY "area"."id";
jbe@4 1165
jbe@4 1166 COMMENT ON VIEW "area_member_count" IS 'View used to update "member_count" column of table "area"';
jbe@4 1167
jbe@4 1168
jbe@9 1169 CREATE VIEW "opening_draft" AS
jbe@9 1170 SELECT "draft".* FROM (
jbe@9 1171 SELECT
jbe@9 1172 "initiative"."id" AS "initiative_id",
jbe@9 1173 min("draft"."id") AS "draft_id"
jbe@9 1174 FROM "initiative" JOIN "draft"
jbe@9 1175 ON "initiative"."id" = "draft"."initiative_id"
jbe@9 1176 GROUP BY "initiative"."id"
jbe@9 1177 ) AS "subquery"
jbe@9 1178 JOIN "draft" ON "subquery"."draft_id" = "draft"."id";
jbe@9 1179
jbe@9 1180 COMMENT ON VIEW "opening_draft" IS 'First drafts of all initiatives';
jbe@9 1181
jbe@9 1182
jbe@0 1183 CREATE VIEW "current_draft" AS
jbe@0 1184 SELECT "draft".* FROM (
jbe@0 1185 SELECT
jbe@0 1186 "initiative"."id" AS "initiative_id",
jbe@0 1187 max("draft"."id") AS "draft_id"
jbe@0 1188 FROM "initiative" JOIN "draft"
jbe@0 1189 ON "initiative"."id" = "draft"."initiative_id"
jbe@0 1190 GROUP BY "initiative"."id"
jbe@0 1191 ) AS "subquery"
jbe@0 1192 JOIN "draft" ON "subquery"."draft_id" = "draft"."id";
jbe@0 1193
jbe@0 1194 COMMENT ON VIEW "current_draft" IS 'All latest drafts for each initiative';
jbe@0 1195
jbe@0 1196
jbe@0 1197 CREATE VIEW "critical_opinion" AS
jbe@0 1198 SELECT * FROM "opinion"
jbe@0 1199 WHERE ("degree" = 2 AND "fulfilled" = FALSE)
jbe@0 1200 OR ("degree" = -2 AND "fulfilled" = TRUE);
jbe@0 1201
jbe@0 1202 COMMENT ON VIEW "critical_opinion" IS 'Opinions currently causing dissatisfaction';
jbe@0 1203
jbe@0 1204
jbe@0 1205 CREATE VIEW "battle" AS
jbe@0 1206 SELECT
jbe@0 1207 "issue"."id" AS "issue_id",
jbe@10 1208 "winning_initiative"."id" AS "winning_initiative_id",
jbe@10 1209 "losing_initiative"."id" AS "losing_initiative_id",
jbe@0 1210 sum(
jbe@0 1211 CASE WHEN
jbe@0 1212 coalesce("better_vote"."grade", 0) >
jbe@0 1213 coalesce("worse_vote"."grade", 0)
jbe@0 1214 THEN "direct_voter"."weight" ELSE 0 END
jbe@0 1215 ) AS "count"
jbe@0 1216 FROM "issue"
jbe@0 1217 LEFT JOIN "direct_voter"
jbe@0 1218 ON "issue"."id" = "direct_voter"."issue_id"
jbe@10 1219 JOIN "initiative" AS "winning_initiative"
jbe@10 1220 ON "issue"."id" = "winning_initiative"."issue_id"
jbe@10 1221 AND "winning_initiative"."agreed"
jbe@10 1222 JOIN "initiative" AS "losing_initiative"
jbe@10 1223 ON "issue"."id" = "losing_initiative"."issue_id"
jbe@10 1224 AND "losing_initiative"."agreed"
jbe@0 1225 LEFT JOIN "vote" AS "better_vote"
jbe@10 1226 ON "direct_voter"."member_id" = "better_vote"."member_id"
jbe@10 1227 AND "winning_initiative"."id" = "better_vote"."initiative_id"
jbe@0 1228 LEFT JOIN "vote" AS "worse_vote"
jbe@10 1229 ON "direct_voter"."member_id" = "worse_vote"."member_id"
jbe@10 1230 AND "losing_initiative"."id" = "worse_vote"."initiative_id"
jbe@0 1231 WHERE
jbe@10 1232 "winning_initiative"."id" != "losing_initiative"."id"
jbe@0 1233 GROUP BY
jbe@0 1234 "issue"."id",
jbe@10 1235 "winning_initiative"."id",
jbe@10 1236 "losing_initiative"."id";
jbe@0 1237
jbe@0 1238 COMMENT ON VIEW "battle" IS 'Number of members preferring one initiative over another';
jbe@0 1239
jbe@0 1240
jbe@1 1241 CREATE VIEW "expired_session" AS
jbe@1 1242 SELECT * FROM "session" WHERE now() > "expiry";
jbe@1 1243
jbe@1 1244 CREATE RULE "delete" AS ON DELETE TO "expired_session" DO INSTEAD
jbe@1 1245 DELETE FROM "session" WHERE "ident" = OLD."ident";
jbe@1 1246
jbe@1 1247 COMMENT ON VIEW "expired_session" IS 'View containing all expired sessions where DELETE is possible';
jbe@1 1248 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 1249
jbe@1 1250
jbe@0 1251 CREATE VIEW "open_issue" AS
jbe@0 1252 SELECT * FROM "issue" WHERE "closed" ISNULL;
jbe@0 1253
jbe@0 1254 COMMENT ON VIEW "open_issue" IS 'All open issues';
jbe@0 1255
jbe@0 1256
jbe@0 1257 CREATE VIEW "issue_with_ranks_missing" AS
jbe@0 1258 SELECT * FROM "issue"
jbe@3 1259 WHERE "fully_frozen" NOTNULL
jbe@0 1260 AND "closed" NOTNULL
jbe@0 1261 AND "ranks_available" = FALSE;
jbe@0 1262
jbe@0 1263 COMMENT ON VIEW "issue_with_ranks_missing" IS 'Issues where voting was finished, but no ranks have been calculated yet';
jbe@0 1264
jbe@0 1265
jbe@9 1266 CREATE VIEW "member_contingent" AS
jbe@9 1267 SELECT
jbe@9 1268 "member"."id" AS "member_id",
jbe@9 1269 "contingent"."time_frame",
jbe@9 1270 CASE WHEN "contingent"."text_entry_limit" NOTNULL THEN
jbe@9 1271 (
jbe@9 1272 SELECT count(1) FROM "draft"
jbe@9 1273 WHERE "draft"."author_id" = "member"."id"
jbe@9 1274 AND "draft"."created" > now() - "contingent"."time_frame"
jbe@9 1275 ) + (
jbe@9 1276 SELECT count(1) FROM "suggestion"
jbe@9 1277 WHERE "suggestion"."author_id" = "member"."id"
jbe@9 1278 AND "suggestion"."created" > now() - "contingent"."time_frame"
jbe@9 1279 )
jbe@9 1280 ELSE NULL END AS "text_entry_count",
jbe@9 1281 "contingent"."text_entry_limit",
jbe@9 1282 CASE WHEN "contingent"."initiative_limit" NOTNULL THEN (
jbe@9 1283 SELECT count(1) FROM "opening_draft"
jbe@9 1284 WHERE "opening_draft"."author_id" = "member"."id"
jbe@9 1285 AND "opening_draft"."created" > now() - "contingent"."time_frame"
jbe@9 1286 ) ELSE NULL END AS "initiative_count",
jbe@9 1287 "contingent"."initiative_limit"
jbe@9 1288 FROM "member" CROSS JOIN "contingent";
jbe@9 1289
jbe@9 1290 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 1291
jbe@9 1292 COMMENT ON COLUMN "member_contingent"."text_entry_count" IS 'Only calculated when "text_entry_limit" is not null in the same row';
jbe@9 1293 COMMENT ON COLUMN "member_contingent"."initiative_count" IS 'Only calculated when "initiative_limit" is not null in the same row';
jbe@9 1294
jbe@9 1295
jbe@9 1296 CREATE VIEW "member_contingent_left" AS
jbe@9 1297 SELECT
jbe@9 1298 "member_id",
jbe@9 1299 max("text_entry_limit" - "text_entry_count") AS "text_entries_left",
jbe@9 1300 max("initiative_limit" - "initiative_count") AS "initiatives_left"
jbe@9 1301 FROM "member_contingent" GROUP BY "member_id";
jbe@9 1302
jbe@9 1303 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 1304
jbe@9 1305
jbe@0 1306
jbe@5 1307 --------------------------------------------------
jbe@5 1308 -- Set returning function for delegation chains --
jbe@5 1309 --------------------------------------------------
jbe@5 1310
jbe@5 1311
jbe@5 1312 CREATE TYPE "delegation_chain_loop_tag" AS ENUM
jbe@5 1313 ('first', 'intermediate', 'last', 'repetition');
jbe@5 1314
jbe@5 1315 COMMENT ON TYPE "delegation_chain_loop_tag" IS 'Type for loop tags in "delegation_chain_row" type';
jbe@5 1316
jbe@5 1317
jbe@5 1318 CREATE TYPE "delegation_chain_row" AS (
jbe@5 1319 "index" INT4,
jbe@5 1320 "member_id" INT4,
jbe@5 1321 "member_active" BOOLEAN,
jbe@5 1322 "participation" BOOLEAN,
jbe@5 1323 "overridden" BOOLEAN,
jbe@5 1324 "scope_in" "delegation_scope",
jbe@5 1325 "scope_out" "delegation_scope",
jbe@5 1326 "loop" "delegation_chain_loop_tag" );
jbe@5 1327
jbe@5 1328 COMMENT ON TYPE "delegation_chain_row" IS 'Type of rows returned by "delegation_chain"(...) functions';
jbe@5 1329
jbe@5 1330 COMMENT ON COLUMN "delegation_chain_row"."index" IS 'Index starting with 0 and counting up';
jbe@5 1331 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 1332 COMMENT ON COLUMN "delegation_chain_row"."overridden" IS 'True, if an entry with lower index has "participation" set to true';
jbe@5 1333 COMMENT ON COLUMN "delegation_chain_row"."scope_in" IS 'Scope of used incoming delegation';
jbe@5 1334 COMMENT ON COLUMN "delegation_chain_row"."scope_out" IS 'Scope of used outgoing delegation';
jbe@5 1335 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 1336
jbe@5 1337
jbe@5 1338 CREATE FUNCTION "delegation_chain"
jbe@5 1339 ( "member_id_p" "member"."id"%TYPE,
jbe@5 1340 "area_id_p" "area"."id"%TYPE,
jbe@5 1341 "issue_id_p" "issue"."id"%TYPE,
jbe@5 1342 "simulate_trustee_id_p" "member"."id"%TYPE )
jbe@5 1343 RETURNS SETOF "delegation_chain_row"
jbe@5 1344 LANGUAGE 'plpgsql' STABLE AS $$
jbe@5 1345 DECLARE
jbe@5 1346 "issue_row" "issue"%ROWTYPE;
jbe@5 1347 "visited_member_ids" INT4[]; -- "member"."id"%TYPE[]
jbe@5 1348 "loop_member_id_v" "member"."id"%TYPE;
jbe@5 1349 "output_row" "delegation_chain_row";
jbe@5 1350 "output_rows" "delegation_chain_row"[];
jbe@5 1351 "delegation_row" "delegation"%ROWTYPE;
jbe@5 1352 "row_count" INT4;
jbe@5 1353 "i" INT4;
jbe@5 1354 "loop_v" BOOLEAN;
jbe@5 1355 BEGIN
jbe@5 1356 SELECT * INTO "issue_row" FROM "issue" WHERE "id" = "issue_id_p";
jbe@5 1357 "visited_member_ids" := '{}';
jbe@5 1358 "loop_member_id_v" := NULL;
jbe@5 1359 "output_rows" := '{}';
jbe@5 1360 "output_row"."index" := 0;
jbe@5 1361 "output_row"."member_id" := "member_id_p";
jbe@5 1362 "output_row"."member_active" := TRUE;
jbe@5 1363 "output_row"."participation" := FALSE;
jbe@5 1364 "output_row"."overridden" := FALSE;
jbe@5 1365 "output_row"."scope_out" := NULL;
jbe@5 1366 LOOP
jbe@5 1367 IF "visited_member_ids" @> ARRAY["output_row"."member_id"] THEN
jbe@5 1368 "loop_member_id_v" := "output_row"."member_id";
jbe@5 1369 ELSE
jbe@5 1370 "visited_member_ids" :=
jbe@5 1371 "visited_member_ids" || "output_row"."member_id";
jbe@5 1372 END IF;
jbe@5 1373 IF "output_row"."participation" THEN
jbe@5 1374 "output_row"."overridden" := TRUE;
jbe@5 1375 END IF;
jbe@5 1376 "output_row"."scope_in" := "output_row"."scope_out";
jbe@5 1377 IF EXISTS (
jbe@5 1378 SELECT NULL FROM "member"
jbe@5 1379 WHERE "id" = "output_row"."member_id" AND "active"
jbe@5 1380 ) THEN
jbe@5 1381 IF "area_id_p" ISNULL AND "issue_id_p" ISNULL THEN
jbe@5 1382 SELECT * INTO "delegation_row" FROM "delegation"
jbe@5 1383 WHERE "truster_id" = "output_row"."member_id"
jbe@10 1384 AND "scope" = 'global';
jbe@5 1385 ELSIF "area_id_p" NOTNULL AND "issue_id_p" ISNULL THEN
jbe@5 1386 "output_row"."participation" := EXISTS (
jbe@5 1387 SELECT NULL FROM "membership"
jbe@5 1388 WHERE "area_id" = "area_id_p"
jbe@5 1389 AND "member_id" = "output_row"."member_id"
jbe@5 1390 );
jbe@5 1391 SELECT * INTO "delegation_row" FROM "delegation"
jbe@5 1392 WHERE "truster_id" = "output_row"."member_id"
jbe@10 1393 AND ("scope" = 'global' OR "area_id" = "area_id_p")
jbe@10 1394 ORDER BY "scope" DESC;
jbe@5 1395 ELSIF "area_id_p" ISNULL AND "issue_id_p" NOTNULL THEN
jbe@5 1396 "output_row"."participation" := EXISTS (
jbe@5 1397 SELECT NULL FROM "interest"
jbe@5 1398 WHERE "issue_id" = "issue_id_p"
jbe@5 1399 AND "member_id" = "output_row"."member_id"
jbe@5 1400 );
jbe@5 1401 SELECT * INTO "delegation_row" FROM "delegation"
jbe@5 1402 WHERE "truster_id" = "output_row"."member_id"
jbe@10 1403 AND ("scope" = 'global' OR
jbe@10 1404 "area_id" = "issue_row"."area_id" OR
jbe@10 1405 "issue_id" = "issue_id_p"
jbe@10 1406 )
jbe@10 1407 ORDER BY "scope" DESC;
jbe@5 1408 ELSE
jbe@5 1409 RAISE EXCEPTION 'Either area_id or issue_id or both must be NULL.';
jbe@5 1410 END IF;
jbe@5 1411 ELSE
jbe@5 1412 "output_row"."member_active" := FALSE;
jbe@5 1413 "output_row"."participation" := FALSE;
jbe@5 1414 "output_row"."scope_out" := NULL;
jbe@5 1415 "delegation_row" := ROW(NULL);
jbe@5 1416 END IF;
jbe@5 1417 IF
jbe@5 1418 "output_row"."member_id" = "member_id_p" AND
jbe@5 1419 "simulate_trustee_id_p" NOTNULL
jbe@5 1420 THEN
jbe@5 1421 "output_row"."scope_out" := CASE
jbe@5 1422 WHEN "area_id_p" ISNULL AND "issue_id_p" ISNULL THEN 'global'
jbe@5 1423 WHEN "area_id_p" NOTNULL AND "issue_id_p" ISNULL THEN 'area'
jbe@5 1424 WHEN "area_id_p" ISNULL AND "issue_id_p" NOTNULL THEN 'issue'
jbe@5 1425 END;
jbe@5 1426 "output_rows" := "output_rows" || "output_row";
jbe@5 1427 "output_row"."member_id" := "simulate_trustee_id_p";
jbe@5 1428 ELSIF "delegation_row"."trustee_id" NOTNULL THEN
jbe@10 1429 "output_row"."scope_out" := "delegation_row"."scope";
jbe@5 1430 "output_rows" := "output_rows" || "output_row";
jbe@5 1431 "output_row"."member_id" := "delegation_row"."trustee_id";
jbe@5 1432 ELSE
jbe@5 1433 "output_row"."scope_out" := NULL;
jbe@5 1434 "output_rows" := "output_rows" || "output_row";
jbe@5 1435 EXIT;
jbe@5 1436 END IF;
jbe@5 1437 EXIT WHEN "loop_member_id_v" NOTNULL;
jbe@5 1438 "output_row"."index" := "output_row"."index" + 1;
jbe@5 1439 END LOOP;
jbe@5 1440 "row_count" := array_upper("output_rows", 1);
jbe@5 1441 "i" := 1;
jbe@5 1442 "loop_v" := FALSE;
jbe@5 1443 LOOP
jbe@5 1444 "output_row" := "output_rows"["i"];
jbe@5 1445 EXIT WHEN "output_row"."member_id" ISNULL;
jbe@5 1446 IF "loop_v" THEN
jbe@5 1447 IF "i" + 1 = "row_count" THEN
jbe@5 1448 "output_row"."loop" := 'last';
jbe@5 1449 ELSIF "i" = "row_count" THEN
jbe@5 1450 "output_row"."loop" := 'repetition';
jbe@5 1451 ELSE
jbe@5 1452 "output_row"."loop" := 'intermediate';
jbe@5 1453 END IF;
jbe@5 1454 ELSIF "output_row"."member_id" = "loop_member_id_v" THEN
jbe@5 1455 "output_row"."loop" := 'first';
jbe@5 1456 "loop_v" := TRUE;
jbe@5 1457 END IF;
jbe@5 1458 IF "area_id_p" ISNULL AND "issue_id_p" ISNULL THEN
jbe@5 1459 "output_row"."participation" := NULL;
jbe@5 1460 END IF;
jbe@5 1461 RETURN NEXT "output_row";
jbe@5 1462 "i" := "i" + 1;
jbe@5 1463 END LOOP;
jbe@5 1464 RETURN;
jbe@5 1465 END;
jbe@5 1466 $$;
jbe@5 1467
jbe@5 1468 COMMENT ON FUNCTION "delegation_chain"
jbe@5 1469 ( "member"."id"%TYPE,
jbe@5 1470 "area"."id"%TYPE,
jbe@5 1471 "issue"."id"%TYPE,
jbe@5 1472 "member"."id"%TYPE )
jbe@5 1473 IS 'Helper function for frontends to display delegation chains; Not part of internal voting logic';
jbe@5 1474
jbe@5 1475 CREATE FUNCTION "delegation_chain"
jbe@5 1476 ( "member_id_p" "member"."id"%TYPE,
jbe@5 1477 "area_id_p" "area"."id"%TYPE,
jbe@5 1478 "issue_id_p" "issue"."id"%TYPE )
jbe@5 1479 RETURNS SETOF "delegation_chain_row"
jbe@5 1480 LANGUAGE 'plpgsql' STABLE AS $$
jbe@5 1481 DECLARE
jbe@5 1482 "result_row" "delegation_chain_row";
jbe@5 1483 BEGIN
jbe@5 1484 FOR "result_row" IN
jbe@5 1485 SELECT * FROM "delegation_chain"(
jbe@5 1486 "member_id_p", "area_id_p", "issue_id_p", NULL
jbe@5 1487 )
jbe@5 1488 LOOP
jbe@5 1489 RETURN NEXT "result_row";
jbe@5 1490 END LOOP;
jbe@5 1491 RETURN;
jbe@5 1492 END;
jbe@5 1493 $$;
jbe@5 1494
jbe@5 1495 COMMENT ON FUNCTION "delegation_chain"
jbe@5 1496 ( "member"."id"%TYPE,
jbe@5 1497 "area"."id"%TYPE,
jbe@5 1498 "issue"."id"%TYPE )
jbe@5 1499 IS 'Shortcut for "delegation_chain"(...) function where 4th parameter is null';
jbe@5 1500
jbe@5 1501
jbe@5 1502
jbe@0 1503 ------------------------------
jbe@0 1504 -- Comparison by vote count --
jbe@0 1505 ------------------------------
jbe@0 1506
jbe@0 1507 CREATE FUNCTION "vote_ratio"
jbe@0 1508 ( "positive_votes_p" "initiative"."positive_votes"%TYPE,
jbe@0 1509 "negative_votes_p" "initiative"."negative_votes"%TYPE )
jbe@0 1510 RETURNS FLOAT8
jbe@0 1511 LANGUAGE 'plpgsql' STABLE AS $$
jbe@0 1512 DECLARE
jbe@0 1513 "total_v" INT4;
jbe@0 1514 BEGIN
jbe@0 1515 "total_v" := "positive_votes_p" + "negative_votes_p";
jbe@0 1516 IF "total_v" > 0 THEN
jbe@0 1517 RETURN "positive_votes_p"::FLOAT8 / "total_v"::FLOAT8;
jbe@0 1518 ELSE
jbe@0 1519 RETURN 0.5;
jbe@0 1520 END IF;
jbe@0 1521 END;
jbe@0 1522 $$;
jbe@0 1523
jbe@0 1524 COMMENT ON FUNCTION "vote_ratio"
jbe@0 1525 ( "initiative"."positive_votes"%TYPE,
jbe@0 1526 "initiative"."negative_votes"%TYPE )
jbe@0 1527 IS 'Ratio of positive votes to sum of positive and negative votes; 0.5, if there are neither positive nor negative votes';
jbe@0 1528
jbe@0 1529
jbe@0 1530
jbe@0 1531 ------------------------------------------------
jbe@0 1532 -- Locking for snapshots and voting procedure --
jbe@0 1533 ------------------------------------------------
jbe@0 1534
jbe@0 1535 CREATE FUNCTION "global_lock"() RETURNS VOID
jbe@0 1536 LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@0 1537 BEGIN
jbe@0 1538 -- NOTE: PostgreSQL allows reading, while tables are locked in
jbe@0 1539 -- exclusive move. Transactions should be kept short anyway!
jbe@0 1540 LOCK TABLE "member" IN EXCLUSIVE MODE;
jbe@4 1541 LOCK TABLE "area" IN EXCLUSIVE MODE;
jbe@4 1542 LOCK TABLE "membership" IN EXCLUSIVE MODE;
jbe@4 1543 -- NOTE: "member", "area" and "membership" are locked first to
jbe@4 1544 -- prevent deadlocks in combination with "calculate_member_counts"()
jbe@0 1545 LOCK TABLE "policy" IN EXCLUSIVE MODE;
jbe@0 1546 LOCK TABLE "issue" IN EXCLUSIVE MODE;
jbe@0 1547 LOCK TABLE "initiative" IN EXCLUSIVE MODE;
jbe@0 1548 LOCK TABLE "draft" IN EXCLUSIVE MODE;
jbe@0 1549 LOCK TABLE "suggestion" IN EXCLUSIVE MODE;
jbe@0 1550 LOCK TABLE "interest" IN EXCLUSIVE MODE;
jbe@0 1551 LOCK TABLE "initiator" IN EXCLUSIVE MODE;
jbe@0 1552 LOCK TABLE "supporter" IN EXCLUSIVE MODE;
jbe@0 1553 LOCK TABLE "opinion" IN EXCLUSIVE MODE;
jbe@0 1554 LOCK TABLE "delegation" IN EXCLUSIVE MODE;
jbe@0 1555 LOCK TABLE "direct_population_snapshot" IN EXCLUSIVE MODE;
jbe@0 1556 LOCK TABLE "delegating_population_snapshot" IN EXCLUSIVE MODE;
jbe@0 1557 LOCK TABLE "direct_interest_snapshot" IN EXCLUSIVE MODE;
jbe@0 1558 LOCK TABLE "delegating_interest_snapshot" IN EXCLUSIVE MODE;
jbe@0 1559 LOCK TABLE "direct_supporter_snapshot" IN EXCLUSIVE MODE;
jbe@0 1560 LOCK TABLE "direct_voter" IN EXCLUSIVE MODE;
jbe@0 1561 LOCK TABLE "delegating_voter" IN EXCLUSIVE MODE;
jbe@0 1562 LOCK TABLE "vote" IN EXCLUSIVE MODE;
jbe@0 1563 RETURN;
jbe@0 1564 END;
jbe@0 1565 $$;
jbe@0 1566
jbe@0 1567 COMMENT ON FUNCTION "global_lock"() IS 'Locks all tables related to support/voting until end of transaction; read access is still possible though';
jbe@0 1568
jbe@0 1569
jbe@0 1570
jbe@4 1571 -------------------------------
jbe@4 1572 -- Materialize member counts --
jbe@4 1573 -------------------------------
jbe@4 1574
jbe@4 1575 CREATE FUNCTION "calculate_member_counts"()
jbe@4 1576 RETURNS VOID
jbe@4 1577 LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@4 1578 BEGIN
jbe@4 1579 LOCK TABLE "member" IN EXCLUSIVE MODE;
jbe@4 1580 LOCK TABLE "area" IN EXCLUSIVE MODE;
jbe@4 1581 LOCK TABLE "membership" IN EXCLUSIVE MODE;
jbe@4 1582 DELETE FROM "member_count";
jbe@5 1583 INSERT INTO "member_count" ("total_count")
jbe@5 1584 SELECT "total_count" FROM "member_count_view";
jbe@5 1585 UPDATE "area" SET
jbe@5 1586 "direct_member_count" = "view"."direct_member_count",
jbe@5 1587 "member_weight" = "view"."member_weight",
jbe@5 1588 "autoreject_weight" = "view"."autoreject_weight"
jbe@5 1589 FROM "area_member_count" AS "view"
jbe@5 1590 WHERE "view"."area_id" = "area"."id";
jbe@4 1591 RETURN;
jbe@4 1592 END;
jbe@4 1593 $$;
jbe@4 1594
jbe@4 1595 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 1596
jbe@4 1597
jbe@4 1598
jbe@0 1599 ------------------------------
jbe@0 1600 -- Calculation of snapshots --
jbe@0 1601 ------------------------------
jbe@0 1602
jbe@0 1603 CREATE FUNCTION "weight_of_added_delegations_for_population_snapshot"
jbe@0 1604 ( "issue_id_p" "issue"."id"%TYPE,
jbe@0 1605 "member_id_p" "member"."id"%TYPE,
jbe@0 1606 "delegate_member_ids_p" "delegating_population_snapshot"."delegate_member_ids"%TYPE )
jbe@0 1607 RETURNS "direct_population_snapshot"."weight"%TYPE
jbe@0 1608 LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@0 1609 DECLARE
jbe@0 1610 "issue_delegation_row" "issue_delegation"%ROWTYPE;
jbe@0 1611 "delegate_member_ids_v" "delegating_population_snapshot"."delegate_member_ids"%TYPE;
jbe@0 1612 "weight_v" INT4;
jbe@8 1613 "sub_weight_v" INT4;
jbe@0 1614 BEGIN
jbe@0 1615 "weight_v" := 0;
jbe@0 1616 FOR "issue_delegation_row" IN
jbe@0 1617 SELECT * FROM "issue_delegation"
jbe@0 1618 WHERE "trustee_id" = "member_id_p"
jbe@0 1619 AND "issue_id" = "issue_id_p"
jbe@0 1620 LOOP
jbe@0 1621 IF NOT EXISTS (
jbe@0 1622 SELECT NULL FROM "direct_population_snapshot"
jbe@0 1623 WHERE "issue_id" = "issue_id_p"
jbe@0 1624 AND "event" = 'periodic'
jbe@0 1625 AND "member_id" = "issue_delegation_row"."truster_id"
jbe@0 1626 ) AND NOT EXISTS (
jbe@0 1627 SELECT NULL FROM "delegating_population_snapshot"
jbe@0 1628 WHERE "issue_id" = "issue_id_p"
jbe@0 1629 AND "event" = 'periodic'
jbe@0 1630 AND "member_id" = "issue_delegation_row"."truster_id"
jbe@0 1631 ) THEN
jbe@0 1632 "delegate_member_ids_v" :=
jbe@0 1633 "member_id_p" || "delegate_member_ids_p";
jbe@10 1634 INSERT INTO "delegating_population_snapshot" (
jbe@10 1635 "issue_id",
jbe@10 1636 "event",
jbe@10 1637 "member_id",
jbe@10 1638 "scope",
jbe@10 1639 "delegate_member_ids"
jbe@10 1640 ) VALUES (
jbe@0 1641 "issue_id_p",
jbe@0 1642 'periodic',
jbe@0 1643 "issue_delegation_row"."truster_id",
jbe@10 1644 "issue_delegation_row"."scope",
jbe@0 1645 "delegate_member_ids_v"
jbe@0 1646 );
jbe@8 1647 "sub_weight_v" := 1 +
jbe@0 1648 "weight_of_added_delegations_for_population_snapshot"(
jbe@0 1649 "issue_id_p",
jbe@0 1650 "issue_delegation_row"."truster_id",
jbe@0 1651 "delegate_member_ids_v"
jbe@0 1652 );
jbe@8 1653 UPDATE "delegating_population_snapshot"
jbe@8 1654 SET "weight" = "sub_weight_v"
jbe@8 1655 WHERE "issue_id" = "issue_id_p"
jbe@8 1656 AND "event" = 'periodic'
jbe@8 1657 AND "member_id" = "issue_delegation_row"."truster_id";
jbe@8 1658 "weight_v" := "weight_v" + "sub_weight_v";
jbe@0 1659 END IF;
jbe@0 1660 END LOOP;
jbe@0 1661 RETURN "weight_v";
jbe@0 1662 END;
jbe@0 1663 $$;
jbe@0 1664
jbe@0 1665 COMMENT ON FUNCTION "weight_of_added_delegations_for_population_snapshot"
jbe@0 1666 ( "issue"."id"%TYPE,
jbe@0 1667 "member"."id"%TYPE,
jbe@0 1668 "delegating_population_snapshot"."delegate_member_ids"%TYPE )
jbe@0 1669 IS 'Helper function for "create_population_snapshot" function';
jbe@0 1670
jbe@0 1671
jbe@0 1672 CREATE FUNCTION "create_population_snapshot"
jbe@0 1673 ( "issue_id_p" "issue"."id"%TYPE )
jbe@0 1674 RETURNS VOID
jbe@0 1675 LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@0 1676 DECLARE
jbe@0 1677 "member_id_v" "member"."id"%TYPE;
jbe@0 1678 BEGIN
jbe@0 1679 DELETE FROM "direct_population_snapshot"
jbe@0 1680 WHERE "issue_id" = "issue_id_p"
jbe@0 1681 AND "event" = 'periodic';
jbe@0 1682 DELETE FROM "delegating_population_snapshot"
jbe@0 1683 WHERE "issue_id" = "issue_id_p"
jbe@0 1684 AND "event" = 'periodic';
jbe@0 1685 INSERT INTO "direct_population_snapshot"
jbe@0 1686 ("issue_id", "event", "member_id", "interest_exists")
jbe@0 1687 SELECT DISTINCT ON ("issue_id", "member_id")
jbe@0 1688 "issue_id_p" AS "issue_id",
jbe@0 1689 'periodic' AS "event",
jbe@0 1690 "subquery"."member_id",
jbe@0 1691 "subquery"."interest_exists"
jbe@0 1692 FROM (
jbe@0 1693 SELECT
jbe@0 1694 "member"."id" AS "member_id",
jbe@0 1695 FALSE AS "interest_exists"
jbe@0 1696 FROM "issue"
jbe@0 1697 JOIN "area" ON "issue"."area_id" = "area"."id"
jbe@0 1698 JOIN "membership" ON "area"."id" = "membership"."area_id"
jbe@0 1699 JOIN "member" ON "membership"."member_id" = "member"."id"
jbe@0 1700 WHERE "issue"."id" = "issue_id_p"
jbe@0 1701 AND "member"."active"
jbe@0 1702 UNION
jbe@0 1703 SELECT
jbe@0 1704 "member"."id" AS "member_id",
jbe@0 1705 TRUE AS "interest_exists"
jbe@0 1706 FROM "interest" JOIN "member"
jbe@0 1707 ON "interest"."member_id" = "member"."id"
jbe@0 1708 WHERE "interest"."issue_id" = "issue_id_p"
jbe@0 1709 AND "member"."active"
jbe@0 1710 ) AS "subquery"
jbe@0 1711 ORDER BY
jbe@0 1712 "issue_id_p",
jbe@0 1713 "subquery"."member_id",
jbe@0 1714 "subquery"."interest_exists" DESC;
jbe@0 1715 FOR "member_id_v" IN
jbe@0 1716 SELECT "member_id" FROM "direct_population_snapshot"
jbe@0 1717 WHERE "issue_id" = "issue_id_p"
jbe@0 1718 AND "event" = 'periodic'
jbe@0 1719 LOOP
jbe@0 1720 UPDATE "direct_population_snapshot" SET
jbe@0 1721 "weight" = 1 +
jbe@0 1722 "weight_of_added_delegations_for_population_snapshot"(
jbe@0 1723 "issue_id_p",
jbe@0 1724 "member_id_v",
jbe@0 1725 '{}'
jbe@0 1726 )
jbe@0 1727 WHERE "issue_id" = "issue_id_p"
jbe@0 1728 AND "event" = 'periodic'
jbe@0 1729 AND "member_id" = "member_id_v";
jbe@0 1730 END LOOP;
jbe@0 1731 RETURN;
jbe@0 1732 END;
jbe@0 1733 $$;
jbe@0 1734
jbe@0 1735 COMMENT ON FUNCTION "create_population_snapshot"
jbe@0 1736 ( "issue_id_p" "issue"."id"%TYPE )
jbe@0 1737 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 1738
jbe@0 1739
jbe@0 1740 CREATE FUNCTION "weight_of_added_delegations_for_interest_snapshot"
jbe@0 1741 ( "issue_id_p" "issue"."id"%TYPE,
jbe@0 1742 "member_id_p" "member"."id"%TYPE,
jbe@0 1743 "delegate_member_ids_p" "delegating_interest_snapshot"."delegate_member_ids"%TYPE )
jbe@0 1744 RETURNS "direct_interest_snapshot"."weight"%TYPE
jbe@0 1745 LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@0 1746 DECLARE
jbe@0 1747 "issue_delegation_row" "issue_delegation"%ROWTYPE;
jbe@0 1748 "delegate_member_ids_v" "delegating_interest_snapshot"."delegate_member_ids"%TYPE;
jbe@0 1749 "weight_v" INT4;
jbe@8 1750 "sub_weight_v" INT4;
jbe@0 1751 BEGIN
jbe@0 1752 "weight_v" := 0;
jbe@0 1753 FOR "issue_delegation_row" IN
jbe@0 1754 SELECT * FROM "issue_delegation"
jbe@0 1755 WHERE "trustee_id" = "member_id_p"
jbe@0 1756 AND "issue_id" = "issue_id_p"
jbe@0 1757 LOOP
jbe@0 1758 IF NOT EXISTS (
jbe@0 1759 SELECT NULL FROM "direct_interest_snapshot"
jbe@0 1760 WHERE "issue_id" = "issue_id_p"
jbe@0 1761 AND "event" = 'periodic'
jbe@0 1762 AND "member_id" = "issue_delegation_row"."truster_id"
jbe@0 1763 ) AND NOT EXISTS (
jbe@0 1764 SELECT NULL FROM "delegating_interest_snapshot"
jbe@0 1765 WHERE "issue_id" = "issue_id_p"
jbe@0 1766 AND "event" = 'periodic'
jbe@0 1767 AND "member_id" = "issue_delegation_row"."truster_id"
jbe@0 1768 ) THEN
jbe@0 1769 "delegate_member_ids_v" :=
jbe@0 1770 "member_id_p" || "delegate_member_ids_p";
jbe@10 1771 INSERT INTO "delegating_interest_snapshot" (
jbe@10 1772 "issue_id",
jbe@10 1773 "event",
jbe@10 1774 "member_id",
jbe@10 1775 "scope",
jbe@10 1776 "delegate_member_ids"
jbe@10 1777 ) VALUES (
jbe@0 1778 "issue_id_p",
jbe@0 1779 'periodic',
jbe@0 1780 "issue_delegation_row"."truster_id",
jbe@10 1781 "issue_delegation_row"."scope",
jbe@0 1782 "delegate_member_ids_v"
jbe@0 1783 );
jbe@8 1784 "sub_weight_v" := 1 +
jbe@0 1785 "weight_of_added_delegations_for_interest_snapshot"(
jbe@0 1786 "issue_id_p",
jbe@0 1787 "issue_delegation_row"."truster_id",
jbe@0 1788 "delegate_member_ids_v"
jbe@0 1789 );
jbe@8 1790 UPDATE "delegating_interest_snapshot"
jbe@8 1791 SET "weight" = "sub_weight_v"
jbe@8 1792 WHERE "issue_id" = "issue_id_p"
jbe@8 1793 AND "event" = 'periodic'
jbe@8 1794 AND "member_id" = "issue_delegation_row"."truster_id";
jbe@8 1795 "weight_v" := "weight_v" + "sub_weight_v";
jbe@0 1796 END IF;
jbe@0 1797 END LOOP;
jbe@0 1798 RETURN "weight_v";
jbe@0 1799 END;
jbe@0 1800 $$;
jbe@0 1801
jbe@0 1802 COMMENT ON FUNCTION "weight_of_added_delegations_for_interest_snapshot"
jbe@0 1803 ( "issue"."id"%TYPE,
jbe@0 1804 "member"."id"%TYPE,
jbe@0 1805 "delegating_interest_snapshot"."delegate_member_ids"%TYPE )
jbe@0 1806 IS 'Helper function for "create_interest_snapshot" function';
jbe@0 1807
jbe@0 1808
jbe@0 1809 CREATE FUNCTION "create_interest_snapshot"
jbe@0 1810 ( "issue_id_p" "issue"."id"%TYPE )
jbe@0 1811 RETURNS VOID
jbe@0 1812 LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@0 1813 DECLARE
jbe@0 1814 "member_id_v" "member"."id"%TYPE;
jbe@0 1815 BEGIN
jbe@0 1816 DELETE FROM "direct_interest_snapshot"
jbe@0 1817 WHERE "issue_id" = "issue_id_p"
jbe@0 1818 AND "event" = 'periodic';
jbe@0 1819 DELETE FROM "delegating_interest_snapshot"
jbe@0 1820 WHERE "issue_id" = "issue_id_p"
jbe@0 1821 AND "event" = 'periodic';
jbe@0 1822 DELETE FROM "direct_supporter_snapshot"
jbe@0 1823 WHERE "issue_id" = "issue_id_p"
jbe@0 1824 AND "event" = 'periodic';
jbe@0 1825 INSERT INTO "direct_interest_snapshot"
jbe@0 1826 ("issue_id", "event", "member_id", "voting_requested")
jbe@0 1827 SELECT
jbe@0 1828 "issue_id_p" AS "issue_id",
jbe@0 1829 'periodic' AS "event",
jbe@0 1830 "member"."id" AS "member_id",
jbe@0 1831 "interest"."voting_requested"
jbe@0 1832 FROM "interest" JOIN "member"
jbe@0 1833 ON "interest"."member_id" = "member"."id"
jbe@0 1834 WHERE "interest"."issue_id" = "issue_id_p"
jbe@0 1835 AND "member"."active";
jbe@0 1836 FOR "member_id_v" IN
jbe@0 1837 SELECT "member_id" FROM "direct_interest_snapshot"
jbe@0 1838 WHERE "issue_id" = "issue_id_p"
jbe@0 1839 AND "event" = 'periodic'
jbe@0 1840 LOOP
jbe@0 1841 UPDATE "direct_interest_snapshot" SET
jbe@0 1842 "weight" = 1 +
jbe@0 1843 "weight_of_added_delegations_for_interest_snapshot"(
jbe@0 1844 "issue_id_p",
jbe@0 1845 "member_id_v",
jbe@0 1846 '{}'
jbe@0 1847 )
jbe@0 1848 WHERE "issue_id" = "issue_id_p"
jbe@0 1849 AND "event" = 'periodic'
jbe@0 1850 AND "member_id" = "member_id_v";
jbe@0 1851 END LOOP;
jbe@0 1852 INSERT INTO "direct_supporter_snapshot"
jbe@0 1853 ( "issue_id", "initiative_id", "event", "member_id",
jbe@0 1854 "informed", "satisfied" )
jbe@0 1855 SELECT
jbe@0 1856 "issue_id_p" AS "issue_id",
jbe@0 1857 "initiative"."id" AS "initiative_id",
jbe@0 1858 'periodic' AS "event",
jbe@0 1859 "member"."id" AS "member_id",
jbe@0 1860 "supporter"."draft_id" = "current_draft"."id" AS "informed",
jbe@0 1861 NOT EXISTS (
jbe@0 1862 SELECT NULL FROM "critical_opinion"
jbe@0 1863 WHERE "initiative_id" = "initiative"."id"
jbe@0 1864 AND "member_id" = "member"."id"
jbe@0 1865 ) AS "satisfied"
jbe@0 1866 FROM "supporter"
jbe@0 1867 JOIN "member"
jbe@0 1868 ON "supporter"."member_id" = "member"."id"
jbe@0 1869 JOIN "initiative"
jbe@0 1870 ON "supporter"."initiative_id" = "initiative"."id"
jbe@0 1871 JOIN "current_draft"
jbe@0 1872 ON "initiative"."id" = "current_draft"."initiative_id"
jbe@0 1873 JOIN "direct_interest_snapshot"
jbe@0 1874 ON "member"."id" = "direct_interest_snapshot"."member_id"
jbe@0 1875 AND "initiative"."issue_id" = "direct_interest_snapshot"."issue_id"
jbe@3 1876 AND "event" = 'periodic'
jbe@0 1877 WHERE "member"."active"
jbe@0 1878 AND "initiative"."issue_id" = "issue_id_p";
jbe@0 1879 RETURN;
jbe@0 1880 END;
jbe@0 1881 $$;
jbe@0 1882
jbe@0 1883 COMMENT ON FUNCTION "create_interest_snapshot"
jbe@0 1884 ( "issue"."id"%TYPE )
jbe@0 1885 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 1886
jbe@0 1887
jbe@0 1888 CREATE FUNCTION "create_snapshot"
jbe@0 1889 ( "issue_id_p" "issue"."id"%TYPE )
jbe@0 1890 RETURNS VOID
jbe@0 1891 LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@0 1892 DECLARE
jbe@0 1893 "initiative_id_v" "initiative"."id"%TYPE;
jbe@0 1894 "suggestion_id_v" "suggestion"."id"%TYPE;
jbe@0 1895 BEGIN
jbe@0 1896 PERFORM "global_lock"();
jbe@0 1897 PERFORM "create_population_snapshot"("issue_id_p");
jbe@0 1898 PERFORM "create_interest_snapshot"("issue_id_p");
jbe@0 1899 UPDATE "issue" SET
jbe@8 1900 "snapshot" = now(),
jbe@8 1901 "latest_snapshot_event" = 'periodic',
jbe@0 1902 "population" = (
jbe@0 1903 SELECT coalesce(sum("weight"), 0)
jbe@0 1904 FROM "direct_population_snapshot"
jbe@0 1905 WHERE "issue_id" = "issue_id_p"
jbe@0 1906 AND "event" = 'periodic'
jbe@0 1907 ),
jbe@8 1908 "vote_now" = (
jbe@0 1909 SELECT coalesce(sum("weight"), 0)
jbe@0 1910 FROM "direct_interest_snapshot"
jbe@0 1911 WHERE "issue_id" = "issue_id_p"
jbe@0 1912 AND "event" = 'periodic'
jbe@0 1913 AND "voting_requested" = TRUE
jbe@0 1914 ),
jbe@0 1915 "vote_later" = (
jbe@0 1916 SELECT coalesce(sum("weight"), 0)
jbe@0 1917 FROM "direct_interest_snapshot"
jbe@0 1918 WHERE "issue_id" = "issue_id_p"
jbe@0 1919 AND "event" = 'periodic'
jbe@0 1920 AND "voting_requested" = FALSE
jbe@0 1921 )
jbe@0 1922 WHERE "id" = "issue_id_p";
jbe@0 1923 FOR "initiative_id_v" IN
jbe@0 1924 SELECT "id" FROM "initiative" WHERE "issue_id" = "issue_id_p"
jbe@0 1925 LOOP
jbe@0 1926 UPDATE "initiative" SET
jbe@0 1927 "supporter_count" = (
jbe@0 1928 SELECT coalesce(sum("di"."weight"), 0)
jbe@0 1929 FROM "direct_interest_snapshot" AS "di"
jbe@0 1930 JOIN "direct_supporter_snapshot" AS "ds"
jbe@0 1931 ON "di"."member_id" = "ds"."member_id"
jbe@0 1932 WHERE "di"."issue_id" = "issue_id_p"
jbe@0 1933 AND "di"."event" = 'periodic'
jbe@0 1934 AND "ds"."initiative_id" = "initiative_id_v"
jbe@0 1935 AND "ds"."event" = 'periodic'
jbe@0 1936 ),
jbe@0 1937 "informed_supporter_count" = (
jbe@0 1938 SELECT coalesce(sum("di"."weight"), 0)
jbe@0 1939 FROM "direct_interest_snapshot" AS "di"
jbe@0 1940 JOIN "direct_supporter_snapshot" AS "ds"
jbe@0 1941 ON "di"."member_id" = "ds"."member_id"
jbe@0 1942 WHERE "di"."issue_id" = "issue_id_p"
jbe@0 1943 AND "di"."event" = 'periodic'
jbe@0 1944 AND "ds"."initiative_id" = "initiative_id_v"
jbe@0 1945 AND "ds"."event" = 'periodic'
jbe@0 1946 AND "ds"."informed"
jbe@0 1947 ),
jbe@0 1948 "satisfied_supporter_count" = (
jbe@0 1949 SELECT coalesce(sum("di"."weight"), 0)
jbe@0 1950 FROM "direct_interest_snapshot" AS "di"
jbe@0 1951 JOIN "direct_supporter_snapshot" AS "ds"
jbe@0 1952 ON "di"."member_id" = "ds"."member_id"
jbe@0 1953 WHERE "di"."issue_id" = "issue_id_p"
jbe@0 1954 AND "di"."event" = 'periodic'
jbe@0 1955 AND "ds"."initiative_id" = "initiative_id_v"
jbe@0 1956 AND "ds"."event" = 'periodic'
jbe@0 1957 AND "ds"."satisfied"
jbe@0 1958 ),
jbe@0 1959 "satisfied_informed_supporter_count" = (
jbe@0 1960 SELECT coalesce(sum("di"."weight"), 0)
jbe@0 1961 FROM "direct_interest_snapshot" AS "di"
jbe@0 1962 JOIN "direct_supporter_snapshot" AS "ds"
jbe@0 1963 ON "di"."member_id" = "ds"."member_id"
jbe@0 1964 WHERE "di"."issue_id" = "issue_id_p"
jbe@0 1965 AND "di"."event" = 'periodic'
jbe@0 1966 AND "ds"."initiative_id" = "initiative_id_v"
jbe@0 1967 AND "ds"."event" = 'periodic'
jbe@0 1968 AND "ds"."informed"
jbe@0 1969 AND "ds"."satisfied"
jbe@0 1970 )
jbe@0 1971 WHERE "id" = "initiative_id_v";
jbe@0 1972 FOR "suggestion_id_v" IN
jbe@0 1973 SELECT "id" FROM "suggestion"
jbe@0 1974 WHERE "initiative_id" = "initiative_id_v"
jbe@0 1975 LOOP
jbe@0 1976 UPDATE "suggestion" SET
jbe@0 1977 "minus2_unfulfilled_count" = (
jbe@0 1978 SELECT coalesce(sum("snapshot"."weight"), 0)
jbe@0 1979 FROM "opinion" JOIN "direct_interest_snapshot" AS "snapshot"
jbe@0 1980 ON "opinion"."member_id" = "snapshot"."member_id"
jbe@1 1981 WHERE "opinion"."suggestion_id" = "suggestion_id_v"
jbe@0 1982 AND "snapshot"."issue_id" = "issue_id_p"
jbe@0 1983 AND "opinion"."degree" = -2
jbe@0 1984 AND "opinion"."fulfilled" = FALSE
jbe@0 1985 ),
jbe@0 1986 "minus2_fulfilled_count" = (
jbe@0 1987 SELECT coalesce(sum("snapshot"."weight"), 0)
jbe@0 1988 FROM "opinion" JOIN "direct_interest_snapshot" AS "snapshot"
jbe@0 1989 ON "opinion"."member_id" = "snapshot"."member_id"
jbe@1 1990 WHERE "opinion"."suggestion_id" = "suggestion_id_v"
jbe@0 1991 AND "snapshot"."issue_id" = "issue_id_p"
jbe@0 1992 AND "opinion"."degree" = -2
jbe@0 1993 AND "opinion"."fulfilled" = TRUE
jbe@0 1994 ),
jbe@0 1995 "minus1_unfulfilled_count" = (
jbe@0 1996 SELECT coalesce(sum("snapshot"."weight"), 0)
jbe@0 1997 FROM "opinion" JOIN "direct_interest_snapshot" AS "snapshot"
jbe@0 1998 ON "opinion"."member_id" = "snapshot"."member_id"
jbe@1 1999 WHERE "opinion"."suggestion_id" = "suggestion_id_v"
jbe@0 2000 AND "snapshot"."issue_id" = "issue_id_p"
jbe@0 2001 AND "opinion"."degree" = -1
jbe@0 2002 AND "opinion"."fulfilled" = FALSE
jbe@0 2003 ),
jbe@0 2004 "minus1_fulfilled_count" = (
jbe@0 2005 SELECT coalesce(sum("snapshot"."weight"), 0)
jbe@0 2006 FROM "opinion" JOIN "direct_interest_snapshot" AS "snapshot"
jbe@0 2007 ON "opinion"."member_id" = "snapshot"."member_id"
jbe@1 2008 WHERE "opinion"."suggestion_id" = "suggestion_id_v"
jbe@0 2009 AND "snapshot"."issue_id" = "issue_id_p"
jbe@0 2010 AND "opinion"."degree" = -1
jbe@0 2011 AND "opinion"."fulfilled" = TRUE
jbe@0 2012 ),
jbe@0 2013 "plus1_unfulfilled_count" = (
jbe@0 2014 SELECT coalesce(sum("snapshot"."weight"), 0)
jbe@0 2015 FROM "opinion" JOIN "direct_interest_snapshot" AS "snapshot"
jbe@0 2016 ON "opinion"."member_id" = "snapshot"."member_id"
jbe@1 2017 WHERE "opinion"."suggestion_id" = "suggestion_id_v"
jbe@0 2018 AND "snapshot"."issue_id" = "issue_id_p"
jbe@0 2019 AND "opinion"."degree" = 1
jbe@0 2020 AND "opinion"."fulfilled" = FALSE
jbe@0 2021 ),
jbe@0 2022 "plus1_fulfilled_count" = (
jbe@0 2023 SELECT coalesce(sum("snapshot"."weight"), 0)
jbe@0 2024 FROM "opinion" JOIN "direct_interest_snapshot" AS "snapshot"
jbe@0 2025 ON "opinion"."member_id" = "snapshot"."member_id"
jbe@1 2026 WHERE "opinion"."suggestion_id" = "suggestion_id_v"
jbe@0 2027 AND "snapshot"."issue_id" = "issue_id_p"
jbe@0 2028 AND "opinion"."degree" = 1
jbe@0 2029 AND "opinion"."fulfilled" = TRUE
jbe@0 2030 ),
jbe@0 2031 "plus2_unfulfilled_count" = (
jbe@0 2032 SELECT coalesce(sum("snapshot"."weight"), 0)
jbe@0 2033 FROM "opinion" JOIN "direct_interest_snapshot" AS "snapshot"
jbe@0 2034 ON "opinion"."member_id" = "snapshot"."member_id"
jbe@1 2035 WHERE "opinion"."suggestion_id" = "suggestion_id_v"
jbe@0 2036 AND "snapshot"."issue_id" = "issue_id_p"
jbe@0 2037 AND "opinion"."degree" = 2
jbe@0 2038 AND "opinion"."fulfilled" = FALSE
jbe@0 2039 ),
jbe@0 2040 "plus2_fulfilled_count" = (
jbe@0 2041 SELECT coalesce(sum("snapshot"."weight"), 0)
jbe@0 2042 FROM "opinion" JOIN "direct_interest_snapshot" AS "snapshot"
jbe@0 2043 ON "opinion"."member_id" = "snapshot"."member_id"
jbe@1 2044 WHERE "opinion"."suggestion_id" = "suggestion_id_v"
jbe@0 2045 AND "snapshot"."issue_id" = "issue_id_p"
jbe@0 2046 AND "opinion"."degree" = 2
jbe@0 2047 AND "opinion"."fulfilled" = TRUE
jbe@0 2048 )
jbe@0 2049 WHERE "suggestion"."id" = "suggestion_id_v";
jbe@0 2050 END LOOP;
jbe@0 2051 END LOOP;
jbe@0 2052 RETURN;
jbe@0 2053 END;
jbe@0 2054 $$;
jbe@0 2055
jbe@0 2056 COMMENT ON FUNCTION "create_snapshot"
jbe@0 2057 ( "issue"."id"%TYPE )
jbe@0 2058 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 2059
jbe@0 2060
jbe@0 2061 CREATE FUNCTION "set_snapshot_event"
jbe@0 2062 ( "issue_id_p" "issue"."id"%TYPE,
jbe@0 2063 "event_p" "snapshot_event" )
jbe@0 2064 RETURNS VOID
jbe@0 2065 LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@0 2066 BEGIN
jbe@8 2067 UPDATE "issue" SET "latest_snapshot_event" = "event_p"
jbe@8 2068 WHERE "id" = "issue_id_p";
jbe@3 2069 UPDATE "direct_population_snapshot" SET "event" = "event_p"
jbe@0 2070 WHERE "issue_id" = "issue_id_p" AND "event" = 'periodic';
jbe@3 2071 UPDATE "delegating_population_snapshot" SET "event" = "event_p"
jbe@0 2072 WHERE "issue_id" = "issue_id_p" AND "event" = 'periodic';
jbe@3 2073 UPDATE "direct_interest_snapshot" SET "event" = "event_p"
jbe@0 2074 WHERE "issue_id" = "issue_id_p" AND "event" = 'periodic';
jbe@3 2075 UPDATE "delegating_interest_snapshot" SET "event" = "event_p"
jbe@0 2076 WHERE "issue_id" = "issue_id_p" AND "event" = 'periodic';
jbe@3 2077 UPDATE "direct_supporter_snapshot" SET "event" = "event_p"
jbe@0 2078 WHERE "issue_id" = "issue_id_p" AND "event" = 'periodic';
jbe@0 2079 RETURN;
jbe@0 2080 END;
jbe@0 2081 $$;
jbe@0 2082
jbe@0 2083 COMMENT ON FUNCTION "set_snapshot_event"
jbe@0 2084 ( "issue"."id"%TYPE,
jbe@0 2085 "snapshot_event" )
jbe@0 2086 IS 'Change "event" attribute of the previous ''periodic'' snapshot';
jbe@0 2087
jbe@0 2088
jbe@0 2089
jbe@0 2090 ---------------------
jbe@0 2091 -- Freezing issues --
jbe@0 2092 ---------------------
jbe@0 2093
jbe@0 2094 CREATE FUNCTION "freeze_after_snapshot"
jbe@0 2095 ( "issue_id_p" "issue"."id"%TYPE )
jbe@0 2096 RETURNS VOID
jbe@0 2097 LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@0 2098 DECLARE
jbe@0 2099 "issue_row" "issue"%ROWTYPE;
jbe@0 2100 "policy_row" "policy"%ROWTYPE;
jbe@0 2101 "initiative_row" "initiative"%ROWTYPE;
jbe@0 2102 BEGIN
jbe@0 2103 SELECT * INTO "issue_row" FROM "issue" WHERE "id" = "issue_id_p";
jbe@0 2104 SELECT * INTO "policy_row"
jbe@0 2105 FROM "policy" WHERE "id" = "issue_row"."policy_id";
jbe@3 2106 PERFORM "set_snapshot_event"("issue_id_p", 'start_of_voting');
jbe@3 2107 UPDATE "issue" SET
jbe@4 2108 "accepted" = coalesce("accepted", now()),
jbe@4 2109 "half_frozen" = coalesce("half_frozen", now()),
jbe@3 2110 "fully_frozen" = now()
jbe@3 2111 WHERE "id" = "issue_id_p";
jbe@0 2112 FOR "initiative_row" IN
jbe@0 2113 SELECT * FROM "initiative" WHERE "issue_id" = "issue_id_p"
jbe@0 2114 LOOP
jbe@0 2115 IF
jbe@0 2116 "initiative_row"."satisfied_supporter_count" > 0 AND
jbe@0 2117 "initiative_row"."satisfied_supporter_count" *
jbe@0 2118 "policy_row"."initiative_quorum_den" >=
jbe@0 2119 "issue_row"."population" * "policy_row"."initiative_quorum_num"
jbe@0 2120 THEN
jbe@0 2121 UPDATE "initiative" SET "admitted" = TRUE
jbe@0 2122 WHERE "id" = "initiative_row"."id";
jbe@0 2123 ELSE
jbe@0 2124 UPDATE "initiative" SET "admitted" = FALSE
jbe@0 2125 WHERE "id" = "initiative_row"."id";
jbe@0 2126 END IF;
jbe@0 2127 END LOOP;
jbe@9 2128 IF NOT EXISTS (
jbe@9 2129 SELECT NULL FROM "initiative"
jbe@9 2130 WHERE "issue_id" = "issue_id_p" AND "admitted" = TRUE
jbe@9 2131 ) THEN
jbe@9 2132 PERFORM "close_voting"("issue_id_p");
jbe@9 2133 END IF;
jbe@0 2134 RETURN;
jbe@0 2135 END;
jbe@0 2136 $$;
jbe@0 2137
jbe@0 2138 COMMENT ON FUNCTION "freeze_after_snapshot"
jbe@0 2139 ( "issue"."id"%TYPE )
jbe@9 2140 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 2141
jbe@0 2142
jbe@0 2143 CREATE FUNCTION "manual_freeze"("issue_id_p" "issue"."id"%TYPE)
jbe@0 2144 RETURNS VOID
jbe@0 2145 LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@0 2146 DECLARE
jbe@0 2147 "issue_row" "issue"%ROWTYPE;
jbe@0 2148 BEGIN
jbe@0 2149 PERFORM "create_snapshot"("issue_id_p");
jbe@0 2150 PERFORM "freeze_after_snapshot"("issue_id_p");
jbe@0 2151 RETURN;
jbe@0 2152 END;
jbe@0 2153 $$;
jbe@0 2154
jbe@0 2155 COMMENT ON FUNCTION "freeze_after_snapshot"
jbe@0 2156 ( "issue"."id"%TYPE )
jbe@3 2157 IS 'Freeze an issue manually (fully) and start voting';
jbe@0 2158
jbe@0 2159
jbe@0 2160
jbe@0 2161 -----------------------
jbe@0 2162 -- Counting of votes --
jbe@0 2163 -----------------------
jbe@0 2164
jbe@0 2165
jbe@5 2166 CREATE FUNCTION "weight_of_added_vote_delegations"
jbe@0 2167 ( "issue_id_p" "issue"."id"%TYPE,
jbe@0 2168 "member_id_p" "member"."id"%TYPE,
jbe@0 2169 "delegate_member_ids_p" "delegating_voter"."delegate_member_ids"%TYPE )
jbe@0 2170 RETURNS "direct_voter"."weight"%TYPE
jbe@0 2171 LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@0 2172 DECLARE
jbe@0 2173 "issue_delegation_row" "issue_delegation"%ROWTYPE;
jbe@0 2174 "delegate_member_ids_v" "delegating_voter"."delegate_member_ids"%TYPE;
jbe@0 2175 "weight_v" INT4;
jbe@8 2176 "sub_weight_v" INT4;
jbe@0 2177 BEGIN
jbe@0 2178 "weight_v" := 0;
jbe@0 2179 FOR "issue_delegation_row" IN
jbe@0 2180 SELECT * FROM "issue_delegation"
jbe@0 2181 WHERE "trustee_id" = "member_id_p"
jbe@0 2182 AND "issue_id" = "issue_id_p"
jbe@0 2183 LOOP
jbe@0 2184 IF NOT EXISTS (
jbe@0 2185 SELECT NULL FROM "direct_voter"
jbe@0 2186 WHERE "member_id" = "issue_delegation_row"."truster_id"
jbe@0 2187 AND "issue_id" = "issue_id_p"
jbe@0 2188 ) AND NOT EXISTS (
jbe@0 2189 SELECT NULL FROM "delegating_voter"
jbe@0 2190 WHERE "member_id" = "issue_delegation_row"."truster_id"
jbe@0 2191 AND "issue_id" = "issue_id_p"
jbe@0 2192 ) THEN
jbe@0 2193 "delegate_member_ids_v" :=
jbe@0 2194 "member_id_p" || "delegate_member_ids_p";
jbe@10 2195 INSERT INTO "delegating_voter" (
jbe@10 2196 "issue_id",
jbe@10 2197 "member_id",
jbe@10 2198 "scope",
jbe@10 2199 "delegate_member_ids"
jbe@10 2200 ) VALUES (
jbe@5 2201 "issue_id_p",
jbe@5 2202 "issue_delegation_row"."truster_id",
jbe@10 2203 "issue_delegation_row"."scope",
jbe@5 2204 "delegate_member_ids_v"
jbe@5 2205 );
jbe@8 2206 "sub_weight_v" := 1 +
jbe@8 2207 "weight_of_added_vote_delegations"(
jbe@8 2208 "issue_id_p",
jbe@8 2209 "issue_delegation_row"."truster_id",
jbe@8 2210 "delegate_member_ids_v"
jbe@8 2211 );
jbe@8 2212 UPDATE "delegating_voter"
jbe@8 2213 SET "weight" = "sub_weight_v"
jbe@8 2214 WHERE "issue_id" = "issue_id_p"
jbe@8 2215 AND "member_id" = "issue_delegation_row"."truster_id";
jbe@8 2216 "weight_v" := "weight_v" + "sub_weight_v";
jbe@0 2217 END IF;
jbe@0 2218 END LOOP;
jbe@0 2219 RETURN "weight_v";
jbe@0 2220 END;
jbe@0 2221 $$;
jbe@0 2222
jbe@5 2223 COMMENT ON FUNCTION "weight_of_added_vote_delegations"
jbe@0 2224 ( "issue"."id"%TYPE,
jbe@0 2225 "member"."id"%TYPE,
jbe@0 2226 "delegating_voter"."delegate_member_ids"%TYPE )
jbe@0 2227 IS 'Helper function for "add_vote_delegations" function';
jbe@0 2228
jbe@0 2229
jbe@0 2230 CREATE FUNCTION "add_vote_delegations"
jbe@0 2231 ( "issue_id_p" "issue"."id"%TYPE )
jbe@0 2232 RETURNS VOID
jbe@0 2233 LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@0 2234 DECLARE
jbe@0 2235 "member_id_v" "member"."id"%TYPE;
jbe@0 2236 BEGIN
jbe@0 2237 FOR "member_id_v" IN
jbe@0 2238 SELECT "member_id" FROM "direct_voter"
jbe@0 2239 WHERE "issue_id" = "issue_id_p"
jbe@0 2240 LOOP
jbe@0 2241 UPDATE "direct_voter" SET
jbe@5 2242 "weight" = "weight" + "weight_of_added_vote_delegations"(
jbe@0 2243 "issue_id_p",
jbe@0 2244 "member_id_v",
jbe@0 2245 '{}'
jbe@0 2246 )
jbe@0 2247 WHERE "member_id" = "member_id_v"
jbe@0 2248 AND "issue_id" = "issue_id_p";
jbe@0 2249 END LOOP;
jbe@0 2250 RETURN;
jbe@0 2251 END;
jbe@0 2252 $$;
jbe@0 2253
jbe@0 2254 COMMENT ON FUNCTION "add_vote_delegations"
jbe@0 2255 ( "issue_id_p" "issue"."id"%TYPE )
jbe@0 2256 IS 'Helper function for "close_voting" function';
jbe@0 2257
jbe@0 2258
jbe@0 2259 CREATE FUNCTION "close_voting"("issue_id_p" "issue"."id"%TYPE)
jbe@0 2260 RETURNS VOID
jbe@0 2261 LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@0 2262 DECLARE
jbe@0 2263 "issue_row" "issue"%ROWTYPE;
jbe@0 2264 "member_id_v" "member"."id"%TYPE;
jbe@0 2265 BEGIN
jbe@0 2266 PERFORM "global_lock"();
jbe@0 2267 SELECT * INTO "issue_row" FROM "issue" WHERE "id" = "issue_id_p";
jbe@0 2268 DELETE FROM "delegating_voter"
jbe@0 2269 WHERE "issue_id" = "issue_id_p";
jbe@0 2270 DELETE FROM "direct_voter"
jbe@0 2271 WHERE "issue_id" = "issue_id_p"
jbe@0 2272 AND "autoreject" = TRUE;
jbe@0 2273 DELETE FROM "direct_voter" USING "member"
jbe@0 2274 WHERE "direct_voter"."member_id" = "member"."id"
jbe@0 2275 AND "direct_voter"."issue_id" = "issue_id_p"
jbe@0 2276 AND "member"."active" = FALSE;
jbe@0 2277 UPDATE "direct_voter" SET "weight" = 1
jbe@0 2278 WHERE "issue_id" = "issue_id_p";
jbe@0 2279 PERFORM "add_vote_delegations"("issue_id_p");
jbe@0 2280 FOR "member_id_v" IN
jbe@0 2281 SELECT "interest"."member_id"
jbe@0 2282 FROM "interest"
jbe@0 2283 LEFT JOIN "direct_voter"
jbe@0 2284 ON "interest"."member_id" = "direct_voter"."member_id"
jbe@0 2285 AND "interest"."issue_id" = "direct_voter"."issue_id"
jbe@0 2286 LEFT JOIN "delegating_voter"
jbe@0 2287 ON "interest"."member_id" = "delegating_voter"."member_id"
jbe@0 2288 AND "interest"."issue_id" = "delegating_voter"."issue_id"
jbe@0 2289 WHERE "interest"."issue_id" = "issue_id_p"
jbe@0 2290 AND "interest"."autoreject" = TRUE
jbe@0 2291 AND "direct_voter"."member_id" ISNULL
jbe@0 2292 AND "delegating_voter"."member_id" ISNULL
jbe@0 2293 UNION SELECT "membership"."member_id"
jbe@0 2294 FROM "membership"
jbe@0 2295 LEFT JOIN "interest"
jbe@0 2296 ON "membership"."member_id" = "interest"."member_id"
jbe@0 2297 AND "interest"."issue_id" = "issue_id_p"
jbe@0 2298 LEFT JOIN "direct_voter"
jbe@0 2299 ON "membership"."member_id" = "direct_voter"."member_id"
jbe@0 2300 AND "direct_voter"."issue_id" = "issue_id_p"
jbe@0 2301 LEFT JOIN "delegating_voter"
jbe@0 2302 ON "membership"."member_id" = "delegating_voter"."member_id"
jbe@0 2303 AND "delegating_voter"."issue_id" = "issue_id_p"
jbe@0 2304 WHERE "membership"."area_id" = "issue_row"."area_id"
jbe@0 2305 AND "membership"."autoreject" = TRUE
jbe@0 2306 AND "interest"."autoreject" ISNULL
jbe@0 2307 AND "direct_voter"."member_id" ISNULL
jbe@0 2308 AND "delegating_voter"."member_id" ISNULL
jbe@0 2309 LOOP
jbe@11 2310 INSERT INTO "direct_voter"
jbe@11 2311 ("member_id", "issue_id", "weight", "autoreject") VALUES
jbe@11 2312 ("member_id_v", "issue_id_p", 1, TRUE);
jbe@0 2313 INSERT INTO "vote" (
jbe@0 2314 "member_id",
jbe@0 2315 "issue_id",
jbe@0 2316 "initiative_id",
jbe@0 2317 "grade"
jbe@0 2318 ) SELECT
jbe@0 2319 "member_id_v" AS "member_id",
jbe@0 2320 "issue_id_p" AS "issue_id",
jbe@0 2321 "id" AS "initiative_id",
jbe@0 2322 -1 AS "grade"
jbe@0 2323 FROM "initiative" WHERE "issue_id" = "issue_id_p";
jbe@0 2324 END LOOP;
jbe@0 2325 PERFORM "add_vote_delegations"("issue_id_p");
jbe@4 2326 UPDATE "issue" SET
jbe@4 2327 "voter_count" = (
jbe@4 2328 SELECT coalesce(sum("weight"), 0)
jbe@4 2329 FROM "direct_voter" WHERE "issue_id" = "issue_id_p"
jbe@6 2330 )
jbe@6 2331 WHERE "id" = "issue_id_p";
jbe@0 2332 UPDATE "initiative" SET
jbe@10 2333 "positive_votes" = "vote_counts"."positive_votes",
jbe@10 2334 "negative_votes" = "vote_counts"."negative_votes",
jbe@10 2335 "agreed" = CASE WHEN "majority_strict" THEN
jbe@10 2336 "vote_counts"."positive_votes" * "majority_den" >
jbe@10 2337 "majority_num" *
jbe@10 2338 ("vote_counts"."positive_votes"+"vote_counts"."negative_votes")
jbe@10 2339 ELSE
jbe@10 2340 "vote_counts"."positive_votes" * "majority_den" >=
jbe@10 2341 "majority_num" *
jbe@10 2342 ("vote_counts"."positive_votes"+"vote_counts"."negative_votes")
jbe@10 2343 END
jbe@10 2344 FROM
jbe@10 2345 ( SELECT
jbe@10 2346 "initiative"."id" AS "initiative_id",
jbe@10 2347 coalesce(
jbe@10 2348 sum(
jbe@10 2349 CASE WHEN "grade" > 0 THEN "direct_voter"."weight" ELSE 0 END
jbe@10 2350 ),
jbe@10 2351 0
jbe@10 2352 ) AS "positive_votes",
jbe@10 2353 coalesce(
jbe@10 2354 sum(
jbe@10 2355 CASE WHEN "grade" < 0 THEN "direct_voter"."weight" ELSE 0 END
jbe@10 2356 ),
jbe@10 2357 0
jbe@10 2358 ) AS "negative_votes"
jbe@10 2359 FROM "initiative"
jbe@10 2360 JOIN "issue" ON "initiative"."issue_id" = "issue"."id"
jbe@10 2361 JOIN "policy" ON "issue"."policy_id" = "policy"."id"
jbe@10 2362 LEFT JOIN "direct_voter"
jbe@10 2363 ON "direct_voter"."issue_id" = "initiative"."issue_id"
jbe@10 2364 LEFT JOIN "vote"
jbe@10 2365 ON "vote"."initiative_id" = "initiative"."id"
jbe@10 2366 AND "vote"."member_id" = "direct_voter"."member_id"
jbe@10 2367 WHERE "initiative"."issue_id" = "issue_id_p"
jbe@10 2368 AND "initiative"."admitted"
jbe@10 2369 GROUP BY "initiative"."id"
jbe@10 2370 ) AS "vote_counts",
jbe@10 2371 "issue",
jbe@10 2372 "policy"
jbe@10 2373 WHERE "vote_counts"."initiative_id" = "initiative"."id"
jbe@10 2374 AND "issue"."id" = "initiative"."issue_id"
jbe@10 2375 AND "policy"."id" = "issue"."policy_id";
jbe@0 2376 UPDATE "issue" SET "closed" = now() WHERE "id" = "issue_id_p";
jbe@0 2377 END;
jbe@0 2378 $$;
jbe@0 2379
jbe@0 2380 COMMENT ON FUNCTION "close_voting"
jbe@0 2381 ( "issue"."id"%TYPE )
jbe@0 2382 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 2383
jbe@0 2384
jbe@0 2385 CREATE FUNCTION "init_array"("dim_p" INTEGER)
jbe@0 2386 RETURNS INT4[]
jbe@0 2387 LANGUAGE 'plpgsql' IMMUTABLE AS $$
jbe@0 2388 DECLARE
jbe@0 2389 "i" INTEGER;
jbe@0 2390 "ary_text_v" TEXT;
jbe@0 2391 BEGIN
jbe@0 2392 IF "dim_p" >= 1 THEN
jbe@0 2393 "ary_text_v" := '{NULL';
jbe@0 2394 "i" := "dim_p";
jbe@0 2395 LOOP
jbe@0 2396 "i" := "i" - 1;
jbe@0 2397 EXIT WHEN "i" = 0;
jbe@0 2398 "ary_text_v" := "ary_text_v" || ',NULL';
jbe@0 2399 END LOOP;
jbe@0 2400 "ary_text_v" := "ary_text_v" || '}';
jbe@0 2401 RETURN "ary_text_v"::INT4[][];
jbe@0 2402 ELSE
jbe@0 2403 RAISE EXCEPTION 'Dimension needs to be at least 1.';
jbe@0 2404 END IF;
jbe@0 2405 END;
jbe@0 2406 $$;
jbe@0 2407
jbe@0 2408 COMMENT ON FUNCTION "init_array"(INTEGER) IS 'Needed for PostgreSQL < 8.4, due to missing "array_fill" function';
jbe@0 2409
jbe@0 2410
jbe@0 2411 CREATE FUNCTION "init_square_matrix"("dim_p" INTEGER)
jbe@0 2412 RETURNS INT4[][]
jbe@0 2413 LANGUAGE 'plpgsql' IMMUTABLE AS $$
jbe@0 2414 DECLARE
jbe@0 2415 "i" INTEGER;
jbe@0 2416 "row_text_v" TEXT;
jbe@0 2417 "ary_text_v" TEXT;
jbe@0 2418 BEGIN
jbe@0 2419 IF "dim_p" >= 1 THEN
jbe@0 2420 "row_text_v" := '{NULL';
jbe@0 2421 "i" := "dim_p";
jbe@0 2422 LOOP
jbe@0 2423 "i" := "i" - 1;
jbe@0 2424 EXIT WHEN "i" = 0;
jbe@0 2425 "row_text_v" := "row_text_v" || ',NULL';
jbe@0 2426 END LOOP;
jbe@0 2427 "row_text_v" := "row_text_v" || '}';
jbe@0 2428 "ary_text_v" := '{' || "row_text_v";
jbe@0 2429 "i" := "dim_p";
jbe@0 2430 LOOP
jbe@0 2431 "i" := "i" - 1;
jbe@0 2432 EXIT WHEN "i" = 0;
jbe@0 2433 "ary_text_v" := "ary_text_v" || ',' || "row_text_v";
jbe@0 2434 END LOOP;
jbe@0 2435 "ary_text_v" := "ary_text_v" || '}';
jbe@0 2436 RETURN "ary_text_v"::INT4[][];
jbe@0 2437 ELSE
jbe@0 2438 RAISE EXCEPTION 'Dimension needs to be at least 1.';
jbe@0 2439 END IF;
jbe@0 2440 END;
jbe@0 2441 $$;
jbe@0 2442
jbe@0 2443 COMMENT ON FUNCTION "init_square_matrix"(INTEGER) IS 'Needed for PostgreSQL < 8.4, due to missing "array_fill" function';
jbe@0 2444
jbe@0 2445
jbe@0 2446 CREATE FUNCTION "calculate_ranks"("issue_id_p" "issue"."id"%TYPE)
jbe@0 2447 RETURNS VOID
jbe@0 2448 LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@0 2449 DECLARE
jbe@0 2450 "dimension_v" INTEGER;
jbe@0 2451 "matrix" INT4[][];
jbe@0 2452 "i" INTEGER;
jbe@0 2453 "j" INTEGER;
jbe@0 2454 "k" INTEGER;
jbe@0 2455 "battle_row" "battle"%ROWTYPE;
jbe@0 2456 "rank_ary" INT4[];
jbe@0 2457 "rank_v" INT4;
jbe@0 2458 "done_v" INTEGER;
jbe@0 2459 "winners_ary" INTEGER[];
jbe@0 2460 "initiative_id_v" "initiative"."id"%TYPE;
jbe@0 2461 BEGIN
jbe@0 2462 PERFORM NULL FROM "issue" WHERE "id" = "issue_id_p" FOR UPDATE;
jbe@0 2463 -- Prepare matrix for Schulze-Method:
jbe@10 2464 SELECT count(1) INTO "dimension_v" FROM "initiative"
jbe@10 2465 WHERE "issue_id" = "issue_id_p" AND "agreed";
jbe@0 2466 IF "dimension_v" = 1 THEN
jbe@10 2467 UPDATE "initiative" SET "rank" = 1
jbe@10 2468 WHERE "issue_id" = "issue_id_p" AND "agreed";
jbe@0 2469 ELSIF "dimension_v" > 1 THEN
jbe@0 2470 "matrix" := "init_square_matrix"("dimension_v"); -- TODO: replace by "array_fill" function (PostgreSQL 8.4)
jbe@0 2471 "i" := 1;
jbe@0 2472 "j" := 2;
jbe@0 2473 -- Fill matrix with data from "battle" view
jbe@0 2474 FOR "battle_row" IN
jbe@0 2475 SELECT * FROM "battle" WHERE "issue_id" = "issue_id_p"
jbe@0 2476 ORDER BY "winning_initiative_id", "losing_initiative_id"
jbe@0 2477 LOOP
jbe@0 2478 "matrix"["i"]["j"] := "battle_row"."count";
jbe@0 2479 IF "j" = "dimension_v" THEN
jbe@0 2480 "i" := "i" + 1;
jbe@0 2481 "j" := 1;
jbe@0 2482 ELSE
jbe@0 2483 "j" := "j" + 1;
jbe@0 2484 IF "j" = "i" THEN
jbe@0 2485 "j" := "j" + 1;
jbe@0 2486 END IF;
jbe@0 2487 END IF;
jbe@0 2488 END LOOP;
jbe@0 2489 IF "i" != "dimension_v" OR "j" != "dimension_v" + 1 THEN
jbe@0 2490 RAISE EXCEPTION 'Wrong battle count (should not happen)';
jbe@0 2491 END IF;
jbe@0 2492 -- Delete losers from matrix:
jbe@0 2493 "i" := 1;
jbe@0 2494 LOOP
jbe@0 2495 "j" := "i" + 1;
jbe@0 2496 LOOP
jbe@0 2497 IF "i" != "j" THEN
jbe@0 2498 IF "matrix"["i"]["j"] < "matrix"["j"]["i"] THEN
jbe@0 2499 "matrix"["i"]["j"] := 0;
jbe@0 2500 ELSIF matrix[j][i] < matrix[i][j] THEN
jbe@0 2501 "matrix"["j"]["i"] := 0;
jbe@0 2502 ELSE
jbe@0 2503 "matrix"["i"]["j"] := 0;
jbe@0 2504 "matrix"["j"]["i"] := 0;
jbe@0 2505 END IF;
jbe@0 2506 END IF;
jbe@0 2507 EXIT WHEN "j" = "dimension_v";
jbe@0 2508 "j" := "j" + 1;
jbe@0 2509 END LOOP;
jbe@0 2510 EXIT WHEN "i" = "dimension_v" - 1;
jbe@0 2511 "i" := "i" + 1;
jbe@0 2512 END LOOP;
jbe@0 2513 -- Find best paths:
jbe@0 2514 "i" := 1;
jbe@0 2515 LOOP
jbe@0 2516 "j" := 1;
jbe@0 2517 LOOP
jbe@0 2518 IF "i" != "j" THEN
jbe@0 2519 "k" := 1;
jbe@0 2520 LOOP
jbe@0 2521 IF "i" != "k" AND "j" != "k" THEN
jbe@0 2522 IF "matrix"["j"]["i"] < "matrix"["i"]["k"] THEN
jbe@0 2523 IF "matrix"["j"]["i"] > "matrix"["j"]["k"] THEN
jbe@0 2524 "matrix"["j"]["k"] := "matrix"["j"]["i"];
jbe@0 2525 END IF;
jbe@0 2526 ELSE
jbe@0 2527 IF "matrix"["i"]["k"] > "matrix"["j"]["k"] THEN
jbe@0 2528 "matrix"["j"]["k"] := "matrix"["i"]["k"];
jbe@0 2529 END IF;
jbe@0 2530 END IF;
jbe@0 2531 END IF;
jbe@0 2532 EXIT WHEN "k" = "dimension_v";
jbe@0 2533 "k" := "k" + 1;
jbe@0 2534 END LOOP;
jbe@0 2535 END IF;
jbe@0 2536 EXIT WHEN "j" = "dimension_v";
jbe@0 2537 "j" := "j" + 1;
jbe@0 2538 END LOOP;
jbe@0 2539 EXIT WHEN "i" = "dimension_v";
jbe@0 2540 "i" := "i" + 1;
jbe@0 2541 END LOOP;
jbe@0 2542 -- Determine order of winners:
jbe@0 2543 "rank_ary" := "init_array"("dimension_v"); -- TODO: replace by "array_fill" function (PostgreSQL 8.4)
jbe@0 2544 "rank_v" := 1;
jbe@0 2545 "done_v" := 0;
jbe@0 2546 LOOP
jbe@0 2547 "winners_ary" := '{}';
jbe@0 2548 "i" := 1;
jbe@0 2549 LOOP
jbe@0 2550 IF "rank_ary"["i"] ISNULL THEN
jbe@0 2551 "j" := 1;
jbe@0 2552 LOOP
jbe@0 2553 IF
jbe@0 2554 "i" != "j" AND
jbe@0 2555 "rank_ary"["j"] ISNULL AND
jbe@0 2556 "matrix"["j"]["i"] > "matrix"["i"]["j"]
jbe@0 2557 THEN
jbe@0 2558 -- someone else is better
jbe@0 2559 EXIT;
jbe@0 2560 END IF;
jbe@0 2561 IF "j" = "dimension_v" THEN
jbe@0 2562 -- noone is better
jbe@0 2563 "winners_ary" := "winners_ary" || "i";
jbe@0 2564 EXIT;
jbe@0 2565 END IF;
jbe@0 2566 "j" := "j" + 1;
jbe@0 2567 END LOOP;
jbe@0 2568 END IF;
jbe@0 2569 EXIT WHEN "i" = "dimension_v";
jbe@0 2570 "i" := "i" + 1;
jbe@0 2571 END LOOP;
jbe@0 2572 "i" := 1;
jbe@0 2573 LOOP
jbe@0 2574 "rank_ary"["winners_ary"["i"]] := "rank_v";
jbe@0 2575 "done_v" := "done_v" + 1;
jbe@0 2576 EXIT WHEN "i" = array_upper("winners_ary", 1);
jbe@0 2577 "i" := "i" + 1;
jbe@0 2578 END LOOP;
jbe@0 2579 EXIT WHEN "done_v" = "dimension_v";
jbe@0 2580 "rank_v" := "rank_v" + 1;
jbe@0 2581 END LOOP;
jbe@0 2582 -- write preliminary ranks:
jbe@0 2583 "i" := 1;
jbe@0 2584 FOR "initiative_id_v" IN
jbe@10 2585 SELECT "id" FROM "initiative"
jbe@10 2586 WHERE "issue_id" = "issue_id_p" AND "agreed"
jbe@10 2587 ORDER BY "id"
jbe@0 2588 LOOP
jbe@0 2589 UPDATE "initiative" SET "rank" = "rank_ary"["i"]
jbe@0 2590 WHERE "id" = "initiative_id_v";
jbe@0 2591 "i" := "i" + 1;
jbe@0 2592 END LOOP;
jbe@0 2593 IF "i" != "dimension_v" + 1 THEN
jbe@0 2594 RAISE EXCEPTION 'Wrong winner count (should not happen)';
jbe@0 2595 END IF;
jbe@0 2596 -- straighten ranks (start counting with 1, no equal ranks):
jbe@0 2597 "rank_v" := 1;
jbe@0 2598 FOR "initiative_id_v" IN
jbe@0 2599 SELECT "id" FROM "initiative"
jbe@0 2600 WHERE "issue_id" = "issue_id_p" AND "rank" NOTNULL
jbe@0 2601 ORDER BY
jbe@0 2602 "rank",
jbe@0 2603 "vote_ratio"("positive_votes", "negative_votes") DESC,
jbe@0 2604 "id"
jbe@0 2605 LOOP
jbe@0 2606 UPDATE "initiative" SET "rank" = "rank_v"
jbe@0 2607 WHERE "id" = "initiative_id_v";
jbe@0 2608 "rank_v" := "rank_v" + 1;
jbe@0 2609 END LOOP;
jbe@0 2610 END IF;
jbe@0 2611 -- mark issue as finished
jbe@0 2612 UPDATE "issue" SET "ranks_available" = TRUE
jbe@0 2613 WHERE "id" = "issue_id_p";
jbe@0 2614 RETURN;
jbe@0 2615 END;
jbe@0 2616 $$;
jbe@0 2617
jbe@0 2618 COMMENT ON FUNCTION "calculate_ranks"
jbe@0 2619 ( "issue"."id"%TYPE )
jbe@0 2620 IS 'Determine ranking (Votes have to be counted first)';
jbe@0 2621
jbe@0 2622
jbe@0 2623
jbe@0 2624 -----------------------------
jbe@0 2625 -- Automatic state changes --
jbe@0 2626 -----------------------------
jbe@0 2627
jbe@0 2628
jbe@0 2629 CREATE FUNCTION "check_issue"
jbe@0 2630 ( "issue_id_p" "issue"."id"%TYPE )
jbe@0 2631 RETURNS VOID
jbe@0 2632 LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@0 2633 DECLARE
jbe@0 2634 "issue_row" "issue"%ROWTYPE;
jbe@0 2635 "policy_row" "policy"%ROWTYPE;
jbe@0 2636 "voting_requested_v" BOOLEAN;
jbe@0 2637 BEGIN
jbe@0 2638 PERFORM "global_lock"();
jbe@0 2639 SELECT * INTO "issue_row" FROM "issue" WHERE "id" = "issue_id_p";
jbe@0 2640 IF "issue_row"."closed" ISNULL THEN
jbe@0 2641 SELECT * INTO "policy_row" FROM "policy"
jbe@0 2642 WHERE "id" = "issue_row"."policy_id";
jbe@3 2643 IF "issue_row"."fully_frozen" ISNULL THEN
jbe@0 2644 PERFORM "create_snapshot"("issue_id_p");
jbe@0 2645 SELECT * INTO "issue_row" FROM "issue" WHERE "id" = "issue_id_p";
jbe@0 2646 END IF;
jbe@0 2647 IF "issue_row"."accepted" ISNULL THEN
jbe@0 2648 IF EXISTS (
jbe@0 2649 SELECT NULL FROM "initiative"
jbe@0 2650 WHERE "issue_id" = "issue_id_p"
jbe@0 2651 AND "supporter_count" > 0
jbe@0 2652 AND "supporter_count" * "policy_row"."issue_quorum_den"
jbe@0 2653 >= "issue_row"."population" * "policy_row"."issue_quorum_num"
jbe@0 2654 ) THEN
jbe@3 2655 PERFORM "set_snapshot_event"("issue_id_p", 'end_of_admission');
jbe@0 2656 "issue_row"."accepted" = now(); -- NOTE: "issue_row" used later
jbe@0 2657 UPDATE "issue" SET "accepted" = "issue_row"."accepted"
jbe@0 2658 WHERE "id" = "issue_row"."id";
jbe@0 2659 ELSIF
jbe@3 2660 now() >= "issue_row"."created" + "policy_row"."admission_time"
jbe@0 2661 THEN
jbe@0 2662 PERFORM "set_snapshot_event"("issue_id_p", 'end_of_admission');
jbe@0 2663 UPDATE "issue" SET "closed" = now()
jbe@0 2664 WHERE "id" = "issue_row"."id";
jbe@0 2665 END IF;
jbe@0 2666 END IF;
jbe@0 2667 IF
jbe@0 2668 "issue_row"."accepted" NOTNULL AND
jbe@3 2669 "issue_row"."half_frozen" ISNULL
jbe@0 2670 THEN
jbe@0 2671 SELECT
jbe@0 2672 CASE
jbe@0 2673 WHEN "vote_now" * 2 > "issue_row"."population" THEN
jbe@0 2674 TRUE
jbe@0 2675 WHEN "vote_later" * 2 > "issue_row"."population" THEN
jbe@0 2676 FALSE
jbe@0 2677 ELSE NULL
jbe@0 2678 END
jbe@0 2679 INTO "voting_requested_v"
jbe@0 2680 FROM "issue" WHERE "id" = "issue_id_p";
jbe@0 2681 IF
jbe@0 2682 "voting_requested_v" OR (
jbe@3 2683 "voting_requested_v" ISNULL AND
jbe@3 2684 now() >= "issue_row"."accepted" + "policy_row"."discussion_time"
jbe@0 2685 )
jbe@0 2686 THEN
jbe@3 2687 "issue_row"."half_frozen" = now(); -- NOTE: "issue_row" used later
jbe@3 2688 UPDATE "issue" SET "half_frozen" = "issue_row"."half_frozen"
jbe@3 2689 WHERE "id" = "issue_row"."id";
jbe@0 2690 END IF;
jbe@0 2691 END IF;
jbe@0 2692 IF
jbe@3 2693 "issue_row"."half_frozen" NOTNULL AND
jbe@3 2694 "issue_row"."fully_frozen" ISNULL AND
jbe@3 2695 now() >= "issue_row"."half_frozen" + "policy_row"."verification_time"
jbe@3 2696 THEN
jbe@3 2697 PERFORM "freeze_after_snapshot"("issue_id_p");
jbe@9 2698 -- "issue" might change, thus "issue_row" has to be updated below
jbe@3 2699 END IF;
jbe@9 2700 SELECT * INTO "issue_row" FROM "issue" WHERE "id" = "issue_id_p";
jbe@3 2701 IF
jbe@9 2702 "issue_row"."closed" ISNULL AND
jbe@3 2703 "issue_row"."fully_frozen" NOTNULL AND
jbe@3 2704 now() >= "issue_row"."fully_frozen" + "policy_row"."voting_time"
jbe@0 2705 THEN
jbe@0 2706 PERFORM "close_voting"("issue_id_p");
jbe@0 2707 END IF;
jbe@0 2708 END IF;
jbe@0 2709 RETURN;
jbe@0 2710 END;
jbe@0 2711 $$;
jbe@0 2712
jbe@0 2713 COMMENT ON FUNCTION "check_issue"
jbe@0 2714 ( "issue"."id"%TYPE )
jbe@0 2715 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 2716
jbe@0 2717
jbe@0 2718 CREATE FUNCTION "check_everything"()
jbe@0 2719 RETURNS VOID
jbe@0 2720 LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@0 2721 DECLARE
jbe@0 2722 "issue_id_v" "issue"."id"%TYPE;
jbe@0 2723 BEGIN
jbe@1 2724 DELETE FROM "expired_session";
jbe@4 2725 PERFORM "calculate_member_counts"();
jbe@4 2726 FOR "issue_id_v" IN SELECT "id" FROM "open_issue" LOOP
jbe@0 2727 PERFORM "check_issue"("issue_id_v");
jbe@0 2728 END LOOP;
jbe@4 2729 FOR "issue_id_v" IN SELECT "id" FROM "issue_with_ranks_missing" LOOP
jbe@0 2730 PERFORM "calculate_ranks"("issue_id_v");
jbe@0 2731 END LOOP;
jbe@0 2732 RETURN;
jbe@0 2733 END;
jbe@0 2734 $$;
jbe@0 2735
jbe@0 2736 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 2737
jbe@0 2738
jbe@0 2739
jbe@8 2740 ------------------------------
jbe@8 2741 -- Deletion of private data --
jbe@8 2742 ------------------------------
jbe@8 2743
jbe@8 2744
jbe@8 2745 CREATE FUNCTION "delete_private_data"()
jbe@8 2746 RETURNS VOID
jbe@8 2747 LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@8 2748 DECLARE
jbe@8 2749 "issue_id_v" "issue"."id"%TYPE;
jbe@8 2750 BEGIN
jbe@9 2751 UPDATE "member" SET
jbe@11 2752 "login" = 'login' || "id"::text,
jbe@11 2753 "password" = NULL,
jbe@11 2754 "notify_email" = NULL,
jbe@11 2755 "notify_email_unconfirmed" = NULL,
jbe@11 2756 "notify_email_secret" = NULL,
jbe@11 2757 "notify_email_secret_expiry" = NULL,
jbe@11 2758 "password_reset_secret" = NULL,
jbe@11 2759 "password_reset_secret_expiry" = NULL,
jbe@11 2760 "organizational_unit" = NULL,
jbe@11 2761 "internal_posts" = NULL,
jbe@11 2762 "realname" = NULL,
jbe@11 2763 "birthday" = NULL,
jbe@11 2764 "address" = NULL,
jbe@11 2765 "email" = NULL,
jbe@11 2766 "xmpp_address" = NULL,
jbe@11 2767 "website" = NULL,
jbe@11 2768 "phone" = NULL,
jbe@11 2769 "mobile_phone" = NULL,
jbe@11 2770 "profession" = NULL,
jbe@11 2771 "external_memberships" = NULL,
jbe@11 2772 "external_posts" = NULL,
jbe@11 2773 "statement" = NULL;
jbe@11 2774 -- "text_search_data" is updated by triggers
jbe@8 2775 DELETE FROM "session";
jbe@10 2776 DELETE FROM "invite_code" WHERE "used" ISNULL;
jbe@8 2777 DELETE FROM "contact" WHERE NOT "public";
jbe@11 2778 DELETE FROM "setting";
jbe@11 2779 DELETE FROM "member_image";
jbe@8 2780 DELETE FROM "direct_voter" USING "issue"
jbe@8 2781 WHERE "direct_voter"."issue_id" = "issue"."id"
jbe@8 2782 AND "issue"."closed" ISNULL;
jbe@8 2783 RETURN;
jbe@8 2784 END;
jbe@8 2785 $$;
jbe@8 2786
jbe@8 2787 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 2788
jbe@8 2789
jbe@8 2790
jbe@0 2791 COMMIT;

Impressum / About Us