Привет, Я DocuDroid!
Оценка ИИ поиска
Спасибо за оценку нашего ИИ поиска!
Мы будем признательны, если вы поделитесь своими впечатлениями, чтобы мы могли улучшить наш ИИ поиск для вас и других читателей.
GitHub

Несовместимости SQL между Greengage DB 6 и 7

Андрей Аксенов
Contents

Эта статья описывает несовместимости на уровне SQL между Greengage DB 6 и 7 с примерами, показывающими, как выявить и устранить каждую несовместимость перед миграцией данных.

Предварительные требования

Примеры в этой статье предназначены для выполнения на Greengage DB 6 и помогают выявить и устранить несовместимости до миграции на Greengage DB 7. Каждый сценарий демонстрирует SQL, который успешно выполняется в версии 6, но приводит к ошибкам при восстановлении объектов из логического бэкапа в версии 7.

Для выполнения команд, описанных в следующих разделах, подключитесь к мастер-хосту Greengage DB с помощью psql, как описано в статье Подключение к Greengage DB с использованием psql. Создайте новую базу данных и подключитесь к ней:

CREATE DATABASE demo;
\c demo

Партиционирование: параметры оптимизированных для добавления таблиц

Сценарий

Партиционированные таблицы в Greengage DB позволяют указывать разные типы хранения для отдельных партиций. Партиции, оптимизированные для добавления (AO), также могут задавать дополнительные параметры хранения, такие как compresstype и compresslevel.

В Greengage DB 6, если для партиции не указан AO-параметр, применяется значение по умолчанию.

В Greengage DB 7, если AO-параметр не указан явно, он наследуется от родительской таблицы. Это изменение может повлиять на зависимые параметры и привести к неподдерживаемым конфигурациям хранения.

Следующий пример определяет партиционированную таблицу, где для родительской таблицы указан compresstype = zstd; для дочерней партиции compresstype не указан, но задан compresslevel = 0:

CREATE TABLE sales
(
    id     INT,
    date   DATE,
    region TEXT,
    amount DECIMAL(10, 2)
)
    WITH (appendoptimized = true, compresstype = zstd, compresslevel = 1)
    DISTRIBUTED BY (id)
    PARTITION BY LIST (region)
        (
        PARTITION asia VALUES ('Asia')
            WITH (appendoptimized = true, compresslevel = 0),
        PARTITION europe VALUES ('Europe')
        );
INSERT INTO sales(id, date, region, amount)
VALUES (1, '2024-01-10', 'Asia', 100.00),
       (2, '2024-01-11', 'Europe', 200.00),
       (3, '2024-01-12', 'Asia', 150.00),
       (4, '2024-01-13', 'Europe', 300.00),
       (5, '2024-01-14', 'Asia', 250.00);

В Greengage DB 6 партиция asia использует compresstype = none по умолчанию. В Greengage DB 7 партиция asia унаследует compresstype = zstd от родительской таблицы. В результате при создании таблицы может возникнуть следующая ошибка:

ERROR:  compresstype "zstd" can't be used with compresslevel 0

Как выявить

Чтобы найти партиционированные таблицы, у которых дочерние партиции не задают явно параметры хранения, определенные в родительской таблице, выполните следующий запрос:

SELECT schemaname,
       tablename,
       partitionschemaname,
       partitiontablename,
       par.reloptions AS parent_options,
       c.reloptions   AS child_options,
       po             AS parent_option
FROM pg_catalog.pg_partitions p
         JOIN pg_catalog.pg_class par ON par.oid = format('%s.%s', schemaname, tablename)::regclass
         JOIN pg_catalog.pg_class c ON c.oid = format('%s.%s', partitionschemaname, partitiontablename)::regclass
         JOIN UNNEST(par.reloptions) po ON TRUE
         LEFT JOIN UNNEST(c.reloptions) co ON split_part(po, '=', 1) = split_part(co, '=', 1)
WHERE co IS NULL;

Запрос возвращает параметры родительской партиционированной таблицы, которые не определены для соответствующих дочерних партиций. Пример вывода:

