
jbe@619: CREATE OR REPLACE VIEW "liquid_feedback_version" AS
jbe@619:   SELECT * FROM (VALUES ('4.2.0-incomplete-update', 4, 2, -1))
jbe@619:   AS "subquery"("string", "major", "minor", "revision");
jbe@619: 
jbe@619: ALTER TYPE "event_type" ADD VALUE IF NOT EXISTS 'posting_created';
jbe@619: 
jbe@619: BEGIN;
jbe@619: 
jbe@619: CREATE OR REPLACE VIEW "liquid_feedback_version" AS
jbe@619:   SELECT * FROM (VALUES ('4.2.1', 4, 2, 1))
jbe@619:   AS "subquery"("string", "major", "minor", "revision");
jbe@619: 
jbe@619: DROP VIEW "newsletter_to_send";
jbe@619: DROP VIEW "scheduled_notification_to_send";
jbe@619: DROP VIEW "member_contingent_left";
jbe@619: DROP VIEW "member_contingent";
jbe@619: DROP VIEW "expired_snapshot";
jbe@619: DROP VIEW "current_draft";
jbe@619: DROP VIEW "opening_draft";
jbe@619: DROP VIEW "area_with_unaccepted_issues";
jbe@619: DROP VIEW "member_to_notify";
jbe@619: DROP VIEW "member_eligible_to_be_notified";
jbe@619: 
jbe@619: DROP FUNCTION "text_search_query" (TEXT);
jbe@619: 
jbe@619: ALTER TABLE "system_setting" DROP COLUMN "snapshot_retention";
jbe@619: 
jbe@619: CREATE TABLE "file" (
jbe@619:         "id"                    SERIAL8         PRIMARY KEY,
jbe@619:         UNIQUE ("content_type", "hash"),
jbe@619:         "content_type"          TEXT            NOT NULL,
jbe@619:         "hash"                  TEXT            NOT NULL,
jbe@619:         "data"                  BYTEA           NOT NULL,
jbe@619:         "preview_content_type"  TEXT,
jbe@619:         "preview_data"          BYTEA );
jbe@619: 
jbe@619: COMMENT ON TABLE "file" IS 'Table holding file contents for draft attachments';
jbe@619: 
jbe@619: COMMENT ON COLUMN "file"."content_type"         IS 'Content type of "data"';
jbe@619: COMMENT ON COLUMN "file"."hash"                 IS 'Hash of "data" to avoid storing duplicates where content-type and data is identical';
jbe@619: COMMENT ON COLUMN "file"."data"                 IS 'Binary content';
jbe@619: COMMENT ON COLUMN "file"."preview_content_type" IS 'Content type of "preview_data"';
jbe@619: COMMENT ON COLUMN "file"."preview_data"         IS 'Preview (e.g. preview image)';
jbe@619: 
jbe@619: ALTER TABLE "member" DROP COLUMN "text_search_data";
jbe@619: DROP TRIGGER "update_text_search_data" ON "member";
jbe@619: 
jbe@619: CREATE INDEX "member_useterms_member_id_contract_identifier" ON "member_useterms" ("member_id", "contract_identifier");
jbe@619: 
jbe@619: ALTER TABLE "member_profile" DROP COLUMN "text_search_data";
jbe@619: DROP TRIGGER "update_text_search_data" ON "member_profile";
jbe@619: 
jbe@619: ALTER TABLE "contact" ADD COLUMN "following" BOOLEAN NOT NULL DEFAULT TRUE;
jbe@619: 
jbe@619: COMMENT ON COLUMN "contact"."following" IS 'TRUE = actions of contact are shown in personal timeline';
jbe@619: 
jbe@619: ALTER TABLE "unit" DROP COLUMN "text_search_data";
jbe@619: DROP TRIGGER "update_text_search_data" ON "unit";
jbe@619: 
jbe@619: ALTER TABLE "area" DROP COLUMN "text_search_data";
jbe@619: DROP TRIGGER "update_text_search_data" ON "area";
jbe@619: 
jbe@619: DROP INDEX "issue_accepted_idx";
jbe@619: DROP INDEX "issue_half_frozen_idx";
jbe@619: DROP INDEX "issue_fully_frozen_idx";
jbe@619: ALTER INDEX "issue_created_idx_open" RENAME TO "issue_open_created_idx";
jbe@619: DROP INDEX "issue_closed_idx_canceled";
jbe@619: ALTER INDEX "issue_latest_snapshot_id" RENAME TO "issue_latest_snapshot_id_idx";
jbe@619: ALTER INDEX "issue_admission_snapshot_id" RENAME TO "issue_admission_snapshot_id_idx";
jbe@619: ALTER INDEX "issue_half_freeze_snapshot_id" RENAME TO "issue_half_freeze_snapshot_id_idx";
jbe@619: ALTER INDEX "issue_full_freeze_snapshot_id" RENAME TO "issue_full_freeze_snapshot_id_idx";
jbe@619: 
jbe@619: ALTER TABLE "initiative" ADD COLUMN "content" TEXT;
jbe@619: ALTER TABLE "initiative" DROP COLUMN "text_search_data";
jbe@619: ALTER TABLE "initiative" DROP COLUMN "draft_text_search_data";
jbe@619: DROP INDEX "initiative_revoked_idx";
jbe@619: DROP TRIGGER "update_text_search_data" ON "initiative";
jbe@619: 
jbe@619: COMMENT ON COLUMN "initiative"."content" IS 'Initiative text (automatically copied from most recent draft)';
jbe@619: 
jbe@619: ALTER TABLE "battle" DROP CONSTRAINT "initiative_ids_not_equal";
jbe@619: ALTER TABLE "battle" ADD CONSTRAINT "initiative_ids_not_equal" CHECK (
jbe@619:   "winning_initiative_id" != "losing_initiative_id" AND
jbe@619:   ("winning_initiative_id" NOTNULL OR "losing_initiative_id" NOTNULL) );
jbe@619: 
jbe@619: ALTER TABLE "draft" DROP COLUMN "text_search_data";
jbe@619: DROP TRIGGER "update_text_search_data" ON "draft";
jbe@619: 
jbe@619: CREATE TABLE "draft_attachment" (
jbe@619:         "id"                    SERIAL8         PRIMARY KEY,
jbe@619:         "draft_id"              INT8            REFERENCES "draft" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@619:         "file_id"               INT8            REFERENCES "file" ("id") ON DELETE RESTRICT ON UPDATE CASCADE,
jbe@619:         "title"                 TEXT,
jbe@619:         "description"           TEXT );
jbe@619: 
jbe@619: COMMENT ON TABLE "draft_attachment" IS 'Binary attachments for a draft (images, PDF file, etc.); Implicitly ordered through ''id'' column';
jbe@619: 
jbe@619: ALTER TABLE "suggestion" DROP COLUMN "text_search_data";
jbe@619: DROP TRIGGER "update_text_search_data" ON "suggestion";
jbe@619: 
jbe@619: ALTER TABLE "direct_voter" DROP COLUMN "text_search_data";
jbe@619: DROP TRIGGER "update_text_search_data" ON "direct_voter";
jbe@619: 
jbe@619: CREATE TABLE "posting" (
jbe@619:         UNIQUE ("author_id", "id"),  -- index needed for foreign-key on table "posting_lexeme"
jbe@619:         "id"                    SERIAL8         PRIMARY KEY,
jbe@619:         "author_id"             INT4            NOT NULL REFERENCES "member" ("id") ON DELETE RESTRICT ON UPDATE CASCADE,
jbe@619:         "created"               TIMESTAMPTZ     NOT NULL DEFAULT now(),
jbe@619:         "message"               TEXT            NOT NULL,
jbe@619:         "unit_id"               INT4            REFERENCES "unit" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@619:         "area_id"               INT4,
jbe@619:         FOREIGN KEY ("unit_id", "area_id") REFERENCES "area" ("unit_id", "id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@619:         "policy_id"             INT4            REFERENCES "policy" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@619:         "issue_id"              INT4            REFERENCES "issue" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@619:         FOREIGN KEY ("area_id", "issue_id") REFERENCES "issue" ("area_id", "id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@619:         FOREIGN KEY ("policy_id", "issue_id") REFERENCES "issue" ("policy_id", "id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@619:         "initiative_id"         INT4,
jbe@619:         "suggestion_id"         INT8,
jbe@619:         -- NOTE: no referential integrity for suggestions because those are
jbe@619:         --       actually deleted
jbe@619:         -- FOREIGN KEY ("initiative_id", "suggestion_id")
jbe@619:         --   REFERENCES "suggestion" ("initiative_id", "id")
jbe@619:         --   ON DELETE CASCADE ON UPDATE CASCADE,
jbe@619:         CONSTRAINT "area_requires_unit" CHECK (
jbe@619:           "area_id" ISNULL OR "unit_id" NOTNULL ),
jbe@619:         CONSTRAINT "policy_set_when_issue_set" CHECK (
jbe@619:           ("policy_id" NOTNULL) = ("issue_id" NOTNULL) ),
jbe@619:         CONSTRAINT "issue_requires_area" CHECK (
jbe@619:           "issue_id" ISNULL OR "area_id" NOTNULL ),
jbe@619:         CONSTRAINT "initiative_requires_issue" CHECK (
jbe@619:           "initiative_id" ISNULL OR "issue_id" NOTNULL ),
jbe@619:         CONSTRAINT "suggestion_requires_initiative" CHECK (
jbe@619:           "suggestion_id" ISNULL OR "initiative_id" NOTNULL ) );
jbe@619: COMMENT ON TABLE "posting" IS 'Text postings of members; a text posting may optionally be associated to a unit, area, policy, issue, initiative, or suggestion';
jbe@619: 
jbe@619: CREATE TABLE "posting_lexeme" (
jbe@619:         PRIMARY KEY ("posting_id", "lexeme"),
jbe@619:         FOREIGN KEY ("posting_id", "author_id") REFERENCES "posting" ("id", "author_id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@619:         "posting_id"            INT8,
jbe@619:         "lexeme"                TEXT,
jbe@619:         "author_id"             INT4 );
jbe@619: 
jbe@619: COMMENT ON TABLE "posting_lexeme" IS 'Helper table to allow searches for hashtags.';
jbe@619: 
jbe@619: ALTER TABLE "event" ADD COLUMN "posting_id" INT8 REFERENCES "posting" ("id") ON DELETE RESTRICT ON UPDATE CASCADE;
jbe@619: ALTER TABLE "event" DROP CONSTRAINT "constr_for_issue_state_changed";
jbe@619: ALTER TABLE "event" DROP CONSTRAINT "constr_for_initiative_creation_or_revocation_or_new_draft";
jbe@619: ALTER TABLE "event" DROP CONSTRAINT "constr_for_suggestion_creation";
jbe@619: ALTER TABLE "event" DROP CONSTRAINT "constr_for_suggestion_removal";
jbe@619: ALTER TABLE "event" DROP CONSTRAINT "constr_for_value_less_member_event";
jbe@619: ALTER TABLE "event" DROP CONSTRAINT "constr_for_member_active";
jbe@619: ALTER TABLE "event" DROP CONSTRAINT "constr_for_member_name_updated";
jbe@619: ALTER TABLE "event" DROP CONSTRAINT "constr_for_interest";
jbe@619: ALTER TABLE "event" DROP CONSTRAINT "constr_for_initiator";
jbe@619: ALTER TABLE "event" DROP CONSTRAINT "constr_for_support";
jbe@619: ALTER TABLE "event" DROP CONSTRAINT "constr_for_support_updated";
jbe@619: ALTER TABLE "event" DROP CONSTRAINT "constr_for_suggestion_rated";
jbe@619: ALTER TABLE "event" DROP CONSTRAINT "constr_for_delegation";
jbe@619: ALTER TABLE "event" DROP CONSTRAINT "constr_for_contact";
jbe@619: ALTER TABLE "event" ADD
jbe@619:         CONSTRAINT "constr_for_issue_state_changed" CHECK (
jbe@619:           "event" != 'issue_state_changed' OR (
jbe@619:             "posting_id"      ISNULL  AND
jbe@619:             "member_id"       ISNULL  AND
jbe@619:             "other_member_id" ISNULL  AND
jbe@619:             "scope"           ISNULL  AND
jbe@619:             "unit_id"         NOTNULL AND
jbe@619:             "area_id"         NOTNULL AND
jbe@619:             "policy_id"       NOTNULL AND
jbe@619:             "issue_id"        NOTNULL AND
jbe@619:             "state"           NOTNULL AND
jbe@619:             "initiative_id"   ISNULL  AND
jbe@619:             "draft_id"        ISNULL  AND
jbe@619:             "suggestion_id"   ISNULL  AND
jbe@619:             "boolean_value"   ISNULL  AND
jbe@619:             "numeric_value"   ISNULL  AND
jbe@619:             "text_value"      ISNULL  AND
jbe@619:             "old_text_value"  ISNULL ));
jbe@619: ALTER TABLE "event" ADD
jbe@619:         CONSTRAINT "constr_for_initiative_creation_or_revocation_or_new_draft" CHECK (
jbe@619:           "event" NOT IN (
jbe@619:             'initiative_created_in_new_issue',
jbe@619:             'initiative_created_in_existing_issue',
jbe@619:             'initiative_revoked',
jbe@619:             'new_draft_created'
jbe@619:           ) OR (
jbe@619:             "posting_id"      ISNULL  AND
jbe@619:             "member_id"       NOTNULL AND
jbe@619:             "other_member_id" ISNULL  AND
jbe@619:             "scope"           ISNULL  AND
jbe@619:             "unit_id"         NOTNULL AND
jbe@619:             "area_id"         NOTNULL AND
jbe@619:             "policy_id"       NOTNULL AND
jbe@619:             "issue_id"        NOTNULL AND
jbe@619:             "state"           NOTNULL AND
jbe@619:             "initiative_id"   NOTNULL AND
jbe@619:             "draft_id"        NOTNULL AND
jbe@619:             "suggestion_id"   ISNULL  AND
jbe@619:             "boolean_value"   ISNULL  AND
jbe@619:             "numeric_value"   ISNULL  AND
jbe@619:             "text_value"      ISNULL  AND
jbe@619:             "old_text_value"  ISNULL ));
jbe@619: ALTER TABLE "event" ADD
jbe@619:         CONSTRAINT "constr_for_suggestion_creation" CHECK (
jbe@619:           "event" != 'suggestion_created' OR (
jbe@619:             "posting_id"      ISNULL  AND
jbe@619:             "member_id"       NOTNULL AND
jbe@619:             "other_member_id" ISNULL  AND
jbe@619:             "scope"           ISNULL  AND
jbe@619:             "unit_id"         NOTNULL AND
jbe@619:             "area_id"         NOTNULL AND
jbe@619:             "policy_id"       NOTNULL AND
jbe@619:             "issue_id"        NOTNULL AND
jbe@619:             "state"           NOTNULL AND
jbe@619:             "initiative_id"   NOTNULL AND
jbe@619:             "draft_id"        ISNULL  AND
jbe@619:             "suggestion_id"   NOTNULL AND
jbe@619:             "boolean_value"   ISNULL  AND
jbe@619:             "numeric_value"   ISNULL  AND
jbe@619:             "text_value"      ISNULL  AND
jbe@619:             "old_text_value"  ISNULL ));
jbe@619: ALTER TABLE "event" ADD
jbe@619:         CONSTRAINT "constr_for_suggestion_removal" CHECK (
jbe@619:           "event" != 'suggestion_deleted' OR (
jbe@619:             "posting_id"      ISNULL  AND
jbe@619:             "member_id"       ISNULL AND
jbe@619:             "other_member_id" ISNULL  AND
jbe@619:             "scope"           ISNULL  AND
jbe@619:             "unit_id"         NOTNULL AND
jbe@619:             "area_id"         NOTNULL AND
jbe@619:             "policy_id"       NOTNULL AND
jbe@619:             "issue_id"        NOTNULL AND
jbe@619:             "state"           NOTNULL AND
jbe@619:             "initiative_id"   NOTNULL AND
jbe@619:             "draft_id"        ISNULL  AND
jbe@619:             "suggestion_id"   NOTNULL AND
jbe@619:             "boolean_value"   ISNULL  AND
jbe@619:             "numeric_value"   ISNULL  AND
jbe@619:             "text_value"      ISNULL  AND
jbe@619:             "old_text_value"  ISNULL ));
jbe@619: ALTER TABLE "event" ADD
jbe@619:         CONSTRAINT "constr_for_value_less_member_event" CHECK (
jbe@619:           "event" NOT IN (
jbe@619:             'member_activated',
jbe@619:             'member_deleted',
jbe@619:             'member_profile_updated',
jbe@619:             'member_image_updated'
jbe@619:           ) OR (
jbe@619:             "posting_id"      ISNULL  AND
jbe@619:             "member_id"       NOTNULL AND
jbe@619:             "other_member_id" ISNULL  AND
jbe@619:             "scope"           ISNULL  AND
jbe@619:             "unit_id"         ISNULL  AND
jbe@619:             "area_id"         ISNULL  AND
jbe@619:             "policy_id"       ISNULL  AND
jbe@619:             "issue_id"        ISNULL  AND
jbe@619:             "state"           ISNULL  AND
jbe@619:             "initiative_id"   ISNULL  AND
jbe@619:             "draft_id"        ISNULL  AND
jbe@619:             "suggestion_id"   ISNULL  AND
jbe@619:             "boolean_value"   ISNULL  AND
jbe@619:             "numeric_value"   ISNULL  AND
jbe@619:             "text_value"      ISNULL  AND
jbe@619:             "old_text_value"  ISNULL ));
jbe@619: ALTER TABLE "event" ADD
jbe@619:         CONSTRAINT "constr_for_member_active" CHECK (
jbe@619:           "event" != 'member_active' OR (
jbe@619:             "posting_id"      ISNULL  AND
jbe@619:             "member_id"       NOTNULL AND
jbe@619:             "other_member_id" ISNULL  AND
jbe@619:             "scope"           ISNULL  AND
jbe@619:             "unit_id"         ISNULL  AND
jbe@619:             "area_id"         ISNULL  AND
jbe@619:             "policy_id"       ISNULL  AND
jbe@619:             "issue_id"        ISNULL  AND
jbe@619:             "state"           ISNULL  AND
jbe@619:             "initiative_id"   ISNULL  AND
jbe@619:             "draft_id"        ISNULL  AND
jbe@619:             "suggestion_id"   ISNULL  AND
jbe@619:             "boolean_value"   NOTNULL AND
jbe@619:             "numeric_value"   ISNULL  AND
jbe@619:             "text_value"      ISNULL  AND
jbe@619:             "old_text_value"  ISNULL ));
jbe@619: ALTER TABLE "event" ADD
jbe@619:         CONSTRAINT "constr_for_member_name_updated" CHECK (
jbe@619:           "event" != 'member_name_updated' OR (
jbe@619:             "posting_id"      ISNULL  AND
jbe@619:             "member_id"       NOTNULL AND
jbe@619:             "other_member_id" ISNULL  AND
jbe@619:             "scope"           ISNULL  AND
jbe@619:             "unit_id"         ISNULL  AND
jbe@619:             "area_id"         ISNULL  AND
jbe@619:             "policy_id"       ISNULL  AND
jbe@619:             "issue_id"        ISNULL  AND
jbe@619:             "state"           ISNULL  AND
jbe@619:             "initiative_id"   ISNULL  AND
jbe@619:             "draft_id"        ISNULL  AND
jbe@619:             "suggestion_id"   ISNULL  AND
jbe@619:             "boolean_value"   ISNULL  AND
jbe@619:             "numeric_value"   ISNULL  AND
jbe@619:             "text_value"      NOTNULL AND
jbe@619:             "old_text_value"  NOTNULL ));
jbe@619: ALTER TABLE "event" ADD
jbe@619:         CONSTRAINT "constr_for_interest" CHECK (
jbe@619:           "event" != 'interest' OR (
jbe@619:             "posting_id"      ISNULL  AND
jbe@619:             "member_id"       NOTNULL AND
jbe@619:             "other_member_id" ISNULL  AND
jbe@619:             "scope"           ISNULL  AND
jbe@619:             "unit_id"         NOTNULL AND
jbe@619:             "area_id"         NOTNULL AND
jbe@619:             "policy_id"       NOTNULL AND
jbe@619:             "issue_id"        NOTNULL AND
jbe@619:             "state"           NOTNULL AND
jbe@619:             "initiative_id"   ISNULL  AND
jbe@619:             "draft_id"        ISNULL  AND
jbe@619:             "suggestion_id"   ISNULL  AND
jbe@619:             "boolean_value"   NOTNULL AND
jbe@619:             "numeric_value"   ISNULL  AND
jbe@619:             "text_value"      ISNULL  AND
jbe@619:             "old_text_value"  ISNULL ));
jbe@619: ALTER TABLE "event" ADD
jbe@619:         CONSTRAINT "constr_for_initiator" CHECK (
jbe@619:           "event" != 'initiator' OR (
jbe@619:             "posting_id"      ISNULL  AND
jbe@619:             "member_id"       NOTNULL AND
jbe@619:             "other_member_id" ISNULL  AND
jbe@619:             "scope"           ISNULL  AND
jbe@619:             "unit_id"         NOTNULL AND
jbe@619:             "area_id"         NOTNULL AND
jbe@619:             "policy_id"       NOTNULL AND
jbe@619:             "issue_id"        NOTNULL AND
jbe@619:             "state"           NOTNULL AND
jbe@619:             "initiative_id"   NOTNULL AND
jbe@619:             "draft_id"        ISNULL  AND
jbe@619:             "suggestion_id"   ISNULL  AND
jbe@619:             "boolean_value"   NOTNULL AND
jbe@619:             "numeric_value"   ISNULL  AND
jbe@619:             "text_value"      ISNULL  AND
jbe@619:             "old_text_value"  ISNULL ));
jbe@619: ALTER TABLE "event" ADD
jbe@619:         CONSTRAINT "constr_for_support" CHECK (
jbe@619:           "event" != 'support' OR (
jbe@619:             "posting_id"      ISNULL  AND
jbe@619:             "member_id"       NOTNULL AND
jbe@619:             "other_member_id" ISNULL  AND
jbe@619:             "scope"           ISNULL  AND
jbe@619:             "unit_id"         NOTNULL AND
jbe@619:             "area_id"         NOTNULL AND
jbe@619:             "policy_id"       NOTNULL AND
jbe@619:             "issue_id"        NOTNULL AND
jbe@619:             "state"           NOTNULL AND
jbe@619:             "initiative_id"   NOTNULL AND
jbe@619:             ("draft_id" NOTNULL) = ("boolean_value" = TRUE) AND
jbe@619:             "suggestion_id"   ISNULL  AND
jbe@619:             "boolean_value"   NOTNULL AND
jbe@619:             "numeric_value"   ISNULL  AND
jbe@619:             "text_value"      ISNULL  AND
jbe@619:             "old_text_value"  ISNULL ));
jbe@619: ALTER TABLE "event" ADD
jbe@619:         CONSTRAINT "constr_for_support_updated" CHECK (
jbe@619:           "event" != 'support_updated' OR (
jbe@619:             "posting_id"      ISNULL  AND
jbe@619:             "member_id"       NOTNULL AND
jbe@619:             "other_member_id" ISNULL  AND
jbe@619:             "scope"           ISNULL  AND
jbe@619:             "unit_id"         NOTNULL AND
jbe@619:             "area_id"         NOTNULL AND
jbe@619:             "policy_id"       NOTNULL AND
jbe@619:             "issue_id"        NOTNULL AND
jbe@619:             "state"           NOTNULL AND
jbe@619:             "initiative_id"   NOTNULL AND
jbe@619:             "draft_id"        NOTNULL AND
jbe@619:             "suggestion_id"   ISNULL  AND
jbe@619:             "boolean_value"   ISNULL  AND
jbe@619:             "numeric_value"   ISNULL  AND
jbe@619:             "text_value"      ISNULL  AND
jbe@619:             "old_text_value"  ISNULL ));
jbe@619: ALTER TABLE "event" ADD
jbe@619:         CONSTRAINT "constr_for_suggestion_rated" CHECK (
jbe@619:           "event" != 'suggestion_rated' OR (
jbe@619:             "posting_id"      ISNULL  AND
jbe@619:             "member_id"       NOTNULL AND
jbe@619:             "other_member_id" ISNULL  AND
jbe@619:             "scope"           ISNULL  AND
jbe@619:             "unit_id"         NOTNULL AND
jbe@619:             "area_id"         NOTNULL AND
jbe@619:             "policy_id"       NOTNULL AND
jbe@619:             "issue_id"        NOTNULL AND
jbe@619:             "state"           NOTNULL AND
jbe@619:             "initiative_id"   NOTNULL AND
jbe@619:             "draft_id"        ISNULL  AND
jbe@619:             "suggestion_id"   NOTNULL AND
jbe@619:             ("boolean_value" NOTNULL) = ("numeric_value" != 0) AND
jbe@619:             "numeric_value"   NOTNULL AND
jbe@619:             "numeric_value" IN (-2, -1, 0, 1, 2) AND
jbe@619:             "text_value"      ISNULL  AND
jbe@619:             "old_text_value"  ISNULL ));
jbe@619: ALTER TABLE "event" ADD
jbe@619:         CONSTRAINT "constr_for_delegation" CHECK (
jbe@619:           "event" != 'delegation' OR (
jbe@619:             "posting_id"      ISNULL  AND
jbe@619:             "member_id"       NOTNULL AND
jbe@619:             (("other_member_id" ISNULL) OR ("boolean_value" = TRUE)) AND
jbe@619:             "scope"           NOTNULL AND
jbe@619:             "unit_id"         NOTNULL AND
jbe@619:             ("area_id"  NOTNULL) = ("scope" != 'unit'::"delegation_scope") AND
jbe@619:             "policy_id"       ISNULL  AND
jbe@619:             ("issue_id" NOTNULL) = ("scope" = 'issue'::"delegation_scope") AND
jbe@619:             ("state"    NOTNULL) = ("scope" = 'issue'::"delegation_scope") AND
jbe@619:             "initiative_id"   ISNULL  AND
jbe@619:             "draft_id"        ISNULL  AND
jbe@619:             "suggestion_id"   ISNULL  AND
jbe@619:             "boolean_value"   NOTNULL AND
jbe@619:             "numeric_value"   ISNULL  AND
jbe@619:             "text_value"      ISNULL  AND
jbe@619:             "old_text_value"  ISNULL ));
jbe@619: ALTER TABLE "event" ADD
jbe@619:         CONSTRAINT "constr_for_contact" CHECK (
jbe@619:           "event" != 'contact' OR (
jbe@619:             "posting_id"      ISNULL  AND
jbe@619:             "member_id"       NOTNULL AND
jbe@619:             "other_member_id" NOTNULL AND
jbe@619:             "scope"           ISNULL  AND
jbe@619:             "unit_id"         ISNULL  AND
jbe@619:             "area_id"         ISNULL  AND
jbe@619:             "policy_id"       ISNULL  AND
jbe@619:             "issue_id"        ISNULL  AND
jbe@619:             "state"           ISNULL  AND
jbe@619:             "initiative_id"   ISNULL  AND
jbe@619:             "draft_id"        ISNULL  AND
jbe@619:             "suggestion_id"   ISNULL  AND
jbe@619:             "boolean_value"   NOTNULL AND
jbe@619:             "numeric_value"   ISNULL  AND
jbe@619:             "text_value"      ISNULL  AND
jbe@619:             "old_text_value"  ISNULL ));
jbe@619: ALTER TABLE "event" ADD
jbe@619:         CONSTRAINT "constr_for_posting_created" CHECK (
jbe@619:           "event" != 'posting_created' OR (
jbe@619:             "posting_id"      NOTNULL AND
jbe@619:             "member_id"       NOTNULL AND
jbe@619:             "other_member_id" ISNULL  AND
jbe@619:             "scope"           ISNULL  AND
jbe@619:             "state"           ISNULL  AND
jbe@619:             ("area_id" ISNULL OR "unit_id" NOTNULL) AND
jbe@619:             ("policy_id" NOTNULL) = ("issue_id" NOTNULL) AND
jbe@619:             ("issue_id" ISNULL OR "area_id" NOTNULL) AND
jbe@619:             ("state" NOTNULL) = ("issue_id" NOTNULL) AND
jbe@619:             ("initiative_id" ISNULL OR "issue_id" NOTNULL) AND
jbe@619:             "draft_id"        ISNULL  AND
jbe@619:             ("suggestion_id" ISNULL OR "initiative_id" NOTNULL) AND
jbe@619:             "boolean_value"   ISNULL  AND
jbe@619:             "numeric_value"   ISNULL  AND
jbe@619:             "text_value"      ISNULL  AND
jbe@619:             "old_text_value"  ISNULL ));
jbe@619: 
jbe@619: CREATE OR REPLACE FUNCTION "highlight"
jbe@619:   ( "body_p"       TEXT,
jbe@619:     "query_text_p" TEXT )
jbe@619:   RETURNS TEXT
jbe@619:   LANGUAGE 'plpgsql' IMMUTABLE AS $$
jbe@619:     BEGIN
jbe@619:       RETURN ts_headline(
jbe@619:         replace(replace("body_p", e'\\', e'\\\\'), '*', e'\\*'),
jbe@619:         "plainto_tsquery"("query_text_p"),
jbe@619:         'StartSel=* StopSel=* HighlightAll=TRUE' );
jbe@619:     END;
jbe@619:   $$;
jbe@619: 
jbe@619: CREATE FUNCTION "to_tsvector"("member") RETURNS TSVECTOR
jbe@619:   LANGUAGE SQL IMMUTABLE AS $$ SELECT to_tsvector(concat_ws(' ',
jbe@619:     $1."name",
jbe@619:     $1."identification"
jbe@619:   )) $$;
jbe@619: CREATE INDEX "member_to_tsvector_idx" ON "member" USING gin
jbe@619:   (("to_tsvector"("member".*)));
jbe@619: 
jbe@619: CREATE FUNCTION "to_tsvector"("member_profile") RETURNS TSVECTOR
jbe@619:   LANGUAGE SQL IMMUTABLE AS $$ SELECT to_tsvector(concat_ws(' ',
jbe@619:     $1."statement",
jbe@619:     $1."profile_text_data"
jbe@619:   )) $$;
jbe@619: CREATE INDEX "member_profile_to_tsvector_idx" ON "member_profile" USING gin
jbe@619:   (("to_tsvector"("member_profile".*)));
jbe@619: 
jbe@619: CREATE FUNCTION "to_tsvector"("unit") RETURNS TSVECTOR
jbe@619:   LANGUAGE SQL IMMUTABLE AS $$ SELECT to_tsvector(concat_ws(' ',
jbe@619:     $1."name",
jbe@619:     $1."description"
jbe@619:   )) $$;
jbe@619: CREATE INDEX "unit_to_tsvector_idx" ON "unit" USING gin
jbe@619:   (("to_tsvector"("unit".*)));
jbe@619: 
jbe@619: CREATE FUNCTION "to_tsvector"("area") RETURNS TSVECTOR
jbe@619:   LANGUAGE SQL IMMUTABLE AS $$ SELECT to_tsvector(concat_ws(' ',
jbe@619:     $1."name",
jbe@619:     $1."description"
jbe@619:   )) $$;
jbe@619: CREATE INDEX "area_to_tsvector_idx" ON "area" USING gin
jbe@619:   (("to_tsvector"("area".*)));
jbe@619: 
jbe@619: CREATE FUNCTION "to_tsvector"("initiative") RETURNS TSVECTOR
jbe@619:   LANGUAGE SQL IMMUTABLE AS $$ SELECT to_tsvector(concat_ws(' ',
jbe@619:     $1."name",
jbe@619:     $1."content"
jbe@619:   )) $$;
jbe@619: CREATE INDEX "initiative_to_tsvector_idx" ON "initiative" USING gin
jbe@619:   (("to_tsvector"("initiative".*)));
jbe@619: 
jbe@619: CREATE FUNCTION "to_tsvector"("draft") RETURNS TSVECTOR
jbe@619:   LANGUAGE SQL IMMUTABLE AS $$ SELECT to_tsvector(concat_ws(' ',
jbe@619:     $1."content"
jbe@619:   )) $$;
jbe@619: CREATE INDEX "draft_to_tsvector_idx" ON "draft" USING gin
jbe@619:   (("to_tsvector"("draft".*)));
jbe@619: 
jbe@619: CREATE FUNCTION "to_tsvector"("suggestion") RETURNS TSVECTOR
jbe@619:   LANGUAGE SQL IMMUTABLE AS $$ SELECT to_tsvector(concat_ws(' ',
jbe@619:     $1."name",
jbe@619:     $1."content"
jbe@619:   )) $$;
jbe@619: CREATE INDEX "suggestion_to_tsvector_idx" ON "suggestion" USING gin
jbe@619:   (("to_tsvector"("suggestion".*)));
jbe@619: 
jbe@619: CREATE FUNCTION "to_tsvector"("direct_voter") RETURNS TSVECTOR
jbe@619:   LANGUAGE SQL IMMUTABLE AS $$ SELECT to_tsvector(concat_ws(' ',
jbe@619:     $1."comment"
jbe@619:   )) $$;
jbe@619: CREATE INDEX "direct_voter_to_tsvector_idx" ON "direct_voter" USING gin
jbe@619:   (("to_tsvector"("direct_voter".*)));
jbe@619: 
jbe@619: CREATE FUNCTION "update_posting_lexeme_trigger"()
jbe@619:   RETURNS TRIGGER
jbe@619:   LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@619:     DECLARE
jbe@619:       "lexeme_v" TEXT;
jbe@619:     BEGIN
jbe@619:       IF TG_OP = 'DELETE' OR TG_OP = 'UPDATE' THEN
jbe@619:         DELETE FROM "posting_lexeme" WHERE "posting_id" = OLD."id";
jbe@619:       END IF;
jbe@619:       IF TG_OP = 'INSERT' OR TG_OP = 'UPDATE' THEN
jbe@619:         FOR "lexeme_v" IN
jbe@619:           SELECT regexp_matches[1]
jbe@619:           FROM regexp_matches(NEW."message", '#[^\s.,;:]+')
jbe@619:         LOOP
jbe@619:           INSERT INTO "posting_lexeme" ("posting_id", "author_id", "lexeme")
jbe@619:             VALUES (
jbe@619:               NEW."id",
jbe@619:               NEW."author_id",
jbe@619:               "lexeme_v" )
jbe@619:             ON CONFLICT ("posting_id", "lexeme") DO NOTHING;
jbe@619:         END LOOP;
jbe@619:       END IF;
jbe@619:       RETURN NULL;
jbe@619:     END;
jbe@619:   $$;
jbe@619: 
jbe@619: CREATE TRIGGER "update_posting_lexeme"
jbe@619:   AFTER INSERT OR UPDATE OR DELETE ON "posting"
jbe@619:   FOR EACH ROW EXECUTE PROCEDURE "update_posting_lexeme_trigger"();
jbe@619: 
jbe@619: COMMENT ON FUNCTION "update_posting_lexeme_trigger"()  IS 'Implementation of trigger "update_posting_lexeme" on table "posting"';
jbe@619: COMMENT ON TRIGGER "update_posting_lexeme" ON "posting" IS 'Keeps table "posting_lexeme" up to date';
jbe@619: 
jbe@619: CREATE FUNCTION "write_event_posting_trigger"()
jbe@619:   RETURNS TRIGGER
jbe@619:   LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@619:     BEGIN
jbe@619:       INSERT INTO "event" (
jbe@619:           "event", "posting_id", "member_id",
jbe@619:           "unit_id", "area_id", "policy_id",
jbe@619:           "issue_id", "initiative_id", "suggestion_id"
jbe@619:         ) VALUES (
jbe@619:           'posting_created', NEW."id", NEW."author_id",
jbe@619:           NEW."unit_id", NEW."area_id", NEW."policy_id",
jbe@619:           NEW."issue_id", NEW."initiative_id", NEW."suggestion_id"
jbe@619:         );
jbe@619:       RETURN NULL;
jbe@619:     END;
jbe@619:   $$;
jbe@619: 
jbe@619: CREATE TRIGGER "write_event_posting"
jbe@619:   AFTER INSERT ON "posting" FOR EACH ROW EXECUTE PROCEDURE
jbe@619:   "write_event_posting_trigger"();
jbe@619: 
jbe@619: COMMENT ON FUNCTION "write_event_posting_trigger"()   IS 'Implementation of trigger "write_event_posting" on table "posting"';
jbe@619: COMMENT ON TRIGGER "write_event_posting" ON "posting" IS 'Create entry in "event" table when creating a new posting';
jbe@619: 
jbe@619: CREATE FUNCTION "file_requires_reference_trigger"()
jbe@619:   RETURNS TRIGGER
jbe@619:   LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@619:     BEGIN
jbe@619:       IF NOT EXISTS (
jbe@619:         SELECT NULL FROM "draft_attachment" WHERE "file_id" = NEW."id"
jbe@619:       ) THEN
jbe@619:         RAISE EXCEPTION 'Cannot create an unreferenced file.' USING
jbe@619:           ERRCODE = 'integrity_constraint_violation',
jbe@619:           HINT    = 'Create file and its reference in another table within the same transaction.';
jbe@619:       END IF;
jbe@619:       RETURN NULL;
jbe@619:     END;
jbe@619:   $$;
jbe@619: 
jbe@619: CREATE CONSTRAINT TRIGGER "file_requires_reference"
jbe@619:   AFTER INSERT OR UPDATE ON "file" DEFERRABLE INITIALLY DEFERRED
jbe@619:   FOR EACH ROW EXECUTE PROCEDURE
jbe@619:   "file_requires_reference_trigger"();
jbe@619: 
jbe@619: COMMENT ON FUNCTION "file_requires_reference_trigger"() IS 'Implementation of trigger "file_requires_reference" on table "file"';
jbe@619: COMMENT ON TRIGGER "file_requires_reference" ON "file"  IS 'Ensure that files are always referenced';
jbe@619: 
jbe@619: CREATE FUNCTION "last_reference_deletes_file_trigger"()
jbe@619:   RETURNS TRIGGER
jbe@619:   LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@619:     DECLARE
jbe@619:       "reference_lost" BOOLEAN;
jbe@619:     BEGIN
jbe@619:       IF TG_OP = 'DELETE' THEN
jbe@619:         "reference_lost" := TRUE;
jbe@619:       ELSE
jbe@619:         "reference_lost" := NEW."file_id" != OLD."file_id";
jbe@619:       END IF;
jbe@619:       IF
jbe@619:         "reference_lost" AND NOT EXISTS (
jbe@619:           SELECT NULL FROM "draft_attachment" WHERE "file_id" = OLD."file_id"
jbe@619:         )
jbe@619:       THEN
jbe@619:         DELETE FROM "file" WHERE "id" = OLD."file_id";
jbe@619:       END IF;
jbe@619:       RETURN NULL;
jbe@619:     END;
jbe@619:   $$;
jbe@619: 
jbe@619: CREATE CONSTRAINT TRIGGER "last_reference_deletes_file"
jbe@619:   AFTER UPDATE OR DELETE ON "draft_attachment" DEFERRABLE INITIALLY DEFERRED
jbe@619:   FOR EACH ROW EXECUTE PROCEDURE
jbe@619:   "last_reference_deletes_file_trigger"();
jbe@619: 
jbe@619: COMMENT ON FUNCTION "last_reference_deletes_file_trigger"()            IS 'Implementation of trigger "last_reference_deletes_file" on table "draft_attachment"';
jbe@619: COMMENT ON TRIGGER "last_reference_deletes_file" ON "draft_attachment" IS 'Removing the last reference to a file deletes the file';
jbe@619: 
jbe@619: CREATE OR REPLACE FUNCTION "copy_current_draft_data"
jbe@619:   ("initiative_id_p" "initiative"."id"%TYPE )
jbe@619:   RETURNS VOID
jbe@619:   LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@619:     BEGIN
jbe@619:       PERFORM NULL FROM "initiative" WHERE "id" = "initiative_id_p"
jbe@619:         FOR UPDATE;
jbe@619:       UPDATE "initiative" SET
jbe@619:         "location" = "draft"."location",
jbe@619:         "content"  = "draft"."content"
jbe@619:         FROM "current_draft" AS "draft"
jbe@619:         WHERE "initiative"."id" = "initiative_id_p"
jbe@619:         AND "draft"."initiative_id" = "initiative_id_p";
jbe@619:     END;
jbe@619:   $$;
jbe@619: 
jbe@619: CREATE VIEW "follower" AS
jbe@619:   SELECT
jbe@619:     "id" AS "follower_id",
jbe@619:     ( SELECT ARRAY["member"."id"] || array_agg("contact"."other_member_id")
jbe@619:       FROM "contact"
jbe@619:       WHERE "contact"."member_id" = "member"."id" AND "contact"."following" )
jbe@619:       AS "following_ids"
jbe@619:   FROM "member";
jbe@619: 
jbe@619: COMMENT ON VIEW "follower" IS 'Provides the contacts of each member that are being followed (including the member itself) as an array of IDs';
jbe@619: 
jbe@619: CREATE OR REPLACE FUNCTION "check_issue"
jbe@619:   ( "issue_id_p" "issue"."id"%TYPE,
jbe@619:     "persist"    "check_issue_persistence" )
jbe@619:   RETURNS "check_issue_persistence"
jbe@619:   LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@619:     DECLARE
jbe@619:       "issue_row"         "issue"%ROWTYPE;
jbe@619:       "last_calculated_v" "snapshot"."calculated"%TYPE;
jbe@619:       "policy_row"        "policy"%ROWTYPE;
jbe@619:       "initiative_row"    "initiative"%ROWTYPE;
jbe@619:       "state_v"           "issue_state";
jbe@619:     BEGIN
jbe@619:       PERFORM "require_transaction_isolation"();
jbe@619:       IF "persist" ISNULL THEN
jbe@619:         SELECT * INTO "issue_row" FROM "issue" WHERE "id" = "issue_id_p"
jbe@619:           FOR UPDATE;
jbe@619:         SELECT "calculated" INTO "last_calculated_v"
jbe@619:           FROM "snapshot" JOIN "snapshot_issue"
jbe@619:           ON "snapshot"."id" = "snapshot_issue"."snapshot_id"
jbe@619:           WHERE "snapshot_issue"."issue_id" = "issue_id_p"
jbe@619:           ORDER BY "snapshot"."id" DESC;
jbe@619:         IF "issue_row"."closed" NOTNULL THEN
jbe@619:           RETURN NULL;
jbe@619:         END IF;
jbe@619:         "persist"."state" := "issue_row"."state";
jbe@619:         IF
jbe@619:           ( "issue_row"."state" = 'admission' AND "last_calculated_v" >=
jbe@619:             "issue_row"."created" + "issue_row"."max_admission_time" ) OR
jbe@619:           ( "issue_row"."state" = 'discussion' AND now() >=
jbe@619:             "issue_row"."accepted" + "issue_row"."discussion_time" ) OR
jbe@619:           ( "issue_row"."state" = 'verification' AND now() >=
jbe@619:             "issue_row"."half_frozen" + "issue_row"."verification_time" ) OR
jbe@619:           ( "issue_row"."state" = 'voting' AND now() >=
jbe@619:             "issue_row"."fully_frozen" + "issue_row"."voting_time" )
jbe@619:         THEN
jbe@619:           "persist"."phase_finished" := TRUE;
jbe@619:         ELSE
jbe@619:           "persist"."phase_finished" := FALSE;
jbe@619:         END IF;
jbe@619:         IF
jbe@619:           NOT EXISTS (
jbe@619:             -- all initiatives are revoked
jbe@619:             SELECT NULL FROM "initiative"
jbe@619:             WHERE "issue_id" = "issue_id_p" AND "revoked" ISNULL
jbe@619:           ) AND (
jbe@619:             -- and issue has not been accepted yet
jbe@619:             "persist"."state" = 'admission' OR
jbe@619:             -- or verification time has elapsed
jbe@619:             ( "persist"."state" = 'verification' AND
jbe@619:               "persist"."phase_finished" ) OR
jbe@619:             -- or no initiatives have been revoked lately
jbe@619:             NOT EXISTS (
jbe@619:               SELECT NULL FROM "initiative"
jbe@619:               WHERE "issue_id" = "issue_id_p"
jbe@619:               AND now() < "revoked" + "issue_row"."verification_time"
jbe@619:             )
jbe@619:           )
jbe@619:         THEN
jbe@619:           "persist"."issue_revoked" := TRUE;
jbe@619:         ELSE
jbe@619:           "persist"."issue_revoked" := FALSE;
jbe@619:         END IF;
jbe@619:         IF "persist"."phase_finished" OR "persist"."issue_revoked" THEN
jbe@619:           UPDATE "issue" SET "phase_finished" = now()
jbe@619:             WHERE "id" = "issue_row"."id";
jbe@619:           RETURN "persist";
jbe@619:         ELSIF
jbe@619:           "persist"."state" IN ('admission', 'discussion', 'verification')
jbe@619:         THEN
jbe@619:           RETURN "persist";
jbe@619:         ELSE
jbe@619:           RETURN NULL;
jbe@619:         END IF;
jbe@619:       END IF;
jbe@619:       IF
jbe@619:         "persist"."state" IN ('admission', 'discussion', 'verification') AND
jbe@619:         coalesce("persist"."snapshot_created", FALSE) = FALSE
jbe@619:       THEN
jbe@619:         IF "persist"."state" != 'admission' THEN
jbe@619:           PERFORM "take_snapshot"("issue_id_p");
jbe@619:           PERFORM "finish_snapshot"("issue_id_p");
jbe@619:         ELSE
jbe@619:           UPDATE "issue" SET "issue_quorum" = "issue_quorum"."issue_quorum"
jbe@619:             FROM "issue_quorum"
jbe@619:             WHERE "id" = "issue_id_p"
jbe@619:             AND "issue_quorum"."issue_id" = "issue_id_p";
jbe@619:         END IF;
jbe@619:         "persist"."snapshot_created" = TRUE;
jbe@619:         IF "persist"."phase_finished" THEN
jbe@619:           IF "persist"."state" = 'admission' THEN
jbe@619:             UPDATE "issue" SET "admission_snapshot_id" = "latest_snapshot_id"
jbe@619:               WHERE "id" = "issue_id_p";
jbe@619:           ELSIF "persist"."state" = 'discussion' THEN
jbe@619:             UPDATE "issue" SET "half_freeze_snapshot_id" = "latest_snapshot_id"
jbe@619:               WHERE "id" = "issue_id_p";
jbe@619:           ELSIF "persist"."state" = 'verification' THEN
jbe@619:             UPDATE "issue" SET "full_freeze_snapshot_id" = "latest_snapshot_id"
jbe@619:               WHERE "id" = "issue_id_p";
jbe@619:             SELECT * INTO "issue_row" FROM "issue" WHERE "id" = "issue_id_p";
jbe@619:             FOR "initiative_row" IN
jbe@619:               SELECT * FROM "initiative"
jbe@619:               WHERE "issue_id" = "issue_id_p" AND "revoked" ISNULL
jbe@619:               FOR UPDATE
jbe@619:             LOOP
jbe@619:               IF
jbe@619:                 "initiative_row"."polling" OR
jbe@619:                 "initiative_row"."satisfied_supporter_count" >=
jbe@619:                 "issue_row"."initiative_quorum"
jbe@619:               THEN
jbe@619:                 UPDATE "initiative" SET "admitted" = TRUE
jbe@619:                   WHERE "id" = "initiative_row"."id";
jbe@619:               ELSE
jbe@619:                 UPDATE "initiative" SET "admitted" = FALSE
jbe@619:                   WHERE "id" = "initiative_row"."id";
jbe@619:               END IF;
jbe@619:             END LOOP;
jbe@619:           END IF;
jbe@619:         END IF;
jbe@619:         RETURN "persist";
jbe@619:       END IF;
jbe@619:       IF
jbe@619:         "persist"."state" IN ('admission', 'discussion', 'verification') AND
jbe@619:         coalesce("persist"."harmonic_weights_set", FALSE) = FALSE
jbe@619:       THEN
jbe@619:         PERFORM "set_harmonic_initiative_weights"("issue_id_p");
jbe@619:         "persist"."harmonic_weights_set" = TRUE;
jbe@619:         IF
jbe@619:           "persist"."phase_finished" OR
jbe@619:           "persist"."issue_revoked" OR
jbe@619:           "persist"."state" = 'admission'
jbe@619:         THEN
jbe@619:           RETURN "persist";
jbe@619:         ELSE
jbe@619:           RETURN NULL;
jbe@619:         END IF;
jbe@619:       END IF;
jbe@619:       IF "persist"."issue_revoked" THEN
jbe@619:         IF "persist"."state" = 'admission' THEN
jbe@619:           "state_v" := 'canceled_revoked_before_accepted';
jbe@619:         ELSIF "persist"."state" = 'discussion' THEN
jbe@619:           "state_v" := 'canceled_after_revocation_during_discussion';
jbe@619:         ELSIF "persist"."state" = 'verification' THEN
jbe@619:           "state_v" := 'canceled_after_revocation_during_verification';
jbe@619:         END IF;
jbe@619:         UPDATE "issue" SET
jbe@619:           "state"          = "state_v",
jbe@619:           "closed"         = "phase_finished",
jbe@619:           "phase_finished" = NULL
jbe@619:           WHERE "id" = "issue_id_p";
jbe@619:         RETURN NULL;
jbe@619:       END IF;
jbe@619:       IF "persist"."state" = 'admission' THEN
jbe@619:         SELECT * INTO "issue_row" FROM "issue" WHERE "id" = "issue_id_p"
jbe@619:           FOR UPDATE;
jbe@619:         IF "issue_row"."phase_finished" NOTNULL THEN
jbe@619:           UPDATE "issue" SET
jbe@619:             "state"          = 'canceled_issue_not_accepted',
jbe@619:             "closed"         = "phase_finished",
jbe@619:             "phase_finished" = NULL
jbe@619:             WHERE "id" = "issue_id_p";
jbe@619:         END IF;
jbe@619:         RETURN NULL;
jbe@619:       END IF;
jbe@619:       IF "persist"."phase_finished" THEN
jbe@619:         IF "persist"."state" = 'discussion' THEN
jbe@619:           UPDATE "issue" SET
jbe@619:             "state"          = 'verification',
jbe@619:             "half_frozen"    = "phase_finished",
jbe@619:             "phase_finished" = NULL
jbe@619:             WHERE "id" = "issue_id_p";
jbe@619:           RETURN NULL;
jbe@619:         END IF;
jbe@619:         IF "persist"."state" = 'verification' THEN
jbe@619:           SELECT * INTO "issue_row" FROM "issue" WHERE "id" = "issue_id_p"
jbe@619:             FOR UPDATE;
jbe@619:           SELECT * INTO "policy_row" FROM "policy"
jbe@619:             WHERE "id" = "issue_row"."policy_id";
jbe@619:           IF EXISTS (
jbe@619:             SELECT NULL FROM "initiative"
jbe@619:             WHERE "issue_id" = "issue_id_p" AND "admitted" = TRUE
jbe@619:           ) THEN
jbe@619:             UPDATE "issue" SET
jbe@619:               "state"          = 'voting',
jbe@619:               "fully_frozen"   = "phase_finished",
jbe@619:               "phase_finished" = NULL
jbe@619:               WHERE "id" = "issue_id_p";
jbe@619:           ELSE
jbe@619:             UPDATE "issue" SET
jbe@619:               "state"          = 'canceled_no_initiative_admitted',
jbe@619:               "fully_frozen"   = "phase_finished",
jbe@619:               "closed"         = "phase_finished",
jbe@619:               "phase_finished" = NULL
jbe@619:               WHERE "id" = "issue_id_p";
jbe@619:             -- NOTE: The following DELETE statements have effect only when
jbe@619:             --       issue state has been manipulated
jbe@619:             DELETE FROM "direct_voter"     WHERE "issue_id" = "issue_id_p";
jbe@619:             DELETE FROM "delegating_voter" WHERE "issue_id" = "issue_id_p";
jbe@619:             DELETE FROM "battle"           WHERE "issue_id" = "issue_id_p";
jbe@619:           END IF;
jbe@619:           RETURN NULL;
jbe@619:         END IF;
jbe@619:         IF "persist"."state" = 'voting' THEN
jbe@619:           IF coalesce("persist"."closed_voting", FALSE) = FALSE THEN
jbe@619:             PERFORM "close_voting"("issue_id_p");
jbe@619:             "persist"."closed_voting" = TRUE;
jbe@619:             RETURN "persist";
jbe@619:           END IF;
jbe@619:           PERFORM "calculate_ranks"("issue_id_p");
jbe@619:           RETURN NULL;
jbe@619:         END IF;
jbe@619:       END IF;
jbe@619:       RAISE WARNING 'should not happen';
jbe@619:       RETURN NULL;
jbe@619:     END;
jbe@619:   $$;
jbe@619: 
jbe@619: CREATE OR REPLACE FUNCTION "check_everything"()
jbe@619:   RETURNS VOID
jbe@619:   LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@619:     DECLARE
jbe@619:       "area_id_v"     "area"."id"%TYPE;
jbe@619:       "snapshot_id_v" "snapshot"."id"%TYPE;
jbe@619:       "issue_id_v"    "issue"."id"%TYPE;
jbe@619:       "persist_v"     "check_issue_persistence";
jbe@619:     BEGIN
jbe@619:       RAISE WARNING 'Function "check_everything" should only be used for development and debugging purposes';
jbe@619:       DELETE FROM "expired_session";
jbe@619:       DELETE FROM "expired_token";
jbe@619:       DELETE FROM "unused_snapshot";
jbe@619:       PERFORM "check_activity"();
jbe@619:       PERFORM "calculate_member_counts"();
jbe@619:       FOR "area_id_v" IN SELECT "id" FROM "area_with_unaccepted_issues" LOOP
jbe@619:         SELECT "take_snapshot"(NULL, "area_id_v") INTO "snapshot_id_v";
jbe@619:         PERFORM "finish_snapshot"("issue_id") FROM "snapshot_issue"
jbe@619:           WHERE "snapshot_id" = "snapshot_id_v";
jbe@619:         LOOP
jbe@619:           EXIT WHEN "issue_admission"("area_id_v") = FALSE;
jbe@619:         END LOOP;
jbe@619:       END LOOP;
jbe@619:       FOR "issue_id_v" IN SELECT "id" FROM "open_issue" LOOP
jbe@619:         "persist_v" := NULL;
jbe@619:         LOOP
jbe@619:           "persist_v" := "check_issue"("issue_id_v", "persist_v");
jbe@619:           EXIT WHEN "persist_v" ISNULL;
jbe@619:         END LOOP;
jbe@619:       END LOOP;
jbe@619:       DELETE FROM "unused_snapshot";
jbe@619:       RETURN;
jbe@619:     END;
jbe@619:   $$;
jbe@619: 
jbe@619: CREATE OR REPLACE FUNCTION "delete_member"("member_id_p" "member"."id"%TYPE)
jbe@619:   RETURNS VOID
jbe@619:   LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@619:     BEGIN
jbe@619:       UPDATE "member" SET
jbe@619:         "last_login"                   = NULL,
jbe@619:         "last_delegation_check"        = NULL,
jbe@619:         "login"                        = NULL,
jbe@619:         "password"                     = NULL,
jbe@619:         "authority"                    = NULL,
jbe@619:         "authority_uid"                = NULL,
jbe@619:         "authority_login"              = NULL,
jbe@619:         "deleted"                      = coalesce("deleted", now()),
jbe@619:         "locked"                       = TRUE,
jbe@619:         "active"                       = FALSE,
jbe@619:         "notify_email"                 = NULL,
jbe@619:         "notify_email_unconfirmed"     = NULL,
jbe@619:         "notify_email_secret"          = NULL,
jbe@619:         "notify_email_secret_expiry"   = NULL,
jbe@619:         "notify_email_lock_expiry"     = NULL,
jbe@619:         "disable_notifications"        = TRUE,
jbe@619:         "notification_counter"         = DEFAULT,
jbe@619:         "notification_sample_size"     = 0,
jbe@619:         "notification_dow"             = NULL,
jbe@619:         "notification_hour"            = NULL,
jbe@619:         "notification_sent"            = NULL,
jbe@619:         "login_recovery_expiry"        = NULL,
jbe@619:         "password_reset_secret"        = NULL,
jbe@619:         "password_reset_secret_expiry" = NULL,
jbe@619:         "location"                     = NULL
jbe@619:         WHERE "id" = "member_id_p";
jbe@619:       DELETE FROM "member_settings"    WHERE "member_id" = "member_id_p";
jbe@619:       DELETE FROM "member_profile"     WHERE "member_id" = "member_id_p";
jbe@619:       DELETE FROM "rendered_member_statement" WHERE "member_id" = "member_id_p";
jbe@619:       DELETE FROM "member_image"       WHERE "member_id" = "member_id_p";
jbe@619:       DELETE FROM "contact"            WHERE "member_id" = "member_id_p";
jbe@619:       DELETE FROM "ignored_member"     WHERE "member_id" = "member_id_p";
jbe@619:       DELETE FROM "session"            WHERE "member_id" = "member_id_p";
jbe@619:       DELETE FROM "member_application" WHERE "member_id" = "member_id_p";
jbe@619:       DELETE FROM "token"              WHERE "member_id" = "member_id_p";
jbe@619:       DELETE FROM "subscription"       WHERE "member_id" = "member_id_p";
jbe@619:       DELETE FROM "ignored_area"       WHERE "member_id" = "member_id_p";
jbe@619:       DELETE FROM "ignored_initiative" WHERE "member_id" = "member_id_p";
jbe@619:       DELETE FROM "delegation"         WHERE "truster_id" = "member_id_p";
jbe@619:       DELETE FROM "non_voter"          WHERE "member_id" = "member_id_p";
jbe@619:       DELETE FROM "direct_voter" USING "issue"
jbe@619:         WHERE "direct_voter"."issue_id" = "issue"."id"
jbe@619:         AND "issue"."closed" ISNULL
jbe@619:         AND "member_id" = "member_id_p";
jbe@619:       DELETE FROM "notification_initiative_sent" WHERE "member_id" = "member_id_p";
jbe@619:       RETURN;
jbe@619:     END;
jbe@619:   $$;
jbe@619: 
jbe@619: CREATE OR REPLACE FUNCTION "delete_private_data"()
jbe@619:   RETURNS VOID
jbe@619:   LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@619:     BEGIN
jbe@619:       DELETE FROM "temporary_transaction_data";
jbe@619:       DELETE FROM "temporary_suggestion_counts";
jbe@619:       DELETE FROM "member" WHERE "activated" ISNULL;
jbe@619:       UPDATE "member" SET
jbe@619:         "invite_code"                  = NULL,
jbe@619:         "invite_code_expiry"           = NULL,
jbe@619:         "admin_comment"                = NULL,
jbe@619:         "last_login"                   = NULL,
jbe@619:         "last_delegation_check"        = NULL,
jbe@619:         "login"                        = NULL,
jbe@619:         "password"                     = NULL,
jbe@619:         "authority"                    = NULL,
jbe@619:         "authority_uid"                = NULL,
jbe@619:         "authority_login"              = NULL,
jbe@619:         "lang"                         = NULL,
jbe@619:         "notify_email"                 = NULL,
jbe@619:         "notify_email_unconfirmed"     = NULL,
jbe@619:         "notify_email_secret"          = NULL,
jbe@619:         "notify_email_secret_expiry"   = NULL,
jbe@619:         "notify_email_lock_expiry"     = NULL,
jbe@619:         "disable_notifications"        = TRUE,
jbe@619:         "notification_counter"         = DEFAULT,
jbe@619:         "notification_sample_size"     = 0,
jbe@619:         "notification_dow"             = NULL,
jbe@619:         "notification_hour"            = NULL,
jbe@619:         "notification_sent"            = NULL,
jbe@619:         "login_recovery_expiry"        = NULL,
jbe@619:         "password_reset_secret"        = NULL,
jbe@619:         "password_reset_secret_expiry" = NULL,
jbe@619:         "location"                     = NULL;
jbe@619:       DELETE FROM "verification";
jbe@619:       DELETE FROM "member_settings";
jbe@619:       DELETE FROM "member_useterms";
jbe@619:       DELETE FROM "member_profile";
jbe@619:       DELETE FROM "rendered_member_statement";
jbe@619:       DELETE FROM "member_image";
jbe@619:       DELETE FROM "contact";
jbe@619:       DELETE FROM "ignored_member";
jbe@619:       DELETE FROM "session";
jbe@619:       DELETE FROM "system_application";
jbe@619:       DELETE FROM "system_application_redirect_uri";
jbe@619:       DELETE FROM "dynamic_application_scope";
jbe@619:       DELETE FROM "member_application";
jbe@619:       DELETE FROM "token";
jbe@619:       DELETE FROM "subscription";
jbe@619:       DELETE FROM "ignored_area";
jbe@619:       DELETE FROM "ignored_initiative";
jbe@619:       DELETE FROM "non_voter";
jbe@619:       DELETE FROM "direct_voter" USING "issue"
jbe@619:         WHERE "direct_voter"."issue_id" = "issue"."id"
jbe@619:         AND "issue"."closed" ISNULL;
jbe@619:       DELETE FROM "event_processed";
jbe@619:       DELETE FROM "notification_initiative_sent";
jbe@619:       DELETE FROM "newsletter";
jbe@619:       RETURN;
jbe@619:     END;
jbe@619:   $$;
jbe@619: 
jbe@619: CREATE VIEW "member_eligible_to_be_notified" AS
jbe@619:   SELECT * FROM "member"
jbe@619:   WHERE "activated" NOTNULL AND "locked" = FALSE;
jbe@619: 
jbe@619: 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")';
jbe@619: 
jbe@619: CREATE VIEW "member_to_notify" AS
jbe@619:   SELECT * FROM "member_eligible_to_be_notified"
jbe@619:   WHERE "disable_notifications" = FALSE;
jbe@619: 
jbe@619: 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)';
jbe@619: 
jbe@619: CREATE VIEW "area_with_unaccepted_issues" AS
jbe@619:   SELECT DISTINCT ON ("area"."id") "area".*
jbe@619:   FROM "area" JOIN "issue" ON "area"."id" = "issue"."area_id"
jbe@619:   WHERE "issue"."state" = 'admission';
jbe@619: 
jbe@619: COMMENT ON VIEW "area_with_unaccepted_issues" IS 'All areas with unaccepted open issues (needed for issue admission system)';
jbe@619: 
jbe@619: CREATE VIEW "opening_draft" AS
jbe@619:   SELECT DISTINCT ON ("initiative_id") * FROM "draft"
jbe@619:   ORDER BY "initiative_id", "id";
jbe@619: 
jbe@619: COMMENT ON VIEW "opening_draft" IS 'First drafts of all initiatives';
jbe@619: 
jbe@619: CREATE VIEW "current_draft" AS
jbe@619:   SELECT DISTINCT ON ("initiative_id") * FROM "draft"
jbe@619:   ORDER BY "initiative_id", "id" DESC;
jbe@619: 
jbe@619: COMMENT ON VIEW "current_draft" IS 'All latest drafts for each initiative';
jbe@619: 
jbe@619: CREATE VIEW "member_contingent" AS
jbe@619:   SELECT
jbe@619:     "member"."id" AS "member_id",
jbe@619:     "contingent"."polling",
jbe@619:     "contingent"."time_frame",
jbe@619:     CASE WHEN "contingent"."text_entry_limit" NOTNULL THEN
jbe@619:       (
jbe@619:         SELECT count(1) FROM "draft"
jbe@619:         JOIN "initiative" ON "initiative"."id" = "draft"."initiative_id"
jbe@619:         WHERE "draft"."author_id" = "member"."id"
jbe@619:         AND "initiative"."polling" = "contingent"."polling"
jbe@619:         AND "draft"."created" > now() - "contingent"."time_frame"
jbe@619:       ) + (
jbe@619:         SELECT count(1) FROM "suggestion"
jbe@619:         JOIN "initiative" ON "initiative"."id" = "suggestion"."initiative_id"
jbe@619:         WHERE "suggestion"."author_id" = "member"."id"
jbe@619:         AND "contingent"."polling" = FALSE
jbe@619:         AND "suggestion"."created" > now() - "contingent"."time_frame"
jbe@619:       )
jbe@619:     ELSE NULL END AS "text_entry_count",
jbe@619:     "contingent"."text_entry_limit",
jbe@619:     CASE WHEN "contingent"."initiative_limit" NOTNULL THEN (
jbe@619:       SELECT count(1) FROM "opening_draft" AS "draft"
jbe@619:         JOIN "initiative" ON "initiative"."id" = "draft"."initiative_id"
jbe@619:       WHERE "draft"."author_id" = "member"."id"
jbe@619:       AND "initiative"."polling" = "contingent"."polling"
jbe@619:       AND "draft"."created" > now() - "contingent"."time_frame"
jbe@619:     ) ELSE NULL END AS "initiative_count",
jbe@619:     "contingent"."initiative_limit"
jbe@619:   FROM "member" CROSS JOIN "contingent";
jbe@619: 
jbe@619: COMMENT ON VIEW "member_contingent" IS 'Actual counts of text entries and initiatives are calculated per member for each limit in the "contingent" table.';
jbe@619: 
jbe@619: COMMENT ON COLUMN "member_contingent"."text_entry_count" IS 'Only calculated when "text_entry_limit" is not null in the same row';
jbe@619: COMMENT ON COLUMN "member_contingent"."initiative_count" IS 'Only calculated when "initiative_limit" is not null in the same row';
jbe@619: 
jbe@619: CREATE VIEW "member_contingent_left" AS
jbe@619:   SELECT
jbe@619:     "member_id",
jbe@619:     "polling",
jbe@619:     max("text_entry_limit" - "text_entry_count") AS "text_entries_left",
jbe@619:     max("initiative_limit" - "initiative_count") AS "initiatives_left"
jbe@619:   FROM "member_contingent" GROUP BY "member_id", "polling";
jbe@619: 
jbe@619: COMMENT ON VIEW "member_contingent_left" IS 'Amount of text entries or initiatives which can be posted now instantly by a member. This view should be used by a frontend to determine, if the contingent for posting is exhausted.';
jbe@619: 
jbe@619: CREATE VIEW "scheduled_notification_to_send" AS
jbe@619:   SELECT * FROM (
jbe@619:     SELECT
jbe@619:       "id" AS "recipient_id",
jbe@619:       now() - CASE WHEN "notification_dow" ISNULL THEN
jbe@619:         ( "notification_sent"::DATE + CASE
jbe@619:           WHEN EXTRACT(HOUR FROM "notification_sent") < "notification_hour"
jbe@619:           THEN 0 ELSE 1 END
jbe@619:         )::TIMESTAMP + '1 hour'::INTERVAL * "notification_hour"
jbe@619:       ELSE
jbe@619:         ( "notification_sent"::DATE +
jbe@619:           ( 7 + "notification_dow" -
jbe@619:             EXTRACT(DOW FROM
jbe@619:               ( "notification_sent"::DATE + CASE
jbe@619:                 WHEN EXTRACT(HOUR FROM "notification_sent") < "notification_hour"
jbe@619:                 THEN 0 ELSE 1 END
jbe@619:               )::TIMESTAMP + '1 hour'::INTERVAL * "notification_hour"
jbe@619:             )::INTEGER
jbe@619:           ) % 7 +
jbe@619:           CASE
jbe@619:             WHEN EXTRACT(HOUR FROM "notification_sent") < "notification_hour"
jbe@619:             THEN 0 ELSE 1
jbe@619:           END
jbe@619:         )::TIMESTAMP + '1 hour'::INTERVAL * "notification_hour"
jbe@619:       END AS "pending"
jbe@619:     FROM (
jbe@619:       SELECT
jbe@619:         "id",
jbe@619:         COALESCE("notification_sent", "activated") AS "notification_sent",
jbe@619:         "notification_dow",
jbe@619:         "notification_hour"
jbe@619:       FROM "member_to_notify"
jbe@619:       WHERE "notification_hour" NOTNULL
jbe@619:     ) AS "subquery1"
jbe@619:   ) AS "subquery2"
jbe@619:   WHERE "pending" > '0'::INTERVAL;
jbe@619: 
jbe@619: COMMENT ON VIEW "scheduled_notification_to_send" IS 'Set of members where a scheduled notification mail is pending';
jbe@619: 
jbe@619: COMMENT ON COLUMN "scheduled_notification_to_send"."recipient_id" IS '"id" of the member who needs to receive a notification mail';
jbe@619: COMMENT ON COLUMN "scheduled_notification_to_send"."pending"      IS 'Duration for which the notification mail has already been pending';
jbe@619: 
jbe@619: CREATE VIEW "newsletter_to_send" AS
jbe@619:   SELECT
jbe@619:     "member"."id" AS "recipient_id",
jbe@619:     "newsletter"."id" AS "newsletter_id",
jbe@619:     "newsletter"."published"
jbe@619:   FROM "newsletter" CROSS JOIN "member_eligible_to_be_notified" AS "member"
jbe@619:   LEFT JOIN "privilege" ON
jbe@619:     "privilege"."member_id" = "member"."id" AND
jbe@619:     "privilege"."unit_id" = "newsletter"."unit_id" AND
jbe@619:     "privilege"."voting_right" = TRUE
jbe@619:   LEFT JOIN "subscription" ON
jbe@619:     "subscription"."member_id" = "member"."id" AND
jbe@619:     "subscription"."unit_id" = "newsletter"."unit_id"
jbe@619:   WHERE "newsletter"."published" <= now()
jbe@619:   AND "newsletter"."sent" ISNULL
jbe@619:   AND (
jbe@619:     "member"."disable_notifications" = FALSE OR
jbe@619:     "newsletter"."include_all_members" = TRUE )
jbe@619:   AND (
jbe@619:     "newsletter"."unit_id" ISNULL OR
jbe@619:     "privilege"."member_id" NOTNULL OR
jbe@619:     "subscription"."member_id" NOTNULL );
jbe@619: 
jbe@619: COMMENT ON VIEW "newsletter_to_send" IS 'List of "newsletter_id"s for each member that are due to be sent out';
jbe@619: 
jbe@619: COMMENT ON COLUMN "newsletter"."published" IS 'Timestamp when the newsletter was supposed to be sent out (can be used for ordering)';
jbe@619: 
jbe@619: SELECT "copy_current_draft_data" ("id") FROM "initiative";
jbe@619: 
jbe@619: COMMIT;
jbe@619: BEGIN;
jbe@619: 
jbe@619: CREATE OR REPLACE VIEW "liquid_feedback_version" AS
jbe@619:   SELECT * FROM (VALUES ('4.2.1', 4, 2, 1))
jbe@619:   AS "subquery"("string", "major", "minor", "revision");
jbe@619: 
jbe@619: ALTER TABLE "unit" ADD COLUMN "attr" JSONB NOT NULL DEFAULT '{}' CHECK (jsonb_typeof("attr") = 'object');
jbe@619: COMMENT ON COLUMN "unit"."attr" IS 'Opaque data structure to store any extended attributes used by frontend or middleware';
jbe@619: 
jbe@619: ALTER TABLE "unit" ADD COLUMN "member_weight" INT4;
jbe@619: COMMENT ON COLUMN "unit"."member_weight" IS 'Sum of active members'' voting weight';
jbe@619: 
jbe@619: ALTER TABLE "snapshot_population" ADD COLUMN "weight" INT4 NOT NULL DEFAULT 1;
jbe@619: ALTER TABLE "snapshot_population" ALTER COLUMN "weight" DROP DEFAULT;
jbe@619:  
jbe@619: ALTER TABLE "privilege" ADD COLUMN "weight" INT4 NOT NULL DEFAULT 1 CHECK ("weight" >= 0);
jbe@619: COMMENT ON COLUMN "privilege"."weight"           IS 'Voting weight of member in unit';
jbe@619: 
jbe@619: CREATE TABLE "issue_privilege" (
jbe@619:         PRIMARY KEY ("issue_id", "member_id"),
jbe@619:         "issue_id"              INT4            REFERENCES "issue" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@619:         "member_id"             INT4            REFERENCES "member" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
jbe@619:         "initiative_right"      BOOLEAN,
jbe@619:         "voting_right"          BOOLEAN,
jbe@619:         "polling_right"         BOOLEAN,
jbe@619:         "weight"                INT4            CHECK ("weight" >= 0) );
jbe@619: CREATE INDEX "issue_privilege_idx" ON "issue_privilege" ("member_id");
jbe@619: COMMENT ON TABLE "issue_privilege" IS 'Override of "privilege" table for rights of members in certain issues';
jbe@619:  
jbe@619: ALTER TABLE "direct_interest_snapshot" ADD COLUMN "ownweight" INT4 NOT NULL DEFAULT 1;
jbe@619: ALTER TABLE "direct_interest_snapshot" ALTER COLUMN "ownweight" DROP DEFAULT;
jbe@619: COMMENT ON COLUMN "direct_interest_snapshot"."ownweight" IS 'Own voting weight of member, disregading delegations';
jbe@619: COMMENT ON COLUMN "direct_interest_snapshot"."weight"    IS 'Voting weight of member according to own weight and "delegating_interest_snapshot"';
jbe@619:  
jbe@619: ALTER TABLE "delegating_interest_snapshot" ADD COLUMN "ownweight" INT4 NOT NULL DEFAULT 1;
jbe@619: ALTER TABLE "delegating_interest_snapshot" ALTER COLUMN "ownweight" DROP DEFAULT;
jbe@619: COMMENT ON COLUMN "delegating_interest_snapshot"."ownweight" IS 'Own voting weight of member, disregading delegations';
jbe@619: COMMENT ON COLUMN "delegating_interest_snapshot"."weight"    IS 'Intermediate voting weight considering incoming delegations';
jbe@619: 
jbe@619: ALTER TABLE "direct_voter" ADD COLUMN "ownweight" INT4 DEFAULT 1;
jbe@619: ALTER TABLE "direct_voter" ALTER COLUMN "ownweight" DROP DEFAULT;
jbe@619: COMMENT ON COLUMN "direct_voter"."ownweight" IS 'Own voting weight of member, disregarding delegations';
jbe@619: COMMENT ON COLUMN "direct_voter"."weight"    IS 'Voting weight of member according to own weight and "delegating_interest_snapshot"';
jbe@619: 
jbe@619: ALTER TABLE "delegating_voter" ADD COLUMN "ownweight" INT4 NOT NULL DEFAULT 1;
jbe@619: ALTER TABLE "delegating_voter" ALTER COLUMN "ownweight" DROP DEFAULT;
jbe@619: COMMENT ON COLUMN "delegating_voter"."ownweight" IS 'Own voting weight of member, disregarding delegations';
jbe@619: COMMENT ON COLUMN "delegating_voter"."weight"    IS 'Intermediate voting weight considering incoming delegations';
jbe@619: 
jbe@619: ALTER TABLE "posting" ADD FOREIGN KEY ("issue_id", "initiative_id") REFERENCES "initiative" ("issue_id", "id");
jbe@619: 
jbe@619: DROP VIEW "issue_delegation";
jbe@619: CREATE VIEW "issue_delegation" AS
jbe@619:   SELECT DISTINCT ON ("issue"."id", "delegation"."truster_id")
jbe@619:     "issue"."id" AS "issue_id",
jbe@619:     "delegation"."id",
jbe@619:     "delegation"."truster_id",
jbe@619:     "delegation"."trustee_id",
jbe@619:     COALESCE("issue_privilege"."weight", "privilege"."weight") AS "weight",
jbe@619:     "delegation"."scope"
jbe@619:   FROM "issue"
jbe@619:   JOIN "area"
jbe@619:     ON "area"."id" = "issue"."area_id"
jbe@619:   JOIN "delegation"
jbe@619:     ON "delegation"."unit_id" = "area"."unit_id"
jbe@619:     OR "delegation"."area_id" = "area"."id"
jbe@619:     OR "delegation"."issue_id" = "issue"."id"
jbe@619:   JOIN "member"
jbe@619:     ON "delegation"."truster_id" = "member"."id"
jbe@619:   LEFT JOIN "privilege"
jbe@619:     ON "area"."unit_id" = "privilege"."unit_id"
jbe@619:     AND "delegation"."truster_id" = "privilege"."member_id"
jbe@619:   LEFT JOIN "issue_privilege"
jbe@619:     ON "issue"."id" = "issue_privilege"."issue_id"
jbe@619:     AND "delegation"."truster_id" = "issue_privilege"."member_id"
jbe@619:   WHERE "member"."active"
jbe@619:   AND COALESCE("issue_privilege"."voting_right", "privilege"."voting_right")
jbe@619:   ORDER BY
jbe@619:     "issue"."id",
jbe@619:     "delegation"."truster_id",
jbe@619:     "delegation"."scope" DESC;
jbe@619: COMMENT ON VIEW "issue_delegation" IS 'Issue delegations where trusters are active and have voting right';
jbe@619: 
jbe@619: CREATE OR REPLACE VIEW "unit_member" AS
jbe@619:   SELECT
jbe@619:     "privilege"."unit_id" AS "unit_id",
jbe@619:     "member"."id"         AS "member_id",
jbe@619:     "privilege"."weight"
jbe@619:   FROM "privilege" JOIN "member" ON "member"."id" = "privilege"."member_id"
jbe@619:   WHERE "privilege"."voting_right" AND "member"."active";
jbe@619: 
jbe@619: CREATE OR REPLACE VIEW "unit_member_count" AS
jbe@619:   SELECT
jbe@619:     "unit"."id" AS "unit_id",
jbe@619:     count("unit_member"."member_id") AS "member_count",
jbe@619:     sum("unit_member"."weight") AS "member_weight"
jbe@619:   FROM "unit" LEFT JOIN "unit_member"
jbe@619:   ON "unit"."id" = "unit_member"."unit_id"
jbe@619:   GROUP BY "unit"."id";
jbe@619: 
jbe@619: CREATE OR REPLACE VIEW "event_for_notification" AS
jbe@619:   SELECT
jbe@619:     "member"."id" AS "recipient_id",
jbe@619:     "event".*
jbe@619:   FROM "member" CROSS JOIN "event"
jbe@619:   JOIN "issue" ON "issue"."id" = "event"."issue_id"
jbe@619:   JOIN "area" ON "area"."id" = "issue"."area_id"
jbe@619:   LEFT JOIN "privilege" ON
jbe@619:     "privilege"."member_id" = "member"."id" AND
jbe@619:     "privilege"."unit_id" = "area"."unit_id"
jbe@619:   LEFT JOIN "issue_privilege" ON
jbe@619:     "issue_privilege"."member_id" = "member"."id" AND
jbe@619:     "issue_privilege"."issue_id" = "event"."issue_id"
jbe@619:   LEFT JOIN "subscription" ON
jbe@619:     "subscription"."member_id" = "member"."id" AND
jbe@619:     "subscription"."unit_id" = "area"."unit_id"
jbe@619:   LEFT JOIN "ignored_area" ON
jbe@619:     "ignored_area"."member_id" = "member"."id" AND
jbe@619:     "ignored_area"."area_id" = "issue"."area_id"
jbe@619:   LEFT JOIN "interest" ON
jbe@619:     "interest"."member_id" = "member"."id" AND
jbe@619:     "interest"."issue_id" = "event"."issue_id"
jbe@619:   LEFT JOIN "supporter" ON
jbe@619:     "supporter"."member_id" = "member"."id" AND
jbe@619:     "supporter"."initiative_id" = "event"."initiative_id"
jbe@619:   WHERE (
jbe@619:     COALESCE("issue_privilege"."voting_right", "privilege"."voting_right") OR
jbe@619:     "subscription"."member_id" NOTNULL
jbe@619:   ) AND ("ignored_area"."member_id" ISNULL OR "interest"."member_id" NOTNULL)
jbe@619:   AND (
jbe@619:     "event"."event" = 'issue_state_changed'::"event_type" OR
jbe@619:     ( "event"."event" = 'initiative_revoked'::"event_type" AND
jbe@619:       "supporter"."member_id" NOTNULL ) );
jbe@619: 
jbe@619: CREATE OR REPLACE FUNCTION "featured_initiative"
jbe@619:   ( "recipient_id_p" "member"."id"%TYPE,
jbe@619:     "area_id_p"      "area"."id"%TYPE )
jbe@619:   RETURNS SETOF "initiative"."id"%TYPE
jbe@619:   LANGUAGE 'plpgsql' STABLE AS $$
jbe@619:     DECLARE
jbe@619:       "counter_v"         "member"."notification_counter"%TYPE;
jbe@619:       "sample_size_v"     "member"."notification_sample_size"%TYPE;
jbe@619:       "initiative_id_ary" INT4[];  --"initiative"."id"%TYPE[]
jbe@619:       "match_v"           BOOLEAN;
jbe@619:       "member_id_v"       "member"."id"%TYPE;
jbe@619:       "seed_v"            TEXT;
jbe@619:       "initiative_id_v"   "initiative"."id"%TYPE;
jbe@619:     BEGIN
jbe@619:       SELECT "notification_counter", "notification_sample_size"
jbe@619:         INTO "counter_v", "sample_size_v"
jbe@619:         FROM "member" WHERE "id" = "recipient_id_p";
jbe@619:       IF COALESCE("sample_size_v" <= 0, TRUE) THEN
jbe@619:         RETURN;
jbe@619:       END IF;
jbe@619:       "initiative_id_ary" := '{}';
jbe@619:       LOOP
jbe@619:         "match_v" := FALSE;
jbe@619:         FOR "member_id_v", "seed_v" IN
jbe@619:           SELECT * FROM (
jbe@619:             SELECT DISTINCT
jbe@619:               "supporter"."member_id",
jbe@619:               md5(
jbe@619:                 "recipient_id_p" || '-' ||
jbe@619:                 "counter_v"      || '-' ||
jbe@619:                 "area_id_p"      || '-' ||
jbe@619:                 "supporter"."member_id"
jbe@619:               ) AS "seed"
jbe@619:             FROM "supporter"
jbe@619:             JOIN "initiative" ON "initiative"."id" = "supporter"."initiative_id"
jbe@619:             JOIN "issue" ON "issue"."id" = "initiative"."issue_id"
jbe@619:             WHERE "supporter"."member_id" != "recipient_id_p"
jbe@619:             AND "issue"."area_id" = "area_id_p"
jbe@619:             AND "issue"."state" IN ('admission', 'discussion', 'verification')
jbe@619:           ) AS "subquery"
jbe@619:           ORDER BY "seed"
jbe@619:         LOOP
jbe@619:           SELECT "initiative"."id" INTO "initiative_id_v"
jbe@619:             FROM "initiative"
jbe@619:             JOIN "issue" ON "issue"."id" = "initiative"."issue_id"
jbe@619:             JOIN "area" ON "area"."id" = "issue"."area_id"
jbe@619:             JOIN "supporter" ON "supporter"."initiative_id" = "initiative"."id"
jbe@619:             LEFT JOIN "supporter" AS "self_support" ON
jbe@619:               "self_support"."initiative_id" = "initiative"."id" AND
jbe@619:               "self_support"."member_id" = "recipient_id_p"
jbe@619:             LEFT JOIN "privilege" ON
jbe@619:               "privilege"."member_id" = "recipient_id_p" AND
jbe@619:               "privilege"."unit_id" = "area"."unit_id"
jbe@619:             LEFT JOIN "issue_privilege" ON
jbe@619:               "issue_privilege"."member_id" = "recipient_id_p" AND
jbe@619:               "issue_privilege"."issue_id" = "initiative"."issue_id"
jbe@619:             LEFT JOIN "subscription" ON
jbe@619:               "subscription"."member_id" = "recipient_id_p" AND
jbe@619:               "subscription"."unit_id" = "area"."unit_id"
jbe@619:             LEFT JOIN "ignored_initiative" ON
jbe@619:               "ignored_initiative"."member_id" = "recipient_id_p" AND
jbe@619:               "ignored_initiative"."initiative_id" = "initiative"."id"
jbe@619:             WHERE "supporter"."member_id" = "member_id_v"
jbe@619:             AND "issue"."area_id" = "area_id_p"
jbe@619:             AND "issue"."state" IN ('admission', 'discussion', 'verification')
jbe@619:             AND "initiative"."revoked" ISNULL
jbe@619:             AND "self_support"."member_id" ISNULL
jbe@619:             AND NOT "initiative_id_ary" @> ARRAY["initiative"."id"]
jbe@619:             AND (
jbe@619:               COALESCE(
jbe@619:                 "issue_privilege"."voting_right", "privilege"."voting_right"
jbe@619:               ) OR "subscription"."member_id" NOTNULL )
jbe@619:             AND "ignored_initiative"."member_id" ISNULL
jbe@619:             AND NOT EXISTS (
jbe@619:               SELECT NULL FROM "draft"
jbe@619:               JOIN "ignored_member" ON
jbe@619:                 "ignored_member"."member_id" = "recipient_id_p" AND
jbe@619:                 "ignored_member"."other_member_id" = "draft"."author_id"
jbe@619:               WHERE "draft"."initiative_id" = "initiative"."id"
jbe@619:             )
jbe@619:             ORDER BY md5("seed_v" || '-' || "initiative"."id")
jbe@619:             LIMIT 1;
jbe@619:           IF FOUND THEN
jbe@619:             "match_v" := TRUE;
jbe@619:             RETURN NEXT "initiative_id_v";
jbe@619:             IF array_length("initiative_id_ary", 1) + 1 >= "sample_size_v" THEN
jbe@619:               RETURN;
jbe@619:             END IF;
jbe@619:             "initiative_id_ary" := "initiative_id_ary" || "initiative_id_v";
jbe@619:           END IF;
jbe@619:         END LOOP;
jbe@619:         EXIT WHEN NOT "match_v";
jbe@619:       END LOOP;
jbe@619:       RETURN;
jbe@619:     END;
jbe@619:   $$;
jbe@619: 
jbe@619: CREATE OR REPLACE FUNCTION "delegation_chain"
jbe@619:   ( "member_id_p"           "member"."id"%TYPE,
jbe@619:     "unit_id_p"             "unit"."id"%TYPE,
jbe@619:     "area_id_p"             "area"."id"%TYPE,
jbe@619:     "issue_id_p"            "issue"."id"%TYPE,
jbe@619:     "simulate_trustee_id_p" "member"."id"%TYPE DEFAULT NULL,
jbe@619:     "simulate_default_p"    BOOLEAN            DEFAULT FALSE )
jbe@619:   RETURNS SETOF "delegation_chain_row"
jbe@619:   LANGUAGE 'plpgsql' STABLE AS $$
jbe@619:     DECLARE
jbe@619:       "scope_v"            "delegation_scope";
jbe@619:       "unit_id_v"          "unit"."id"%TYPE;
jbe@619:       "area_id_v"          "area"."id"%TYPE;
jbe@619:       "issue_row"          "issue"%ROWTYPE;
jbe@619:       "visited_member_ids" INT4[];  -- "member"."id"%TYPE[]
jbe@619:       "loop_member_id_v"   "member"."id"%TYPE;
jbe@619:       "output_row"         "delegation_chain_row";
jbe@619:       "output_rows"        "delegation_chain_row"[];
jbe@619:       "simulate_v"         BOOLEAN;
jbe@619:       "simulate_here_v"    BOOLEAN;
jbe@619:       "delegation_row"     "delegation"%ROWTYPE;
jbe@619:       "row_count"          INT4;
jbe@619:       "i"                  INT4;
jbe@619:       "loop_v"             BOOLEAN;
jbe@619:     BEGIN
jbe@619:       IF "simulate_trustee_id_p" NOTNULL AND "simulate_default_p" THEN
jbe@619:         RAISE EXCEPTION 'Both "simulate_trustee_id_p" is set, and "simulate_default_p" is true';
jbe@619:       END IF;
jbe@619:       IF "simulate_trustee_id_p" NOTNULL OR "simulate_default_p" THEN
jbe@619:         "simulate_v" := TRUE;
jbe@619:       ELSE
jbe@619:         "simulate_v" := FALSE;
jbe@619:       END IF;
jbe@619:       IF
jbe@619:         "unit_id_p" NOTNULL AND
jbe@619:         "area_id_p" ISNULL AND
jbe@619:         "issue_id_p" ISNULL
jbe@619:       THEN
jbe@619:         "scope_v" := 'unit';
jbe@619:         "unit_id_v" := "unit_id_p";
jbe@619:       ELSIF
jbe@619:         "unit_id_p" ISNULL AND
jbe@619:         "area_id_p" NOTNULL AND
jbe@619:         "issue_id_p" ISNULL
jbe@619:       THEN
jbe@619:         "scope_v" := 'area';
jbe@619:         "area_id_v" := "area_id_p";
jbe@619:         SELECT "unit_id" INTO "unit_id_v"
jbe@619:           FROM "area" WHERE "id" = "area_id_v";
jbe@619:       ELSIF
jbe@619:         "unit_id_p" ISNULL AND
jbe@619:         "area_id_p" ISNULL AND
jbe@619:         "issue_id_p" NOTNULL
jbe@619:       THEN
jbe@619:         SELECT INTO "issue_row" * FROM "issue" WHERE "id" = "issue_id_p";
jbe@619:         IF "issue_row"."id" ISNULL THEN
jbe@619:           RETURN;
jbe@619:         END IF;
jbe@619:         IF "issue_row"."closed" NOTNULL THEN
jbe@619:           IF "simulate_v" THEN
jbe@619:             RAISE EXCEPTION 'Tried to simulate delegation chain for closed issue.';
jbe@619:           END IF;
jbe@619:           FOR "output_row" IN
jbe@619:             SELECT * FROM
jbe@619:             "delegation_chain_for_closed_issue"("member_id_p", "issue_id_p")
jbe@619:           LOOP
jbe@619:             RETURN NEXT "output_row";
jbe@619:           END LOOP;
jbe@619:           RETURN;
jbe@619:         END IF;
jbe@619:         "scope_v" := 'issue';
jbe@619:         SELECT "area_id" INTO "area_id_v"
jbe@619:           FROM "issue" WHERE "id" = "issue_id_p";
jbe@619:         SELECT "unit_id" INTO "unit_id_v"
jbe@619:           FROM "area"  WHERE "id" = "area_id_v";
jbe@619:       ELSE
jbe@619:         RAISE EXCEPTION 'Exactly one of unit_id_p, area_id_p, or issue_id_p must be NOTNULL.';
jbe@619:       END IF;
jbe@619:       "visited_member_ids" := '{}';
jbe@619:       "loop_member_id_v"   := NULL;
jbe@619:       "output_rows"        := '{}';
jbe@619:       "output_row"."index"         := 0;
jbe@619:       "output_row"."member_id"     := "member_id_p";
jbe@619:       "output_row"."member_valid"  := TRUE;
jbe@619:       "output_row"."participation" := FALSE;
jbe@619:       "output_row"."overridden"    := FALSE;
jbe@619:       "output_row"."disabled_out"  := FALSE;
jbe@619:       "output_row"."scope_out"     := NULL;
jbe@619:       LOOP
jbe@619:         IF "visited_member_ids" @> ARRAY["output_row"."member_id"] THEN
jbe@619:           "loop_member_id_v" := "output_row"."member_id";
jbe@619:         ELSE
jbe@619:           "visited_member_ids" :=
jbe@619:             "visited_member_ids" || "output_row"."member_id";
jbe@619:         END IF;
jbe@619:         IF "output_row"."participation" ISNULL THEN
jbe@619:           "output_row"."overridden" := NULL;
jbe@619:         ELSIF "output_row"."participation" THEN
jbe@619:           "output_row"."overridden" := TRUE;
jbe@619:         END IF;
jbe@619:         "output_row"."scope_in" := "output_row"."scope_out";
jbe@619:         "output_row"."member_valid" := EXISTS (
jbe@619:           SELECT NULL FROM "member"
jbe@619:           LEFT JOIN "privilege"
jbe@619:           ON "privilege"."member_id" = "member"."id"
jbe@619:           AND "privilege"."unit_id" = "unit_id_v"
jbe@619:           LEFT JOIN "issue_privilege"
jbe@619:           ON "issue_privilege"."member_id" = "member"."id"
jbe@619:           AND "issue_privilege"."issue_id" = "issue_id_p"
jbe@619:           WHERE "id" = "output_row"."member_id"
jbe@619:           AND "member"."active"
jbe@619:           AND COALESCE(
jbe@619:             "issue_privilege"."voting_right", "privilege"."voting_right")
jbe@619:         );
jbe@619:         "simulate_here_v" := (
jbe@619:           "simulate_v" AND
jbe@619:           "output_row"."member_id" = "member_id_p"
jbe@619:         );
jbe@619:         "delegation_row" := ROW(NULL);
jbe@619:         IF "output_row"."member_valid" OR "simulate_here_v" THEN
jbe@619:           IF "scope_v" = 'unit' THEN
jbe@619:             IF NOT "simulate_here_v" THEN
jbe@619:               SELECT * INTO "delegation_row" FROM "delegation"
jbe@619:                 WHERE "truster_id" = "output_row"."member_id"
jbe@619:                 AND "unit_id" = "unit_id_v";
jbe@619:             END IF;
jbe@619:           ELSIF "scope_v" = 'area' THEN
jbe@619:             IF "simulate_here_v" THEN
jbe@619:               IF "simulate_trustee_id_p" ISNULL THEN
jbe@619:                 SELECT * INTO "delegation_row" FROM "delegation"
jbe@619:                   WHERE "truster_id" = "output_row"."member_id"
jbe@619:                   AND "unit_id" = "unit_id_v";
jbe@619:               END IF;
jbe@619:             ELSE
jbe@619:               SELECT * INTO "delegation_row" FROM "delegation"
jbe@619:                 WHERE "truster_id" = "output_row"."member_id"
jbe@619:                 AND (
jbe@619:                   "unit_id" = "unit_id_v" OR
jbe@619:                   "area_id" = "area_id_v"
jbe@619:                 )
jbe@619:                 ORDER BY "scope" DESC;
jbe@619:             END IF;
jbe@619:           ELSIF "scope_v" = 'issue' THEN
jbe@619:             IF "issue_row"."fully_frozen" ISNULL THEN
jbe@619:               "output_row"."participation" := EXISTS (
jbe@619:                 SELECT NULL FROM "interest"
jbe@619:                 WHERE "issue_id" = "issue_id_p"
jbe@619:                 AND "member_id" = "output_row"."member_id"
jbe@619:               );
jbe@619:             ELSE
jbe@619:               IF "output_row"."member_id" = "member_id_p" THEN
jbe@619:                 "output_row"."participation" := EXISTS (
jbe@619:                   SELECT NULL FROM "direct_voter"
jbe@619:                   WHERE "issue_id" = "issue_id_p"
jbe@619:                   AND "member_id" = "output_row"."member_id"
jbe@619:                 );
jbe@619:               ELSE
jbe@619:                 "output_row"."participation" := NULL;
jbe@619:               END IF;
jbe@619:             END IF;
jbe@619:             IF "simulate_here_v" THEN
jbe@619:               IF "simulate_trustee_id_p" ISNULL THEN
jbe@619:                 SELECT * INTO "delegation_row" FROM "delegation"
jbe@619:                   WHERE "truster_id" = "output_row"."member_id"
jbe@619:                   AND (
jbe@619:                     "unit_id" = "unit_id_v" OR
jbe@619:                     "area_id" = "area_id_v"
jbe@619:                   )
jbe@619:                   ORDER BY "scope" DESC;
jbe@619:               END IF;
jbe@619:             ELSE
jbe@619:               SELECT * INTO "delegation_row" FROM "delegation"
jbe@619:                 WHERE "truster_id" = "output_row"."member_id"
jbe@619:                 AND (
jbe@619:                   "unit_id" = "unit_id_v" OR
jbe@619:                   "area_id" = "area_id_v" OR
jbe@619:                   "issue_id" = "issue_id_p"
jbe@619:                 )
jbe@619:                 ORDER BY "scope" DESC;
jbe@619:             END IF;
jbe@619:           END IF;
jbe@619:         ELSE
jbe@619:           "output_row"."participation" := FALSE;
jbe@619:         END IF;
jbe@619:         IF "simulate_here_v" AND "simulate_trustee_id_p" NOTNULL THEN
jbe@619:           "output_row"."scope_out" := "scope_v";
jbe@619:           "output_rows" := "output_rows" || "output_row";
jbe@619:           "output_row"."member_id" := "simulate_trustee_id_p";
jbe@619:         ELSIF "delegation_row"."trustee_id" NOTNULL THEN
jbe@619:           "output_row"."scope_out" := "delegation_row"."scope";
jbe@619:           "output_rows" := "output_rows" || "output_row";
jbe@619:           "output_row"."member_id" := "delegation_row"."trustee_id";
jbe@619:         ELSIF "delegation_row"."scope" NOTNULL THEN
jbe@619:           "output_row"."scope_out" := "delegation_row"."scope";
jbe@619:           "output_row"."disabled_out" := TRUE;
jbe@619:           "output_rows" := "output_rows" || "output_row";
jbe@619:           EXIT;
jbe@619:         ELSE
jbe@619:           "output_row"."scope_out" := NULL;
jbe@619:           "output_rows" := "output_rows" || "output_row";
jbe@619:           EXIT;
jbe@619:         END IF;
jbe@619:         EXIT WHEN "loop_member_id_v" NOTNULL;
jbe@619:         "output_row"."index" := "output_row"."index" + 1;
jbe@619:       END LOOP;
jbe@619:       "row_count" := array_upper("output_rows", 1);
jbe@619:       "i"      := 1;
jbe@619:       "loop_v" := FALSE;
jbe@619:       LOOP
jbe@619:         "output_row" := "output_rows"["i"];
jbe@619:         EXIT WHEN "output_row" ISNULL;  -- NOTE: ISNULL and NOT ... NOTNULL produce different results!
jbe@619:         IF "loop_v" THEN
jbe@619:           IF "i" + 1 = "row_count" THEN
jbe@619:             "output_row"."loop" := 'last';
jbe@619:           ELSIF "i" = "row_count" THEN
jbe@619:             "output_row"."loop" := 'repetition';
jbe@619:           ELSE
jbe@619:             "output_row"."loop" := 'intermediate';
jbe@619:           END IF;
jbe@619:         ELSIF "output_row"."member_id" = "loop_member_id_v" THEN
jbe@619:           "output_row"."loop" := 'first';
jbe@619:           "loop_v" := TRUE;
jbe@619:         END IF;
jbe@619:         IF "scope_v" = 'unit' THEN
jbe@619:           "output_row"."participation" := NULL;
jbe@619:         END IF;
jbe@619:         RETURN NEXT "output_row";
jbe@619:         "i" := "i" + 1;
jbe@619:       END LOOP;
jbe@619:       RETURN;
jbe@619:     END;
jbe@619:   $$;
jbe@619: 
jbe@619: CREATE OR REPLACE FUNCTION "calculate_member_counts"()
jbe@619:   RETURNS VOID
jbe@619:   LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@619:     BEGIN
jbe@619:       PERFORM "require_transaction_isolation"();
jbe@619:       DELETE FROM "member_count";
jbe@619:       INSERT INTO "member_count" ("total_count")
jbe@619:         SELECT "total_count" FROM "member_count_view";
jbe@619:       UPDATE "unit" SET
jbe@619:         "member_count" = "view"."member_count",
jbe@619:         "member_weight" = "view"."member_weight"
jbe@619:         FROM "unit_member_count" AS "view"
jbe@619:         WHERE "view"."unit_id" = "unit"."id";
jbe@619:       RETURN;
jbe@619:     END;
jbe@619:   $$;
jbe@619: COMMENT ON FUNCTION "calculate_member_counts"() IS 'Updates "member_count" table and "member_count" and "member_weight" columns of table "area" by materializing data from views "member_count_view" and "unit_member_count"';
jbe@619:  
jbe@619: CREATE OR REPLACE FUNCTION "weight_of_added_delegations_for_snapshot"
jbe@619:   ( "snapshot_id_p"         "snapshot"."id"%TYPE,
jbe@619:     "issue_id_p"            "issue"."id"%TYPE,
jbe@619:     "member_id_p"           "member"."id"%TYPE,
jbe@619:     "delegate_member_ids_p" "delegating_interest_snapshot"."delegate_member_ids"%TYPE )
jbe@619:   RETURNS "direct_interest_snapshot"."weight"%TYPE
jbe@619:   LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@619:     DECLARE
jbe@619:       "issue_delegation_row"  "issue_delegation"%ROWTYPE;
jbe@619:       "delegate_member_ids_v" "delegating_interest_snapshot"."delegate_member_ids"%TYPE;
jbe@619:       "weight_v"              INT4;
jbe@619:       "sub_weight_v"          INT4;
jbe@619:     BEGIN
jbe@619:       PERFORM "require_transaction_isolation"();
jbe@619:       "weight_v" := 0;
jbe@619:       FOR "issue_delegation_row" IN
jbe@619:         SELECT * FROM "issue_delegation"
jbe@619:         WHERE "trustee_id" = "member_id_p"
jbe@619:         AND "issue_id" = "issue_id_p"
jbe@619:       LOOP
jbe@619:         IF NOT EXISTS (
jbe@619:           SELECT NULL FROM "direct_interest_snapshot"
jbe@619:           WHERE "snapshot_id" = "snapshot_id_p"
jbe@619:           AND "issue_id" = "issue_id_p"
jbe@619:           AND "member_id" = "issue_delegation_row"."truster_id"
jbe@619:         ) AND NOT EXISTS (
jbe@619:           SELECT NULL FROM "delegating_interest_snapshot"
jbe@619:           WHERE "snapshot_id" = "snapshot_id_p"
jbe@619:           AND "issue_id" = "issue_id_p"
jbe@619:           AND "member_id" = "issue_delegation_row"."truster_id"
jbe@619:         ) THEN
jbe@619:           "delegate_member_ids_v" :=
jbe@619:             "member_id_p" || "delegate_member_ids_p";
jbe@619:           INSERT INTO "delegating_interest_snapshot" (
jbe@619:               "snapshot_id",
jbe@619:               "issue_id",
jbe@619:               "member_id",
jbe@619:               "ownweight",
jbe@619:               "scope",
jbe@619:               "delegate_member_ids"
jbe@619:             ) VALUES (
jbe@619:               "snapshot_id_p",
jbe@619:               "issue_id_p",
jbe@619:               "issue_delegation_row"."truster_id",
jbe@619:               "issue_delegation_row"."weight",
jbe@619:               "issue_delegation_row"."scope",
jbe@619:               "delegate_member_ids_v"
jbe@619:             );
jbe@619:           "sub_weight_v" := "issue_delegation_row"."weight" +
jbe@619:             "weight_of_added_delegations_for_snapshot"(
jbe@619:               "snapshot_id_p",
jbe@619:               "issue_id_p",
jbe@619:               "issue_delegation_row"."truster_id",
jbe@619:               "delegate_member_ids_v"
jbe@619:             );
jbe@619:           UPDATE "delegating_interest_snapshot"
jbe@619:             SET "weight" = "sub_weight_v"
jbe@619:             WHERE "snapshot_id" = "snapshot_id_p"
jbe@619:             AND "issue_id" = "issue_id_p"
jbe@619:             AND "member_id" = "issue_delegation_row"."truster_id";
jbe@619:           "weight_v" := "weight_v" + "sub_weight_v";
jbe@619:         END IF;
jbe@619:       END LOOP;
jbe@619:       RETURN "weight_v";
jbe@619:     END;
jbe@619:   $$;
jbe@619: 
jbe@619: CREATE OR REPLACE FUNCTION "take_snapshot"
jbe@619:   ( "issue_id_p" "issue"."id"%TYPE,
jbe@619:     "area_id_p"  "area"."id"%TYPE = NULL )
jbe@619:   RETURNS "snapshot"."id"%TYPE
jbe@619:   LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@619:     DECLARE
jbe@619:       "area_id_v"     "area"."id"%TYPE;
jbe@619:       "unit_id_v"     "unit"."id"%TYPE;
jbe@619:       "snapshot_id_v" "snapshot"."id"%TYPE;
jbe@619:       "issue_id_v"    "issue"."id"%TYPE;
jbe@619:       "member_id_v"   "member"."id"%TYPE;
jbe@619:     BEGIN
jbe@619:       IF "issue_id_p" NOTNULL AND "area_id_p" NOTNULL THEN
jbe@619:         RAISE EXCEPTION 'One of "issue_id_p" and "area_id_p" must be NULL';
jbe@619:       END IF;
jbe@619:       PERFORM "require_transaction_isolation"();
jbe@619:       IF "issue_id_p" ISNULL THEN
jbe@619:         "area_id_v" := "area_id_p";
jbe@619:       ELSE
jbe@619:         SELECT "area_id" INTO "area_id_v"
jbe@619:           FROM "issue" WHERE "id" = "issue_id_p";
jbe@619:       END IF;
jbe@619:       SELECT "unit_id" INTO "unit_id_v" FROM "area" WHERE "id" = "area_id_v";
jbe@619:       INSERT INTO "snapshot" ("area_id", "issue_id")
jbe@619:         VALUES ("area_id_v", "issue_id_p")
jbe@619:         RETURNING "id" INTO "snapshot_id_v";
jbe@619:       INSERT INTO "snapshot_population" ("snapshot_id", "member_id", "weight")
jbe@619:         SELECT
jbe@619:           "snapshot_id_v",
jbe@619:           "member"."id",
jbe@619:           COALESCE("issue_privilege"."weight", "privilege"."weight")
jbe@619:         FROM "member"
jbe@619:         LEFT JOIN "privilege"
jbe@619:         ON "privilege"."unit_id" = "unit_id_v"
jbe@619:         AND "privilege"."member_id" = "member"."id"
jbe@619:         LEFT JOIN "issue_privilege"
jbe@619:         ON "issue_privilege"."issue_id" = "issue_id_p"
jbe@619:         AND "issue_privilege"."member_id" = "member"."id"
jbe@619:         WHERE "member"."active" AND COALESCE(
jbe@619:           "issue_privilege"."voting_right", "privilege"."voting_right");
jbe@619:       UPDATE "snapshot" SET
jbe@619:         "population" = (
jbe@619:           SELECT sum("weight") FROM "snapshot_population"
jbe@619:           WHERE "snapshot_id" = "snapshot_id_v"
jbe@619:         ) WHERE "id" = "snapshot_id_v";
jbe@619:       FOR "issue_id_v" IN
jbe@619:         SELECT "id" FROM "issue"
jbe@619:         WHERE CASE WHEN "issue_id_p" ISNULL THEN
jbe@619:           "area_id" = "area_id_p" AND
jbe@619:           "state" = 'admission'
jbe@619:         ELSE
jbe@619:           "id" = "issue_id_p"
jbe@619:         END
jbe@619:       LOOP
jbe@619:         INSERT INTO "snapshot_issue" ("snapshot_id", "issue_id")
jbe@619:           VALUES ("snapshot_id_v", "issue_id_v");
jbe@619:         INSERT INTO "direct_interest_snapshot"
jbe@619:           ("snapshot_id", "issue_id", "member_id", "ownweight")
jbe@619:           SELECT
jbe@619:             "snapshot_id_v" AS "snapshot_id",
jbe@619:             "issue_id_v"    AS "issue_id",
jbe@619:             "member"."id"   AS "member_id",
jbe@619:             COALESCE(
jbe@619:               "issue_privilege"."weight", "privilege"."weight"
jbe@619:             ) AS "ownweight"
jbe@619:           FROM "issue"
jbe@619:           JOIN "area" ON "issue"."area_id" = "area"."id"
jbe@619:           JOIN "interest" ON "issue"."id" = "interest"."issue_id"
jbe@619:           JOIN "member" ON "interest"."member_id" = "member"."id"
jbe@619:           LEFT JOIN "privilege"
jbe@619:             ON "privilege"."unit_id" = "area"."unit_id"
jbe@619:             AND "privilege"."member_id" = "member"."id"
jbe@619:           LEFT JOIN "issue_privilege"
jbe@619:             ON "issue_privilege"."issue_id" = "issue_id_v"
jbe@619:             AND "issue_privilege"."member_id" = "member"."id"
jbe@619:           WHERE "issue"."id" = "issue_id_v"
jbe@619:           AND "member"."active" AND COALESCE(
jbe@619:             "issue_privilege"."voting_right", "privilege"."voting_right");
jbe@619:         FOR "member_id_v" IN
jbe@619:           SELECT "member_id" FROM "direct_interest_snapshot"
jbe@619:           WHERE "snapshot_id" = "snapshot_id_v"
jbe@619:           AND "issue_id" = "issue_id_v"
jbe@619:         LOOP
jbe@619:           UPDATE "direct_interest_snapshot" SET
jbe@619:             "weight" = "ownweight" +
jbe@619:               "weight_of_added_delegations_for_snapshot"(
jbe@619:                 "snapshot_id_v",
jbe@619:                 "issue_id_v",
jbe@619:                 "member_id_v",
jbe@619:                 '{}'
jbe@619:               )
jbe@619:             WHERE "snapshot_id" = "snapshot_id_v"
jbe@619:             AND "issue_id" = "issue_id_v"
jbe@619:             AND "member_id" = "member_id_v";
jbe@619:         END LOOP;
jbe@619:         INSERT INTO "direct_supporter_snapshot"
jbe@619:           ( "snapshot_id", "issue_id", "initiative_id", "member_id",
jbe@619:             "draft_id", "informed", "satisfied" )
jbe@619:           SELECT
jbe@619:             "snapshot_id_v"         AS "snapshot_id",
jbe@619:             "issue_id_v"            AS "issue_id",
jbe@619:             "initiative"."id"       AS "initiative_id",
jbe@619:             "supporter"."member_id" AS "member_id",
jbe@619:             "supporter"."draft_id"  AS "draft_id",
jbe@619:             "supporter"."draft_id" = "current_draft"."id" AS "informed",
jbe@619:             NOT EXISTS (
jbe@619:               SELECT NULL FROM "critical_opinion"
jbe@619:               WHERE "initiative_id" = "initiative"."id"
jbe@619:               AND "member_id" = "supporter"."member_id"
jbe@619:             ) AS "satisfied"
jbe@619:           FROM "initiative"
jbe@619:           JOIN "supporter"
jbe@619:           ON "supporter"."initiative_id" = "initiative"."id"
jbe@619:           JOIN "current_draft"
jbe@619:           ON "initiative"."id" = "current_draft"."initiative_id"
jbe@619:           JOIN "direct_interest_snapshot"
jbe@619:           ON "snapshot_id_v" = "direct_interest_snapshot"."snapshot_id"
jbe@619:           AND "supporter"."member_id" = "direct_interest_snapshot"."member_id"
jbe@619:           AND "initiative"."issue_id" = "direct_interest_snapshot"."issue_id"
jbe@619:           WHERE "initiative"."issue_id" = "issue_id_v";
jbe@619:         DELETE FROM "temporary_suggestion_counts";
jbe@619:         INSERT INTO "temporary_suggestion_counts"
jbe@619:           ( "id",
jbe@619:             "minus2_unfulfilled_count", "minus2_fulfilled_count",
jbe@619:             "minus1_unfulfilled_count", "minus1_fulfilled_count",
jbe@619:             "plus1_unfulfilled_count", "plus1_fulfilled_count",
jbe@619:             "plus2_unfulfilled_count", "plus2_fulfilled_count" )
jbe@619:           SELECT
jbe@619:             "suggestion"."id",
jbe@619:             ( SELECT coalesce(sum("di"."weight"), 0)
jbe@619:               FROM "opinion" JOIN "direct_interest_snapshot" AS "di"
jbe@619:               ON "di"."snapshot_id" = "snapshot_id_v"
jbe@619:               AND "di"."issue_id" = "issue_id_v"
jbe@619:               AND "di"."member_id" = "opinion"."member_id"
jbe@619:               WHERE "opinion"."suggestion_id" = "suggestion"."id"
jbe@619:               AND "opinion"."degree" = -2
jbe@619:               AND "opinion"."fulfilled" = FALSE
jbe@619:             ) AS "minus2_unfulfilled_count",
jbe@619:             ( SELECT coalesce(sum("di"."weight"), 0)
jbe@619:               FROM "opinion" JOIN "direct_interest_snapshot" AS "di"
jbe@619:               ON "di"."snapshot_id" = "snapshot_id_v"
jbe@619:               AND "di"."issue_id" = "issue_id_v"
jbe@619:               AND "di"."member_id" = "opinion"."member_id"
jbe@619:               WHERE "opinion"."suggestion_id" = "suggestion"."id"
jbe@619:               AND "opinion"."degree" = -2
jbe@619:               AND "opinion"."fulfilled" = TRUE
jbe@619:             ) AS "minus2_fulfilled_count",
jbe@619:             ( SELECT coalesce(sum("di"."weight"), 0)
jbe@619:               FROM "opinion" JOIN "direct_interest_snapshot" AS "di"
jbe@619:               ON "di"."snapshot_id" = "snapshot_id_v"
jbe@619:               AND "di"."issue_id" = "issue_id_v"
jbe@619:               AND "di"."member_id" = "opinion"."member_id"
jbe@619:               WHERE "opinion"."suggestion_id" = "suggestion"."id"
jbe@619:               AND "opinion"."degree" = -1
jbe@619:               AND "opinion"."fulfilled" = FALSE
jbe@619:             ) AS "minus1_unfulfilled_count",
jbe@619:             ( SELECT coalesce(sum("di"."weight"), 0)
jbe@619:               FROM "opinion" JOIN "direct_interest_snapshot" AS "di"
jbe@619:               ON "di"."snapshot_id" = "snapshot_id_v"
jbe@619:               AND "di"."issue_id" = "issue_id_v"
jbe@619:               AND "di"."member_id" = "opinion"."member_id"
jbe@619:               WHERE "opinion"."suggestion_id" = "suggestion"."id"
jbe@619:               AND "opinion"."degree" = -1
jbe@619:               AND "opinion"."fulfilled" = TRUE
jbe@619:             ) AS "minus1_fulfilled_count",
jbe@619:             ( SELECT coalesce(sum("di"."weight"), 0)
jbe@619:               FROM "opinion" JOIN "direct_interest_snapshot" AS "di"
jbe@619:               ON "di"."snapshot_id" = "snapshot_id_v"
jbe@619:               AND "di"."issue_id" = "issue_id_v"
jbe@619:               AND "di"."member_id" = "opinion"."member_id"
jbe@619:               WHERE "opinion"."suggestion_id" = "suggestion"."id"
jbe@619:               AND "opinion"."degree" = 1
jbe@619:               AND "opinion"."fulfilled" = FALSE
jbe@619:             ) AS "plus1_unfulfilled_count",
jbe@619:             ( SELECT coalesce(sum("di"."weight"), 0)
jbe@619:               FROM "opinion" JOIN "direct_interest_snapshot" AS "di"
jbe@619:               ON "di"."snapshot_id" = "snapshot_id_v"
jbe@619:               AND "di"."issue_id" = "issue_id_v"
jbe@619:               AND "di"."member_id" = "opinion"."member_id"
jbe@619:               WHERE "opinion"."suggestion_id" = "suggestion"."id"
jbe@619:               AND "opinion"."degree" = 1
jbe@619:               AND "opinion"."fulfilled" = TRUE
jbe@619:             ) AS "plus1_fulfilled_count",
jbe@619:             ( SELECT coalesce(sum("di"."weight"), 0)
jbe@619:               FROM "opinion" JOIN "direct_interest_snapshot" AS "di"
jbe@619:               ON "di"."snapshot_id" = "snapshot_id_v"
jbe@619:               AND "di"."issue_id" = "issue_id_v"
jbe@619:               AND "di"."member_id" = "opinion"."member_id"
jbe@619:               WHERE "opinion"."suggestion_id" = "suggestion"."id"
jbe@619:               AND "opinion"."degree" = 2
jbe@619:               AND "opinion"."fulfilled" = FALSE
jbe@619:             ) AS "plus2_unfulfilled_count",
jbe@619:             ( SELECT coalesce(sum("di"."weight"), 0)
jbe@619:               FROM "opinion" JOIN "direct_interest_snapshot" AS "di"
jbe@619:               ON "di"."snapshot_id" = "snapshot_id_v"
jbe@619:               AND "di"."issue_id" = "issue_id_v"
jbe@619:               AND "di"."member_id" = "opinion"."member_id"
jbe@619:               WHERE "opinion"."suggestion_id" = "suggestion"."id"
jbe@619:               AND "opinion"."degree" = 2
jbe@619:               AND "opinion"."fulfilled" = TRUE
jbe@619:             ) AS "plus2_fulfilled_count"
jbe@619:             FROM "suggestion" JOIN "initiative"
jbe@619:             ON "suggestion"."initiative_id" = "initiative"."id"
jbe@619:             WHERE "initiative"."issue_id" = "issue_id_v";
jbe@619:       END LOOP;
jbe@619:       RETURN "snapshot_id_v";
jbe@619:     END;
jbe@619:   $$;
jbe@619: 
jbe@619: CREATE OR REPLACE FUNCTION "weight_of_added_vote_delegations"
jbe@619:   ( "issue_id_p"            "issue"."id"%TYPE,
jbe@619:     "member_id_p"           "member"."id"%TYPE,
jbe@619:     "delegate_member_ids_p" "delegating_voter"."delegate_member_ids"%TYPE )
jbe@619:   RETURNS "direct_voter"."weight"%TYPE
jbe@619:   LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@619:     DECLARE
jbe@619:       "issue_delegation_row"  "issue_delegation"%ROWTYPE;
jbe@619:       "delegate_member_ids_v" "delegating_voter"."delegate_member_ids"%TYPE;
jbe@619:       "weight_v"              INT4;
jbe@619:       "sub_weight_v"          INT4;
jbe@619:     BEGIN
jbe@619:       PERFORM "require_transaction_isolation"();
jbe@619:       "weight_v" := 0;
jbe@619:       FOR "issue_delegation_row" IN
jbe@619:         SELECT * FROM "issue_delegation"
jbe@619:         WHERE "trustee_id" = "member_id_p"
jbe@619:         AND "issue_id" = "issue_id_p"
jbe@619:       LOOP
jbe@619:         IF NOT EXISTS (
jbe@619:           SELECT NULL FROM "direct_voter"
jbe@619:           WHERE "member_id" = "issue_delegation_row"."truster_id"
jbe@619:           AND "issue_id" = "issue_id_p"
jbe@619:         ) AND NOT EXISTS (
jbe@619:           SELECT NULL FROM "delegating_voter"
jbe@619:           WHERE "member_id" = "issue_delegation_row"."truster_id"
jbe@619:           AND "issue_id" = "issue_id_p"
jbe@619:         ) THEN
jbe@619:           "delegate_member_ids_v" :=
jbe@619:             "member_id_p" || "delegate_member_ids_p";
jbe@619:           INSERT INTO "delegating_voter" (
jbe@619:               "issue_id",
jbe@619:               "member_id",
jbe@619:               "ownweight",
jbe@619:               "scope",
jbe@619:               "delegate_member_ids"
jbe@619:             ) VALUES (
jbe@619:               "issue_id_p",
jbe@619:               "issue_delegation_row"."truster_id",
jbe@619:               "issue_delegation_row"."weight",
jbe@619:               "issue_delegation_row"."scope",
jbe@619:               "delegate_member_ids_v"
jbe@619:             );
jbe@619:           "sub_weight_v" := "issue_delegation_row"."weight" +
jbe@619:             "weight_of_added_vote_delegations"(
jbe@619:               "issue_id_p",
jbe@619:               "issue_delegation_row"."truster_id",
jbe@619:               "delegate_member_ids_v"
jbe@619:             );
jbe@619:           UPDATE "delegating_voter"
jbe@619:             SET "weight" = "sub_weight_v"
jbe@619:             WHERE "issue_id" = "issue_id_p"
jbe@619:             AND "member_id" = "issue_delegation_row"."truster_id";
jbe@619:           "weight_v" := "weight_v" + "sub_weight_v";
jbe@619:         END IF;
jbe@619:       END LOOP;
jbe@619:       RETURN "weight_v";
jbe@619:     END;
jbe@619:   $$;
jbe@619: 
jbe@619: CREATE OR REPLACE FUNCTION "add_vote_delegations"
jbe@619:   ( "issue_id_p" "issue"."id"%TYPE )
jbe@619:   RETURNS VOID
jbe@619:   LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@619:     DECLARE
jbe@619:       "member_id_v" "member"."id"%TYPE;
jbe@619:     BEGIN
jbe@619:       PERFORM "require_transaction_isolation"();
jbe@619:       FOR "member_id_v" IN
jbe@619:         SELECT "member_id" FROM "direct_voter"
jbe@619:         WHERE "issue_id" = "issue_id_p"
jbe@619:       LOOP
jbe@619:         UPDATE "direct_voter" SET
jbe@619:           "weight" = "ownweight" + "weight_of_added_vote_delegations"(
jbe@619:             "issue_id_p",
jbe@619:             "member_id_v",
jbe@619:             '{}'
jbe@619:           )
jbe@619:           WHERE "member_id" = "member_id_v"
jbe@619:           AND "issue_id" = "issue_id_p";
jbe@619:       END LOOP;
jbe@619:       RETURN;
jbe@619:     END;
jbe@619:   $$;
jbe@619: 
jbe@619: CREATE OR REPLACE FUNCTION "close_voting"("issue_id_p" "issue"."id"%TYPE)
jbe@619:   RETURNS VOID
jbe@619:   LANGUAGE 'plpgsql' VOLATILE AS $$
jbe@619:     DECLARE
jbe@619:       "area_id_v"   "area"."id"%TYPE;
jbe@619:       "unit_id_v"   "unit"."id"%TYPE;
jbe@619:       "member_id_v" "member"."id"%TYPE;
jbe@619:     BEGIN
jbe@619:       PERFORM "require_transaction_isolation"();
jbe@619:       SELECT "area_id" INTO "area_id_v" FROM "issue" WHERE "id" = "issue_id_p";
jbe@619:       SELECT "unit_id" INTO "unit_id_v" FROM "area"  WHERE "id" = "area_id_v";
jbe@619:       -- override protection triggers:
jbe@619:       INSERT INTO "temporary_transaction_data" ("key", "value")
jbe@619:         VALUES ('override_protection_triggers', TRUE::TEXT);
jbe@619:       -- delete timestamp of voting comment:
jbe@619:       UPDATE "direct_voter" SET "comment_changed" = NULL
jbe@619:         WHERE "issue_id" = "issue_id_p";
jbe@619:       -- delete delegating votes (in cases of manual reset of issue state):
jbe@619:       DELETE FROM "delegating_voter"
jbe@619:         WHERE "issue_id" = "issue_id_p";
jbe@619:       -- delete votes from non-privileged voters:
jbe@619:       DELETE FROM "direct_voter"
jbe@619:         USING (
jbe@619:           SELECT "direct_voter"."member_id"
jbe@619:           FROM "direct_voter"
jbe@619:           JOIN "member" ON "direct_voter"."member_id" = "member"."id"
jbe@619:           LEFT JOIN "privilege"
jbe@619:           ON "privilege"."unit_id" = "unit_id_v"
jbe@619:           AND "privilege"."member_id" = "direct_voter"."member_id"
jbe@619:           LEFT JOIN "issue_privilege"
jbe@619:           ON "issue_privilege"."issue_id" = "issue_id_p"
jbe@619:           AND "issue_privilege"."member_id" = "direct_voter"."member_id"
jbe@619:           WHERE "direct_voter"."issue_id" = "issue_id_p" AND (
jbe@619:             "member"."active" = FALSE OR
jbe@619:             COALESCE(
jbe@619:               "issue_privilege"."voting_right",
jbe@619:               "privilege"."voting_right",
jbe@619:               FALSE
jbe@619:             ) = FALSE
jbe@619:           )
jbe@619:         ) AS "subquery"
jbe@619:         WHERE "direct_voter"."issue_id" = "issue_id_p"
jbe@619:         AND "direct_voter"."member_id" = "subquery"."member_id";
jbe@619:       -- consider voting weight and delegations:
jbe@619:       UPDATE "direct_voter" SET "ownweight" = "privilege"."weight"
jbe@619:         FROM "privilege"
jbe@619:         WHERE "issue_id" = "issue_id_p"
jbe@619:         AND "privilege"."unit_id" = "unit_id_v"
jbe@619:         AND "privilege"."member_id" = "direct_voter"."member_id";
jbe@619:       UPDATE "direct_voter" SET "ownweight" = "issue_privilege"."weight"
jbe@619:         FROM "issue_privilege"
jbe@619:         WHERE "direct_voter"."issue_id" = "issue_id_p"
jbe@619:         AND "issue_privilege"."issue_id" = "issue_id_p"
jbe@619:         AND "issue_privilege"."member_id" = "direct_voter"."member_id";
jbe@619:       PERFORM "add_vote_delegations"("issue_id_p");
jbe@619:       -- mark first preferences:
jbe@619:       UPDATE "vote" SET "first_preference" = "subquery"."first_preference"
jbe@619:         FROM (
jbe@619:           SELECT
jbe@619:             "vote"."initiative_id",
jbe@619:             "vote"."member_id",
jbe@619:             CASE WHEN "vote"."grade" > 0 THEN
jbe@619:               CASE WHEN "vote"."grade" = max("agg"."grade") THEN TRUE ELSE FALSE END
jbe@619:             ELSE NULL
jbe@619:             END AS "first_preference"
jbe@619:           FROM "vote"
jbe@619:           JOIN "initiative"  -- NOTE: due to missing index on issue_id
jbe@619:           ON "vote"."issue_id" = "initiative"."issue_id"
jbe@619:           JOIN "vote" AS "agg"
jbe@619:           ON "initiative"."id" = "agg"."initiative_id"
jbe@619:           AND "vote"."member_id" = "agg"."member_id"
jbe@619:           GROUP BY "vote"."initiative_id", "vote"."member_id", "vote"."grade"
jbe@619:         ) AS "subquery"
jbe@619:         WHERE "vote"."issue_id" = "issue_id_p"
jbe@619:         AND "vote"."initiative_id" = "subquery"."initiative_id"
jbe@619:         AND "vote"."member_id" = "subquery"."member_id";
jbe@619:       -- finish overriding protection triggers (avoids garbage):
jbe@619:       DELETE FROM "temporary_transaction_data"
jbe@619:         WHERE "key" = 'override_protection_triggers';
jbe@619:       -- materialize battle_view:
jbe@619:       -- NOTE: "closed" column of issue must be set at this point
jbe@619:       DELETE FROM "battle" WHERE "issue_id" = "issue_id_p";
jbe@619:       INSERT INTO "battle" (
jbe@619:         "issue_id",
jbe@619:         "winning_initiative_id", "losing_initiative_id",
jbe@619:         "count"
jbe@619:       ) SELECT
jbe@619:         "issue_id",
jbe@619:         "winning_initiative_id", "losing_initiative_id",
jbe@619:         "count"
jbe@619:         FROM "battle_view" WHERE "issue_id" = "issue_id_p";
jbe@619:       -- set voter count:
jbe@619:       UPDATE "issue" SET
jbe@619:         "voter_count" = (
jbe@619:           SELECT coalesce(sum("weight"), 0)
jbe@619:           FROM "direct_voter" WHERE "issue_id" = "issue_id_p"
jbe@619:         )
jbe@619:         WHERE "id" = "issue_id_p";
jbe@619:       -- copy "positive_votes" and "negative_votes" from "battle" table:
jbe@619:       -- NOTE: "first_preference_votes" is set to a default of 0 at this step
jbe@619:       UPDATE "initiative" SET
jbe@619:         "first_preference_votes" = 0,
jbe@619:         "positive_votes" = "battle_win"."count",
jbe@619:         "negative_votes" = "battle_lose"."count"
jbe@619:         FROM "battle" AS "battle_win", "battle" AS "battle_lose"
jbe@619:         WHERE
jbe@619:           "battle_win"."issue_id" = "issue_id_p" AND
jbe@619:           "battle_win"."winning_initiative_id" = "initiative"."id" AND
jbe@619:           "battle_win"."losing_initiative_id" ISNULL AND
jbe@619:           "battle_lose"."issue_id" = "issue_id_p" AND
jbe@619:           "battle_lose"."losing_initiative_id" = "initiative"."id" AND
jbe@619:           "battle_lose"."winning_initiative_id" ISNULL;
jbe@619:       -- calculate "first_preference_votes":
jbe@619:       -- NOTE: will only set values not equal to zero
jbe@619:       UPDATE "initiative" SET "first_preference_votes" = "subquery"."sum"
jbe@619:         FROM (
jbe@619:           SELECT "vote"."initiative_id", sum("direct_voter"."weight")
jbe@619:           FROM "vote" JOIN "direct_voter"
jbe@619:           ON "vote"."issue_id" = "direct_voter"."issue_id"
jbe@619:           AND "vote"."member_id" = "direct_voter"."member_id"
jbe@619:           WHERE "vote"."first_preference"
jbe@619:           GROUP BY "vote"."initiative_id"
jbe@619:         ) AS "subquery"
jbe@619:         WHERE "initiative"."issue_id" = "issue_id_p"
jbe@619:         AND "initiative"."admitted"
jbe@619:         AND "initiative"."id" = "subquery"."initiative_id";
jbe@619:     END;
jbe@619:   $$;
jbe@619: 
jbe@619: COMMIT;




