BEGIN;
SELECT _v.register_patch('exchange-0005', NULL, NULL);
SET search_path TO exchange;
CREATE OR REPLACE FUNCTION random_bytea(
  bytea_length INT
)
RETURNS BYTEA
  AS $body$
  SELECT decode(string_agg(lpad(to_hex(width_bucket(random(), 0, 1, 256)-1),2,'0') ,''), 'hex')
    FROM generate_series(1, $1);
  $body$
LANGUAGE 'sql'
VOLATILE;
CREATE FUNCTION alter_table_wire_targets5()
RETURNS VOID
LANGUAGE plpgsql
AS $$
BEGIN
  EXECUTE FORMAT (
    'ALTER TABLE wire_targets'
    ' ADD COLUMN access_token BYTEA CHECK(LENGTH(access_token)=32)'
    '   DEFAULT random_bytea(32)'
    ',ADD COLUMN target_pub BYTEA CHECK(LENGTH(target_pub)=32)'
    '   DEFAULT NULL'
    ';'
  );
  PERFORM comment_partitioned_column(
     'high-entropy random value that is used as a bearer token used to authenticate access to the KYC SPA and its state (without requiring a signature)'
    ,'access_token'
    ,'wire_targets'
    ,NULL
  );
  PERFORM comment_partitioned_column(
     'Public key of a merchant instance or reserve to authenticate access; NULL if KYC is not allowed for the account (if there was no incoming KYC wire transfer yet); updated, thus NOT available to the auditor'
    ,'target_pub'
    ,'wire_targets'
    ,NULL
  );
END $$;
CREATE FUNCTION constrain_table_wire_targets5(
  IN partition_suffix TEXT
)
RETURNS void
LANGUAGE plpgsql
AS $$
DECLARE
  table_name TEXT DEFAULT 'wire_targets';
BEGIN
  table_name = concat_ws('_', table_name, partition_suffix);
  EXECUTE FORMAT (
    'ALTER TABLE ' || table_name ||
    ' ADD CONSTRAINT ' || table_name || '_wire_target_access_token_unique'
    ' UNIQUE (access_token)'
  );
END
$$;
INSERT INTO exchange_tables
    (name
    ,version
    ,action
    ,partitioned
    ,by_range)
  VALUES
    ('wire_targets5'
    ,'exchange-0005'
    ,'alter'
    ,TRUE
    ,FALSE),
    ('wire_targets5'
    ,'exchange-0005'
    ,'constrain'
    ,TRUE
    ,FALSE);
CREATE FUNCTION create_table_legitimization_measures(
  IN partition_suffix TEXT DEFAULT NULL
)
RETURNS VOID
LANGUAGE plpgsql
AS $$
BEGIN
  PERFORM create_partitioned_table(
    'CREATE TABLE %I'
      '(legitimization_measure_serial_id INT8 GENERATED BY DEFAULT AS IDENTITY'
      ',access_token BYTEA NOT NULL CHECK (LENGTH(access_token)=32)'
      ',start_time INT8 NOT NULL'
      ',jmeasures TEXT NOT NULL'
      ',display_priority INT4 NOT NULL' 
      ',is_finished BOOL NOT NULL DEFAULT(FALSE)'
    ') %s ;'
    ,'legitimization_measures'
    ,'PARTITION BY HASH (access_token)'
    ,partition_suffix
  );
  PERFORM comment_partitioned_table(
     'List of required legitimizations by account'
    ,'legitimization_measures'
    ,partition_suffix
  );
  PERFORM comment_partitioned_column(
     'unique ID for this legitimization process at the exchange'
    ,'legitimization_measure_serial_id'
    ,'legitimization_measures'
    ,partition_suffix
  );
  PERFORM comment_partitioned_column(
     'foreign key linking the entry to the wire_targets table, NOT a primary key (multiple legitimizations are possible per account)'
    ,'access_token'
    ,'legitimization_measures'
    ,partition_suffix
  );
  PERFORM comment_partitioned_column(
     'Time when the measure was triggered (by decision or rule)'
    ,'start_time'
    ,'legitimization_measures'
    ,partition_suffix
  );
  PERFORM comment_partitioned_column(
     'JSON object of type LegitimizationMeasures with KYC/AML measures for the account encoded'
    ,'jmeasures'
    ,'legitimization_measures'
    ,partition_suffix
  );
  PERFORM comment_partitioned_column(
     'Display priority of the rule that triggered this measure; if in the meantime another rule also triggers, the measure is only replaced if the new rule has a higher display priority; probably not really useful, as right now there is only ever one set of legitimization_measures active at any time, might be removed in the future'
    ,'display_priority'
    ,'legitimization_measures'
    ,partition_suffix
  );
  PERFORM comment_partitioned_column(
     'Set to TRUE if this set of measures was processed; used to avoid indexing measures that are done'
    ,'is_finished'
    ,'legitimization_measures'
    ,partition_suffix
  );