-[ RECORD 1 ]-------+----------------------------------------------------
schemaname          | public
tablename           | sales
partitionschemaname | public
partitiontablename  | sales_1_prt_asia
parent_options      | {appendonly=true,compresstype=zstd,compresslevel=1}
child_options       | {appendonly=true,compresslevel=0}
parent_option       | compresstype=zstd
РЕКОМЕНДАЦИЯ

Пример вывода отображается в режиме расширенного форматирования, где каждый столбец выводится на отдельной строке. Этот режим можно включить в psql с помощью метакоманды \x.

Как исправить

Создайте копию затронутой партиции с явно заданным compresstype = none:

CREATE TABLE sales_asia_copy
(
    LIKE sales_1_prt_asia INCLUDING ALL
)
    WITH (compresstype = none);

Скопируйте данные из исходной партиции:

INSERT INTO sales_asia_copy
SELECT *
FROM sales_1_prt_asia;

Замените исходную партицию обновленной копией:

ALTER TABLE sales
    EXCHANGE PARTITION asia
        WITH TABLE sales_asia_copy
        WITH VALIDATION;

Удалите временную таблицу:

DROP TABLE sales_asia_copy;

Партиционирование на основе списков по нескольким столбцам

Сценарий

Начиная с Greengage DB 7 партиционирование на основе списков по нескольким столбцам больше не поддерживается, что приводит к следующей ошибке:

ERROR: cannot use "list" partition strategy with more than one column

В следующем примере используется ключ партиционирования на основе столбцов region и channel:

CREATE TABLE sales
(
    id      INT,
    region  TEXT,
    channel TEXT,
    amount  DECIMAL(10, 2)
)
    DISTRIBUTED BY (id)
    PARTITION BY LIST (region, channel)
        (
        VALUES (('Asia', 'Online'), ('Asia', 'Retail')),
        VALUES (('Europe', 'Online'), ('Europe', 'Retail'))
        );
INSERT INTO sales (id, region, channel, amount)
VALUES (1, 'Asia', 'Online', 100.00),
       (2, 'Asia', 'Retail', 120.00),
       (3, 'Europe', 'Online', 180.00),
       (4, 'Europe', 'Retail', 200.00),
       (5, 'Asia', 'Online', 150.00);

Как выявить

Чтобы найти таблицы, использующие стратегию партиционирования LIST с более чем одним столбцом партиционирования, используйте следующий запрос:

SELECT n.nspname,
       c.relname
FROM pg_catalog.pg_class c
         JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
         JOIN pg_catalog.pg_partition p ON p.parrelid = c.oid
WHERE parkind = 'l'
  AND parnatts > 1;

Пример вывода:

 nspname | relname
---------+---------
 public  | sales
(1 row)

Как исправить

Пересоздайте партиционированную таблицу с использованием структуры, поддерживаемой в Greengage DB 7. В следующем примере в качестве ключа партиционирования используется один столбец-массив (region_channel):

CREATE TABLE sales
(
    id             INT,
    region_channel TEXT[],
    amount         DECIMAL(10, 2)
)
    DISTRIBUTED BY (id)
    PARTITION BY LIST (region_channel)
        (
        VALUES ('{Asia,Online}', '{Asia,Retail}'),
        VALUES ('{Europe,Online}', '{Europe,Retail}')
        );

Вставьте данные, используя формат массива:

INSERT INTO sales (id, region_channel, amount)
VALUES (1, '{Asia,Online}', 100.00),
       (2, '{Asia,Retail}', 120.00),
       (3, '{Europe,Online}', 180.00),
       (4, '{Europe,Retail}', 200.00),
       (5, '{Asia,Online}', 150.00);

Партиционирование: ключи для ограничений уникальности

Сценарий

В Greengage DB 7 уникальный индекс для партиционированной таблицы должен включать все столбцы партиционирования. Если столбец партиционирования не включен в уникальный индекс, возникает следующая ошибка:

ERROR: unique constraint on partitioned table must include all partitioning keys

В следующей таблице столбец id используется как ключ распределения, а столбец region — как ключ партиционирования:

CREATE TABLE sales
(
    id     INT NOT NULL,
    region TEXT,
    amount DECIMAL(10, 2)
)
    DISTRIBUTED BY (id)
    PARTITION BY LIST (region)
        (
        VALUES ('Asia'),
        VALUES ('Europe')
        );
