Продолжаем цикл статей с обзором изменений 19-й версии. На этот раз рассмотрим что появилось в рамках сентябрьского коммитфеста 2025 года.

Самое интересное из первого июльского коммитфеста можно прочитать здесь: 2025-07.

GROUP BY ALL
Оконные функции: обработка значений NULL
Триггеры событий на PL/Python
Более точное сообщение об ошибке при неверном указании имен параметров подпрограмм
random: случайная дата/время в заданном диапазоне
Формат base64url для функций encode и decode
Новый параметр debug_print_raw_parse
Параметр log_lock_waits включен по умолчанию
pg_stat_progress_basebackup: тип резервной копии
vacuumdb: сбор статистики по секционированным таблицам
Буферный кеш: использование алгоритма clock-sweep для поиска свободных буферов
Фейковые псевдонимы таблиц в запросах


GROUP BY ALL
commit: ef38a4d9756

Полегче станет тем, кому лень заново перечислять в предложении GROUP BY выражения из SELECT. Ключевое слово ALL подразумевает включение всех выражений из SELECT, не использующих групповые функции.

SELECT to_char(actual_departure, 'YYYY'),
       count(*)
FROM flights
GROUP BY ALL
ORDER BY 1;
 to_char | count 
---------+-------
 2025    | 16477
 2026    | 42457
         | 10776
(3 rows)

Захотели добавить в группировку новое выражение — достаточно указать его в списке SELECT, предложение GROUP BY трогать не нужно:

SELECT to_char(actual_departure, 'YYYY'),
       status,
       count(*)
FROM flights
GROUP BY ALL
ORDER BY 1;
 to_char |  status   | count 
---------+-----------+-------
 2025    | Arrived   | 16477
 2026    | Arrived   | 42438
 2026    | Departed  |    19
         | Boarding  |     5
         | Scheduled | 10249
         | Delayed   |     9
         | Cancelled |   358
         | On Time   |   155
(8 rows)

Кстати не все участники обсуждения были согласны с таким изменением. Были и те, кто высказался резко против. Основная мотивация — разработчик должен контролировать код запроса полностью, а не надеятся на умолчания системы. Однако предложение включить GROUP BY ALL в стандарт SQL уже принято комитетом, поэтому разговоры о необходимости более неуместны, а PostgreSQL 19 появится с этой возможностью.

См. также
Waiting for SQL:202y: GROUP BY ALL (Peter Eisentraut)

Оконные функции: обработка значений NULL
commit: 25a30bbd423, 2273fa32bce

В соответствии со стандартом SQL для функций lag, lead, first_value, last_value, nth_value добавлена возможность не учитывать пустые значения. Для этого при вызове оконной функции можно указать IGNORE NULLS или RESPECT NULLS (по умолчанию).

SELECT a, b,
       first_value(b) RESPECT NULLS OVER w AS respect_nulls,
       first_value(b) IGNORE NULLS OVER w AS ignore_nulls
FROM (VALUES ('a',NULL),('b',1),('c',2)) AS t(a,b)
WINDOW w AS (ORDER BY a ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING);
 a | b | respect_nulls | ignore_nulls 
---+---+---------------+--------------
 a |   |               |            1
 b | 1 |               |            1
 c | 2 |               |            1
(3 rows)

Триггеры событий на PL/Python
commit: 53eff471c

Функции триггеров событий теперь можно писать на PL/Python. Пример триггера, срабатывающего по окончании операции DDL. Триггер просто выводит информацию о выполненных командах:

CREATE EXTENSION IF NOT EXISTS plpython3u;

CREATE OR REPLACE FUNCTION describe_ddl()
RETURNS event_trigger AS $$
    for row in plpy.cursor("SELECT command_tag, object_identity FROM pg_event_trigger_ddl_commands()"):
        plpy.notice(
            "{}. name: {}".format(
                row['command_tag'],
                row['object_identity']
            )
        )
$$ LANGUAGE plpython3u;

CREATE EVENT TRIGGER after_ddl 
ON ddl_command_end EXECUTE FUNCTION describe_ddl();

Проверим:

CREATE TABLE test(id integer PRIMARY KEY GENERATED ALWAYS AS IDENTITY);
NOTICE:  CREATE SEQUENCE. name: public.test_id_seq
NOTICE:  CREATE TABLE. name: public.test
NOTICE:  CREATE INDEX. name: public.test_pkey
NOTICE:  ALTER SEQUENCE. name: public.test_id_seq
CREATE TABLE

Более точное сообщение об ошибке при неверном указании имен параметров подпрограм��
commit: 83a56419457

Есть функция с именованным параметром.

CREATE FUNCTION f(a int) RETURNS int LANGUAGE SQL RETURN a;
SELECT f(a=>42);
 f  
----
 42
(1 row)

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

18=# SELECT f(b=>42);
ERROR:  function f(b => integer) does not exist
LINE 1: SELECT f(b=>42);
               ^
HINT:  No function matches the given name and argument types. You might need to add explicit type casts.

А это неверная подсказка. Функция с таким именем и типами параметров существует, но имя у параметра другое. Детальное сообщение об ошибке в 19-й версии более точное: нет функции с таким именем и такими именами у параметров:

19=# SELECT f(b=>42);
ERROR:  function f(b => integer) does not exist
LINE 1: SELECT f(b=>42);
               ^
DETAIL:  No function of that name accepts the given argument names.

random: случайная дата/время в заданном диапазоне
commit: faf071b5538, 9c24111c4da

