packages feed

pgmq-migration-0.1.0.0: database/v1.9.0/08_partitioning.sql

------------------------------------------------------------
-- Partitioning support functions
------------------------------------------------------------

CREATE FUNCTION pgmq._get_pg_partman_schema()
RETURNS TEXT AS $$
    SELECT extnamespace::regnamespace::text
    FROM pg_extension
    WHERE extname = 'pg_partman';
$$ LANGUAGE sql STABLE;

CREATE FUNCTION pgmq._get_partition_col(queue_name TEXT)
RETURNS TEXT AS $$
DECLARE
    _partition_col TEXT;
    _table_name TEXT := pgmq.format_table_name(queue_name, 'q');
BEGIN
    SELECT part_config.partition_interval INTO _partition_col
    FROM pg_class
    JOIN pg_namespace ON pg_class.relnamespace = pg_namespace.oid
    JOIN (SELECT parent_table, partition_interval FROM pgmq._get_partman_config()) AS part_config
      ON part_config.parent_table = pg_namespace.nspname || '.' || pg_class.relname
    WHERE pg_class.relname = _table_name
      AND pg_namespace.nspname = 'pgmq';

    RETURN _partition_col;
END;
$$ LANGUAGE plpgsql;

CREATE FUNCTION pgmq._get_partman_config()
RETURNS TABLE (parent_table text, partition_interval text) AS $$
DECLARE
    schema_name TEXT;
BEGIN
    SELECT pgmq._get_pg_partman_schema() INTO schema_name;
    IF schema_name IS NULL THEN
        RETURN QUERY SELECT NULL::text, NULL::text WHERE FALSE;
        RETURN;
    END IF;
    RETURN QUERY EXECUTE FORMAT(
        $QUERY$
        SELECT parent_table, partition_interval
        FROM %I.part_config
        $QUERY$,
        schema_name
    );
END;
$$ LANGUAGE plpgsql;

-- partitioned, with partition interval
CREATE FUNCTION pgmq.create_partitioned(
  queue_name TEXT,
  partition_interval TEXT DEFAULT '10000',
  retention_interval TEXT DEFAULT '100000'
)
RETURNS void AS $$
DECLARE
  qtable TEXT := pgmq.format_table_name(queue_name, 'q');
  qtable_seq TEXT := qtable || '_msg_id_seq';
  atable TEXT := pgmq.format_table_name(queue_name, 'a');
  atable_seq TEXT := atable || '_msg_id_seq';
BEGIN
  PERFORM pgmq.validate_queue_name(queue_name);
  PERFORM pgmq.acquire_queue_lock(queue_name);

  EXECUTE FORMAT(
    $QUERY$
    CREATE TABLE IF NOT EXISTS pgmq.%I (
        msg_id BIGINT GENERATED ALWAYS AS IDENTITY,
        read_ct INT DEFAULT 0 NOT NULL,
        enqueued_at TIMESTAMP WITH TIME ZONE DEFAULT now() NOT NULL,
        vt TIMESTAMP WITH TIME ZONE NOT NULL,
        message JSONB,
        headers JSONB
    ) PARTITION BY RANGE (msg_id)
    $QUERY$,
    qtable
  );

  EXECUTE FORMAT(
    $QUERY$
    CREATE TABLE IF NOT EXISTS pgmq.%I (
      msg_id BIGINT NOT NULL,
      read_ct INT DEFAULT 0 NOT NULL,
      enqueued_at TIMESTAMP WITH TIME ZONE DEFAULT now() NOT NULL,
      archived_at TIMESTAMP WITH TIME ZONE DEFAULT now() NOT NULL,
      vt TIMESTAMP WITH TIME ZONE NOT NULL,
      message JSONB,
      headers JSONB
    ) PARTITION BY RANGE (msg_id);
    $QUERY$,
    atable
  );

  IF NOT pgmq._extension_exists('pg_partman') THEN
    RAISE EXCEPTION 'pg_partman is required for partitioned queues';
  END IF;

  EXECUTE FORMAT(
    $QUERY$
    SELECT %I.create_parent(
        p_parent_table := 'pgmq.%I',
        p_control := 'msg_id',
        p_type := 'range',
        p_interval := %L,
        p_premake := 1
    )
    $QUERY$,
    pgmq._get_pg_partman_schema(), qtable, partition_interval
  );

  EXECUTE FORMAT(
    $QUERY$
    SELECT %I.create_parent(
        p_parent_table := 'pgmq.%I',
        p_control := 'msg_id',
        p_type := 'range',
        p_interval := %L,
        p_premake := 1
    )
    $QUERY$,
    pgmq._get_pg_partman_schema(), atable, partition_interval
  );

  EXECUTE FORMAT(
    $QUERY$
    UPDATE %I.part_config
    SET
        retention = %L,
        retention_keep_table = false,
        retention_keep_index = true,
        automatic_maintenance = 'on'
    WHERE parent_table = 'pgmq.%I';
    $QUERY$,
    pgmq._get_pg_partman_schema(), retention_interval, qtable
  );

  EXECUTE FORMAT(
    $QUERY$
    UPDATE %I.part_config
    SET
        retention = %L,
        retention_keep_table = false,
        retention_keep_index = true,
        automatic_maintenance = 'on'
    WHERE parent_table = 'pgmq.%I';
    $QUERY$,
    pgmq._get_pg_partman_schema(), retention_interval, atable
  );

  EXECUTE FORMAT(
    $QUERY$
    CREATE INDEX IF NOT EXISTS %I ON pgmq.%I (vt ASC);
    $QUERY$,
    qtable || '_vt_idx', qtable
  );

  EXECUTE FORMAT(
    $QUERY$
    CREATE INDEX IF NOT EXISTS %I ON pgmq.%I (archived_at);
    $QUERY$,
    'archived_at_idx_' || queue_name, atable
  );

  EXECUTE FORMAT(
    $QUERY$
    INSERT INTO pgmq.meta (queue_name, is_partitioned, is_unlogged)
    VALUES (%L, true, false)
    ON CONFLICT
    DO NOTHING;
    $QUERY$,
    queue_name
  );