END
$$;
CREATE FUNCTION constrain_table_legitimization_measures(
  IN partition_suffix TEXT
)
RETURNS VOID
LANGUAGE plpgsql
AS $$
DECLARE
  table_name TEXT DEFAULT 'legitimization_measures';
BEGIN
  table_name = concat_ws('_', table_name, partition_suffix);
  EXECUTE FORMAT (
    'ALTER TABLE ' || table_name ||
    ' ADD CONSTRAINT ' || table_name || '_serial_id_key'
    ' UNIQUE (legitimization_measure_serial_id)');
  EXECUTE FORMAT (
    'CREATE INDEX ' || table_name || '_by_access_token'
    ' ON ' || table_name ||
    ' (access_token)'
    ' WHERE NOT is_finished' ||
    ';'
  );
END
$$;
CREATE FUNCTION foreign_table_legitimization_measures()
RETURNS void
LANGUAGE plpgsql
AS $$
DECLARE
  table_name TEXT DEFAULT 'legitimization_measures';
BEGIN
  EXECUTE FORMAT (
    'ALTER TABLE ' || table_name ||
    ' ADD CONSTRAINT ' || table_name || '_foreign_key_access_token'
    ' FOREIGN KEY (access_token)'
    ' REFERENCES wire_targets (access_token)'
    '  ON DELETE CASCADE');
END
$$;
INSERT INTO exchange_tables
    (name
    ,version
    ,action
    ,partitioned
    ,by_range)
  VALUES
    ('legitimization_measures'
    ,'exchange-0005'
    ,'create'
    ,TRUE
    ,FALSE),
    ('legitimization_measures'
    ,'exchange-0005'
    ,'constrain'
    ,TRUE
    ,FALSE),
    ('legitimization_measures'
    ,'exchange-0005'
    ,'foreign'
    ,TRUE
    ,FALSE);