INSERT INTO sales (id, region, amount)
VALUES (1, 'Asia', 100.00),
       (1, 'Europe', 150.00),
       (2, 'Asia', 200.00);

Команда ниже создает уникальный индекс только по столбцу id, без учета ключа партиции. В Greengage DB 6 она выполняется успешно, а в Greengage DB 7 приводит к ошибке:

CREATE UNIQUE INDEX sales_id_idx ON sales (id);

Как выявить

Чтобы найти уникальные индексы на партиционированных таблицах, которые не включают все столбцы партиционирования, выполните следующий запрос:

WITH partitions AS (SELECT DISTINCT n.nspname,
                                    c.relname,
                                    c.oid,
                                    p.paratts
                    FROM pg_catalog.pg_partition p
                             JOIN pg_catalog.pg_class c ON (p.parrelid = c.oid)
                             JOIN pg_catalog.pg_namespace n ON (n.oid = c.relnamespace))
SELECT p.nspname,
       p.relname,
       ir.relname AS indexname
FROM pg_catalog.pg_index i
         JOIN partitions p ON p.oid = i.indrelid
         JOIN pg_catalog.pg_class ir ON ir.oid = i.indexrelid
WHERE (i.indisunique OR i.indisprimary)
  AND NOT (p.paratts <@ i.indkey);

Пример вывода:

 nspname | relname |  indexname
---------+---------+--------------
 public  | sales   | sales_id_idx
(1 row)

Как исправить

Удалите индекс, который не включает все столбцы партиционирования:

DROP INDEX sales_id_idx;

Создайте новый индекс, включающий все столбцы партиционирования:

CREATE UNIQUE INDEX sales_id_region_idx ON sales (id, region);

Партиционирование: модификаторы EXCLUSIVE/INCLUSIVE для числовых и текстовых диапазонов

Сценарий

В Greengage DB 7 партиции на основе диапазонов не поддерживают START EXCLUSIVE и END INCLUSIVE для столбцов текстового и числовых типов (например, float4, float8, numeric). Может возникнуть следующая ошибка:

ERROR:  START EXCLUSIVE not supported for partition key data type: numeric
HINT:  Specify an inclusive START value and remove the EXCLUSIVE keyword

или:

ERROR:  END INCLUSIVE not supported for partition key data type: numeric
HINT:  Specify an exclusive END value and remove the INCLUSIVE keyword

Следующий пример создает таблицу, где нижняя граница диапазона для столбца amount типа decimal задана как EXCLUSIVE:

CREATE TABLE sales
(
    id     INT,
    date   DATE,
    amount DECIMAL(10, 2)
)
    WITH (appendoptimized = true)
    DISTRIBUTED BY (id)
    PARTITION BY RANGE (amount)
        (
        START (0) EXCLUSIVE
            END (1000)
            EVERY (250)
        );

Как выявить

Чтобы найти таблицы, использующие стратегию партиционирования RANGE с числовыми или текстовыми ключами партиционирования, выполните следующий запрос:

SELECT n.nspname,
       c.relname,
       t.typname,
       pr.parchildrelid::regclass
FROM pg_catalog.pg_partition p
         JOIN pg_catalog.pg_partition_rule pr ON p.oid = pr.paroid
         JOIN pg_catalog.pg_class c ON (p.parrelid = c.oid)
         JOIN pg_catalog.pg_namespace n ON (n.oid = c.relnamespace)
         JOIN pg_catalog.pg_attribute a ON a.attrelid = c.oid AND a.attnum = ANY (p.paratts)
         JOIN pg_catalog.pg_type t ON t.oid = a.atttypid
WHERE t.typname IN ('text', 'float8', 'float4', 'numeric')
  AND (NOT pr.parrangestartincl OR pr.parrangeendincl);

Пример вывода:

 nspname | relname | typname | parchildrelid
---------+---------+---------+---------------
 public  | sales   | numeric | sales_1_prt_1
(1 row)

Как исправить

Создайте таблицу заново, используя поддерживаемый тип ключа партиционирования или без границ EXCLUSIVE:

