Несовместимости SQL между Greengage DB 6 и 7
- Партиционирование: параметры оптимизированных для добавления таблиц
- Партиционирование на основе списков по нескольким столбцам
- Партиционирование: ключи для ограничений уникальности
- Партиционирование: модификаторы EXCLUSIVE/INCLUSIVE для числовых и текстовых диапазонов
- Партиционирование: шаблоны сабпартиций в иерархиях с тремя и более уровнями
- Удаленные типы данных
- Удаленные расширения
- Прекращение поддержки Python 2
- Возвращаемое значение функций EXECUTE ON
- Триггеры на уровне операторов
- Пользовательские объекты, зависящие от удаленных объектов системного каталога
- Ресурсные группы
Эта статья описывает несовместимости на уровне SQL между Greengage DB 6 и 7 с примерами, показывающими, как выявить и устранить каждую несовместимость перед миграцией данных.
Партиционирование: параметры оптимизированных для добавления таблиц
Сценарий
Партиционированные таблицы в 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 больше не поддерживается обращение к партициям с помощью функции RANK().
Это изменение может затронуть партиционированные таблицы с тремя и более уровнями, если для них используются шаблоны сабпартиций.
Следующий пример создает таблицу с трехуровневой иерархией шаблонов сабпартиций:
CREATE TABLE customer_activity
(
customer_id INT,
activity_date DATE,
activity_time TIMESTAMP,
region_id INT,
customer_segment VARCHAR(20),
channel VARCHAR(20)
)
WITH (appendoptimized = true, orientation = column)
PARTITION BY RANGE (activity_date)
SUBPARTITION BY LIST (region_id)
SUBPARTITION TEMPLATE
(
SUBPARTITION europe VALUES (1,2),
SUBPARTITION asia VALUES (3,4)
)
SUBPARTITION BY LIST (customer_segment)
SUBPARTITION TEMPLATE
(
SUBPARTITION retail VALUES ('Retail'),
SUBPARTITION enterprise VALUES ('Enterprise')
)
SUBPARTITION BY LIST (channel)
SUBPARTITION TEMPLATE
(
SUBPARTITION webchannel VALUES ('Web'),
SUBPARTITION mobileapp VALUES ('Mobile')
)
(
START (DATE '2020-01-01')
END (DATE '2023-01-01') EVERY (INTERVAL '1 year')
);
В Greengage DB 6 gpbackup может создавать DDL-операторы, в которых функция RANK() используется для указания позиции партиции:
ALTER TABLE public.customer_activity ALTER PARTITION FOR (RANK(1)) ALTER PARTITION europe
SET SUBPARTITION TEMPLATE
(
SUBPARTITION webchannel VALUES('Web') WITH (tablename='customer_activity'),
SUBPARTITION mobileapp VALUES('Mobile') WITH (tablename='customer_activity')
);
ALTER TABLE public.customer_activity ALTER PARTITION FOR (RANK(1))
SET SUBPARTITION TEMPLATE
(
SUBPARTITION retail VALUES('Retail') WITH (tablename='customer_activity') ,
SUBPARTITION enterprise VALUES('Enterprise') WITH (tablename='customer_activity')
);
ALTER TABLE public.customer_activity
SET SUBPARTITION TEMPLATE
(
SUBPARTITION europe VALUES(1, 2) WITH (tablename='customer_activity') ,
SUBPARTITION asia VALUES(3, 4) WITH (tablename='customer_activity')
);
При восстановлении такого бэкапа в Greengage DB 7 возникает следующая ошибка:
ERROR: addressing partition by RANK is no longer supported HINT: Use partition name or FOR (<partition key value>) instead.
Как выявить
Чтобы найти таблицы с многоуровневыми шаблонами сабпартиций, выполните запрос к pg_partition_templates:
SELECT schemaname,
tablename,
partitionname,
partitiontype,
partitionlevel,
partitionposition,
partitionlistvalues,
partitionboundary
FROM pg_partition_templates
WHERE partitionlevel > 1;
Пример вывода:
schemaname | tablename | partitionname | partitiontype | partitionlevel | partitionposition | partitionlistvalues | partitionboundary
------------+-------------------+---------------+---------------+----------------+-------------------+-------------------------------------+----------------------------------------------
public | customer_activity | retail | list | 2 | 1 | 'Retail'::character varying(20) | SUBPARTITION retail VALUES('Retail')
public | customer_activity | enterprise | list | 2 | 2 | 'Enterprise'::character varying(20) | SUBPARTITION enterprise VALUES('Enterprise')
public | customer_activity | webchannel | list | 3 | 1 | 'Web'::character varying(20) | SUBPARTITION webchannel VALUES('Web')
public | customer_activity | mobileapp | list | 3 | 2 | 'Mobile'::character varying(20) | SUBPARTITION mobileapp VALUES('Mobile')
(4 rows)
Как исправить
Шаг 1: создайте бэкап существующих шаблонов сабпартиций
Перед изменением структуры партиций сохраните текущие определения шаблонов:
SELECT DISTINCT pg_get_partition_template_def((schemaname || '.' || tablename)::regclass, false, false)
FROM pg_catalog.pg_partition_templates t
WHERE EXISTS(SELECT *
FROM pg_catalog.pg_partition_templates t2
WHERE t2.schemaname = t.schemaname
AND t2.tablename = t.tablename
AND t2.partitionlevel > 1);
Пример вывода:
pg_get_partition_template_def
---------------------------------------------------------------------------------------------------------------------
ALTER TABLE customer_activity ALTER PARTITION FOR (RANK(1)) ALTER PARTITION europe +
SET SUBPARTITION TEMPLATE (SUBPARTITION webchannel VALUES('Web'), SUBPARTITION mobileapp VALUES('Mobile')); +
+
ALTER TABLE customer_activity ALTER PARTITION FOR (RANK(1)) +
SET SUBPARTITION TEMPLATE (SUBPARTITION retail VALUES('Retail') , SUBPARTITION enterprise VALUES('Enterprise') );+
+
ALTER TABLE customer_activity +
SET SUBPARTITION TEMPLATE (SUBPARTITION europe VALUES(1, 2) , SUBPARTITION asia VALUES(3, 4) ) +
(1 row)
Шаг 2: создайте DDL для миграции
Создайте новые операторы ALTER TABLE, которые явно ссылаются на партиции и не используют RANK():
SELECT FORMAT(' ALTER TABLE %s SET SUBPARTITION TEMPLATE ( %s ); ',
COALESCE(partition_name, FORMAT('%I.%I', t.schemaname, t.tablename)),
STRING_AGG(t.partitionboundary, ', ' ORDER BY t.partitionposition))
FROM pg_catalog.pg_partition_templates t
LEFT JOIN (SELECT p.schemaname,
p.tablename,
p.partitionlevel,
MIN(FORMAT('%I.%I', p.partitionschemaname, p.partitiontablename)) AS partition_name
FROM pg_catalog.pg_partitions p
GROUP BY p.schemaname,
p.tablename,
p.partitionlevel) p
ON p.schemaname = t.schemaname
AND p.tablename = t.tablename
AND p.partitionlevel + 2 = t.partitionlevel
WHERE EXISTS(SELECT *
FROM pg_catalog.pg_partition_templates t2
WHERE t2.schemaname = t.schemaname
AND t2.tablename = t.tablename
AND t2.partitionlevel > 1)
GROUP BY partition_name, t.schemaname, t.tablename, t.partitionlevel
ORDER BY t.partitionlevel DESC;
Пример вывода:
format
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------
ALTER TABLE public.customer_activity_1_prt_1_2_prt_asia SET SUBPARTITION TEMPLATE ( SUBPARTITION webchannel VALUES('Web'), SUBPARTITION mobileapp VALUES('Mobile') );
ALTER TABLE public.customer_activity_1_prt_1 SET SUBPARTITION TEMPLATE ( SUBPARTITION retail VALUES('Retail'), SUBPARTITION enterprise VALUES('Enterprise') );
ALTER TABLE public.customer_activity SET SUBPARTITION TEMPLATE ( SUBPARTITION europe VALUES(1, 2), SUBPARTITION asia VALUES(3, 4) );
(3 rows)
Шаг 3: удалите устаревшие определения шаблонов сабпартиций
Удалите существующие определения шаблонов, в которых используется RANK().
Для этого можно использовать скрипт, сгенерированный на шаге 1, предварительно заменив в нем определения шаблонов на пустые строки, например:
ALTER TABLE customer_activity
ALTER PARTITION FOR (RANK (1))
ALTER PARTITION europe
SET SUBPARTITION TEMPLATE ();
ALTER TABLE customer_activity
ALTER PARTITION FOR (RANK (1))
SET SUBPARTITION TEMPLATE ();
ALTER TABLE customer_activity
SET SUBPARTITION TEMPLATE ();
Шаг 4: восстановите данные
Восстановите данные в кластере Greengage DB 7 с помощью gprestore.
Шаг 5: примените шаблоны сабпартиций повторно
После восстановления данных выполните DDL-операторы, созданные на шаге 2, чтобы заново применить шаблоны сабпартиций. Пример сгенерированного скрипта:
ALTER TABLE public.customer_activity_1_prt_1_2_prt_asia
SET SUBPARTITION TEMPLATE (
SUBPARTITION webchannel VALUES ('Web'),
SUBPARTITION mobileapp VALUES ('Mobile')
);
ALTER TABLE public.customer_activity_1_prt_1
SET SUBPARTITION TEMPLATE (
SUBPARTITION retail VALUES ('Retail'),
SUBPARTITION enterprise VALUES ('Enterprise')
);
ALTER TABLE public.customer_activity
SET SUBPARTITION TEMPLATE (
SUBPARTITION europe VALUES (1, 2),
SUBPARTITION asia VALUES (3, 4)
);
Удаленные типы данных
Сценарий
Следующие типы данных были удалены в 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, которая будет выступать в роли стороннего сервера:
-
Используйте данный скрипт 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"; -
Выполните из Greengage DB скрипт для настройки доступа к внешнему экземпляру PostgreSQL:
\! PGPASSWORD=password psql -h postgres -U postgres -f examples/foreign_tables/script_pg.sql -
Установите расширение
postgres_fdw, которое позволяет обращаться к сторонним таблицам:CREATE EXTENSION postgres_fdw; -
Создайте сторонний сервер, указывающий на экземпляр PostgreSQL:
CREATE SERVER postgres FOREIGN DATA WRAPPER postgres_fdw OPTIONS (host 'postgres', port '5432', dbname 'customers'); -
Создайте сопоставление пользователя для аутентификации:
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 гарантирует, что глобальные объекты, такие как ресурсные группы и табличные пространства, не будут выгружены во время резервного копирования.
Эти глобальные объекты необходимо заранее создать вручную в новом кластере перед восстановлением данных.