Daily MaverickWHAT’S COOKING: Roast butternut soup with roasted garlic, spices and orange zestThe Jerusalem PostIsrael 'in the dark' over flydubai terror hijacker investigation as Mossad probes link to IranPunchAt 70, Alake has made Ekiti proud — OyebanjiBollywood HungamaEggoz onboards Boman Irani as brand ambassadorInquirerGroup hits ‘veiled threats’ to journalists in Sara Duterte’s trialZDF heuteEntdecken Sie das ZDF-NachrichtenstudioUOLTécnico de jiu-jítsu Bruno Formiga é acusado de assédio sexual e estupro por alunas no RJColliderThis Horror Legend's Biggest Bomb Got Better With Its Unrated VersionObservador DesportoHomem aterra avioneta em Serpa e sai de táxiFootball ItaliaLazio linked with move for ex Man Utd and Crystal Palace winger ZahaABC NewsSeeing threats to religious liberty, Alito calls same-sex marriage a 'decisive' turnCNN بالعربيةبرفقة والديها وسروال جينز.. هل تتعمد عارضة أزياء هندية لفت الأنظار في باريس؟
The Daily Newsstand · Free, Always
Tuesday, October 6, 2026

PostgreSQL 19: Коммитфест 2026-03 Часть 5.4 (SQL, DEV1, DEV2)

Translate

Завершаем обзор мартовского коммитфеста 19-й версии. В четвертой и последней статье серии рассмотрим изменения, относящиеся к языку SQL и материалам курсов DEV1 и DEV2.

Напоминаю план статей о последнем коммитфесте 19-й версии:

Об изменениях в предыдущих коммитфестах рассказано здесь: 2025-07, 2025-09, 2025-11, 2026-01.

В этом обзоре:

INSERT … ON CONFLICT DO SELECT
commit: 88327092ff0

Команда INSERT … ON CONFLICT DO NOTHING RETURNING корректно обрабатывает конфликт, но не возвращает существующую строку. В следующем примере после добавления новой строки команда ее возвращает:

CREATE TABLE test (
id int PRIMARY KEY,
value numeric(4,2) DEFAULT random()
);
INSERT INTO test VALUES (1)
ON CONFLICT (id) DO NOTHING
RETURNING id, value;
 id | value 
----+-------
  1 |  0.74
(1 row)

Но если строка уже есть, то фраза RETURNING ничего не вернет:

INSERT INTO test VALUES (1)
ON CONFLICT (id) DO NOTHING
RETURNING id, value;
 id | value 
----+-------
(0 rows)

Если приложению требуется, чтобы команда INSERT всегда возвращала строку с указанным идентификатором, то приходилось проверять статус завершения команды и в случае конфликта делать отдельный SELECT. Это лишний запрос, а между двумя командами строку может удалить или изменить другая транзакция.

Новый вариант ON CONFLICT DO SELECT гарантированно возвращает через RETURNING либо новую, либо уже существующую конфликтующую строку:

INSERT INTO test VALUES (1)
ON CONFLICT (id) DO SELECT
RETURNING id, value;
 id | value 
----+-------
  1 |  0.74
(1 row)

Столбцы для выборки по-прежнему перечисляются во фразе RETURNING, а после SELECT можно указать режим блокировки строки (FOR UPDATE, FOR SHARE и др.) и условие WHERE.

COPY FROM: замена ошибочных значений на NULL
commit: 2a525cc97e1

В 17-й версии COPY научилась игнорировать строки с ошибками преобразования формата данных. Для этого в команду нужно добавить параметр ON_ERROR ignore, и ошибочные строки исключаются из загрузки. Теперь ошибочные значения можно заменять на NULL и загружать строку. Ограничения NOT NULL (в том числе у доменов) при этом проверяются: если в такой столбец попадет NULL, команда завершится ошибкой.

CREATE TABLE test (col int);
COPY test FROM STDIN WITH (on_error set_null);
Enter data to be copied followed by a newline.
End with a backslash and a period on a line by itself, or an EOF signal.
>> 1
>> два
>> 3
>> \.
NOTICE:  in 1 row, columns were set to null due to data type incompatibility
COPY 3
SELECT * FROM test;
 col 
-----
   1
    
   3
(3 rows)

COPY TO: выгрузка данных в формате json
commit: 7dadd38cda9, 4c0390ac53b

Команда COPY теперь может выгружать данные не только в форматах text, csv и binary, но и в формате json:

COPY (SELECT * FROM bookings LIMIT 3) TO STDOUT WITH (format json);
{"book_ref":"2EW1SQ","book_date":"2025-09-01T03:00:12.557744+03:00","total_amount":8125.00}
{"book_ref":"3ZY3O5","book_date":"2025-09-03T03:25:35.278453+03:00","total_amount":7500.00}
{"book_ref":"756UAS","book_date":"2025-09-10T19:24:02.978559+03:00","total_amount":6325.00}