CREATE TABLE sales
(
    id     INT,
    date   DATE,
    amount DECIMAL(10, 2)
)
    WITH (appendoptimized = true)
    DISTRIBUTED BY (id)
    PARTITION BY RANGE (amount)
        (
        START (0)
            END (1000)
            EVERY (250)
        );

Удаленные типы данных

Сценарий

Следующие типы данных были удалены в Greengage DB 7:

  • unknown

  • reltime

  • tinterval

  • abstime

Использование этих типов в Greengage DB 7 приводит к следующим ошибкам:

ERROR: type "unknown" does not exist
ERROR: type "reltime" does not exist
ERROR: type "tinterval" does not exist
ERROR: type "abstime" does not exist

Следующий пример создает таблицу со столбцами этих типов:

CREATE TABLE user_activity_log
(
    event_id       INT,
    user_note      unknown,
    session_length reltime,
    active_window  tinterval,
    created_at     abstime
);
INSERT INTO user_activity_log
VALUES (1, 'login from mobile', '45 minutes',
        tinterval('2024-01-10 08:00:00'::abstime, '2024-01-10 08:45:00'::abstime),
        '2024-01-10 08:45:00'),
       (2, 'password change', '5 minutes',
        tinterval('2024-01-10 09:10:00'::abstime, '2024-01-10 09:15:00'::abstime),
        '2024-01-10 09:15:00'),
       (3, 'checkout completed', '12 minutes',
        tinterval('2024-01-10 10:00:00'::abstime, '2024-01-10 10:12:00'::abstime),
        '2024-01-10 10:12:00');

Как выявить

Создайте вспомогательную схему:

CREATE SCHEMA __ggmigrate_tmp;

Создайте функцию, которая рекурсивно находит все объекты, зависящие от заданных типов:

CREATE FUNCTION __ggmigrate_tmp.data_type_checks(base_oids REGTYPE[])
    RETURNS TABLE
            (
                nspname NAME,
                relname NAME,
                attname NAME
            )
AS
$$
DECLARE
    result_oids    REGTYPE[];
    dependent_oids REGTYPE[];
BEGIN
    dependent_oids = base_oids;
    result_oids = base_oids;
    WHILE array_length(dependent_oids, 1) IS NOT NULL
        LOOP
            dependent_oids := ARRAY(
                    SELECT t.oid
                    FROM (SELECT t.oid
                          FROM pg_catalog.pg_type t,
                               unnest(dependent_oids) AS x(oid)
                          WHERE typbasetype = x.oid
                            AND typtype = 'd'
                          UNION ALL
                          SELECT t.oid
                          FROM pg_catalog.pg_type t,
                               unnest(dependent_oids) AS x(oid)
                          WHERE typelem = x.oid
                            AND typtype = 'b'
                          UNION ALL
                          SELECT t.oid
                          FROM pg_catalog.pg_type t
                                   JOIN pg_catalog.pg_class c ON t.oid = c.reltype
                                   JOIN pg_catalog.pg_attribute a ON c.oid = a.attrelid
                          WHERE t.typtype = 'c'
                            AND NOT a.attisdropped
                            AND a.atttypid = ANY (dependent_oids)
                          UNION ALL
                          SELECT t.oid
                          FROM pg_catalog.pg_type t,
                               pg_catalog.pg_range r,
                               unnest(dependent_oids) AS x(oid)
                          WHERE t.typtype = 'r'
                            AND r.rngtypid = t.oid
                            AND r.rngsubtype = x.oid) AS t
                              );
            result_oids := result_oids || dependent_oids;
        END LOOP;
    RETURN QUERY
        SELECT n.nspname, c.relname, a.attname
        FROM pg_catalog.pg_class c
                 JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid
                 JOIN pg_catalog.pg_attribute a ON c.oid = a.attrelid
        WHERE NOT a.attisdropped
          AND a.atttypid = ANY (result_oids)
          AND c.relkind IN ('r', 'm', 'i')
          AND n.nspname !~ '^pg_temp_'
          AND n.nspname !~ '^pg_toast_temp_'
          AND n.nspname NOT IN ('pg_catalog', 'information_schema');
END;
$$ LANGUAGE plpgsql;

Найдите все таблицы со столбцами, использующими удаленные типы данных:

