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