CREATE FUNCTION create_table_legitimization_outcomes(
  IN partition_suffix TEXT DEFAULT NULL
)
RETURNS VOID
LANGUAGE plpgsql
AS $$
BEGIN
  PERFORM create_partitioned_table(
    'CREATE TABLE %I'
      '(outcome_serial_id BIGINT GENERATED BY DEFAULT AS IDENTITY'
      ',h_payto BYTEA NOT NULL CHECK (LENGTH(h_payto)=32)'
      ',decision_time INT8 NOT NULL'
      ',expiration_time INT8 NOT NULL'
      ',jproperties TEXT'
      ',new_measure_name TEXT'
      ',to_investigate BOOL NOT NULL'
      ',is_active BOOL NOT NULL DEFAULT(TRUE)'
      ',jnew_rules TEXT'
    ') %s ;'
    ,'legitimization_outcomes'
    ,'PARTITION BY HASH (h_payto)'
    ,partition_suffix
  );
  PERFORM comment_partitioned_table(
     'Outcomes of legitimization processes by account'
    ,'legitimization_outcomes'
    ,partition_suffix
  );
  PERFORM comment_partitioned_column(
     'unique ID for this legitimization outcome at the exchange'
    ,'outcome_serial_id'
    ,'legitimization_outcomes'
    ,partition_suffix
  );
  PERFORM comment_partitioned_column(
     'hash of the payto://-URI this outcome is about; foreign key linking the entry to the wire_targets table, NOT a primary key (multiple outcomes are possible per account over time)'
    ,'h_payto'
    ,'legitimization_outcomes'
    ,partition_suffix
  );
  PERFORM comment_partitioned_column(
     'when was this outcome decided, rounded timestamp'
    ,'decision_time'
    ,'legitimization_outcomes'
    ,partition_suffix
  );
  PERFORM comment_partitioned_column(
     'space-separated list of names of measures to trigger immediately, NULL for none, prefixed with a "+" to indicate AND combination for the measures'
    ,'new_measure_name'
    ,'legitimization_outcomes'
    ,partition_suffix
  );
  PERFORM comment_partitioned_column(
     'time when the decision expires and the expiration jnew_rules should be applied'
    ,'expiration_time'
    ,'legitimization_outcomes'
    ,partition_suffix
  );
  PERFORM comment_partitioned_column(
     'JSON object of type AccountProperties, such as PEP status, business domain, risk assessment, etc.'
    ,'jproperties'
    ,'legitimization_outcomes'
    ,partition_suffix
  );
  PERFORM comment_partitioned_column(
     'AML staff should investigate the activity of this account'
    ,'to_investigate'
    ,'legitimization_outcomes'
    ,partition_suffix
  );
  PERFORM comment_partitioned_column(
     'TRUE if this is the current authoritative legitimization outcome'
    ,'is_active'
    ,'legitimization_outcomes'
    ,partition_suffix
  );
  PERFORM comment_partitioned_column(
     'JSON object of type LegitimizationRuleSet with rules to apply to the various operation types for this account; all KYC checks should first check if active new rules for a given account exist in this table (and apply specified measures); if not, it should check the default rules to decide if a measure is required; NULL if the default rules apply'
    ,'jnew_rules'
    ,'legitimization_outcomes'
    ,partition_suffix
  );
END
$$;
CREATE FUNCTION constrain_table_legitimization_outcomes(
  IN partition_suffix TEXT
)
RETURNS VOID
LANGUAGE plpgsql
AS $$
DECLARE
  table_name TEXT DEFAULT 'legitimization_outcomes';
BEGIN
  table_name = concat_ws('_', table_name, partition_suffix);
  EXECUTE FORMAT (
    'CREATE INDEX ' || table_name || '_by_target_token'
    ' ON ' || table_name ||
    ' (h_payto)'
    ' WHERE is_active' ||
    ';'
  );
END
$$;
CREATE FUNCTION foreign_table_legitimization_outcomes()
RETURNS void
LANGUAGE plpgsql
AS $$
DECLARE
  table_name TEXT DEFAULT 'legitimization_outcomes';
BEGIN
  EXECUTE FORMAT (
    'ALTER TABLE ' || table_name ||
    ' ADD CONSTRAINT ' || table_name || '_serial_id_key'
    ' UNIQUE (outcome_serial_id)');
  EXECUTE FORMAT (
    'ALTER TABLE ' || table_name ||
    ' ADD CONSTRAINT ' || table_name || '_foreign_key_h_payto'
    ' FOREIGN KEY (h_payto)'
    ' REFERENCES wire_targets (wire_target_h_payto) ON DELETE CASCADE');
END
$$;
INSERT INTO exchange_tables
    (name
    ,version
    ,action
    ,partitioned
    ,by_range)
  VALUES
    ('legitimization_outcomes'
    ,'exchange-0005'
    ,'create'
    ,TRUE
    ,FALSE),
    ('legitimization_outcomes'
    ,'exchange-0005'
    ,'constrain'
    ,TRUE
    ,FALSE),
    ('legitimization_outcomes'
    ,'exchange-0005'
    ,'foreign'
    ,TRUE
    ,FALSE);