SELECT *
FROM __ggmigrate_tmp.data_type_checks(
        ARRAY [
            'unknown'::regtype,
            'reltime'::regtype,
            'tinterval'::regtype,
            'abstime'::regtype
            ]
     );

Пример вывода:

 nspname |      relname      |    attname
---------+-------------------+----------------
 public  | user_activity_log | user_note
 public  | user_activity_log | session_length
 public  | user_activity_log | active_window
 public  | user_activity_log | created_at
(4 rows)

Как исправить

Преобразуйте каждый устаревший столбец в поддерживаемый тип:

ALTER TABLE user_activity_log
    ALTER COLUMN user_note TYPE text;
ALTER TABLE user_activity_log
    ALTER COLUMN session_length TYPE interval;
ALTER TABLE user_activity_log
    ALTER COLUMN active_window TYPE text;
ALTER TABLE user_activity_log
    ALTER COLUMN created_at TYPE timestamp;

Удаленные расширения

Сценарий

Следующие расширения были удалены в Greengage DB 7. Их функциональность теперь встроена в ядро:

  • gp_parallel_retrieve_cursor

  • gp_array_agg

  • gp_percentile_agg

Попытка установить их в Greengage DB 7 приводит к следующим ошибкам:

ERROR:  could not open extension control file "/usr/local/gpdb/share/postgresql/extension/gp_parallel_retrieve_cursor.control": No such file or directory
ERROR:  could not open extension control file "/usr/local/gpdb/share/postgresql/extension/gp_array_agg.control": No such file or directory
ERROR:  could not open extension control file "/usr/local/gpdb/share/postgresql/extension/gp_percentile_agg.control": No such file or directory

В следующем примере эти расширения устанавливаются в Greengage DB 6, где они по-прежнему доступны:

CREATE EXTENSION gp_parallel_retrieve_cursor;
CREATE EXTENSION gp_array_agg;
CREATE EXTENSION gp_percentile_agg;

Как выявить

Проверьте, какие из удаленных расширений установлены:

SELECT extname,
       extversion
FROM pg_catalog.pg_extension
WHERE extname IN ('gp_parallel_retrieve_cursor', 'gp_array_agg', 'gp_percentile_agg');

Пример вывода:

           extname           | extversion
-----------------------------+------------
 gp_parallel_retrieve_cursor | 1.0
 gp_array_agg                | 1.0.0
 gp_percentile_agg           | 1.0.0
(3 rows)

Как исправить

Удалите расширения перед миграцией:

DROP EXTENSION IF EXISTS gp_parallel_retrieve_cursor;
DROP EXTENSION IF EXISTS gp_array_agg;
DROP EXTENSION IF EXISTS gp_percentile_agg;

Прекращение поддержки Python 2

Сценарий

В Greengage DB 6 функции могли быть реализованы либо на Python 2 (plpython2u), либо на Python 3 (plpython3u).

Начиная с Greengage DB 7 для создания функций поддерживается только Python 3. Попытка использовать Python 2 приводит к следующей ошибке:

ERROR:  could not open extension control file "/usr/local/gpdb/share/postgresql/extension/plpython2u.control": No such file or directory

В следующем примере регистрируется расширение plpython2u:

CREATE EXTENSION plpython2u;

Функция integer_division_demo() использует оператор /, который в Python 2 выполняет целочисленное деление, если оба операнда являются целыми числами:

CREATE OR REPLACE FUNCTION integer_division_demo(a int, b int)
    RETURNS text
AS
$$
    result = a / b
    return str(result)
$$ LANGUAGE plpython2u;

В Python 3 / всегда возвращает число с плавающей точкой — вместо этого для целочисленного деления необходимо использовать //.

Вызовите функцию:

SELECT integer_division_demo(10, 3);

Пример вывода:

 integer_division_demo
-----------------------
 3
(1 row)

Как выявить

Чтобы найти функции plpython2u и просмотреть их определения, выполните следующий запрос:

SELECT n.nspname,
       p.oid,
       p.proname,
       p.prosrc
FROM pg_catalog.pg_proc p
         JOIN pg_namespace n ON n.oid = p.pronamespace
         JOIN pg_language l ON l.oid = p.prolang
         JOIN pg_pltemplate t ON t.tmplname = l.lanname