END;
$$ LANGUAGE plpgsql;

-- convert_archive_partitioned
CREATE FUNCTION pgmq.convert_archive_partitioned(
  queue_name TEXT,
  partition_interval TEXT DEFAULT '10000',
  retention_interval TEXT DEFAULT '100000',
  leading_partition INT DEFAULT 10
)
RETURNS void AS $$
DECLARE
  atable TEXT := pgmq.format_table_name(queue_name, 'a');
BEGIN
  EXECUTE FORMAT(
    $QUERY$
    ALTER TABLE IF EXISTS pgmq.%I RENAME TO %I
    $QUERY$,
    atable, atable || '_old'
  );

  EXECUTE FORMAT(
    $QUERY$
    CREATE TABLE IF NOT EXISTS pgmq.%I (
      msg_id BIGINT NOT NULL,
      read_ct INT DEFAULT 0 NOT NULL,
      enqueued_at TIMESTAMP WITH TIME ZONE DEFAULT now() NOT NULL,
      archived_at TIMESTAMP WITH TIME ZONE DEFAULT now() NOT NULL,
      vt TIMESTAMP WITH TIME ZONE NOT NULL,
      message JSONB,
      headers JSONB
    ) PARTITION BY RANGE (msg_id);
    $QUERY$,
    atable
  );

  IF NOT pgmq._extension_exists('pg_partman') THEN
    RAISE EXCEPTION 'pg_partman is required for partitioned queues';
  END IF;

  EXECUTE FORMAT(
    $QUERY$
    SELECT %I.create_parent(
        p_parent_table := 'pgmq.%I',
        p_control := 'msg_id',
        p_type := 'range',
        p_interval := %L,
        p_premake := %L
    )
    $QUERY$,
    pgmq._get_pg_partman_schema(), atable, partition_interval, leading_partition
  );

  EXECUTE FORMAT(
    $QUERY$
    UPDATE %I.part_config
    SET
        retention = %L,
        retention_keep_table = false,
        retention_keep_index = true,
        automatic_maintenance = 'on'
    WHERE parent_table = 'pgmq.%I';
    $QUERY$,
    pgmq._get_pg_partman_schema(), retention_interval, atable
  );

  EXECUTE FORMAT(
    $QUERY$
    INSERT INTO pgmq.%I (msg_id, read_ct, enqueued_at, archived_at, vt, message, headers)
    SELECT msg_id, read_ct, enqueued_at, archived_at, vt, message, headers FROM pgmq.%I
    $QUERY$,
    atable, atable || '_old'
  );

  EXECUTE FORMAT(
    $QUERY$
    CREATE INDEX IF NOT EXISTS %I ON pgmq.%I (archived_at);
    $QUERY$,
    'archived_at_idx_' || queue_name, atable
  );

  EXECUTE FORMAT(
    $QUERY$
    DROP TABLE IF EXISTS pgmq.%I
    $QUERY$,
    atable || '_old'
  );
END;
$$ LANGUAGE plpgsql;