CREATE FUNCTION alter_table_legitimization_processes5(
  IN shard_suffix TEXT DEFAULT NULL
)
RETURNS VOID
LANGUAGE plpgsql
AS $$
BEGIN
  PERFORM create_partitioned_table(
    'ALTER TABLE %I'
    ' ADD COLUMN legitimization_measure_serial_id BIGINT'
    ',ADD COLUMN measure_index INT4 DEFAULT(0)'
    ',ADD COLUMN error_code INT4 DEFAULT (0)'
    ',ADD COLUMN error_message TEXT DEFAULT NULL'
    ';'
    ,'legitimization_processes'
    ,''
    ,shard_suffix
  );
  PERFORM create_partitioned_table(
    'ALTER TABLE %I'
    ' RENAME provider_section TO provider_name'
    ';'
    ,'legitimization_processes'
    ,''
    ,shard_suffix
  );
  PERFORM comment_partitioned_column(
     'measure that enabled this setup, NULL if client voluntarily initiated the process'
    ,'legitimization_measure_serial_id'
    ,'legitimization_processes'
    ,shard_suffix
  );
  PERFORM comment_partitioned_column(
     'index of the measure in legitimization_measures that was selected for this KYC setup; NULL if legitimization_measure_serial_id is NULL; enables determination of the context data provided to the external process'
    ,'measure_index'
    ,'legitimization_processes'
    ,shard_suffix
  );
  PERFORM comment_partitioned_column(
     'TALER_ErrorCode set if the process failed, otherwise NULL'
    ,'error_code'
    ,'legitimization_processes'
    ,shard_suffix
  );
  PERFORM comment_partitioned_column(
     'human-readable error details set if the process failed, otherwise NULL'
    ,'error_message'
    ,'legitimization_processes'
    ,shard_suffix
  );
END
$$;
CREATE FUNCTION foreign_table_legitimization_processes5()
RETURNS void
LANGUAGE plpgsql
AS $$
DECLARE
  table_name TEXT DEFAULT 'legitimization_processes';
BEGIN
  EXECUTE FORMAT (
    'ALTER TABLE ' || table_name ||
    ' ADD CONSTRAINT ' || table_name || '_foreign_key_legitimization_measure'
    ' FOREIGN KEY (legitimization_measure_serial_id)'
    ' REFERENCES legitimization_measures (legitimization_measure_serial_id)');
  EXECUTE FORMAT (
    'ALTER TABLE ' || table_name ||
    ' ADD CONSTRAINT ' || table_name || '_unique_measure_and_index'
    ' UNIQUE (legitimization_measure_serial_id,measure_index)');
END
$$;
INSERT INTO exchange_tables
    (name
    ,version
    ,action
    ,partitioned
    ,by_range)
  VALUES
    ('legitimization_processes5'
    ,'exchange-0005'
    ,'alter'
    ,TRUE
    ,FALSE),
    ('legitimization_processes5'
    ,'exchange-0005'
    ,'foreign'
    ,TRUE
    ,FALSE);
CREATE FUNCTION alter_table_kyc_attributes5(
  IN partition_suffix TEXT DEFAULT NULL
)
RETURNS VOID
LANGUAGE plpgsql
AS $$
DECLARE
  table_name TEXT DEFAULT 'kyc_attributes';
BEGIN
  PERFORM create_partitioned_table(
    'ALTER TABLE %I'
    ' DROP COLUMN kyc_prox'
    ',DROP COLUMN provider'
    ',DROP COLUMN satisfied_checks'
    ',DROP CONSTRAINT kyc_attributes_pkey'
    ',ADD COLUMN trigger_outcome_serial INT8 NOT NULL'
    ';'
    ,table_name
    ,''
    ,partition_suffix
  );
  PERFORM comment_partitioned_column(
     'ID of the outcome that was returned by the AML program based on the KYC data collected'
    ,'trigger_outcome_serial'
    ,table_name
    ,partition_suffix
  );
END $$;
CREATE OR REPLACE FUNCTION constrain_table_kyc_attributes5(
  IN partition_suffix TEXT
)
RETURNS void
LANGUAGE plpgsql
AS $$
DECLARE
  table_name TEXT DEFAULT 'kyc_attributes';
BEGIN
  table_name = concat_ws('_', table_name, partition_suffix);
  EXECUTE FORMAT (
    'CREATE INDEX ' || table_name || '_h_payto_index '
    'ON ' || table_name || ' '
    '(h_payto);'
  );
END $$;
CREATE FUNCTION foreign_table_kyc_attributes5()
RETURNS void
LANGUAGE plpgsql
AS $$
DECLARE
  table_name TEXT DEFAULT 'kyc_attributes';