WHERE t.tmpllibrary = '$libdir/plpython2';

Пример вывода:

 nspname |  oid  |        proname         |         prosrc
---------+-------+------------------------+------------------------
 public  | 16461 | integer_division_demo  |                       +
         |       |                        |     result = a / b    +
         |       |                        |     return str(result)+

Получить определение функции можно с помощью pg_get_functiondef(func_oid).

Как исправить

ВАЖНО

Перед удалением расширения plpython2u убедитесь, что определения всех функций, реализованных на Python 2, были экспортированы и сохранены.

Удалите расширение plpython2u и все зависимые функции:

DROP EXTENSION plpython2u CASCADE;

Повторно подключитесь к базе данных, так как в рамках одной сессии Greengage DB все функции PL/Python должны использовать один и тот же вариант PL/Python (plpython2u или plpython3u):

\c demo gpadmin

Зарегистрируйте расширение plpython3u:

CREATE EXTENSION plpython3u;

Пересоздайте функцию на Python 3, заменив / на // для целочисленного деления:

CREATE OR REPLACE FUNCTION integer_division_demo(a int, b int)
    RETURNS text
AS
$$
    result = a // b
    return str(result)
$$ LANGUAGE plpython3u;

Проверьте результат:

SELECT integer_division_demo(10, 3);

Пример вывода:

 integer_division_demo
-----------------------
 3
(1 row)

Возвращаемое значение функций EXECUTE ON

Сценарий

Начиная с Greengage DB 7 функции, определенные с атрибутом EXECUTE ON, должны возвращать SETOF. Использование скалярного типа для возвращаемого значения приводит к следующей ошибке:

ERROR: EXECUTE ON ALL SEGMENTS is only supported for set-returning functions

Ниже приведен пример функции с EXECUTE ON ALL SEGMENTS, которая возвращает скалярное значение. Это определение работает в Greengage DB 6, но не поддерживается в Greengage DB 7:

CREATE FUNCTION get_segment_ip()
    RETURNS TEXT
AS
$$
BEGIN
    RETURN inet_server_addr();
END;
$$
    LANGUAGE plpgsql
    NO SQL
    EXECUTE ON ALL SEGMENTS;

В Greengage DB 6 вызов функции создает одну строку для каждого выполнения на сегменте:

SELECT get_segment_ip();

Пример вывода:

 get_segment_ip
----------------
 192.168.1.40
 192.168.1.30
 192.168.1.40
 192.168.1.30
(4 rows)

Как выявить

Чтобы найти пользовательские функции, задающие атрибут EXECUTE ON, но не возвращающие набор (SETOF), выполните следующий запрос:

SELECT n.nspname,
       p.proname
FROM pg_catalog.pg_proc p
         JOIN pg_catalog.pg_language l ON l.oid = p.prolang
         JOIN pg_catalog.pg_type t ON t.oid = p.prorettype
         JOIN pg_catalog.pg_namespace n ON n.oid = p.pronamespace
WHERE NOT proretset
  AND proexeclocation IN ('s', 'm', 'i')
  AND l.lanname NOT IN ('internal')
  AND t.typname NOT IN ('record');

Пример вывода:

 nspname |    proname
---------+----------------
 public  | get_segment_ip
(1 row)

Как исправить

Поскольку тип возвращаемого значения нельзя изменить напрямую, сначала удалите существующую функцию:

DROP FUNCTION get_segment_ip();

Затем пересоздайте ее с RETURNS SETOF и RETURN NEXT:

CREATE FUNCTION get_segment_ip()
    RETURNS SETOF TEXT
AS
$$
BEGIN
    RETURN NEXT inet_server_addr();
END;
$$
    LANGUAGE plpgsql
    NO SQL
    EXECUTE ON ALL SEGMENTS;

Проверьте выполнение функции:

SELECT get_segment_ip();

Пример вывода:

 get_segment_ip
----------------
 192.168.1.30
 192.168.1.30
 192.168.1.40
 192.168.1.40
(4 rows)

Триггеры на уровне операторов

