PostgreSQL 18: Часть 4 или Коммитфест 2025-01

jnvsptt2houib8sqngcltp2jmxw.png


Продолжаем следить за новостями из мира PostgreSQL 18. Январский коммитфест принес много полезных изменений в области мониторинга и не только.


А самое интересное из предыдущих коммитфестов можно прочитать здесь: 2024–07, 2024–09, 2024–11.


EXPLAIN (analyze): buffers on по умолчанию
pg_stat_io: статистика ввода/вывода в байтах вместо страниц
pg_stat_io: статистика WAL
pg_stat_get_backend_io: статистика ввода/вывода отдельного процесса
VACUUM (verbose): информация из карты видимости
Суммарное время на очистку и анализ в разрезе таблиц
autovacuum: изменение количества рабочих процессов без перезапуска сервера
psql: информация о службе соединения
psql: расширенный режим для команд \d*
psql: признак leakproof в выводе команд \df*
jsonb: приведение значения null к другим типам
Алгоритм шифрования MD5: подготовка к снятию с поддержки
Новая функция uuidv7
postgres_fdw: использование аутентификации SCRAM без хранения пароля
passwordcheck: минимальная длина пароля
Новая функция casefold и правило сортировки pg_unicode_fast
Команды DML: RETURNING с OLD и NEW
to_number: преобразование строки с римскими цифрами в число



EXPLAIN (analyze): buffers on по умолчанию
commit: c2a4078eb


После долгих обсуждений победили сторонники включения по умолчанию вывода информации о буферах в команде EXPLAIN ANALYZE.


Привыкаем к более многословному выводу:


EXPLAIN (analyze, costs off)
SELECT * FROM bookings;

                               QUERY PLAN                               
------------------------------------------------------------------------
 Seq Scan on bookings (actual time=0.086..119.142 rows=2111110 loops=1)
   Buffers: shared hit=160 read=13287
 Planning:
   Buffers: shared hit=57
 Planning Time: 0.543 ms
 Execution Time: 180.270 ms
(6 rows)

Впрочем, часть сообщения о коммите посвящена сомнениям относительно принятого решения вплоть до возможности откатить патч до официального выпуска 18-й версии, если будет много возражений.


См. также
Waiting for PostgreSQL 18 — Enable BUFFERS with EXPLAIN ANALYZE by default (Hubert 'depesz' Lubaczewski)



pg_stat_io: статистика ввода/вывода в байтах вместо страниц
commit: f92c854cf


В предоставлении pg_stat_io три новых столбца: read_bytes, write_bytes, extend_bytes. В них накапливается объем соответствующих операций чтения, записи и расширения файлов. Количество операций по-прежнему можно получить из столбцов: reads, writes, extends.


Раньше для вычисления размера в байтах был необходим столбец op_bytes. В нем хранился размер страницы. Умножая op_bytes на reads, writes и extends, вычисляли объем операций в байтах. Теперь для размера в байтах есть отдельные столбцы, поэтому op_bytes был удален.


Это изменение открывает возможность добавлять в pg_stat_io другие операции ввода/вывода, не связанные с буферным кешем. Например, операции с WAL, где размер отдельной операции не связан с размером страницы.


Да и с буферным кешем не всегда всё строго в терминах страниц. Появившийся в 17-й версии параметр io_combine_limit позволяет группировать некоторые операции чтения более крупными кусками. К таким операциям относятся последовательное чтение таблиц, сбор статистики (analyze) и некоторые другие.


Посмотрим на влияние io_combine_limit на последовательное чтение таблицы в деле. Для теста потребуется небольшой psql-скрипт:


$ cat pg18/pg_stat_io.sql

\o /dev/null
SELECT pg_stat_reset_shared('io');
SET io_combine_limit = :'io_combine_limit';

CREATE TABLE bookings_copy
AS SELECT * FROM bookings;
DROP TABLE bookings_copy;
\o

SELECT context,
       reads, read_bytes,
       writes, write_bytes,
       extends, extend_bytes
FROM pg_stat_io
WHERE backend_type = 'client backend' AND
      object = 'relation' AND
      (reads > 0 OR writes > 0);

В начале скрипта сбрасывается статистика и устанавливается io_combine_limit, затем выполняется CREATE TABLE AS SELECT. Для чтения таблицы bookings будет использовано последовательное сканирование. Последний запрос показывает статистику ввода/вывода.


Запустим скрипт со значением io_combine_limit по умолчанию:


$ psql -f pg18/pg_stat_io.sql -v io_combine_limit='128kB'

  context  | reads | read_bytes | writes | write_bytes | extends | extend_bytes
-----------+-------+------------+--------+-------------+---------+--------------
 bulkread  |   789 |   99753984 |      0 |           0 |         |             
 bulkwrite |     0 |          0 |  11456 |    93847552 |     217 |    110624768
(2 rows)

При увеличении io_combine_limit в два раза, примерно в два раза уменьшается количество операций чтения:


$ psql -f pg18/pg_stat_io.sql -v io_combine_limit='256kB'

  context  | reads | read_bytes | writes | write_bytes | extends | extend_bytes
-----------+-------+------------+--------+-------------+---------+--------------
 bulkread  |   396 |   99491840 |      0 |           0 |         |             
 bulkwrite |     0 |          0 |  11456 |    93847552 |     217 |    110624768
(2 rows)


pg_stat_io: статистика WAL
commit: a051e71e2


Благодаря предыдущему изменению, статистика ввода/вывода больше не привязана к размеру страницы. Это позволило добавить в pg_stat_io статистику работы с WAL.


Работа с WAL учитывается для разных типов процессов и в двух контекстах:


  • normal ― операции с уже созданным сегментом WAL,
  • init ― операции, когда сначала инициализируется новый сегмент WAL.

Пример статистики записи в WAL:


SELECT backend_type, object, context,
       writes, write_bytes, pg_size_pretty(round(write_bytes/writes)) bytes_per_op
FROM pg_stat_io
WHERE object = 'wal' AND writes > 0;

   backend_type    | object | context | writes | write_bytes | bytes_per_op
-------------------+--------+---------+--------+-------------+--------------
 client backend    | wal    | init    |      2 |    33554432 | 16 MB
 client backend    | wal    | normal  | 207883 |  2198847488 | 10 kB
 autovacuum worker | wal    | init    |      4 |    67108864 | 16 MB
 autovacuum worker | wal    | normal  |  25595 |  1446313984 | 55 kB
 background worker | wal    | normal  |  19012 |   155746304 | 8192 bytes
 background writer | wal    | normal  |      1 |        8192 | 8192 bytes
 checkpointer      | wal    | init    |      1 |    16777216 | 16 MB
 checkpointer      | wal    | normal  |     12 |     8495104 | 691 kB
 walwriter         | wal    | normal  |    314 |   452460544 | 1407 kB
(9 rows)


pg_stat_get_backend_io: статистика ввода/вывода отдельного процесса
commit: 9aea73fc6


Новая функция pg_stat_get_backend_io выдает статистику ввода/вывода отдельного клиентского процесса. На входе функция принимает идентификатор процесса, а на выходе возвращает набор строк такой же структуры, как представление pg_stat_io.


Пример запроса статистики для текущего процесса:


SELECT object, context,
       reads, read_bytes,
       writes, write_bytes
FROM pg_stat_get_backend_io(pg_backend_pid())
WHERE reads > 0 OR writes > 0;

  object  | context | reads | read_bytes | writes | write_bytes
----------+---------+-------+------------+--------+-------------
 relation | normal  |   543 |    4489216 |      0 |           0
 relation | vacuum  | 76666 | 1122197504 |  34301 |   280993792
 wal      | normal  |     0 |          0 |    256 |   283475968
(3 rows)

Функция работает только с клиентскими процессами. Статистика накапливается, пока процесс работает. По завершении процесса статистика удаляется. Сбрасывать статистику во время работы процесса можно функцией pg_stat_reset_backend_stats.


Соединяя pg_stat_get_backend_io с pg_stat_activity, можно получить много новой полезной информации: не просто какой процесс дольше всех работает, а какой из действующих процессов больше всего обращается к диску.


См. также
Postgres backend statistics (Part 1): I/O statistics (Bertrand Drouvot)



VACUUM (verbose): информация из карты видимости
commit: dc6acfd91, 4b565a198


Для только что созданной таблицы еще нет карты видимости, поэтому оба счетчика all-visible и all-frozen равны 0.


CREATE EXTENSION pg_visibility;

CREATE TABLE bookings_copy WITH (autovacuum_enabled = off)
AS SELECT * FROM bookings;

SELECT * FROM pg_visibility_map_summary('bookings_copy'::regclass);

 all_visible | all_frozen
-------------+------------
           0 |          0
(1 row)