BEGIN
  EXECUTE FORMAT (
    'ALTER TABLE ' || table_name ||
    ' ADD CONSTRAINT ' || table_name || '_foreign_legitimization_outcomes'
    ' FOREIGN KEY (trigger_outcome_serial)'
    ' REFERENCES legitimization_outcomes (outcome_serial_id) ON DELETE CASCADE'
  );
END $$;
INSERT INTO exchange_tables
    (name
    ,version
    ,action
    ,partitioned
    ,by_range)
  VALUES
    ('kyc_attributes5'
    ,'exchange-0005'
    ,'alter'
    ,TRUE
    ,FALSE),
    ('kyc_attributes5'
    ,'exchange-0005'
    ,'constrain'
    ,TRUE
    ,FALSE),
    ('kyc_attributes5'
    ,'exchange-0005'
    ,'foreign'
    ,TRUE
    ,FALSE);
CREATE FUNCTION alter_table_aml_history5(
  IN partition_suffix TEXT DEFAULT NULL
)
RETURNS VOID
LANGUAGE plpgsql
AS $$
DECLARE
  table_name TEXT DEFAULT 'aml_history';
BEGIN
  PERFORM create_partitioned_table(
    'ALTER TABLE %I'
    ' DROP COLUMN new_threshold'
    ',DROP COLUMN new_status'
    ',DROP COLUMN decision_time'
    ',DROP COLUMN kyc_requirements'
    ',DROP COLUMN kyc_req_row'
    ',ADD COLUMN outcome_serial_id INT8 NOT NULL'
    ';'
    ,table_name
    ,''
    ,partition_suffix
  );
  PERFORM comment_partitioned_column(
     'Actual outcome for the account (included in what decider_sig signs over)'
    ,'outcome_serial_id'
    ,table_name
    ,partition_suffix
  );
END $$;
CREATE FUNCTION foreign_table_aml_history5()
RETURNS void
LANGUAGE plpgsql
AS $$
DECLARE
  table_name TEXT DEFAULT 'aml_history';
BEGIN
  EXECUTE FORMAT (
    'ALTER TABLE ' || table_name ||
    ' ADD CONSTRAINT ' || table_name || '_legitimization_outcome'
    ' FOREIGN KEY (outcome_serial_id)'
    ' REFERENCES legitimization_outcomes (outcome_serial_id)'
  );
END $$;
INSERT INTO exchange_tables
    (name
    ,version
    ,action
    ,partitioned
    ,by_range)
  VALUES
    ('aml_history5'
    ,'exchange-0005'
    ,'alter'
    ,TRUE
    ,FALSE),
    ('aml_history5'
    ,'exchange-0005'
    ,'foreign'
    ,TRUE
    ,FALSE);
CREATE TABLE kyc_events (
  kyc_event_serial_id BIGINT GENERATED BY DEFAULT AS IDENTITY
 ,event_timestamp INT8 NOT NULL
 ,event_type TEXT NOT NULL
);
COMMENT ON TABLE kyc_events
  IS 'Records of key events for statistics. Populated via triggers.';
COMMENT ON COLUMN kyc_events.event_type
  IS 'Name of the event, such as account-open or sar-filed';
COMMENT ON COLUMN kyc_events.event_timestamp
  IS 'When did the event occur; timestamp in rounded absolute time';
CREATE INDEX kyc_event_index
  ON kyc_events(event_type,event_timestamp);
CREATE FUNCTION create_table_kycauths_in(
  IN partition_suffix TEXT DEFAULT NULL
)
RETURNS VOID
LANGUAGE plpgsql
AS $$
DECLARE
  table_name TEXT default 'kycauths_in';
