liquid_feedback_core

annotate core.sql @ 9:4af4df1415f9

Version beta10

Voting will be skipped, if no initiative is admitted for voting

Invite code feature allows people having an invite code to create one account

Contingent system to limit the number of initiatives or text entries to be submitted by each member within a given time

Ability to store a formatting engine for each draft, which can be used to allow initiatives to choose between available wiki parsers

New table setting storing user settings for the frontend (replaced hidden_hints column of beta9)

Better policy support:
- New table allowed_policy to select which policies can be used in each area
- Policies are now ordered by an index field

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

Impressum / About Us