А дополнительный параметр FORCE_ARRAY оборачивает вывод в массив:

COPY (SELECT * FROM bookings LIMIT 3) TO STDOUT WITH (format json, force_array);
[
 {"book_ref":"2EW1SQ","book_date":"2025-09-01T03:00:12.557744+03:00","total_amount":8125.00}
,{"book_ref":"3ZY3O5","book_date":"2025-09-03T03:25:35.278453+03:00","total_amount":7500.00}
,{"book_ref":"756UAS","book_date":"2025-09-10T19:24:02.978559+03:00","total_amount":6325.00}
]

Речь только о выгрузке (COPY TO), а не о загрузке (COPY FROM). Кроме того, с форматом json не поддерживаются некоторые параметры команды, а именно HEADER, DEFAULT, NULL, DELIMITER, FORCE QUOTE, FORCE NOT NULL и FORCE NULL.

jsonpath: новые методы работы со строками
commit: bd4f879a9cd

В язык jsonpath добавили новые методы для работы со строками: lower(), upper(), initcap(), replace(), split_part(), а также btrim(), ltrim() и rtrim():

SELECT jsonb_path_query('"  hello, world!  "', '$.btrim()'),
       jsonb_path_query('"hello, world!"', '$.upper()'),
       jsonb_path_query('"HELLO, WORLD!"', '$.lower()'),
       jsonb_path_query('"hello, world!"', '$.initcap()'),
       jsonb_path_query('"hello, world?"', '$.replace("?","!")'),
       jsonb_path_query('"hello, world!"', '$.split_part(", ",1)')
\gx
-[ RECORD 1 ]----+----------------
jsonb_path_query | "hello, world!"
jsonb_path_query | "HELLO, WORLD!"
jsonb_path_query | "hello, world!"
jsonb_path_query | "Hello, World!"
jsonb_path_query | "hello, world!"
jsonb_path_query | "hello"

Предикат IS JSON с доменными типами
commit: 3b4c2b9db25

Предикат IS JSON раньше не принимал домены, базовым типом которых являются json, jsonb, bytea или text — сервер не определял базовый тип домена.

CREATE DOMAIN js AS jsonb;
18=# SELECT '"Hello, World!"'::js IS JSON;
ERROR:  cannot use type js in IS JSON predicate

Проверка исправлена:

19=# SELECT '"Hello, World!"'::js IS JSON;
 ?column? 
----------
 t
(1 row)

postgres_fdw: удаленные транзакции наследуют режимы READ ONLY и DEFERRABLE
commit: de28140ded8

Удаленная транзакция, открываемая postgres_fdw на внешнем сервере, раньше не наследовала режимы READ ONLY и DEFERRABLE локальной транзакции. Из-за этого локальная транзакция, объявленная как READ ONLY, могла изменить данные на внешнем сервере, например, через вызов функций. А транзакция, объявленная как DEFERRABLE, могла прерваться из-за ошибки сериализации на внешнем сервере.

Теперь эти параметры локальной транзакции передаются на внешний сервер для открытия удаленной транзакции. Это несовместимое изменение с предыдущими версиями: если приложение в транзакциях READ ONLY изменяло данные через postgres_fdw, такие транзакции придется объявить как READ WRITE.

Ограничения целостности: включение/отключение ENFORCED для ограничений CHECK
commit: 342051d73b3

В 18-й версии стало возможным создавать ограничения целостности CHECK как NOT ENFORCED:

CREATE TABLE test (id int);
ALTER TABLE test ADD CONSTRAINT check_id CHECK (id > 0) NOT ENFORCED;

Но изменить ограничение на ENFORCED было нельзя.

18=# ALTER TABLE test ALTER CONSTRAINT check_id ENFORCED;
ERROR:  cannot alter enforceability of constraint "check_id" of relation "test"

Требовалось его удалить и создать заново. В 19-й версии это стало возможным. При переводе в ENFORCED таблица сканируется, чтобы проверить существующие строки:

19=# ALTER TABLE test ALTER CONSTRAINT check_id ENFORCED;
ALTER TABLE

Ограничения целостности: изменение выражения виртуальных столбцов с ограничением CHECK
commit: f80bedd52b1

В 18-й версии появилась поддержка виртуальных вычисляемых столбцов. Например:

CREATE TABLE test (
  id int,
  amount numeric,
  tax numeric GENERATED ALWAYS AS (amount*0.2) VIRTUAL,
  CONSTRAINT check_tax CHECK (tax > 0)
);