BEGIN
  PERFORM create_partitioned_table(
    'CREATE TABLE %I'
      '(kycauth_in_serial_id BIGINT GENERATED BY DEFAULT AS IDENTITY'
      ',account_pub BYTEA CHECK (LENGTH(account_pub)=32)'
      ',wire_reference INT8 NOT NULL'
      ',credit taler_amount NOT NULL'
      ',wire_source_h_payto BYTEA CHECK (LENGTH(wire_source_h_payto)=32)'
      ',exchange_account_section TEXT NOT NULL'
      ',execution_date INT8 NOT NULL'
      ',PRIMARY KEY(wire_source_h_payto, wire_reference)'
    ') %s ;'
    ,table_name
    ,'PARTITION BY HASH (wire_source_h_payto)'
    ,partition_suffix
  );
  PERFORM comment_partitioned_table(
     'list of transfers to register a key for KYC authentication, one per incoming wire transfer'
    ,table_name
    ,partition_suffix
  );
  PERFORM comment_partitioned_column(
     'Identifies the debited bank account and KYC status by the hash over the full payto URI'
    ,'wire_source_h_payto'
    ,table_name
    ,partition_suffix
  );
  PERFORM comment_partitioned_column(
     'Public key to be associated with the account.'
    ,'account_pub'
    ,table_name
    ,partition_suffix
  );
  PERFORM comment_partitioned_column(
     'Amount that was transferred into the account'
    ,'credit'
    ,table_name
    ,partition_suffix
  );
END $$;
CREATE FUNCTION constrain_table_kycauths_in(
  IN partition_suffix TEXT
)
RETURNS void
LANGUAGE plpgsql
AS $$
DECLARE
  table_name TEXT default 'kycauths_in';
BEGIN
  table_name = concat_ws('_', table_name, partition_suffix);
  EXECUTE FORMAT (
    'ALTER TABLE ' || table_name ||
    ' ADD CONSTRAINT ' || table_name || '_kycauth_in_serial_id_key'
    ' UNIQUE (kycauth_in_serial_id)'
  );
END
$$;
INSERT INTO exchange_tables
    (name
    ,version
    ,action
    ,partitioned
    ,by_range)
  VALUES
    ('kycauths_in'
    ,'exchange-0005'
    ,'create'
    ,TRUE
    ,FALSE),
    ('kycauths_in'
    ,'exchange-0005'
    ,'constrain'
    ,TRUE
    ,FALSE);
CREATE FUNCTION foreign_table_reserves_in5()
RETURNS void
LANGUAGE plpgsql
AS $$
DECLARE
  table_name TEXT DEFAULT 'reserves_in';
BEGIN
  EXECUTE FORMAT (
    'ALTER TABLE ' || table_name ||
    ' ADD CONSTRAINT ' || table_name || '_wire_target_h_payto_foreign'
    ' FOREIGN KEY (wire_source_h_payto)'
    ' REFERENCES wire_targets (wire_target_h_payto)'
    ' ON DELETE RESTRICT'
  );
END
$$;
INSERT INTO exchange_tables
    (name
    ,version
    ,action
    ,partitioned
    ,by_range)
  VALUES
    ('reserves_in5'
    ,'exchange-0005'
    ,'foreign'
    ,TRUE
    ,FALSE);
CREATE FUNCTION alter_table_aml_status5(
  IN partition_suffix TEXT DEFAULT NULL
)
RETURNS VOID
LANGUAGE plpgsql
AS $$
DECLARE
  table_name TEXT DEFAULT 'aml_status';
BEGIN
  PERFORM create_partitioned_table(
    'DROP TABLE %I;'
    ,table_name
    ,''
    ,partition_suffix
  );
END $$;
INSERT INTO exchange_tables
    (name
    ,version
    ,action
    ,partitioned
    ,by_range)
  VALUES
    ('aml_status5'
    ,'exchange-0005'
    ,'alter'
    ,TRUE
    ,FALSE);
CREATE FUNCTION alter_table_legitimization_requirements5(
  IN partition_suffix TEXT DEFAULT NULL
)
RETURNS VOID
LANGUAGE plpgsql
AS $$
BEGIN
  PERFORM create_partitioned_table(
    'DROP TABLE %I;'
    ,'legitimization_requirements'
    ,''
    ,partition_suffix
  );
END
$$;
INSERT INTO exchange_tables
    (name
    ,version
    ,action
    ,partitioned
    ,by_range)
  VALUES
    ('legitimization_requirements5'
    ,'exchange-0005'
    ,'alter'
    ,TRUE
    ,FALSE);
COMMIT;