Для работы с триггерами на уровне операторов потребуется внешняя база данных PostgreSQL, которая будет выступать в роли стороннего сервера:

  1. Используйте данный скрипт examples/foreign_tables/script_pg.sql для создания тестовой базы данных, таблиц и пользователя:

    DROP DATABASE IF EXISTS customers;
    CREATE DATABASE customers;
    \c customers
    CREATE TABLE customers
    (
        id INTEGER,
        name VARCHAR(50),
        email VARCHAR(100),
        address VARCHAR(255)
    );
    INSERT INTO customers (id, name, email, address)
    VALUES (1,'John Doe','john.doe@example.com','123 Elm Street'),
           (2,'Jane Smith','jane.smith@example.com','456 Oak Street'),
           (3,'Bob Brown','bob.brown@example.com','789 Pine Street'),
           (4,'Rob Stuart','rob.stuart@example.com','119 Willow Street');
    CREATE USER "user" WITH PASSWORD 'password';
    GRANT ALL ON TABLE customers TO "user";
  2. Выполните из Greengage DB скрипт для настройки доступа к внешнему экземпляру PostgreSQL:

    \! PGPASSWORD=password psql -h postgres -U postgres -f examples/foreign_tables/script_pg.sql
  3. Установите расширение postgres_fdw, которое позволяет обращаться к внешним таблицам:

    CREATE EXTENSION postgres_fdw;
  4. Создайте сторонний сервер, указывающий на экземпляр PostgreSQL:

    CREATE SERVER postgres
        FOREIGN DATA WRAPPER postgres_fdw
        OPTIONS (host 'postgres', port '5432', dbname 'customers');
  5. Создайте сопоставление пользователя для аутентификации:

    CREATE USER MAPPING FOR gpadmin
        SERVER postgres
        OPTIONS (user 'user', password 'password');

Сценарий

В Greengage DB 7 триггеры на уровне операторов не поддерживаются. Может возникнуть следующая ошибка:

ERROR: Triggers for statements are not yet supported

Создайте стороннюю таблицу, которая ссылается на таблицу PostgreSQL:

CREATE FOREIGN TABLE customers
(
    id      INTEGER,
    name    VARCHAR(50),
    email   VARCHAR(100),
    address VARCHAR(255)
) SERVER postgres;

Создайте функцию триггера, выполняющую действие, например отправку уведомления:

CREATE OR REPLACE FUNCTION raise_notice_after_insert()
    RETURNS TRIGGER
AS
$$
BEGIN
    RAISE NOTICE 'Trigger fired';
    RETURN NULL;
END
$$ LANGUAGE plpgsql;

Создайте триггер с FOR EACH STATEMENT для таблицы. Этот тип триггера выполняется один раз для каждого SQL-запроса, независимо от того, сколько строк затронуто:

CREATE TRIGGER raise_notice_for_each_statement
    AFTER INSERT
    ON customers
    FOR EACH STATEMENT
    EXECUTE PROCEDURE raise_notice_after_insert();

Вставьте несколько строк. В Greengage DB 6 триггер уровня оператора срабатывает только один раз для всей вставки:

INSERT INTO customers (id, name, email, address)
VALUES (5, 'Alice Johnson', 'alice.johnson@example.com', '221 Maple Drive'),
       (6, 'David Lee', 'david.lee@example.com', '333 Birch Lane'),
       (7, 'Emily Wilson', 'emily.wilson@example.com', '444 Cedar Court');

Ожидаемый вывод:

NOTICE:  Trigger fired
INSERT 0 3

Как выявить

Для проверки существования триггеров на уровне операторов выполните запрос к системному каталогу:

SELECT n.nspname,
       c.relname,
       tgname
FROM pg_catalog.pg_trigger t
         JOIN pg_catalog.pg_class c ON t.tgrelid = c.oid
         JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
WHERE tgtype & 1 = 0
  AND NOT tgisinternal;

Пример вывода:

 nspname |  relname  |             tgname
---------+-----------+---------------------------------
 public  | customers | raise_notice_for_each_statement
(1 row)

Как исправить

Удалите ранее добавленные строки:

DELETE
FROM customers
WHERE id IN (5, 6, 7);

Удалите существующий триггер уровня оператора:

DROP TRIGGER raise_notice_for_each_statement ON customers;

Пересоздайте триггер с уровнем FOR EACH ROW, чтобы он выполнялся для каждой затронутой строки:

