gnunet-svn
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[taler-grid5k] 114/189: test with sharding


From: gnunet
Subject: [taler-grid5k] 114/189: test with sharding
Date: Thu, 28 Apr 2022 10:48:04 +0200

This is an automated email from the git hooks/post-receive script.

marco-boss pushed a commit to branch master
in repository grid5k.

commit 4fb7a844d572bfebe3ec3c28216ae074d12cc0b4
Author: Boss Marco <bossm8@bfh.ch>
AuthorDate: Mon Mar 28 21:48:24 2022 +0200

    test with sharding
---
 experiment/scripts/database.sh |   17 +-
 experiment/scripts/shard.sh    |   11 +-
 sql/exchange-0001.sql          | 1291 ++++++++++++++++++++++++++++------------
 sql/exchange-0002.sql          |  257 --------
 sql/exchange-shard-0000.sql    |  227 -------
 sql/exchange-tables.sql        | 1139 +++++++++++++++++++++++++++++++++++
 sql/partition-0001.sql         |  601 +++++++++++++++++++
 sql/shard-0001.sql             |   69 +++
 8 files changed, 2725 insertions(+), 887 deletions(-)

diff --git a/experiment/scripts/database.sh b/experiment/scripts/database.sh
index 16c28ec..468b366 100755
--- a/experiment/scripts/database.sh
+++ b/experiment/scripts/database.sh
@@ -108,7 +108,7 @@ function setup_config() {
   shared_preload_libraries='pg_stat_statements,auto_explain'
 
   # Should be set locally
-  # join_collapse_limit=1
+  join_collapse_limit=1
 
   # Large tables perform bad with the default settings
   # However, they could also be set on each table indiviudally
@@ -236,27 +236,26 @@ function enable_remote_access() {
   fi
 }
 
-function setup_distributed_db() {
+function setup_shards() {
+  su postgres << EOF
+psql -d "${DB_NAME}" -f ${G5K_HOME}/sql/exchange-tables.sql
+EOF
 
   cp ${G5K_HOME}/sql/exchange-0001.sql /usr/share/taler/sql/exchange/
-  cp ${G5K_HOME}/sql/exchange-0002.sql /usr/share/taler/sql/exchange/
+  
   chmod o+r /usr/share/taler/sql/exchange/exchange-0001.sql
-  chmod o+r /usr/share/taler/sql/exchange/exchange-0002.sql
 
   sudo -u taler-exchange-httpd taler-exchange-dbinit -r || true
   sudo -u taler-exchange-httpd taler-exchange-dbinit -s || true
   sudo -u taler-exchange-httpd taler-exchange-dbinit
-}
-
-function setup_shards() {
-  setup_distributed_db
 
   su postgres << EOF
+psql -d "${DB_NAME}" -f ${G5K_HOME}/sql/partition-0001.sql 
 psql -d "${DB_NAME}" -tAc "CREATE EXTENSION IF NOT EXISTS postgres_fdw;"
 EOF
 
   su taler-exchange-httpd -s /bin/bash << EOF
-psql -d "${DB_NAME}" -tAc "SELECT prepare_sharding();"
+psql -d "${DB_NAME}" -tAc "SELECT master_prepare_sharding();"
 EOF
 
   let "i=1"
diff --git a/experiment/scripts/shard.sh b/experiment/scripts/shard.sh
index bf72819..4f6ad9b 100755
--- a/experiment/scripts/shard.sh
+++ b/experiment/scripts/shard.sh
@@ -152,14 +152,19 @@ psql -tAc "SELECT 1 FROM pg_database WHERE 
datname='${DB_NAME}'" | \
   createdb -O "${DB_USER}" "${DB_NAME}"
 EOF
   
-  cp ${G5K_HOME}/sql/exchange-shard-0000.sql /tmp
-  chmod o+r /tmp/exchange-shard-0000.sql
+  cp ${G5K_HOME}/sql/shard-0001.sql ${G5K_HOME$/sql/exchange-tables.sql /tmp
+  chmod o+r /tmp/shard-0001.sql /tmp/exchange-tables.sql
 
   PGPASSWORD=${DB_PASSWORD} psql -tA \
                                  -U ${DB_USER} \
                                  -h localhost \
                                  -d ${DB_NAME} \
-                                 -f /tmp/exchange-shard-0000.sql
+                                 -f /tmp/exchange-tables.sql
+  PGPASSWORD=${DB_PASSWORD} psql -tA \
+                                 -U ${DB_USER} \
+                                 -h localhost \
+                                 -d ${DB_NAME} \
+                                 -f /tmp/shard-0000.sql
   PGPASSWORD=${DB_PASSWORD} psql -tA \
                                  -U ${DB_USER} \
                                  -h localhost \
diff --git a/sql/exchange-0001.sql b/sql/exchange-0001.sql
index 54cc8af..eb5860a 100644
--- a/sql/exchange-0001.sql
+++ b/sql/exchange-0001.sql
@@ -20,7 +20,6 @@ BEGIN;
 -- Check patch versioning is in place.
 SELECT _v.register_patch('exchange-0001', NULL, NULL);
 
-
 CREATE TABLE IF NOT EXISTS denominations
   (denominations_serial BIGINT GENERATED BY DEFAULT AS IDENTITY UNIQUE
   ,denom_pub_hash BYTEA PRIMARY KEY CHECK (LENGTH(denom_pub_hash)=64)
@@ -66,45 +65,28 @@ COMMENT ON TABLE denomination_revocations
   IS 'remembering which denomination keys have been revoked';
 
 
-CREATE TABLE IF NOT EXISTS wire_targets
-  (wire_target_serial_id BIGINT GENERATED BY DEFAULT AS IDENTITY -- UNIQUE
-  ,h_payto BYTEA PRIMARY KEY CHECK (LENGTH(h_payto)=64)
-  ,payto_uri VARCHAR NOT NULL
-  ,kyc_ok BOOLEAN NOT NULL DEFAULT (FALSE)
-  ,external_id VARCHAR
-  )
-  PARTITION BY HASH (h_payto);
+SELECT create_table_wire_targets();
+
 COMMENT ON TABLE wire_targets
   IS 'All senders and recipients of money via the exchange';
 COMMENT ON COLUMN wire_targets.payto_uri
   IS 'Can be a regular bank account, or also be a URI identifying a 
reserve-account (for P2P payments)';
-COMMENT ON COLUMN wire_targets.h_payto
+COMMENT ON COLUMN wire_targets.wire_target_h_payto
   IS 'Unsalted hash of payto_uri';
 COMMENT ON COLUMN wire_targets.kyc_ok
   IS 'true if the KYC check was passed successfully';
 COMMENT ON COLUMN wire_targets.external_id
   IS 'Name of the user that was used for OAuth 2.0-based legitimization';
+
 CREATE TABLE IF NOT EXISTS wire_targets_default
   PARTITION OF wire_targets
   FOR VALUES WITH (MODULUS 1, REMAINDER 0);
 
--- FIXME partition by serial_id rather than h_payto, 
--- it is used more in join conditions - crucial for sharding to select this.
--- Author: (Boss Marco)
-CREATE INDEX IF NOT EXISTS wire_targets_wire_target_serial_id
-  ON wire_targets
-  (wire_target_serial_id
-  );
+SELECT add_constraints_to_wire_targets_partition('default');
+
+
+SELECT create_table_reserves();
 
-CREATE TABLE IF NOT EXISTS reserves
-  (reserve_uuid BIGINT GENERATED BY DEFAULT AS IDENTITY
-  ,reserve_pub BYTEA PRIMARY KEY CHECK(LENGTH(reserve_pub)=32)
-  ,current_balance_val INT8 NOT NULL
-  ,current_balance_frac INT4 NOT NULL
-  ,expiration_date INT8 NOT NULL
-  ,gc_date INT8 NOT NULL
-  )
-  PARTITION BY HASH (reserve_pub);
 COMMENT ON TABLE reserves
   IS 'Summarizes the balance of a reserve. Updated when new funds are added or 
withdrawn.';
 COMMENT ON COLUMN reserves.reserve_pub
@@ -115,84 +97,38 @@ COMMENT ON COLUMN reserves.expiration_date
   IS 'Used to trigger closing of reserves that have not been drained after 
some time';
 COMMENT ON COLUMN reserves.gc_date
   IS 'Used to forget all information about a reserve during garbage 
collection';
+
 CREATE TABLE IF NOT EXISTS reserves_default
   PARTITION OF reserves
   FOR VALUES WITH (MODULUS 1, REMAINDER 0);
 
-CREATE INDEX IF NOT EXISTS reserves_by_expiration_index
-  ON reserves
-  (expiration_date
-  ,current_balance_val
-  ,current_balance_frac
-  );
-COMMENT ON INDEX reserves_by_expiration_index
-  IS 'used in get_expired_reserves';
-CREATE INDEX IF NOT EXISTS reserves_by_reserve_uuid_index
-  ON reserves
-  (reserve_uuid);
-CREATE INDEX IF NOT EXISTS reserves_by_gc_date_index
-  ON reserves
-  (gc_date);
-COMMENT ON INDEX reserves_by_gc_date_index
-  IS 'for reserve garbage collection';
-
-
-CREATE TABLE IF NOT EXISTS reserves_in
-  (reserve_in_serial_id BIGINT GENERATED BY DEFAULT AS IDENTITY -- UNIQUE
-  ,reserve_pub BYTEA PRIMARY KEY REFERENCES reserves (reserve_pub) ON DELETE 
CASCADE
-  ,wire_reference INT8 NOT NULL
-  ,credit_val INT8 NOT NULL
-  ,credit_frac INT4 NOT NULL
-  ,wire_source_serial_id INT8 NOT NULL -- REFERENCES wire_targets 
(wire_target_serial_id)
-  ,exchange_account_section TEXT NOT NULL
-  ,execution_date INT8 NOT NULL
-  )
-  PARTITION BY HASH (reserve_pub);
+
+
+SELECT create_table_reserves_in();
+
 COMMENT ON TABLE reserves_in
   IS 'list of transfers of funds into the reserves, one per incoming wire 
transfer';
-COMMENT ON COLUMN reserves_in.wire_source_serial_id
+COMMENT ON COLUMN reserves_in.wire_source_h_payto
   IS 'Identifies the debited bank account and KYC status';
 COMMENT ON COLUMN reserves_in.reserve_pub
   IS 'Public key of the reserve. Private key signifies ownership of the 
remaining balance.';
 COMMENT ON COLUMN reserves_in.credit_val
   IS 'Amount that was transferred into the reserve';
+
 CREATE TABLE IF NOT EXISTS reserves_in_default
   PARTITION OF reserves_in
   FOR VALUES WITH (MODULUS 1, REMAINDER 0);
 
-CREATE INDEX IF NOT EXISTS reserves_in_by_reserve_in_serial_id_index
-  ON reserves_in
-  (reserve_in_serial_id);
-CREATE INDEX IF NOT EXISTS 
reserves_in_by_exchange_account_section_execution_date_index
-  ON reserves_in
-  (exchange_account_section
-  ,execution_date
-  );
-CREATE INDEX IF NOT EXISTS 
reserves_in_by_exchange_account_reserve_in_serial_id_index
-  ON reserves_in
-  (exchange_account_section,
-  reserve_in_serial_id DESC
-  );
+SELECT add_constraints_to_reserves_in_partition('default');
 
 
-CREATE TABLE IF NOT EXISTS reserves_close
-  (close_uuid BIGINT GENERATED BY DEFAULT AS IDENTITY -- UNIQUE / PRIMARY KEY
-  ,reserve_pub BYTEA NOT NULL REFERENCES reserves (reserve_pub) ON DELETE 
CASCADE
-  ,execution_date INT8 NOT NULL
-  ,wtid BYTEA NOT NULL CHECK (LENGTH(wtid)=32)
-  ,wire_target_serial_id INT8 NOT NULL -- REFERENCES wire_targets 
(wire_target_serial_id)
-  ,amount_val INT8 NOT NULL
-  ,amount_frac INT4 NOT NULL
-  ,closing_fee_val INT8 NOT NULL
-  ,closing_fee_frac INT4 NOT NULL)
-  PARTITION BY HASH (reserve_pub);
+
+SELECT create_table_reserves_close();
+
 COMMENT ON TABLE reserves_close
   IS 'wire transfers executed by the reserve to close reserves';
-COMMENT ON COLUMN reserves_close.wire_target_serial_id
+COMMENT ON COLUMN reserves_close.wire_target_h_payto
   IS 'Identifies the credited bank account (and KYC status). Note that closing 
does not depend on KYC.';
-CREATE TABLE IF NOT EXISTS reserves_close_default
-  PARTITION OF reserves_close
-  FOR VALUES WITH (MODULUS 1, REMAINDER 0);
 
 CREATE INDEX IF NOT EXISTS reserves_close_by_close_uuid_index
   ON reserves_close
@@ -201,37 +137,27 @@ CREATE INDEX IF NOT EXISTS 
reserves_close_by_reserve_pub_index
   ON reserves_close
   (reserve_pub);
 
+CREATE TABLE IF NOT EXISTS reserves_close_default
+  PARTITION OF reserves_close
+  FOR VALUES WITH (MODULUS 1, REMAINDER 0);
+
+SELECT add_constraints_to_reserves_close_partition('default');
+
+
+SELECT create_table_reserves_out();
 
-CREATE TABLE IF NOT EXISTS reserves_out
-  (reserve_out_serial_id BIGINT GENERATED BY DEFAULT AS IDENTITY -- UNIQUE
-  ,h_blind_ev BYTEA CHECK (LENGTH(h_blind_ev)=64) UNIQUE
-  ,denominations_serial INT8 NOT NULL REFERENCES denominations 
(denominations_serial)
-  ,denom_sig BYTEA NOT NULL
-  ,reserve_uuid INT8 NOT NULL -- REFERENCES reserves (reserve_uuid) ON DELETE 
CASCADE
-  ,reserve_sig BYTEA NOT NULL CHECK (LENGTH(reserve_sig)=64)
-  ,execution_date INT8 NOT NULL
-  ,amount_with_fee_val INT8 NOT NULL
-  ,amount_with_fee_frac INT4 NOT NULL
-  )
-  PARTITION BY HASH (h_blind_ev);
 COMMENT ON TABLE reserves_out
   IS 'Withdraw operations performed on reserves.';
 COMMENT ON COLUMN reserves_out.h_blind_ev
   IS 'Hash of the blinded coin, used as primary key here so that broken 
clients that use a non-random coin or blinding factor fail to withdraw 
(otherwise they would fail on deposit when the coin is not unique there).';
 COMMENT ON COLUMN reserves_out.denominations_serial
   IS 'We do not CASCADE ON DELETE here, we may keep the denomination data 
alive';
+
 CREATE TABLE IF NOT EXISTS reserves_out_default
   PARTITION OF reserves_out
   FOR VALUES WITH (MODULUS 1, REMAINDER 0);
 
-CREATE INDEX IF NOT EXISTS reserves_out_by_reserve_out_serial_id_index
-  ON reserves_out
-  (reserve_out_serial_id);
-CREATE INDEX IF NOT EXISTS 
reserves_out_by_reserve_uuid_and_execution_date_index
-  ON reserves_out
-  (reserve_uuid, execution_date);
-COMMENT ON INDEX reserves_out_by_reserve_uuid_and_execution_date_index
-  IS 'for get_reserves_out and exchange_do_withdraw_limit_check';
+SELECT add_constraints_to_reserves_out_partition('default');
 
 
 CREATE TABLE IF NOT EXISTS auditors
@@ -315,16 +241,8 @@ COMMENT ON COLUMN extensions.config
   IS 'Configuration of the extension as JSON-blob, maybe NULL';
 
 
-CREATE TABLE IF NOT EXISTS known_coins
-  (known_coin_id BIGINT GENERATED BY DEFAULT AS IDENTITY -- UNIQUE
-  ,denominations_serial INT8 NOT NULL REFERENCES denominations 
(denominations_serial) ON DELETE CASCADE
-  ,coin_pub BYTEA NOT NULL PRIMARY KEY CHECK (LENGTH(coin_pub)=32)
-  ,age_commitment_hash BYTEA CHECK (LENGTH(age_commitment_hash)=32)
-  ,denom_sig BYTEA NOT NULL
-  ,remaining_val INT8 NOT NULL
-  ,remaining_frac INT4 NOT NULL
-  )
-  PARTITION BY HASH (coin_pub); -- FIXME: or include denominations_serial? or 
multi-level partitioning?
+SELECT create_table_known_coins();
+
 COMMENT ON TABLE known_coins
   IS 'information about coins and their signatures, so we do not have to store 
the signatures more than once if a coin is involved in multiple operations';
 COMMENT ON COLUMN known_coins.denominations_serial
@@ -337,26 +255,16 @@ COMMENT ON COLUMN known_coins.age_commitment_hash
   IS 'Optional hash of the age commitment for age restrictions as per DD 24 
(active if denom_type has the respective bit set)';
 COMMENT ON COLUMN known_coins.denom_sig
   IS 'This is the signature of the exchange that affirms that the coin is a 
valid coin. The specific signature type depends on denom_type of the 
denomination.';
+
 CREATE TABLE IF NOT EXISTS known_coins_default
   PARTITION OF known_coins
   FOR VALUES WITH (MODULUS 1, REMAINDER 0);
 
-CREATE INDEX IF NOT EXISTS known_coins_by_known_coin_id_index
-  ON known_coins
-  (known_coin_id);
+SELECT add_constraints_to_known_coins_partition('default');
 
 
-CREATE TABLE IF NOT EXISTS refresh_commitments
-  (melt_serial_id BIGINT GENERATED BY DEFAULT AS IDENTITY -- UNIQUE
-  ,rc BYTEA PRIMARY KEY CHECK (LENGTH(rc)=64)
-  ,old_coin_pub BYTEA NOT NULL REFERENCES known_coins (coin_pub) ON DELETE 
CASCADE
-  ,h_age_commitment BYTEA CHECK(LENGTH(h_age_commitment)=32)
-  ,old_coin_sig BYTEA NOT NULL CHECK(LENGTH(old_coin_sig)=64)
-  ,amount_with_fee_val INT8 NOT NULL
-  ,amount_with_fee_frac INT4 NOT NULL
-  ,noreveal_index INT4 NOT NULL
-  )
-  PARTITION BY HASH (rc);
+SELECT create_table_refresh_commitments();
+
 COMMENT ON TABLE refresh_commitments
   IS 'Commitments made when melting coins and the gamma value chosen by the 
exchange.';
 COMMENT ON COLUMN refresh_commitments.noreveal_index
@@ -365,33 +273,16 @@ COMMENT ON COLUMN refresh_commitments.rc
   IS 'Commitment made by the client, hash over the various client inputs in 
the cut-and-choose protocol';
 COMMENT ON COLUMN refresh_commitments.old_coin_pub
   IS 'Coin being melted in the refresh process.';
-COMMENT ON COLUMN refresh_commitments.h_age_commitment
-  IS 'The (optional) age commitment that was involved in the minting process 
of the coin, may be NULL.';
+
 CREATE TABLE IF NOT EXISTS refresh_commitments_default
   PARTITION OF refresh_commitments
   FOR VALUES WITH (MODULUS 1, REMAINDER 0);
 
-CREATE INDEX IF NOT EXISTS refresh_commitments_by_melt_serial_id_index
-  ON refresh_commitments
-  (melt_serial_id);
-CREATE INDEX IF NOT EXISTS refresh_commitments_by_old_coin_pub_index
-  ON refresh_commitments
-  (old_coin_pub);
+SELECT add_constraints_to_refresh_commitments_partition('default');
 
 
-CREATE TABLE IF NOT EXISTS refresh_revealed_coins
-  (rrc_serial BIGINT GENERATED BY DEFAULT AS IDENTITY -- UNIQUE
-  ,melt_serial_id INT8 NOT NULL -- REFERENCES refresh_commitments 
(melt_serial_id) ON DELETE CASCADE
-  ,freshcoin_index INT4 NOT NULL
-  ,link_sig BYTEA NOT NULL CHECK(LENGTH(link_sig)=64)
-  ,denominations_serial INT8 NOT NULL REFERENCES denominations 
(denominations_serial) ON DELETE CASCADE
-  ,coin_ev BYTEA NOT NULL -- UNIQUE
-  ,h_coin_ev BYTEA NOT NULL CHECK(LENGTH(h_coin_ev)=64) -- UNIQUE
-  ,ev_sig BYTEA NOT NULL
-  ,ewv BYTEA NOT NULL
-  --  ,PRIMARY KEY (melt_serial_id, freshcoin_index) -- done per shard
-  )
-  PARTITION BY HASH (melt_serial_id);
+SELECT create_table_refresh_revealed_coins();
+
 COMMENT ON TABLE refresh_revealed_coins
   IS 'Revelations about the new coins that are to be created during a melting 
session.';
 COMMENT ON COLUMN refresh_revealed_coins.rrc_serial
@@ -408,28 +299,16 @@ COMMENT ON COLUMN refresh_revealed_coins.h_coin_ev
   IS 'hash of the envelope of the new coin to be signed (for lookups)';
 COMMENT ON COLUMN refresh_revealed_coins.ev_sig
   IS 'exchange signature over the envelope';
+
 CREATE TABLE IF NOT EXISTS refresh_revealed_coins_default
   PARTITION OF refresh_revealed_coins
   FOR VALUES WITH (MODULUS 1, REMAINDER 0);
--- We do require this primary key on each shard!
-ALTER TABLE refresh_revealed_coins_default
-  ADD PRIMARY KEY (melt_serial_id, freshcoin_index);
-
-CREATE INDEX IF NOT EXISTS refresh_revealed_coins_by_rrc_serial_index
-  ON refresh_revealed_coins
-  (rrc_serial);
-CREATE INDEX IF NOT EXISTS refresh_revealed_coins_by_melt_serial_id_index
-  ON refresh_revealed_coins
-  (melt_serial_id);
-
-
-CREATE TABLE IF NOT EXISTS refresh_transfer_keys
-  (rtc_serial BIGINT GENERATED BY DEFAULT AS IDENTITY -- UNIQUE
-  ,melt_serial_id INT8 PRIMARY KEY -- REFERENCES refresh_commitments 
(melt_serial_id) ON DELETE CASCADE
-  ,transfer_pub BYTEA NOT NULL CHECK(LENGTH(transfer_pub)=32)
-  ,transfer_privs BYTEA NOT NULL
-  )
-  PARTITION BY HASH (melt_serial_id);
+
+SELECT add_constraints_to_refresh_revealed_coins_partition('default');
+
+
+SELECT create_table_refresh_transfer_keys();
+
 COMMENT ON TABLE refresh_transfer_keys
   IS 'Transfer keys of a refresh operation (the data revealed to the 
exchange).';
 COMMENT ON COLUMN refresh_transfer_keys.rtc_serial
@@ -440,13 +319,12 @@ COMMENT ON COLUMN refresh_transfer_keys.transfer_pub
   IS 'transfer public key for the gamma index';
 COMMENT ON COLUMN refresh_transfer_keys.transfer_privs
   IS 'array of TALER_CNC_KAPPA - 1 transfer private keys that have been 
revealed, with the gamma entry being skipped';
+
 CREATE TABLE IF NOT EXISTS refresh_transfer_keys_default
   PARTITION OF refresh_transfer_keys
   FOR VALUES WITH (MODULUS 1, REMAINDER 0);
 
-CREATE INDEX IF NOT EXISTS refresh_transfer_keys_by_rtc_serial_index
-  ON refresh_transfer_keys
-  (rtc_serial);
+SELECT add_constraints_to_refresh_transfer_keys_partition('default');
 
 
 CREATE TABLE IF NOT EXISTS extension_details
@@ -458,37 +336,38 @@ COMMENT ON COLUMN extension_details.extension_options
   IS 'JSON object with options set that the exchange needs to consider when 
executing a deposit. Supported details depend on the extensions supported by 
the exchange.';
 
 
-CREATE TABLE IF NOT EXISTS deposits
-  (deposit_serial_id BIGINT GENERATED BY DEFAULT AS IDENTITY -- PRIMARY KEY
-  ,shard INT8 NOT NULL
-  ,known_coin_id INT8 NOT NULL -- REFERENCES known_coins (known_coin_id) ON 
DELETE CASCADE
-  ,amount_with_fee_val INT8 NOT NULL
-  ,amount_with_fee_frac INT4 NOT NULL
-  ,wallet_timestamp INT8 NOT NULL
-  ,exchange_timestamp INT8 NOT NULL
-  ,refund_deadline INT8 NOT NULL
-  ,wire_deadline INT8 NOT NULL
-  ,merchant_pub BYTEA NOT NULL CHECK (LENGTH(merchant_pub)=32)
-  ,h_contract_terms BYTEA NOT NULL CHECK (LENGTH(h_contract_terms)=64)
-  ,coin_sig BYTEA NOT NULL CHECK (LENGTH(coin_sig)=64)
-  ,wire_salt BYTEA NOT NULL CHECK (LENGTH(wire_salt)=16)
-  ,wire_target_serial_id INT8 NOT NULL -- REFERENCES wire_targets 
(wire_target_serial_id)
-  ,tiny BOOLEAN NOT NULL DEFAULT FALSE
-  ,done BOOLEAN NOT NULL DEFAULT FALSE
-  ,extension_blocked BOOLEAN NOT NULL DEFAULT FALSE
-  ,extension_details_serial_id INT8 REFERENCES extension_details 
(extension_details_serial_id) ON DELETE CASCADE
-  ,UNIQUE (shard, known_coin_id, merchant_pub, h_contract_terms)
-  )
-  PARTITION BY HASH (shard);
-CREATE TABLE IF NOT EXISTS deposits_default
-  PARTITION OF deposits
-  FOR VALUES WITH (MODULUS 1, REMAINDER 0);
+SELECT create_table_deposits();
+-- FIXME:
+-- TODO: dynamically (!) creating/deleting partitions:
+--    create new partitions 'as needed', drop old ones once the aggregator has 
made
+--    them empty; as 'new' deposits will always have deadlines in the future, 
this
+--    would basically guarantee no conflict between aggregator and exchange 
service!
+-- SEE also: 
https://www.cybertec-postgresql.com/en/automatic-partition-creation-in-postgresql/
+-- (article is slightly wrong, as this works:)
+--CREATE TABLE tab (
+--  id bigint GENERATED ALWAYS AS IDENTITY,
+--  ts timestamp NOT NULL,
+--  data text
+-- PARTITION BY LIST ((ts::date));
+-- CREATE TABLE tab_def PARTITION OF tab DEFAULT;
+-- BEGIN
+-- CREATE TABLE tab_part2 (LIKE tab);
+-- insert into tab_part2 (id,ts, data) values (5,'2022-03-21', 'foo');
+-- alter table tab attach partition tab_part2 for values in ('2022-03-21');
+-- commit;
+-- Naturally, to ensure this is actually 100% conflict-free, we'd
+-- need to create tables at the granularity of the wire/refund deadlines;
+-- that is right now seconds (!). But I see no problem with changing the
+-- aggregator to basically always run 1 minute behind and use minutes instead!
+
 
 COMMENT ON TABLE deposits
   IS 'Deposits we have received and for which we need to make (aggregate) wire 
transfers (and manage refunds).';
 COMMENT ON COLUMN deposits.shard
-  IS 'Used for load sharding. Should be set based on h_payto and merchant_pub. 
64-bit value because we need an *unsigned* 32-bit value.';
-COMMENT ON COLUMN deposits.wire_target_serial_id
+  IS 'Used for load sharding. Should be set based on merchant_pub. 64-bit 
value because we need an *unsigned* 32-bit value.';
+COMMENT ON COLUMN deposits.known_coin_id
+  IS 'Used for garbage collection';
+COMMENT ON COLUMN deposits.wire_target_h_payto
   IS 'Identifies the target bank account and KYC status';
 COMMENT ON COLUMN deposits.wire_salt
   IS 'Salt used when hashing the payto://-URI to get the h_wire';
@@ -501,113 +380,233 @@ COMMENT ON COLUMN deposits.extension_details_serial_id
 COMMENT ON COLUMN deposits.tiny
   IS 'Set to TRUE if we decided that the amount is too small to ever trigger a 
wire transfer by itself (requires real aggregation)';
 
--- FIXME: check if we can ALWAYS include the shard in the WHERE clauses,
--- thereby resulting in a much better use of the index: we could do 
(shard,deposit_serial_id)!
-CREATE INDEX IF NOT EXISTS deposits_deposit_by_serial_id_index
-  ON deposits
-  (deposit_serial_id);
-CREATE INDEX IF NOT EXISTS deposits_for_get_ready_index
-  ON deposits
-  (shard ASC
-  ,done
-  ,extension_blocked
-  ,tiny
-  ,wire_deadline ASC
-  );
-COMMENT ON INDEX deposits_for_get_ready_index
-  IS 'for deposits_get_ready';
--- FIXME: check if we can ALWAYS include the shard in the WHERE clauses,
--- thereby resulting in a much better use of the index: we could do 
(shard,merchant_pub, ...)!
-CREATE INDEX IF NOT EXISTS deposits_for_iterate_matching_index
-  ON deposits
-  (merchant_pub
-  ,wire_target_serial_id
-  ,done
-  ,extension_blocked
-  ,refund_deadline ASC
-  );
-COMMENT ON INDEX deposits_for_iterate_matching_index
-  IS 'for deposits_iterate_matching';
+CREATE TABLE IF NOT EXISTS deposits_default
+  PARTITION OF deposits
+  FOR VALUES WITH (MODULUS 1, REMAINDER 0);
 
+SELECT add_constraints_to_deposits_partition('default');
+
+
+SELECT create_table_deposits_by_ready();
+
+COMMENT ON TABLE deposits_by_ready
+  IS 'Enables fast lookups for deposits_get_ready, auto-populated via TRIGGER 
below';
+
+CREATE TABLE IF NOT EXISTS deposits_by_ready_default
+  PARTITION OF deposits_by_ready
+  DEFAULT;
+
+
+SELECT create_table_deposits_for_matching();
+
+COMMENT ON TABLE deposits_for_matching
+  IS 'Enables fast lookups for deposits_iterate_matching, auto-populated via 
TRIGGER below';
+
+CREATE TABLE IF NOT EXISTS deposits_for_matching_default
+  PARTITION OF deposits_for_matching
+  DEFAULT;
+  
+
+CREATE OR REPLACE FUNCTION deposits_insert_trigger()
+  RETURNS trigger
+  LANGUAGE plpgsql
+  AS $$
+DECLARE
+  is_ready BOOLEAN;
+DECLARE
+  is_tready BOOLEAN; -- is ready, but may be tiny
+BEGIN
+  is_ready  = NOT (NEW.done OR NEW.tiny OR NEW.extension_blocked);
+  is_tready = NOT (NEW.done OR NEW.extension_blocked);
+
+  IF (is_ready)
+  THEN
+    INSERT INTO deposits_by_ready
+      (wire_deadline
+      ,shard
+      ,coin_pub
+      ,deposit_serial_id)
+    VALUES
+      (NEW.wire_deadline
+      ,NEW.shard
+      ,NEW.coin_pub
+      ,NEW.deposit_serial_id);
+  END IF;
+  IF (is_tready)
+  THEN
+    INSERT INTO deposits_for_matching
+      (refund_deadline
+      ,shard
+      ,coin_pub
+      ,deposit_serial_id)
+    VALUES
+      (NEW.refund_deadline
+      ,NEW.shard
+      ,NEW.coin_pub
+      ,NEW.deposit_serial_id);
+  END IF;
+  RETURN NEW;
+END $$;  
+COMMENT ON FUNCTION deposits_insert_trigger()
+  IS 'Replicate deposit inserts into materialized indices.';
+
+CREATE TRIGGER deposits_on_insert
+  AFTER INSERT
+   ON deposits
+   FOR EACH ROW EXECUTE FUNCTION deposits_insert_trigger();
+
+CREATE OR REPLACE FUNCTION deposits_update_trigger()
+  RETURNS trigger
+  LANGUAGE plpgsql
+  AS $$
+DECLARE
+  was_ready BOOLEAN;
+DECLARE
+  is_ready BOOLEAN;
+DECLARE
+  was_tready BOOLEAN; -- was ready, but may be tiny
+DECLARE
+  is_tready BOOLEAN; -- is ready, but may be tiny
+BEGIN
+  was_ready = NOT (OLD.done OR OLD.tiny OR OLD.extension_blocked);
+  is_ready  = NOT (NEW.done OR NEW.tiny OR NEW.extension_blocked);
+  was_tready = NOT (OLD.done OR OLD.extension_blocked);
+  is_tready  = NOT (NEW.done OR NEW.extension_blocked);
+  IF (was_ready AND NOT is_ready)
+  THEN
+    DELETE FROM deposits_by_ready
+     WHERE wire_deadline = OLD.wire_deadline
+       AND shard = OLD.shard
+       AND coin_pub = OLD.coin_pub
+       AND deposit_serial_id = OLD.deposit_serial_id;
+  END IF;
+  IF (was_tready AND NOT is_tready)
+  THEN
+    DELETE FROM deposits_for_matching
+     WHERE refund_deadline = OLD.refund_deadline
+       AND shard = OLD.shard
+       AND coin_pub = OLD.coin_pub
+       AND deposit_serial_id = OLD.deposit_serial_id;
+  END IF;
+  IF (is_ready AND NOT was_ready)
+  THEN
+    INSERT INTO deposits_by_ready
+      (wire_deadline
+      ,shard
+      ,coin_pub
+      ,deposit_serial_id)
+    VALUES
+      (NEW.wire_deadline
+      ,NEW.shard
+      ,NEW.coin_pub
+      ,NEW.deposit_serial_id);
+  END IF;
+  IF (is_tready AND NOT was_tready)
+  THEN
+    INSERT INTO deposits_for_matching
+      (refund_deadline
+      ,shard
+      ,coin_pub
+      ,deposit_serial_id)
+    VALUES
+      (NEW.refund_deadline
+      ,NEW.shard
+      ,NEW.coin_pub
+      ,NEW.deposit_serial_id);
+  END IF;
+  RETURN NEW;
+END $$;
+COMMENT ON FUNCTION deposits_update_trigger()
+  IS 'Replicate deposits changes into materialized indices.';
+
+CREATE TRIGGER deposits_on_update
+  AFTER UPDATE
+    ON deposits
+   FOR EACH ROW EXECUTE FUNCTION deposits_update_trigger();
+
+CREATE OR REPLACE FUNCTION deposits_delete_trigger()
+  RETURNS trigger
+  LANGUAGE plpgsql
+  AS $$
+DECLARE
+  was_ready BOOLEAN;
+DECLARE
+  was_tready BOOLEAN; -- is ready, but may be tiny
+BEGIN
+  was_ready  = NOT (OLD.done OR OLD.tiny OR OLD.extension_blocked);
+  was_tready = NOT (OLD.done OR OLD.extension_blocked);
+
+  IF (was_ready)
+  THEN
+    DELETE FROM deposits_by_ready
+     WHERE wire_deadline = OLD.wire_deadline
+       AND shard = OLD.shard
+       AND coin_pub = OLD.coin_pub
+       AND deposit_serial_id = OLD.deposit_serial_id;
+  END IF;
+  IF (was_tready)
+  THEN
+    DELETE FROM deposits_for_matching
+     WHERE refund_deadline = OLD.refund_deadline
+       AND shard = OLD.shard
+       AND coin_pub = OLD.coin_pub
+       AND deposit_serial_id = OLD.deposit_serial_id;
+  END IF;
+  RETURN NEW;
+END $$;  
+COMMENT ON FUNCTION deposits_delete_trigger()
+  IS 'Replicate deposit deletions into materialized indices.';
+
+CREATE TRIGGER deposits_on_delete
+  AFTER DELETE
+   ON deposits
+   FOR EACH ROW EXECUTE FUNCTION deposits_delete_trigger();
+
+
+SELECT create_table_refunds();
 
-CREATE TABLE IF NOT EXISTS refunds
-  (refund_serial_id BIGINT GENERATED BY DEFAULT AS IDENTITY -- UNIQUE
-  ,deposit_serial_id INT8 NOT NULL -- REFERENCES deposits (deposit_serial_id) 
ON DELETE CASCADE
-  ,merchant_sig BYTEA NOT NULL CHECK(LENGTH(merchant_sig)=64)
-  ,rtransaction_id INT8 NOT NULL
-  ,amount_with_fee_val INT8 NOT NULL
-  ,amount_with_fee_frac INT4 NOT NULL
-  -- ,PRIMARY KEY (deposit_serial_id, rtransaction_id) -- done per shard!
-  )
-  PARTITION BY HASH (deposit_serial_id);
 COMMENT ON TABLE refunds
   IS 'Data on coins that were refunded. Technically, refunds always apply 
against specific deposit operations involving a coin. The combination of 
coin_pub, merchant_pub, h_contract_terms and rtransaction_id MUST be unique, 
and we usually select by coin_pub so that one goes first.';
 COMMENT ON COLUMN refunds.deposit_serial_id
-  IS 'Identifies ONLY the merchant_pub, h_contract_terms and known_coin_id. 
Multiple deposits may match a refund, this only identifies one of them.';
+  IS 'Identifies ONLY the merchant_pub, h_contract_terms and coin_pub. 
Multiple deposits may match a refund, this only identifies one of them.';
 COMMENT ON COLUMN refunds.rtransaction_id
   IS 'used by the merchant to make refunds unique in case the same coin for 
the same deposit gets a subsequent (higher) refund';
+
 CREATE TABLE IF NOT EXISTS refunds_default
   PARTITION OF refunds
   FOR VALUES WITH (MODULUS 1, REMAINDER 0);
-ALTER TABLE refunds_default
-  ADD PRIMARY KEY (deposit_serial_id, rtransaction_id);
 
-CREATE INDEX IF NOT EXISTS refunds_by_refund_serial_id_index
-  ON refunds
-  (refund_serial_id);
+SELECT add_constraints_to_refunds_partition('default');
 
 
-CREATE TABLE IF NOT EXISTS wire_out
-  (wireout_uuid BIGINT GENERATED BY DEFAULT AS IDENTITY -- PRIMARY KEY
-  ,execution_date INT8 NOT NULL
-  ,wtid_raw BYTEA UNIQUE NOT NULL CHECK (LENGTH(wtid_raw)=32)
-  ,wire_target_serial_id INT8 NOT NULL -- REFERENCES wire_targets 
(wire_target_serial_id)
-  ,exchange_account_section TEXT NOT NULL
-  ,amount_val INT8 NOT NULL
-  ,amount_frac INT4 NOT NULL
-  )
-  PARTITION BY HASH (wtid_raw);
+SELECT create_table_wire_out();
+
 COMMENT ON TABLE wire_out
   IS 'wire transfers the exchange has executed';
 COMMENT ON COLUMN wire_out.exchange_account_section
   IS 'identifies the configuration section with the debit account of this 
payment';
-COMMENT ON COLUMN wire_out.wire_target_serial_id
+COMMENT ON COLUMN wire_out.wire_target_h_payto
   IS 'Identifies the credited bank account and KYC status';
+
 CREATE TABLE IF NOT EXISTS wire_out_default
   PARTITION OF wire_out
   FOR VALUES WITH (MODULUS 1, REMAINDER 0);
 
-CREATE INDEX IF NOT EXISTS wire_out_by_wireout_uuid_index
-  ON wire_out
-  (wireout_uuid);
-CREATE INDEX IF NOT EXISTS wire_out_by_wire_target_serial_id_index
-  ON wire_out
-  (wire_target_serial_id);
+SELECT add_constraints_to_wire_out_partition('default');
 
 
+SELECT create_table_aggregation_tracking();
 
-CREATE TABLE IF NOT EXISTS aggregation_tracking
-  (aggregation_serial_id BIGINT GENERATED BY DEFAULT AS IDENTITY -- UNIQUE
-  ,deposit_serial_id INT8 PRIMARY KEY -- REFERENCES deposits 
(deposit_serial_id) ON DELETE CASCADE
-  ,wtid_raw BYTEA NOT NULL CONSTRAINT wire_out_ref REFERENCES 
wire_out(wtid_raw) ON DELETE CASCADE DEFERRABLE
-  )
-  PARTITION BY HASH (deposit_serial_id);
 COMMENT ON TABLE aggregation_tracking
   IS 'mapping from wire transfer identifiers (WTID) to deposits (and back)';
 COMMENT ON COLUMN aggregation_tracking.wtid_raw
   IS 'We first create entries in the aggregation_tracking table and then 
finally the wire_out entry once we know the total amount. Hence the constraint 
must be deferrable and we cannot use a wireout_uuid here, because we do not 
have it when these rows are created. Changing the logic to first INSERT a dummy 
row into wire_out and then UPDATEing that row in the same transaction would 
theoretically reduce per-deposit storage costs by 5 percent (24/~460 bytes).';
+
 CREATE TABLE IF NOT EXISTS aggregation_tracking_default
   PARTITION OF aggregation_tracking
   FOR VALUES WITH (MODULUS 1, REMAINDER 0);
 
-CREATE INDEX IF NOT EXISTS aggregation_tracking_by_aggregation_serial_id_index
-  ON aggregation_tracking
-  (aggregation_serial_id);
-CREATE INDEX IF NOT EXISTS aggregation_tracking_by_wtid_raw_index
-  ON aggregation_tracking
-  (wtid_raw);
-COMMENT ON INDEX aggregation_tracking_by_wtid_raw_index
-  IS 'for lookup_transactions';
+SELECT add_constraints_to_aggregation_tracking_partition('default');
 
 
 CREATE TABLE IF NOT EXISTS wire_fee
@@ -619,6 +618,8 @@ CREATE TABLE IF NOT EXISTS wire_fee
   ,wire_fee_frac INT4 NOT NULL
   ,closing_fee_val INT8 NOT NULL
   ,closing_fee_frac INT4 NOT NULL
+  ,wad_fee_val INT8 NOT NULL
+  ,wad_fee_frac INT4 NOT NULL
   ,master_sig BYTEA NOT NULL CHECK (LENGTH(master_sig)=64)
   ,PRIMARY KEY (wire_method, start_date)
   );
@@ -632,20 +633,40 @@ CREATE INDEX IF NOT EXISTS wire_fee_by_end_date_index
   (end_date);
 
 
-CREATE TABLE IF NOT EXISTS recoup
-  (recoup_uuid BIGINT GENERATED BY DEFAULT AS IDENTITY -- UNIQUE
-  ,known_coin_id INT8 NOT NULL -- REFERENCES known_coins (known_coin_id)
-  ,coin_sig BYTEA NOT NULL CHECK(LENGTH(coin_sig)=64)
-  ,coin_blind BYTEA NOT NULL CHECK(LENGTH(coin_blind)=32)
-  ,amount_val INT8 NOT NULL
-  ,amount_frac INT4 NOT NULL
-  ,recoup_timestamp INT8 NOT NULL
-  ,reserve_out_serial_id INT8 NOT NULL -- REFERENCES reserves_out 
(reserve_out_serial_id) ON DELETE CASCADE
-  )
-  PARTITION BY HASH (known_coin_id);
+CREATE TABLE IF NOT EXISTS global_fee
+  (global_fee_serial BIGINT GENERATED BY DEFAULT AS IDENTITY UNIQUE
+  ,start_date INT8 NOT NULL
+  ,end_date INT8 NOT NULL
+  ,history_fee_val INT8 NOT NULL
+  ,history_fee_frac INT4 NOT NULL
+  ,kyc_fee_val INT8 NOT NULL
+  ,kyc_fee_frac INT4 NOT NULL
+  ,account_fee_val INT8 NOT NULL
+  ,account_fee_frac INT4 NOT NULL
+  ,purse_fee_val INT8 NOT NULL
+  ,purse_fee_frac INT4 NOT NULL
+  ,purse_timeout INT8 NOT NULL
+  ,kyc_timeout INT8 NOT NULL
+  ,history_expiration INT8 NOT NULL
+  ,purse_account_limit INT4 NOT NULL
+  ,master_sig BYTEA NOT NULL CHECK (LENGTH(master_sig)=64)
+  ,PRIMARY KEY (start_date)
+  );
+COMMENT ON TABLE global_fee
+  IS 'list of the global fees of this exchange, by date';
+COMMENT ON COLUMN global_fee.global_fee_serial
+  IS 'needed for exchange-auditor replication logic';
+
+CREATE INDEX IF NOT EXISTS global_fee_by_end_date_index
+  ON global_fee
+  (end_date);
+
+
+SELECT create_table_recoup();
+
 COMMENT ON TABLE recoup
   IS 'Information about recoups that were executed between a coin and a 
reserve. In this type of recoup, the amount is credited back to the reserve 
from which the coin originated.';
-COMMENT ON COLUMN recoup.known_coin_id
+COMMENT ON COLUMN recoup.coin_pub
   IS 'Coin that is being debited in the recoup. Do not CASCADE ON DROP on the 
coin_pub, as we may keep the coin alive!';
 COMMENT ON COLUMN recoup.reserve_out_serial_id
   IS 'Identifies the h_blind_ev of the recouped coin and provides the link to 
the credited reserve.';
@@ -653,63 +674,138 @@ COMMENT ON COLUMN recoup.coin_sig
   IS 'Signature by the coin affirming the recoup, of type 
TALER_SIGNATURE_WALLET_COIN_RECOUP';
 COMMENT ON COLUMN recoup.coin_blind
   IS 'Denomination blinding key used when creating the blinded coin from the 
planchet. Secret revealed during the recoup to provide the linkage between the 
coin and the withdraw operation.';
+
 CREATE TABLE IF NOT EXISTS recoup_default
   PARTITION OF recoup
   FOR VALUES WITH (MODULUS 1, REMAINDER 0);
 
-CREATE INDEX IF NOT EXISTS recoup_by_recoup_uuid_index
-  ON recoup
-  (recoup_uuid);
-CREATE INDEX IF NOT EXISTS recoup_by_reserve_out_serial_id_index
-  ON recoup
-  (reserve_out_serial_id);
-CREATE INDEX IF NOT EXISTS recoup_by_known_coin_id_index
-  ON recoup
-  (known_coin_id);
+SELECT add_constraints_to_recoup_partition('default');
 
 
-CREATE TABLE IF NOT EXISTS recoup_refresh
-  (recoup_refresh_uuid BIGINT GENERATED BY DEFAULT AS IDENTITY -- UNIQUE
-  ,known_coin_id INT8 NOT NULL -- REFERENCES known_coins (known_coin_id)
-  ,coin_sig BYTEA NOT NULL CHECK(LENGTH(coin_sig)=64)
-  ,coin_blind BYTEA NOT NULL CHECK(LENGTH(coin_blind)=32)
-  ,amount_val INT8 NOT NULL
-  ,amount_frac INT4 NOT NULL
-  ,recoup_timestamp INT8 NOT NULL
-  ,rrc_serial INT8 NOT NULL -- REFERENCES refresh_revealed_coins (rrc_serial) 
ON DELETE CASCADE -- UNIQUE
-  )
-  PARTITION BY HASH (known_coin_id);
+SELECT create_table_recoup_by_reserve();
+
+COMMENT ON TABLE recoup_by_reserve
+  IS 'Information in this table is strictly redundant with that of recoup, but 
saved by a different primary key for fast lookups by reserve_out_serial_id.';
+
+CREATE TABLE IF NOT EXISTS recoup_by_reserve_default
+  PARTITION OF recoup_by_reserve
+  FOR VALUES WITH (MODULUS 1, REMAINDER 0);
+
+CREATE OR REPLACE FUNCTION recoup_insert_trigger()
+  RETURNS trigger
+  LANGUAGE plpgsql
+  AS $$
+BEGIN
+  INSERT INTO recoup_by_reserve
+    (reserve_out_serial_id
+    ,coin_pub)
+  VALUES
+    (NEW.reserve_out_serial_id
+    ,NEW.coin_pub);
+  RETURN NEW;
+END $$;  
+COMMENT ON FUNCTION recoup_insert_trigger()
+  IS 'Replicate recoup inserts into recoup_by_reserve table.';
+
+CREATE TRIGGER recoup_on_insert
+  AFTER INSERT
+   ON recoup
+   FOR EACH ROW EXECUTE FUNCTION recoup_insert_trigger();
+
+
+CREATE OR REPLACE FUNCTION recoup_delete_trigger()
+  RETURNS trigger
+  LANGUAGE plpgsql
+  AS $$
+BEGIN
+  DELETE FROM recoup_by_reserve
+   WHERE reserve_out_serial_id = OLD.reserve_out_serial_id
+     AND coin_pub = OLD.coin_pub;
+  RETURN OLD;
+END $$;  
+COMMENT ON FUNCTION recoup_delete_trigger()
+  IS 'Replicate recoup deletions into recoup_by_reserve table.';
+
+CREATE TRIGGER recoup_on_delete
+  AFTER DELETE
+    ON recoup
+   FOR EACH ROW EXECUTE FUNCTION recoup_delete_trigger();
+
+
+
+SELECT create_table_reserves_out_by_reserve();
+
+COMMENT ON TABLE reserves_out_by_reserve
+  IS 'Information in this table is strictly redundant with that of 
reserves_out, but saved by a different primary key for fast lookups by reserve 
public key/uuid.';
+
+
+CREATE TABLE IF NOT EXISTS reserves_out_by_reserve_default
+  PARTITION OF reserves_out_by_reserve
+  FOR VALUES WITH (MODULUS 1, REMAINDER 0);
+
+CREATE OR REPLACE FUNCTION reserves_out_by_reserve_insert_trigger()
+  RETURNS trigger
+  LANGUAGE plpgsql
+  AS $$
+BEGIN
+  INSERT INTO reserves_out_by_reserve
+    (reserve_uuid
+    ,h_blind_ev)
+  VALUES
+    (NEW.reserve_uuid
+    ,NEW.h_blind_ev);
+  RETURN NEW;
+END $$;  
+COMMENT ON FUNCTION reserves_out_by_reserve_insert_trigger()
+  IS 'Replicate reserve_out inserts into reserve_out_by_reserve table.';
+
+CREATE TRIGGER reserves_out_on_insert
+  AFTER INSERT
+   ON reserves_out
+   FOR EACH ROW EXECUTE FUNCTION reserves_out_by_reserve_insert_trigger();
+
+
+CREATE OR REPLACE FUNCTION reserves_out_by_reserve_delete_trigger()
+  RETURNS trigger
+  LANGUAGE plpgsql
+  AS $$
+BEGIN
+  DELETE FROM reserves_out_by_reserve
+   WHERE reserve_uuid = OLD.reserve_uuid;
+  RETURN OLD;
+END $$;  
+COMMENT ON FUNCTION reserves_out_by_reserve_delete_trigger()
+  IS 'Replicate reserve_out deletions into reserve_out_by_reserve table.';
+
+CREATE TRIGGER reserves_out_on_delete
+  AFTER DELETE
+    ON reserves_out
+   FOR EACH ROW EXECUTE FUNCTION reserves_out_by_reserve_delete_trigger();
+
+
+
+SELECT create_table_recoup_refresh();
+
 COMMENT ON TABLE recoup_refresh
   IS 'Table of coins that originated from a refresh operation and that were 
recouped. Links the (fresh) coin to the melted operation (and thus the old 
coin). A recoup on a refreshed coin credits the old coin and debits the fresh 
coin.';
+COMMENT ON COLUMN recoup_refresh.coin_pub
+  IS 'Refreshed coin of a revoked denomination where the residual value is 
credited to the old coin. Do not CASCADE ON DROP on the coin_pub, as we may 
keep the coin alive!';
 COMMENT ON COLUMN recoup_refresh.known_coin_id
-  IS 'Refreshed coin of a revoked denomination where the residual value is 
credited to the old coin. Do not CASCADE ON DROP on the known_coin_id, as we 
may keep the coin alive!';
+  IS 'FIXME: (To be) used for garbage collection (in the future)';
 COMMENT ON COLUMN recoup_refresh.rrc_serial
   IS 'Link to the refresh operation. Also identifies the h_blind_ev of the 
recouped coin (as h_coin_ev).';
 COMMENT ON COLUMN recoup_refresh.coin_blind
   IS 'Denomination blinding key used when creating the blinded coin from the 
planchet. Secret revealed during the recoup to provide the linkage between the 
coin and the refresh operation.';
+
 CREATE TABLE IF NOT EXISTS recoup_refresh_default
   PARTITION OF recoup_refresh
   FOR VALUES WITH (MODULUS 1, REMAINDER 0);
 
-CREATE INDEX IF NOT EXISTS recoup_refresh_by_recoup_refresh_uuid_index
-  ON recoup_refresh
-  (recoup_refresh_uuid);
-CREATE INDEX IF NOT EXISTS recoup_refresh_by_rrc_serial_index
-  ON recoup_refresh
-  (rrc_serial);
-CREATE INDEX IF NOT EXISTS recoup_refresh_by_known_coin_id_index
-  ON recoup_refresh
-  (known_coin_id);
-
-
-CREATE TABLE IF NOT EXISTS prewire
-  (prewire_uuid BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY
-  ,wire_method TEXT NOT NULL
-  ,finished BOOLEAN NOT NULL DEFAULT false
-  ,failed BOOLEAN NOT NULL DEFAULT false
-  ,buf BYTEA NOT NULL
-  )
-  PARTITION BY HASH (prewire_uuid);
+SELECT add_constraints_to_recoup_refresh_partition('default');
+
+
+SELECT create_table_prewire();
+
 COMMENT ON TABLE prewire
   IS 'pre-commit data for wire transfers we are about to execute';
 COMMENT ON COLUMN prewire.failed
@@ -718,21 +814,11 @@ COMMENT ON COLUMN prewire.finished
   IS 'set to TRUE once bank confirmed receiving the wire transfer request';
 COMMENT ON COLUMN prewire.buf
   IS 'serialized data to send to the bank to execute the wire transfer';
+
 CREATE TABLE IF NOT EXISTS prewire_default
   PARTITION OF prewire
   FOR VALUES WITH (MODULUS 1, REMAINDER 0);
 
-CREATE INDEX IF NOT EXISTS prewire_by_finished_index
-  ON prewire
-  (finished);
-COMMENT ON INDEX prewire_by_finished_index
-  IS 'for gc_prewire';
--- FIXME: find a way to combine these two indices?
-CREATE INDEX IF NOT EXISTS prewire_by_failed_finished_index
-  ON prewire
-  (failed,finished);
-COMMENT ON INDEX prewire_by_failed_finished_index
-  IS 'for wire_prepare_data_get';
 
 
 CREATE TABLE IF NOT EXISTS wire_accounts
@@ -755,13 +841,8 @@ COMMENT ON COLUMN wire_accounts.last_change
 --            and is of no concern to the auditor
 
 
-CREATE TABLE IF NOT EXISTS cs_nonce_locks
-  (cs_nonce_lock_serial_id BIGINT GENERATED BY DEFAULT AS IDENTITY -- UNIQUE
-  ,nonce BYTEA PRIMARY KEY CHECK (LENGTH(nonce)=32)
-  ,op_hash BYTEA NOT NULL CHECK (LENGTH(op_hash)=64)
-  ,max_denomination_serial INT8 NOT NULL
-  )
-  PARTITION BY HASH (nonce);
+SELECT create_table_cs_nonce_locks();
+
 COMMENT ON TABLE cs_nonce_locks
   IS 'ensures a Clause Schnorr client nonce is locked for use with an 
operation identified by a hash';
 COMMENT ON COLUMN cs_nonce_locks.nonce
@@ -771,6 +852,12 @@ COMMENT ON COLUMN cs_nonce_locks.op_hash
 COMMENT ON COLUMN cs_nonce_locks.max_denomination_serial
   IS 'Maximum number of a CS denomination serial the nonce could be used with, 
for GC';
 
+CREATE TABLE IF NOT EXISTS cs_nonce_locks_default
+  PARTITION OF cs_nonce_locks
+  FOR VALUES WITH (MODULUS 1, REMAINDER 0);
+
+SELECT add_constraints_to_cs_nonce_locks_partition('default');
+
 
 CREATE TABLE IF NOT EXISTS work_shards
   (shard_serial_id BIGINT GENERATED BY DEFAULT AS IDENTITY UNIQUE
@@ -835,6 +922,344 @@ CREATE INDEX IF NOT EXISTS 
revolving_work_shards_by_job_name_active_last_attempt
   ,last_attempt
   );
 
+-- Tables for P2P payments
+
+CREATE TABLE IF NOT EXISTS partners
+  (partner_serial_id BIGSERIAL UNIQUE
+  ,partner_master_pub BYTEA NOT NULL CHECK(LENGTH(partner_master_pub)=32)
+  ,start_date INT8 NOT NULL
+  ,end_date INT8 NOT NULL
+  ,wad_frequency INT8 NOT NULL
+  ,wad_fee_val INT8 NOT NULL
+  ,wad_fee_frac INT4 NOT NULL
+  ,master_sig BYTEA NOT NULL CHECK (LENGTH(master_sig)=64)
+  ,partner_base_url TEXT NOT NULL
+  );
+COMMENT ON TABLE partners
+  IS 'exchanges we do wad transfers to';
+COMMENT ON COLUMN partners.partner_master_pub
+  IS 'offline master public key of the partner';
+COMMENT ON COLUMN partners.start_date
+  IS 'starting date of the partnership';
+COMMENT ON COLUMN partners.end_date
+  IS 'end date of the partnership';
+COMMENT ON COLUMN partners.wad_frequency
+  IS 'how often do we promise to do wad transfers';
+COMMENT ON COLUMN partners.wad_fee_val
+  IS 'how high is the fee for a wallet to be added to a wad to this partner';
+COMMENT ON COLUMN partners.partner_base_url
+  IS 'base URL of the REST API for this partner';
+COMMENT ON COLUMN partners.master_sig
+  IS 'signature of our master public key affirming the partnership, of purpose 
TALER_SIGNATURE_MASTER_PARTNER_DETAILS';
+
+
+CREATE TABLE IF NOT EXISTS purse_requests
+  (purse_deposit_serial_id BIGSERIAL UNIQUE
+  ,purse_pub BYTEA NOT NULL CHECK (LENGTH(purse_pub)=32)
+  ,merge_pub BYTEA NOT NULL CHECK (LENGTH(merge_pub)=32)
+  ,purse_expiration INT8 NOT NULL
+  ,h_contract_terms BYTEA NOT NULL CHECK (LENGTH(h_contract_terms)=64)
+  ,age_limit INT4 NOT NULL
+  ,amount_with_fee_val INT8 NOT NULL
+  ,amount_with_fee_frac INT4 NOT NULL
+  ,balance_val INT8 NOT NULL DEFAULT (0)
+  ,balance_frac INT4 NOT NULL DEFAULT (0)
+  ,purse_sig BYTEA NOT NULL CHECK(LENGTH(purse_sig)=64)
+  ,PRIMARY KEY (purse_pub)
+  ); -- partition by purse_pub
+COMMENT ON TABLE purse_requests
+  IS 'Requests establishing purses, associating them with a contract but 
without a target reserve';
+COMMENT ON COLUMN purse_requests.purse_pub
+  IS 'Public key of the purse';
+COMMENT ON COLUMN purse_requests.purse_expiration
+  IS 'When the purse is set to expire';
+COMMENT ON COLUMN purse_requests.h_contract_terms
+  IS 'Hash of the contract the parties are to agree to';
+COMMENT ON COLUMN purse_requests.amount_with_fee_val
+  IS 'Total amount expected to be in the purse';
+COMMENT ON COLUMN purse_requests.balance_val
+  IS 'Total amount actually in the purse';
+COMMENT ON COLUMN purse_requests.purse_sig
+  IS 'Signature of the purse affirming the purse parameters, of type 
TALER_SIGNATURE_PURSE_REQUEST';
+
+-- FIXME: create purse_by_merge materialized index table
+-- for merge_pub => purse_pub mapping!
+
+
+CREATE TABLE IF NOT EXISTS purse_merges
+  (purse_merge_request_serial_id BIGSERIAL -- UNIQUE
+  ,partner_serial_id INT8 REFERENCES partners(partner_serial_id) ON DELETE 
CASCADE
+  ,reserve_pub BYTEA NOT NULL CHECK(length(reserve_pub)=32)--REFERENCES 
reserves (reserve_pub) ON DELETE CASCADE
+  ,purse_pub BYTEA NOT NULL CHECK (LENGTH(purse_pub)=32) --REFERENCES 
purse_requests (purse_pub) ON DELETE CASCADE
+  ,merge_sig BYTEA NOT NULL CHECK (LENGTH(merge_sig)=64)
+  ,merge_timestamp INT8 NOT NULL
+  ,PRIMARY KEY (purse_pub)
+  ); -- partition by purse_pub; plus materialized index by reserve_pub!
+COMMENT ON TABLE purse_merges
+  IS 'Merge requests where a purse-owner requested merging the purse into the 
account';
+COMMENT ON COLUMN purse_merges.partner_serial_id
+  IS 'identifies the partner exchange, NULL in case the target reserve lives 
at this exchange';
+COMMENT ON COLUMN purse_merges.reserve_pub
+  IS 'public key of the target reserve';
+COMMENT ON COLUMN purse_merges.purse_pub
+  IS 'public key of the purse';
+COMMENT ON COLUMN purse_merges.merge_sig
+  IS 'signature by the purse private key affirming the merge, of type 
TALER_SIGNATURE_WALLET_PURSE_MERGE';
+COMMENT ON COLUMN purse_merges.merge_timestamp
+  IS 'when was the merge message signed';
+CREATE INDEX IF NOT EXISTS purse_merges_reserve_pub
+  ON purse_merges (reserve_pub);
+COMMENT ON INDEX purse_merges_reserve_pub
+  IS 'needed in reserve history computation';
+
+
+CREATE TABLE IF NOT EXISTS account_mergers
+  (account_merge_request_serial_id BIGSERIAL -- UNIQUE
+  ,reserve_pub BYTEA NOT NULL CHECK (LENGTH(reserve_pub)=32) -- REFERENCES 
reserves (reserve_pub) ON DELETE CASCADE
+  ,reserve_sig BYTEA NOT NULL CHECK (LENGTH(reserve_sig)=64)
+  ,purse_pub BYTEA NOT NULL CHECK (LENGTH(purse_pub)=32) -- REFERENCES 
purse_requests (purse_pub)
+  ,PRIMARY KEY (reserve_pub)
+  ); -- partition by purse_pub; plus materialized index by reserve_pub!
+COMMENT ON TABLE account_mergers
+  IS 'Merge requests where a purse- and account-owner requested merging the 
purse into the account';
+COMMENT ON COLUMN account_mergers.reserve_pub
+  IS 'public key of the target reserve';
+COMMENT ON COLUMN account_mergers.purse_pub
+  IS 'public key of the purse';
+COMMENT ON COLUMN account_mergers.reserve_sig
+  IS 'signature by the reserve private key affirming the merge, of type 
TALER_SIGNATURE_WALLET_ACCOUNT_MERGE';
+
+CREATE INDEX IF NOT EXISTS account_mergers_purse_pub
+  ON account_mergers (purse_pub);
+COMMENT ON INDEX account_mergers_purse_pub
+  IS 'needed when checking for a purse merge status';
+  
+
+CREATE TABLE IF NOT EXISTS contracts
+  (contract_serial_id BIGSERIAL UNIQUE
+  ,purse_pub BYTEA NOT NULL CHECK (LENGTH(purse_pub)=32)
+  ,pub_ckey BYTEA NOT NULL CHECK (LENGTH(pub_ckey)=32)
+  ,e_contract BYTEA NOT NULL
+  ,purse_expiration INT8 NOT NULL
+  ,PRIMARY KEY (purse_pub)
+  ); -- partition by purse_pub
+COMMENT ON TABLE contracts
+  IS 'encrypted contracts associated with purses';
+COMMENT ON COLUMN contracts.purse_pub
+  IS 'public key of the purse that the contract is associated with';
+COMMENT ON COLUMN contracts.pub_ckey
+  IS 'Public ECDH key used to encrypt the contract, to be used with the purse 
private key for decryption';
+COMMENT ON COLUMN contracts.e_contract
+  IS 'AES-GCM encrypted contract terms (contains gzip compressed JSON after 
decryption)';
+
+CREATE TABLE IF NOT EXISTS history_requests
+  (reserve_pub BYTEA NOT NULL CHECK (LENGTH(reserve_pub)=32) REFERENCES 
reserves(reserve_pub) ON DELETE CASCADE
+  ,request_timestamp INT8 NOT NULL
+  ,reserve_sig BYTEA NOT NULL CHECK (LENGTH(reserve_sig)=64)
+  ,history_fee_val INT8 NOT NULL
+  ,history_fee_frac INT4 NOT NULL
+  ,PRIMARY KEY (reserve_pub,request_timestamp)
+  ); -- partition by reserve_pub
+COMMENT ON TABLE history_requests
+  IS 'Paid history requests issued by a client against a reserve';
+COMMENT ON COLUMN history_requests.request_timestamp
+  IS 'When was the history request made';
+COMMENT ON COLUMN history_requests.reserve_sig
+  IS 'Signature approving payment for the history request';
+COMMENT ON COLUMN history_requests.history_fee_val
+  IS 'History fee approved by the signature';
+
+CREATE TABLE IF NOT EXISTS close_requests
+  (reserve_pub BYTEA NOT NULL CHECK (LENGTH(reserve_pub)=32) REFERENCES 
reserves(reserve_pub) ON DELETE CASCADE
+  ,close_timestamp INT8 NOT NULL
+  ,reserve_sig BYTEA NOT NULL CHECK (LENGTH(reserve_sig)=64)
+  ,close_val INT8 NOT NULL
+  ,close_frac INT4 NOT NULL
+  ,PRIMARY KEY (reserve_pub,close_timestamp)
+  ); -- partition by reserve_pub
+COMMENT ON TABLE close_requests
+  IS 'Explicit requests by a reserve owner to close a reserve immediately';
+COMMENT ON COLUMN close_requests.close_timestamp
+  IS 'When the request was created by the client';
+COMMENT ON COLUMN close_requests.reserve_sig
+  IS 'Signature affirming that the reserve is to be closed';
+COMMENT ON COLUMN close_requests.close_val
+  IS 'Balance of the reserve at the time of closing, to be wired to the 
associated bank account (minus the closing fee)';
+
+
+CREATE TABLE IF NOT EXISTS purse_deposits
+  (purse_deposit_serial_id BIGSERIAL UNIQUE
+  ,partner_serial_id INT8 REFERENCES partners(partner_serial_id) ON DELETE 
CASCADE
+  ,purse_pub BYTEA NOT NULL CHECK (LENGTH(purse_pub)=32)
+  ,coin_pub BYTEA NOT NULL REFERENCES known_coins (coin_pub) ON DELETE CASCADE
+  ,amount_with_fee_val INT8 NOT NULL
+  ,amount_with_fee_frac INT4 NOT NULL
+  ,coin_sig BYTEA NOT NULL CHECK(LENGTH(coin_sig)=64)
+  ,PRIMARY KEY (purse_pub,coin_pub)
+  ); -- partition by purse_pub, plus a materialized index by coin_pub!
+COMMENT ON TABLE purse_deposits
+  IS 'Requests depositing coins into a purse';
+COMMENT ON COLUMN purse_deposits.partner_serial_id
+  IS 'identifies the partner exchange, NULL in case the target purse lives at 
this exchange';
+COMMENT ON COLUMN purse_deposits.purse_pub
+  IS 'Public key of the purse';
+COMMENT ON COLUMN purse_deposits.coin_pub
+  IS 'Public key of the coin being deposited';
+COMMENT ON COLUMN purse_deposits.amount_with_fee_val
+  IS 'Total amount being deposited';
+COMMENT ON COLUMN purse_deposits.coin_sig
+  IS 'Signature of the coin affirming the deposit into the purse, of type 
TALER_SIGNATURE_PURSE_DEPOSIT';
+
+CREATE TABLE IF NOT EXISTS wads_out
+  (wad_out_serial_id BIGSERIAL UNIQUE
+  ,wad_id BYTEA PRIMARY KEY CHECK (LENGTH(wad_id)=24)
+  ,partner_serial_id INT8 NOT NULL REFERENCES partners(partner_serial_id) ON 
DELETE CASCADE
+  ,amount_val INT8 NOT NULL
+  ,amount_frac INT4 NOT NULL
+  ,execution_time INT8 NOT NULL
+  ); -- partition by wad_id
+COMMENT ON TABLE wads_out
+  IS 'Wire transfers made to another exchange to transfer purse funds';
+COMMENT ON COLUMN wads_out.wad_id
+  IS 'Unique identifier of the wad, part of the wire transfer subject';
+COMMENT ON COLUMN wads_out.partner_serial_id
+  IS 'target exchange of the wad';
+COMMENT ON COLUMN wads_out.amount_val
+  IS 'Amount that was wired';
+COMMENT ON COLUMN wads_out.execution_time
+  IS 'Time when the wire transfer was scheduled';
+
+CREATE TABLE IF NOT EXISTS wad_out_entries
+  (wad_out_entry_serial_id BIGSERIAL UNIQUE
+  ,wad_out_serial_id INT8 REFERENCES wads_out (wad_out_serial_id) ON DELETE 
CASCADE
+  ,reserve_pub BYTEA NOT NULL CHECK(LENGTH(reserve_pub)=32)
+  ,purse_pub BYTEA PRIMARY KEY CHECK(LENGTH(purse_pub)=32)
+  ,h_contract BYTEA NOT NULL CHECK(LENGTH(h_contract)=64)
+  ,purse_expiration INT8 NOT NULL
+  ,merge_timestamp INT8 NOT NULL
+  ,amount_with_fee_val INT8 NOT NULL
+  ,amount_with_fee_frac INT4 NOT NULL
+  ,wad_fee_val INT8 NOT NULL
+  ,wad_fee_frac INT4 NOT NULL
+  ,deposit_fees_val INT8 NOT NULL
+  ,deposit_fees_frac INT4 NOT NULL
+  ,reserve_sig BYTEA NOT NULL CHECK (LENGTH(reserve_sig)=64)
+  ,purse_sig BYTEA NOT NULL CHECK (LENGTH(purse_sig)=64)
+  ); -- partition by purse_pub? do we need a materialized index by reserve_pub?
+CREATE INDEX IF NOT EXISTS wad_out_entries_index_by_wad
+  ON wad_out_entries (wad_out_serial_id);
+COMMENT ON TABLE wad_out_entries
+  IS 'Purses combined into a wad';
+COMMENT ON COLUMN wad_out_entries.wad_out_serial_id
+  IS 'Wad the purse was part of';
+COMMENT ON COLUMN wad_out_entries.reserve_pub
+  IS 'Target reserve for the purse';
+COMMENT ON COLUMN wad_out_entries.purse_pub
+  IS 'Public key of the purse';
+COMMENT ON COLUMN wad_out_entries.h_contract
+  IS 'Hash of the contract associated with the purse';
+COMMENT ON COLUMN wad_out_entries.purse_expiration
+  IS 'Time when the purse expires';
+COMMENT ON COLUMN wad_out_entries.merge_timestamp
+  IS 'Time when the merge was approved';
+COMMENT ON COLUMN wad_out_entries.amount_with_fee_val
+  IS 'Total amount in the purse';
+COMMENT ON COLUMN wad_out_entries.wad_fee_val
+  IS 'Wat fee charged to the purse';
+COMMENT ON COLUMN wad_out_entries.deposit_fees_val
+  IS 'Total deposit fees charged to the purse';
+COMMENT ON COLUMN wad_out_entries.reserve_sig
+  IS 'Signature by the receiving reserve, of purpose 
TALER_SIGNATURE_ACCOUNT_MERGE';
+COMMENT ON COLUMN wad_out_entries.purse_sig
+  IS 'Signature by the purse of purpose TALER_SIGNATURE_PURSE_MERGE';
+
+CREATE TABLE IF NOT EXISTS wads_in
+  (wad_in_serial_id BIGSERIAL UNIQUE
+  ,wad_id BYTEA PRIMARY KEY CHECK (LENGTH(wad_id)=24)
+  ,origin_exchange_url TEXT NOT NULL
+  ,amount_val INT8 NOT NULL
+  ,amount_frac INT4 NOT NULL
+  ,arrival_time INT8 NOT NULL
+  ,UNIQUE (wad_id, origin_exchange_url)
+  ); -- partition by wad_id
+COMMENT ON TABLE wads_in
+  IS 'Incoming exchange-to-exchange wad wire transfers';
+COMMENT ON COLUMN wads_in.wad_id
+  IS 'Unique identifier of the wad, part of the wire transfer subject';
+COMMENT ON COLUMN wads_in.origin_exchange_url
+  IS 'Base URL of the originating URL, also part of the wire transfer subject';
+COMMENT ON COLUMN wads_in.amount_val
+  IS 'Actual amount that was received by our exchange';
+COMMENT ON COLUMN wads_in.arrival_time
+  IS 'Time when the wad was received';
+
+CREATE TABLE IF NOT EXISTS wad_in_entries
+  (wad_in_entry_serial_id BIGSERIAL UNIQUE
+  ,wad_in_serial_id INT8 REFERENCES wads_in (wad_in_serial_id) ON DELETE 
CASCADE
+  ,reserve_pub BYTEA NOT NULL CHECK(LENGTH(reserve_pub)=32)
+  ,purse_pub BYTEA PRIMARY KEY CHECK(LENGTH(purse_pub)=32)
+  ,h_contract BYTEA NOT NULL CHECK(LENGTH(h_contract)=64)
+  ,purse_expiration INT8 NOT NULL
+  ,merge_timestamp INT8 NOT NULL
+  ,amount_with_fee_val INT8 NOT NULL
+  ,amount_with_fee_frac INT4 NOT NULL
+  ,wad_fee_val INT8 NOT NULL
+  ,wad_fee_frac INT4 NOT NULL
+  ,deposit_fees_val INT8 NOT NULL
+  ,deposit_fees_frac INT4 NOT NULL
+  ,reserve_sig BYTEA NOT NULL CHECK (LENGTH(reserve_sig)=64)
+  ,purse_sig BYTEA NOT NULL CHECK (LENGTH(purse_sig)=64)
+  ); -- partition by purse or reserve? likely need both (so extra table?)
+COMMENT ON TABLE wad_in_entries
+  IS 'list of purses aggregated in a wad according to the sending exchange';
+COMMENT ON COLUMN wad_in_entries.wad_in_serial_id
+  IS 'wad for which the given purse was included in the aggregation';
+COMMENT ON COLUMN wad_in_entries.reserve_pub
+  IS 'target account of the purse (must be at the local exchange)';
+COMMENT ON COLUMN wad_in_entries.purse_pub
+  IS 'public key of the purse that was merged';
+COMMENT ON COLUMN wad_in_entries.h_contract
+  IS 'hash of the contract terms of the purse';
+COMMENT ON COLUMN wad_in_entries.purse_expiration
+  IS 'Time when the purse was set to expire';
+COMMENT ON COLUMN wad_in_entries.merge_timestamp
+  IS 'Time when the merge was approved';
+COMMENT ON COLUMN wad_in_entries.amount_with_fee_val
+  IS 'Total amount in the purse';
+COMMENT ON COLUMN wad_in_entries.wad_fee_val
+  IS 'Total wad fees paid by the purse';
+COMMENT ON COLUMN wad_in_entries.deposit_fees_val
+  IS 'Total deposit fees paid when depositing coins into the purse';
+COMMENT ON COLUMN wad_in_entries.reserve_sig
+  IS 'Signature by the receiving reserve, of purpose 
TALER_SIGNATURE_ACCOUNT_MERGE';
+COMMENT ON COLUMN wad_in_entries.purse_sig
+  IS 'Signature by the purse of purpose TALER_SIGNATURE_PURSE_MERGE';
+CREATE INDEX IF NOT EXISTS wad_in_entries_wad_in_serial
+  ON wad_in_entries (wad_in_serial_id);
+CREATE INDEX IF NOT EXISTS wad_in_entries_reserve_pub
+  ON wad_in_entries (reserve_pub);
+COMMENT ON INDEX wad_in_entries_wad_in_serial
+  IS 'needed to lookup all transfers associated with a wad';
+COMMENT ON INDEX wad_in_entries_reserve_pub
+  IS 'needed to compute reserve history';
+
+CREATE TABLE IF NOT EXISTS partner_accounts
+  (payto_uri VARCHAR PRIMARY KEY
+  ,partner_serial_id INT8 REFERENCES partners(partner_serial_id) ON DELETE 
CASCADE
+  ,partner_master_sig BYTEA CHECK (LENGTH(partner_master_sig)=64)
+  ,last_seen INT8 NOT NULL
+  );
+CREATE INDEX IF NOT EXISTS partner_accounts_index_by_partner_and_time
+  ON partner_accounts (partner_serial_id,last_seen);
+COMMENT ON TABLE partner_accounts
+  IS 'Table with bank accounts of the partner exchange. Entries never expire 
as we need to remember the signature for the auditor.';
+COMMENT ON COLUMN partner_accounts.payto_uri
+  IS 'payto URI (RFC 8905) with the bank account of the partner exchange.';
+COMMENT ON COLUMN partner_accounts.partner_master_sig
+  IS 'Signature of purpose TALER_SIGNATURE_MASTER_WIRE_DETAILS by the partner 
master public key';
+COMMENT ON COLUMN partner_accounts.last_seen
+  IS 'Last time we saw this account as being active at the partner exchange. 
Used to select the most recent entry, and to detect when we should check 
again.';
+
 
 -- Stored procedures
 
@@ -870,7 +1295,7 @@ BEGIN
 --         reserves_out (INSERT, with CONFLICT detection) by wih
 --         reserves by reserve_pub (UPDATE)
 --         reserves_in by reserve_pub (SELECT)
---         wire_targets by wire_target_serial_id
+--         wire_targets by wire_target_h_payto
 
 SELECT denominations_serial
   INTO denom_serial
@@ -1027,12 +1452,12 @@ END IF;
 -- this reserve. FIXME: likely not adequate for reserves that got P2P 
transfers!
 SELECT
    kyc_ok
-  ,wire_source_serial_id
+  ,wire_target_serial_id
   INTO
    kycok
   ,account_uuid
   FROM reserves_in
-  JOIN wire_targets ON (wire_source_serial_id = wire_target_serial_id)
+  JOIN wire_targets ON (wire_source_h_payto = wire_target_h_payto)
  WHERE reserve_pub=rpub
  LIMIT 1; -- limit 1 should not be required (without p2p transfers)
 
@@ -1097,8 +1522,8 @@ IF EXISTS (
   SELECT 1
     FROM information_Schema.constraint_column_usage
    WHERE table_name='wire_out'
-     AND constraint_name='wire_out_ref') 
-THEN 
+     AND constraint_name='wire_out_ref')
+THEN
   SET CONSTRAINTS wire_out_ref DEFERRED;
 END IF;
 
@@ -1135,7 +1560,7 @@ DECLARE
 BEGIN
 -- Shards: INSERT extension_details (by extension_details_serial_id)
 --         INSERT wire_targets (by h_payto), on CONFLICT DO NOTHING;
---         INSERT deposits (by shard + known_coin_id, merchant_pub, 
h_contract_terms), ON CONFLICT DO NOTHING;
+--         INSERT deposits (by coin_pub, shard), ON CONFLICT DO NOTHING;
 --         UPDATE known_coins (by coin_pub)
 
 IF NOT NULL in_extension_details
@@ -1151,12 +1576,12 @@ END IF;
 
 
 INSERT INTO wire_targets
-  (h_payto
+  (wire_target_h_payto
   ,payto_uri)
   VALUES
   (in_h_payto
   ,in_receiver_wire_account)
-ON CONFLICT DO NOTHING -- for CONFLICT ON (h_payto)
+ON CONFLICT DO NOTHING -- for CONFLICT ON (wire_target_h_payto)
   RETURNING wire_target_serial_id INTO wtsi;
 
 IF NOT FOUND
@@ -1164,12 +1589,13 @@ THEN
   SELECT wire_target_serial_id
   INTO wtsi
   FROM wire_targets
-  WHERE h_payto=in_h_payto;
+  WHERE wire_target_h_payto=in_h_payto;
 END IF;
 
 
 INSERT INTO deposits
   (shard
+  ,coin_pub
   ,known_coin_id
   ,amount_with_fee_val
   ,amount_with_fee_frac
@@ -1181,12 +1607,13 @@ INSERT INTO deposits
   ,h_contract_terms
   ,coin_sig
   ,wire_salt
-  ,wire_target_serial_id
+  ,wire_target_h_payto
   ,extension_blocked
   ,extension_details_serial_id
   )
   VALUES
   (in_shard
+  ,in_coin_pub
   ,in_known_coin_id
   ,in_amount_with_fee_val
   ,in_amount_with_fee_frac
@@ -1198,7 +1625,7 @@ INSERT INTO deposits
   ,in_h_contract_terms
   ,in_coin_sig
   ,in_wire_salt
-  ,wtsi
+  ,in_h_payto
   ,in_extension_blocked
   ,xdi)
   ON CONFLICT DO NOTHING;
@@ -1208,19 +1635,18 @@ THEN
   -- Idempotency check: see if an identical record exists.
   -- Note that by checking 'coin_sig', we implicitly check
   -- identity over everything that the signature covers.
-  -- We do select over merchant_pub and h_contract_terms
+  -- We do select over merchant_pub and wire_target_h_payto
   -- primarily here to maximally use the existing index.
   SELECT
      exchange_timestamp
    INTO
      out_exchange_timestamp
    FROM deposits
-   WHERE
-     shard=in_shard AND
-     known_coin_id=in_known_coin_id AND
-     merchant_pub=in_merchant_pub AND
-     h_contract_terms=in_h_contract_terms AND
-     coin_sig=in_coin_sig;
+   WHERE shard=in_shard
+     AND merchant_pub=in_merchant_pub
+     AND wire_target_h_payto=in_h_payto
+     AND coin_pub=in_coin_pub
+     AND coin_sig=in_coin_sig;
 
   IF NOT FOUND
   THEN
@@ -1284,7 +1710,6 @@ CREATE OR REPLACE FUNCTION exchange_do_melt(
   IN in_old_coin_pub BYTEA,
   IN in_old_coin_sig BYTEA,
   IN in_known_coin_id INT8, -- not used, but that's OK
-  IN in_h_age_commitment BYTEA,
   IN in_noreveal_index INT4,
   IN in_zombie_required BOOLEAN,
   OUT out_balance_ok BOOLEAN,
@@ -1307,7 +1732,6 @@ INSERT INTO refresh_commitments
   ,old_coin_sig
   ,amount_with_fee_val
   ,amount_with_fee_frac
-  ,h_age_commitment
   ,noreveal_index
   )
   VALUES
@@ -1316,7 +1740,6 @@ INSERT INTO refresh_commitments
   ,in_old_coin_sig
   ,in_amount_with_fee_val
   ,in_amount_with_fee_frac
-  ,in_h_age_commitment
   ,in_noreveal_index)
   ON CONFLICT DO NOTHING;
 
@@ -1482,7 +1905,7 @@ DECLARE
 DECLARE
   deposit_frac INT8; -- amount that was originally deposited
 BEGIN
--- Shards: SELECT deposits (by shard, known_coin_id,h_contract_terms, 
merchant_pub)
+-- Shards: SELECT deposits (coin_pub, shard, h_contract_terms, merchant_pub)
 --         INSERT refunds (by deposit_serial_id, rtransaction_id) ON CONFLICT 
DO NOTHING
 --         SELECT refunds (by deposit_serial_id)
 --         UPDATE known_coins (by coin_pub)
@@ -1498,10 +1921,10 @@ INTO
   ,deposit_frac
   ,out_gone
 FROM deposits
-WHERE shard=in_deposit_shard
-  AND known_coin_id=in_known_coin_id
-  AND h_contract_terms=in_h_contract_terms
-  AND merchant_pub=in_merchant_pub;
+ WHERE coin_pub=in_coin_pub
+  AND shard=in_deposit_shard
+  AND merchant_pub=in_merchant_pub
+  AND h_contract_terms=in_h_contract_terms;
 
 IF NOT FOUND
 THEN
@@ -1516,6 +1939,7 @@ END IF;
 
 INSERT INTO refunds
   (deposit_serial_id
+  ,shard
   ,merchant_sig
   ,rtransaction_id
   ,amount_with_fee_val
@@ -1523,6 +1947,7 @@ INSERT INTO refunds
   )
   VALUES
   (dsi
+  ,in_deposit_shard
   ,in_merchant_sig
   ,in_rtransaction_id
   ,in_amount_with_fee_val
@@ -1538,11 +1963,11 @@ THEN
   -- primarily here to maximally use the existing index.
    PERFORM
    FROM refunds
-   WHERE
-     deposit_serial_id=dsi AND
-     rtransaction_id=in_rtransaction_id AND
-     amount_with_fee_val=in_amount_with_fee_val AND
-     amount_with_fee_frac=in_amount_with_fee_frac;
+   WHERE shard=in_deposit_shard
+     AND deposit_serial_id=dsi
+     AND rtransaction_id=in_rtransaction_id
+     AND amount_with_fee_val=in_amount_with_fee_val
+     AND amount_with_fee_frac=in_amount_with_fee_frac;
 
   IF NOT FOUND
   THEN
@@ -1581,8 +2006,8 @@ SELECT
    tmp_val
   ,tmp_frac
   FROM refunds
-  WHERE
-    deposit_serial_id=dsi;
+  WHERE shard=in_deposit_shard
+    AND deposit_serial_id=dsi;
 IF tmp_val IS NULL
 THEN
   RAISE NOTICE 'failed to sum up existing refunds';
@@ -1669,10 +2094,10 @@ DECLARE
   tmp_frac INT8; -- amount recouped
 BEGIN
 -- Shards: SELECT known_coins (by coin_pub)
---         SELECT recoup (by known_coin_id)
+--         SELECT recoup      (by coin_pub)
 --         UPDATE known_coins (by coin_pub)
 --         UPDATE reserves (by reserve_pub)
---         INSERT recoup (by known_coin_id)
+--         INSERT recoup      (by coin_pub)
 
 out_internal_failure=FALSE;
 
@@ -1702,7 +2127,7 @@ THEN
   INTO
     out_recoup_timestamp
     FROM recoup
-    WHERE known_coin_id=in_known_coin_id;
+    WHERE coin_pub=in_coin_pub;
 
   out_recoup_ok=FOUND;
   RETURN;
@@ -1747,7 +2172,7 @@ END IF;
 
 
 INSERT INTO recoup
-  (known_coin_id
+  (coin_pub
   ,coin_sig
   ,coin_blind
   ,amount_val
@@ -1756,7 +2181,7 @@ INSERT INTO recoup
   ,reserve_out_serial_id
   )
 VALUES
-  (in_known_coin_id
+  (in_coin_pub
   ,in_coin_sig
   ,in_coin_blind
   ,tmp_val
@@ -1798,9 +2223,9 @@ DECLARE
 BEGIN
 
 -- Shards: UPDATE known_coins (by coin_pub)
---         SELECT recoup_refresh (by known_coin_id)
+--         SELECT recoup_refresh (by coin_pub)
 --         UPDATE known_coins (by coin_pub)
---         INSERT recoup_refresh (by known_coin_id)
+--         INSERT recoup_refresh (by coin_pub)
 
 
 out_internal_failure=FALSE;
@@ -1831,7 +2256,7 @@ THEN
     INTO
       out_recoup_timestamp
     FROM recoup_refresh
-    WHERE known_coin_id=in_known_coin_id;
+    WHERE coin_pub=in_coin_pub;
   out_recoup_ok=FOUND;
   RETURN;
 END IF;
@@ -1872,7 +2297,8 @@ END IF;
 
 
 INSERT INTO recoup_refresh
-  (known_coin_id
+  (coin_pub
+  ,known_coin_id
   ,coin_sig
   ,coin_blind
   ,amount_val
@@ -1881,7 +2307,8 @@ INSERT INTO recoup_refresh
   ,rrc_serial
   )
 VALUES
-  (in_known_coin_id
+  (in_coin_pub
+  ,in_known_coin_id
   ,in_coin_sig
   ,in_coin_blind
   ,tmp_val
@@ -1942,7 +2369,7 @@ SELECT
 
 DELETE FROM recoup
   WHERE reserve_out_serial_id < reserve_out_min;
-
+-- FIXME: recoup_refresh lacks GC!
 
 SELECT
      reserve_uuid
@@ -1955,7 +2382,8 @@ SELECT
 DELETE FROM reserves_out
   WHERE reserve_uuid < reserve_uuid_min;
 
-
+-- FIXME: this query will be horribly slow;
+-- need to find another way to formulate it...
 DELETE FROM denominations
   WHERE expire_legal < in_now
     AND denominations_serial NOT IN
@@ -1964,14 +2392,14 @@ DELETE FROM denominations
     AND denominations_serial NOT IN
       (SELECT DISTINCT denominations_serial
          FROM known_coins
-        WHERE known_coin_id IN
-          (SELECT DISTINCT known_coin_id
+        WHERE coin_pub IN
+          (SELECT DISTINCT coin_pub
              FROM recoup))
     AND denominations_serial NOT IN
       (SELECT DISTINCT denominations_serial
          FROM known_coins
-        WHERE known_coin_id IN
-          (SELECT DISTINCT known_coin_id
+        WHERE coin_pub IN
+          (SELECT DISTINCT coin_pub
              FROM recoup_refresh));
 
 SELECT
@@ -2027,5 +2455,86 @@ DELETE FROM cs_nonce_locks
 END $$;
 
 
+
+
+
+
+
+
+
+CREATE OR REPLACE FUNCTION exchange_do_purse_deposit(
+  IN in_purse_pub BYTEA,
+  IN in_amount_with_fee_val INT8,
+  IN in_amount_with_fee_frac INT4,
+  IN in_coin_pub BYTEA,
+  IN in_coin_sig BYTEA,
+  OUT out_balance_ok BOOLEAN,
+  OUT out_conflict BOOLEAN)
+LANGUAGE plpgsql
+AS $$
+BEGIN
+  -- FIXME
+END $$;
+
+
+CREATE OR REPLACE FUNCTION exchange_do_purse_merge(
+  IN in_purse_pub BYTEA,
+  IN in_merge_sig BYTEA,
+  IN in_merge_timestamp INT8,
+  IN in_partner_url VARCHAR,
+  IN in_reserve_pub BYTEA,
+  OUT out_balance_ok BOOLEAN,
+  OUT out_conflict BOOLEAN)
+LANGUAGE plpgsql
+AS $$
+BEGIN
+  -- FIXME
+END $$;
+
+
+CREATE OR REPLACE FUNCTION exchange_do_account_merge(
+  IN in_purse_pub BYTEA,
+  IN in_reserve_pub BYTEA,
+  IN in_reserve_sig BYTEA,
+  OUT out_balance_ok BOOLEAN,
+  OUT out_conflict BOOLEAN)
+LANGUAGE plpgsql
+AS $$
+BEGIN
+  -- FIXME
+END $$;
+
+
+CREATE OR REPLACE FUNCTION exchange_do_history_request(
+  IN in_reserve_pub BYTEA,
+  IN in_reserve_sig BYTEA,
+  IN in_request_timestamp INT8,
+  IN in_history_fee_val INT8,
+  IN in_history_fee_frac INT4,
+  OUT out_balance_ok BOOLEAN,
+  OUT out_conflict BOOLEAN)
+LANGUAGE plpgsql
+AS $$
+BEGIN
+  -- FIXME
+END $$;
+
+
+CREATE OR REPLACE FUNCTION exchange_do_close_request(
+  IN in_reserve_pub BYTEA,
+  IN in_reserve_sig BYTEA,
+  OUT out_final_balance_val INT8,
+  OUT out_final_balance_frac INT4,
+  OUT out_balance_ok BOOLEAN,
+  OUT out_conflict BOOLEAN)
+LANGUAGE plpgsql
+AS $$
+BEGIN
+  -- FIXME
+END $$;
+
+
+
 -- Complete transaction
 COMMIT;
+
diff --git a/sql/exchange-0002.sql b/sql/exchange-0002.sql
deleted file mode 100644
index dd55aab..0000000
--- a/sql/exchange-0002.sql
+++ /dev/null
@@ -1,257 +0,0 @@
-CREATE OR REPLACE FUNCTION detach_default_partitions()
-  RETURNS VOID
-  LANGUAGE plpgsql
-AS $$
-BEGIN
-
-  RAISE NOTICE 'Detaching default table partitions';
-
-  -- ALTER TABLE IF EXISTS wire_targets DETACH PARTITION wire_targets_default;
-  ALTER TABLE IF EXISTS reserves DETACH PARTITION reserves_default;
-  ALTER TABLE IF EXISTS reserves_in DETACH PARTITION reserves_in_default;
-  ALTER TABLE IF EXISTS reserves_close DETACH PARTITION reserves_close_default;
-  ALTER TABLE IF EXISTS reserves_out DETACH PARTITION reserves_out_default;
-  ALTER TABLE IF EXISTS known_coins DETACH PARTITION known_coins_default;
-  ALTER TABLE IF EXISTS refresh_commitments DETACH PARTITION 
refresh_commitments_default;
-  ALTER TABLE IF EXISTS refresh_revealed_coins DETACH PARTITION 
refresh_revealed_coins_default;
-  ALTER TABLE IF EXISTS refresh_transfer_keys DETACH PARTITION 
refresh_transfer_keys_default;
-  ALTER TABLE IF EXISTS deposits DETACH PARTITION deposits_default;
-  ALTER TABLE IF EXISTS refunds DETACH PARTITION refunds_default;
-  ALTER TABLE IF EXISTS wire_out DETACH PARTITION wire_out_default;
-  ALTER TABLE IF EXISTS aggregation_tracking DETACH PARTITION 
aggregation_tracking_default;
-  ALTER TABLE IF EXISTS recoup DETACH PARTITION recoup_default;
-  ALTER TABLE IF EXISTS recoup_refresh DETACH PARTITION recoup_refresh_default;
-  ALTER TABLE IF EXISTS prewire DETACH PARTITION prewire_default;
-END
-$$;
-
-CREATE OR REPLACE FUNCTION drop_default_partitions()
-  RETURNS VOID
-  LANGUAGE plpgsql
-AS $$
-BEGIN
-
-  RAISE NOTICE 'Dropping default table partitions';
-
-  -- DROP TABLE IF EXISTS wire_targets_default;
-  DROP TABLE IF EXISTS reserves_default;
-  DROP TABLE IF EXISTS reserves_in_default;
-  DROP TABLE IF EXISTS reserves_close_default;
-  DROP TABLE IF EXISTS reserves_out_default;
-  DROP TABLE IF EXISTS known_coins_default;
-  DROP TABLE IF EXISTS refresh_commitments_default;
-  DROP TABLE IF EXISTS refresh_revealed_coins_default;
-  DROP TABLE IF EXISTS refresh_transfer_keys_default;
-  DROP TABLE IF EXISTS deposits_default;
-  DROP TABLE IF EXISTS refunds_default;
-  DROP TABLE IF EXISTS wire_out_default;
-  DROP TABLE IF EXISTS aggregation_tracking_default;
-  DROP TABLE IF EXISTS recoup_default;
-  DROP TABLE IF EXISTS recoup_refresh_default;
-  DROP TABLE IF EXISTS prewire_default;
-END
-$$;
-
-CREATE OR REPLACE FUNCTION create_partition(
-    source_table VARCHAR, 
-    modulus INTEGER,
-    num INTEGER
-  )
-  RETURNS VOID
-  LANGUAGE plpgsql
-AS $$
-BEGIN
-
-  RAISE NOTICE 'Creating partition %_%', source_table, num;
-
-  EXECUTE FORMAT(
-    'CREATE TABLE IF NOT EXISTS %I '
-      'PARTITION OF %I '
-      'FOR VALUES WITH (MODULUS %s, REMAINDER %s)',
-    source_table || '_' || num,
-    source_table,
-    modulus,
-    num-1
-  );
-
-END
-$$;
-
-CREATE OR REPLACE FUNCTION setup_partitions(
-    num_partitions INTEGER
-  )
-  RETURNS VOID
-  LANGUAGE plpgsql
-AS $$
-DECLARE
-  modulus INTEGER;
-BEGIN
-
-  modulus := num_partitions;
-
-  PERFORM detach_default_partitions();
-
-  LOOP
-    -- PERFORM create_partition('wire_targets', modulus, num_partitions);
-    PERFORM create_partition('reserves', modulus, num_partitions);
-    PERFORM create_partition('reserves_in', modulus, num_partitions);
-    PERFORM create_partition('reserves_close', modulus, num_partitions);
-    PERFORM create_partition('reserves_out', modulus, num_partitions);
-    PERFORM create_partition('known_coins', modulus, num_partitions);
-    PERFORM create_partition('refresh_commitments', modulus, num_partitions);
-    PERFORM create_partition('refresh_revealed_coins', modulus, 
num_partitions);
-    PERFORM create_partition('refresh_transfer_keys', modulus, num_partitions);
-    PERFORM create_partition('deposits', modulus, num_partitions);
-    PERFORM create_partition('refunds', modulus, num_partitions);
-    PERFORM create_partition('wire_out', modulus, num_partitions);
-    PERFORM create_partition('aggregation_tracking', modulus, num_partitions);
-    PERFORM create_partition('recoup', modulus, num_partitions);
-    PERFORM create_partition('recoup_refresh', modulus, num_partitions);
-    PERFORM create_partition('prewire', modulus, num_partitions);
-
-    num_partitions=num_partitions-1;
-    EXIT WHEN num_partitions=0;
-
-  END LOOP;
-
-  PERFORM drop_default_partitions();
-END
-$$;
-
-CREATE OR REPLACE FUNCTION create_foreign_table(
-    source_table VARCHAR, 
-    modulus INTEGER,
-    suffix VARCHAR,
-    num INTEGER
-  )
-  RETURNS VOID
-  LANGUAGE plpgsql
-AS $$
-BEGIN
-
-  RAISE NOTICE 'Creating %_% on shard_%', source_table, suffix, suffix;
-
-  EXECUTE FORMAT(
-    'CREATE FOREIGN TABLE IF NOT EXISTS %I '
-      'PARTITION OF %I '
-      'FOR VALUES WITH (MODULUS %s, REMAINDER %s) '
-      'SERVER %I',     
-    source_table || '_' || suffix,
-    source_table,
-    modulus,
-    num-1,
-    'shard_' || suffix
-  );
-
-  EXECUTE FORMAT(
-    'ALTER FOREIGN TABLE %I OWNER TO "taler-exchange-httpd"',
-    source_table || '_' || suffix
-  );
-
-END
-$$;
-
-CREATE OR REPLACE FUNCTION prepare_sharding()
-  RETURNS VOID
-  LANGUAGE plpgsql
-AS $$
-BEGIN
-
-  PERFORM detach_default_partitions();
-
-  ALTER TABLE IF EXISTS wire_targets DROP CONSTRAINT IF EXISTS 
wire_targets_pkey CASCADE;
-
-  ALTER TABLE IF EXISTS reserves DROP CONSTRAINT IF EXISTS reserves_pkey 
CASCADE;
-
-  ALTER TABLE IF EXISTS reserves_in DROP CONSTRAINT IF EXISTS reserves_in_pkey 
CASCADE;
-
-  ALTER TABLE IF EXISTS reserves_close DROP CONSTRAINT IF EXISTS 
reserves_close_pkey CASCADE;
-
-  ALTER TABLE IF EXISTS reserves_out DROP CONSTRAINT IF EXISTS 
reserves_out_pkey CASCADE;
-  ALTER TABLE IF EXISTS reserves_out DROP CONSTRAINT IF EXISTS 
reserves_out_denominations_serial_fkey;
-
-  ALTER TABLE IF EXISTS known_coins DROP CONSTRAINT IF EXISTS known_coins_pkey 
CASCADE;
-  ALTER TABLE IF EXISTS known_coins DROP CONSTRAINT IF EXISTS 
known_coins_denominations_serial_fkey;
-
-  ALTER TABLE IF EXISTS refresh_commitments DROP CONSTRAINT IF EXISTS 
refresh_commitments_pkey CASCADE;
-  ALTER TABLE IF EXISTS refresh_commitments DROP CONSTRAINT IF EXISTS 
refresh_old_coin_pub_fkey;
-
-  ALTER TABLE IF EXISTS refresh_revealed_coins DROP CONSTRAINT IF EXISTS 
refresh_revealed_coins_pkey CASCADE;
-  ALTER TABLE IF EXISTS refresh_revealed_coins DROP CONSTRAINT IF EXISTS 
refresh_revealed_coins_denominations_serial_fkey;
-
-  ALTER TABLE IF EXISTS refresh_transfer_keys DROP CONSTRAINT IF EXISTS 
refresh_transfer_keys_pkey CASCADE;
-
-  ALTER TABLE IF EXISTS deposits DROP CONSTRAINT IF EXISTS deposits_pkey 
CASCADE;
-  ALTER TABLE IF EXISTS deposits DROP CONSTRAINT IF EXISTS 
deposits_extension_details_serial_id_fkey;
-  ALTER TABLE IF EXISTS deposits DROP CONSTRAINT IF EXISTS 
deposits_shard_known_coin_id_merchant_pub_h_contract_terms_key CASCADE;
-
-  ALTER TABLE IF EXISTS refunds DROP CONSTRAINT IF EXISTS refunds_pkey CASCADE;
-
-  ALTER TABLE IF EXISTS wire_out DROP CONSTRAINT IF EXISTS wire_out_pkey 
CASCADE;
-  ALTER TABLE IF EXISTS wire_out DROP CONSTRAINT IF EXISTS 
wire_out_wtid_raw_key CASCADE;
-
-  ALTER TABLE IF EXISTS aggregation_tracking DROP CONSTRAINT IF EXISTS 
aggregation_tracking_pkey CASCADE;
-  ALTER TABLE IF EXISTS aggregation_tracking DROP CONSTRAINT IF EXISTS 
aggregation_tracking_wtid_raw_fkey;
-
-  ALTER TABLE IF EXISTS recoup DROP CONSTRAINT IF EXISTS recoup_pkey CASCADE;
-
-  ALTER TABLE IF EXISTS recoup_refresh DROP CONSTRAINT IF EXISTS 
recoup_refresh_pkey CASCADE;
-
-  ALTER TABLE IF EXISTS prewire DROP CONSTRAINT IF EXISTS prewire_pkey CASCADE;
-END
-$$;
-
-CREATE OR REPLACE FUNCTION create_shard_server(
-    host VARCHAR,
-    port INTEGER,
-    usr VARCHAR,
-    passw VARCHAR,
-    suffix VARCHAR,
-    num_shards INTEGER,
-    shard_idx INTEGER,
-    db_name VARCHAR
-  )
-  RETURNS VOID
-  LANGUAGE plpgsql
-AS $$
-BEGIN
-
-  RAISE NOTICE 'Creating server shard_%', suffix;
-
-  EXECUTE FORMAT(
-    'CREATE SERVER IF NOT EXISTS %I '
-      'FOREIGN DATA WRAPPER postgres_fdw '
-      'OPTIONS (dbname %L, host %L, port %L)',
-    'shard_' || suffix,
-    db_name,
-    host,
-    port
-  );
-
-  EXECUTE FORMAT(
-    'CREATE USER MAPPING IF NOT EXISTS FOR "taler-exchange-httpd" SERVER %I '
-      'OPTIONS (user %L, password %L)',
-    'shard_' || suffix,
-    usr,
-    passw
-  );
-
-  PERFORM create_foreign_table('wire_targets', num_shards, suffix, shard_idx);
-  PERFORM create_foreign_table('reserves', num_shards, suffix, shard_idx);
-  PERFORM create_foreign_table('reserves_in', num_shards, suffix, shard_idx);
-  PERFORM create_foreign_table('reserves_close', num_shards, suffix, 
shard_idx);
-  PERFORM create_foreign_table('reserves_out', num_shards, suffix, shard_idx);
-  PERFORM create_foreign_table('known_coins', num_shards, suffix, shard_idx);
-  PERFORM create_foreign_table('refresh_commitments', num_shards, suffix, 
shard_idx);
-  PERFORM create_foreign_table('refresh_revealed_coins', num_shards, suffix, 
shard_idx);
-  PERFORM create_foreign_table('refresh_transfer_keys', num_shards, suffix, 
shard_idx);
-  PERFORM create_foreign_table('deposits', num_shards, suffix, shard_idx);
-  PERFORM create_foreign_table('refunds', num_shards, suffix, shard_idx);
-  PERFORM create_foreign_table('wire_out', num_shards, suffix, shard_idx);
-  PERFORM create_foreign_table('aggregation_tracking', num_shards, suffix, 
shard_idx);
-  PERFORM create_foreign_table('recoup', num_shards, suffix, shard_idx);
-  PERFORM create_foreign_table('recoup_refresh', num_shards, suffix, 
shard_idx);
-  PERFORM create_foreign_table('prewire', num_shards, suffix, shard_idx);
-
-END
-$$;
diff --git a/sql/exchange-shard-0000.sql b/sql/exchange-shard-0000.sql
deleted file mode 100644
index 5f16d62..0000000
--- a/sql/exchange-shard-0000.sql
+++ /dev/null
@@ -1,227 +0,0 @@
-CREATE OR REPLACE FUNCTION setup_shard_tables(suffix VARCHAR)
-  RETURNS VOID
-  LANGUAGE plpgsql
-AS $$
-BEGIN
-
-  EXECUTE FORMAT(
-    'CREATE TABLE IF NOT EXISTS %I ' 
-      '(wire_target_serial_id BIGINT UNIQUE '
-      ',h_payto BYTEA PRIMARY KEY CHECK (LENGTH(h_payto)=64) '
-      ',payto_uri VARCHAR NOT NULL '
-      ',kyc_ok BOOLEAN NOT NULL DEFAULT (FALSE) '
-      ',external_id VARCHAR '
-    ')',
-    'wire_targets_' || suffix
-  );
-
-  EXECUTE FORMAT(
-    'CREATE TABLE IF NOT EXISTS %I '
-      '(reserve_uuid BIGINT '
-      ',reserve_pub BYTEA PRIMARY KEY CHECK(LENGTH(reserve_pub)=32) '
-      ',current_balance_val INT8 NOT NULL '
-      ',current_balance_frac INT4 NOT NULL '
-      ',expiration_date INT8 NOT NULL '
-      ',gc_date INT8 NOT NULL '
-    ')',
-    'reserves_' || suffix
-  );
-
-  EXECUTE FORMAT(
-    'CREATE TABLE IF NOT EXISTS %I '
-      '(reserve_in_serial_id BIGINT UNIQUE '
-      ',reserve_pub BYTEA PRIMARY KEY '
-      ',wire_reference INT8 NOT NULL '
-      ',credit_val INT8 NOT NULL '
-      ',credit_frac INT4 NOT NULL '
-      ',wire_source_serial_id INT8 NOT NULL '
-      ',exchange_account_section TEXT NOT NULL '
-      ',execution_date INT8 NOT NULL '
-    ')',
-    'reserves_in_' || suffix
-  );
-
-  EXECUTE FORMAT(
-    'CREATE TABLE IF NOT EXISTS %I '
-      '(close_uuid BIGINT PRIMARY KEY '
-      ',reserve_pub BYTEA NOT NULL '
-      ',execution_date INT8 NOT NULL '
-      ',wtid BYTEA NOT NULL CHECK (LENGTH(wtid)=32) '
-      ',wire_target_serial_id INT8 NOT NULL  '
-      ',amount_val INT8 NOT NULL '
-      ',amount_frac INT4 NOT NULL '
-      ',closing_fee_val INT8 NOT NULL '
-      ',closing_fee_frac INT4 NOT NULL '
-    ') ',
-    'reserves_close_' || suffix 
-  );
-
-  EXECUTE FORMAT(
-    'CREATE TABLE IF NOT EXISTS %I '
-      '(reserve_out_serial_id BIGINT UNIQUE '
-      ',h_blind_ev BYTEA CHECK (LENGTH(h_blind_ev)=64) UNIQUE'
-      ',denominations_serial INT8 NOT NULL '
-      ',denom_sig BYTEA NOT NULL '
-      ',reserve_uuid INT8 NOT NULL '
-      ',reserve_sig BYTEA NOT NULL CHECK (LENGTH(reserve_sig)=64) '
-      ',execution_date INT8 NOT NULL '
-      ',amount_with_fee_val INT8 NOT NULL '
-      ',amount_with_fee_frac INT4 NOT NULL '
-    ')',
-    'reserves_out_' || suffix
-  );
-
-  EXECUTE FORMAT(
-    'CREATE TABLE IF NOT EXISTS %I '
-      '(known_coin_id BIGINT UNIQUE '
-      ',denominations_serial INT8 NOT NULL '
-      ',coin_pub BYTEA NOT NULL PRIMARY KEY CHECK (LENGTH(coin_pub)=32) '
-      ',age_hash BYTEA CHECK (LENGTH(age_hash)=32) '
-      ',denom_sig BYTEA NOT NULL '
-      ',remaining_val INT8 NOT NULL '
-      ',remaining_frac INT4 NOT NULL '
-    ')',
-    'known_coins_' || suffix
-  );
-
-  EXECUTE FORMAT(
-    'CREATE TABLE IF NOT EXISTS %I '
-      '(melt_serial_id BIGINT UNIQUE '
-      ',rc BYTEA PRIMARY KEY CHECK (LENGTH(rc)=64) '
-      ',old_coin_pub BYTEA NOT NULL '
-      ',h_age_commitment BYTEA CHECK(LENGTH(h_age_commitment)=32) '
-      ',old_coin_sig BYTEA NOT NULL CHECK(LENGTH(old_coin_sig)=64) '
-      ',amount_with_fee_val INT8 NOT NULL '
-      ',amount_with_fee_frac INT4 NOT NULL '
-      ',noreveal_index INT4 NOT NULL '
-    ')',
-    'refresh_commitments_' || suffix
-  );
-  
-  EXECUTE FORMAT(
-    'CREATE TABLE IF NOT EXISTS %I '
-      '(rrc_serial BIGINT UNIQUE '
-      ',melt_serial_id INT8 NOT NULL '
-      ',freshcoin_index INT4 NOT NULL '
-      ',link_sig BYTEA NOT NULL CHECK(LENGTH(link_sig)=64) '
-      ',denominations_serial INT8 NOT NULL '
-      ',coin_ev BYTEA NOT NULL UNIQUE'
-      ',h_coin_ev BYTEA NOT NULL CHECK(LENGTH(h_coin_ev)=64) UNIQUE'
-      ',ev_sig BYTEA NOT NULL '
-      ',ewv BYTEA NOT NULL '
-      ',PRIMARY KEY (melt_serial_id, freshcoin_index) '
-    ')',
-    'refresh_revealed_coins_' || suffix
-  );
-  
-  EXECUTE FORMAT(
-    'CREATE TABLE IF NOT EXISTS %I '
-      '(rtc_serial BIGINT UNIQUE '
-      ',melt_serial_id INT8 PRIMARY KEY '
-      ',transfer_pub BYTEA NOT NULL CHECK(LENGTH(transfer_pub)=32) '
-      ',transfer_privs BYTEA NOT NULL '
-    ')',
-    'refresh_transfer_keys_' || suffix
-  );
-
-  EXECUTE FORMAT(
-    'CREATE TABLE IF NOT EXISTS %I '
-      '(deposit_serial_id BIGINT PRIMARY KEY '
-      ',shard INT8 NOT NULL '
-      ',known_coin_id INT8 NOT NULL '
-      ',amount_with_fee_val INT8 NOT NULL '
-      ',amount_with_fee_frac INT4 NOT NULL '
-      ',wallet_timestamp INT8 NOT NULL '
-      ',exchange_timestamp INT8 NOT NULL '
-      ',refund_deadline INT8 NOT NULL '
-      ',wire_deadline INT8 NOT NULL '
-      ',merchant_pub BYTEA NOT NULL CHECK (LENGTH(merchant_pub)=32) '
-      ',h_contract_terms BYTEA NOT NULL CHECK (LENGTH(h_contract_terms)=64) '
-      ',coin_sig BYTEA NOT NULL CHECK (LENGTH(coin_sig)=64) '
-      ',wire_salt BYTEA NOT NULL CHECK (LENGTH(wire_salt)=16) '
-      ',wire_target_serial_id INT8 NOT NULL '
-      ',tiny BOOLEAN NOT NULL DEFAULT FALSE '
-      ',done BOOLEAN NOT NULL DEFAULT FALSE '
-      ',extension_blocked BOOLEAN NOT NULL DEFAULT FALSE '
-      ',extension_details_serial_id INT8 '
-      ',UNIQUE (shard, known_coin_id, merchant_pub, h_contract_terms) '
-    ')',
-    'deposits_' || suffix
-  );
-
-  EXECUTE FORMAT(
-    'CREATE TABLE IF NOT EXISTS %I '
-      '(refund_serial_id BIGINT UNIQUE'
-      ',deposit_serial_id INT8 NOT NULL '
-      ',merchant_sig BYTEA NOT NULL CHECK(LENGTH(merchant_sig)=64) '
-      ',rtransaction_id INT8 NOT NULL '
-      ',amount_with_fee_val INT8 NOT NULL '
-      ',amount_with_fee_frac INT4 NOT NULL '
-      ',PRIMARY KEY (deposit_serial_id, rtransaction_id) '
-    ')',
-    'refunds_' || suffix
-  );
-
-  EXECUTE FORMAT(
-    'CREATE TABLE IF NOT EXISTS %I '
-      '(wireout_uuid BIGINT PRIMARY KEY'
-      ',execution_date INT8 NOT NULL '
-      ',wtid_raw BYTEA UNIQUE NOT NULL CHECK (LENGTH(wtid_raw)=32) '
-      ',wire_target_serial_id INT8 NOT NULL '
-      ',exchange_account_section TEXT NOT NULL '
-      ',amount_val INT8 NOT NULL '
-      ',amount_frac INT4 NOT NULL '
-    ')',
-    'wire_out_' || suffix
-  );
-
-  EXECUTE FORMAT(
-    'CREATE TABLE IF NOT EXISTS %I '
-      '(aggregation_serial_id BIGINT UNIQUE '
-      ',deposit_serial_id INT8 PRIMARY KEY '
-      ',wtid_raw BYTEA ' 
-    ')',
-    'aggregation_tracking_' || suffix
-  );
-
-  EXECUTE FORMAT(
-    'CREATE TABLE IF NOT EXISTS %I '
-      '(recoup_uuid BIGINT UNIQUE '
-      ',known_coin_id INT8 NOT NULL '
-      ',coin_sig BYTEA NOT NULL CHECK(LENGTH(coin_sig)=64) '
-      ',coin_blind BYTEA NOT NULL CHECK(LENGTH(coin_blind)=32) '
-      ',amount_val INT8 NOT NULL '
-      ',amount_frac INT4 NOT NULL '
-      ',recoup_timestamp INT8 NOT NULL '
-      ',reserve_out_serial_id INT8 NOT NULL '
-    ')',
-    'recoup_' || suffix
-  );
-
-  EXECUTE FORMAT(
-    'CREATE TABLE IF NOT EXISTS %I '
-      '(recoup_refresh_uuid BIGINT UNIQUE '
-      ',known_coin_id INT8 NOT NULL '
-      ',coin_sig BYTEA NOT NULL CHECK(LENGTH(coin_sig)=64) '
-      ',coin_blind BYTEA NOT NULL CHECK(LENGTH(coin_blind)=32) '
-      ',amount_val INT8 NOT NULL '
-      ',amount_frac INT4 NOT NULL '
-      ',recoup_timestamp INT8 NOT NULL '
-      ',rrc_serial INT8 NOT NULL UNIQUE'
-    ')',
-    'recoup_refresh_' || suffix
-  );
-
-  EXECUTE FORMAT(
-    'CREATE TABLE IF NOT EXISTS %I '
-      '(prewire_uuid BIGINT PRIMARY KEY '
-      ',wire_method TEXT NOT NULL '
-      ',finished BOOLEAN NOT NULL DEFAULT false '
-      ',failed BOOLEAN NOT NULL DEFAULT false '
-      ',buf BYTEA NOT NULL '
-    ')',
-    'prewire_' || suffix
-  );
-
-END
-$$;
diff --git a/sql/exchange-tables.sql b/sql/exchange-tables.sql
new file mode 100644
index 0000000..0658594
--- /dev/null
+++ b/sql/exchange-tables.sql
@@ -0,0 +1,1139 @@
+CREATE OR REPLACE FUNCTION create_partitioned_table(
+   IN table_definition VARCHAR
+  ,IN table_name VARCHAR
+  ,IN main_table_partition_str VARCHAR -- Used only when it is the main table 
- we do not partition shard tables
+  ,IN shard_suffix VARCHAR DEFAULT NULL
+)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+BEGIN
+
+  IF shard_suffix IS NOT NULL THEN 
+    table_name=table_name || '_' || shard_suffix;
+    main_table_partition_str = '';
+  END IF;
+
+  EXECUTE FORMAT(
+    table_definition,
+    table_name,
+    main_table_partition_str
+  );
+
+END 
+$$;
+
+-----------------------------------------------------------
+
+CREATE OR REPLACE FUNCTION create_table_wire_targets(
+  IN shard_suffix VARCHAR DEFAULT NULL
+)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+BEGIN
+
+  PERFORM create_partitioned_table(
+    'CREATE TABLE IF NOT EXISTS %I'
+      '(wire_target_serial_id BIGINT GENERATED BY DEFAULT AS IDENTITY' -- 
UNIQUE'
+      ',wire_target_h_payto BYTEA PRIMARY KEY CHECK 
(LENGTH(wire_target_h_payto)=32)'
+      ',payto_uri VARCHAR NOT NULL'
+      ',kyc_ok BOOLEAN NOT NULL DEFAULT (FALSE)'
+      ',external_id VARCHAR'
+    ') %s ;'
+    ,'wire_targets'
+    ,'PARTITION BY HASH (wire_target_h_payto)'
+    ,shard_suffix
+  );
+
+END
+$$;
+
+-- We need a seperate function for this, as we call create_table only once but 
need to add
+-- those constraints to each partition which gets created
+CREATE OR REPLACE FUNCTION add_constraints_to_wire_targets_partition(
+  IN partition_suffix VARCHAR
+)
+RETURNS void
+LANGUAGE plpgsql
+AS $$
+BEGIN
+
+  EXECUTE FORMAT (
+    'ALTER TABLE wire_targets_' || partition_suffix || ' '
+      'ADD CONSTRAINT wire_targets_' || partition_suffix || 
'_wire_target_serial_id_key '
+        'UNIQUE (wire_target_serial_id)'
+  );
+END
+$$;
+
+-----------------------------------------------------------
+
+CREATE OR REPLACE FUNCTION create_table_reserves(
+  IN shard_suffix VARCHAR DEFAULT NULL
+)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+  table_name VARCHAR DEFAULT 'reserves';
+BEGIN
+
+  PERFORM create_partitioned_table(
+    'CREATE TABLE IF NOT EXISTS %I'
+      '(reserve_uuid BIGINT GENERATED BY DEFAULT AS IDENTITY'
+      ',reserve_pub BYTEA PRIMARY KEY CHECK(LENGTH(reserve_pub)=32)'
+      ',current_balance_val INT8 NOT NULL'
+      ',current_balance_frac INT4 NOT NULL'
+      ',expiration_date INT8 NOT NULL'
+      ',gc_date INT8 NOT NULL'
+    ') %s ;'
+    ,table_name
+    ,'PARTITION BY HASH (reserve_pub)'
+    ,shard_suffix
+  );
+
+  table_name = concat_ws('_', table_name, shard_suffix);
+
+  EXECUTE FORMAT (
+    'CREATE INDEX IF NOT EXISTS ' || table_name || '_by_expiration_index '
+    'ON ' || table_name || ' '
+    '(expiration_date'
+    ',current_balance_val'
+    ',current_balance_frac'
+    ');'
+  );
+  EXECUTE FORMAT (
+    'COMMENT ON INDEX ' || table_name || '_by_expiration_index '
+    'IS ' || quote_literal('used in get_expired_reserves') || ';'
+  );
+  EXECUTE FORMAT (
+    'CREATE INDEX IF NOT EXISTS ' || table_name || '_by_reserve_uuid_index '
+    'ON ' || table_name || ' '
+    '(reserve_uuid);'
+  );
+  EXECUTE FORMAT (
+    'CREATE INDEX IF NOT EXISTS ' || table_name || '_by_gc_date_index '
+    'ON ' || table_name || ' '
+    '(gc_date);'
+  );
+  EXECUTE FORMAT (
+    'COMMENT ON INDEX ' || table_name || '_by_gc_date_index '
+    'IS ' || quote_literal('for reserve garbage collection') || ';'
+  );
+
+END
+$$;
+
+-----------------------------------------------------------
+
+CREATE OR REPLACE FUNCTION create_table_reserves_in(
+  IN shard_suffix VARCHAR DEFAULT NULL
+)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+  table_name VARCHAR default 'reserves_in';
+BEGIN
+
+  PERFORM create_partitioned_table(
+    'CREATE TABLE IF NOT EXISTS %I'
+      '(reserve_in_serial_id BIGINT GENERATED BY DEFAULT AS IDENTITY' -- 
UNIQUE'
+      ',reserve_pub BYTEA PRIMARY KEY' -- REFERENCES reserves (reserve_pub) ON 
DELETE CASCADE'
+      ',wire_reference INT8 NOT NULL'
+      ',credit_val INT8 NOT NULL'
+      ',credit_frac INT4 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'
+    ') %s ;'
+    ,table_name
+    ,'PARTITION BY HASH (reserve_pub)'
+    ,shard_suffix
+  );
+
+  table_name = concat_ws('_', table_name, shard_suffix);
+  
+  EXECUTE FORMAT (
+    'CREATE INDEX IF NOT EXISTS ' || table_name || 
'_by_reserve_in_serial_id_index '
+    'ON ' || table_name || ' '
+    '(reserve_in_serial_id);'
+  );
+  EXECUTE FORMAT (
+    'CREATE INDEX IF NOT EXISTS ' || table_name || 
'_by_exch_accnt_section_execution_date_idx '
+    'ON ' || table_name || ' '
+    '(exchange_account_section '
+    ',execution_date'
+    ');'
+  );
+  EXECUTE FORMAT (
+    'CREATE INDEX IF NOT EXISTS ' || table_name || 
'_by_exch_accnt_reserve_in_serial_id_idx '
+    'ON ' || table_name || ' '
+    '(exchange_account_section,'
+    'reserve_in_serial_id DESC'
+    ');'
+  );
+
+END
+$$;
+
+CREATE OR REPLACE FUNCTION add_constraints_to_reserves_in_partition(
+  IN partition_suffix VARCHAR
+)
+RETURNS void
+LANGUAGE plpgsql
+AS $$
+BEGIN
+  EXECUTE FORMAT (
+    'ALTER TABLE reserves_in_' || partition_suffix || ' '
+      'ADD CONSTRAINT reserves_in_' || partition_suffix || 
'_reserve_in_serial_id_key '
+        'UNIQUE (reserve_in_serial_id)'
+  );
+END
+$$;
+
+-----------------------------------------------------------
+
+CREATE OR REPLACE FUNCTION create_table_reserves_close(
+  IN shard_suffix VARCHAR DEFAULT NULL
+)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+BEGIN
+
+  PERFORM create_partitioned_table(
+    'CREATE TABLE IF NOT EXISTS %I'
+      '(close_uuid BIGINT GENERATED BY DEFAULT AS IDENTITY' -- UNIQUE / 
PRIMARY KEY'
+      ',reserve_pub BYTEA NOT NULL' -- REFERENCES reserves (reserve_pub) ON 
DELETE CASCADE'
+      ',execution_date INT8 NOT NULL'
+      ',wtid BYTEA NOT NULL CHECK (LENGTH(wtid)=32)'
+      ',wire_target_h_payto BYTEA CHECK (LENGTH(wire_target_h_payto)=32)'
+      ',amount_val INT8 NOT NULL'
+      ',amount_frac INT4 NOT NULL'
+      ',closing_fee_val INT8 NOT NULL'
+      ',closing_fee_frac INT4 NOT NULL'
+    ') %s ;'
+    ,'reserves_close'
+    ,'PARTITION BY HASH (reserve_pub)'
+    ,shard_suffix
+  );
+
+END
+$$;
+
+CREATE OR REPLACE FUNCTION add_constraints_to_reserves_close_partition(
+  IN partition_suffix VARCHAR
+)
+RETURNS void
+LANGUAGE plpgsql
+AS $$
+BEGIN
+  EXECUTE FORMAT (
+    'ALTER TABLE reserves_close_' || partition_suffix || ' '
+      'ADD CONSTRAINT reserves_close_' || partition_suffix || 
'_close_uuid_pkey '
+        'PRIMARY KEY (close_uuid)'
+  );
+END
+$$;
+
+-----------------------------------------------------------
+
+CREATE OR REPLACE FUNCTION create_table_reserves_out(
+  IN shard_suffix VARCHAR DEFAULT NULL
+)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+  table_name VARCHAR default 'reserves_out';
+BEGIN
+
+  PERFORM create_partitioned_table(
+    'CREATE TABLE IF NOT EXISTS %I'
+      '(reserve_out_serial_id BIGINT GENERATED BY DEFAULT AS IDENTITY' -- 
UNIQUE'
+      ',h_blind_ev BYTEA CHECK (LENGTH(h_blind_ev)=64) UNIQUE'
+      ',denominations_serial INT8 NOT NULL' -- REFERENCES denominations 
(denominations_serial)'
+      ',denom_sig BYTEA NOT NULL'
+      ',reserve_uuid INT8 NOT NULL' -- REFERENCES reserves (reserve_uuid) ON 
DELETE CASCADE'
+      ',reserve_sig BYTEA NOT NULL CHECK (LENGTH(reserve_sig)=64)'
+      ',execution_date INT8 NOT NULL'
+      ',amount_with_fee_val INT8 NOT NULL'
+      ',amount_with_fee_frac INT4 NOT NULL'
+    ') %s ;'
+    ,'reserves_out'
+    ,'PARTITION BY HASH (h_blind_ev)'
+    ,shard_suffix
+  );
+
+  table_name = concat_ws('_', table_name, shard_suffix);
+
+  EXECUTE FORMAT (
+    'CREATE INDEX IF NOT EXISTS ' || table_name || 
'_by_reserve_out_serial_id_index '
+    'ON ' || table_name || ' '
+    '(reserve_out_serial_id);'
+  );
+  EXECUTE FORMAT (
+    'CREATE INDEX IF NOT EXISTS ' || table_name || 
'_by_reserve_uuid_and_execution_date_index '
+    'ON ' || table_name || ' '
+    '(reserve_uuid, execution_date);'
+  );
+  EXECUTE FORMAT (
+    'COMMENT ON INDEX ' || table_name || 
'_by_reserve_uuid_and_execution_date_index '
+    'IS ' || quote_literal('for get_reserves_out and 
exchange_do_withdraw_limit_check') || ';'
+  );
+
+END
+$$;
+
+
+CREATE OR REPLACE FUNCTION add_constraints_to_reserves_out_partition(
+  IN partition_suffix VARCHAR
+)
+RETURNS void
+LANGUAGE plpgsql
+AS $$
+BEGIN
+  EXECUTE FORMAT (
+    'ALTER TABLE reserves_out_' || partition_suffix || ' '
+      'ADD CONSTRAINT reserves_out_' || partition_suffix || 
'_reserve_out_serial_id_key '
+        'UNIQUE (reserve_out_serial_id)'
+  );
+END
+$$;
+
+-----------------------------------------------------------
+
+CREATE OR REPLACE FUNCTION create_table_known_coins(
+  IN shard_suffix VARCHAR DEFAULT NULL
+)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+  table_name VARCHAR default 'known_coins';
+BEGIN
+
+  PERFORM create_partitioned_table(
+    'CREATE TABLE IF NOT EXISTS %I'
+      '(known_coin_id BIGINT GENERATED BY DEFAULT AS IDENTITY' -- UNIQUE'
+      ',denominations_serial INT8 NOT NULL' -- REFERENCES denominations 
(denominations_serial) ON DELETE CASCADE'
+      ',coin_pub BYTEA NOT NULL PRIMARY KEY CHECK (LENGTH(coin_pub)=32)'
+      ',age_commitment_hash BYTEA CHECK (LENGTH(age_commitment_hash)=32)'
+      ',denom_sig BYTEA NOT NULL'
+      ',remaining_val INT8 NOT NULL'
+      ',remaining_frac INT4 NOT NULL'
+    ') %s ;'
+    ,table_name
+    ,'PARTITION BY HASH (coin_pub)' -- FIXME: or include denominations_serial? 
or multi-level partitioning?;
+    ,shard_suffix
+  );
+
+  table_name = concat_ws('_', table_name, shard_suffix);
+
+  EXECUTE FORMAT (
+    'CREATE INDEX IF NOT EXISTS ' || table_name || '_by_known_coin_id_index '
+    'ON ' || table_name || ' '
+    '(known_coin_id);'
+  );
+
+END
+$$;
+
+CREATE OR REPLACE FUNCTION add_constraints_to_known_coins_partition(
+  IN partition_suffix VARCHAR
+)
+RETURNS void
+LANGUAGE plpgsql
+AS $$
+BEGIN
+  EXECUTE FORMAT (
+    'ALTER TABLE known_coins_' || partition_suffix || ' '
+      'ADD CONSTRAINT known_coins_' || partition_suffix || 'k_nown_coin_id_key 
'
+        'UNIQUE (known_coin_id)'
+  );
+END
+$$;
+
+-----------------------------------------------------------
+
+CREATE OR REPLACE FUNCTION create_table_refresh_commitments(
+  IN shard_suffix VARCHAR DEFAULT NULL
+)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+  table_name VARCHAR DEFAULT 'refresh_commitments';
+BEGIN
+
+  PERFORM create_partitioned_table(
+    'CREATE TABLE IF NOT EXISTS %I'
+      '(melt_serial_id BIGINT GENERATED BY DEFAULT AS IDENTITY' -- UNIQUE'
+      ',rc BYTEA PRIMARY KEY CHECK (LENGTH(rc)=64)'
+      ',old_coin_pub BYTEA NOT NULL' -- REFERENCES known_coins (coin_pub) ON 
DELETE CASCADE'
+      ',old_coin_sig BYTEA NOT NULL CHECK(LENGTH(old_coin_sig)=64)'
+      ',amount_with_fee_val INT8 NOT NULL'
+      ',amount_with_fee_frac INT4 NOT NULL'
+      ',noreveal_index INT4 NOT NULL'
+    ') %s ;'
+    ,table_name
+    ,'PARTITION BY HASH (rc)'
+    ,shard_suffix
+  );
+
+  table_name = concat_ws('_', table_name, shard_suffix);
+
+  EXECUTE FORMAT (
+    'CREATE INDEX IF NOT EXISTS ' || table_name || '_by_melt_serial_id_index '
+    'ON ' || table_name || ' '
+    '(melt_serial_id);'
+  );
+  EXECUTE FORMAT (
+    'CREATE INDEX IF NOT EXISTS ' || table_name || '_by_old_coin_pub_index '
+    'ON ' || table_name || ' '
+    '(old_coin_pub);'
+  );
+
+END
+$$;
+
+CREATE OR REPLACE FUNCTION add_constraints_to_refresh_commitments_partition(
+  IN partition_suffix VARCHAR
+)
+RETURNS void
+LANGUAGE plpgsql
+AS $$
+BEGIN
+  EXECUTE FORMAT (
+    'ALTER TABLE refresh_commitments_' || partition_suffix || ' '
+      'ADD CONSTRAINT refresh_commitments_' || partition_suffix || 
'_melt_serial_id_key '
+        'UNIQUE (melt_serial_id)'
+  );
+END
+$$;
+
+--------------------------------------------------------------
+
+CREATE OR REPLACE FUNCTION create_table_refresh_revealed_coins(
+  IN shard_suffix VARCHAR DEFAULT NULL
+)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+  table_name VARCHAR DEFAULT 'refresh_revealed_coins';
+BEGIN
+
+  PERFORM create_partitioned_table(
+    'CREATE TABLE IF NOT EXISTS %I'
+      '(rrc_serial BIGINT GENERATED BY DEFAULT AS IDENTITY' -- UNIQUE'
+      ',melt_serial_id INT8 NOT NULL' -- REFERENCES refresh_commitments 
(melt_serial_id) ON DELETE CASCADE'
+      ',freshcoin_index INT4 NOT NULL'
+      ',link_sig BYTEA NOT NULL CHECK(LENGTH(link_sig)=64)'
+      ',denominations_serial INT8 NOT NULL' -- REFERENCES denominations 
(denominations_serial) ON DELETE CASCADE'
+      ',coin_ev BYTEA NOT NULL' -- UNIQUE'
+      ',h_coin_ev BYTEA NOT NULL CHECK(LENGTH(h_coin_ev)=64)' -- UNIQUE'
+      ',ev_sig BYTEA NOT NULL'
+      ',ewv BYTEA NOT NULL'
+      --  ,PRIMARY KEY (melt_serial_id, freshcoin_index) -- done per shard
+    ') %s ;'
+    ,table_name
+    ,'PARTITION BY HASH (melt_serial_id)'
+    ,shard_suffix
+  );
+
+  table_name = concat_ws('_', table_name, shard_suffix);
+  
+  EXECUTE FORMAT (
+    'CREATE INDEX IF NOT EXISTS ' || table_name || '_coins_by_rrc_serial_index 
'
+    'ON ' || table_name || ' '
+    '(rrc_serial);'
+  );
+  EXECUTE FORMAT (
+    'CREATE INDEX IF NOT EXISTS ' || table_name || 
'_coins_by_melt_serial_id_index '
+    'ON ' || table_name || ' '
+    '(melt_serial_id);'
+  );
+
+END
+$$;
+
+CREATE OR REPLACE FUNCTION add_constraints_to_refresh_revealed_coins_partition(
+  IN partition_suffix VARCHAR
+)
+RETURNS void
+LANGUAGE plpgsql
+AS $$
+BEGIN
+  EXECUTE FORMAT (
+    'ALTER TABLE refresh_revealed_coins_' || partition_suffix || ' '
+      'ADD CONSTRAINT refresh_revealed_coins_' || partition_suffix || 
'_rrc_serial_key '
+        'UNIQUE (rrc_serial) '
+      ',ADD CONSTRAINT refresh_revealed_coins_' || partition_suffix || 
'_coin_ev_key '
+        'UNIQUE (coin_ev) '
+      ',ADD CONSTRAINT refresh_revealed_coins_' || partition_suffix || 
'_h_coin_ev_key '
+        'UNIQUE (h_coin_ev) '
+      ',ADD PRIMARY KEY (melt_serial_id, freshcoin_index) '
+  );
+END
+$$;
+
+-----------------------------------------------------------
+
+CREATE OR REPLACE FUNCTION create_table_refresh_transfer_keys(
+  IN shard_suffix VARCHAR DEFAULT NULL
+)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+  table_name VARCHAR DEFAULT 'refresh_transfer_keys';
+BEGIN
+
+  PERFORM create_partitioned_table(
+    'CREATE TABLE IF NOT EXISTS %I'
+      '(rtc_serial BIGINT GENERATED BY DEFAULT AS IDENTITY' -- UNIQUE'
+      ',melt_serial_id INT8 PRIMARY KEY' -- REFERENCES refresh_commitments 
(melt_serial_id) ON DELETE CASCADE'
+      ',transfer_pub BYTEA NOT NULL CHECK(LENGTH(transfer_pub)=32)'
+      ',transfer_privs BYTEA NOT NULL'
+    ') %s ;'
+    ,table_name
+    ,'PARTITION BY HASH (melt_serial_id)'
+    ,shard_suffix
+  );
+
+  table_name = concat_ws('_', table_name, shard_suffix);
+
+  EXECUTE FORMAT (
+    'CREATE INDEX IF NOT EXISTS ' || table_name || '_by_rtc_serial_index '
+    'ON ' || table_name || ' '
+    '(rtc_serial);'
+  );
+
+END
+$$;
+
+CREATE OR REPLACE FUNCTION add_constraints_to_refresh_transfer_keys_partition(
+  IN partition_suffix VARCHAR
+)
+RETURNS void
+LANGUAGE plpgsql
+AS $$
+BEGIN
+  EXECUTE FORMAT (
+    'ALTER TABLE refresh_transfer_keys_' || partition_suffix || ' '
+      'ADD CONSTRAINT refresh_transfer_keys_' || partition_suffix || 
'_rtc_serial_key '
+        'UNIQUE (rtc_serial)'
+  );
+END
+$$;
+
+-----------------------------------------------------------
+
+CREATE OR REPLACE FUNCTION create_table_deposits(
+  IN shard_suffix VARCHAR DEFAULT NULL
+)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+  table_name VARCHAR DEFAULT 'deposits';
+BEGIN
+
+  PERFORM create_partitioned_table(
+    'CREATE TABLE IF NOT EXISTS %I'
+      '(deposit_serial_id BIGINT GENERATED BY DEFAULT AS IDENTITY' -- PRIMARY 
KEY'
+      ',shard INT8 NOT NULL'
+      ',coin_pub BYTEA NOT NULL CHECK (LENGTH(coin_pub)=32)' -- REFERENCES 
known_coins (coin_pub) ON DELETE CASCADE
+      ',known_coin_id INT8 NOT NULL' -- REFERENCES known_coins (known_coin_id) 
ON DELETE CASCADE' --- FIXME: column needed???
+      ',amount_with_fee_val INT8 NOT NULL'
+      ',amount_with_fee_frac INT4 NOT NULL'
+      ',wallet_timestamp INT8 NOT NULL'
+      ',exchange_timestamp INT8 NOT NULL'
+      ',refund_deadline INT8 NOT NULL'
+      ',wire_deadline INT8 NOT NULL'
+      ',merchant_pub BYTEA NOT NULL CHECK (LENGTH(merchant_pub)=32)'
+      ',h_contract_terms BYTEA NOT NULL CHECK (LENGTH(h_contract_terms)=64)'
+      ',coin_sig BYTEA NOT NULL CHECK (LENGTH(coin_sig)=64)'
+      ',wire_salt BYTEA NOT NULL CHECK (LENGTH(wire_salt)=16)'
+      ',wire_target_h_payto BYTEA CHECK (LENGTH(wire_target_h_payto)=32)'
+      ',tiny BOOLEAN NOT NULL DEFAULT FALSE'
+      ',done BOOLEAN NOT NULL DEFAULT FALSE'
+      ',extension_blocked BOOLEAN NOT NULL DEFAULT FALSE'
+      ',extension_details_serial_id INT8' -- REFERENCES extension_details 
(extension_details_serial_id) ON DELETE CASCADE'
+      ',UNIQUE (shard, known_coin_id, merchant_pub, h_contract_terms)'
+    ') %s ;'
+    ,table_name
+    ,'PARTITION BY HASH (shard)'
+    ,shard_suffix
+  );
+
+  table_name = concat_ws('_', table_name, shard_suffix);
+
+  -- FIXME: we sometimes go ONLY by 'deposit_serial_id',
+  --        check if queries could be improved by adding shard or adding 
another index without shard here, or inverting the order of the index here!
+  EXECUTE FORMAT (
+    'CREATE INDEX IF NOT EXISTS ' || table_name || 
'_deposit_by_serial_id_index '
+    'ON ' || table_name || ' '
+    '(shard,deposit_serial_id);'
+  );
+  EXECUTE FORMAT (
+    'CREATE INDEX IF NOT EXISTS ' || table_name || '_by_coin_pub_index '
+    'ON ' || table_name || ' ' 
+    '(coin_pub);'
+  );
+
+
+END
+$$;
+
+CREATE OR REPLACE FUNCTION add_constraints_to_deposits_partition(
+  IN partition_suffix VARCHAR
+)
+RETURNS void
+LANGUAGE plpgsql
+AS $$
+BEGIN
+  EXECUTE FORMAT (
+    'ALTER TABLE deposits_' || partition_suffix || ' '
+      'ADD CONSTRAINT deposits_' || partition_suffix || 
'_deposit_serial_id_pkey '
+        'PRIMARY KEY (deposit_serial_id)'
+  );
+END
+$$;
+
+-----------------------------------------------------------
+
+CREATE OR REPLACE FUNCTION create_table_deposits_by_ready(
+  IN shard_suffix VARCHAR DEFAULT NULL
+)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+  table_name VARCHAR DEFAULT 'deposits_by_ready';
+BEGIN
+
+  PERFORM create_partitioned_table(
+  'CREATE TABLE IF NOT EXISTS %I'
+    '(wire_deadline INT8 NOT NULL'
+    ',shard INT8 NOT NULL'
+    ',coin_pub BYTEA NOT NULL CHECK (LENGTH(coin_pub)=32)'
+    ',deposit_serial_id INT8'
+    ') %s ;'
+    ,table_name
+    ,'PARTITION BY RANGE (wire_deadline)'
+    ,shard_suffix
+  );
+
+  table_name = concat_ws('_', table_name, shard_suffix);
+
+  EXECUTE FORMAT (
+    'CREATE INDEX IF NOT EXISTS ' || table_name || '_main_index '
+    'ON ' || table_name || ' '
+    '(wire_deadline ASC, shard ASC, coin_pub);'
+  );
+
+END
+$$;
+
+-----------------------------------------------------------
+
+CREATE OR REPLACE FUNCTION create_table_deposits_for_matching(
+  IN shard_suffix VARCHAR DEFAULT NULL
+)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+  table_name VARCHAR DEFAULT 'deposits_for_matching';
+BEGIN
+
+  PERFORM create_partitioned_table(
+  'CREATE TABLE IF NOT EXISTS %I'
+    '(refund_deadline INT8 NOT NULL'
+    ',shard INT8 NOT NULL'
+    ',coin_pub BYTEA NOT NULL CHECK (LENGTH(coin_pub)=32)'
+    ',deposit_serial_id INT8'
+    ') %s ;'
+    ,table_name
+    ,'PARTITION BY RANGE (refund_deadline)'
+    ,shard_suffix
+  );
+
+  table_name = concat_ws('_', table_name, shard_suffix);
+
+  EXECUTE FORMAT (
+    'CREATE INDEX IF NOT EXISTS ' || table_name || '_main_index '
+    'ON ' || table_name || ' '
+    '(refund_deadline ASC, shard, coin_pub);'
+  );
+
+END
+$$;
+
+-----------------------------------------------------------
+
+CREATE OR REPLACE FUNCTION create_table_refunds(
+  IN shard_suffix VARCHAR DEFAULT NULL
+)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+  table_name VARCHAR DEFAULT 'refunds';
+BEGIN
+
+  PERFORM create_partitioned_table(
+    'CREATE TABLE IF NOT EXISTS %I'
+      '(refund_serial_id BIGINT GENERATED BY DEFAULT AS IDENTITY' -- UNIQUE'
+      ',shard INT8 NOT NULL' -- REFERENCES deposits (shard) 
+      ',deposit_serial_id INT8 NOT NULL' -- REFERENCES deposits 
(deposit_serial_id) ON DELETE CASCADE'
+      ',merchant_sig BYTEA NOT NULL CHECK(LENGTH(merchant_sig)=64)'
+      ',rtransaction_id INT8 NOT NULL'
+      ',amount_with_fee_val INT8 NOT NULL'
+      ',amount_with_fee_frac INT4 NOT NULL'
+      -- ,PRIMARY KEY (deposit_serial_id, rtransaction_id) -- done per shard!
+    ') %s ;'
+    ,table_name
+    ,'PARTITION BY HASH (shard)'
+    ,shard_suffix
+  );
+
+  table_name = concat_ws('_', table_name, shard_suffix);
+
+  EXECUTE FORMAT (
+    'CREATE INDEX IF NOT EXISTS ' || table_name || '_by_refund_serial_id_index 
'
+    'ON ' || table_name || ' '
+    '(refund_serial_id);'
+  );
+  EXECUTE FORMAT (
+    'CREATE INDEX IF NOT EXISTS ' || table_name || 
'_by_deposit_serial_id_index '
+    'ON ' || table_name || ' '
+    '(shard,deposit_serial_id);'
+  );
+
+END
+$$;
+
+CREATE OR REPLACE FUNCTION add_constraints_to_refunds_partition(
+  IN partition_suffix VARCHAR
+)
+RETURNS void
+LANGUAGE plpgsql
+AS $$
+BEGIN
+  EXECUTE FORMAT (
+    'ALTER TABLE refunds_' || partition_suffix || ' '
+      'ADD CONSTRAINT refunds_' || partition_suffix || '_refund_serial_id_key '
+        'UNIQUE (refund_serial_id) '
+      ',ADD PRIMARY KEY (deposit_serial_id, rtransaction_id) '
+  );
+END
+$$;
+
+-----------------------------------------------------------
+
+CREATE OR REPLACE FUNCTION create_table_wire_out(
+  IN shard_suffix VARCHAR DEFAULT NULL
+)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+  table_name VARCHAR DEFAULT 'wire_out';
+BEGIN
+
+  PERFORM create_partitioned_table(
+    'CREATE TABLE IF NOT EXISTS %I'
+      '(wireout_uuid BIGINT GENERATED BY DEFAULT AS IDENTITY' -- PRIMARY KEY'
+      ',execution_date INT8 NOT NULL'
+      ',wtid_raw BYTEA UNIQUE NOT NULL CHECK (LENGTH(wtid_raw)=32)'
+      ',wire_target_h_payto BYTEA CHECK (LENGTH(wire_target_h_payto)=32)'
+      ',exchange_account_section TEXT NOT NULL'
+      ',amount_val INT8 NOT NULL'
+      ',amount_frac INT4 NOT NULL'
+    ') %s ;'
+    ,table_name
+    ,'PARTITION BY HASH (wtid_raw)'
+    ,shard_suffix
+  );
+
+  table_name = concat_ws('_', table_name, shard_suffix);
+
+  EXECUTE FORMAT (
+    'CREATE INDEX IF NOT EXISTS ' || table_name || '_by_wireout_uuid_index '
+    'ON ' || table_name || ' '
+    '(wireout_uuid);'
+  );
+  EXECUTE FORMAT (
+    'CREATE INDEX IF NOT EXISTS ' || table_name || 
'_by_wire_target_h_payto_index '
+    'ON ' || table_name || ' '
+    '(wire_target_h_payto);'
+  );
+
+
+END
+$$;
+
+CREATE OR REPLACE FUNCTION add_constraints_to_wire_out_partition(
+  IN partition_suffix VARCHAR
+)
+RETURNS void
+LANGUAGE plpgsql
+AS $$
+BEGIN
+  EXECUTE FORMAT (
+    'ALTER TABLE wire_out_' || partition_suffix || ' '
+      'ADD CONSTRAINT wire_out_' || partition_suffix || '_wireout_uuid_pkey '
+        'PRIMARY KEY (wireout_uuid)'
+  );
+END
+$$;
+
+-----------------------------------------------------------
+
+CREATE OR REPLACE FUNCTION create_table_aggregation_tracking(
+  IN shard_suffix VARCHAR DEFAULT NULL
+)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+  table_name VARCHAR DEFAULT 'aggregation_tracking';
+BEGIN
+
+  PERFORM create_partitioned_table(
+    'CREATE TABLE IF NOT EXISTS %I'
+      '(aggregation_serial_id BIGINT GENERATED BY DEFAULT AS IDENTITY' -- 
UNIQUE'
+       ',deposit_serial_id INT8 PRIMARY KEY' -- REFERENCES deposits 
(deposit_serial_id) ON DELETE CASCADE' -- FIXME chnage to coint_pub + 
deposit_serial_id for more efficient depost -- or something else ???
+      ',wtid_raw BYTEA NOT NULL' -- CONSTRAINT wire_out_ref REFERENCES 
wire_out(wtid_raw) ON DELETE CASCADE DEFERRABLE'
+    ') %s ;'
+    ,table_name
+    ,'PARTITION BY HASH (deposit_serial_id)'
+    ,shard_suffix
+  );
+
+  table_name = concat_ws('_', table_name, shard_suffix);
+
+  EXECUTE FORMAT (
+    'CREATE INDEX IF NOT EXISTS ' || table_name || 
'_by_aggregation_serial_id_index '
+    'ON ' || table_name || ' '
+    '(aggregation_serial_id);'
+  );
+  EXECUTE FORMAT (
+    'CREATE INDEX IF NOT EXISTS ' || table_name || '_by_wtid_raw_index '
+    'ON ' || table_name || ' '
+    '(wtid_raw);'
+  );
+  EXECUTE FORMAT (
+    'COMMENT ON INDEX ' || table_name || '_by_wtid_raw_index '
+    'IS ' || quote_literal('for lookup_transactions') || ';'
+  );
+
+END
+$$;
+
+CREATE OR REPLACE FUNCTION add_constraints_to_aggregation_tracking_partition(
+  IN partition_suffix VARCHAR
+)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+BEGIN
+  EXECUTE FORMAT (
+    'ALTER TABLE aggregation_tracking_' || partition_suffix || ' '
+      'ADD CONSTRAINT aggregation_tracking_' || partition_suffix || 
'_aggregation_serial_id_key '
+        'UNIQUE (aggregation_serial_id) '
+  );
+END
+$$;
+
+-----------------------------------------------------------
+
+CREATE OR REPLACE FUNCTION create_table_recoup(
+  IN shard_suffix VARCHAR DEFAULT NULL
+)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+  table_name VARCHAR DEFAULT 'recoup';
+BEGIN
+
+  PERFORM create_partitioned_table(
+    'CREATE TABLE IF NOT EXISTS %I'
+      '(recoup_uuid BIGINT GENERATED BY DEFAULT AS IDENTITY' -- UNIQUE'
+      ',coin_pub BYTEA NOT NULL CHECK (LENGTH(coin_pub)=32)' -- REFERENCES 
known_coins (coin_pub)
+      ',coin_sig BYTEA NOT NULL CHECK(LENGTH(coin_sig)=64)'
+      ',coin_blind BYTEA NOT NULL CHECK(LENGTH(coin_blind)=32)'
+      ',amount_val INT8 NOT NULL'
+      ',amount_frac INT4 NOT NULL'
+      ',recoup_timestamp INT8 NOT NULL'
+      ',reserve_out_serial_id INT8 NOT NULL' -- REFERENCES reserves_out 
(reserve_out_serial_id) ON DELETE CASCADE'
+    ') %s ;'
+    ,table_name
+    ,'PARTITION BY HASH (coin_pub);'
+    ,shard_suffix
+  );
+
+  table_name = concat_ws('_', table_name, shard_suffix);
+  
+  EXECUTE FORMAT (
+    'CREATE INDEX IF NOT EXISTS ' || table_name || '_recoup_uuid_index '
+    'ON ' || table_name || ' '
+    '(recoup_uuid);'
+  );
+  EXECUTE FORMAT (
+    'CREATE INDEX IF NOT EXISTS ' || table_name || 
'_by_reserve_out_serial_id_index '
+    'ON ' || table_name || ' '
+    '(reserve_out_serial_id);'
+  );
+  EXECUTE FORMAT (
+    'CREATE INDEX IF NOT EXISTS ' || table_name || '_by_coin_pub_index '
+    'ON ' || table_name || ' '
+    '(coin_pub);'
+  );
+
+END
+$$;
+
+CREATE OR REPLACE FUNCTION add_constraints_to_recoup_partition(
+  IN partition_suffix VARCHAR
+)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+BEGIN
+  EXECUTE FORMAT (
+    'ALTER TABLE recoup_' || partition_suffix || ' '
+      'ADD CONSTRAINT recoup_' || partition_suffix || '_recoup_uuid_key '
+        'UNIQUE (recoup_uuid) '
+  );
+END
+$$;
+
+-----------------------------------------------------------
+
+CREATE OR REPLACE FUNCTION create_table_recoup_by_reserve(
+  IN shard_suffix VARCHAR DEFAULT NULL
+)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+  table_name VARCHAR DEFAULT 'recoup_by_reserve';
+BEGIN
+
+  PERFORM create_partitioned_table(
+  'CREATE TABLE IF NOT EXISTS %I'
+    '(reserve_out_serial_id INT8 NOT NULL' -- REFERENCES reserves 
(reserve_out_serial_id) ON DELETE CASCADE
+    ',coin_pub BYTEA CHECK (LENGTH(coin_pub)=32)'
+    ') %s ;'
+    ,table_name
+    ,'PARTITION BY HASH (reserve_out_serial_id)'
+    ,shard_suffix
+  );
+
+  table_name = concat_ws('_', table_name, shard_suffix);
+
+  EXECUTE FORMAT (
+    'CREATE INDEX IF NOT EXISTS ' || table_name || '_main_index '
+    'ON ' || table_name || ' '
+    '(reserve_out_serial_id);'
+  );
+
+END
+$$;
+
+-----------------------------------------------------------
+
+CREATE OR REPLACE FUNCTION create_table_reserves_out_by_reserve(
+  IN shard_suffix VARCHAR DEFAULT NULL
+)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+  table_name VARCHAR DEFAULT 'reserves_out_by_reserve';
+BEGIN
+
+  PERFORM create_partitioned_table(
+  'CREATE TABLE IF NOT EXISTS %I'
+    '(reserve_uuid INT8 NOT NULL' -- REFERENCES reserves (reserve_uuid) ON 
DELETE CASCADE
+    ',h_blind_ev BYTEA CHECK (LENGTH(h_blind_ev)=64)'
+    ') %s '
+    ,table_name
+    ,'PARTITION BY HASH (reserve_uuid)'
+    ,shard_suffix
+  );
+
+  table_name = concat_ws('_', table_name, shard_suffix);
+
+  EXECUTE FORMAT (
+    'CREATE INDEX IF NOT EXISTS ' || table_name || '_main_index '
+    'ON ' || table_name || ' '
+    '(reserve_uuid);'
+  );
+
+END
+$$;
+
+-----------------------------------------------------------
+
+CREATE OR REPLACE FUNCTION create_table_recoup_refresh(
+  IN shard_suffix VARCHAR DEFAULT NULL
+)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+  table_name VARCHAR DEFAULT 'recoup_refresh';
+BEGIN
+
+  PERFORM create_partitioned_table(
+    'CREATE TABLE IF NOT EXISTS %I'
+      '(recoup_refresh_uuid BIGINT GENERATED BY DEFAULT AS IDENTITY' -- UNIQUE'
+      ',coin_pub BYTEA NOT NULL CHECK (LENGTH(coin_pub)=32)' -- REFERENCES 
known_coins (coin_pub)
+      ',known_coin_id BIGINT NOT NULL' -- REFERENCES known_coins 
(known_coin_id) ON DELETE CASCADE
+      ',coin_sig BYTEA NOT NULL CHECK(LENGTH(coin_sig)=64)'
+      ',coin_blind BYTEA NOT NULL CHECK(LENGTH(coin_blind)=32)'
+      ',amount_val INT8 NOT NULL'
+      ',amount_frac INT4 NOT NULL'
+      ',recoup_timestamp INT8 NOT NULL'
+      ',rrc_serial INT8 NOT NULL' -- REFERENCES refresh_revealed_coins 
(rrc_serial) ON DELETE CASCADE -- UNIQUE'
+    ') %s ;'
+    ,table_name
+    ,'PARTITION BY HASH (coin_pub)'
+    ,shard_suffix
+  );
+
+  table_name = concat_ws('_', table_name, shard_suffix);
+
+  EXECUTE FORMAT (
+    'CREATE INDEX IF NOT EXISTS ' || table_name || 
'_by_recoup_refresh_uuid_index '
+    'ON ' || table_name || ' '
+    '(recoup_refresh_uuid);'
+  );
+  EXECUTE FORMAT (
+    'CREATE INDEX IF NOT EXISTS ' || table_name || '_by_rrc_serial_index '
+    'ON ' || table_name || ' '
+    '(rrc_serial);'
+  );
+  EXECUTE FORMAT (
+    'CREATE INDEX IF NOT EXISTS ' || table_name || '_by_coin_pub_index '
+    'ON ' || table_name || ' '
+    '(coin_pub);'
+  );
+
+END
+$$;
+
+CREATE OR REPLACE FUNCTION add_constraints_to_recoup_refresh_partition(
+  IN partition_suffix VARCHAR
+)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+BEGIN
+  EXECUTE FORMAT (
+    'ALTER TABLE recoup_refresh_' || partition_suffix || ' '
+      'ADD CONSTRAINT recoup_refresh_' || partition_suffix || 
'_recoup_refresh_uuid_key '
+        'UNIQUE (recoup_refresh_uuid) '
+  );
+END
+$$;
+
+-----------------------------------------------------------
+
+CREATE OR REPLACE FUNCTION create_table_prewire(
+  IN shard_suffix VARCHAR DEFAULT NULL
+)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+  table_name VARCHAR DEFAULT 'prewire';
+BEGIN
+
+  PERFORM create_partitioned_table(
+    'CREATE TABLE IF NOT EXISTS %I'
+      '(prewire_uuid BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY'
+      ',wire_method TEXT NOT NULL'
+      ',finished BOOLEAN NOT NULL DEFAULT false'
+      ',failed BOOLEAN NOT NULL DEFAULT false'
+      ',buf BYTEA NOT NULL'
+    ') %s ;'
+    ,table_name
+    ,'PARTITION BY HASH (prewire_uuid)'
+    ,shard_suffix
+  );
+
+  table_name = concat_ws('_', table_name, shard_suffix);
+
+  EXECUTE FORMAT (
+    'CREATE INDEX IF NOT EXISTS ' || table_name || '_by_finished_index '
+    'ON ' || table_name || ' '
+    '(finished);'
+  );
+  EXECUTE FORMAT (
+    'COMMENT ON INDEX ' || table_name || '_by_finished_index '
+    'IS ' || quote_literal('for gc_prewire') || ';'
+  );
+  -- FIXME: find a way to combine these two indices?
+  EXECUTE FORMAT (
+    'CREATE INDEX IF NOT EXISTS ' || table_name || '_by_failed_finished_index '
+    'ON ' || table_name || ' '
+    '(failed,finished);'
+  );
+  EXECUTE FORMAT (
+    'COMMENT ON INDEX ' || table_name || '_by_failed_finished_index '
+    'IS ' || quote_literal('for wire_prepare_data_get') || ';'
+  );
+
+END
+$$;
+
+-----------------------------------------------------------
+
+CREATE OR REPLACE FUNCTION create_table_cs_nonce_locks(
+  shard_suffix VARCHAR DEFAULT NULL
+)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+BEGIN
+
+  PERFORM create_partitioned_table(
+    'CREATE TABLE IF NOT EXISTS %I'
+      '(cs_nonce_lock_serial_id BIGINT GENERATED BY DEFAULT AS IDENTITY' -- 
UNIQUE'
+      ',nonce BYTEA PRIMARY KEY CHECK (LENGTH(nonce)=32)'
+      ',op_hash BYTEA NOT NULL CHECK (LENGTH(op_hash)=64)'
+      ',max_denomination_serial INT8 NOT NULL'
+    ') %s ;'
+    ,'cs_nonce_locks'
+    ,'PARTITION BY HASH (nonce)'
+    ,shard_suffix
+  );
+
+END
+$$;
+
+CREATE OR REPLACE FUNCTION add_constraints_to_cs_nonce_locks_partition(
+  IN partition_suffix VARCHAR
+)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+BEGIN
+  EXECUTE FORMAT (
+    'ALTER TABLE cs_nonce_locks_' || partition_suffix || ' '
+      'ADD CONSTRAINT cs_nonce_locks_' || partition_suffix || 
'_cs_nonce_lock_serial_id_key '
+        'UNIQUE (cs_nonce_lock_serial_id)'
+  );
+END
+$$;
+
+-----------------------------------------------------------
diff --git a/sql/partition-0001.sql b/sql/partition-0001.sql
new file mode 100644
index 0000000..f3910c6
--- /dev/null
+++ b/sql/partition-0001.sql
@@ -0,0 +1,601 @@
+BEGIN;
+
+CREATE OR REPLACE FUNCTION create_table_partition(
+    source_table_name VARCHAR
+    ,modulus INTEGER
+    ,partition_num INTEGER
+  )
+  RETURNS VOID
+  LANGUAGE plpgsql
+AS $$
+BEGIN
+
+  RAISE NOTICE 'Creating partition %_%', source_table_name, partition_num;
+
+  EXECUTE FORMAT(
+    'CREATE TABLE IF NOT EXISTS %I '
+      'PARTITION OF %I '
+      'FOR VALUES WITH (MODULUS %s, REMAINDER %s)'
+    ,source_table_name || '_' || partition_num
+    ,source_table_name
+    ,modulus
+    ,partition_num-1
+  );
+
+END
+$$;
+
+CREATE OR REPLACE FUNCTION detach_default_partitions()
+  RETURNS VOID
+  LANGUAGE plpgsql
+AS $$
+BEGIN
+
+  RAISE NOTICE 'Detaching all default table partitions';
+
+  ALTER TABLE IF EXISTS wire_targets
+    DETACH PARTITION wire_targets_default;
+
+  ALTER TABLE IF EXISTS reserves
+    DETACH PARTITION reserves_default;
+
+  ALTER TABLE IF EXISTS reserves_in
+    DETACH PARTITION reserves_in_default;
+  
+  ALTER TABLE IF EXISTS reserves_close
+    DETACH PARTITION reserves_close_default;
+  
+  ALTER TABLE IF EXISTS reserves_out
+    DETACH PARTITION reserves_out_default;
+  
+  ALTER TABLE IF EXISTS known_coins
+    DETACH PARTITION known_coins_default;
+  
+  ALTER TABLE IF EXISTS refresh_commitments
+    DETACH PARTITION refresh_commitments_default;
+  
+  ALTER TABLE IF EXISTS refresh_revealed_coins
+    DETACH PARTITION refresh_revealed_coins_default;
+  
+  ALTER TABLE IF EXISTS refresh_transfer_keys
+    DETACH PARTITION refresh_transfer_keys_default;
+  
+  ALTER TABLE IF EXISTS deposits
+    DETACH PARTITION deposits_default;
+
+--  ALTER TABLE IF EXISTS deposits_by_ready
+--    DETACH PARTITION deposits_by_ready_default;
+--
+--  ALTER TABLE IF EXISTS deposits_for_matching
+--    DETACH PARTITION deposits_default_for_matching_default;
+  
+  ALTER TABLE IF EXISTS refunds
+    DETACH PARTITION refunds_default;
+  
+  ALTER TABLE IF EXISTS wire_out
+    DETACH PARTITION wire_out_default;
+  
+  ALTER TABLE IF EXISTS aggregation_tracking
+    DETACH PARTITION aggregation_tracking_default;
+  
+  ALTER TABLE IF EXISTS recoup
+    DETACH PARTITION recoup_default;
+  
+  ALTER TABLE IF EXISTS recoup_by_reserve
+    DETACH PARTITION recoup_by_reserve_default;
+
+  ALTER TABLE IF EXISTS reserves_out_by_reserve
+    DETACH PARTITION reserves_out_by_reserve_default;
+
+  ALTER TABLE IF EXISTS recoup_refresh
+    DETACH PARTITION recoup_refresh_default;
+  
+  ALTER TABLE IF EXISTS prewire
+    DETACH PARTITION prewire_default;
+  
+  ALTER TABLE IF EXISTS cs_nonce_locks
+    DETACH partition cs_nonce_locks_default;
+
+END
+$$;
+
+COMMENT ON FUNCTION detach_default_partitions
+  IS 'We need to drop default and create new one before deleting the default 
partitions
+      otherwise constraints get lost too. Might be needed in shardig too';
+
+
+CREATE OR REPLACE FUNCTION drop_default_partitions()
+  RETURNS VOID
+  LANGUAGE plpgsql
+AS $$
+BEGIN
+
+  RAISE NOTICE 'Dropping default table partitions';
+
+  DROP TABLE IF EXISTS wire_targets_default;
+  DROP TABLE IF EXISTS reserves_default;
+  DROP TABLE IF EXISTS reserves_in_default;
+  DROP TABLE IF EXISTS reserves_close_default;
+  DROP TABLE IF EXISTS reserves_out_default;
+  DROP TABLE IF EXISTS known_coins_default;
+  DROP TABLE IF EXISTS refresh_commitments_default;
+  DROP TABLE IF EXISTS refresh_revealed_coins_default;
+  DROP TABLE IF EXISTS refresh_transfer_keys_default;
+  DROP TABLE IF EXISTS deposits_default;
+  --DROP TABLE IF EXISTS deposits_by_ready_default;
+  --DROP TABLE IF EXISTS deposits_for_matching_default;
+  DROP TABLE IF EXISTS refunds_default;
+  DROP TABLE IF EXISTS wire_out_default;
+  DROP TABLE IF EXISTS aggregation_tracking_default;
+  DROP TABLE IF EXISTS recoup_default;
+  DROP TABLE IF EXISTS recoup_by_reserve_default;
+  DROP TABLE IF EXISTS reserves_out_by_reserve_default;
+  DROP TABLE IF EXISTS recoup_refresh_default;
+  DROP TABLE IF EXISTS prewire_default;
+  DROP TABLE IF EXISTS cs_nonce_locks_default;
+
+END
+$$;
+
+COMMENT ON FUNCTION drop_default_partitions
+  IS 'Drop all default partitions once other partitions are attached.
+      Might be needed in sharding too.';
+
+CREATE OR REPLACE FUNCTION create_partitions(
+    num_partitions INTEGER
+)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+  modulus INTEGER;
+BEGIN
+
+  modulus := num_partitions;
+
+  PERFORM detach_default_partitions();
+
+  LOOP
+    PERFORM create_table_partition(
+      'wire_targets'
+      ,modulus
+      ,num_partitions
+    );
+    PERFORM add_constraints_to_wire_targets_partition(num_partitions::varchar);
+
+    PERFORM create_table_partition(
+      'reserves'
+      ,modulus
+      ,num_partitions
+    );
+
+    PERFORM create_table_partition(
+      'reserves_in'
+      ,modulus
+      ,num_partitions
+    );
+    PERFORM add_constraints_to_reserves_in_partition(num_partitions::varchar);
+
+    PERFORM create_table_partition(
+      'reserves_close'
+      ,modulus
+      ,num_partitions
+    );
+    PERFORM 
add_constraints_to_reserves_close_partition(num_partitions::varchar);
+
+    PERFORM create_table_partition(
+      'reserves_out'
+      ,modulus
+      ,num_partitions
+    );
+    PERFORM add_constraints_to_reserves_out_partition(num_partitions::varchar);
+
+    PERFORM create_table_partition(
+      'known_coins'
+      ,modulus
+      ,num_partitions
+    );
+    PERFORM add_constraints_to_known_coins_partition(num_partitions::varchar);
+    
+    PERFORM create_table_partition(
+      'refresh_commitments'
+      ,modulus
+      ,num_partitions
+    );
+    PERFORM 
add_constraints_to_refresh_commitments_partition(num_partitions::varchar);
+
+    PERFORM create_table_partition(
+      'refresh_revealed_coins'
+      ,modulus
+      ,num_partitions
+    );
+    PERFORM 
add_constraints_to_refresh_revealed_coins_partition(num_partitions::varchar);
+
+    PERFORM create_table_partition(
+      'refresh_transfer_keys'
+      ,modulus
+      ,num_partitions
+    );
+    PERFORM 
add_constraints_to_refresh_transfer_keys_partition(num_partitions::varchar);
+
+    PERFORM create_table_partition(
+      'deposits'
+      ,modulus
+      ,num_partitions
+    );
+    PERFORM add_constraints_to_deposits_partition(num_partitions::varchar);
+
+--    PERFORM create_table_partition(
+--      'deposits_by_ready'
+--      ,modulus
+--      ,num_partitions
+--    );
+--
+--    PERFORM create_table_partition(
+--      'deposits_for_matching'
+--      ,modulus
+--      ,num_partitions
+--    );
+
+    PERFORM create_table_partition(
+      'refunds'
+      ,modulus
+      ,num_partitions
+    );
+    PERFORM add_constraints_to_refunds_partition(num_partitions::varchar);
+
+    PERFORM create_table_partition(
+      'wire_out'
+      ,modulus
+      ,num_partitions
+    );
+    PERFORM add_constraints_to_wire_out_partition(num_partitions::varchar);
+
+    PERFORM create_table_partition(
+      'aggregation_tracking'
+      ,modulus
+      ,num_partitions
+    );
+    PERFORM 
add_constraints_to_aggregation_tracking_partition(num_partitions::varchar);
+
+    PERFORM create_table_partition(
+      'recoup'
+      ,modulus
+      ,num_partitions
+    );
+    PERFORM add_constraints_to_recoup_partition(num_partitions::varchar);
+
+    PERFORM create_table_partition(
+      'recoup_by_reserve'
+      ,modulus
+      ,num_partitions
+    );
+
+    PERFORM create_table_partition(
+      'reserves_out_by_reserve'
+      ,modulus
+      ,num_partitions
+    );
+
+    PERFORM create_table_partition(
+      'recoup_refresh'
+      ,modulus
+      ,num_partitions
+    );
+    PERFORM 
add_constraints_to_recoup_refresh_partition(num_partitions::varchar);
+
+    PERFORM create_table_partition(
+      'prewire'
+      ,modulus
+      ,num_partitions
+    );
+
+    PERFORM create_table_partition(
+      'cs_nonce_locks'
+      ,modulus
+      ,num_partitions
+    );
+    PERFORM 
add_constraints_to_cs_nonce_locks_partition(num_partitions::varchar);
+
+    num_partitions=num_partitions-1;
+    EXIT WHEN num_partitions=0;
+
+  END LOOP;
+
+  PERFORM drop_default_partitions();
+
+END
+$$;
+
+
+CREATE OR REPLACE FUNCTION create_foreign_table(
+    source_table_name VARCHAR
+    ,modulus INTEGER
+    ,shard_suffix VARCHAR
+    ,current_shard_num INTEGER
+  )
+  RETURNS VOID
+  LANGUAGE plpgsql
+AS $$
+BEGIN
+
+  RAISE NOTICE 'Creating %_% on %', source_table_name, shard_suffix, 
shard_suffix;
+
+  EXECUTE FORMAT(
+    'CREATE FOREIGN TABLE IF NOT EXISTS %I '
+      'PARTITION OF %I '
+      'FOR VALUES WITH (MODULUS %s, REMAINDER %s) '
+      'SERVER %I'
+    ,source_table_name || '_' || shard_suffix
+    ,source_table_name
+    ,modulus
+    ,current_shard_num-1
+    ,shard_suffix
+  );
+
+  EXECUTE FORMAT(
+    'ALTER FOREIGN TABLE %I OWNER TO "taler-exchange-httpd"',
+    source_table_name || '_' || shard_suffix
+  );
+
+END
+$$;
+
+CREATE OR REPLACE FUNCTION master_prepare_sharding()
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+BEGIN
+
+  -- CREATE EXTENSION IF NOT EXISTS postgres_fdw;
+
+  PERFORM detach_default_partitions();
+
+  ALTER TABLE IF EXISTS wire_targets
+    DROP CONSTRAINT IF EXISTS wire_targets_pkey CASCADE
+  ;
+
+  ALTER TABLE IF EXISTS reserves
+    DROP CONSTRAINT IF EXISTS reserves_pkey CASCADE
+  ;
+
+  ALTER TABLE IF EXISTS reserves_in
+    DROP CONSTRAINT IF EXISTS reserves_in_pkey CASCADE
+  ;
+
+  ALTER TABLE IF EXISTS reserves_close
+    DROP CONSTRAINT IF EXISTS reserves_close_pkey CASCADE
+  ;
+
+  ALTER TABLE IF EXISTS reserves_out
+    DROP CONSTRAINT IF EXISTS reserves_out_pkey CASCADE
+    ,DROP CONSTRAINT IF EXISTS reserves_out_denominations_serial_fkey
+    ,DROP CONSTRAINT IF EXISTS reserves_out_h_blind_ev_key
+  ;
+
+  ALTER TABLE IF EXISTS known_coins
+    DROP CONSTRAINT IF EXISTS known_coins_pkey CASCADE
+    ,DROP CONSTRAINT IF EXISTS known_coins_denominations_serial_fkey
+  ;
+
+  ALTER TABLE IF EXISTS refresh_commitments
+    DROP CONSTRAINT IF EXISTS refresh_commitments_pkey CASCADE
+    ,DROP CONSTRAINT IF EXISTS refresh_old_coin_pub_fkey
+  ;
+
+  ALTER TABLE IF EXISTS refresh_revealed_coins
+    DROP CONSTRAINT IF EXISTS refresh_revealed_coins_pkey CASCADE
+    ,DROP CONSTRAINT IF EXISTS refresh_revealed_coins_denominations_serial_fkey
+  ;
+
+  ALTER TABLE IF EXISTS refresh_transfer_keys
+    DROP CONSTRAINT IF EXISTS refresh_transfer_keys_pkey CASCADE
+  ;
+
+  ALTER TABLE IF EXISTS deposits
+    DROP CONSTRAINT IF EXISTS deposits_pkey CASCADE
+    ,DROP CONSTRAINT IF EXISTS deposits_extension_details_serial_id_fkey
+    ,DROP CONSTRAINT IF EXISTS 
deposits_shard_known_coin_id_merchant_pub_h_contract_terms_key CASCADE
+  ;
+
+  ALTER TABLE IF EXISTS refunds
+    DROP CONSTRAINT IF EXISTS refunds_pkey CASCADE
+  ;
+
+  ALTER TABLE IF EXISTS wire_out
+    DROP CONSTRAINT IF EXISTS wire_out_pkey CASCADE
+    ,DROP CONSTRAINT IF EXISTS wire_out_wtid_raw_key CASCADE
+  ;
+
+  ALTER TABLE IF EXISTS aggregation_tracking
+    DROP CONSTRAINT IF EXISTS aggregation_tracking_pkey CASCADE
+    ,DROP CONSTRAINT IF EXISTS aggregation_tracking_wtid_raw_fkey
+  ;
+
+  ALTER TABLE IF EXISTS recoup
+    DROP CONSTRAINT IF EXISTS recoup_pkey CASCADE
+  ;
+
+  ALTER TABLE IF EXISTS recoup_refresh
+    DROP CONSTRAINT IF EXISTS recoup_refresh_pkey CASCADE
+  ;
+
+  ALTER TABLE IF EXISTS prewire
+    DROP CONSTRAINT IF EXISTS prewire_pkey CASCADE
+  ;
+
+  ALTER TABLE IF EXISTS cs_nonce_locks
+    DROP CONSTRAINT IF EXISTS cs_nonce_locks_pkey CASCADE
+  ;
+
+END
+$$;
+  
+
+CREATE OR REPLACE FUNCTION create_shard_server(
+    shard_suffix VARCHAR
+    ,total_num_shards INTEGER
+    ,current_shard_num INTEGER
+    ,remote_host VARCHAR
+    ,remote_user VARCHAR
+    ,remote_user_password VARCHAR
+    ,remote_db_name VARCHAR DEFAULT 'taler-exchange'
+    ,remote_port INTEGER DEFAULT '5432'
+)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+BEGIN
+
+  RAISE NOTICE 'Creating server %s', remote_host;
+
+  EXECUTE FORMAT(
+    'CREATE SERVER IF NOT EXISTS %I '
+      'FOREIGN DATA WRAPPER postgres_fdw '
+      'OPTIONS (dbname %L, host %L, port %L)'
+    ,shard_suffix
+    ,remote_db_name
+    ,remote_host
+    ,remote_port
+  );
+
+  EXECUTE FORMAT(
+    'CREATE USER MAPPING IF NOT EXISTS '
+      'FOR "taler-exchange-httpd" SERVER %I '
+      'OPTIONS (user %L, password %L)'
+    ,shard_suffix
+    ,remote_user
+    ,remote_user_password
+  );
+
+  PERFORM create_foreign_table(
+    'wire_targets'
+    ,total_num_shards
+    ,shard_suffix
+    ,current_shard_num
+  );
+  PERFORM create_foreign_table(
+    'reserves'
+    ,total_num_shards
+    ,shard_suffix
+    ,current_shard_num
+  );
+  PERFORM create_foreign_table(
+    'reserves_in'
+    ,total_num_shards
+    ,shard_suffix
+    ,current_shard_num
+  );
+  PERFORM create_foreign_table(
+    'reserves_out'
+    ,total_num_shards
+    ,shard_suffix
+    ,current_shard_num
+  );
+  PERFORM create_foreign_table(
+    'reserves_close'
+    ,total_num_shards
+    ,shard_suffix
+    ,current_shard_num
+  );
+  PERFORM create_foreign_table(
+    'known_coins'
+    ,total_num_shards
+    ,shard_suffix
+    ,current_shard_num
+  );
+  PERFORM create_foreign_table(
+    'refresh_commitments'
+    ,total_num_shards
+    ,shard_suffix
+    ,current_shard_num
+  );
+  PERFORM create_foreign_table(
+    'refresh_revealed_coins'
+    ,total_num_shards
+    ,shard_suffix
+    ,current_shard_num
+  );
+  PERFORM create_foreign_table(
+    'refresh_transfer_keys'
+    ,total_num_shards
+    ,shard_suffix
+    ,current_shard_num
+  );
+  PERFORM create_foreign_table(
+    'deposits'
+    ,total_num_shards
+    ,shard_suffix
+    ,current_shard_num
+  );
+--  PERFORM create_foreign_table(
+--    'deposits_by_ready'
+--    ,total_num_shards
+--    ,shard_suffix
+--    ,current_shard_num
+--  );
+--  PERFORM create_foreign_table(
+--    'deposits_for_matching'
+--    ,total_num_shards
+--    ,shard_suffix
+--    ,current_shard_num
+--  );
+  PERFORM create_foreign_table(
+    'refunds'
+    ,total_num_shards
+    ,shard_suffix
+    ,current_shard_num
+  );
+  PERFORM create_foreign_table(
+    'wire_out'
+    ,total_num_shards
+    ,shard_suffix
+    ,current_shard_num
+  );
+  PERFORM create_foreign_table(
+    'aggregation_tracking'
+    ,total_num_shards
+    ,shard_suffix
+    ,current_shard_num
+  );
+  PERFORM create_foreign_table(
+    'recoup'
+    ,total_num_shards
+    ,shard_suffix
+    ,current_shard_num
+  );
+  PERFORM create_foreign_table(
+    'recoup_by_reserve'
+    ,total_num_shards
+    ,shard_suffix
+    ,current_shard_num
+  );
+  PERFORM create_foreign_table(
+    'reserves_out_by_reserve'
+    ,total_num_shards
+    ,shard_suffix
+    ,current_shard_num
+  );
+  PERFORM create_foreign_table(
+    'recoup_refresh'
+    ,total_num_shards
+    ,shard_suffix
+    ,current_shard_num
+  );
+  PERFORM create_foreign_table(
+    'prewire'
+    ,total_num_shards
+    ,shard_suffix
+    ,current_shard_num
+  );
+  PERFORM create_foreign_table(
+    'cs_nonce_locks'
+    ,total_num_shards
+    ,shard_suffix
+    ,current_shard_num
+  );
+
+END
+$$;
+
+COMMIT;
diff --git a/sql/shard-0001.sql b/sql/shard-0001.sql
new file mode 100644
index 0000000..a5134b3
--- /dev/null
+++ b/sql/shard-0001.sql
@@ -0,0 +1,69 @@
+BEGIN;
+
+CREATE OR REPLACE FUNCTION setup_shard(
+  shard_suffix VARCHAR
+)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+BEGIN
+
+  PERFORM create_table_wire_targets(shard_suffix);
+  PERFORM add_constraints_to_wire_targets_partition(shard_suffix);
+
+  PERFORM create_table_reserves(shard_suffix);
+
+  PERFORM create_table_reserves_in(shard_suffix);
+  PERFORM add_constraints_to_reserves_in_partition(shard_suffix);
+
+  PERFORM create_table_reserves_close(shard_suffix);
+
+  PERFORM create_table_reserves_out(shard_suffix);
+
+  PERFORM create_table_known_coins(shard_suffix);
+  PERFORM add_constraints_to_known_coins_partition(shard_suffix);
+
+  PERFORM create_table_refresh_commitments(shard_suffix);
+  PERFORM add_constraints_to_refresh_commitments_partition(shard_suffix);
+ 
+  PERFORM create_table_refresh_revealed_coins(shard_suffix);
+  PERFORM add_constraints_to_refresh_revealed_coins_partition(shard_suffix);
+  
+  PERFORM create_table_refresh_transfer_keys(shard_suffix);
+  PERFORM add_constraints_to_refresh_transfer_keys_partition(shard_suffix);
+  
+  PERFORM create_table_deposits(shard_suffix);
+  PERFORM add_constraints_to_deposits_partition(shard_suffix);
+
+  PERFORM create_table_deposits_by_ready(shard_suffix);
+
+  PERFORM create_table_deposits_for_matching(shard_suffix);
+  
+  PERFORM create_table_refunds(shard_suffix);
+  PERFORM add_constraints_to_refunds_partition(shard_suffix);
+  
+  PERFORM create_table_wire_out(shard_suffix);
+  PERFORM add_constraints_to_wire_out_partition(shard_suffix);
+  
+  PERFORM create_table_aggregation_tracking(shard_suffix);
+  PERFORM add_constraints_to_aggregation_tracking_partition(shard_suffix);
+
+  PERFORM create_table_recoup(shard_suffix);
+  PERFORM add_constraints_to_recoup_partition(shard_suffix);
+
+  PERFORM create_table_recoup_by_reserve(shard_suffix);
+
+  PERFORM create_table_reserves_out_by_reserve(shard_suffix);
+
+  PERFORM create_table_recoup_refresh(shard_suffix);
+  PERFORM add_constraints_to_recoup_refresh_partition(shard_suffix);
+
+  PERFORM create_table_prewire(shard_suffix);
+
+  PERFORM create_table_cs_nonce_locks(shard_suffix);
+  PERFORM add_constraints_to_cs_nonce_locks_partition(shard_suffix);
+
+END
+$$;
+  
+COMMIT;

-- 
To stop receiving notification emails like this one, please contact
gnunet@gnunet.org.



reply via email to

[Prev in Thread] Current Thread [Next in Thread]