В вывод команды VACUUM с параметром verbose добавлена строка visibility map, показывающая итоговые значения этих счетчиков после очистки, а также количество страниц all-visible перед очисткой (значение 0 в скобках). Это позволяет посчитать количество страниц, помеченных как all-visible за время очистки.


VACUUM(freeze, verbose) bookings_copy;
INFO:  aggressively vacuuming "demo.bookings.bookings_copy"
INFO:  finished vacuuming "demo.bookings.bookings_copy": index scans: 0
pages: 0 removed, 13504 remain, 13504 scanned (100.00% of total), 0 eagerly scanned
tuples: 0 removed, 2111110 remain, 0 are dead but not yet removable
removable cutoff: 964, which was 0 XIDs old when operation ended
new relfrozenxid: 964, which is 1 XIDs ahead of previous value
frozen: 13447 pages from table (99.58% of total) had 2111110 tuples frozen
visibility map: 13447 pages set all-visible, 13447 pages set all-frozen (0 were all-visible)
index scan not needed: 0 pages from table (0.00% of total) had 0 dead item identifiers removed
avg read rate: 269.712 MB/s, avg write rate: 269.806 MB/s
buffer usage: 15623 hits, 11456 reads, 11460 dirtied
WAL usage: 26898 records, 4 full page images, 5963212 bytes
system usage: CPU: user: 0.19 s, system: 0.03 s, elapsed: 0.33 s
VACUUM

Теперь строки всех страниц заморожены и видны всем транзакциям.


SELECT * FROM pg_visibility_map_summary('bookings_copy'::regclass);

 all_visible | all_frozen
-------------+------------
       13447 |      13447
(1 row)


Суммарное время на очистку и анализ в разрезе таблиц
commit: 30a6ed0ce


В представлении pg_stat_all_table четыре новых столбца, начинающихся на total: total_vacuum_time, total_autovacuum_time, total_analyze_time, total_autoanalyze_time. В этих столбцах для каждой таблицы накапливается время, затраченное на очистку, автоочистку, анализ и автоанализ соответственно.


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


SELECT relname,
       vacuum_count,
       total_vacuum_time,
       total_vacuum_time/nullif(vacuum_count,0) avg_vacuum_time,
       autovacuum_count,
       total_autovacuum_time,
       total_autovacuum_time/nullif(autovacuum_count,0) avg_autovacuum_time,
       analyze_count,
       total_analyze_time,
       total_analyze_time/nullif(analyze_count,0) avg_analyze_time,
       autoanalyze_count,
       total_autoanalyze_time,
       total_autoanalyze_time/nullif(autoanalyze_count,0) avg_autoanalyze_time
FROM pg_stat_all_tables
ORDER BY total_autovacuum_time + total_vacuum_time +
         total_analyze_time + total_autoanalyze_time DESC
LIMIT 1
\gx

-[ RECORD 1 ]----------+------------------
relname                | ticket_flights
vacuum_count           | 3
total_vacuum_time      | 2992
avg_vacuum_time        | 997.3333333333334
autovacuum_count       | 0
total_autovacuum_time  | 0
avg_autovacuum_time    |
analyze_count          | 2
total_analyze_time     | 594
avg_analyze_time       | 297
autoanalyze_count      | 0
total_autoanalyze_time | 0
avg_autoanalyze_time   |

Время везде в миллисекундах, поэтому очевидно, что в этой тестовой базе данных очистка и анализ не создают хлопот.


См. также
PostgreSQL 18: Per-relation cumulative statistics for [auto]vacuum and [auto]analyze (Daniel Westermann)



autovacuum: изменение количества рабочих процессов без перезапуска сервера
commit: c758119e5


Количество рабочих процессов автоочистки теперь можно изменять без перезапуска сервера. Достаточно изменить значение параметра autovacuum_max_workers и перечитать конфигурацию. Но количество рабочих процессов не должно превышать количество слотов, зарезервированных в новом параметре autovacuum_worker_slots. А вот изменение количества слотов уже потребует перезапуска.


\dconfig+ autovacuum*worker*

                      List of configuration parameters
        Parameter        | Value |  Type   |  Context   | Access privileges
-------------------------+-------+---------+------------+-------------------
 autovacuum_max_workers  | 3     | integer | sighup     |
 autovacuum_worker_slots | 16    | integer | postmaster |
(2 rows)

См. также
Waiting for PostgreSQL 18 — Allow changing autovacuum_max_workers without restarting.(Hubert 'depesz' Lubaczewski)



psql: информация о службе соединения
commit: 477728b5d


