--
-- This file is part of TALER
-- Copyright (C) 2014--2022 Taler Systems SA
--
-- TALER is free software; you can redistribute it and/or modify it under the
-- terms of the GNU General Public License as published by the Free Software
-- Foundation; either version 3, or (at your option) any later version.
--
-- TALER is distributed in the hope that it will be useful, but WITHOUT ANY
-- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
-- A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
--
-- You should have received a copy of the GNU General Public License along with
-- TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
--

---------------------------------------------------------------------------
--                   Main DB setup loop
---------------------------------------------------------------------------


CREATE OR REPLACE FUNCTION exchange_do_create_tables(
  num_partitions INTEGER
-- NULL: no partitions, add foreign constraints
-- 0: no partitions, no foreign constraints
-- 1: only 1 default partition
-- > 1: normal partitions
)
  RETURNS VOID
  LANGUAGE plpgsql
AS $$
DECLARE
  tc CURSOR FOR
    SELECT table_serial_id
          ,name
          ,action
          ,partitioned
          ,by_range
      FROM exchange.exchange_tables
     WHERE NOT finished
     ORDER BY table_serial_id ASC;
BEGIN
  FOR rec IN tc
  LOOP
    CASE rec.action
    -- "create" actions apply to master and partitions, providing the partition ID to the creation function (if any)
    WHEN 'create'
    THEN
      IF (rec.partitioned AND
          (num_partitions IS NOT NULL))
      THEN
        -- Create master table with partitioning.
        EXECUTE FORMAT(
          'SELECT exchange.create_table_%s (%s)'::text
          ,rec.name
          ,quote_literal('0')
        );
        IF (rec.by_range OR
            (num_partitions = 0))
        THEN
          -- Create default partition.
          IF (rec.by_range)
          THEN
            -- Range partition
            EXECUTE FORMAT(
              'CREATE TABLE exchange.%s_default'
              ' PARTITION OF %s'
              ' DEFAULT'
             ,rec.name
             ,rec.name
            );
          ELSE
            -- Hash partition
            EXECUTE FORMAT(
              'CREATE TABLE exchange.%s_default'
              ' PARTITION OF %s'
              ' FOR VALUES WITH (MODULUS 1, REMAINDER 0)'
             ,rec.name
             ,rec.name
            );
          END IF;
        ELSE
          FOR i IN 1..num_partitions LOOP
            -- Create num_partitions
            EXECUTE FORMAT(
               'CREATE TABLE exchange.%I'
               ' PARTITION OF %I'
               ' FOR VALUES WITH (MODULUS %s, REMAINDER %s)'
              ,rec.name || '_' || i
              ,rec.name
              ,num_partitions
              ,i-1
            );
          END LOOP;
        END IF;
      ELSE
        -- Only create master table. No partitions.
        EXECUTE FORMAT(
          'SELECT exchange.create_table_%s ()'::text
          ,rec.name
        );
      END IF;
      EXECUTE FORMAT(
        'DROP FUNCTION exchange.create_table_%s'::text
          ,rec.name
        );
    -- "alter" actions apply to master and partitions (but are called without partition ID, as altering master applies to partitions for these); use when changing table layouts (adding or removing columns).
    WHEN 'alter'
    THEN
      -- Alter master table.
      EXECUTE FORMAT(
        'SELECT exchange.alter_table_%s ()'::text
        ,rec.name
      );
      EXECUTE FORMAT(
        'DROP FUNCTION exchange.alter_table_%s'::text
          ,rec.name
        );
    -- Constrain action apply to master OR each partition (but not on master if we have partitions); use to create (or remove) indices or constraints that apply to a partition and may not be aligned with the partition key
    WHEN 'constrain'
    THEN
      ASSERT rec.partitioned, 'constrain action only applies to partitioned tables';
      IF (num_partitions IS NULL)
      THEN
        -- Constrain master table
        EXECUTE FORMAT(
           'SELECT exchange.constrain_table_%s (NULL)'::text
          ,rec.name
        );
      ELSE
        IF ( (num_partitions = 0) OR
             (rec.by_range) )
        THEN
          -- Constrain default table
          EXECUTE FORMAT(
             'SELECT exchange.constrain_table_%s (%s)'::text
            ,rec.name
            ,quote_literal('default')
          );
        ELSE
          -- Constrain each partition
          FOR i IN 1..num_partitions LOOP
            EXECUTE FORMAT(
              'SELECT exchange.constrain_table_%s (%s)'::text
              ,rec.name
              ,quote_literal(i)
            );
          END LOOP;
        END IF;
      END IF;
      EXECUTE FORMAT(
        'DROP FUNCTION exchange.constrain_table_%s'::text
          ,rec.name
        );
    -- Foreign actions only apply if partitioning is off; use for foreign-key constraints that may span partitions
    WHEN 'foreign'
    THEN
      IF (num_partitions IS NULL)
      THEN
        -- Add foreign constraints
        EXECUTE FORMAT(
          'SELECT exchange.foreign_table_%s (%s)'::text
          ,rec.name
          ,NULL
        );
      END IF;
      EXECUTE FORMAT(
        'DROP FUNCTION exchange.foreign_table_%s'::text
          ,rec.name
        );
    -- "master" actions only apply to the master table
    WHEN 'master'
    THEN
      EXECUTE FORMAT(
        'SELECT exchange.master_table_%s ()'::text
        ,rec.name
      );
      EXECUTE FORMAT(
        'DROP FUNCTION exchange.master_table_%s'::text
          ,rec.name
        );
    ELSE
      ASSERT FALSE, 'unsupported action type: ' || rec.action;
    END CASE;  -- END CASE (rec.action)
    -- Mark as finished
    UPDATE exchange.exchange_tables
       SET finished=TRUE
     WHERE table_serial_id=rec.table_serial_id;
  END LOOP; -- create/alter/drop actions
END $$;

COMMENT ON FUNCTION exchange_do_create_tables
  IS 'Creates all tables for the given number of partitions that need creating. Does NOT support sharding.';