В 17-й версии функция random научилась выдавать случайные числа равномерно распределенные в заданном диапазоне. А теперь то же самое с типами даты/времени:

SELECT random(current_date, current_date + 100);
   random   
------------
 2026-03-23
(1 row)

Формат base64url для функций encode и decode
commit: e1d917182c1

Функции encode и decode теперь поддерживают формат base64url, который можно безопасно использовать в именах файлов и адресах URL.

SELECT encode(decode('+-/_', 'base64url'), 'base64url');
 encode 
--------
 --__
(1 row)

Новый параметр debug_print_raw_parse
commit: 06473f5a344

Параметры семейства debug_print_* включают вывод отладочной информации о запросе. К ним добавлен параметр debug_print_raw_parse для вывода необработанного дерева синтаксического анализа.

\dconfig debug_print_*
List of configuration parameters
       Parameter       | Value 
-----------------------+-------
 debug_print_parse     | off
 debug_print_plan      | off
 debug_print_raw_parse | off
 debug_print_rewritten | off
(4 rows)

Посмотрим что он покажет для команды COMMIT.

SET debug_print_raw_parse = on;
SET client_min_messages = 'LOG';

COMMIT;
LOG:  raw parse tree:
DETAIL:  (
   {RAWSTMT 
   :stmt 
      {TRANSACTIONSTMT 
      :kind 2 
      :options <> 
      :savepoint_name <> 
      :gid <> 
      :chain false 
      :location -1
      }
   :stmt_location 0 
   :stmt_len 6
   }
)

WARNING:  there is no transaction in progress
COMMIT

Параметр log_lock_waits включен по умолчанию
commit: 2aac62be8cb

Предполагается (и вполне справедливо), что длительные блокировки — это проблема для работы системы, поэтому вывод информации о блокировках в журнал сервера должен быть включен по умолчанию.

\dconfig log_lock_waits
List of configuration parameters
   Parameter    | Value 
----------------+-------
 log_lock_waits | on
(1 row)

pg_stat_progress_basebackup: тип резервной копии
commit: deb674454c5

Новый столбец backup_type представления pg_stat_progress_basebackup показывает тип создаваемой резервной копии: полная (full) или инкрементальная (incremental):

\d pg_stat_progress_basebackup
          View "pg_catalog.pg_stat_progress_basebackup"
        Column        |  Type   | Collation | Nullable | Default 
----------------------+---------+-----------+----------+---------
 pid                  | integer |           |          | 
 phase                | text    |           |          | 
 backup_total         | bigint  |           |          | 
 backup_streamed      | bigint  |           |          | 
 tablespaces_total    | bigint  |           |          | 
 tablespaces_streamed | bigint  |           |          | 
 backup_type          | text    |           |          | 

vacuumdb: сбор статистики по секционированным таблицам
commit: 6429e5b771d

Утилита vacuumdb с параметрами --analyze-only и --analyze-in-stages будет собирать статистику не только по обычным, но и по секционированным таблицам.

Раньше для сбора статистики по секционированным таблицам нужно было явно указывать их в параметре --table, что не всегда удобно, особенно для сбора статистики после обновления сервера.

Буферный кеш: использование алгоритма clock-sweep для поиска свободных буферов
commit: 2c789405275

Раньше для поиска свободного буфера в буферном кеше поддерживался список свободных буферов. Этот список больше не используется, а для поиска применяется clock-sweep алгоритм, перебирающий по кругу все буферы. Предполагается, что отказ от списка свободных буферов упростит последующие патчи, связанные с поддержкой NUMA.

С точки зрения пользователей этот патч ничего не меняет, и о нем можно было бы не писать. Но в нескольких темах нашего курса DBA2 (Буферный кеш, Блокировки в оперативной памяти) мы рассказываем о списке свободных буферов. Поэтому включение патча в обзор — это и напоминание, что при обновлении курса на 19-ю версию в этих темах точно потребуются изменения.

Фейковые псевдонимы таблиц в запросах
commit: 585e31fcb, 6f79024df34, 5a170e992a4

Исторически планировщик «придумывает» странные псевдонимы для наборов строк, если в запросе имена явно не указаны. Например "*VALUES*":

EXPLAIN SELECT * FROM (VALUES (2),(1));
                         QUERY PLAN                          
-------------------------------------------------------------
 Values Scan on "*VALUES*"  (cost=0.00..0.03 rows=2 width=4)
(1 row)

Зная об этом, можно даже использовать эти псевдонимы в запросах:

SELECT * FROM (VALUES (2),(1) ORDER BY "*VALUES*".column1);
 column1 
---------
       1
       2
(2 rows)

Среди таких имен были обнаружены: old, new, "*SELECT*", ANY_subquery, "*MERGE*", "*RESULT*", excluded, unnamed_subquery, unnamed_join, "*GROUP*", "*TLOCRN*", "*TROCRN*", "*SELECT* %d", "*VALUES*", xmltable, json_table.

Но заменить их на что-то более осмысленное оказалось не просто из-за потенциальных проблем с обратной совместимостью. Оказалось что даже в сценариях регрессионного тестирования PostgreSQL полно запросов, использующих подобные имена, в частности "*VALUES*".

В конечном итоге отказались лишь от некоторых имен, а именно: "*SELECT*", "*SELECT* %d", "ANY_subquery", "*TLOCRN*", "*TROCRN*". Вместо них теперь формируются имена в стиле "unnamed_subquery" or "unnamed_subquery_%d".


На этом пока всё. Впереди новости ноябрьского коммитфеста.