Файл службы соединения (pg_service.conf) позволяет подключаться к разным базам данных без необходимости каждый раз вводить параметры подключения.


$ grep -A 6 '^\[replica\]' ~/.pg_service.conf

[replica]
# Реплика основного сервера
host=localhost
port=5433
user=postgres
dbname=demo
options=-c search_path=bookings

Любое приложение, использующее libpq, может подключаться по имени службы replica. В psql теперь можно получить имя службы из переменной SERVICE:


$ psql service=replica -c '\echo :SERVICE'

replica

А также включать имя службы в приглашение psql, используя специальную последовательность %s.


См. также
Waiting for PostgreSQL 18 — psql: Add more information about service name (Hubert 'depesz' Lubaczewski)



psql: расширенный режим для команд \d*
commit: 00f4c2959


Для того чтобы команды \d* выводили дополнительную информацию, приходилось перед командой включать расширенный режим, а после команды ― отключать. И писать не удобно, и лишний вывод о включении/выключении режима не нужен:


\x \du+ \x

Expanded display is on.
List of roles
-[ RECORD 1 ]-----------------------------------------------------------
Role name   | postgres
Attributes  | Superuser, Create role, Create DB, Replication, Bypass RLS
Description |

Expanded display is off.

Теперь в командах, выводящих списки объектов, можно использовать символ x для включения расширенного режима только для этой команды:


\dux+

List of roles
-[ RECORD 1 ]-----------------------------------------------------------
Role name   | postgres
Attributes  | Superuser, Create role, Create DB, Replication, Bypass RLS
Description |

Поведение аналогично включению расширенного режима для вывода результата запроса при помощи \gx. При этом символы x и + могут быть указаны в любом порядке.


\dt+x tickets

List of tables
-[ RECORD 1 ]-+----------
Schema        | bookings
Name          | tickets
Type          | table
Owner         | postgres
Persistence   | permanent
Access method | heap
Size          | 386 MB
Description   | Tickets


psql: признак leakproof в выводе команд \df*
commit: 2355e5111


В вывод команд \df+, \do+, \dAo+ и \dC+ добавлен признак leakproof. А с новым модификатором x это легко продемонстрировать:


\df+x bookings.now

List of functions
-[ RECORD 1 ]-------+--------------------------------------------------------
Schema              | bookings
Name                | now
Result data type    | timestamp with time zone
Argument data types |
Type                | func
Volatility          | immutable
Parallel            | unsafe
Owner               | postgres
Security            | invoker
Leakproof?          | no
Access privileges   |
Language            | sql
Internal name       |
Description         | Point in time according to which the data are generated


jsonb: приведение значения null к другим типам
commit: a5579a90a


После приведения JSON-значения null к SQL-типу text для элемента jsonb получается строка 'null'. Но приведение к другому скалярному типу, например int, float, etc, вызывает ошибку:


17=# WITH t AS (
    SELECT '{"a": null}'::jsonb col
)
SELECT t.col['a']::int  null_as_int,
       t.col['a']::text null_as_text
FROM t;

ERROR:  cannot cast jsonb null to type integer

Больше этой ошибки не будет, JSON-значение null будет приведено к SQL-значению NULL:


18=# WITH t AS (
    SELECT '{"a": null}'::jsonb col
)
SELECT t.col['a']::int  null_as_int,
       t.col['a']::text null_as_text
FROM t;

 null_as_int | null_as_text
-------------+--------------
             | null
(1 row)


Алгоритм шифрования MD5: подготовка к снятию с поддержки
commit: db6a4a985


Проблемы с безопасностью паролей, зашифрованных алгоритмом MD5, известны, на этом не стоит останавливаться. Поэтому в 19-й версии этот алгоритм не будет поддерживаться.


В 18-й версии MD5 еще работает, но в документации уже объявлен как устаревший, а при попытке создать пароль с алгоритмом MD5 будет выдаваться соответствующее предупреждение:


SET password_encryption = 'md5';

CREATE ROLE alice LOGIN PASSWORD 'secret';

WARNING:  setting an MD5-encrypted password
DETAIL:  MD5 password support is deprecated and will be removed in a future release of PostgreSQL.
HINT:  Refer to the PostgreSQL documentation for details about migrating to another password type.
CREATE ROLE

Впрочем, предупреждение можно отключить параметром md5_password_warnings, но готовиться к отказу от MD5 стоит уже сейчас.



Новая функция uuidv7
commit: 78c5e141e


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