Однако изменить выражение для вычисляемого столбца было нельзя, если для этого столбца определено ограничение CHECK:

18=# ALTER TABLE test ALTER COLUMN tax SET EXPRESSION AS (amount*0.22);
ERROR:  ALTER TABLE / SET EXPRESSION is not supported for virtual generated columns in tables with check constraints
DETAIL:  Column "tax" of relation "test" is a virtual generated column.

В 19-й версии это ограничение снято. Таблица при этом не перезаписывается, а только сканируется для проверки ограничений CHECK:

19=# ALTER TABLE test ALTER COLUMN tax SET EXPRESSION AS (amount*0.22);
ALTER TABLE

Преобразование между uuid и bytea и поддержка формата base32hex
commit: 497c1170cb1, ba21f5bf8af

Преобразование между uuid и bytea раньше требовало обходных путей через текстовое представление, а функции encode/decode не поддерживали формат base32hex. Теперь доступно явное приведение типов. Формат base32hex удобен для UUIDv7: строка получается короче шестнадцатеричной и сохраняет порядок сортировки (при использовании правила сортировки C). Значения UUID после конвертации в base32hex дополняются справа знаками «=». Для более компактного представления эти знаки можно обрезать функцией rtrim, ведь функция decode для формата base32hex понимает оба варианта:

WITH cte(val) AS (
  VALUES (uuidv7())
)
SELECT
  val "text",
  val::bytea "bytea",
  encode(val::bytea, 'base32hex') "base32hex",
  rtrim(encode(val::bytea, 'base32hex'),'=') "base32hex trimmed",
  decode(rtrim(encode(val::bytea, 'base32hex'),'='), 'base32hex') "bytea decoded"
FROM cte
\gx
-[ RECORD 1 ]-----+-------------------------------------
text              | 01a013f7-edf0-760d-a654-f2ec6b3a9709
bytea             | \x01a013f7edf0760da654f2ec6b3a9709
base32hex         | 06G17TVDU1R0R9IKUBM6MEKN14======
base32hex trimmed | 06G17TVDU1R0R9IKUBM6MEKN14
bytea decoded     | \x01a013f7edf0760da654f2ec6b3a9709

PL/pgSQL: оптимизация SELECT … INTO
commit: ce8d5fe0e28

В PL/pgSQL обе конструкции делают одно и то же: вычисляют выражение и присваивают значение переменной:

DECLARE
  x int;
BEGIN
  x := 2+2;
  SELECT 2+2 INTO x;
END;

Но делают это по-разному. В первом случае выражение вычисляется прямо в PL/pgSQL, а во втором выполняется полноценный запрос через SPI, что значительно медленнее. Теперь выражения, не обращающиеся к таблицам, вычисляются напрямую, без накладных расходов SPI, если результат присваивается одной переменной. Побочный эффект: такие выражения больше не попадают в статистику pg_stat_statements.

Функции tid_block и tid_offset
commit: df6949ccf7a

Извлечь номер блока и смещение внутри блока из значения ctid раньше можно было через приведение к строке, а затем к типу point:

SELECT ctid,
       (ctid::text::point)[0]::bigint AS block,
       (ctid::text::point)[1]::int    AS offset
FROM bookings LIMIT 1;
 ctid  | block | offset 
-------+-------+--------
 (0,1) |     0 |      1
(1 row)

Что не совсем удобно. Новые функции делают это напрямую:

SELECT ctid,
       tid_block(ctid),
       tid_offset(ctid)
FROM bookings LIMIT 1;
 ctid  | tid_block | tid_offset 
-------+-----------+------------
 (0,1) |         0 |          1
(1 row)

TOAST: алгоритм lz4 для сжатия по умолчанию
commit: 7c1849311e4, 34dfca29343

Исторически для сжатия TOAST используется встроенный в сервер алгоритм pglz. Однако еще в 14-й версии появилась поддержка более эффективного (как по степени сжатия, так и по использованию процессорного времени) алгоритма lz4.

В 19-й версии, если сервер собран с поддержкой lz4, то для сжатия TOAST-таблиц будет использоваться именно этот алгоритм:

SELECT setting ~ '--with-lz4' FROM pg_config() WHERE name = 'CONFIGURE';
 ?column? 
----------
 t
(1 row)
\dconfig default_toast_compression
 List of configuration parameters
         Parameter         | Value 
---------------------------+-------
 default_toast_compression | lz4
(1 row)

На этом обзор изменений 19-й версии завершен. Остается дождаться официального выпуска PostgreSQL 19, запланированного на 29 октября, и не забыть проверить финальный список изменений в Замечаниях к выпуску.

View the original on Хабр →

KioskNews shows a cleaned-up reading view extracted from the publisher’s page — the original always lives on their site, not ours.