liquid_feedback_core

view update/core-update.v3.2.2-v4.0.0.sql @ 551:deabd90adae8

Renamed "region" (for "unit" and "area") to "location"
author jbe
date Sat Sep 16 15:53:31 2017 +0200 (2017-09-16)
parents 81a35235b450
children a676d305502f
line source
1 ALTER TYPE "event_type" ADD VALUE IF NOT EXISTS 'unit_created';
2 ALTER TYPE "event_type" ADD VALUE IF NOT EXISTS 'unit_updated';
3 ALTER TYPE "event_type" ADD VALUE IF NOT EXISTS 'area_created';
4 ALTER TYPE "event_type" ADD VALUE IF NOT EXISTS 'area_updated';
5 ALTER TYPE "event_type" ADD VALUE IF NOT EXISTS 'policy_created';
6 ALTER TYPE "event_type" ADD VALUE IF NOT EXISTS 'policy_updated';
7 ALTER TYPE "event_type" ADD VALUE IF NOT EXISTS 'suggestion_removed';
8 ALTER TYPE "event_type" ADD VALUE IF NOT EXISTS 'member_activated';
9 ALTER TYPE "event_type" ADD VALUE IF NOT EXISTS 'member_removed';
10 ALTER TYPE "event_type" ADD VALUE IF NOT EXISTS 'member_active';
11 ALTER TYPE "event_type" ADD VALUE IF NOT EXISTS 'member_name_updated';
12 ALTER TYPE "event_type" ADD VALUE IF NOT EXISTS 'member_profile_updated';
13 ALTER TYPE "event_type" ADD VALUE IF NOT EXISTS 'member_image_updated';
14 ALTER TYPE "event_type" ADD VALUE IF NOT EXISTS 'interest';
15 ALTER TYPE "event_type" ADD VALUE IF NOT EXISTS 'initiator';
16 ALTER TYPE "event_type" ADD VALUE IF NOT EXISTS 'support';
17 ALTER TYPE "event_type" ADD VALUE IF NOT EXISTS 'support_updated';
18 ALTER TYPE "event_type" ADD VALUE IF NOT EXISTS 'suggestion_rated';
19 ALTER TYPE "event_type" ADD VALUE IF NOT EXISTS 'delegation';
20 ALTER TYPE "event_type" ADD VALUE IF NOT EXISTS 'contact';
23 BEGIN;
26 CREATE OR REPLACE VIEW "liquid_feedback_version" AS
27 SELECT * FROM (VALUES ('4.0-dev', 4, 0, -1))
28 AS "subquery"("string", "major", "minor", "revision");
31 ALTER TABLE "system_setting" ADD COLUMN "snapshot_retention" INTERVAL;
33 COMMENT ON COLUMN "system_setting"."snapshot_retention" IS 'Unreferenced snapshots are retained for the given period of time after creation; set to NULL for infinite retention.';
36 CREATE TABLE "member_settings" (
37 "member_id" INT4 PRIMARY KEY REFERENCES "member" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
38 "settings" JSONB NOT NULL CHECK (jsonb_typeof("settings") = 'object') );
40 COMMENT ON TABLE "member_settings" IS 'Stores a JSON document for each member containing optional (additional) settings for the respective member';
43 CREATE TABLE "member_useterms" (
44 "member_id" INT4 PRIMARY KEY REFERENCES "member" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
45 "accepted" TIMESTAMPTZ NOT NULL,
46 "contract_identifier" TEXT NOT NULL );
48 COMMENT ON TABLE "member_useterms" IS 'Keeps record of accepted terms of use; may contain multiple rows per member';
50 COMMENT ON COLUMN "member_useterms"."accepted" IS 'Point in time when user accepted the terms of use';
51 COMMENT ON COLUMN "member_useterms"."contract_identifier" IS 'String identifier to denote the accepted terms of use, including their version or revision';
54 CREATE TABLE "member_profile" (
55 "member_id" INT4 PRIMARY KEY REFERENCES "member" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
56 "formatting_engine" TEXT,
57 "statement" TEXT,
58 "profile" JSONB NOT NULL DEFAULT '{}' CHECK (jsonb_typeof("profile") = 'object'),
59 "profile_text_data" TEXT,
60 "text_search_data" TSVECTOR );
61 CREATE INDEX "member_profile_text_search_data_idx" ON "member_profile" USING gin ("text_search_data");
62 CREATE TRIGGER "update_text_search_data"
63 BEFORE INSERT OR UPDATE ON "member_profile"
64 FOR EACH ROW EXECUTE PROCEDURE
65 tsvector_update_trigger('text_search_data', 'pg_catalog.simple',
66 'statement', 'profile_text_data');
68 COMMENT ON COLUMN "member_profile"."formatting_engine" IS 'Allows different formatting engines (i.e. wiki formats) to be used for "member_profile"."statement"';
69 COMMENT ON COLUMN "member_profile"."statement" IS 'Freely chosen text of the member for his/her profile';
70 COMMENT ON COLUMN "member_profile"."profile" IS 'Additional profile data as JSON document';
71 COMMENT ON COLUMN "member_profile"."profile_text_data" IS 'Text data from "profile" field for full text search';
74 INSERT INTO "member_profile"
75 ( "member_id", "formatting_engine", "statement", "profile")
76 SELECT
77 "id" AS "member_id",
78 "formatting_engine",
79 "statement",
80 json_build_object(
81 'organizational_unit', "organizational_unit",
82 'internal_posts', "internal_posts",
83 'realname', "realname",
84 'birthday', to_char("birthday", 'YYYY-MM-DD'),
85 'address', "address",
86 'email', "email",
87 'xmpp_address', "xmpp_address",
88 'website', "website",
89 'phone', "phone",
90 'mobile_phone', "mobile_phone",
91 'profession', "profession",
92 'external_memberships', "external_memberships",
93 'external_posts', "external_posts"
94 ) AS "profile"
95 FROM "member";
97 UPDATE "member_profile" SET "profile_text_data" =
98 coalesce(("profile"->>'organizational_unit') || ' ', '') ||
99 coalesce(("profile"->>'internal_posts') || ' ', '') ||
100 coalesce(("profile"->>'realname') || ' ', '') ||
101 coalesce(("profile"->>'birthday') || ' ', '') ||
102 coalesce(("profile"->>'address') || ' ', '') ||
103 coalesce(("profile"->>'email') || ' ', '') ||
104 coalesce(("profile"->>'xmpp_address') || ' ', '') ||
105 coalesce(("profile"->>'website') || ' ', '') ||
106 coalesce(("profile"->>'phone') || ' ', '') ||
107 coalesce(("profile"->>'mobile_phone') || ' ', '') ||
108 coalesce(("profile"->>'profession') || ' ', '') ||
109 coalesce(("profile"->>'external_memberships') || ' ', '') ||
110 coalesce(("profile"->>'external_posts') || ' ', '');
113 DROP VIEW "newsletter_to_send";
114 DROP VIEW "scheduled_notification_to_send";
115 DROP VIEW "member_to_notify";
116 DROP VIEW "member_eligible_to_be_notified";
119 ALTER TABLE "member" DROP COLUMN "organizational_unit";
120 ALTER TABLE "member" DROP COLUMN "internal_posts";
121 ALTER TABLE "member" DROP COLUMN "realname";
122 ALTER TABLE "member" DROP COLUMN "birthday";
123 ALTER TABLE "member" DROP COLUMN "address";
124 ALTER TABLE "member" DROP COLUMN "email";
125 ALTER TABLE "member" DROP COLUMN "xmpp_address";
126 ALTER TABLE "member" DROP COLUMN "website";
127 ALTER TABLE "member" DROP COLUMN "phone";
128 ALTER TABLE "member" DROP COLUMN "mobile_phone";
129 ALTER TABLE "member" DROP COLUMN "profession";
130 ALTER TABLE "member" DROP COLUMN "external_memberships";
131 ALTER TABLE "member" DROP COLUMN "external_posts";
132 ALTER TABLE "member" DROP COLUMN "formatting_engine";
133 ALTER TABLE "member" DROP COLUMN "statement";
135 ALTER TABLE "member" ADD COLUMN "location" JSONB;
136 COMMENT ON COLUMN "member"."location" IS 'Geographic location on earth as GeoJSON object';
137 CREATE INDEX "member_location_idx" ON "member" USING gist ((GeoJSON_to_ecluster("location")));
139 DROP TRIGGER "update_text_search_data" ON "member";
140 CREATE TRIGGER "update_text_search_data"
141 BEFORE INSERT OR UPDATE ON "member"
142 FOR EACH ROW EXECUTE PROCEDURE
143 tsvector_update_trigger('text_search_data', 'pg_catalog.simple',
144 "name", "identification");
147 CREATE VIEW "member_eligible_to_be_notified" AS
148 SELECT * FROM "member"
149 WHERE "activated" NOTNULL AND "locked" = FALSE;
151 COMMENT ON VIEW "member_eligible_to_be_notified" IS 'Filtered "member" table containing only activated and non-locked members (used as helper view for "member_to_notify" and "newsletter_to_send")';
154 CREATE VIEW "member_to_notify" AS
155 SELECT * FROM "member_eligible_to_be_notified"
156 WHERE "disable_notifications" = FALSE;
158 COMMENT ON VIEW "member_to_notify" IS 'Filtered "member" table containing only members that are eligible to and wish to receive notifications; NOTE: "notify_email" may still be NULL and might need to be checked by frontend (this allows other means of messaging)';
161 CREATE VIEW "scheduled_notification_to_send" AS
162 SELECT * FROM (
163 SELECT
164 "id" AS "recipient_id",
165 now() - CASE WHEN "notification_dow" ISNULL THEN
166 ( "notification_sent"::DATE + CASE
167 WHEN EXTRACT(HOUR FROM "notification_sent") < "notification_hour"
168 THEN 0 ELSE 1 END
169 )::TIMESTAMP + '1 hour'::INTERVAL * "notification_hour"
170 ELSE
171 ( "notification_sent"::DATE +
172 ( 7 + "notification_dow" -
173 EXTRACT(DOW FROM
174 ( "notification_sent"::DATE + CASE
175 WHEN EXTRACT(HOUR FROM "notification_sent") < "notification_hour"
176 THEN 0 ELSE 1 END
177 )::TIMESTAMP + '1 hour'::INTERVAL * "notification_hour"
178 )::INTEGER
179 ) % 7 +
180 CASE
181 WHEN EXTRACT(HOUR FROM "notification_sent") < "notification_hour"
182 THEN 0 ELSE 1
183 END
184 )::TIMESTAMP + '1 hour'::INTERVAL * "notification_hour"
185 END AS "pending"
186 FROM (
187 SELECT
188 "id",
189 COALESCE("notification_sent", "activated") AS "notification_sent",
190 "notification_dow",
191 "notification_hour"
192 FROM "member_to_notify"
193 WHERE "notification_hour" NOTNULL
194 ) AS "subquery1"
195 ) AS "subquery2"
196 WHERE "pending" > '0'::INTERVAL;
198 COMMENT ON VIEW "scheduled_notification_to_send" IS 'Set of members where a scheduled notification mail is pending';
200 COMMENT ON COLUMN "scheduled_notification_to_send"."recipient_id" IS '"id" of the member who needs to receive a notification mail';
201 COMMENT ON COLUMN "scheduled_notification_to_send"."pending" IS 'Duration for which the notification mail has already been pending';
204 CREATE VIEW "newsletter_to_send" AS
205 SELECT
206 "member"."id" AS "recipient_id",
207 "newsletter"."id" AS "newsletter_id",
208 "newsletter"."published"
209 FROM "newsletter" CROSS JOIN "member_eligible_to_be_notified" AS "member"
210 LEFT JOIN "privilege" ON
211 "privilege"."member_id" = "member"."id" AND
212 "privilege"."unit_id" = "newsletter"."unit_id" AND
213 "privilege"."voting_right" = TRUE
214 LEFT JOIN "subscription" ON
215 "subscription"."member_id" = "member"."id" AND
216 "subscription"."unit_id" = "newsletter"."unit_id"
217 WHERE "newsletter"."published" <= now()
218 AND "newsletter"."sent" ISNULL
219 AND (
220 "member"."disable_notifications" = FALSE OR
221 "newsletter"."include_all_members" = TRUE )
222 AND (
223 "newsletter"."unit_id" ISNULL OR
224 "privilege"."member_id" NOTNULL OR
225 "subscription"."member_id" NOTNULL );
227 COMMENT ON VIEW "newsletter_to_send" IS 'List of "newsletter_id"s for each member that are due to be sent out';
229 COMMENT ON COLUMN "newsletter"."published" IS 'Timestamp when the newsletter was supposed to be sent out (can be used for ordering)';
232 DROP VIEW "expired_session";
233 DROP TABLE "session";
236 CREATE TABLE "session" (
237 UNIQUE ("member_id", "id"), -- index needed for foreign-key on table "token"
238 "id" SERIAL8 PRIMARY KEY,
239 "ident" TEXT NOT NULL UNIQUE,
240 "additional_secret" TEXT,
241 "logout_token" TEXT,
242 "expiry" TIMESTAMPTZ NOT NULL DEFAULT now() + '24 hours',
243 "member_id" INT4 REFERENCES "member" ("id") ON DELETE SET NULL,
244 "authority" TEXT,
245 "authority_uid" TEXT,
246 "authority_login" TEXT,
247 "needs_delegation_check" BOOLEAN NOT NULL DEFAULT FALSE,
248 "lang" TEXT );
249 CREATE INDEX "session_expiry_idx" ON "session" ("expiry");
251 COMMENT ON TABLE "session" IS 'Sessions, i.e. for a web-frontend or API layer';
253 COMMENT ON COLUMN "session"."ident" IS 'Secret session identifier (i.e. random string)';
254 COMMENT ON COLUMN "session"."additional_secret" IS 'Additional field to store a secret, which can be used against CSRF attacks';
255 COMMENT ON COLUMN "session"."logout_token" IS 'Optional token to authorize logout through external component';
256 COMMENT ON COLUMN "session"."member_id" IS 'Reference to member, who is logged in';
257 COMMENT ON COLUMN "session"."authority" IS 'Temporary store for "member"."authority" during member account creation';
258 COMMENT ON COLUMN "session"."authority_uid" IS 'Temporary store for "member"."authority_uid" during member account creation';
259 COMMENT ON COLUMN "session"."authority_login" IS 'Temporary store for "member"."authority_login" during member account creation';
260 COMMENT ON COLUMN "session"."needs_delegation_check" IS 'Set to TRUE, if member must perform a delegation check to proceed with login; see column "last_delegation_check" in "member" table';
261 COMMENT ON COLUMN "session"."lang" IS 'Language code of the selected language';
264 CREATE TYPE "authflow" AS ENUM ('code', 'token');
266 COMMENT ON TYPE "authflow" IS 'OAuth 2.0 flows: ''code'' = Authorization Code flow, ''token'' = Implicit flow';
269 CREATE TABLE "system_application" (
270 "id" SERIAL4 PRIMARY KEY,
271 "name" TEXT NOT NULL,
272 "discovery_baseurl" TEXT,
273 "client_id" TEXT NOT NULL UNIQUE,
274 "default_redirect_uri" TEXT NOT NULL,
275 "cert_common_name" TEXT,
276 "client_cred_scope" TEXT,
277 "flow" "authflow",
278 "automatic_scope" TEXT,
279 "permitted_scope" TEXT,
280 "forbidden_scope" TEXT );
282 COMMENT ON TABLE "system_application" IS 'OAuth 2.0 clients that are registered by the system administrator';
284 COMMENT ON COLUMN "system_application"."name" IS 'Human readable name of application';
285 COMMENT ON COLUMN "system_application"."discovery_baseurl" IS 'Base URL for application discovery; NULL for hidden application';
286 COMMENT ON COLUMN "system_application"."client_id" IS 'OAuth 2.0 "client_id"';
287 COMMENT ON COLUMN "system_application"."cert_common_name" IS 'Value for CN field of TLS client certificate';
288 COMMENT ON COLUMN "system_application"."client_cred_scope" IS 'Space-separated list of scopes; If set, Client Credentials Grant is allowed; value determines scope';
289 COMMENT ON COLUMN "system_application"."flow" IS 'If set to ''code'' or ''token'', then Authorization Code or Implicit flow is allowed respectively';
290 COMMENT ON COLUMN "system_application"."automatic_scope" IS 'Space-separated list of scopes; Automatically granted scope for Authorization Code or Implicit flow';
291 COMMENT ON COLUMN "system_application"."permitted_scope" IS 'Space-separated list of scopes; If set, scope that members may grant to the application is limited to the given value';
292 COMMENT ON COLUMN "system_application"."forbidden_scope" IS 'Space-separated list of scopes that may not be granted to the application by a member';
295 CREATE TABLE "system_application_redirect_uri" (
296 PRIMARY KEY ("system_application_id", "redirect_uri"),
297 "system_application_id" INT4 REFERENCES "system_application" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
298 "redirect_uri" TEXT );
300 COMMENT ON TABLE "system_application_redirect_uri" IS 'Additional OAuth 2.0 redirection endpoints, which may be selected through the "redirect_uri" GET parameter';
303 CREATE TABLE "dynamic_application_scope" (
304 PRIMARY KEY ("redirect_uri", "flow", "scope"),
305 "redirect_uri" TEXT,
306 "flow" TEXT,
307 "scope" TEXT,
308 "expiry" TIMESTAMPTZ NOT NULL DEFAULT now() + '24 hours' );
309 CREATE INDEX "dynamic_application_scope_redirect_uri_scope_idx" ON "dynamic_application_scope" ("redirect_uri", "flow", "scope");
310 CREATE INDEX "dynamic_application_scope_expiry_idx" ON "dynamic_application_scope" ("expiry");
312 COMMENT ON TABLE "dynamic_application_scope" IS 'Dynamic OAuth 2.0 client registration data';
314 COMMENT ON COLUMN "dynamic_application_scope"."redirect_uri" IS 'Redirection endpoint for which the registration has been done';
315 COMMENT ON COLUMN "dynamic_application_scope"."flow" IS 'OAuth 2.0 flow for which the registration has been done (see also "system_application"."flow")';
316 COMMENT ON COLUMN "dynamic_application_scope"."scope" IS 'Single scope without space characters (use multiple rows for more scopes)';
317 COMMENT ON COLUMN "dynamic_application_scope"."expiry" IS 'Expiry unless renewed';
320 CREATE TABLE "member_application" (
321 "id" SERIAL4 PRIMARY KEY,
322 UNIQUE ("system_application_id", "member_id"),
323 UNIQUE ("domain", "member_id"),
324 "member_id" INT4 NOT NULL REFERENCES "member" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
325 "system_application_id" INT4 REFERENCES "system_application" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
326 "domain" TEXT,
327 "session_id" INT8,
328 FOREIGN KEY ("member_id", "session_id") REFERENCES "session" ("member_id", "id") ON DELETE CASCADE ON UPDATE CASCADE,
329 "scope" TEXT NOT NULL,
330 CONSTRAINT "system_application_or_domain_but_not_both" CHECK (
331 ("system_application_id" NOTNULL AND "domain" ISNULL) OR
332 ("system_application_id" ISNULL AND "domain" NOTNULL) ) );
333 CREATE INDEX "member_application_member_id_idx" ON "member_application" ("member_id");
335 COMMENT ON TABLE "member_application" IS 'Application authorized by a member';
337 COMMENT ON COLUMN "member_application"."system_application_id" IS 'If set, then application is a system application';
338 COMMENT ON COLUMN "member_application"."domain" IS 'If set, then application is a dynamically registered OAuth 2.0 client; value is set to client''s domain';
339 COMMENT ON COLUMN "member_application"."session_id" IS 'If set, registration ends with session';
340 COMMENT ON COLUMN "member_application"."scope" IS 'Granted scope as space-separated list of strings';
343 CREATE TYPE "token_type" AS ENUM ('authorization', 'refresh', 'access');
345 COMMENT ON TYPE "token_type" IS 'Types for entries in "token" table';
348 CREATE TABLE "token" (
349 "id" SERIAL8 PRIMARY KEY,
350 "token" TEXT NOT NULL UNIQUE,
351 "token_type" "token_type" NOT NULL,
352 "authorization_token_id" INT8 REFERENCES "token" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
353 "member_id" INT4 NOT NULL REFERENCES "member" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
354 "system_application_id" INT4 REFERENCES "system_application" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
355 "domain" TEXT,
356 FOREIGN KEY ("member_id", "domain") REFERENCES "member_application" ("member_id", "domain") ON DELETE CASCADE ON UPDATE CASCADE,
357 "session_id" INT8,
358 FOREIGN KEY ("member_id", "session_id") REFERENCES "session" ("member_id", "id") ON DELETE RESTRICT ON UPDATE CASCADE, -- NOTE: deletion through "detach_token_from_session" trigger on table "session"
359 "redirect_uri" TEXT,
360 "redirect_uri_explicit" BOOLEAN,
361 "created" TIMESTAMPTZ NOT NULL DEFAULT now(),
362 "expiry" TIMESTAMPTZ DEFAULT now() + '1 hour',
363 "used" BOOLEAN NOT NULL DEFAULT FALSE,
364 "scope" TEXT NOT NULL,
365 CONSTRAINT "access_token_needs_expiry"
366 CHECK ("token_type" != 'access'::"token_type" OR "expiry" NOTNULL),
367 CONSTRAINT "authorization_token_needs_redirect_uri"
368 CHECK ("token_type" != 'authorization'::"token_type" OR ("redirect_uri" NOTNULL AND "redirect_uri_explicit" NOTNULL) ) );
369 CREATE INDEX "token_member_id_idx" ON "token" ("member_id");
370 CREATE INDEX "token_authorization_token_id_idx" ON "token" ("authorization_token_id");
371 CREATE INDEX "token_expiry_idx" ON "token" ("expiry");
373 COMMENT ON TABLE "token" IS 'Issued OAuth 2.0 authorization codes and access/refresh tokens';
375 COMMENT ON COLUMN "token"."token" IS 'String secret (the actual token)';
376 COMMENT ON COLUMN "token"."authorization_token_id" IS 'Reference to authorization token if tokens were originally created by Authorization Code flow (allows deletion if code is used twice)';
377 COMMENT ON COLUMN "token"."system_application_id" IS 'If set, then application is a system application';
378 COMMENT ON COLUMN "token"."domain" IS 'If set, then application is a dynamically registered OAuth 2.0 client; value is set to client''s domain';
379 COMMENT ON COLUMN "token"."session_id" IS 'If set, then token is tied to a session; Deletion of session sets value to NULL (via trigger) and removes all scopes without suffix ''_detached''';
380 COMMENT ON COLUMN "token"."redirect_uri" IS 'Authorization codes must be bound to a specific redirect URI';
381 COMMENT ON COLUMN "token"."redirect_uri_explicit" IS 'True if ''redirect_uri'' parameter was explicitly specified during authorization request of the Authorization Code flow (since RFC 6749 requires it to be included in the access token request in this case)';
382 COMMENT ON COLUMN "token"."expiry" IS 'Point in time when code or token expired; In case of "used" authorization codes, authorization code must not be deleted as long as tokens exist which refer to the authorization code';
383 COMMENT ON COLUMN "token"."used" IS 'Can be set to TRUE for authorization codes that have been used (enables deletion of authorization codes that were used twice)';
384 COMMENT ON COLUMN "token"."scope" IS 'Scope as space-separated list of strings (detached scopes are marked with ''_detached'' suffix)';
387 CREATE TABLE "token_scope" (
388 PRIMARY KEY ("token_id", "index"),
389 "token_id" INT8 REFERENCES "token" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
390 "index" INT4,
391 "scope" TEXT NOT NULL );
393 COMMENT ON TABLE "token_scope" IS 'Additional scopes for an authorization code if ''scope1'', ''scope2'', etc. parameters were used during Authorization Code flow to request several access and refresh tokens at once';
396 ALTER TABLE "policy" ADD COLUMN "issue_quorum" INT4 CHECK ("issue_quorum" >= 1);
397 ALTER TABLE "policy" ADD COLUMN "initiative_quorum" INT4 CHECK ("initiative_quorum" >= 1);
399 UPDATE "policy" SET "issue_quorum" = 1 WHERE "issue_quorum_num" NOTNULL;
400 UPDATE "policy" SET "initiative_quorum" = 1;
402 ALTER TABLE "policy" ALTER COLUMN "initiative_quorum" SET NOT NULL;
404 ALTER TABLE "policy" DROP CONSTRAINT "timing";
405 ALTER TABLE "policy" DROP CONSTRAINT "issue_quorum_if_and_only_if_not_polling";
406 ALTER TABLE "policy" ADD CONSTRAINT
407 "issue_quorum_if_and_only_if_not_polling" CHECK (
408 "polling" = ("issue_quorum" ISNULL) AND
409 "polling" = ("issue_quorum_num" ISNULL) AND
410 "polling" = ("issue_quorum_den" ISNULL)
411 );
412 ALTER TABLE "policy" ADD CONSTRAINT
413 "min_admission_time_smaller_than_max_admission_time" CHECK (
414 "min_admission_time" < "max_admission_time"
415 );
416 ALTER TABLE "policy" ADD CONSTRAINT
417 "timing_null_or_not_null_constraints" CHECK (
418 ( "polling" = FALSE AND
419 "min_admission_time" NOTNULL AND "max_admission_time" NOTNULL AND
420 "discussion_time" NOTNULL AND
421 "verification_time" NOTNULL AND
422 "voting_time" NOTNULL ) OR
423 ( "polling" = TRUE AND
424 "min_admission_time" ISNULL AND "max_admission_time" ISNULL AND
425 "discussion_time" NOTNULL AND
426 "verification_time" NOTNULL AND
427 "voting_time" NOTNULL ) OR
428 ( "polling" = TRUE AND
429 "min_admission_time" ISNULL AND "max_admission_time" ISNULL AND
430 "discussion_time" ISNULL AND
431 "verification_time" ISNULL AND
432 "voting_time" ISNULL )
433 );
435 COMMENT ON COLUMN "policy"."min_admission_time" IS 'Minimum duration of issue state ''admission''; Minimum time an issue stays open; Note: should be considerably smaller than "max_admission_time"';
436 COMMENT ON COLUMN "policy"."issue_quorum" IS 'Absolute number of supporters needed by an initiative to be "accepted", i.e. pass from ''admission'' to ''discussion'' state';
437 COMMENT ON COLUMN "policy"."issue_quorum_num" IS 'Numerator of supporter quorum to be reached by an initiative to be "accepted", i.e. pass from ''admission'' to ''discussion'' state (Note: further requirements apply, see quorum columns of "area" table)';
438 COMMENT ON COLUMN "policy"."issue_quorum_den" IS 'Denominator of supporter quorum to be reached by an initiative to be "accepted", i.e. pass from ''admission'' to ''discussion'' state (Note: further requirements apply, see quorum columns of "area" table)';
439 COMMENT ON COLUMN "policy"."initiative_quorum" IS 'Absolute number of satisfied supporters to be reached by an initiative to be "admitted" for voting';
440 COMMENT ON COLUMN "policy"."initiative_quorum_num" IS 'Numerator of satisfied supporter quorum to be reached by an initiative to be "admitted" for voting';
441 COMMENT ON COLUMN "policy"."initiative_quorum_den" IS 'Denominator of satisfied supporter quorum to be reached by an initiative to be "admitted" for voting';
444 ALTER TABLE "unit" ADD COLUMN "location" JSONB;
446 CREATE INDEX "unit_location_idx" ON "unit" USING gist ((GeoJSON_to_ecluster("location")));
448 COMMENT ON COLUMN "unit"."member_count" IS 'Count of members as determined by column "voting_right" in table "privilege" (only active members counted)';
449 COMMENT ON COLUMN "unit"."location" IS 'Geographic location on earth as GeoJSON object indicating valid coordinates for initiatives of issues with this policy';
452 DROP INDEX "area_unit_id_idx";
453 ALTER TABLE "area" ADD UNIQUE ("unit_id", "id");
455 ALTER TABLE "area" ADD COLUMN "quorum_standard" NUMERIC NOT NULL DEFAULT 2 CHECK ("quorum_standard" >= 0);
456 ALTER TABLE "area" ADD COLUMN "quorum_issues" NUMERIC NOT NULL DEFAULT 1 CHECK ("quorum_issues" > 0);
457 ALTER TABLE "area" ADD COLUMN "quorum_time" INTERVAL NOT NULL DEFAULT '1 day' CHECK ("quorum_time" > '0'::INTERVAL);
458 ALTER TABLE "area" ADD COLUMN "quorum_exponent" NUMERIC NOT NULL DEFAULT 0.5 CHECK ("quorum_exponent" BETWEEN 0 AND 1);
459 ALTER TABLE "area" ADD COLUMN "quorum_factor" NUMERIC NOT NULL DEFAULT 2 CHECK ("quorum_factor" >= 1);
460 ALTER TABLE "area" ADD COLUMN "quorum_den" INT4 CHECK ("quorum_den" > 0);
461 ALTER TABLE "area" ADD COLUMN "issue_quorum" INT4;
462 ALTER TABLE "area" ADD COLUMN "location" JSONB;
464 ALTER TABLE "area" DROP COLUMN "direct_member_count";
465 ALTER TABLE "area" DROP COLUMN "member_weight";
467 CREATE INDEX "area_location_idx" ON "area" USING gist ((GeoJSON_to_ecluster("location")));
469 COMMENT ON COLUMN "area"."quorum_standard" IS 'Parameter for dynamic issue quorum: default quorum';
470 COMMENT ON COLUMN "area"."quorum_issues" IS 'Parameter for dynamic issue quorum: number of open issues for default quorum';
471 COMMENT ON COLUMN "area"."quorum_time" IS 'Parameter for dynamic issue quorum: discussion, verification, and voting time of open issues to result in the given default quorum (open issues with shorter time will increase quorum and open issues with longer time will reduce quorum if "quorum_exponent" is greater than zero)';
472 COMMENT ON COLUMN "area"."quorum_exponent" IS 'Parameter for dynamic issue quorum: set to zero to ignore duration of open issues, set to one to fully take duration of open issues into account; defaults to 0.5';
473 COMMENT ON COLUMN "area"."quorum_factor" IS 'Parameter for dynamic issue quorum: factor to increase dynamic quorum when a number of "quorum_issues" issues with "quorum_time" duration of discussion, verification, and voting phase are added to the number of open admitted issues';
474 COMMENT ON COLUMN "area"."quorum_den" IS 'Parameter for dynamic issue quorum: when set, dynamic quorum is multiplied with "issue"."population" and divided by "quorum_den" (and then rounded up)';
475 COMMENT ON COLUMN "area"."issue_quorum" IS 'Additional dynamic issue quorum based on the number of open accepted issues; automatically calculated by function "issue_admission"';
476 COMMENT ON COLUMN "area"."external_reference" IS 'Opaque data field to store an external reference';
477 COMMENT ON COLUMN "area"."location" IS 'Geographic location on earth as GeoJSON object indicating valid coordinates for initiatives of issues with this policy';
480 CREATE TABLE "snapshot" (
481 UNIQUE ("issue_id", "id"), -- index needed for foreign-key on table "issue"
482 "id" SERIAL8 PRIMARY KEY,
483 "calculated" TIMESTAMPTZ NOT NULL DEFAULT now(),
484 "population" INT4,
485 "area_id" INT4 NOT NULL REFERENCES "area" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
486 "issue_id" INT4 ); -- NOTE: following (cyclic) reference is added later through ALTER command: REFERENCES "issue" ("id") ON DELETE CASCADE ON UPDATE CASCADE
488 COMMENT ON TABLE "snapshot" IS 'Point in time when a snapshot of one or more issues (see table "snapshot_issue") and their supporter situation is taken';
491 CREATE TABLE "snapshot_population" (
492 PRIMARY KEY ("snapshot_id", "member_id"),
493 "snapshot_id" INT8 REFERENCES "snapshot" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
494 "member_id" INT4 REFERENCES "member" ("id") ON DELETE RESTRICT ON UPDATE CASCADE );
496 COMMENT ON TABLE "snapshot_population" IS 'Members with voting right relevant for a snapshot';
499 ALTER TABLE "issue" ADD UNIQUE ("area_id", "id");
500 DROP INDEX "issue_area_id_idx";
501 ALTER TABLE "issue" ADD UNIQUE ("policy_id", "id");
502 DROP INDEX "issue_policy_id_idx";
504 ALTER TABLE "issue" RENAME COLUMN "snapshot" TO "calculated";
506 ALTER TABLE "issue" ADD COLUMN "latest_snapshot_id" INT8 REFERENCES "snapshot" ("id") ON DELETE RESTRICT ON UPDATE CASCADE;
507 ALTER TABLE "issue" ADD COLUMN "admission_snapshot_id" INT8 REFERENCES "snapshot" ("id") ON DELETE SET NULL ON UPDATE CASCADE;
508 ALTER TABLE "issue" ADD COLUMN "half_freeze_snapshot_id" INT8;
509 ALTER TABLE "issue" ADD COLUMN "full_freeze_snapshot_id" INT8;
511 ALTER TABLE "issue" ADD FOREIGN KEY ("id", "half_freeze_snapshot_id")
512 REFERENCES "snapshot" ("issue_id", "id") ON DELETE RESTRICT ON UPDATE CASCADE;
513 ALTER TABLE "issue" ADD FOREIGN KEY ("id", "full_freeze_snapshot_id")
514 REFERENCES "snapshot" ("issue_id", "id") ON DELETE RESTRICT ON UPDATE CASCADE;
516 ALTER TABLE "issue" DROP CONSTRAINT "last_snapshot_on_full_freeze";
517 ALTER TABLE "issue" DROP CONSTRAINT "freeze_requires_snapshot";
518 ALTER TABLE "issue" DROP CONSTRAINT "set_both_or_none_of_snapshot_and_latest_snapshot_event";
520 CREATE INDEX "issue_state_idx" ON "issue" ("state");
521 CREATE INDEX "issue_latest_snapshot_id" ON "issue" ("latest_snapshot_id");
522 CREATE INDEX "issue_admission_snapshot_id" ON "issue" ("admission_snapshot_id");
523 CREATE INDEX "issue_half_freeze_snapshot_id" ON "issue" ("half_freeze_snapshot_id");
524 CREATE INDEX "issue_full_freeze_snapshot_id" ON "issue" ("full_freeze_snapshot_id");
526 COMMENT ON COLUMN "issue"."accepted" IS 'Point in time, when the issue was accepted for further discussion (see columns "issue_quorum_num" and "issue_quorum_den" of table "policy" and quorum columns of table "area")';
527 COMMENT ON COLUMN "issue"."calculated" IS 'Point in time, when most recent snapshot and "population" and *_count values were calculated (NOTE: value is equal to "snapshot"."calculated" of snapshot with "id"="issue"."latest_snapshot_id")';
528 COMMENT ON COLUMN "issue"."latest_snapshot_id" IS 'Snapshot id of most recent snapshot';
529 COMMENT ON COLUMN "issue"."admission_snapshot_id" IS 'Snapshot id when issue as accepted or canceled in admission phase';
530 COMMENT ON COLUMN "issue"."half_freeze_snapshot_id" IS 'Snapshot id at end of discussion phase';
531 COMMENT ON COLUMN "issue"."full_freeze_snapshot_id" IS 'Snapshot id at end of verification phase';
532 COMMENT ON COLUMN "issue"."population" IS 'Count of members in "snapshot_population" table with "snapshot_id" equal to "issue"."latest_snapshot_id"';
535 ALTER TABLE "snapshot" ADD FOREIGN KEY ("issue_id") REFERENCES "issue" ("id") ON DELETE CASCADE ON UPDATE CASCADE;
538 ALTER TABLE "initiative" DROP CONSTRAINT "initiative_suggested_initiative_id_fkey";
539 ALTER TABLE "initiative" ADD FOREIGN KEY ("suggested_initiative_id") REFERENCES "initiative" ("id") ON DELETE SET NULL ON UPDATE CASCADE;
541 ALTER TABLE "initiative" ADD COLUMN "location" JSONB;
542 ALTER TABLE "initiative" ADD COLUMN "draft_text_search_data" TSVECTOR;
544 CREATE INDEX "initiative_location_idx" ON "initiative" USING gist ((GeoJSON_to_ecluster("location")));
545 CREATE INDEX "initiative_draft_text_search_data_idx" ON "initiative" USING gin ("draft_text_search_data");
547 COMMENT ON COLUMN "initiative"."location" IS 'Geographic location of initiative as GeoJSON object (automatically copied from most recent draft)';
550 ALTER TABLE "draft" ADD COLUMN "location" JSONB;
552 CREATE INDEX "draft_location_idx" ON "draft" USING gist ((GeoJSON_to_ecluster("location")));
554 COMMENT ON COLUMN "draft"."location" IS 'Geographic location of initiative as GeoJSON object (automatically copied to "initiative" table if draft is most recent)';
557 ALTER TABLE "suggestion" ADD COLUMN "location" JSONB;
559 CREATE INDEX "suggestion_location_idx" ON "suggestion" USING gist ((GeoJSON_to_ecluster("location")));
561 COMMENT ON COLUMN "suggestion"."location" IS 'Geographic location of suggestion as GeoJSON object';
564 CREATE TABLE "temporary_suggestion_counts" (
565 "id" INT8 PRIMARY KEY, -- NOTE: no referential integrity due to performance/locking issues; REFERENCES "suggestion" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
566 "minus2_unfulfilled_count" INT4 NOT NULL,
567 "minus2_fulfilled_count" INT4 NOT NULL,
568 "minus1_unfulfilled_count" INT4 NOT NULL,
569 "minus1_fulfilled_count" INT4 NOT NULL,
570 "plus1_unfulfilled_count" INT4 NOT NULL,
571 "plus1_fulfilled_count" INT4 NOT NULL,
572 "plus2_unfulfilled_count" INT4 NOT NULL,
573 "plus2_fulfilled_count" INT4 NOT NULL );
575 COMMENT ON TABLE "temporary_suggestion_counts" IS 'Holds certain calculated values (suggestion counts) temporarily until they can be copied into table "suggestion"';
577 COMMENT ON COLUMN "temporary_suggestion_counts"."id" IS 'References "suggestion" ("id") but has no referential integrity trigger associated, due to performance/locking issues';
580 ALTER TABLE "interest" DROP CONSTRAINT "interest_member_id_fkey";
581 ALTER TABLE "interest" ADD FOREIGN KEY ("member_id") REFERENCES "member" ("id") ON DELETE RESTRICT ON UPDATE CASCADE;
584 ALTER TABLE "initiator" DROP CONSTRAINT "initiator_member_id_fkey";
585 ALTER TABLE "initiator" ADD FOREIGN KEY ("member_id") REFERENCES "member" ("id") ON DELETE RESTRICT ON UPDATE CASCADE;
588 ALTER TABLE "delegation" DROP CONSTRAINT "delegation_trustee_id_fkey";
589 ALTER TABLE "delegation" ADD FOREIGN KEY ("trustee_id") REFERENCES "member" ("id") ON DELETE RESTRICT ON UPDATE CASCADE;
592 CREATE TABLE "snapshot_issue" (
593 PRIMARY KEY ("snapshot_id", "issue_id"),
594 "snapshot_id" INT8 REFERENCES "snapshot" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
595 "issue_id" INT4 REFERENCES "issue" ("id") ON DELETE CASCADE ON UPDATE CASCADE );
596 CREATE INDEX "snapshot_issue_issue_id_idx" ON "snapshot_issue" ("issue_id");
598 COMMENT ON TABLE "snapshot_issue" IS 'List of issues included in a snapshot';
600 COMMENT ON COLUMN "snapshot_issue"."issue_id" IS 'Issue being part of the snapshot; Trigger "delete_snapshot_on_partial_delete" on "snapshot_issue" table will delete snapshot if an issue of the snapshot is deleted.';
603 ALTER TABLE "direct_interest_snapshot" RENAME TO "direct_interest_snapshot_old"; -- TODO!
604 ALTER INDEX "direct_interest_snapshot_pkey" RENAME TO "direct_interest_snapshot_old_pkey";
605 ALTER INDEX "direct_interest_snapshot_member_id_idx" RENAME TO "direct_interest_snapshot_old_member_id_idx";
607 ALTER TABLE "delegating_interest_snapshot" RENAME TO "delegating_interest_snapshot_old"; -- TODO!
608 ALTER INDEX "delegating_interest_snapshot_pkey" RENAME TO "delegating_interest_snapshot_old_pkey";
609 ALTER INDEX "delegating_interest_snapshot_member_id_idx" RENAME TO "delegating_interest_snapshot_old_member_id_idx";
611 ALTER TABLE "direct_supporter_snapshot" RENAME TO "direct_supporter_snapshot_old"; -- TODO!
612 ALTER INDEX "direct_supporter_snapshot_pkey" RENAME TO "direct_supporter_snapshot_old_pkey";
613 ALTER INDEX "direct_supporter_snapshot_member_id_idx" RENAME TO "direct_supporter_snapshot_old_member_id_idx";
616 CREATE TABLE "direct_interest_snapshot" (
617 PRIMARY KEY ("snapshot_id", "issue_id", "member_id"),
618 "snapshot_id" INT8,
619 "issue_id" INT4,
620 FOREIGN KEY ("snapshot_id", "issue_id")
621 REFERENCES "snapshot_issue" ("snapshot_id", "issue_id") ON DELETE CASCADE ON UPDATE CASCADE,
622 "member_id" INT4 REFERENCES "member" ("id") ON DELETE RESTRICT ON UPDATE RESTRICT,
623 "weight" INT4 );
624 CREATE INDEX "direct_interest_snapshot_member_id_idx" ON "direct_interest_snapshot" ("member_id");
626 COMMENT ON TABLE "direct_interest_snapshot" IS 'Snapshot of active members having an "interest" in the "issue"; for corrections refer to column "issue_notice" of "issue" table';
628 COMMENT ON COLUMN "direct_interest_snapshot"."weight" IS 'Weight of member (1 or higher) according to "delegating_interest_snapshot"';
631 CREATE TABLE "delegating_interest_snapshot" (
632 PRIMARY KEY ("snapshot_id", "issue_id", "member_id"),
633 "snapshot_id" INT8,
634 "issue_id" INT4,
635 FOREIGN KEY ("snapshot_id", "issue_id")
636 REFERENCES "snapshot_issue" ("snapshot_id", "issue_id") ON DELETE CASCADE ON UPDATE CASCADE,
637 "member_id" INT4 REFERENCES "member" ("id") ON DELETE RESTRICT ON UPDATE RESTRICT,
638 "weight" INT4,
639 "scope" "delegation_scope" NOT NULL,
640 "delegate_member_ids" INT4[] NOT NULL );
641 CREATE INDEX "delegating_interest_snapshot_member_id_idx" ON "delegating_interest_snapshot" ("member_id");
643 COMMENT ON TABLE "delegating_interest_snapshot" IS 'Delegations increasing the weight of entries in the "direct_interest_snapshot" table; for corrections refer to column "issue_notice" of "issue" table';
645 COMMENT ON COLUMN "delegating_interest_snapshot"."member_id" IS 'Delegating member';
646 COMMENT ON COLUMN "delegating_interest_snapshot"."weight" IS 'Intermediate weight';
647 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"';
650 CREATE TABLE "direct_supporter_snapshot" (
651 PRIMARY KEY ("snapshot_id", "initiative_id", "member_id"),
652 "snapshot_id" INT8,
653 "issue_id" INT4 NOT NULL,
654 FOREIGN KEY ("snapshot_id", "issue_id")
655 REFERENCES "snapshot_issue" ("snapshot_id", "issue_id") ON DELETE CASCADE ON UPDATE CASCADE,
656 "initiative_id" INT4,
657 "member_id" INT4 REFERENCES "member" ("id") ON DELETE RESTRICT ON UPDATE RESTRICT,
658 "draft_id" INT8 NOT NULL,
659 "informed" BOOLEAN NOT NULL,
660 "satisfied" BOOLEAN NOT NULL,
661 FOREIGN KEY ("issue_id", "initiative_id") REFERENCES "initiative" ("issue_id", "id") ON DELETE CASCADE ON UPDATE CASCADE,
662 FOREIGN KEY ("initiative_id", "draft_id") REFERENCES "draft" ("initiative_id", "id") ON DELETE NO ACTION ON UPDATE CASCADE,
663 FOREIGN KEY ("snapshot_id", "issue_id", "member_id") REFERENCES "direct_interest_snapshot" ("snapshot_id", "issue_id", "member_id") ON DELETE CASCADE ON UPDATE CASCADE );
664 CREATE INDEX "direct_supporter_snapshot_member_id_idx" ON "direct_supporter_snapshot" ("member_id");
666 COMMENT ON TABLE "direct_supporter_snapshot" IS 'Snapshot of supporters of initiatives (weight is stored in "direct_interest_snapshot"); for corrections refer to column "issue_notice" of "issue" table';
668 COMMENT ON COLUMN "direct_supporter_snapshot"."issue_id" IS 'WARNING: No index: For selections use column "initiative_id" and join via table "initiative" where neccessary';
669 COMMENT ON COLUMN "direct_supporter_snapshot"."informed" IS 'Supporter has seen the latest draft of the initiative';
670 COMMENT ON COLUMN "direct_supporter_snapshot"."satisfied" IS 'Supporter has no "critical_opinion"s';
673 ALTER TABLE "non_voter" DROP CONSTRAINT "non_voter_pkey";
674 DROP INDEX "non_voter_member_id_idx";
676 ALTER TABLE "non_voter" ADD PRIMARY KEY ("member_id", "issue_id");
677 CREATE INDEX "non_voter_issue_id_idx" ON "non_voter" ("issue_id");
680 INSERT INTO "member_useterms" ("member_id", "accepted", "contract_identifier")
681 SELECT
682 "member_id",
683 regexp_replace("value", '^accepted at ', '')::TIMESTAMPTZ AS "accepted",
684 regexp_replace("key", '^use_terms_checkbox_', '') AS "contract_identifier"
685 FROM "setting" WHERE "key" LIKE 'use_terms_checkbox_%';
688 DROP TABLE "setting";
689 DROP TABLE "setting_map";
690 DROP TABLE "member_relation_setting";
691 DROP TABLE "unit_setting";
692 DROP TABLE "area_setting";
693 DROP TABLE "initiative_setting";
694 DROP TABLE "suggestion_setting";
697 ALTER TABLE "event" ADD COLUMN "other_member_id" INT4 REFERENCES "member" ("id") ON DELETE RESTRICT ON UPDATE CASCADE;
698 ALTER TABLE "event" ADD COLUMN "scope" "delegation_scope";
699 ALTER TABLE "event" ADD COLUMN "unit_id" INT4 REFERENCES "unit" ("id") ON DELETE CASCADE ON UPDATE CASCADE;
700 ALTER TABLE "event" ADD COLUMN "area_id" INT4;
701 ALTER TABLE "event" ADD COLUMN "policy_id" INT4 REFERENCES "policy" ("id") ON DELETE CASCADE ON UPDATE CASCADE;
702 ALTER TABLE "event" ADD COLUMN "boolean_value" BOOLEAN;
703 ALTER TABLE "event" ADD COLUMN "numeric_value" INT4;
704 ALTER TABLE "event" ADD COLUMN "text_value" TEXT;
705 ALTER TABLE "event" ADD COLUMN "old_text_value" TEXT;
707 ALTER TABLE "event" ADD FOREIGN KEY ("unit_id", "area_id") REFERENCES "area" ("unit_id", "id") ON DELETE CASCADE ON UPDATE CASCADE;
708 ALTER TABLE "event" ADD FOREIGN KEY ("area_id", "issue_id") REFERENCES "issue" ("area_id", "id") ON DELETE CASCADE ON UPDATE CASCADE;
709 ALTER TABLE "event" ADD FOREIGN KEY ("policy_id", "issue_id") REFERENCES "issue" ("policy_id", "id") ON DELETE CASCADE ON UPDATE CASCADE;
711 ALTER TABLE "event" DROP CONSTRAINT "event_initiative_id_fkey1";
712 ALTER TABLE "event" DROP CONSTRAINT "null_constr_for_issue_state_changed";
713 ALTER TABLE "event" DROP CONSTRAINT "null_constr_for_initiative_creation_or_revocation_or_new_draft";
714 ALTER TABLE "event" DROP CONSTRAINT "null_constr_for_suggestion_creation";
716 UPDATE "event" SET "unit_id" = "area"."unit_id", "area_id" = "issue"."area_id"
717 FROM "issue", "area"
718 WHERE "issue"."id" = "event"."issue_id" AND "area"."id" = "issue"."area_id";
720 ALTER TABLE "event" ADD CONSTRAINT "constr_for_issue_state_changed" CHECK (
721 "event" != 'issue_state_changed' OR (
722 "member_id" ISNULL AND
723 "other_member_id" ISNULL AND
724 "scope" ISNULL AND
725 "unit_id" NOTNULL AND
726 "area_id" NOTNULL AND
727 "policy_id" NOTNULL AND
728 "issue_id" NOTNULL AND
729 "state" NOTNULL AND
730 "initiative_id" ISNULL AND
731 "draft_id" ISNULL AND
732 "suggestion_id" ISNULL AND
733 "boolean_value" ISNULL AND
734 "numeric_value" ISNULL AND
735 "text_value" ISNULL AND
736 "old_text_value" ISNULL ));
737 ALTER TABLE "event" ADD CONSTRAINT "constr_for_initiative_creation_or_revocation_or_new_draft" CHECK (
738 "event" NOT IN (
739 'initiative_created_in_new_issue',
740 'initiative_created_in_existing_issue',
741 'initiative_revoked',
742 'new_draft_created'
743 ) OR (
744 "member_id" NOTNULL AND
745 "other_member_id" ISNULL AND
746 "scope" ISNULL AND
747 "unit_id" NOTNULL AND
748 "area_id" NOTNULL AND
749 "policy_id" NOTNULL AND
750 "issue_id" NOTNULL AND
751 "state" NOTNULL AND
752 "initiative_id" NOTNULL AND
753 "draft_id" NOTNULL AND
754 "suggestion_id" ISNULL AND
755 "boolean_value" ISNULL AND
756 "numeric_value" ISNULL AND
757 "text_value" ISNULL AND
758 "old_text_value" ISNULL ));
759 ALTER TABLE "event" ADD CONSTRAINT "constr_for_suggestion_creation" CHECK (
760 "event" != 'suggestion_created' OR (
761 "member_id" NOTNULL AND
762 "other_member_id" ISNULL AND
763 "scope" ISNULL AND
764 "unit_id" NOTNULL AND
765 "area_id" NOTNULL AND
766 "policy_id" NOTNULL AND
767 "issue_id" NOTNULL AND
768 "state" NOTNULL AND
769 "initiative_id" NOTNULL AND
770 "draft_id" ISNULL AND
771 "suggestion_id" NOTNULL AND
772 "boolean_value" ISNULL AND
773 "numeric_value" ISNULL AND
774 "text_value" ISNULL AND
775 "old_text_value" ISNULL ));
776 ALTER TABLE "event" ADD CONSTRAINT "constr_for_suggestion_removal" CHECK (
777 "event" != 'suggestion_removed' OR (
778 "member_id" ISNULL AND
779 "other_member_id" ISNULL AND
780 "scope" ISNULL AND
781 "unit_id" NOTNULL AND
782 "area_id" NOTNULL AND
783 "policy_id" NOTNULL AND
784 "issue_id" NOTNULL AND
785 "state" NOTNULL AND
786 "initiative_id" NOTNULL AND
787 "draft_id" ISNULL AND
788 "suggestion_id" NOTNULL AND
789 "boolean_value" ISNULL AND
790 "numeric_value" ISNULL AND
791 "text_value" ISNULL AND
792 "old_text_value" ISNULL ));
793 ALTER TABLE "event" ADD CONSTRAINT "constr_for_value_less_member_event" CHECK (
794 "event" NOT IN (
795 'member_activated',
796 'member_removed',
797 'member_profile_updated',
798 'member_image_updated'
799 ) OR (
800 "member_id" NOTNULL AND
801 "other_member_id" ISNULL AND
802 "scope" ISNULL AND
803 "unit_id" ISNULL AND
804 "area_id" ISNULL AND
805 "policy_id" ISNULL AND
806 "issue_id" ISNULL AND
807 "state" ISNULL AND
808 "initiative_id" ISNULL AND
809 "draft_id" ISNULL AND
810 "suggestion_id" ISNULL AND
811 "boolean_value" ISNULL AND
812 "numeric_value" ISNULL AND
813 "text_value" ISNULL AND
814 "old_text_value" ISNULL ));
815 ALTER TABLE "event" ADD CONSTRAINT "constr_for_member_active" CHECK (
816 "event" != 'member_active' OR (
817 "member_id" NOTNULL AND
818 "other_member_id" ISNULL AND
819 "scope" ISNULL AND
820 "unit_id" ISNULL AND
821 "area_id" ISNULL AND
822 "policy_id" ISNULL AND
823 "issue_id" ISNULL AND
824 "state" ISNULL AND
825 "initiative_id" ISNULL AND
826 "draft_id" ISNULL AND
827 "suggestion_id" ISNULL AND
828 "boolean_value" NOTNULL AND
829 "numeric_value" ISNULL AND
830 "text_value" ISNULL AND
831 "old_text_value" ISNULL ));
832 ALTER TABLE "event" ADD CONSTRAINT "constr_for_member_name_updated" CHECK (
833 "event" != 'member_name_updated' OR (
834 "member_id" NOTNULL AND
835 "other_member_id" ISNULL AND
836 "scope" ISNULL AND
837 "unit_id" ISNULL AND
838 "area_id" ISNULL AND
839 "policy_id" ISNULL AND
840 "issue_id" ISNULL AND
841 "state" ISNULL AND
842 "initiative_id" ISNULL AND
843 "draft_id" ISNULL AND
844 "suggestion_id" ISNULL AND
845 "boolean_value" ISNULL AND
846 "numeric_value" ISNULL AND
847 "text_value" NOTNULL AND
848 "old_text_value" NOTNULL ));
849 ALTER TABLE "event" ADD CONSTRAINT "constr_for_interest" CHECK (
850 "event" != 'interest' OR (
851 "member_id" NOTNULL AND
852 "other_member_id" ISNULL AND
853 "scope" ISNULL AND
854 "unit_id" NOTNULL AND
855 "area_id" NOTNULL AND
856 "policy_id" NOTNULL AND
857 "issue_id" NOTNULL AND
858 "state" NOTNULL AND
859 "initiative_id" ISNULL AND
860 "draft_id" ISNULL AND
861 "suggestion_id" ISNULL AND
862 "boolean_value" NOTNULL AND
863 "numeric_value" ISNULL AND
864 "text_value" ISNULL AND
865 "old_text_value" ISNULL ));
866 ALTER TABLE "event" ADD CONSTRAINT "constr_for_initiator" CHECK (
867 "event" != 'initiator' OR (
868 "member_id" NOTNULL AND
869 "other_member_id" ISNULL AND
870 "scope" ISNULL AND
871 "unit_id" NOTNULL AND
872 "area_id" NOTNULL AND
873 "policy_id" NOTNULL AND
874 "issue_id" NOTNULL AND
875 "state" NOTNULL AND
876 "initiative_id" NOTNULL AND
877 "draft_id" ISNULL AND
878 "suggestion_id" ISNULL AND
879 "boolean_value" NOTNULL AND
880 "numeric_value" ISNULL AND
881 "text_value" ISNULL AND
882 "old_text_value" ISNULL ));
883 ALTER TABLE "event" ADD CONSTRAINT "constr_for_support" CHECK (
884 "event" != 'support' OR (
885 "member_id" NOTNULL AND
886 "other_member_id" ISNULL AND
887 "scope" ISNULL AND
888 "unit_id" NOTNULL AND
889 "area_id" NOTNULL AND
890 "policy_id" NOTNULL AND
891 "issue_id" NOTNULL AND
892 "state" NOTNULL AND
893 "initiative_id" NOTNULL AND
894 ("draft_id" NOTNULL) = ("boolean_value" = TRUE) AND
895 "suggestion_id" ISNULL AND
896 "boolean_value" NOTNULL AND
897 "numeric_value" ISNULL AND
898 "text_value" ISNULL AND
899 "old_text_value" ISNULL ));
900 ALTER TABLE "event" ADD CONSTRAINT "constr_for_support_updated" CHECK (
901 "event" != 'support_updated' OR (
902 "member_id" NOTNULL AND
903 "other_member_id" ISNULL AND
904 "scope" ISNULL AND
905 "unit_id" NOTNULL AND
906 "area_id" NOTNULL AND
907 "policy_id" NOTNULL AND
908 "issue_id" NOTNULL AND
909 "state" NOTNULL AND
910 "initiative_id" NOTNULL AND
911 "draft_id" NOTNULL AND
912 "suggestion_id" ISNULL AND
913 "boolean_value" ISNULL AND
914 "numeric_value" ISNULL AND
915 "text_value" ISNULL AND
916 "old_text_value" ISNULL ));
917 ALTER TABLE "event" ADD CONSTRAINT "constr_for_suggestion_rated" CHECK (
918 "event" != 'suggestion_rated' OR (
919 "member_id" NOTNULL AND
920 "other_member_id" ISNULL AND
921 "scope" ISNULL AND
922 "unit_id" NOTNULL AND
923 "area_id" NOTNULL AND
924 "policy_id" NOTNULL AND
925 "issue_id" NOTNULL AND
926 "state" NOTNULL AND
927 "initiative_id" NOTNULL AND
928 "draft_id" ISNULL AND
929 "suggestion_id" NOTNULL AND
930 ("boolean_value" NOTNULL) = ("numeric_value" != 0) AND
931 "numeric_value" NOTNULL AND
932 "numeric_value" IN (-2, -1, 0, 1, 2) AND
933 "text_value" ISNULL AND
934 "old_text_value" ISNULL ));
935 ALTER TABLE "event" ADD CONSTRAINT "constr_for_delegation" CHECK (
936 "event" != 'delegation' OR (
937 "member_id" NOTNULL AND
938 ("other_member_id" NOTNULL) OR ("boolean_value" = FALSE) AND
939 "scope" NOTNULL AND
940 "unit_id" NOTNULL AND
941 ("area_id" NOTNULL) = ("scope" != 'unit'::"delegation_scope") AND
942 "policy_id" ISNULL AND
943 ("issue_id" NOTNULL) = ("scope" = 'issue'::"delegation_scope") AND
944 ("state" NOTNULL) = ("scope" = 'issue'::"delegation_scope") AND
945 "initiative_id" ISNULL AND
946 "draft_id" ISNULL AND
947 "suggestion_id" ISNULL AND
948 "boolean_value" NOTNULL AND
949 "numeric_value" ISNULL AND
950 "text_value" ISNULL AND
951 "old_text_value" ISNULL ));
952 ALTER TABLE "event" ADD CONSTRAINT "constr_for_contact" CHECK (
953 "event" != 'contact' OR (
954 "member_id" NOTNULL AND
955 "other_member_id" NOTNULL AND
956 "scope" ISNULL AND
957 "unit_id" ISNULL AND
958 "area_id" ISNULL AND
959 "policy_id" ISNULL AND
960 "issue_id" ISNULL AND
961 "state" ISNULL AND
962 "initiative_id" ISNULL AND
963 "draft_id" ISNULL AND
964 "suggestion_id" ISNULL AND
965 "boolean_value" NOTNULL AND
966 "numeric_value" ISNULL AND
967 "text_value" ISNULL AND
968 "old_text_value" ISNULL ));
971 ALTER TABLE "notification_event_sent" RENAME TO "event_processed";
972 ALTER INDEX "notification_event_sent_singleton_idx" RENAME TO "event_processed_singleton_idx";
974 COMMENT ON TABLE "event_processed" IS 'This table stores one row with the last event_id, for which event handlers have been executed (e.g. notifications having been sent out)';
975 COMMENT ON INDEX "event_processed_singleton_idx" IS 'This index ensures that "event_processed" only contains one row maximum.';
978 CREATE FUNCTION "write_event_unit_trigger"()
979 RETURNS TRIGGER
980 LANGUAGE 'plpgsql' VOLATILE AS $$
981 DECLARE
982 "event_v" "event_type";
983 BEGIN
984 IF TG_OP = 'UPDATE' THEN
985 IF OLD."active" = FALSE AND NEW."active" = FALSE THEN
986 RETURN NULL;
987 --ELSIF OLD."active" = FALSE AND NEW."active" = TRUE THEN
988 -- "event_v" := 'unit_created';
989 --ELSIF OLD."active" = TRUE AND NEW."active" = FALSE THEN
990 -- "event_v" := 'unit_removed';
991 ELSIF OLD != NEW THEN
992 "event_v" := 'unit_updated';
993 ELSE
994 RETURN NULL;
995 END IF;
996 ELSE
997 "event_v" := 'unit_created';
998 END IF;
999 INSERT INTO "event" ("event", "unit_id") VALUES ("event_v", NEW."id");
1000 RETURN NULL;
1001 END;
1002 $$;
1004 CREATE TRIGGER "write_event_unit" AFTER INSERT OR UPDATE ON "unit"
1005 FOR EACH ROW EXECUTE PROCEDURE "write_event_unit_trigger"();
1007 COMMENT ON FUNCTION "write_event_unit_trigger"() IS 'Implementation of trigger "write_event_unit" on table "unit"';
1008 COMMENT ON TRIGGER "write_event_unit" ON "unit" IS 'Create entry in "event" table on new or changed/disabled units';
1011 CREATE FUNCTION "write_event_area_trigger"()
1012 RETURNS TRIGGER
1013 LANGUAGE 'plpgsql' VOLATILE AS $$
1014 DECLARE
1015 "event_v" "event_type";
1016 BEGIN
1017 IF TG_OP = 'UPDATE' THEN
1018 IF OLD."active" = FALSE AND NEW."active" = FALSE THEN
1019 RETURN NULL;
1020 --ELSIF OLD."active" = FALSE AND NEW."active" = TRUE THEN
1021 -- "event_v" := 'area_created';
1022 --ELSIF OLD."active" = TRUE AND NEW."active" = FALSE THEN
1023 -- "event_v" := 'area_removed';
1024 ELSIF OLD != NEW THEN
1025 "event_v" := 'area_updated';
1026 ELSE
1027 RETURN NULL;
1028 END IF;
1029 ELSE
1030 "event_v" := 'area_created';
1031 END IF;
1032 INSERT INTO "event" ("event", "area_id") VALUES ("event_v", NEW."id");
1033 RETURN NULL;
1034 END;
1035 $$;
1037 CREATE TRIGGER "write_event_area" AFTER INSERT OR UPDATE ON "area"
1038 FOR EACH ROW EXECUTE PROCEDURE "write_event_area_trigger"();
1040 COMMENT ON FUNCTION "write_event_area_trigger"() IS 'Implementation of trigger "write_event_area" on table "area"';
1041 COMMENT ON TRIGGER "write_event_area" ON "area" IS 'Create entry in "event" table on new or changed/disabled areas';
1044 CREATE FUNCTION "write_event_policy_trigger"()
1045 RETURNS TRIGGER
1046 LANGUAGE 'plpgsql' VOLATILE AS $$
1047 DECLARE
1048 "event_v" "event_type";
1049 BEGIN
1050 IF TG_OP = 'UPDATE' THEN
1051 IF OLD."active" = FALSE AND NEW."active" = FALSE THEN
1052 RETURN NULL;
1053 --ELSIF OLD."active" = FALSE AND NEW."active" = TRUE THEN
1054 -- "event_v" := 'policy_created';
1055 --ELSIF OLD."active" = TRUE AND NEW."active" = FALSE THEN
1056 -- "event_v" := 'policy_removed';
1057 ELSIF OLD != NEW THEN
1058 "event_v" := 'policy_updated';
1059 ELSE
1060 RETURN NULL;
1061 END IF;
1062 ELSE
1063 "event_v" := 'policy_created';
1064 END IF;
1065 INSERT INTO "event" ("event", "policy_id") VALUES ("event_v", NEW."id");
1066 RETURN NULL;
1067 END;
1068 $$;
1070 CREATE TRIGGER "write_event_policy" AFTER INSERT OR UPDATE ON "policy"
1071 FOR EACH ROW EXECUTE PROCEDURE "write_event_policy_trigger"();
1073 COMMENT ON FUNCTION "write_event_policy_trigger"() IS 'Implementation of trigger "write_event_policy" on table "policy"';
1074 COMMENT ON TRIGGER "write_event_policy" ON "policy" IS 'Create entry in "event" table on new or changed/disabled policies';
1077 CREATE OR REPLACE FUNCTION "write_event_issue_state_changed_trigger"()
1078 RETURNS TRIGGER
1079 LANGUAGE 'plpgsql' VOLATILE AS $$
1080 DECLARE
1081 "area_row" "area"%ROWTYPE;
1082 BEGIN
1083 IF NEW."state" != OLD."state" THEN
1084 SELECT * INTO "area_row" FROM "area" WHERE "id" = NEW."area_id"
1085 FOR SHARE;
1086 INSERT INTO "event" (
1087 "event",
1088 "unit_id", "area_id", "policy_id", "issue_id", "state"
1089 ) VALUES (
1090 'issue_state_changed',
1091 "area_row"."unit_id", NEW."area_id", NEW."policy_id",
1092 NEW."id", NEW."state"
1093 );
1094 END IF;
1095 RETURN NULL;
1096 END;
1097 $$;
1100 CREATE OR REPLACE FUNCTION "write_event_initiative_or_draft_created_trigger"()
1101 RETURNS TRIGGER
1102 LANGUAGE 'plpgsql' VOLATILE AS $$
1103 DECLARE
1104 "initiative_row" "initiative"%ROWTYPE;
1105 "issue_row" "issue"%ROWTYPE;
1106 "area_row" "area"%ROWTYPE;
1107 "event_v" "event_type";
1108 BEGIN
1109 SELECT * INTO "initiative_row" FROM "initiative"
1110 WHERE "id" = NEW."initiative_id" FOR SHARE;
1111 SELECT * INTO "issue_row" FROM "issue"
1112 WHERE "id" = "initiative_row"."issue_id" FOR SHARE;
1113 SELECT * INTO "area_row" FROM "area"
1114 WHERE "id" = "issue_row"."area_id" FOR SHARE;
1115 IF EXISTS (
1116 SELECT NULL FROM "draft"
1117 WHERE "initiative_id" = NEW."initiative_id" AND "id" != NEW."id"
1118 FOR SHARE
1119 ) THEN
1120 "event_v" := 'new_draft_created';
1121 ELSE
1122 IF EXISTS (
1123 SELECT NULL FROM "initiative"
1124 WHERE "issue_id" = "initiative_row"."issue_id"
1125 AND "id" != "initiative_row"."id"
1126 FOR SHARE
1127 ) THEN
1128 "event_v" := 'initiative_created_in_existing_issue';
1129 ELSE
1130 "event_v" := 'initiative_created_in_new_issue';
1131 END IF;
1132 END IF;
1133 INSERT INTO "event" (
1134 "event", "member_id",
1135 "unit_id", "area_id", "policy_id", "issue_id", "state",
1136 "initiative_id", "draft_id"
1137 ) VALUES (
1138 "event_v", NEW."author_id",
1139 "area_row"."unit_id", "issue_row"."area_id", "issue_row"."policy_id",
1140 "initiative_row"."issue_id", "issue_row"."state",
1141 NEW."initiative_id", NEW."id"
1142 );
1143 RETURN NULL;
1144 END;
1145 $$;
1148 CREATE OR REPLACE FUNCTION "write_event_initiative_revoked_trigger"()
1149 RETURNS TRIGGER
1150 LANGUAGE 'plpgsql' VOLATILE AS $$
1151 DECLARE
1152 "issue_row" "issue"%ROWTYPE;
1153 "area_row" "area"%ROWTYPE;
1154 "draft_id_v" "draft"."id"%TYPE;
1155 BEGIN
1156 IF OLD."revoked" ISNULL AND NEW."revoked" NOTNULL THEN
1157 SELECT * INTO "issue_row" FROM "issue"
1158 WHERE "id" = NEW."issue_id" FOR SHARE;
1159 SELECT * INTO "area_row" FROM "area"
1160 WHERE "id" = "issue_row"."area_id" FOR SHARE;
1161 SELECT "id" INTO "draft_id_v" FROM "current_draft"
1162 WHERE "initiative_id" = NEW."id" FOR SHARE;
1163 INSERT INTO "event" (
1164 "event", "member_id",
1165 "unit_id", "area_id", "policy_id", "issue_id", "state",
1166 "initiative_id", "draft_id"
1167 ) VALUES (
1168 'initiative_revoked', NEW."revoked_by_member_id",
1169 "area_row"."unit_id", "issue_row"."area_id",
1170 "issue_row"."policy_id",
1171 NEW."issue_id", "issue_row"."state",
1172 NEW."id", "draft_id_v"
1173 );
1174 END IF;
1175 RETURN NULL;
1176 END;
1177 $$;
1180 CREATE OR REPLACE FUNCTION "write_event_suggestion_created_trigger"()
1181 RETURNS TRIGGER
1182 LANGUAGE 'plpgsql' VOLATILE AS $$
1183 DECLARE
1184 "initiative_row" "initiative"%ROWTYPE;
1185 "issue_row" "issue"%ROWTYPE;
1186 "area_row" "area"%ROWTYPE;
1187 BEGIN
1188 SELECT * INTO "initiative_row" FROM "initiative"
1189 WHERE "id" = NEW."initiative_id" FOR SHARE;
1190 SELECT * INTO "issue_row" FROM "issue"
1191 WHERE "id" = "initiative_row"."issue_id" FOR SHARE;
1192 SELECT * INTO "area_row" FROM "area"
1193 WHERE "id" = "issue_row"."area_id" FOR SHARE;
1194 INSERT INTO "event" (
1195 "event", "member_id",
1196 "unit_id", "area_id", "policy_id", "issue_id", "state",
1197 "initiative_id", "suggestion_id"
1198 ) VALUES (
1199 'suggestion_created', NEW."author_id",
1200 "area_row"."unit_id", "issue_row"."area_id", "issue_row"."policy_id",
1201 "initiative_row"."issue_id", "issue_row"."state",
1202 NEW."initiative_id", NEW."id"
1203 );
1204 RETURN NULL;
1205 END;
1206 $$;
1209 CREATE FUNCTION "write_event_suggestion_removed_trigger"()
1210 RETURNS TRIGGER
1211 LANGUAGE 'plpgsql' VOLATILE AS $$
1212 DECLARE
1213 "initiative_row" "initiative"%ROWTYPE;
1214 "issue_row" "issue"%ROWTYPE;
1215 "area_row" "area"%ROWTYPE;
1216 BEGIN
1217 SELECT * INTO "initiative_row" FROM "initiative"
1218 WHERE "id" = OLD."initiative_id" FOR SHARE;
1219 IF "initiative_row"."id" NOTNULL THEN
1220 SELECT * INTO "issue_row" FROM "issue"
1221 WHERE "id" = "initiative_row"."issue_id" FOR SHARE;
1222 SELECT * INTO "area_row" FROM "area"
1223 WHERE "id" = "issue_row"."area_id" FOR SHARE;
1224 INSERT INTO "event" (
1225 "event",
1226 "unit_id", "area_id", "policy_id", "issue_id", "state",
1227 "initiative_id", "suggestion_id"
1228 ) VALUES (
1229 'suggestion_removed',
1230 "area_row"."unit_id", "issue_row"."area_id",
1231 "issue_row"."policy_id",
1232 "initiative_row"."issue_id", "issue_row"."state",
1233 OLD."initiative_id", OLD."id"
1234 );
1235 END IF;
1236 RETURN NULL;
1237 END;
1238 $$;
1240 CREATE TRIGGER "write_event_suggestion_removed"
1241 AFTER DELETE ON "suggestion" FOR EACH ROW EXECUTE PROCEDURE
1242 "write_event_suggestion_removed_trigger"();
1244 COMMENT ON FUNCTION "write_event_suggestion_removed_trigger"() IS 'Implementation of trigger "write_event_suggestion_removed" on table "issue"';
1245 COMMENT ON TRIGGER "write_event_suggestion_removed" ON "suggestion" IS 'Create entry in "event" table on suggestion creation';
1248 CREATE FUNCTION "write_event_member_trigger"()
1249 RETURNS TRIGGER
1250 LANGUAGE 'plpgsql' VOLATILE AS $$
1251 BEGIN
1252 IF TG_OP = 'INSERT' THEN
1253 IF NEW."activated" NOTNULL THEN
1254 INSERT INTO "event" ("event", "member_id")
1255 VALUES ('member_activated', NEW."id");
1256 END IF;
1257 IF NEW."active" THEN
1258 INSERT INTO "event" ("event", "member_id", "boolean_value")
1259 VALUES ('member_active', NEW."id", TRUE);
1260 END IF;
1261 ELSIF TG_OP = 'UPDATE' THEN
1262 IF OLD."id" != NEW."id" THEN
1263 RAISE EXCEPTION 'Cannot change member ID';
1264 END IF;
1265 IF OLD."name" != NEW."name" THEN
1266 INSERT INTO "event" (
1267 "event", "member_id", "text_value", "old_text_value"
1268 ) VALUES (
1269 'member_name_updated', NEW."id", NEW."name", OLD."name"
1270 );
1271 END IF;
1272 IF OLD."active" != NEW."active" THEN
1273 INSERT INTO "event" ("event", "member_id", "boolean_value") VALUES (
1274 'member_active', NEW."id", NEW."active"
1275 );
1276 END IF;
1277 IF
1278 OLD."activated" NOTNULL AND
1279 (OLD."login" NOTNULL OR OLD."authority_login" NOTNULL) AND
1280 NEW."login" ISNULL AND
1281 NEW."authority_login" ISNULL AND
1282 NEW."locked" = TRUE
1283 THEN
1284 INSERT INTO "event" ("event", "member_id")
1285 VALUES ('member_removed', NEW."id");
1286 END IF;
1287 END IF;
1288 RETURN NULL;
1289 END;
1290 $$;
1292 CREATE TRIGGER "write_event_member"
1293 AFTER INSERT OR UPDATE ON "member" FOR EACH ROW EXECUTE PROCEDURE
1294 "write_event_member_trigger"();
1296 COMMENT ON FUNCTION "write_event_member_trigger"() IS 'Implementation of trigger "write_event_member" on table "member"';
1297 COMMENT ON TRIGGER "write_event_member" ON "member" IS 'Create entries in "event" table on insertion to member table';
1300 CREATE FUNCTION "write_event_member_profile_updated_trigger"()
1301 RETURNS TRIGGER
1302 LANGUAGE 'plpgsql' VOLATILE AS $$
1303 BEGIN
1304 IF TG_OP = 'DELETE' OR TG_OP = 'UPDATE' THEN
1305 IF EXISTS (SELECT NULL FROM "member" WHERE "id" = OLD."member_id") THEN
1306 INSERT INTO "event" ("event", "member_id") VALUES (
1307 'member_profile_updated', OLD."member_id"
1308 );
1309 END IF;
1310 END IF;
1311 IF TG_OP = 'UPDATE' THEN
1312 IF OLD."member_id" = NEW."member_id" THEN
1313 RETURN NULL;
1314 END IF;
1315 END IF;
1316 IF TG_OP = 'INSERT' OR TG_OP = 'UPDATE' THEN
1317 INSERT INTO "event" ("event", "member_id") VALUES (
1318 'member_profile_updated', NEW."member_id"
1319 );
1320 END IF;
1321 RETURN NULL;
1322 END;
1323 $$;
1325 CREATE TRIGGER "write_event_member_profile_updated"
1326 AFTER INSERT OR UPDATE OR DELETE ON "member_profile"
1327 FOR EACH ROW EXECUTE PROCEDURE
1328 "write_event_member_profile_updated_trigger"();
1330 COMMENT ON FUNCTION "write_event_member_profile_updated_trigger"() IS 'Implementation of trigger "write_event_member_profile_updated" on table "member_profile"';
1331 COMMENT ON TRIGGER "write_event_member_profile_updated" ON "member_profile" IS 'Creates entries in "event" table on member profile update';
1334 CREATE FUNCTION "write_event_member_image_updated_trigger"()
1335 RETURNS TRIGGER
1336 LANGUAGE 'plpgsql' VOLATILE AS $$
1337 BEGIN
1338 IF TG_OP = 'DELETE' OR TG_OP = 'UPDATE' THEN
1339 IF NOT OLD."scaled" THEN
1340 IF EXISTS (SELECT NULL FROM "member" WHERE "id" = OLD."member_id") THEN
1341 INSERT INTO "event" ("event", "member_id") VALUES (
1342 'member_image_updated', OLD."member_id"
1343 );
1344 END IF;
1345 END IF;
1346 END IF;
1347 IF TG_OP = 'UPDATE' THEN
1348 IF
1349 OLD."member_id" = NEW."member_id" AND
1350 OLD."scaled" = NEW."scaled"
1351 THEN
1352 RETURN NULL;
1353 END IF;
1354 END IF;
1355 IF TG_OP = 'INSERT' OR TG_OP = 'UPDATE' THEN
1356 IF NOT NEW."scaled" THEN
1357 INSERT INTO "event" ("event", "member_id") VALUES (
1358 'member_image_updated', NEW."member_id"
1359 );
1360 END IF;
1361 END IF;
1362 RETURN NULL;
1363 END;
1364 $$;
1366 CREATE TRIGGER "write_event_member_image_updated"
1367 AFTER INSERT OR UPDATE OR DELETE ON "member_image"
1368 FOR EACH ROW EXECUTE PROCEDURE
1369 "write_event_member_image_updated_trigger"();
1371 COMMENT ON FUNCTION "write_event_member_image_updated_trigger"() IS 'Implementation of trigger "write_event_member_image_updated" on table "member_image"';
1372 COMMENT ON TRIGGER "write_event_member_image_updated" ON "member_image" IS 'Creates entries in "event" table on member image update';
1375 CREATE FUNCTION "write_event_interest_trigger"()
1376 RETURNS TRIGGER
1377 LANGUAGE 'plpgsql' VOLATILE AS $$
1378 DECLARE
1379 "issue_row" "issue"%ROWTYPE;
1380 "area_row" "area"%ROWTYPE;
1381 BEGIN
1382 IF TG_OP = 'UPDATE' THEN
1383 IF OLD = NEW THEN
1384 RETURN NULL;
1385 END IF;
1386 END IF;
1387 IF TG_OP = 'DELETE' OR TG_OP = 'UPDATE' THEN
1388 SELECT * INTO "issue_row" FROM "issue"
1389 WHERE "id" = OLD."issue_id" FOR SHARE;
1390 SELECT * INTO "area_row" FROM "area"
1391 WHERE "id" = "issue_row"."area_id" FOR SHARE;
1392 IF "issue_row"."id" NOTNULL THEN
1393 INSERT INTO "event" (
1394 "event", "member_id",
1395 "unit_id", "area_id", "policy_id", "issue_id", "state",
1396 "boolean_value"
1397 ) VALUES (
1398 'interest', OLD."member_id",
1399 "area_row"."unit_id", "issue_row"."area_id",
1400 "issue_row"."policy_id",
1401 OLD."issue_id", "issue_row"."state",
1402 FALSE
1403 );
1404 END IF;
1405 END IF;
1406 IF TG_OP = 'INSERT' OR TG_OP = 'UPDATE' THEN
1407 SELECT * INTO "issue_row" FROM "issue"
1408 WHERE "id" = NEW."issue_id" FOR SHARE;
1409 SELECT * INTO "area_row" FROM "area"
1410 WHERE "id" = "issue_row"."area_id" FOR SHARE;
1411 INSERT INTO "event" (
1412 "event", "member_id",
1413 "unit_id", "area_id", "policy_id", "issue_id", "state",
1414 "boolean_value"
1415 ) VALUES (
1416 'interest', NEW."member_id",
1417 "area_row"."unit_id", "issue_row"."area_id",
1418 "issue_row"."policy_id",
1419 NEW."issue_id", "issue_row"."state",
1420 TRUE
1421 );
1422 END IF;
1423 RETURN NULL;
1424 END;
1425 $$;
1427 CREATE TRIGGER "write_event_interest"
1428 AFTER INSERT OR UPDATE OR DELETE ON "interest" FOR EACH ROW EXECUTE PROCEDURE
1429 "write_event_interest_trigger"();
1431 COMMENT ON FUNCTION "write_event_interest_trigger"() IS 'Implementation of trigger "write_event_interest_inserted" on table "interest"';
1432 COMMENT ON TRIGGER "write_event_interest" ON "interest" IS 'Create entry in "event" table on adding or removing interest';
1435 CREATE FUNCTION "write_event_initiator_trigger"()
1436 RETURNS TRIGGER
1437 LANGUAGE 'plpgsql' VOLATILE AS $$
1438 DECLARE
1439 "initiative_row" "initiative"%ROWTYPE;
1440 "issue_row" "issue"%ROWTYPE;
1441 "area_row" "area"%ROWTYPE;
1442 "accepted_v" BOOLEAN = FALSE;
1443 "rejected_v" BOOLEAN = FALSE;
1444 BEGIN
1445 IF TG_OP = 'UPDATE' THEN
1446 IF
1447 OLD."initiative_id" = NEW."initiative_id" AND
1448 OLD."member_id" = NEW."member_id"
1449 THEN
1450 IF
1451 coalesce(OLD."accepted", FALSE) = coalesce(NEW."accepted", FALSE)
1452 THEN
1453 RETURN NULL;
1454 END IF;
1455 IF coalesce(NEW."accepted", FALSE) = TRUE THEN
1456 "accepted_v" := TRUE;
1457 ELSE
1458 "rejected_v" := TRUE;
1459 END IF;
1460 END IF;
1461 END IF;
1462 IF (TG_OP = 'DELETE' OR TG_OP = 'UPDATE') AND NOT "accepted_v" THEN
1463 IF coalesce(OLD."accepted", FALSE) = TRUE THEN
1464 SELECT * INTO "initiative_row" FROM "initiative"
1465 WHERE "id" = OLD."initiative_id" FOR SHARE;
1466 IF "initiative_row"."id" NOTNULL THEN
1467 SELECT * INTO "issue_row" FROM "issue"
1468 WHERE "id" = "initiative_row"."issue_id" FOR SHARE;
1469 SELECT * INTO "area_row" FROM "area"
1470 WHERE "id" = "issue_row"."area_id" FOR SHARE;
1471 INSERT INTO "event" (
1472 "event", "member_id",
1473 "unit_id", "area_id", "policy_id", "issue_id", "state",
1474 "initiative_id", "boolean_value"
1475 ) VALUES (
1476 'initiator', OLD."member_id",
1477 "area_row"."unit_id", "issue_row"."area_id",
1478 "issue_row"."policy_id",
1479 "issue_row"."id", "issue_row"."state",
1480 OLD."initiative_id", FALSE
1481 );
1482 END IF;
1483 END IF;
1484 END IF;
1485 IF TG_OP = 'UPDATE' AND NOT "rejected_v" THEN
1486 IF coalesce(NEW."accepted", FALSE) = TRUE THEN
1487 SELECT * INTO "initiative_row" FROM "initiative"
1488 WHERE "id" = NEW."initiative_id" FOR SHARE;
1489 SELECT * INTO "issue_row" FROM "issue"
1490 WHERE "id" = "initiative_row"."issue_id" FOR SHARE;
1491 SELECT * INTO "area_row" FROM "area"
1492 WHERE "id" = "issue_row"."area_id" FOR SHARE;
1493 INSERT INTO "event" (
1494 "event", "member_id",
1495 "unit_id", "area_id", "policy_id", "issue_id", "state",
1496 "initiative_id", "boolean_value"
1497 ) VALUES (
1498 'initiator', NEW."member_id",
1499 "area_row"."unit_id", "issue_row"."area_id",
1500 "issue_row"."policy_id",
1501 "issue_row"."id", "issue_row"."state",
1502 NEW."initiative_id", TRUE
1503 );
1504 END IF;
1505 END IF;
1506 RETURN NULL;
1507 END;
1508 $$;
1510 CREATE TRIGGER "write_event_initiator"
1511 AFTER UPDATE OR DELETE ON "initiator" FOR EACH ROW EXECUTE PROCEDURE
1512 "write_event_initiator_trigger"();
1514 COMMENT ON FUNCTION "write_event_initiator_trigger"() IS 'Implementation of trigger "write_event_initiator" on table "initiator"';
1515 COMMENT ON TRIGGER "write_event_initiator" ON "initiator" IS 'Create entry in "event" table when accepting or removing initiatorship (NOTE: trigger does not fire on INSERT to avoid events on initiative creation)';
1518 CREATE FUNCTION "write_event_support_trigger"()
1519 RETURNS TRIGGER
1520 LANGUAGE 'plpgsql' VOLATILE AS $$
1521 DECLARE
1522 "issue_row" "issue"%ROWTYPE;
1523 "area_row" "area"%ROWTYPE;
1524 BEGIN
1525 IF TG_OP = 'UPDATE' THEN
1526 IF
1527 OLD."initiative_id" = NEW."initiative_id" AND
1528 OLD."member_id" = NEW."member_id"
1529 THEN
1530 IF OLD."draft_id" != NEW."draft_id" THEN
1531 SELECT * INTO "issue_row" FROM "issue"
1532 WHERE "id" = NEW."issue_id" FOR SHARE;
1533 SELECT * INTO "area_row" FROM "area"
1534 WHERE "id" = "issue_row"."area_id" FOR SHARE;
1535 INSERT INTO "event" (
1536 "event", "member_id",
1537 "unit_id", "area_id", "policy_id", "issue_id", "state",
1538 "initiative_id", "draft_id"
1539 ) VALUES (
1540 'support_updated', NEW."member_id",
1541 "area_row"."unit_id", "issue_row"."area_id",
1542 "issue_row"."policy_id",
1543 "issue_row"."id", "issue_row"."state",
1544 NEW."initiative_id", NEW."draft_id"
1545 );
1546 END IF;
1547 RETURN NULL;
1548 END IF;
1549 END IF;
1550 IF TG_OP = 'DELETE' OR TG_OP = 'UPDATE' THEN
1551 IF EXISTS (
1552 SELECT NULL FROM "initiative" WHERE "id" = OLD."initiative_id"
1553 FOR SHARE
1554 ) THEN
1555 SELECT * INTO "issue_row" FROM "issue"
1556 WHERE "id" = OLD."issue_id" FOR SHARE;
1557 SELECT * INTO "area_row" FROM "area"
1558 WHERE "id" = "issue_row"."area_id" FOR SHARE;
1559 INSERT INTO "event" (
1560 "event", "member_id",
1561 "unit_id", "area_id", "policy_id", "issue_id", "state",
1562 "initiative_id", "boolean_value"
1563 ) VALUES (
1564 'support', OLD."member_id",
1565 "area_row"."unit_id", "issue_row"."area_id",
1566 "issue_row"."policy_id",
1567 "issue_row"."id", "issue_row"."state",
1568 OLD."initiative_id", FALSE
1569 );
1570 END IF;
1571 END IF;
1572 IF TG_OP = 'INSERT' OR TG_OP = 'UPDATE' THEN
1573 SELECT * INTO "issue_row" FROM "issue"
1574 WHERE "id" = NEW."issue_id" FOR SHARE;
1575 SELECT * INTO "area_row" FROM "area"
1576 WHERE "id" = "issue_row"."area_id" FOR SHARE;
1577 INSERT INTO "event" (
1578 "event", "member_id",
1579 "unit_id", "area_id", "policy_id", "issue_id", "state",
1580 "initiative_id", "draft_id", "boolean_value"
1581 ) VALUES (
1582 'support', NEW."member_id",
1583 "area_row"."unit_id", "issue_row"."area_id",
1584 "issue_row"."policy_id",
1585 "issue_row"."id", "issue_row"."state",
1586 NEW."initiative_id", NEW."draft_id", TRUE
1587 );
1588 END IF;
1589 RETURN NULL;
1590 END;
1591 $$;
1593 CREATE TRIGGER "write_event_support"
1594 AFTER INSERT OR UPDATE OR DELETE ON "supporter" FOR EACH ROW EXECUTE PROCEDURE
1595 "write_event_support_trigger"();
1597 COMMENT ON FUNCTION "write_event_support_trigger"() IS 'Implementation of trigger "write_event_support" on table "supporter"';
1598 COMMENT ON TRIGGER "write_event_support" ON "supporter" IS 'Create entry in "event" table when adding, updating, or removing support';
1601 CREATE FUNCTION "write_event_suggestion_rated_trigger"()
1602 RETURNS TRIGGER
1603 LANGUAGE 'plpgsql' VOLATILE AS $$
1604 DECLARE
1605 "same_pkey_v" BOOLEAN = FALSE;
1606 "initiative_row" "initiative"%ROWTYPE;
1607 "issue_row" "issue"%ROWTYPE;
1608 "area_row" "area"%ROWTYPE;
1609 BEGIN
1610 IF TG_OP = 'UPDATE' THEN
1611 IF
1612 OLD."suggestion_id" = NEW."suggestion_id" AND
1613 OLD."member_id" = NEW."member_id"
1614 THEN
1615 IF
1616 OLD."degree" = NEW."degree" AND
1617 OLD."fulfilled" = NEW."fulfilled"
1618 THEN
1619 RETURN NULL;
1620 END IF;
1621 "same_pkey_v" := TRUE;
1622 END IF;
1623 END IF;
1624 IF (TG_OP = 'DELETE' OR TG_OP = 'UPDATE') AND NOT "same_pkey_v" THEN
1625 IF EXISTS (
1626 SELECT NULL FROM "suggestion" WHERE "id" = OLD."suggestion_id"
1627 FOR SHARE
1628 ) THEN
1629 SELECT * INTO "initiative_row" FROM "initiative"
1630 WHERE "id" = OLD."initiative_id" FOR SHARE;
1631 SELECT * INTO "issue_row" FROM "issue"
1632 WHERE "id" = "initiative_row"."issue_id" FOR SHARE;
1633 SELECT * INTO "area_row" FROM "area"
1634 WHERE "id" = "issue_row"."area_id" FOR SHARE;
1635 INSERT INTO "event" (
1636 "event", "member_id",
1637 "unit_id", "area_id", "policy_id", "issue_id", "state",
1638 "initiative_id", "suggestion_id",
1639 "boolean_value", "numeric_value"
1640 ) VALUES (
1641 'suggestion_rated', OLD."member_id",
1642 "area_row"."unit_id", "issue_row"."area_id",
1643 "issue_row"."policy_id",
1644 "initiative_row"."issue_id", "issue_row"."state",
1645 OLD."initiative_id", OLD."suggestion_id",
1646 NULL, 0
1647 );
1648 END IF;
1649 END IF;
1650 IF TG_OP = 'INSERT' OR TG_OP = 'UPDATE' THEN
1651 SELECT * INTO "initiative_row" FROM "initiative"
1652 WHERE "id" = NEW."initiative_id" FOR SHARE;
1653 SELECT * INTO "issue_row" FROM "issue"
1654 WHERE "id" = "initiative_row"."issue_id" FOR SHARE;
1655 SELECT * INTO "area_row" FROM "area"
1656 WHERE "id" = "issue_row"."area_id" FOR SHARE;
1657 INSERT INTO "event" (
1658 "event", "member_id",
1659 "unit_id", "area_id", "policy_id", "issue_id", "state",
1660 "initiative_id", "suggestion_id",
1661 "boolean_value", "numeric_value"
1662 ) VALUES (
1663 'suggestion_rated', NEW."member_id",
1664 "area_row"."unit_id", "issue_row"."area_id",
1665 "issue_row"."policy_id",
1666 "initiative_row"."issue_id", "issue_row"."state",
1667 NEW."initiative_id", NEW."suggestion_id",
1668 NEW."fulfilled", NEW."degree"
1669 );
1670 END IF;
1671 RETURN NULL;
1672 END;
1673 $$;
1675 CREATE TRIGGER "write_event_suggestion_rated"
1676 AFTER INSERT OR UPDATE OR DELETE ON "opinion" FOR EACH ROW EXECUTE PROCEDURE
1677 "write_event_suggestion_rated_trigger"();
1679 COMMENT ON FUNCTION "write_event_suggestion_rated_trigger"() IS 'Implementation of trigger "write_event_suggestion_rated" on table "opinion"';
1680 COMMENT ON TRIGGER "write_event_suggestion_rated" ON "opinion" IS 'Create entry in "event" table when adding, updating, or removing support';
1683 CREATE FUNCTION "write_event_delegation_trigger"()
1684 RETURNS TRIGGER
1685 LANGUAGE 'plpgsql' VOLATILE AS $$
1686 DECLARE
1687 "issue_row" "issue"%ROWTYPE;
1688 "area_row" "area"%ROWTYPE;
1689 BEGIN
1690 IF TG_OP = 'DELETE' THEN
1691 IF EXISTS (
1692 SELECT NULL FROM "member" WHERE "id" = OLD."truster_id"
1693 ) AND (CASE OLD."scope"
1694 WHEN 'unit'::"delegation_scope" THEN EXISTS (
1695 SELECT NULL FROM "unit" WHERE "id" = OLD."unit_id"
1697 WHEN 'area'::"delegation_scope" THEN EXISTS (
1698 SELECT NULL FROM "area" WHERE "id" = OLD."area_id"
1700 WHEN 'issue'::"delegation_scope" THEN EXISTS (
1701 SELECT NULL FROM "issue" WHERE "id" = OLD."issue_id"
1703 END) THEN
1704 SELECT * INTO "issue_row" FROM "issue"
1705 WHERE "id" = OLD."issue_id" FOR SHARE;
1706 SELECT * INTO "area_row" FROM "area"
1707 WHERE "id" = COALESCE(OLD."area_id", "issue_row"."area_id")
1708 FOR SHARE;
1709 INSERT INTO "event" (
1710 "event", "member_id", "scope",
1711 "unit_id", "area_id", "issue_id", "state",
1712 "boolean_value"
1713 ) VALUES (
1714 'delegation', OLD."truster_id", OLD."scope",
1715 COALESCE(OLD."unit_id", "area_row"."unit_id"), "area_row"."id",
1716 OLD."issue_id", "issue_row"."state",
1717 FALSE
1718 );
1719 END IF;
1720 ELSE
1721 SELECT * INTO "issue_row" FROM "issue"
1722 WHERE "id" = NEW."issue_id" FOR SHARE;
1723 SELECT * INTO "area_row" FROM "area"
1724 WHERE "id" = COALESCE(NEW."area_id", "issue_row"."area_id")
1725 FOR SHARE;
1726 INSERT INTO "event" (
1727 "event", "member_id", "other_member_id", "scope",
1728 "unit_id", "area_id", "issue_id", "state",
1729 "boolean_value"
1730 ) VALUES (
1731 'delegation', NEW."truster_id", NEW."trustee_id", NEW."scope",
1732 COALESCE(NEW."unit_id", "area_row"."unit_id"), "area_row"."id",
1733 NEW."issue_id", "issue_row"."state",
1734 TRUE
1735 );
1736 END IF;
1737 RETURN NULL;
1738 END;
1739 $$;
1741 CREATE TRIGGER "write_event_delegation"
1742 AFTER INSERT OR UPDATE OR DELETE ON "delegation" FOR EACH ROW EXECUTE PROCEDURE
1743 "write_event_delegation_trigger"();
1745 COMMENT ON FUNCTION "write_event_delegation_trigger"() IS 'Implementation of trigger "write_event_delegation" on table "delegation"';
1746 COMMENT ON TRIGGER "write_event_delegation" ON "delegation" IS 'Create entry in "event" table when adding, updating, or removing a delegation';
1749 CREATE FUNCTION "write_event_contact_trigger"()
1750 RETURNS TRIGGER
1751 LANGUAGE 'plpgsql' VOLATILE AS $$
1752 BEGIN
1753 IF TG_OP = 'UPDATE' THEN
1754 IF
1755 OLD."member_id" = NEW."member_id" AND
1756 OLD."other_member_id" = NEW."other_member_id" AND
1757 OLD."public" = NEW."public"
1758 THEN
1759 RETURN NULL;
1760 END IF;
1761 END IF;
1762 IF TG_OP = 'DELETE' OR TG_OP = 'UPDATE' THEN
1763 IF OLD."public" THEN
1764 IF EXISTS (
1765 SELECT NULL FROM "member" WHERE "id" = OLD."member_id"
1766 FOR SHARE
1767 ) AND EXISTS (
1768 SELECT NULL FROM "member" WHERE "id" = OLD."other_member_id"
1769 FOR SHARE
1770 ) THEN
1771 INSERT INTO "event" (
1772 "event", "member_id", "other_member_id", "boolean_value"
1773 ) VALUES (
1774 'contact', OLD."member_id", OLD."other_member_id", FALSE
1775 );
1776 END IF;
1777 END IF;
1778 END IF;
1779 IF TG_OP = 'INSERT' OR TG_OP = 'UPDATE' THEN
1780 IF NEW."public" THEN
1781 INSERT INTO "event" (
1782 "event", "member_id", "other_member_id", "boolean_value"
1783 ) VALUES (
1784 'contact', NEW."member_id", NEW."other_member_id", TRUE
1785 );
1786 END IF;
1787 END IF;
1788 RETURN NULL;
1789 END;
1790 $$;
1792 CREATE TRIGGER "write_event_contact"
1793 AFTER INSERT OR UPDATE OR DELETE ON "contact" FOR EACH ROW EXECUTE PROCEDURE
1794 "write_event_contact_trigger"();
1796 COMMENT ON FUNCTION "write_event_contact_trigger"() IS 'Implementation of trigger "write_event_contact" on table "contact"';
1797 COMMENT ON TRIGGER "write_event_contact" ON "contact" IS 'Create entry in "event" table when adding or removing public contacts';
1800 CREATE FUNCTION "send_event_notify_trigger"()
1801 RETURNS TRIGGER
1802 LANGUAGE 'plpgsql' VOLATILE AS $$
1803 BEGIN
1804 EXECUTE 'NOTIFY "event", ''' || NEW."event" || '''';
1805 RETURN NULL;
1806 END;
1807 $$;
1809 CREATE TRIGGER "send_notify"
1810 AFTER INSERT OR UPDATE ON "event" FOR EACH ROW EXECUTE PROCEDURE
1811 "send_event_notify_trigger"();
1814 CREATE FUNCTION "delete_extended_scope_tokens_trigger"()
1815 RETURNS TRIGGER
1816 LANGUAGE 'plpgsql' VOLATILE AS $$
1817 DECLARE
1818 "system_application_row" "system_application"%ROWTYPE;
1819 BEGIN
1820 IF OLD."system_application_id" NOTNULL THEN
1821 SELECT * FROM "system_application" INTO "system_application_row"
1822 WHERE "id" = OLD."system_application_id";
1823 DELETE FROM "token"
1824 WHERE "member_id" = OLD."member_id"
1825 AND "system_application_id" = OLD."system_application_id"
1826 AND NOT COALESCE(
1827 regexp_split_to_array("scope", E'\\s+') <@
1828 regexp_split_to_array(
1829 "system_application_row"."automatic_scope", E'\\s+'
1830 ),
1831 FALSE
1832 );
1833 END IF;
1834 RETURN OLD;
1835 END;
1836 $$;
1838 CREATE TRIGGER "delete_extended_scope_tokens"
1839 BEFORE DELETE ON "member_application" FOR EACH ROW EXECUTE PROCEDURE
1840 "delete_extended_scope_tokens_trigger"();
1843 CREATE FUNCTION "detach_token_from_session_trigger"()
1844 RETURNS TRIGGER
1845 LANGUAGE 'plpgsql' VOLATILE AS $$
1846 BEGIN
1847 UPDATE "token" SET "session_id" = NULL
1848 WHERE "session_id" = OLD."id";
1849 RETURN OLD;
1850 END;
1851 $$;
1853 CREATE TRIGGER "detach_token_from_session"
1854 BEFORE DELETE ON "session" FOR EACH ROW EXECUTE PROCEDURE
1855 "detach_token_from_session_trigger"();
1858 CREATE FUNCTION "delete_non_detached_scope_with_session_trigger"()
1859 RETURNS TRIGGER
1860 LANGUAGE 'plpgsql' VOLATILE AS $$
1861 BEGIN
1862 IF NEW."session_id" ISNULL THEN
1863 SELECT coalesce(string_agg("element", ' '), '') INTO NEW."scope"
1864 FROM unnest(regexp_split_to_array(NEW."scope", E'\\s+')) AS "element"
1865 WHERE "element" LIKE '%_detached';
1866 END IF;
1867 RETURN NEW;
1868 END;
1869 $$;
1871 CREATE TRIGGER "delete_non_detached_scope_with_session"
1872 BEFORE INSERT OR UPDATE ON "token" FOR EACH ROW EXECUTE PROCEDURE
1873 "delete_non_detached_scope_with_session_trigger"();
1876 CREATE FUNCTION "delete_token_with_empty_scope_trigger"()
1877 RETURNS TRIGGER
1878 LANGUAGE 'plpgsql' VOLATILE AS $$
1879 BEGIN
1880 IF NEW."scope" = '' THEN
1881 DELETE FROM "token" WHERE "id" = NEW."id";
1882 END IF;
1883 RETURN NULL;
1884 END;
1885 $$;
1887 CREATE TRIGGER "delete_token_with_empty_scope"
1888 AFTER INSERT OR UPDATE ON "token" FOR EACH ROW EXECUTE PROCEDURE
1889 "delete_token_with_empty_scope_trigger"();
1892 CREATE FUNCTION "delete_snapshot_on_partial_delete_trigger"()
1893 RETURNS TRIGGER
1894 LANGUAGE 'plpgsql' VOLATILE AS $$
1895 BEGIN
1896 IF TG_OP = 'UPDATE' THEN
1897 IF
1898 OLD."snapshot_id" = NEW."snapshot_id" AND
1899 OLD."issue_id" = NEW."issue_id"
1900 THEN
1901 RETURN NULL;
1902 END IF;
1903 END IF;
1904 DELETE FROM "snapshot" WHERE "id" = OLD."snapshot_id";
1905 RETURN NULL;
1906 END;
1907 $$;
1909 CREATE TRIGGER "delete_snapshot_on_partial_delete"
1910 AFTER UPDATE OR DELETE ON "snapshot_issue"
1911 FOR EACH ROW EXECUTE PROCEDURE
1912 "delete_snapshot_on_partial_delete_trigger"();
1914 COMMENT ON FUNCTION "delete_snapshot_on_partial_delete_trigger"() IS 'Implementation of trigger "delete_snapshot_on_partial_delete" on table "snapshot_issue"';
1915 COMMENT ON TRIGGER "delete_snapshot_on_partial_delete" ON "snapshot_issue" IS 'Deletes whole snapshot if one issue is deleted from the snapshot';
1918 CREATE FUNCTION "copy_current_draft_data"
1919 ("initiative_id_p" "initiative"."id"%TYPE )
1920 RETURNS VOID
1921 LANGUAGE 'plpgsql' VOLATILE AS $$
1922 BEGIN
1923 PERFORM NULL FROM "initiative" WHERE "id" = "initiative_id_p"
1924 FOR UPDATE;
1925 UPDATE "initiative" SET
1926 "location" = "draft"."location",
1927 "draft_text_search_data" = "draft"."text_search_data"
1928 FROM "current_draft" AS "draft"
1929 WHERE "initiative"."id" = "initiative_id_p"
1930 AND "draft"."initiative_id" = "initiative_id_p";
1931 END;
1932 $$;
1934 COMMENT ON FUNCTION "copy_current_draft_data"
1935 ( "initiative"."id"%TYPE )
1936 IS 'Helper function for function "copy_current_draft_data_trigger"';
1939 CREATE FUNCTION "copy_current_draft_data_trigger"()
1940 RETURNS TRIGGER
1941 LANGUAGE 'plpgsql' VOLATILE AS $$
1942 BEGIN
1943 IF TG_OP='DELETE' THEN
1944 PERFORM "copy_current_draft_data"(OLD."initiative_id");
1945 ELSE
1946 IF TG_OP='UPDATE' THEN
1947 IF COALESCE(OLD."inititiave_id" != NEW."initiative_id", TRUE) THEN
1948 PERFORM "copy_current_draft_data"(OLD."initiative_id");
1949 END IF;
1950 END IF;
1951 PERFORM "copy_current_draft_data"(NEW."initiative_id");
1952 END IF;
1953 RETURN NULL;
1954 END;
1955 $$;
1957 CREATE TRIGGER "copy_current_draft_data"
1958 AFTER INSERT OR UPDATE OR DELETE ON "draft"
1959 FOR EACH ROW EXECUTE PROCEDURE
1960 "copy_current_draft_data_trigger"();
1962 COMMENT ON FUNCTION "copy_current_draft_data_trigger"() IS 'Implementation of trigger "copy_current_draft_data" on table "draft"';
1963 COMMENT ON TRIGGER "copy_current_draft_data" ON "draft" IS 'Copy certain fields from most recent "draft" to "initiative"';
1966 CREATE VIEW "area_quorum" AS
1967 SELECT
1968 "area"."id" AS "area_id",
1969 ceil(
1970 "area"."quorum_standard"::FLOAT8 * "quorum_factor"::FLOAT8 ^ (
1971 coalesce(
1972 ( SELECT sum(
1973 ( extract(epoch from "area"."quorum_time")::FLOAT8 /
1974 extract(epoch from
1975 ("issue"."accepted"-"issue"."created") +
1976 "issue"."discussion_time" +
1977 "issue"."verification_time" +
1978 "issue"."voting_time"
1979 )::FLOAT8
1980 ) ^ "area"."quorum_exponent"::FLOAT8
1982 FROM "issue" JOIN "policy"
1983 ON "issue"."policy_id" = "policy"."id"
1984 WHERE "issue"."area_id" = "area"."id"
1985 AND "issue"."accepted" NOTNULL
1986 AND "issue"."closed" ISNULL
1987 AND "policy"."polling" = FALSE
1988 )::FLOAT8, 0::FLOAT8
1989 ) / "area"."quorum_issues"::FLOAT8 - 1::FLOAT8
1990 ) * CASE WHEN "area"."quorum_den" ISNULL THEN 1 ELSE (
1991 SELECT "snapshot"."population"
1992 FROM "snapshot"
1993 WHERE "snapshot"."area_id" = "area"."id"
1994 AND "snapshot"."issue_id" ISNULL
1995 ORDER BY "snapshot"."id" DESC
1996 LIMIT 1
1997 ) END / coalesce("area"."quorum_den", 1)
1999 )::INT4 AS "issue_quorum"
2000 FROM "area";
2002 COMMENT ON VIEW "area_quorum" IS 'Area-based quorum considering number of open (accepted) issues';
2005 CREATE VIEW "area_with_unaccepted_issues" AS
2006 SELECT DISTINCT ON ("area"."id") "area".*
2007 FROM "area" JOIN "issue" ON "area"."id" = "issue"."area_id"
2008 WHERE "issue"."state" = 'admission';
2010 COMMENT ON VIEW "area_with_unaccepted_issues" IS 'All areas with unaccepted open issues (needed for issue admission system)';
2013 DROP VIEW "area_member_count";
2016 DROP TABLE "membership";
2019 DROP FUNCTION "membership_weight"
2020 ( "area_id_p" "area"."id"%TYPE,
2021 "member_id_p" "member"."id"%TYPE );
2024 DROP FUNCTION "membership_weight_with_skipping"
2025 ( "area_id_p" "area"."id"%TYPE,
2026 "member_id_p" "member"."id"%TYPE,
2027 "skip_member_ids_p" INT4[] ); -- TODO: ordering/cascade
2030 CREATE OR REPLACE VIEW "issue_delegation" AS
2031 SELECT DISTINCT ON ("issue"."id", "delegation"."truster_id")
2032 "issue"."id" AS "issue_id",
2033 "delegation"."id",
2034 "delegation"."truster_id",
2035 "delegation"."trustee_id",
2036 "delegation"."scope"
2037 FROM "issue"
2038 JOIN "area"
2039 ON "area"."id" = "issue"."area_id"
2040 JOIN "delegation"
2041 ON "delegation"."unit_id" = "area"."unit_id"
2042 OR "delegation"."area_id" = "area"."id"
2043 OR "delegation"."issue_id" = "issue"."id"
2044 JOIN "member"
2045 ON "delegation"."truster_id" = "member"."id"
2046 JOIN "privilege"
2047 ON "area"."unit_id" = "privilege"."unit_id"
2048 AND "delegation"."truster_id" = "privilege"."member_id"
2049 WHERE "member"."active" AND "privilege"."voting_right"
2050 ORDER BY
2051 "issue"."id",
2052 "delegation"."truster_id",
2053 "delegation"."scope" DESC;
2056 CREATE VIEW "unit_member" AS
2057 SELECT
2058 "unit"."id" AS "unit_id",
2059 "member"."id" AS "member_id"
2060 FROM "privilege"
2061 JOIN "unit" ON "unit_id" = "privilege"."unit_id"
2062 JOIN "member" ON "member"."id" = "privilege"."member_id"
2063 WHERE "privilege"."voting_right" AND "member"."active";
2065 COMMENT ON VIEW "unit_member" IS 'Active members with voting right in a unit';
2068 CREATE OR REPLACE VIEW "unit_member_count" AS
2069 SELECT
2070 "unit"."id" AS "unit_id",
2071 count("unit_member"."member_id") AS "member_count"
2072 FROM "unit" LEFT JOIN "unit_member"
2073 ON "unit"."id" = "unit_member"."unit_id"
2074 GROUP BY "unit"."id";
2076 COMMENT ON VIEW "unit_member_count" IS 'View used to update "member_count" column of "unit" table';
2079 CREATE OR REPLACE VIEW "opening_draft" AS
2080 SELECT DISTINCT ON ("initiative_id") * FROM "draft"
2081 ORDER BY "initiative_id", "id";
2084 CREATE OR REPLACE VIEW "current_draft" AS
2085 SELECT DISTINCT ON ("initiative_id") * FROM "draft"
2086 ORDER BY "initiative_id", "id" DESC;
2089 CREATE OR REPLACE VIEW "issue_supporter_in_admission_state" AS
2090 SELECT
2091 "area"."unit_id",
2092 "issue"."area_id",
2093 "issue"."id" AS "issue_id",
2094 "supporter"."member_id",
2095 "direct_interest_snapshot"."weight"
2096 FROM "issue"
2097 JOIN "area" ON "area"."id" = "issue"."area_id"
2098 JOIN "supporter" ON "supporter"."issue_id" = "issue"."id"
2099 JOIN "direct_interest_snapshot"
2100 ON "direct_interest_snapshot"."snapshot_id" = "issue"."latest_snapshot_id"
2101 AND "direct_interest_snapshot"."issue_id" = "issue"."id"
2102 AND "direct_interest_snapshot"."member_id" = "supporter"."member_id"
2103 WHERE "issue"."state" = 'admission'::"issue_state";
2106 CREATE OR REPLACE VIEW "individual_suggestion_ranking" AS
2107 SELECT
2108 "opinion"."initiative_id",
2109 "opinion"."member_id",
2110 "direct_interest_snapshot"."weight",
2111 CASE WHEN
2112 ("opinion"."degree" = 2 AND "opinion"."fulfilled" = FALSE) OR
2113 ("opinion"."degree" = -2 AND "opinion"."fulfilled" = TRUE)
2114 THEN 1 ELSE
2115 CASE WHEN
2116 ("opinion"."degree" = 1 AND "opinion"."fulfilled" = FALSE) OR
2117 ("opinion"."degree" = -1 AND "opinion"."fulfilled" = TRUE)
2118 THEN 2 ELSE
2119 CASE WHEN
2120 ("opinion"."degree" = 2 AND "opinion"."fulfilled" = TRUE) OR
2121 ("opinion"."degree" = -2 AND "opinion"."fulfilled" = FALSE)
2122 THEN 3 ELSE 4 END
2123 END
2124 END AS "preference",
2125 "opinion"."suggestion_id"
2126 FROM "opinion"
2127 JOIN "initiative" ON "initiative"."id" = "opinion"."initiative_id"
2128 JOIN "issue" ON "issue"."id" = "initiative"."issue_id"
2129 JOIN "direct_interest_snapshot"
2130 ON "direct_interest_snapshot"."snapshot_id" = "issue"."latest_snapshot_id"
2131 AND "direct_interest_snapshot"."issue_id" = "issue"."id"
2132 AND "direct_interest_snapshot"."member_id" = "opinion"."member_id";
2135 CREATE VIEW "expired_session" AS
2136 SELECT * FROM "session" WHERE now() > "expiry";
2138 CREATE RULE "delete" AS ON DELETE TO "expired_session" DO INSTEAD
2139 DELETE FROM "session" WHERE "id" = OLD."id";
2141 COMMENT ON VIEW "expired_session" IS 'View containing all expired sessions where DELETE is possible';
2142 COMMENT ON RULE "delete" ON "expired_session" IS 'Rule allowing DELETE on rows in "expired_session" view, i.e. DELETE FROM "expired_session"';
2145 CREATE VIEW "expired_token" AS
2146 SELECT * FROM "token" WHERE now() > "expiry" AND NOT (
2147 "token_type" = 'authorization' AND "used" AND EXISTS (
2148 SELECT NULL FROM "token" AS "other"
2149 WHERE "other"."authorization_token_id" = "id" ) );
2151 CREATE RULE "delete" AS ON DELETE TO "expired_token" DO INSTEAD
2152 DELETE FROM "token" WHERE "id" = OLD."id";
2154 COMMENT ON VIEW "expired_token" IS 'View containing all expired tokens where DELETE is possible; Note that used authorization codes must not be deleted if still referred to by other tokens';
2157 CREATE VIEW "unused_snapshot" AS
2158 SELECT "snapshot".* FROM "snapshot"
2159 LEFT JOIN "issue"
2160 ON "snapshot"."id" = "issue"."latest_snapshot_id"
2161 OR "snapshot"."id" = "issue"."admission_snapshot_id"
2162 OR "snapshot"."id" = "issue"."half_freeze_snapshot_id"
2163 OR "snapshot"."id" = "issue"."full_freeze_snapshot_id"
2164 WHERE "issue"."id" ISNULL;
2166 CREATE RULE "delete" AS ON DELETE TO "unused_snapshot" DO INSTEAD
2167 DELETE FROM "snapshot" WHERE "id" = OLD."id";
2169 COMMENT ON VIEW "unused_snapshot" IS 'Snapshots that are not referenced by any issue (either as latest snapshot or as snapshot at phase/state change)';
2172 CREATE VIEW "expired_snapshot" AS
2173 SELECT "unused_snapshot".* FROM "unused_snapshot" CROSS JOIN "system_setting"
2174 WHERE "unused_snapshot"."calculated" <
2175 now() - "system_setting"."snapshot_retention";
2177 CREATE RULE "delete" AS ON DELETE TO "expired_snapshot" DO INSTEAD
2178 DELETE FROM "snapshot" WHERE "id" = OLD."id";
2180 COMMENT ON VIEW "expired_snapshot" IS 'Contains "unused_snapshot"s that are older than "system_setting"."snapshot_retention" (for deletion)';
2183 COMMENT ON COLUMN "delegation_chain_row"."participation" IS 'In case of delegation chains for issues: interest; for area and global delegation chains: always null';
2186 CREATE OR REPLACE FUNCTION "delegation_chain"
2187 ( "member_id_p" "member"."id"%TYPE,
2188 "unit_id_p" "unit"."id"%TYPE,
2189 "area_id_p" "area"."id"%TYPE,
2190 "issue_id_p" "issue"."id"%TYPE,
2191 "simulate_trustee_id_p" "member"."id"%TYPE DEFAULT NULL,
2192 "simulate_default_p" BOOLEAN DEFAULT FALSE )
2193 RETURNS SETOF "delegation_chain_row"
2194 LANGUAGE 'plpgsql' STABLE AS $$
2195 DECLARE
2196 "scope_v" "delegation_scope";
2197 "unit_id_v" "unit"."id"%TYPE;
2198 "area_id_v" "area"."id"%TYPE;
2199 "issue_row" "issue"%ROWTYPE;
2200 "visited_member_ids" INT4[]; -- "member"."id"%TYPE[]
2201 "loop_member_id_v" "member"."id"%TYPE;
2202 "output_row" "delegation_chain_row";
2203 "output_rows" "delegation_chain_row"[];
2204 "simulate_v" BOOLEAN;
2205 "simulate_here_v" BOOLEAN;
2206 "delegation_row" "delegation"%ROWTYPE;
2207 "row_count" INT4;
2208 "i" INT4;
2209 "loop_v" BOOLEAN;
2210 BEGIN
2211 IF "simulate_trustee_id_p" NOTNULL AND "simulate_default_p" THEN
2212 RAISE EXCEPTION 'Both "simulate_trustee_id_p" is set, and "simulate_default_p" is true';
2213 END IF;
2214 IF "simulate_trustee_id_p" NOTNULL OR "simulate_default_p" THEN
2215 "simulate_v" := TRUE;
2216 ELSE
2217 "simulate_v" := FALSE;
2218 END IF;
2219 IF
2220 "unit_id_p" NOTNULL AND
2221 "area_id_p" ISNULL AND
2222 "issue_id_p" ISNULL
2223 THEN
2224 "scope_v" := 'unit';
2225 "unit_id_v" := "unit_id_p";
2226 ELSIF
2227 "unit_id_p" ISNULL AND
2228 "area_id_p" NOTNULL AND
2229 "issue_id_p" ISNULL
2230 THEN
2231 "scope_v" := 'area';
2232 "area_id_v" := "area_id_p";
2233 SELECT "unit_id" INTO "unit_id_v"
2234 FROM "area" WHERE "id" = "area_id_v";
2235 ELSIF
2236 "unit_id_p" ISNULL AND
2237 "area_id_p" ISNULL AND
2238 "issue_id_p" NOTNULL
2239 THEN
2240 SELECT INTO "issue_row" * FROM "issue" WHERE "id" = "issue_id_p";
2241 IF "issue_row"."id" ISNULL THEN
2242 RETURN;
2243 END IF;
2244 IF "issue_row"."closed" NOTNULL THEN
2245 IF "simulate_v" THEN
2246 RAISE EXCEPTION 'Tried to simulate delegation chain for closed issue.';
2247 END IF;
2248 FOR "output_row" IN
2249 SELECT * FROM
2250 "delegation_chain_for_closed_issue"("member_id_p", "issue_id_p")
2251 LOOP
2252 RETURN NEXT "output_row";
2253 END LOOP;
2254 RETURN;
2255 END IF;
2256 "scope_v" := 'issue';
2257 SELECT "area_id" INTO "area_id_v"
2258 FROM "issue" WHERE "id" = "issue_id_p";
2259 SELECT "unit_id" INTO "unit_id_v"
2260 FROM "area" WHERE "id" = "area_id_v";
2261 ELSE
2262 RAISE EXCEPTION 'Exactly one of unit_id_p, area_id_p, or issue_id_p must be NOTNULL.';
2263 END IF;
2264 "visited_member_ids" := '{}';
2265 "loop_member_id_v" := NULL;
2266 "output_rows" := '{}';
2267 "output_row"."index" := 0;
2268 "output_row"."member_id" := "member_id_p";
2269 "output_row"."member_valid" := TRUE;
2270 "output_row"."participation" := FALSE;
2271 "output_row"."overridden" := FALSE;
2272 "output_row"."disabled_out" := FALSE;
2273 "output_row"."scope_out" := NULL;
2274 LOOP
2275 IF "visited_member_ids" @> ARRAY["output_row"."member_id"] THEN
2276 "loop_member_id_v" := "output_row"."member_id";
2277 ELSE
2278 "visited_member_ids" :=
2279 "visited_member_ids" || "output_row"."member_id";
2280 END IF;
2281 IF "output_row"."participation" ISNULL THEN
2282 "output_row"."overridden" := NULL;
2283 ELSIF "output_row"."participation" THEN
2284 "output_row"."overridden" := TRUE;
2285 END IF;
2286 "output_row"."scope_in" := "output_row"."scope_out";
2287 "output_row"."member_valid" := EXISTS (
2288 SELECT NULL FROM "member" JOIN "privilege"
2289 ON "privilege"."member_id" = "member"."id"
2290 AND "privilege"."unit_id" = "unit_id_v"
2291 WHERE "id" = "output_row"."member_id"
2292 AND "member"."active" AND "privilege"."voting_right"
2293 );
2294 "simulate_here_v" := (
2295 "simulate_v" AND
2296 "output_row"."member_id" = "member_id_p"
2297 );
2298 "delegation_row" := ROW(NULL);
2299 IF "output_row"."member_valid" OR "simulate_here_v" THEN
2300 IF "scope_v" = 'unit' THEN
2301 IF NOT "simulate_here_v" THEN
2302 SELECT * INTO "delegation_row" FROM "delegation"
2303 WHERE "truster_id" = "output_row"."member_id"
2304 AND "unit_id" = "unit_id_v";
2305 END IF;
2306 ELSIF "scope_v" = 'area' THEN
2307 IF "simulate_here_v" THEN
2308 IF "simulate_trustee_id_p" ISNULL THEN
2309 SELECT * INTO "delegation_row" FROM "delegation"
2310 WHERE "truster_id" = "output_row"."member_id"
2311 AND "unit_id" = "unit_id_v";
2312 END IF;
2313 ELSE
2314 SELECT * INTO "delegation_row" FROM "delegation"
2315 WHERE "truster_id" = "output_row"."member_id"
2316 AND (
2317 "unit_id" = "unit_id_v" OR
2318 "area_id" = "area_id_v"
2320 ORDER BY "scope" DESC;
2321 END IF;
2322 ELSIF "scope_v" = 'issue' THEN
2323 IF "issue_row"."fully_frozen" ISNULL THEN
2324 "output_row"."participation" := EXISTS (
2325 SELECT NULL FROM "interest"
2326 WHERE "issue_id" = "issue_id_p"
2327 AND "member_id" = "output_row"."member_id"
2328 );
2329 ELSE
2330 IF "output_row"."member_id" = "member_id_p" THEN
2331 "output_row"."participation" := EXISTS (
2332 SELECT NULL FROM "direct_voter"
2333 WHERE "issue_id" = "issue_id_p"
2334 AND "member_id" = "output_row"."member_id"
2335 );
2336 ELSE
2337 "output_row"."participation" := NULL;
2338 END IF;
2339 END IF;
2340 IF "simulate_here_v" THEN
2341 IF "simulate_trustee_id_p" ISNULL THEN
2342 SELECT * INTO "delegation_row" FROM "delegation"
2343 WHERE "truster_id" = "output_row"."member_id"
2344 AND (
2345 "unit_id" = "unit_id_v" OR
2346 "area_id" = "area_id_v"
2348 ORDER BY "scope" DESC;
2349 END IF;
2350 ELSE
2351 SELECT * INTO "delegation_row" FROM "delegation"
2352 WHERE "truster_id" = "output_row"."member_id"
2353 AND (
2354 "unit_id" = "unit_id_v" OR
2355 "area_id" = "area_id_v" OR
2356 "issue_id" = "issue_id_p"
2358 ORDER BY "scope" DESC;
2359 END IF;
2360 END IF;
2361 ELSE
2362 "output_row"."participation" := FALSE;
2363 END IF;
2364 IF "simulate_here_v" AND "simulate_trustee_id_p" NOTNULL THEN
2365 "output_row"."scope_out" := "scope_v";
2366 "output_rows" := "output_rows" || "output_row";
2367 "output_row"."member_id" := "simulate_trustee_id_p";
2368 ELSIF "delegation_row"."trustee_id" NOTNULL THEN
2369 "output_row"."scope_out" := "delegation_row"."scope";
2370 "output_rows" := "output_rows" || "output_row";
2371 "output_row"."member_id" := "delegation_row"."trustee_id";
2372 ELSIF "delegation_row"."scope" NOTNULL THEN
2373 "output_row"."scope_out" := "delegation_row"."scope";
2374 "output_row"."disabled_out" := TRUE;
2375 "output_rows" := "output_rows" || "output_row";
2376 EXIT;
2377 ELSE
2378 "output_row"."scope_out" := NULL;
2379 "output_rows" := "output_rows" || "output_row";
2380 EXIT;
2381 END IF;
2382 EXIT WHEN "loop_member_id_v" NOTNULL;
2383 "output_row"."index" := "output_row"."index" + 1;
2384 END LOOP;
2385 "row_count" := array_upper("output_rows", 1);
2386 "i" := 1;
2387 "loop_v" := FALSE;
2388 LOOP
2389 "output_row" := "output_rows"["i"];
2390 EXIT WHEN "output_row" ISNULL; -- NOTE: ISNULL and NOT ... NOTNULL produce different results!
2391 IF "loop_v" THEN
2392 IF "i" + 1 = "row_count" THEN
2393 "output_row"."loop" := 'last';
2394 ELSIF "i" = "row_count" THEN
2395 "output_row"."loop" := 'repetition';
2396 ELSE
2397 "output_row"."loop" := 'intermediate';
2398 END IF;
2399 ELSIF "output_row"."member_id" = "loop_member_id_v" THEN
2400 "output_row"."loop" := 'first';
2401 "loop_v" := TRUE;
2402 END IF;
2403 IF "scope_v" = 'unit' THEN
2404 "output_row"."participation" := NULL;
2405 END IF;
2406 RETURN NEXT "output_row";
2407 "i" := "i" + 1;
2408 END LOOP;
2409 RETURN;
2410 END;
2411 $$;
2414 CREATE OR REPLACE FUNCTION "get_initiatives_for_notification"
2415 ( "recipient_id_p" "member"."id"%TYPE )
2416 RETURNS SETOF "initiative_for_notification"
2417 LANGUAGE 'plpgsql' VOLATILE AS $$
2418 DECLARE
2419 "result_row" "initiative_for_notification"%ROWTYPE;
2420 "last_draft_id_v" "draft"."id"%TYPE;
2421 "last_suggestion_id_v" "suggestion"."id"%TYPE;
2422 BEGIN
2423 PERFORM "require_transaction_isolation"();
2424 PERFORM NULL FROM "member" WHERE "id" = "recipient_id_p" FOR UPDATE;
2425 FOR "result_row" IN
2426 SELECT * FROM "initiative_for_notification"
2427 WHERE "recipient_id" = "recipient_id_p"
2428 LOOP
2429 SELECT "id" INTO "last_draft_id_v" FROM "draft"
2430 WHERE "draft"."initiative_id" = "result_row"."initiative_id"
2431 ORDER BY "id" DESC LIMIT 1;
2432 SELECT "id" INTO "last_suggestion_id_v" FROM "suggestion"
2433 WHERE "suggestion"."initiative_id" = "result_row"."initiative_id"
2434 ORDER BY "id" DESC LIMIT 1;
2435 INSERT INTO "notification_initiative_sent"
2436 ("member_id", "initiative_id", "last_draft_id", "last_suggestion_id")
2437 VALUES (
2438 "recipient_id_p",
2439 "result_row"."initiative_id",
2440 "last_draft_id_v",
2441 "last_suggestion_id_v" )
2442 ON CONFLICT ("member_id", "initiative_id") DO UPDATE SET
2443 "last_draft_id" = "last_draft_id_v",
2444 "last_suggestion_id" = "last_suggestion_id_v";
2445 RETURN NEXT "result_row";
2446 END LOOP;
2447 DELETE FROM "notification_initiative_sent"
2448 USING "initiative", "issue"
2449 WHERE "notification_initiative_sent"."member_id" = "recipient_id_p"
2450 AND "initiative"."id" = "notification_initiative_sent"."initiative_id"
2451 AND "issue"."id" = "initiative"."issue_id"
2452 AND ( "issue"."closed" NOTNULL OR "issue"."fully_frozen" NOTNULL );
2453 UPDATE "member" SET
2454 "notification_counter" = "notification_counter" + 1,
2455 "notification_sent" = now()
2456 WHERE "id" = "recipient_id_p";
2457 RETURN;
2458 END;
2459 $$;
2462 CREATE OR REPLACE FUNCTION "calculate_member_counts"()
2463 RETURNS VOID
2464 LANGUAGE 'plpgsql' VOLATILE AS $$
2465 BEGIN
2466 PERFORM "require_transaction_isolation"();
2467 DELETE FROM "member_count";
2468 INSERT INTO "member_count" ("total_count")
2469 SELECT "total_count" FROM "member_count_view";
2470 UPDATE "unit" SET "member_count" = "view"."member_count"
2471 FROM "unit_member_count" AS "view"
2472 WHERE "view"."unit_id" = "unit"."id";
2473 RETURN;
2474 END;
2475 $$;
2477 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 "unit_member_count"';
2480 CREATE FUNCTION "calculate_area_quorum"()
2481 RETURNS VOID
2482 LANGUAGE 'plpgsql' VOLATILE AS $$
2483 BEGIN
2484 PERFORM "dont_require_transaction_isolation"();
2485 UPDATE "area" SET "issue_quorum" = "view"."issue_quorum"
2486 FROM "area_quorum" AS "view"
2487 WHERE "view"."area_id" = "area"."id";
2488 RETURN;
2489 END;
2490 $$;
2492 COMMENT ON FUNCTION "calculate_area_quorum"() IS 'Calculate column "issue_quorum" in table "area" from view "area_quorum"';
2495 DROP VIEW "remaining_harmonic_initiative_weight_summands";
2496 DROP VIEW "remaining_harmonic_supporter_weight";
2499 CREATE VIEW "remaining_harmonic_supporter_weight" AS
2500 SELECT
2501 "direct_interest_snapshot"."snapshot_id",
2502 "direct_interest_snapshot"."issue_id",
2503 "direct_interest_snapshot"."member_id",
2504 "direct_interest_snapshot"."weight" AS "weight_num",
2505 count("initiative"."id") AS "weight_den"
2506 FROM "issue"
2507 JOIN "direct_interest_snapshot"
2508 ON "issue"."latest_snapshot_id" = "direct_interest_snapshot"."snapshot_id"
2509 AND "issue"."id" = "direct_interest_snapshot"."issue_id"
2510 JOIN "initiative"
2511 ON "issue"."id" = "initiative"."issue_id"
2512 AND "initiative"."harmonic_weight" ISNULL
2513 JOIN "direct_supporter_snapshot"
2514 ON "issue"."latest_snapshot_id" = "direct_supporter_snapshot"."snapshot_id"
2515 AND "initiative"."id" = "direct_supporter_snapshot"."initiative_id"
2516 AND "direct_interest_snapshot"."member_id" = "direct_supporter_snapshot"."member_id"
2517 AND (
2518 "direct_supporter_snapshot"."satisfied" = TRUE OR
2519 coalesce("initiative"."admitted", FALSE) = FALSE
2521 GROUP BY
2522 "direct_interest_snapshot"."snapshot_id",
2523 "direct_interest_snapshot"."issue_id",
2524 "direct_interest_snapshot"."member_id",
2525 "direct_interest_snapshot"."weight";
2528 CREATE VIEW "remaining_harmonic_initiative_weight_summands" AS
2529 SELECT
2530 "initiative"."issue_id",
2531 "initiative"."id" AS "initiative_id",
2532 "initiative"."admitted",
2533 sum("remaining_harmonic_supporter_weight"."weight_num") AS "weight_num",
2534 "remaining_harmonic_supporter_weight"."weight_den"
2535 FROM "remaining_harmonic_supporter_weight"
2536 JOIN "initiative"
2537 ON "remaining_harmonic_supporter_weight"."issue_id" = "initiative"."issue_id"
2538 AND "initiative"."harmonic_weight" ISNULL
2539 JOIN "direct_supporter_snapshot"
2540 ON "remaining_harmonic_supporter_weight"."snapshot_id" = "direct_supporter_snapshot"."snapshot_id"
2541 AND "initiative"."id" = "direct_supporter_snapshot"."initiative_id"
2542 AND "remaining_harmonic_supporter_weight"."member_id" = "direct_supporter_snapshot"."member_id"
2543 AND (
2544 "direct_supporter_snapshot"."satisfied" = TRUE OR
2545 coalesce("initiative"."admitted", FALSE) = FALSE
2547 GROUP BY
2548 "initiative"."issue_id",
2549 "initiative"."id",
2550 "initiative"."admitted",
2551 "remaining_harmonic_supporter_weight"."weight_den";
2554 DROP FUNCTION "create_population_snapshot"
2555 ( "issue_id_p" "issue"."id"%TYPE );
2558 DROP FUNCTION "weight_of_added_delegations_for_population_snapshot"
2559 ( "issue_id_p" "issue"."id"%TYPE,
2560 "member_id_p" "member"."id"%TYPE,
2561 "delegate_member_ids_p" "delegating_population_snapshot"."delegate_member_ids"%TYPE );
2564 DROP FUNCTION "weight_of_added_delegations_for_interest_snapshot"
2565 ( "issue_id_p" "issue"."id"%TYPE,
2566 "member_id_p" "member"."id"%TYPE,
2567 "delegate_member_ids_p" "delegating_interest_snapshot"."delegate_member_ids"%TYPE );
2570 CREATE FUNCTION "weight_of_added_delegations_for_snapshot"
2571 ( "snapshot_id_p" "snapshot"."id"%TYPE,
2572 "issue_id_p" "issue"."id"%TYPE,
2573 "member_id_p" "member"."id"%TYPE,
2574 "delegate_member_ids_p" "delegating_interest_snapshot"."delegate_member_ids"%TYPE )
2575 RETURNS "direct_interest_snapshot"."weight"%TYPE
2576 LANGUAGE 'plpgsql' VOLATILE AS $$
2577 DECLARE
2578 "issue_delegation_row" "issue_delegation"%ROWTYPE;
2579 "delegate_member_ids_v" "delegating_interest_snapshot"."delegate_member_ids"%TYPE;
2580 "weight_v" INT4;
2581 "sub_weight_v" INT4;
2582 BEGIN
2583 PERFORM "require_transaction_isolation"();
2584 "weight_v" := 0;
2585 FOR "issue_delegation_row" IN
2586 SELECT * FROM "issue_delegation"
2587 WHERE "trustee_id" = "member_id_p"
2588 AND "issue_id" = "issue_id_p"
2589 LOOP
2590 IF NOT EXISTS (
2591 SELECT NULL FROM "direct_interest_snapshot"
2592 WHERE "snapshot_id" = "snapshot_id_p"
2593 AND "issue_id" = "issue_id_p"
2594 AND "member_id" = "issue_delegation_row"."truster_id"
2595 ) AND NOT EXISTS (
2596 SELECT NULL FROM "delegating_interest_snapshot"
2597 WHERE "snapshot_id" = "snapshot_id_p"
2598 AND "issue_id" = "issue_id_p"
2599 AND "member_id" = "issue_delegation_row"."truster_id"
2600 ) THEN
2601 "delegate_member_ids_v" :=
2602 "member_id_p" || "delegate_member_ids_p";
2603 INSERT INTO "delegating_interest_snapshot" (
2604 "snapshot_id",
2605 "issue_id",
2606 "member_id",
2607 "scope",
2608 "delegate_member_ids"
2609 ) VALUES (
2610 "snapshot_id_p",
2611 "issue_id_p",
2612 "issue_delegation_row"."truster_id",
2613 "issue_delegation_row"."scope",
2614 "delegate_member_ids_v"
2615 );
2616 "sub_weight_v" := 1 +
2617 "weight_of_added_delegations_for_snapshot"(
2618 "snapshot_id_p",
2619 "issue_id_p",
2620 "issue_delegation_row"."truster_id",
2621 "delegate_member_ids_v"
2622 );
2623 UPDATE "delegating_interest_snapshot"
2624 SET "weight" = "sub_weight_v"
2625 WHERE "snapshot_id" = "snapshot_id_p"
2626 AND "issue_id" = "issue_id_p"
2627 AND "member_id" = "issue_delegation_row"."truster_id";
2628 "weight_v" := "weight_v" + "sub_weight_v";
2629 END IF;
2630 END LOOP;
2631 RETURN "weight_v";
2632 END;
2633 $$;
2635 COMMENT ON FUNCTION "weight_of_added_delegations_for_snapshot"
2636 ( "snapshot"."id"%TYPE,
2637 "issue"."id"%TYPE,
2638 "member"."id"%TYPE,
2639 "delegating_interest_snapshot"."delegate_member_ids"%TYPE )
2640 IS 'Helper function for "fill_snapshot" function';
2643 DROP FUNCTION "create_interest_snapshot"
2644 ( "issue_id_p" "issue"."id"%TYPE );
2647 DROP FUNCTION "create_snapshot"
2648 ( "issue_id_p" "issue"."id"%TYPE );
2651 CREATE FUNCTION "take_snapshot"
2652 ( "issue_id_p" "issue"."id"%TYPE,
2653 "area_id_p" "area"."id"%TYPE = NULL )
2654 RETURNS "snapshot"."id"%TYPE
2655 LANGUAGE 'plpgsql' VOLATILE AS $$
2656 DECLARE
2657 "area_id_v" "area"."id"%TYPE;
2658 "unit_id_v" "unit"."id"%TYPE;
2659 "snapshot_id_v" "snapshot"."id"%TYPE;
2660 "issue_id_v" "issue"."id"%TYPE;
2661 "member_id_v" "member"."id"%TYPE;
2662 BEGIN
2663 IF "issue_id_p" NOTNULL AND "area_id_p" NOTNULL THEN
2664 RAISE EXCEPTION 'One of "issue_id_p" and "area_id_p" must be NULL';
2665 END IF;
2666 PERFORM "require_transaction_isolation"();
2667 IF "issue_id_p" ISNULL THEN
2668 "area_id_v" := "area_id_p";
2669 ELSE
2670 SELECT "area_id" INTO "area_id_v"
2671 FROM "issue" WHERE "id" = "issue_id_p";
2672 END IF;
2673 SELECT "unit_id" INTO "unit_id_v" FROM "area" WHERE "id" = "area_id_p";
2674 INSERT INTO "snapshot" ("area_id", "issue_id")
2675 VALUES ("area_id_v", "issue_id_p")
2676 RETURNING "id" INTO "snapshot_id_v";
2677 INSERT INTO "snapshot_population" ("snapshot_id", "member_id")
2678 SELECT "snapshot_id_v", "member_id"
2679 FROM "unit_member" WHERE "unit_id" = "unit_id_v";
2680 UPDATE "snapshot" SET
2681 "population" = (
2682 SELECT count(1) FROM "snapshot_population"
2683 WHERE "snapshot_id" = "snapshot_id_v"
2684 ) WHERE "id" = "snapshot_id_v";
2685 FOR "issue_id_v" IN
2686 SELECT "id" FROM "issue"
2687 WHERE CASE WHEN "issue_id_p" ISNULL THEN
2688 "area_id" = "area_id_p" AND
2689 "state" = 'admission'
2690 ELSE
2691 "id" = "issue_id_p"
2692 END
2693 LOOP
2694 INSERT INTO "snapshot_issue" ("snapshot_id", "issue_id")
2695 VALUES ("snapshot_id_v", "issue_id_v");
2696 INSERT INTO "direct_interest_snapshot"
2697 ("snapshot_id", "issue_id", "member_id")
2698 SELECT
2699 "snapshot_id_v" AS "snapshot_id",
2700 "issue_id_v" AS "issue_id",
2701 "member"."id" AS "member_id"
2702 FROM "issue"
2703 JOIN "area" ON "issue"."area_id" = "area"."id"
2704 JOIN "interest" ON "issue"."id" = "interest"."issue_id"
2705 JOIN "member" ON "interest"."member_id" = "member"."id"
2706 JOIN "privilege"
2707 ON "privilege"."unit_id" = "area"."unit_id"
2708 AND "privilege"."member_id" = "member"."id"
2709 WHERE "issue"."id" = "issue_id_v"
2710 AND "member"."active" AND "privilege"."voting_right";
2711 FOR "member_id_v" IN
2712 SELECT "member_id" FROM "direct_interest_snapshot"
2713 WHERE "snapshot_id" = "snapshot_id_v"
2714 AND "issue_id" = "issue_id_v"
2715 LOOP
2716 UPDATE "direct_interest_snapshot" SET
2717 "weight" = 1 +
2718 "weight_of_added_delegations_for_snapshot"(
2719 "snapshot_id_v",
2720 "issue_id_v",
2721 "member_id_v",
2722 '{}'
2724 WHERE "snapshot_id" = "snapshot_id_v"
2725 AND "issue_id" = "issue_id_v"
2726 AND "member_id" = "member_id_v";
2727 END LOOP;
2728 INSERT INTO "direct_supporter_snapshot"
2729 ( "snapshot_id", "issue_id", "initiative_id", "member_id",
2730 "draft_id", "informed", "satisfied" )
2731 SELECT
2732 "snapshot_id_v" AS "snapshot_id",
2733 "issue_id_v" AS "issue_id",
2734 "initiative"."id" AS "initiative_id",
2735 "supporter"."member_id" AS "member_id",
2736 "supporter"."draft_id" AS "draft_id",
2737 "supporter"."draft_id" = "current_draft"."id" AS "informed",
2738 NOT EXISTS (
2739 SELECT NULL FROM "critical_opinion"
2740 WHERE "initiative_id" = "initiative"."id"
2741 AND "member_id" = "supporter"."member_id"
2742 ) AS "satisfied"
2743 FROM "initiative"
2744 JOIN "supporter"
2745 ON "supporter"."initiative_id" = "initiative"."id"
2746 JOIN "current_draft"
2747 ON "initiative"."id" = "current_draft"."initiative_id"
2748 JOIN "direct_interest_snapshot"
2749 ON "snapshot_id_v" = "direct_interest_snapshot"."snapshot_id"
2750 AND "supporter"."member_id" = "direct_interest_snapshot"."member_id"
2751 AND "initiative"."issue_id" = "direct_interest_snapshot"."issue_id"
2752 WHERE "initiative"."issue_id" = "issue_id_v";
2753 DELETE FROM "temporary_suggestion_counts";
2754 INSERT INTO "temporary_suggestion_counts"
2755 ( "id",
2756 "minus2_unfulfilled_count", "minus2_fulfilled_count",
2757 "minus1_unfulfilled_count", "minus1_fulfilled_count",
2758 "plus1_unfulfilled_count", "plus1_fulfilled_count",
2759 "plus2_unfulfilled_count", "plus2_fulfilled_count" )
2760 SELECT
2761 "suggestion"."id",
2762 ( SELECT coalesce(sum("di"."weight"), 0)
2763 FROM "opinion" JOIN "direct_interest_snapshot" AS "di"
2764 ON "di"."snapshot_id" = "snapshot_id_v"
2765 AND "di"."issue_id" = "issue_id_v"
2766 AND "di"."member_id" = "opinion"."member_id"
2767 WHERE "opinion"."suggestion_id" = "suggestion"."id"
2768 AND "opinion"."degree" = -2
2769 AND "opinion"."fulfilled" = FALSE
2770 ) AS "minus2_unfulfilled_count",
2771 ( SELECT coalesce(sum("di"."weight"), 0)
2772 FROM "opinion" JOIN "direct_interest_snapshot" AS "di"
2773 ON "di"."snapshot_id" = "snapshot_id_v"
2774 AND "di"."issue_id" = "issue_id_v"
2775 AND "di"."member_id" = "opinion"."member_id"
2776 WHERE "opinion"."suggestion_id" = "suggestion"."id"
2777 AND "opinion"."degree" = -2
2778 AND "opinion"."fulfilled" = TRUE
2779 ) AS "minus2_fulfilled_count",
2780 ( SELECT coalesce(sum("di"."weight"), 0)
2781 FROM "opinion" JOIN "direct_interest_snapshot" AS "di"
2782 ON "di"."snapshot_id" = "snapshot_id_v"
2783 AND "di"."issue_id" = "issue_id_v"
2784 AND "di"."member_id" = "opinion"."member_id"
2785 WHERE "opinion"."suggestion_id" = "suggestion"."id"
2786 AND "opinion"."degree" = -1
2787 AND "opinion"."fulfilled" = FALSE
2788 ) AS "minus1_unfulfilled_count",
2789 ( SELECT coalesce(sum("di"."weight"), 0)
2790 FROM "opinion" JOIN "direct_interest_snapshot" AS "di"
2791 ON "di"."snapshot_id" = "snapshot_id_v"
2792 AND "di"."issue_id" = "issue_id_v"
2793 AND "di"."member_id" = "opinion"."member_id"
2794 WHERE "opinion"."suggestion_id" = "suggestion"."id"
2795 AND "opinion"."degree" = -1
2796 AND "opinion"."fulfilled" = TRUE
2797 ) AS "minus1_fulfilled_count",
2798 ( SELECT coalesce(sum("di"."weight"), 0)
2799 FROM "opinion" JOIN "direct_interest_snapshot" AS "di"
2800 ON "di"."snapshot_id" = "snapshot_id_v"
2801 AND "di"."issue_id" = "issue_id_v"
2802 AND "di"."member_id" = "opinion"."member_id"
2803 WHERE "opinion"."suggestion_id" = "suggestion"."id"
2804 AND "opinion"."degree" = 1
2805 AND "opinion"."fulfilled" = FALSE
2806 ) AS "plus1_unfulfilled_count",
2807 ( SELECT coalesce(sum("di"."weight"), 0)
2808 FROM "opinion" JOIN "direct_interest_snapshot" AS "di"
2809 ON "di"."snapshot_id" = "snapshot_id_v"
2810 AND "di"."issue_id" = "issue_id_v"
2811 AND "di"."member_id" = "opinion"."member_id"
2812 WHERE "opinion"."suggestion_id" = "suggestion"."id"
2813 AND "opinion"."degree" = 1
2814 AND "opinion"."fulfilled" = TRUE
2815 ) AS "plus1_fulfilled_count",
2816 ( SELECT coalesce(sum("di"."weight"), 0)
2817 FROM "opinion" JOIN "direct_interest_snapshot" AS "di"
2818 ON "di"."snapshot_id" = "snapshot_id_v"
2819 AND "di"."issue_id" = "issue_id_v"
2820 AND "di"."member_id" = "opinion"."member_id"
2821 WHERE "opinion"."suggestion_id" = "suggestion"."id"
2822 AND "opinion"."degree" = 2
2823 AND "opinion"."fulfilled" = FALSE
2824 ) AS "plus2_unfulfilled_count",
2825 ( SELECT coalesce(sum("di"."weight"), 0)
2826 FROM "opinion" JOIN "direct_interest_snapshot" AS "di"
2827 ON "di"."snapshot_id" = "snapshot_id_v"
2828 AND "di"."issue_id" = "issue_id_v"
2829 AND "di"."member_id" = "opinion"."member_id"
2830 WHERE "opinion"."suggestion_id" = "suggestion"."id"
2831 AND "opinion"."degree" = 2
2832 AND "opinion"."fulfilled" = TRUE
2833 ) AS "plus2_fulfilled_count"
2834 FROM "suggestion" JOIN "initiative"
2835 ON "suggestion"."initiative_id" = "initiative"."id"
2836 WHERE "initiative"."issue_id" = "issue_id_v";
2837 END LOOP;
2838 RETURN "snapshot_id_v";
2839 END;
2840 $$;
2842 COMMENT ON FUNCTION "take_snapshot"
2843 ( "issue"."id"%TYPE,
2844 "area"."id"%TYPE )
2845 IS 'This function creates a new interest/supporter snapshot of a particular issue, or, if the first argument is NULL, for all issues in ''admission'' phase of the area given as second argument. It must be executed with TRANSACTION ISOLATION LEVEL REPEATABLE READ. The snapshot must later be finished by calling "finish_snapshot" for every issue.';
2848 DROP FUNCTION "set_snapshot_event"
2849 ( "issue_id_p" "issue"."id"%TYPE,
2850 "event_p" "snapshot_event" );
2853 CREATE FUNCTION "finish_snapshot"
2854 ( "issue_id_p" "issue"."id"%TYPE )
2855 RETURNS VOID
2856 LANGUAGE 'plpgsql' VOLATILE AS $$
2857 DECLARE
2858 "snapshot_id_v" "snapshot"."id"%TYPE;
2859 BEGIN
2860 -- NOTE: function does not require snapshot isolation but we don't call
2861 -- "dont_require_snapshot_isolation" here because this function is
2862 -- also invoked by "check_issue"
2863 LOCK TABLE "snapshot" IN EXCLUSIVE MODE;
2864 SELECT "id" INTO "snapshot_id_v" FROM "snapshot"
2865 ORDER BY "id" DESC LIMIT 1;
2866 UPDATE "issue" SET
2867 "calculated" = "snapshot"."calculated",
2868 "latest_snapshot_id" = "snapshot_id_v",
2869 "population" = "snapshot"."population"
2870 FROM "snapshot"
2871 WHERE "issue"."id" = "issue_id_p"
2872 AND "snapshot"."id" = "snapshot_id_v";
2873 UPDATE "initiative" SET
2874 "supporter_count" = (
2875 SELECT coalesce(sum("di"."weight"), 0)
2876 FROM "direct_interest_snapshot" AS "di"
2877 JOIN "direct_supporter_snapshot" AS "ds"
2878 ON "di"."member_id" = "ds"."member_id"
2879 WHERE "di"."snapshot_id" = "snapshot_id_v"
2880 AND "di"."issue_id" = "issue_id_p"
2881 AND "ds"."snapshot_id" = "snapshot_id_v"
2882 AND "ds"."initiative_id" = "initiative"."id"
2883 ),
2884 "informed_supporter_count" = (
2885 SELECT coalesce(sum("di"."weight"), 0)
2886 FROM "direct_interest_snapshot" AS "di"
2887 JOIN "direct_supporter_snapshot" AS "ds"
2888 ON "di"."member_id" = "ds"."member_id"
2889 WHERE "di"."snapshot_id" = "snapshot_id_v"
2890 AND "di"."issue_id" = "issue_id_p"
2891 AND "ds"."snapshot_id" = "snapshot_id_v"
2892 AND "ds"."initiative_id" = "initiative"."id"
2893 AND "ds"."informed"
2894 ),
2895 "satisfied_supporter_count" = (
2896 SELECT coalesce(sum("di"."weight"), 0)
2897 FROM "direct_interest_snapshot" AS "di"
2898 JOIN "direct_supporter_snapshot" AS "ds"
2899 ON "di"."member_id" = "ds"."member_id"
2900 WHERE "di"."snapshot_id" = "snapshot_id_v"
2901 AND "di"."issue_id" = "issue_id_p"
2902 AND "ds"."snapshot_id" = "snapshot_id_v"
2903 AND "ds"."initiative_id" = "initiative"."id"
2904 AND "ds"."satisfied"
2905 ),
2906 "satisfied_informed_supporter_count" = (
2907 SELECT coalesce(sum("di"."weight"), 0)
2908 FROM "direct_interest_snapshot" AS "di"
2909 JOIN "direct_supporter_snapshot" AS "ds"
2910 ON "di"."member_id" = "ds"."member_id"
2911 WHERE "di"."snapshot_id" = "snapshot_id_v"
2912 AND "di"."issue_id" = "issue_id_p"
2913 AND "ds"."snapshot_id" = "snapshot_id_v"
2914 AND "ds"."initiative_id" = "initiative"."id"
2915 AND "ds"."informed"
2916 AND "ds"."satisfied"
2918 WHERE "issue_id" = "issue_id_p";
2919 UPDATE "suggestion" SET
2920 "minus2_unfulfilled_count" = "temp"."minus2_unfulfilled_count",
2921 "minus2_fulfilled_count" = "temp"."minus2_fulfilled_count",
2922 "minus1_unfulfilled_count" = "temp"."minus1_unfulfilled_count",
2923 "minus1_fulfilled_count" = "temp"."minus1_fulfilled_count",
2924 "plus1_unfulfilled_count" = "temp"."plus1_unfulfilled_count",
2925 "plus1_fulfilled_count" = "temp"."plus1_fulfilled_count",
2926 "plus2_unfulfilled_count" = "temp"."plus2_unfulfilled_count",
2927 "plus2_fulfilled_count" = "temp"."plus2_fulfilled_count"
2928 FROM "temporary_suggestion_counts" AS "temp", "initiative"
2929 WHERE "temp"."id" = "suggestion"."id"
2930 AND "initiative"."issue_id" = "issue_id_p"
2931 AND "suggestion"."initiative_id" = "initiative"."id";
2932 DELETE FROM "temporary_suggestion_counts";
2933 RETURN;
2934 END;
2935 $$;
2937 COMMENT ON FUNCTION "finish_snapshot"
2938 ( "issue"."id"%TYPE )
2939 IS 'After calling "take_snapshot", this function "finish_snapshot" needs to be called for every issue in the snapshot (separate function calls keep locking time minimal)';
2942 CREATE FUNCTION "issue_admission"
2943 ( "area_id_p" "area"."id"%TYPE )
2944 RETURNS BOOLEAN
2945 LANGUAGE 'plpgsql' VOLATILE AS $$
2946 DECLARE
2947 "issue_id_v" "issue"."id"%TYPE;
2948 BEGIN
2949 PERFORM "dont_require_transaction_isolation"();
2950 LOCK TABLE "snapshot" IN EXCLUSIVE MODE;
2951 UPDATE "area" SET "issue_quorum" = "view"."issue_quorum"
2952 FROM "area_quorum" AS "view"
2953 WHERE "area"."id" = "view"."area_id"
2954 AND "area"."id" = "area_id_p";
2955 SELECT "id" INTO "issue_id_v" FROM "issue_for_admission"
2956 WHERE "area_id" = "area_id_p";
2957 IF "issue_id_v" ISNULL THEN RETURN FALSE; END IF;
2958 UPDATE "issue" SET
2959 "admission_snapshot_id" = "latest_snapshot_id",
2960 "state" = 'discussion',
2961 "accepted" = now(),
2962 "phase_finished" = NULL
2963 WHERE "id" = "issue_id_v";
2964 RETURN TRUE;
2965 END;
2966 $$;
2968 COMMENT ON FUNCTION "issue_admission"
2969 ( "area"."id"%TYPE )
2970 IS 'Checks if an issue in the area can be admitted for further discussion; returns TRUE on success in which case the function must be called again until it returns FALSE';
2973 CREATE OR REPLACE FUNCTION "check_issue"
2974 ( "issue_id_p" "issue"."id"%TYPE,
2975 "persist" "check_issue_persistence" )
2976 RETURNS "check_issue_persistence"
2977 LANGUAGE 'plpgsql' VOLATILE AS $$
2978 DECLARE
2979 "issue_row" "issue"%ROWTYPE;
2980 "last_calculated_v" "snapshot"."calculated"%TYPE;
2981 "policy_row" "policy"%ROWTYPE;
2982 "initiative_row" "initiative"%ROWTYPE;
2983 "state_v" "issue_state";
2984 BEGIN
2985 PERFORM "require_transaction_isolation"();
2986 IF "persist" ISNULL THEN
2987 SELECT * INTO "issue_row" FROM "issue" WHERE "id" = "issue_id_p"
2988 FOR UPDATE;
2989 SELECT "calculated" INTO "last_calculated_v"
2990 FROM "snapshot" JOIN "snapshot_issue"
2991 ON "snapshot"."id" = "snapshot_issue"."snapshot_id"
2992 WHERE "snapshot_issue"."issue_id" = "issue_id_p";
2993 IF "issue_row"."closed" NOTNULL THEN
2994 RETURN NULL;
2995 END IF;
2996 "persist"."state" := "issue_row"."state";
2997 IF
2998 ( "issue_row"."state" = 'admission' AND "last_calculated_v" >=
2999 "issue_row"."created" + "issue_row"."max_admission_time" ) OR
3000 ( "issue_row"."state" = 'discussion' AND now() >=
3001 "issue_row"."accepted" + "issue_row"."discussion_time" ) OR
3002 ( "issue_row"."state" = 'verification' AND now() >=
3003 "issue_row"."half_frozen" + "issue_row"."verification_time" ) OR
3004 ( "issue_row"."state" = 'voting' AND now() >=
3005 "issue_row"."fully_frozen" + "issue_row"."voting_time" )
3006 THEN
3007 "persist"."phase_finished" := TRUE;
3008 ELSE
3009 "persist"."phase_finished" := FALSE;
3010 END IF;
3011 IF
3012 NOT EXISTS (
3013 -- all initiatives are revoked
3014 SELECT NULL FROM "initiative"
3015 WHERE "issue_id" = "issue_id_p" AND "revoked" ISNULL
3016 ) AND (
3017 -- and issue has not been accepted yet
3018 "persist"."state" = 'admission' OR
3019 -- or verification time has elapsed
3020 ( "persist"."state" = 'verification' AND
3021 "persist"."phase_finished" ) OR
3022 -- or no initiatives have been revoked lately
3023 NOT EXISTS (
3024 SELECT NULL FROM "initiative"
3025 WHERE "issue_id" = "issue_id_p"
3026 AND now() < "revoked" + "issue_row"."verification_time"
3029 THEN
3030 "persist"."issue_revoked" := TRUE;
3031 ELSE
3032 "persist"."issue_revoked" := FALSE;
3033 END IF;
3034 IF "persist"."phase_finished" OR "persist"."issue_revoked" THEN
3035 UPDATE "issue" SET "phase_finished" = now()
3036 WHERE "id" = "issue_row"."id";
3037 RETURN "persist";
3038 ELSIF
3039 "persist"."state" IN ('admission', 'discussion', 'verification')
3040 THEN
3041 RETURN "persist";
3042 ELSE
3043 RETURN NULL;
3044 END IF;
3045 END IF;
3046 IF
3047 "persist"."state" IN ('admission', 'discussion', 'verification') AND
3048 coalesce("persist"."snapshot_created", FALSE) = FALSE
3049 THEN
3050 IF "persist"."state" != 'admission' THEN
3051 PERFORM "take_snapshot"("issue_id_p");
3052 PERFORM "finish_snapshot"("issue_id_p");
3053 END IF;
3054 "persist"."snapshot_created" = TRUE;
3055 IF "persist"."phase_finished" THEN
3056 IF "persist"."state" = 'admission' THEN
3057 UPDATE "issue" SET "admission_snapshot_id" = "latest_snapshot_id";
3058 ELSIF "persist"."state" = 'discussion' THEN
3059 UPDATE "issue" SET "half_freeze_snapshot_id" = "latest_snapshot_id";
3060 ELSIF "persist"."state" = 'verification' THEN
3061 UPDATE "issue" SET "full_freeze_snapshot_id" = "latest_snapshot_id";
3062 SELECT * INTO "issue_row" FROM "issue" WHERE "id" = "issue_id_p";
3063 SELECT * INTO "policy_row" FROM "policy"
3064 WHERE "id" = "issue_row"."policy_id";
3065 FOR "initiative_row" IN
3066 SELECT * FROM "initiative"
3067 WHERE "issue_id" = "issue_id_p" AND "revoked" ISNULL
3068 FOR UPDATE
3069 LOOP
3070 IF
3071 "initiative_row"."polling" OR (
3072 "initiative_row"."satisfied_supporter_count" >
3073 "policy_row"."initiative_quorum" AND
3074 "initiative_row"."satisfied_supporter_count" *
3075 "policy_row"."initiative_quorum_den" >=
3076 "issue_row"."population" * "policy_row"."initiative_quorum_num"
3078 THEN
3079 UPDATE "initiative" SET "admitted" = TRUE
3080 WHERE "id" = "initiative_row"."id";
3081 ELSE
3082 UPDATE "initiative" SET "admitted" = FALSE
3083 WHERE "id" = "initiative_row"."id";
3084 END IF;
3085 END LOOP;
3086 END IF;
3087 END IF;
3088 RETURN "persist";
3089 END IF;
3090 IF
3091 "persist"."state" IN ('admission', 'discussion', 'verification') AND
3092 coalesce("persist"."harmonic_weights_set", FALSE) = FALSE
3093 THEN
3094 PERFORM "set_harmonic_initiative_weights"("issue_id_p");
3095 "persist"."harmonic_weights_set" = TRUE;
3096 IF
3097 "persist"."phase_finished" OR
3098 "persist"."issue_revoked" OR
3099 "persist"."state" = 'admission'
3100 THEN
3101 RETURN "persist";
3102 ELSE
3103 RETURN NULL;
3104 END IF;
3105 END IF;
3106 IF "persist"."issue_revoked" THEN
3107 IF "persist"."state" = 'admission' THEN
3108 "state_v" := 'canceled_revoked_before_accepted';
3109 ELSIF "persist"."state" = 'discussion' THEN
3110 "state_v" := 'canceled_after_revocation_during_discussion';
3111 ELSIF "persist"."state" = 'verification' THEN
3112 "state_v" := 'canceled_after_revocation_during_verification';
3113 END IF;
3114 UPDATE "issue" SET
3115 "state" = "state_v",
3116 "closed" = "phase_finished",
3117 "phase_finished" = NULL
3118 WHERE "id" = "issue_id_p";
3119 RETURN NULL;
3120 END IF;
3121 IF "persist"."state" = 'admission' THEN
3122 SELECT * INTO "issue_row" FROM "issue" WHERE "id" = "issue_id_p"
3123 FOR UPDATE;
3124 IF "issue_row"."phase_finished" NOTNULL THEN
3125 UPDATE "issue" SET
3126 "state" = 'canceled_issue_not_accepted',
3127 "closed" = "phase_finished",
3128 "phase_finished" = NULL
3129 WHERE "id" = "issue_id_p";
3130 END IF;
3131 RETURN NULL;
3132 END IF;
3133 IF "persist"."phase_finished" THEN
3134 IF "persist"."state" = 'discussion' THEN
3135 UPDATE "issue" SET
3136 "state" = 'verification',
3137 "half_frozen" = "phase_finished",
3138 "phase_finished" = NULL
3139 WHERE "id" = "issue_id_p";
3140 RETURN NULL;
3141 END IF;
3142 IF "persist"."state" = 'verification' THEN
3143 SELECT * INTO "issue_row" FROM "issue" WHERE "id" = "issue_id_p"
3144 FOR UPDATE;
3145 SELECT * INTO "policy_row" FROM "policy"
3146 WHERE "id" = "issue_row"."policy_id";
3147 IF EXISTS (
3148 SELECT NULL FROM "initiative"
3149 WHERE "issue_id" = "issue_id_p" AND "admitted" = TRUE
3150 ) THEN
3151 UPDATE "issue" SET
3152 "state" = 'voting',
3153 "fully_frozen" = "phase_finished",
3154 "phase_finished" = NULL
3155 WHERE "id" = "issue_id_p";
3156 ELSE
3157 UPDATE "issue" SET
3158 "state" = 'canceled_no_initiative_admitted',
3159 "fully_frozen" = "phase_finished",
3160 "closed" = "phase_finished",
3161 "phase_finished" = NULL
3162 WHERE "id" = "issue_id_p";
3163 -- NOTE: The following DELETE statements have effect only when
3164 -- issue state has been manipulated
3165 DELETE FROM "direct_voter" WHERE "issue_id" = "issue_id_p";
3166 DELETE FROM "delegating_voter" WHERE "issue_id" = "issue_id_p";
3167 DELETE FROM "battle" WHERE "issue_id" = "issue_id_p";
3168 END IF;
3169 RETURN NULL;
3170 END IF;
3171 IF "persist"."state" = 'voting' THEN
3172 IF coalesce("persist"."closed_voting", FALSE) = FALSE THEN
3173 PERFORM "close_voting"("issue_id_p");
3174 "persist"."closed_voting" = TRUE;
3175 RETURN "persist";
3176 END IF;
3177 PERFORM "calculate_ranks"("issue_id_p");
3178 RETURN NULL;
3179 END IF;
3180 END IF;
3181 RAISE WARNING 'should not happen';
3182 RETURN NULL;
3183 END;
3184 $$;
3187 CREATE OR REPLACE FUNCTION "check_everything"()
3188 RETURNS VOID
3189 LANGUAGE 'plpgsql' VOLATILE AS $$
3190 DECLARE
3191 "area_id_v" "area"."id"%TYPE;
3192 "snapshot_id_v" "snapshot"."id"%TYPE;
3193 "issue_id_v" "issue"."id"%TYPE;
3194 "persist_v" "check_issue_persistence";
3195 BEGIN
3196 RAISE WARNING 'Function "check_everything" should only be used for development and debugging purposes';
3197 DELETE FROM "expired_session";
3198 DELETE FROM "expired_token";
3199 DELETE FROM "expired_snapshot";
3200 PERFORM "check_activity"();
3201 PERFORM "calculate_member_counts"();
3202 FOR "area_id_v" IN SELECT "id" FROM "area_with_unaccepted_issues" LOOP
3203 SELECT "take_snapshot"(NULL, "area_id_v") INTO "snapshot_id_v";
3204 PERFORM "finish_snapshot"("issue_id") FROM "snapshot_issue"
3205 WHERE "snapshot_id" = "snapshot_id_v";
3206 LOOP
3207 EXIT WHEN "issue_admission"("area_id_v") = FALSE;
3208 END LOOP;
3209 END LOOP;
3210 FOR "issue_id_v" IN SELECT "id" FROM "open_issue" LOOP
3211 "persist_v" := NULL;
3212 LOOP
3213 "persist_v" := "check_issue"("issue_id_v", "persist_v");
3214 EXIT WHEN "persist_v" ISNULL;
3215 END LOOP;
3216 END LOOP;
3217 RETURN;
3218 END;
3219 $$;
3221 COMMENT ON FUNCTION "check_everything"() IS 'Amongst other regular tasks, this function performs "check_issue" for every open issue. Use this function only for development and debugging purposes, as you may run into locking and/or serialization problems in productive environments. For production, use lf_update binary instead';
3224 CREATE OR REPLACE FUNCTION "clean_issue"("issue_id_p" "issue"."id"%TYPE)
3225 RETURNS VOID
3226 LANGUAGE 'plpgsql' VOLATILE AS $$
3227 BEGIN
3228 IF EXISTS (
3229 SELECT NULL FROM "issue" WHERE "id" = "issue_id_p" AND "cleaned" ISNULL
3230 ) THEN
3231 -- override protection triggers:
3232 INSERT INTO "temporary_transaction_data" ("key", "value")
3233 VALUES ('override_protection_triggers', TRUE::TEXT);
3234 -- clean data:
3235 DELETE FROM "delegating_voter"
3236 WHERE "issue_id" = "issue_id_p";
3237 DELETE FROM "direct_voter"
3238 WHERE "issue_id" = "issue_id_p";
3239 DELETE FROM "delegating_interest_snapshot"
3240 WHERE "issue_id" = "issue_id_p";
3241 DELETE FROM "direct_interest_snapshot"
3242 WHERE "issue_id" = "issue_id_p";
3243 DELETE FROM "non_voter"
3244 WHERE "issue_id" = "issue_id_p";
3245 DELETE FROM "delegation"
3246 WHERE "issue_id" = "issue_id_p";
3247 DELETE FROM "supporter"
3248 USING "initiative" -- NOTE: due to missing index on issue_id
3249 WHERE "initiative"."issue_id" = "issue_id_p"
3250 AND "supporter"."initiative_id" = "initiative_id";
3251 -- mark issue as cleaned:
3252 UPDATE "issue" SET "cleaned" = now() WHERE "id" = "issue_id_p";
3253 -- finish overriding protection triggers (avoids garbage):
3254 DELETE FROM "temporary_transaction_data"
3255 WHERE "key" = 'override_protection_triggers';
3256 END IF;
3257 RETURN;
3258 END;
3259 $$;
3262 CREATE OR REPLACE FUNCTION "delete_member"("member_id_p" "member"."id"%TYPE)
3263 RETURNS VOID
3264 LANGUAGE 'plpgsql' VOLATILE AS $$
3265 BEGIN
3266 UPDATE "member" SET
3267 "last_login" = NULL,
3268 "last_delegation_check" = NULL,
3269 "login" = NULL,
3270 "password" = NULL,
3271 "authority" = NULL,
3272 "authority_uid" = NULL,
3273 "authority_login" = NULL,
3274 "locked" = TRUE,
3275 "active" = FALSE,
3276 "notify_email" = NULL,
3277 "notify_email_unconfirmed" = NULL,
3278 "notify_email_secret" = NULL,
3279 "notify_email_secret_expiry" = NULL,
3280 "notify_email_lock_expiry" = NULL,
3281 "disable_notifications" = TRUE,
3282 "notification_counter" = DEFAULT,
3283 "notification_sample_size" = 0,
3284 "notification_dow" = NULL,
3285 "notification_hour" = NULL,
3286 "notification_sent" = NULL,
3287 "login_recovery_expiry" = NULL,
3288 "password_reset_secret" = NULL,
3289 "password_reset_secret_expiry" = NULL,
3290 "location" = NULL
3291 WHERE "id" = "member_id_p";
3292 -- "text_search_data" is updated by triggers
3293 DELETE FROM "member_settings" WHERE "member_id" = "member_id_p";
3294 DELETE FROM "member_profile" WHERE "member_id" = "member_id_p";
3295 DELETE FROM "rendered_member_statement" WHERE "member_id" = "member_id_p";
3296 DELETE FROM "member_image" WHERE "member_id" = "member_id_p";
3297 DELETE FROM "contact" WHERE "member_id" = "member_id_p";
3298 DELETE FROM "ignored_member" WHERE "member_id" = "member_id_p";
3299 DELETE FROM "session" WHERE "member_id" = "member_id_p";
3300 DELETE FROM "member_application" WHERE "member_id" = "member_id_p";
3301 DELETE FROM "token" WHERE "member_id" = "member_id_p";
3302 DELETE FROM "subscription" WHERE "member_id" = "member_id_p";
3303 DELETE FROM "ignored_area" WHERE "member_id" = "member_id_p";
3304 DELETE FROM "ignored_initiative" WHERE "member_id" = "member_id_p";
3305 DELETE FROM "delegation" WHERE "truster_id" = "member_id_p";
3306 DELETE FROM "non_voter" WHERE "member_id" = "member_id_p";
3307 DELETE FROM "direct_voter" USING "issue"
3308 WHERE "direct_voter"."issue_id" = "issue"."id"
3309 AND "issue"."closed" ISNULL
3310 AND "member_id" = "member_id_p";
3311 DELETE FROM "notification_initiative_sent" WHERE "member_id" = "member_id_p";
3312 RETURN;
3313 END;
3314 $$;
3317 CREATE OR REPLACE FUNCTION "delete_private_data"()
3318 RETURNS VOID
3319 LANGUAGE 'plpgsql' VOLATILE AS $$
3320 BEGIN
3321 DELETE FROM "temporary_transaction_data";
3322 DELETE FROM "temporary_suggestion_counts";
3323 DELETE FROM "member" WHERE "activated" ISNULL;
3324 UPDATE "member" SET
3325 "invite_code" = NULL,
3326 "invite_code_expiry" = NULL,
3327 "admin_comment" = NULL,
3328 "last_login" = NULL,
3329 "last_delegation_check" = NULL,
3330 "login" = NULL,
3331 "password" = NULL,
3332 "authority" = NULL,
3333 "authority_uid" = NULL,
3334 "authority_login" = NULL,
3335 "lang" = NULL,
3336 "notify_email" = NULL,
3337 "notify_email_unconfirmed" = NULL,
3338 "notify_email_secret" = NULL,
3339 "notify_email_secret_expiry" = NULL,
3340 "notify_email_lock_expiry" = NULL,
3341 "disable_notifications" = TRUE,
3342 "notification_counter" = DEFAULT,
3343 "notification_sample_size" = 0,
3344 "notification_dow" = NULL,
3345 "notification_hour" = NULL,
3346 "notification_sent" = NULL,
3347 "login_recovery_expiry" = NULL,
3348 "password_reset_secret" = NULL,
3349 "password_reset_secret_expiry" = NULL,
3350 "location" = NULL;
3351 -- "text_search_data" is updated by triggers
3352 DELETE FROM "member_settings";
3353 DELETE FROM "member_useterms";
3354 DELETE FROM "member_profile";
3355 DELETE FROM "rendered_member_statement";
3356 DELETE FROM "member_image";
3357 DELETE FROM "contact";
3358 DELETE FROM "ignored_member";
3359 DELETE FROM "session";
3360 DELETE FROM "system_application";
3361 DELETE FROM "system_application_redirect_uri";
3362 DELETE FROM "dynamic_application_scope";
3363 DELETE FROM "member_application";
3364 DELETE FROM "token";
3365 DELETE FROM "subscription";
3366 DELETE FROM "ignored_area";
3367 DELETE FROM "ignored_initiative";
3368 DELETE FROM "non_voter";
3369 DELETE FROM "direct_voter" USING "issue"
3370 WHERE "direct_voter"."issue_id" = "issue"."id"
3371 AND "issue"."closed" ISNULL;
3372 DELETE FROM "event_processed";
3373 DELETE FROM "notification_initiative_sent";
3374 DELETE FROM "newsletter";
3375 RETURN;
3376 END;
3377 $$;
3380 CREATE TEMPORARY TABLE "old_snapshot" AS
3381 SELECT "ordered".*, row_number() OVER () AS "snapshot_id"
3382 FROM (
3383 SELECT * FROM (
3384 SELECT
3385 "id" AS "issue_id",
3386 'end_of_admission'::"snapshot_event" AS "event",
3387 "accepted" AS "calculated"
3388 FROM "issue" WHERE "accepted" NOTNULL
3389 UNION ALL
3390 SELECT
3391 "id" AS "issue_id",
3392 'half_freeze'::"snapshot_event" AS "event",
3393 "half_frozen" AS "calculated"
3394 FROM "issue" WHERE "half_frozen" NOTNULL
3395 UNION ALL
3396 SELECT
3397 "id" AS "issue_id",
3398 'full_freeze'::"snapshot_event" AS "event",
3399 "fully_frozen" AS "calculated"
3400 FROM "issue" WHERE "fully_frozen" NOTNULL
3401 ) AS "unordered"
3402 ORDER BY "calculated", "issue_id", "event"
3403 ) AS "ordered";
3406 INSERT INTO "snapshot" ("id", "calculated", "population", "area_id", "issue_id")
3407 SELECT
3408 "old_snapshot"."snapshot_id" AS "id",
3409 "old_snapshot"."calculated",
3410 ( SELECT COALESCE(sum("weight"), 0)
3411 FROM "direct_population_snapshot" "dps"
3412 WHERE "dps"."issue_id" = "old_snapshot"."issue_id"
3413 AND "dps"."event" = "old_snapshot"."event"
3414 ) AS "population",
3415 "issue"."area_id" AS "area_id",
3416 "issue"."id" AS "issue_id"
3417 FROM "old_snapshot" JOIN "issue"
3418 ON "old_snapshot"."issue_id" = "issue"."id";
3421 INSERT INTO "snapshot_issue" ("snapshot_id", "issue_id")
3422 SELECT "id" AS "snapshot_id", "issue_id" FROM "snapshot";
3425 INSERT INTO "snapshot_population" ("snapshot_id", "member_id")
3426 SELECT
3427 "old_snapshot"."snapshot_id",
3428 "direct_population_snapshot"."member_id"
3429 FROM "old_snapshot" JOIN "direct_population_snapshot"
3430 ON "old_snapshot"."issue_id" = "direct_population_snapshot"."issue_id"
3431 AND "old_snapshot"."event" = "direct_population_snapshot"."event";
3433 INSERT INTO "snapshot_population" ("snapshot_id", "member_id")
3434 SELECT
3435 "old_snapshot"."snapshot_id",
3436 "delegating_population_snapshot"."member_id"
3437 FROM "old_snapshot" JOIN "delegating_population_snapshot"
3438 ON "old_snapshot"."issue_id" = "delegating_population_snapshot"."issue_id"
3439 AND "old_snapshot"."event" = "delegating_population_snapshot"."event";
3442 INSERT INTO "direct_interest_snapshot"
3443 ("snapshot_id", "issue_id", "member_id", "weight")
3444 SELECT
3445 "old_snapshot"."snapshot_id",
3446 "old_snapshot"."issue_id",
3447 "direct_interest_snapshot_old"."member_id",
3448 "direct_interest_snapshot_old"."weight"
3449 FROM "old_snapshot" JOIN "direct_interest_snapshot_old"
3450 ON "old_snapshot"."issue_id" = "direct_interest_snapshot_old"."issue_id"
3451 AND "old_snapshot"."event" = "direct_interest_snapshot_old"."event";
3453 INSERT INTO "delegating_interest_snapshot"
3454 ( "snapshot_id", "issue_id",
3455 "member_id", "weight", "scope", "delegate_member_ids" )
3456 SELECT
3457 "old_snapshot"."snapshot_id",
3458 "old_snapshot"."issue_id",
3459 "delegating_interest_snapshot_old"."member_id",
3460 "delegating_interest_snapshot_old"."weight",
3461 "delegating_interest_snapshot_old"."scope",
3462 "delegating_interest_snapshot_old"."delegate_member_ids"
3463 FROM "old_snapshot" JOIN "delegating_interest_snapshot_old"
3464 ON "old_snapshot"."issue_id" = "delegating_interest_snapshot_old"."issue_id"
3465 AND "old_snapshot"."event" = "delegating_interest_snapshot_old"."event";
3467 INSERT INTO "direct_supporter_snapshot"
3468 ( "snapshot_id", "issue_id",
3469 "initiative_id", "member_id", "draft_id", "informed", "satisfied" )
3470 SELECT
3471 "old_snapshot"."snapshot_id",
3472 "old_snapshot"."issue_id",
3473 "direct_supporter_snapshot_old"."initiative_id",
3474 "direct_supporter_snapshot_old"."member_id",
3475 "direct_supporter_snapshot_old"."draft_id",
3476 "direct_supporter_snapshot_old"."informed",
3477 "direct_supporter_snapshot_old"."satisfied"
3478 FROM "old_snapshot" JOIN "direct_supporter_snapshot_old"
3479 ON "old_snapshot"."issue_id" = "direct_supporter_snapshot_old"."issue_id"
3480 AND "old_snapshot"."event" = "direct_supporter_snapshot_old"."event";
3483 ALTER TABLE "issue" DISABLE TRIGGER USER; -- NOTE: required to modify table later
3485 UPDATE "issue" SET "latest_snapshot_id" = "snapshot"."id"
3486 FROM (
3487 SELECT DISTINCT ON ("issue_id") "issue_id", "id"
3488 FROM "snapshot" ORDER BY "issue_id", "id" DESC
3489 ) AS "snapshot"
3490 WHERE "snapshot"."issue_id" = "issue"."id";
3492 UPDATE "issue" SET "admission_snapshot_id" = "old_snapshot"."snapshot_id"
3493 FROM "old_snapshot"
3494 WHERE "old_snapshot"."issue_id" = "issue"."id"
3495 AND "old_snapshot"."event" = 'end_of_admission';
3497 UPDATE "issue" SET "half_freeze_snapshot_id" = "old_snapshot"."snapshot_id"
3498 FROM "old_snapshot"
3499 WHERE "old_snapshot"."issue_id" = "issue"."id"
3500 AND "old_snapshot"."event" = 'half_freeze';
3502 UPDATE "issue" SET "full_freeze_snapshot_id" = "old_snapshot"."snapshot_id"
3503 FROM "old_snapshot"
3504 WHERE "old_snapshot"."issue_id" = "issue"."id"
3505 AND "old_snapshot"."event" = 'full_freeze';
3507 ALTER TABLE "issue" ENABLE TRIGGER USER;
3510 DROP TABLE "old_snapshot";
3512 DROP TABLE "direct_supporter_snapshot_old";
3513 DROP TABLE "delegating_interest_snapshot_old";
3514 DROP TABLE "direct_interest_snapshot_old";
3515 DROP TABLE "delegating_population_snapshot";
3516 DROP TABLE "direct_population_snapshot";
3519 DROP VIEW "open_issue";
3522 ALTER TABLE "issue" DROP COLUMN "latest_snapshot_event";
3525 CREATE VIEW "open_issue" AS
3526 SELECT * FROM "issue" WHERE "closed" ISNULL;
3528 COMMENT ON VIEW "open_issue" IS 'All open issues';
3531 -- NOTE: create "issue_for_admission" view after altering table "issue"
3532 CREATE VIEW "issue_for_admission" AS
3533 SELECT DISTINCT ON ("issue"."area_id")
3534 "issue".*,
3535 max("initiative"."supporter_count") AS "max_supporter_count"
3536 FROM "issue"
3537 JOIN "policy" ON "issue"."policy_id" = "policy"."id"
3538 JOIN "initiative" ON "issue"."id" = "initiative"."issue_id"
3539 JOIN "area" ON "issue"."area_id" = "area"."id"
3540 WHERE "issue"."state" = 'admission'::"issue_state"
3541 AND now() >= "issue"."created" + "issue"."min_admission_time"
3542 AND "initiative"."supporter_count" >= "policy"."issue_quorum"
3543 AND "initiative"."supporter_count" * "policy"."issue_quorum_den" >=
3544 "issue"."population" * "policy"."issue_quorum_num"
3545 AND "initiative"."supporter_count" >= "area"."issue_quorum"
3546 AND "initiative"."revoked" ISNULL
3547 GROUP BY "issue"."id"
3548 ORDER BY "issue"."area_id", "max_supporter_count" DESC, "issue"."id";
3550 COMMENT ON VIEW "issue_for_admission" IS 'Contains up to 1 issue per area eligible to pass from ''admission'' to ''discussion'' state; needs to be recalculated after admitting the issue in this view';
3553 DROP TYPE "snapshot_event";
3556 ALTER TABLE "issue" ADD CONSTRAINT "snapshot_required" CHECK (
3557 ("half_frozen" ISNULL OR "half_freeze_snapshot_id" NOTNULL) AND
3558 ("fully_frozen" ISNULL OR "full_freeze_snapshot_id" NOTNULL) );
3561 COMMIT;

Impressum / About Us