CREATE TRIGGER raise_notice_for_each_row
    AFTER INSERT
    ON customers
    FOR EACH ROW
    EXECUTE PROCEDURE raise_notice_after_insert();

Повторно вставьте данные. Теперь триггер будет срабатывать отдельно для каждой добавленной строки:

INSERT INTO customers (id, name, email, address)
VALUES (5, 'Alice Johnson', 'alice.johnson@example.com', '221 Maple Drive'),
       (6, 'David Lee', 'david.lee@example.com', '333 Birch Lane'),
       (7, 'Emily Wilson', 'emily.wilson@example.com', '444 Cedar Court');

Ожидаемый вывод:

NOTICE:  Trigger fired
NOTICE:  Trigger fired
NOTICE:  Trigger fired
INSERT 0 3

Пользовательские объекты, зависящие от удаленных объектов системного каталога

Сценарий

В Greengage DB 7 удален или изменен ряд объектов системного каталога, включая таблицы, представления и отдельные столбцы. Ниже приведен список удаленных системных таблиц и представлений:

  • gp_toolkit.gp_size_of_partition_and_indexes_disk

  • gp_toolkit.__gp_user_data_tables

  • pg_catalog.pg_partition

  • pg_catalog.pg_partition_columns

  • pg_catalog.pg_partition_encoding

  • pg_catalog.pg_partition_rule

  • pg_catalog.pg_partitions

  • pg_catalog.pg_partition_templates

  • pg_catalog.pg_stat_partition_operations

Любые пользовательские объекты (включая представления и функции), ссылающиеся на эти объекты, перестанут работать после миграции. При восстановлении логического бэкапа создание представлений со ссылками на отсутствующие объекты завершится с ошибкой.

Например, следующее представление ссылается на удаленное представление pg_partitions:

CREATE VIEW table_partitions AS
SELECT schemaname,
       tablename,
       partitionname
FROM pg_partitions;

Как выявить

Чтобы получить список пользовательских представлений, которые ссылаются на системные объекты в своих определениях, выполните следующую команду:

WITH RECURSIVE system_objects AS
    (SELECT
         n.nspname,
         c.relname,
         format('%s.%s', n.nspname, c.relname) AS system_object
     FROM pg_class c
         JOIN pg_namespace n
             ON c.relnamespace = n.oid WHERE nspname IN ('pg_catalog', 'gp_toolkit')
    UNION ALL
    SELECT
        v.schemaname,
        v.viewname,
        c.system_object
    FROM pg_views v
        JOIN system_objects c ON v.definition ~* ('[^\.]\y' || c.relname || '\y')
    WHERE schemaname NOT IN ('pg_catalog', 'information_schema', 'gp_toolkit'))
SELECT *
FROM system_objects
WHERE nspname NOT IN ('pg_catalog', 'information_schema', 'gp_toolkit');

Пример вывода:

 nspname |     relname      |      system_object
---------+------------------+--------------------------
 public  | table_partitions | pg_catalog.pg_partitions
(1 row)

Как исправить

Удалите затронутое представление:

DROP VIEW table_partitions;

Изучите обновленную структуру системных таблиц в Greengage DB 7 и пересоздайте представление при необходимости.

Ресурсные группы

Сценарий

В Greengage DB 7 изменились некоторые параметры ресурсных групп, например:

  • CPU_RATE_LIMIT был переименован в CPU_MAX_PERCENT.

  • MEMORY_SHARED_QUOTA, MEMORY_SPILL_RATIO и MEMORY_AUDITOR были удалены.

Пример ниже создает ресурсную группу с параметрами, используемыми в Greengage DB 6:

CREATE RESOURCE GROUP rg_analysts WITH (
    CPU_RATE_LIMIT = 20,
    MEMORY_LIMIT = 20,
    CONCURRENCY = 30
    );

Как исправить

Выполните gpbackup с параметром --without-globals при создании резервной копии данных старого кластера. Параметр --without-globals гарантирует, что глобальные объекты, такие как ресурсные группы и табличные пространства, не будут выгружены во время резервного копирования. Эти глобальные объекты необходимо заранее создать вручную в новом кластере перед восстановлением данных.