SELECT g.x, gen_random_uuid()
from generate_series(1,5) AS g(x)
ORDER BY 2;

 x |           gen_random_uuid            
---+--------------------------------------
 4 | 00743ce3-1e99-4cf6-8d74-6c057018b7d6
 3 | 26d9a9bc-f7a0-4c91-8afe-945960f2f59b
 5 | 384869c0-8d7d-4201-945c-ee339b62e6b5
 1 | c970dce9-d883-440a-8f41-0d47cc818328
 2 | dd5cc4ed-adcc-4dbe-8926-d2222b826316
(5 rows)

В спецификации для 7-й версии UUID говорится, что значения должны начинаться с метки времени эпохи Unix в миллисекундах, поэтому генерируемые значения монотонно возрастают. В чем легко убедиться с новой функцией uuidv7, гарантирующей монотонное возрастание в пределах клиентского процесса:


SELECT g.x, uuidv7()
from generate_series(1,5) AS g(x)
ORDER BY 2;

 x |                uuidv7                
---+--------------------------------------
 1 | 01953813-d813-7c2f-81b0-9f74e90928b8
 2 | 01953813-d813-7cb9-b5bb-1df8003f07b6
 3 | 01953813-d813-7ce1-b13b-4f87ceb29651
 4 | 01953813-d813-7d11-a4e6-cc1f343741b8
 5 | 01953813-d813-7d37-94b0-bb2977fb3f4f
(5 rows)

Кроме того, при помощи функции uuid_extract_timestamp можно получить дату и время формирования UUID, а при вызове uuidv7 можно «сдвинуть время», передав параметром интервал сдвига:


SELECT g.x,
       uuid_extract_timestamp(
         uuidv7(format('%s day',g.x)::interval)
       )
from generate_series(-2,2) AS g(x)
ORDER BY 2;

 x  |   uuid_extract_timestamp   
----+----------------------------
 -2 | 2025-02-22 16:22:06.566+03
 -1 | 2025-02-23 16:22:06.566+03
  0 | 2025-02-24 16:22:06.566+03
  1 | 2025-02-25 16:22:06.566+03
  2 | 2025-02-26 16:22:06.566+03
(5 rows)

См. также
Waiting for PostgreSQL 18 — Add UUID version 7 generation function.(Hubert 'depesz' Lubaczewski)



postgres_fdw: использование аутентификации SCRAM без хранения пароля
commit: 761c79508


Если на удаленном сервере использовалась парольная аутентификация, то в настройках сопоставления ролей (CREATE USER MAPPINGS) приходилось явно указывать пароль. Чтобы избежать хранения пароля в открытом виде, теперь можно воспользоваться новым параметром use_scram_passthrough в настройках внешнего сервера или таблицы.


На удаленном сервере создана роль alice с паролем, зашифрованным алгоритмом scram-sha-256. В pg_hba.conf для этой роли настроен только один способ подключения: аутентификация по методу SCRAM.


remote=# SELECT * FROM pg_hba_file_rules LIMIT 1

-[ RECORD 1 ]------------------------------------------------
rule_number | 1
file_name   | /home/pal/master/pg18/remote_server/pg_hba.conf
line_number | 1
type        | host
database    | {all}
user_name   | {alice}
address     | 127.0.0.1
netmask     | 255.255.255.255
auth_method | scram-sha-256
options     |
error       |

Настройка postgres_fdw на локальном сервере будет выглядеть следующим образом:


local=# CREATE EXTENSION postgres_fdw;

В настройках сервера включим новый параметр use_scram_passthrough:


local=# CREATE SERVER remote_server
  FOREIGN DATA WRAPPER postgres_fdw
  OPTIONS (
    host 'localhost',
    port '5400',
    dbname 'postgres',
    use_scram_passthrough 'true'
);

Использовать внешний сервер также будет роль alice. Пароль для alice установим позже. Важно, что в сопоставлении ролей не нужно указывать пароль роли alice с удаленного сервера:


local=# CREATE ROLE alice LOGIN;
local=# CREATE USER MAPPING FOR alice
  SERVER remote_server
  OPTIONS (
     user 'alice'
);
local=# GRANT USAGE ON FOREIGN SERVER remote_server TO alice;

Теперь для локальной роли alice установим точно такой же пароль, как у удаленной роли alice. Нужно не просто ввести такую же строку, а прямо скопировать с удаленного сервера зашифрованное значение пароля из таблицы pg_authid:


remote=# SELECT rolpassword FROM pg_authid WHERE rolname = 'alice';

