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