И подставить это значение в качестве пароля локальной роли alice:


local=# ALTER ROLE alice PASSWORD '...';

Теперь alice на локальном сервере может создавать и использовать внешние таблицы, подключаясь к удаленному серверу без ввода пароля.



passwordcheck: минимальная длина пароля
commit: f7e1b3828


Новый параметр расширения passwordcheck позволяет указать минимальную длину пароля.


SET passwordcheck.min_password_length = 8;

ALTER ROLE postgres PASSWORD '123';

ERROR:  password is too short
DETAIL:  password must be at least "passwordcheck.min_password_length" (8) bytes long

Но отправлять пароли в открытом виде небезопасно, поэтому многие клиентские приложения предоставляют возможности для отправки пароля на сервер в предварительно зашифрованном виде. Например, в psql для этого используется команда \password. А с предварительно зашифрованными паролями параметр passwordcheck.min_password_length работать не будет.



Новая функция casefold и правило сортировки pg_unicode_fast
commit: bfc599206, d3d098316, 286a365b9


Функция casefold предназначена для регистронезависимого поиска в тех случаях, когда преобразование сравниваемых строк к нижнему (или верхнему) регистру не помогает. В зависимости от правила сортировки, для одного символа могут быть определены несколько вариантов в нижнем регистре.


Пример вызова функции для знака эсцет:


SELECT c, lower(c), casefold(c), upper(c)
FROM (VALUES('ß' collate "pg_unicode_fast")) AS t(c);

 c | lower | casefold | upper
---+-------+----------+-------
 ß | ß     | ss       | SS
(1 row)

Здесь используется новое правило сортировки pg_unicode_fast, которое более точно соответствует определенному в стандарте SQL правилу сортировки ucs_basic.



Команды DML: RETURNING с OLD и NEW
commit: 80feb727c


В командах INSERT, UPDATE, DELETE и MERGE во фразе RETURNING появилась возможность получать как старые значения (до выполнения команды), так и новые значения. Для этого нужно обращаться к столбцам при помощи конструкций OLD и NEW. (Очень похоже на работу со старыми и новыми значениями в триггерах уровня строки.)


CREATE TABLE t(
    id integer PRIMARY KEY,
    s text
);

Для INSERT обращаться к старым значениям обычно нет смысла, старые значения всегда NULL.


INSERT INTO t
VALUES (1,'aaa'), (2, 'bbb')
RETURNING old.*, new.*;

 id | s | id |  s  
----+---+----+-----
    |   |  1 | aaa
    |   |  2 | bbb
(2 rows)

Для DELETE отсутствуют новые значения:


DELETE FROM t
WHERE id > 1
RETURNING old.*, new.*;

 id |  s  | id | s
----+-----+----+---
  2 | bbb |    |
(1 row)

А вот для UPDATE доступ к старым и новым значениям будет полезен для анализа изменений:


UPDATE t SET s = 'ccc'
WHERE id = 1
RETURNING old.*, new.*,
          format('%s->%s', old.s, new.s) AS change;

 id |  s  | id |  s  |  change  
----+-----+----+-----+----------
  1 | aaa |  1 | ccc | aaa->ccc
(1 row)

То же относится к варианту INSERT с ON CONFLICT:


INSERT INTO t
VALUES (1,'ddd'), (3,'eee')
ON CONFLICT(id) DO UPDATE SET s = EXCLUDED.s
RETURNING to_json(old.*) AS old,
          to_json(new.*) AS new;

        old         |        new         
--------------------+--------------------
 {"id":1,"s":"ccc"} | {"id":1,"s":"ddd"}
                    | {"id":3,"s":"eee"}
(2 rows)

См. также
Waiting for PostgreSQL 18 — Add OLD/NEW support to RETURNING in DML queries.(Hubert 'depesz' Lubaczewski)



to_number: преобразование строки с римскими цифрами в число
commit: 172e6b3ad


Преобразовать натуральное число в строку с римскими цифрами уже давно можно при помощи функции to_char. Теперь появился обратный способ конвертации строки с римскими цифрами в число при помощи to_number:


SELECT to_char(42, 'RN'),
       to_number('XLII', 'RN');

     to_char     | to_number
-----------------+-----------
            XLII |        42
(1 row)

См. также
Waiting for PostgreSQL 18 — Support RN (roman-numeral format) in to_number ().(Hubert 'depesz' Lubaczewski)


На этом пока всё. Впереди основные события последнего мартовского коммитфеста 18-й версии.

© Habrahabr.ru