{"id":458274,"date":"2025-04-30T21:00:58","date_gmt":"2025-04-30T21:00:58","guid":{"rendered":"http:\/\/savepearlharbor.com\/?p=458274"},"modified":"-0001-11-30T00:00:00","modified_gmt":"-0001-11-29T21:00:00","slug":"","status":"publish","type":"post","link":"https:\/\/savepearlharbor.com\/?p=458274","title":{"rendered":"<span>PostgreSQL 18: Part 4, or CommitFest 2025-01<\/span>"},"content":{"rendered":"<div><!--[--><!--]--><\/div>\n<div id=\"post-content-body\">\n<div>\n<div class=\"article-formatted-body article-formatted-body article-formatted-body_version-1\">\n<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">\n<p><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/webt\/fc\/ru\/ch\/fcruchd4hzgooayvankxaup0gpe.png\" sizes=\"(max-width: 780px) 100vw, 50vw\" srcset=\"https:\/\/habrastorage.org\/r\/w780\/webt\/fc\/ru\/ch\/fcruchd4hzgooayvankxaup0gpe.png 780w,&#10;       https:\/\/habrastorage.org\/r\/w1560\/webt\/fc\/ru\/ch\/fcruchd4hzgooayvankxaup0gpe.png 781w\" loading=\"lazy\" decode=\"async\"\/><\/p>\n<p> <\/p>\n<p>We continue to follow the news about PostgreSQL 18. The January CommitFest brings in some notable improvements to monitoring, as well as other new features.<\/p>\n<p> <\/p>\n<p>You can find previous reviews of PostgreSQL 18 CommitFests here: <a href=\"https:\/\/postgrespro.com\/blog\/pgsql\/5971431\">2024-07<\/a>, <a href=\"https:\/\/postgrespro.com\/blog\/pgsql\/5971632\">2024-09<\/a>, <a href=\"https:\/\/postgrespro.com\/blog\/pgsql\/5971769\">2024-11<\/a>.<\/p>\n<p><a name=\"habracut\"><\/a> <\/p>\n<p><a href=\"#commit_c2a4078eb\">EXPLAIN (analyze): buffers on by default<\/a><br \/> <a href=\"#commit_f92c854cf\">pg_stat_io: input\/output statistics in bytes instead of pages<\/a><br \/> <a href=\"#commit_a051e71e2\">pg_stat_io: WAL statistics<\/a><br \/> <a href=\"#commit_9aea73fc6\">pg_stat_get_backend_io: I\/O statistics for a specific process<\/a><br \/> <a href=\"#commit_dc6acfd91\">VACUUM(verbose): visibility map information<\/a><br \/> <a href=\"#commit_30a6ed0ce\">Total vacuum and analysis time per table<\/a><br \/> <a href=\"#commit_c758119e5\">autovacuum: change the number of workers without restarting the server<\/a><br \/> <a href=\"#commit_477728b5d\">psql: connection service information<\/a><br \/> <a href=\"#commit_00f4c2959\">psql: expanded display for \\d* commands<\/a><br \/> <a href=\"#commit_2355e5111\">psql: leakproof flag in \\df* output<\/a><br \/> <a href=\"#commit_a5579a90a\">jsonb: null conversion to other types<\/a><br \/> <a href=\"#commit_db6a4a985\">MD5 encryption algorithm: end-of-support preparations<\/a><br \/> <a href=\"#commit_78c5e141e\">New function uuidv7<\/a><br \/> <a href=\"#commit_761c79508\">postgres_fdw: SCRAM authentication without storing the password<\/a><br \/> <a href=\"#commit_f7e1b3828\">passwordcheck: minimum password length<\/a><br \/> <a href=\"#commit_bfc599206\">New function casefold and pg_unicode_fast collation<\/a><br \/> <a href=\"#commit_80feb727c\">DML commands: RETURNING with OLD and NEW<\/a><br \/> <a href=\"#commit_172e6b3ad\">to_number: convert a string of roman numberals to numeric<\/a><\/p>\n<p> <a name=\"commit_c2a4078eb\"><\/a> <\/p>\n<p><strong>EXPLAIN (analyze): buffers on by default<\/strong><br \/> commit: <a href=\"https:\/\/git.postgresql.org\/gitweb\/?p=postgresql.git;a=commitdiff;h=c2a4078eb\">c2a4078eb<\/a><\/p>\n<p> <\/p>\n<p>After a lengthy discussion, the decision to display buffer information in <code>EXPLAIN ANALYZE<\/code> by default has prevailed.<\/p>\n<p> <\/p>\n<p>Time to get used to the more verbose output:<\/p>\n<p> <\/p>\n<pre><code class=\"pgsql\">EXPLAIN (analyze, costs off) SELECT * FROM bookings;<\/code><\/pre>\n<p> <\/p>\n<pre><code class=\"plaintext\">                               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)<\/code><\/pre>\n<p> <\/p>\n<p>However, a part of the commit message notes the discord regarding the decision and even suggests a rollback before the official release in case of a major uproar.<\/p>\n<p> <\/p>\n<p>See also:<\/p>\n<p> <\/p>\n<p><a href=\"https:\/\/www.depesz.com\/2025\/01\/15\/waiting-for-postgresql-18-enable-buffers-with-explain-analyze-by-default\/\">Waiting for PostgreSQL 18 \u2013 Enable BUFFERS with EXPLAIN ANALYZE by default<\/a> (Hubert &#8216;depesz&#8217; Lubaczewski)<\/p>\n<p> <a name=\"commit_f92c854cf\"><\/a> <\/p>\n<p><strong><a href=\"https:\/\/commitfest.postgresql.org\/51\/5256\/\">pg_stat_io: input\/output statistics in bytes instead of pages<\/a><\/strong><br \/> commit: <a href=\"https:\/\/git.postgresql.org\/gitweb\/?p=postgresql.git;a=commitdiff;h=f92c854cf\">f92c854cf<\/a><\/p>\n<p> <\/p>\n<p>The <code>pg_stat_io<\/code> view gets three new columns: <code>read_bytes<\/code>, <code>write_bytes<\/code>, and <code>extend_bytes<\/code>. They track the volumes of read, write, and extend operations in bytes. The operation counts still reside under <code>reads<\/code>, <code>writes<\/code>, <code>extends<\/code>.<\/p>\n<p> <\/p>\n<p>Previously, to get the volumes, you had to take the page size from <code>op_bytes<\/code> and multiply it by the <code>reads<\/code>, <code>writes<\/code>, or <code>extends<\/code> count. Since there are separate columns for the volumes, the <code>op_bytes<\/code> column is removed.<\/p>\n<p> <\/p>\n<p>This patch enables tracking additional I\/O operations that are unrelated to the buffer cache in the <code>pg_stat_io<\/code> view: for example, WAL operations, where the volume of an operation is unrelated to the page size.<\/p>\n<p> <\/p>\n<p>Speaking of which, even for buffer cache operations, it&#8217;s not one-page-size-fits-all. The parameter <em>io_combine_limit<\/em> (introduced in PostgreSQL 17) allows grouping some read operations into larger chunks: sequential scan, analyze, and some others.<\/p>\n<p> <\/p>\n<p>Let&#8217;s test <em>io_combine_limit<\/em> with sequential scan. Run a tiny psql script:<\/p>\n<p> <\/p>\n<pre><code class=\"bash\">$ cat pg18\/pg_stat_io.sql<\/code><\/pre>\n<p> <\/p>\n<pre><code class=\"plaintext\">\\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 &gt; 0 OR writes &gt; 0);<\/code><\/pre>\n<p> <\/p>\n<p>The script resets statistics, sets the <a href=\"https:\/\/www.postgresql.org\/docs\/devel\/runtime-config-resource.html#GUC-IO-COMBINE-LIMIT\">io_combine_limit<\/a>, then executes <code>CREATE TABLE AS SELECT<\/code>. Then, the <code>bookings<\/code> table is scanned sequentially. The last query returns the I\/O statistics.<\/p>\n<p> <\/p>\n<p>Run the script with the default <em>io_combine_limit<\/em>:<\/p>\n<p> <\/p>\n<pre><code class=\"bash\">$ psql -f pg18\/pg_stat_io.sql -v io_combine_limit='128kB'<\/code><\/pre>\n<p> <\/p>\n<pre><code class=\"plaintext\">  context  | reads | read_bytes | writes | write_bytes | extends | extend_bytes -----------+-------+------------+--------+-------------+---------+--------------  bulkread  |   789 |   99753984 |      0 |           0 |         |               bulkwrite |     0 |          0 |  11456 |    93847552 |     217 |    110624768 (2 rows)<\/code><\/pre>\n<p> <\/p>\n<p>When the <em>io_combine_limit<\/em> is doubled, the number of read operations is just about halved:<\/p>\n<p> <\/p>\n<pre><code class=\"bash\">$ psql -f pg18\/pg_stat_io.sql -v io_combine_limit='256kB'<\/code><\/pre>\n<p> <\/p>\n<pre><code class=\"plaintext\">  context  | reads | read_bytes | writes | write_bytes | extends | extend_bytes -----------+-------+------------+--------+-------------+---------+--------------  bulkread  |   396 |   99491840 |      0 |           0 |         |               bulkwrite |     0 |          0 |  11456 |    93847552 |     217 |    110624768 (2 rows)<\/code><\/pre>\n<p> <a name=\"commit_a051e71e2\"><\/a> <\/p>\n<p><strong><a href=\"https:\/\/commitfest.postgresql.org\/51\/4950\/\">pg_stat_io: WAL statistics<\/a><\/strong><br \/> commit: <a href=\"https:\/\/git.postgresql.org\/gitweb\/?p=postgresql.git;a=commitdiff;h=a051e71e2\">a051e71e2<\/a><\/p>\n<p> <\/p>\n<p>Thanks to the patch above, I\/O statistics are no longer tied to the page size, making it possible to start tracking WAL statistics in <code>pg_stat_io<\/code>.<\/p>\n<p> <\/p>\n<p>WAL operations are tracked across various processes, and within two contexts:<\/p>\n<p> <\/p>\n<ul>\n<li>normal \u2013 operations with existing WAL segments,<\/li>\n<li>init \u2013 operations that include initializing a new WAL segment.<\/li>\n<\/ul>\n<p> <\/p>\n<p>For example:<\/p>\n<p> <\/p>\n<pre><code class=\"pgsql\">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 &gt; 0;<\/code><\/pre>\n<p> <\/p>\n<pre><code class=\"plaintext\">   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)<\/code><\/pre>\n<p> <a name=\"commit_9aea73fc6\"><\/a> <\/p>\n<p><strong><a href=\"https:\/\/commitfest.postgresql.org\/51\/5232\/\">pg_stat_get_backend_io: I\/O statistics for a specific process<\/a><\/strong><br \/> commit: <a href=\"https:\/\/git.postgresql.org\/gitweb\/?p=postgresql.git;a=commitdiff;h=9aea73fc6\">9aea73fc6<\/a><\/p>\n<p> <\/p>\n<p>The new function <code>pg_stat_get_backend_io<\/code> returns I\/O statistics for a client backend. It takes the process ID and returns a set of rows in the <code>pg_stat_io<\/code> format.<\/p>\n<p> <\/p>\n<p>For example:<\/p>\n<p> <\/p>\n<pre><code class=\"pgsql\">SELECT object, context,        reads, read_bytes,        writes, write_bytes FROM pg_stat_get_backend_io(pg_backend_pid()) WHERE reads &gt; 0 OR writes &gt; 0;<\/code><\/pre>\n<p> <\/p>\n<pre><code class=\"plaintext\">  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)<\/code><\/pre>\n<p> <\/p>\n<p>The function only works with client backends. Statistics accumulate for as long as the process runs, then flush when the process completes. You can flush the statistics manually with <code>pg_stat_reset_backend_stats<\/code>.<\/p>\n<p> <\/p>\n<p>By combining <code>pg_stat_get_backend_io<\/code> with <code>pg_stat_activity<\/code>, powerful insights can be gained: not just which process runs the longest, but which one of the currently running processes touches the disk most.<\/p>\n<p> <\/p>\n<p>See also:<br \/> <a href=\"https:\/\/bdrouvot.github.io\/2025\/01\/07\/postgres-backend-statistics-part-1\/\">Postgres backend statistics (Part 1): I\/O statistics<\/a> (Bertrand Drouvot)<\/p>\n<p> <a name=\"commit_dc6acfd91\"><\/a> <\/p>\n<p><strong><a href=\"https:\/\/commitfest.postgresql.org\/51\/5346\/\">VACUUM(verbose): visibility map information<\/a><\/strong><br \/> commit: <a href=\"https:\/\/git.postgresql.org\/gitweb\/?p=postgresql.git;a=commitdiff;h=dc6acfd91\">dc6acfd91<\/a>, <a href=\"https:\/\/git.postgresql.org\/gitweb\/?p=postgresql.git;a=commitdiff;h=4b565a198\">4b565a198<\/a><\/p>\n<p> <\/p>\n<p>A newly created table does not have a visibility map yet, so both all-visible and all-frozen counters are at 0.<\/p>\n<p> <\/p>\n<pre><code class=\"pgsql\">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);<\/code><\/pre>\n<p> <\/p>\n<pre><code class=\"plaintext\"> all_visible | all_frozen -------------+------------            0 |          0 (1 row)<\/code><\/pre>\n<p> <\/p>\n<p><code>VACUUM (verbose)<\/code> output now includes a new row <code>visibility map<\/code>. It shows the counter values after vacuuming and the number of all-visible pages (the number in parentheses, 0 in this case). This provides a count of all pages marked as all-visible during vacuuming.<\/p>\n<p> <\/p>\n<pre><code class=\"pgsql\">VACUUM(freeze, verbose) bookings_copy;<\/code><\/pre>\n<p> <\/p>\n<pre><code class=\"plaintext\">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<\/code><\/pre>\n<p> <\/p>\n<p>Now all pages are frozen and visible to all transactions.<\/p>\n<p> <\/p>\n<pre><code class=\"pgsql\">SELECT * FROM pg_visibility_map_summary('bookings_copy'::regclass);<\/code><\/pre>\n<p> <\/p>\n<pre><code class=\"plaintext\"> all_visible | all_frozen -------------+------------        13447 |      13447 (1 row)<\/code><\/pre>\n<p> <a name=\"commit_30a6ed0ce\"><\/a> <\/p>\n<p><strong><a href=\"https:\/\/commitfest.postgresql.org\/patch\/5485\/\">Total vacuum and analysis time per table<\/a><\/strong><br \/> commit: <a href=\"https:\/\/git.postgresql.org\/gitweb\/?p=postgresql.git;a=commitdiff;h=30a6ed0ce\">30a6ed0ce<\/a><\/p>\n<p> <\/p>\n<p>The <code>pg_stat_all_table<\/code> view gets four new columns starting with <code>total<\/code>: <code>total_vacuum_time<\/code>, <code>total_autovacuum_time<\/code>, <code>total_analyze_time<\/code>, and <code>total_autoanalyze_time<\/code>. These columns track vacuum, autovacuum, analyze and autoanalyze times for each table.<\/p>\n<p> <\/p>\n<p>Since tables already track operation counts, together these metrics give the average operation execution time statistics per table. This query returns the table that took the most time to vacuum and analyze:<\/p>\n<p> <\/p>\n<pre><code class=\"pgsql\">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<\/code><\/pre>\n<p> <\/p>\n<pre><code class=\"plaintext\">-[ 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   |<\/code><\/pre>\n<p> <\/p>\n<p>The times are in milliseconds. Clearly, vacuuming and analysis in this test database do not take long.<\/p>\n<p> <\/p>\n<p>See also:<br \/> <a href=\"https:\/\/www.dbi-services.com\/blog\/postgresql-18-per-relation-statistics-for-autovacuum-and-autoanalyze\/\">PostgreSQL 18: Per-relation cumulative statistics for [auto]vacuum and [auto]analyze<\/a>(Daniel Westermann)<\/p>\n<p> <a name=\"commit_c758119e5\"><\/a> <\/p>\n<p><strong><a href=\"https:\/\/commitfest.postgresql.org\/51\/4921\/\">autovacuum: change the number of workers without restarting the server<\/a><\/strong><br \/> commit: <a href=\"https:\/\/git.postgresql.org\/gitweb\/?p=postgresql.git;a=commitdiff;h=c758119e5\">c758119e5<\/a><\/p>\n<p> <\/p>\n<p>The number of autovacuum workers can now be changed without needing to restart the server afterwards. You can just set the new number in <em>autovacuum_max_workers<\/em> and reload the configuration files, as long as the new number does not exceed the number of allocated slots set by the new parameter <em>autovacuum_worker_slots<\/em>. Changing the number of slots, however, does require a restart.<\/p>\n<p> <\/p>\n<pre><code class=\"pgsql\">\\dconfig+ autovacuum*worker*<\/code><\/pre>\n<p> <\/p>\n<pre><code class=\"plaintext\">                      List of configuration parameters         Parameter        | Value |  Type   |  Context   | Access privileges -------------------------+-------+---------+------------+-------------------  autovacuum_max_workers  | 3     | integer | sighup     |  autovacuum_worker_slots | 16    | integer | postmaster | (2 rows)<\/code><\/pre>\n<p> <\/p>\n<p>See also:<br \/> <a href=\"https:\/\/www.depesz.com\/2025\/01\/24\/waiting-for-postgresql-18-allow-changing-autovacuum_max_workers-without-restarting\/\">Waiting for PostgreSQL 18 \u2013 Allow changing autovacuum_max_workers without restarting.<\/a>(Hubert &#8216;depesz&#8217; Lubaczewski)<\/p>\n<p> <a name=\"commit_477728b5d\"><\/a> <\/p>\n<p><strong><a href=\"https:\/\/commitfest.postgresql.org\/51\/5356\/\">psql: connection service information<\/a><\/strong><br \/> commit: <a href=\"https:\/\/git.postgresql.org\/gitweb\/?p=postgresql.git;a=commitdiff;h=477728b5d\">477728b5d<\/a><\/p>\n<p> <\/p>\n<p>The connection service file <code>pg_service.conf<\/code> is used to connect to various databases without the need to enter the credentials manually every time.<\/p>\n<p> <\/p>\n<pre><code class=\"bash\">$ grep -A 6 '^\\[replica\\]' ~\/.pg_service.conf<\/code><\/pre>\n<p> <\/p>\n<pre><code class=\"plaintext\">[replica] # Primary server replica host=localhost port=5433 user=postgres dbname=demo options=-c search_path=bookings<\/code><\/pre>\n<p> <\/p>\n<p>Any application using <code>libpq<\/code> can connect by the service name <code>replica<\/code>. In psql, the service name is now stored in the variable <code>SERVICE<\/code>:<\/p>\n<p> <\/p>\n<pre><code class=\"bash\">$ psql service=replica -c '\\echo :SERVICE'<\/code><\/pre>\n<p> <\/p>\n<pre><code class=\"plaintext\">replica<\/code><\/pre>\n<p> <\/p>\n<p>You can also put the service name into the psql prompt with the <code>%s<\/code> sequence.<\/p>\n<p> <\/p>\n<p>See also:<br \/> <a href=\"https:\/\/www.depesz.com\/2025\/01\/06\/waiting-for-postgresql-18-psql-add-more-information-about-service-name\/\">Waiting for PostgreSQL 18 \u2013 psql: Add more information about service name<\/a> (Hubert &#8216;depesz&#8217; Lubaczewski)<\/p>\n<p> <a name=\"commit_00f4c2959\"><\/a> <\/p>\n<p><strong><a href=\"https:\/\/commitfest.postgresql.org\/51\/5476\/\">psql: expanded display for \\d* commands<\/a><\/strong><br \/> commit: <a href=\"https:\/\/git.postgresql.org\/gitweb\/?p=postgresql.git;a=commitdiff;h=00f4c2959\">00f4c2959<\/a><\/p>\n<p> <\/p>\n<p>In order for <code>\\d*<\/code> commands to display expanded output, the mode had to be turned on before running the command, and turned off afterwards. It was inconvenient to type every time, and the mode change notifications were unnecessary.<\/p>\n<p> <\/p>\n<pre><code class=\"pgsql\">\\x \\du+ \\x<\/code><\/pre>\n<p> <\/p>\n<pre><code class=\"plaintext\">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.<\/code><\/pre>\n<p> <\/p>\n<p>Now, commands that return object lists support the <code>x<\/code> flag that enables the expanded display mode for the current command only:<\/p>\n<p> <\/p>\n<pre><code class=\"pgsql\">\\dux+<\/code><\/pre>\n<p> <\/p>\n<pre><code class=\"plaintext\">List of roles -[ RECORD 1 ]----------------------------------------------------------- Role name   | postgres Attributes  | Superuser, Create role, Create DB, Replication, Bypass RLS Description |<\/code><\/pre>\n<p> <\/p>\n<p>The behavior is similar to the expanded display mode flag <code>\\gx<\/code> for queries. The <code>x<\/code> and <code>+<\/code> flags can come in any order.<\/p>\n<p> <\/p>\n<pre><code class=\"pgsql\">\\dt+x tickets<\/code><\/pre>\n<p> <\/p>\n<pre><code class=\"plaintext\">List of tables -[ RECORD 1 ]-+---------- Schema        | bookings Name          | tickets Type          | table Owner         | postgres Persistence   | permanent Access method | heap Size          | 386 MB Description   | Tickets<\/code><\/pre>\n<p> <a name=\"commit_2355e5111\"><\/a> <\/p>\n<p><strong><a href=\"https:\/\/commitfest.postgresql.org\/51\/5102\/\">psql: leakproof flag in \\df* output<\/a><\/strong><br \/> commit: <a href=\"https:\/\/git.postgresql.org\/gitweb\/?p=postgresql.git;a=commitdiff;h=2355e5111\">2355e5111<\/a><\/p>\n<p> <\/p>\n<p>The commands <code>\\df+<\/code>, <code>\\do+<\/code>, <code>\\dAo+<\/code>, and <code>\\dC+<\/code> now have the <code>leakproof<\/code> flag in their output. The new flag <code>x<\/code> shows just that:<\/p>\n<p> <\/p>\n<pre><code class=\"pgsql\">\\df+x bookings.now<\/code><\/pre>\n<p> <\/p>\n<pre><code class=\"plaintext\">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<\/code><\/pre>\n<p> <a name=\"commit_a5579a90a\"><\/a> <\/p>\n<p><strong><a href=\"https:\/\/commitfest.postgresql.org\/51\/5152\/\">jsonb: null conversion to other types<\/a><\/strong><br \/> commit: <a href=\"https:\/\/git.postgresql.org\/gitweb\/?p=postgresql.git;a=commitdiff;h=a5579a90a\">a5579a90a<\/a><\/p>\n<p> <\/p>\n<p>After converting a JSON null to the SQL text type, the jsonb element becomes a string: <code>'null'<\/code>. However, conversion to another scalar type, for example int or float, causes an error:<\/p>\n<p> <\/p>\n<pre><code class=\"pgsql\">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;<\/code><\/pre>\n<p> <\/p>\n<pre><code class=\"plaintext\">ERROR:  cannot cast jsonb null to type integer<\/code><\/pre>\n<p> <\/p>\n<p>This patch fixes that, converting JSON nulls to SQL NULLs properly:<\/p>\n<p> <\/p>\n<pre><code class=\"pgsql\">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;<\/code><\/pre>\n<p> <\/p>\n<pre><code class=\"plaintext\"> null_as_int | null_as_text -------------+--------------              | null (1 row)<\/code><\/pre>\n<p> <a name=\"commit_db6a4a985\"><\/a> <\/p>\n<p><strong><a href=\"https:\/\/commitfest.postgresql.org\/51\/5295\/\">MD5 encryption algorithm: end-of-support preparations<\/a><\/strong><br \/> commit: <a href=\"https:\/\/git.postgresql.org\/gitweb\/?p=postgresql.git;a=commitdiff;h=db6a4a985\">db6a4a985<\/a><\/p>\n<p> <\/p>\n<p>The MD5-encrypted password security issues are <a href=\"https:\/\/en.wikipedia.org\/wiki\/MD5#Security\">notorious<\/a>. Therefore, this algorithm will not be supported in PostgreSQL 19 and later.<\/p>\n<p> <\/p>\n<p>In PostgreSQL 18, MD5 still works, but it has been declared obsolete in the documentation, and attempting to create an MD5-encrypted password shows a warning:<\/p>\n<p> <\/p>\n<pre><code class=\"pgsql\">SET password_encryption = 'md5';  CREATE ROLE alice LOGIN PASSWORD 'secret';<\/code><\/pre>\n<p> <\/p>\n<pre><code class=\"plaintext\">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<\/code><\/pre>\n<p> <\/p>\n<p>You can disable the warning with the parameter <em>md5_password_warnings<\/em>, but that should not delay your transition away from MD5.<\/p>\n<p> <a name=\"commit_78c5e141e\"><\/a> <\/p>\n<p><strong><a href=\"https:\/\/commitfest.postgresql.org\/51\/4388\/\">New function uuidv7<\/a><\/strong><br \/> commit: <a href=\"https:\/\/git.postgresql.org\/gitweb\/?p=postgresql.git;a=commitdiff;h=78c5e141e\">78c5e141e<\/a><\/p>\n<p> <\/p>\n<p>The existing function <code>gen_random_uuid<\/code> returns a UUID version 4. The values are generated randomly, and sorting them does not indicate the order they came in:<\/p>\n<p> <\/p>\n<pre><code class=\"pgsql\">SELECT g.x, gen_random_uuid() from generate_series(1,5) AS g(x) ORDER BY 2;<\/code><\/pre>\n<p> <\/p>\n<pre><code class=\"plaintext\"> 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)<\/code><\/pre>\n<p> <\/p>\n<p>The UUIDv7 <a href=\"https:\/\/datatracker.ietf.org\/doc\/html\/rfc9562\">specification<\/a> says that the values must begin with a Unix-epoch timestamp in milliseconds, therefore, generated values will increase in order. The new function <code>uuidv7<\/code> ensures this monotonous growth within the scope of a given client backend:<\/p>\n<p> <\/p>\n<pre><code class=\"pgsql\">SELECT g.x, uuidv7() from generate_series(1,5) AS g(x) ORDER BY 2;<\/code><\/pre>\n<p> <\/p>\n<pre><code class=\"plaintext\"> 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)<\/code><\/pre>\n<p> <\/p>\n<p>In addition, the function <code>uuid_extract_timestamp<\/code> returns the time and date when a UUID was created, and <code>uuidv7<\/code> itself can shift the time forward by a given interval:<\/p>\n<p> <\/p>\n<pre><code class=\"pgsql\">SELECT g.x,        uuid_extract_timestamp(          uuidv7(format('%s day',g.x)::interval)        ) from generate_series(-2,2) AS g(x) ORDER BY 2;<\/code><\/pre>\n<p> <\/p>\n<pre><code class=\"plaintext\"> 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)<\/code><\/pre>\n<p> <\/p>\n<p>See also:<br \/> <a href=\"https:\/\/www.depesz.com\/2024\/12\/31\/waiting-for-postgresql-18-add-uuid-version-7-generation-function\/\">Waiting for PostgreSQL 18 \u2013 Add UUID version 7 generation function.<\/a>(Hubert &#8216;depesz&#8217; Lubaczewski)<\/p>\n<p> <a name=\"commit_761c79508\"><\/a> <\/p>\n<p><strong><a href=\"https:\/\/commitfest.postgresql.org\/51\/5423\/\">postgres_fdw: SCRAM authentication without storing the password<\/a><\/strong><br \/> commit: <a href=\"https:\/\/git.postgresql.org\/gitweb\/?p=postgresql.git;a=commitdiff;h=761c79508\">761c79508<\/a><\/p>\n<p> <\/p>\n<p>For authentication by password on a remote server to function, user passwords had to be explicitly set in the user mapping configuration (<code>CREATE USER MAPPING<\/code>). To avoid storing passwords openly, the new remote server and table parameter <code>use_scram_passthrough<\/code> is introduced.<\/p>\n<p> <\/p>\n<p>Create a role <code>alice<\/code> on a remote server with a password encrypted with the scram-sha-256 algorithm. In <code>pg_hba.conf<\/code>, the only authentication method allowed for <code>alice<\/code> is SCRAM.<\/p>\n<p> <\/p>\n<pre><code class=\"pgsql\">remote=# SELECT * FROM pg_hba_file_rules LIMIT 1<\/code><\/pre>\n<p> <\/p>\n<pre><code class=\"plaintext\">-[ 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       |<\/code><\/pre>\n<p> <\/p>\n<p>This is what <code>postgres_fdw<\/code> configuration on the local server will look like:<\/p>\n<p> <\/p>\n<pre><code class=\"pgsql\">local=# CREATE EXTENSION postgres_fdw;<\/code><\/pre>\n<p> <\/p>\n<p>Enable the new parameter <a href=\"https:\/\/www.postgresql.org\/docs\/devel\/postgres-fdw.html#POSTGRES-FDW-OPTIONS-CONNECTION-MANAGEMENT\">use_scram_passthrough<\/a>:<\/p>\n<p> <\/p>\n<pre><code class=\"pgsql\">local=# CREATE SERVER remote_server   FOREIGN DATA WRAPPER postgres_fdw   OPTIONS (     host 'localhost',     port '5400',     dbname 'postgres',     use_scram_passthrough 'true' );<\/code><\/pre>\n<p> <\/p>\n<p><code>alice<\/code> will also be accessing the remote server. We will set the password for <code>alice<\/code> in a bit. Note that you do not need to specify <code>alice<\/code>&#8216;s remote server password here:<\/p>\n<p> <\/p>\n<pre><code class=\"pgsql\">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;<\/code><\/pre>\n<p> <\/p>\n<p>Now, for the local <code>alice<\/code>, set the same password as the remote one. You need to copy the entire encrypted password string from the remote server&#8217;s <code>pg_authid<\/code>:<\/p>\n<p> <\/p>\n<pre><code class=\"pgsql\">remote=# SELECT rolpassword FROM pg_authid WHERE rolname = 'alice';<\/code><\/pre>\n<p> <\/p>\n<p>\u2026 and put it in as the local <code>alice<\/code>&#8216;s password:<\/p>\n<p> <\/p>\n<pre><code class=\"pgsql\">local=# ALTER ROLE alice PASSWORD '...';<\/code><\/pre>\n<p> <\/p>\n<p>Now, the local <code>alice<\/code> can create and access remote tables without entering a password.<\/p>\n<p> <a name=\"commit_f7e1b3828\"><\/a> <\/p>\n<p><strong><a href=\"https:\/\/commitfest.postgresql.org\/51\/5380\/\">passwordcheck: minimum password length<\/a><\/strong><br \/> commit: <a href=\"https:\/\/git.postgresql.org\/gitweb\/?p=postgresql.git;a=commitdiff;h=f7e1b3828\">f7e1b3828<\/a><\/p>\n<p> <\/p>\n<p>The new parameter for the <em>passwordcheck<\/em> extension sets the minimum accepted password length.<\/p>\n<p> <\/p>\n<pre><code class=\"pgsql\">SET passwordcheck.min_password_length = 8;  ALTER ROLE postgres PASSWORD '123';<\/code><\/pre>\n<p> <\/p>\n<pre><code class=\"plaintext\">ERROR:  password is too short DETAIL:  password must be at least \"passwordcheck.min_password_length\" (8) bytes long<\/code><\/pre>\n<p> <\/p>\n<p>Transmitting passwords as plain text is unsafe, so most client applications can encrypt passwords before sending them to the server: for example, the <code>\\password<\/code> command in psql. However, with pre-encrypted passwords, the <em>passwordcheck.min_password_length<\/em> parameter will not work.<\/p>\n<p> <a name=\"commit_bfc599206\"><\/a> <\/p>\n<p><strong>New function casefold and pg_unicode_fast collation<\/strong><br \/> commit: <a href=\"https:\/\/git.postgresql.org\/gitweb\/?p=postgresql.git;a=commitdiff;h=bfc599206\">bfc599206<\/a>, <a href=\"https:\/\/git.postgresql.org\/gitweb\/?p=postgresql.git;a=commitdiff;h=d3d098316\">d3d098316<\/a>, <a href=\"https:\/\/git.postgresql.org\/gitweb\/?p=postgresql.git;a=commitdiff;h=286a365b9\">286a365b9<\/a><\/p>\n<p> <\/p>\n<p><code>casefold<\/code> is a case-independent search function that works when simply converting the strings to upper or lower case does not suffice. Depending on collation, several lowercase variants can exist for a single character.<\/p>\n<p> <\/p>\n<p>Here is an example for the <a href=\"https:\/\/en.wikipedia.org\/wiki\/%C3%9F\">Eszett<\/a>:<\/p>\n<p> <\/p>\n<pre><code class=\"pgsql\">SELECT c, lower(c), casefold(c), upper(c) FROM (VALUES('\u00df' collate \"pg_unicode_fast\")) AS t(c);<\/code><\/pre>\n<p> <\/p>\n<pre><code class=\"plaintext\"> c | lower | casefold | upper ---+-------+----------+-------  \u00df | \u00df     | ss       | SS (1 row)<\/code><\/pre>\n<p> <\/p>\n<p>The new <a href=\"https:\/\/www.postgresql.org\/docs\/devel\/collation.html#COLLATION-MANAGING-STANDARD\">collation<\/a> <code>pg_unicode_fast<\/code> is used here. It matches the SQL standard <code>ucs_basic<\/code> collation more closely.<\/p>\n<p> <a name=\"commit_80feb727c\"><\/a> <\/p>\n<p><strong><a href=\"https:\/\/commitfest.postgresql.org\/51\/4723\/\">DML commands: RETURNING with OLD and NEW<\/a><\/strong><br \/> commit: <a href=\"https:\/\/git.postgresql.org\/gitweb\/?p=postgresql.git;a=commitdiff;h=80feb727c\">80feb727c<\/a><\/p>\n<p> <\/p>\n<p>In the <code>INSERT<\/code>, <code>UPDATE<\/code>, <code>DELETE<\/code>, and <code>MERGE<\/code> commands, the <code>RETURNING<\/code> clause can now return both old values (before executing the command) and new ones, using <code>OLD<\/code> and <code>NEW<\/code>, respectively (similarly to old and new values in row level triggers).<\/p>\n<p> <\/p>\n<pre><code class=\"pgsql\">CREATE TABLE t(     id integer PRIMARY KEY,     s text );<\/code><\/pre>\n<p> <\/p>\n<p>Since the old values are always NULL in <code>INSERT<\/code>, returning them serves no purpose.<\/p>\n<p> <\/p>\n<pre><code class=\"pgsql\">INSERT INTO t VALUES (1,'aaa'), (2, 'bbb') RETURNING old.*, new.*;<\/code><\/pre>\n<p> <\/p>\n<pre><code class=\"plaintext\"> id | s | id |  s   ----+---+----+-----     |   |  1 | aaa     |   |  2 | bbb (2 rows)<\/code><\/pre>\n<p> <\/p>\n<p>For <code>DELETE<\/code>, there are no <code>NEW<\/code> values:<\/p>\n<p> <\/p>\n<pre><code class=\"pgsql\">DELETE FROM t WHERE id &gt; 1 RETURNING old.*, new.*;<\/code><\/pre>\n<p> <\/p>\n<pre><code class=\"plaintext\"> id |  s  | id | s ----+-----+----+---   2 | bbb |    | (1 row)<\/code><\/pre>\n<p> <\/p>\n<p>But with <code>UPDATE<\/code>, being able to access both states may come in handy for change tracking:<\/p>\n<p> <\/p>\n<pre><code class=\"pgsql\">UPDATE t SET s = 'ccc' WHERE id = 1 RETURNING old.*, new.*,           format('%s-&gt;%s', old.s, new.s) AS change;<\/code><\/pre>\n<p> <\/p>\n<pre><code class=\"plaintext\"> id |  s  | id |  s  |  change   ----+-----+----+-----+----------   1 | aaa |  1 | ccc | aaa-&gt;ccc (1 row)<\/code><\/pre>\n<p> <\/p>\n<p>Same goes for <code>INSERT<\/code> with <code>ON CONFLICT<\/code>:<\/p>\n<p> <\/p>\n<pre><code class=\"pgsql\">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;<\/code><\/pre>\n<p> <\/p>\n<pre><code class=\"plaintext\">        old         |        new          --------------------+--------------------  {\"id\":1,\"s\":\"ccc\"} | {\"id\":1,\"s\":\"ddd\"}                     | {\"id\":3,\"s\":\"eee\"} (2 rows)<\/code><\/pre>\n<p> <\/p>\n<p>See also:<br \/> <a href=\"https:\/\/www.depesz.com\/2025\/01\/30\/waiting-for-postgresql-18-add-old-new-support-to-returning-in-dml-queries\/\">Waiting for PostgreSQL 18 \u2013 Add OLD\/NEW support to RETURNING in DML queries.<\/a>(Hubert &#8216;depesz&#8217; Lubaczewski)<\/p>\n<p> <a name=\"commit_172e6b3ad\"><\/a> <\/p>\n<p><strong><a href=\"https:\/\/commitfest.postgresql.org\/51\/5221\/\">to_number: convert a string of roman numberals to numeric<\/a><\/strong><br \/> commit: <a href=\"https:\/\/git.postgresql.org\/gitweb\/?p=postgresql.git;a=commitdiff;h=172e6b3ad\">172e6b3ad<\/a><\/p>\n<p> <\/p>\n<p>Converting an integer to roman numerals has been possible for a while now with the help of <code>to_char<\/code>. Now, the new function <code>to_number<\/code> can do the reverse:<\/p>\n<p> <\/p>\n<pre><code class=\"pgsql\">SELECT to_char(42, 'RN'),        to_number('XLII', 'RN');<\/code><\/pre>\n<p> <\/p>\n<pre><code class=\"plaintext\">     to_char     | to_number -----------------+-----------             XLII |        42 (1 row)<\/code><\/pre>\n<p> <\/p>\n<p>See also:<br \/> <a href=\"https:\/\/www.depesz.com\/2025\/02\/09\/waiting-for-postgresql-18-support-rn-roman-numeral-format-in-to_number\/\">Waiting for PostgreSQL 18 \u2013 Support RN (roman-numeral format) in to_number().<\/a>(Hubert &#8216;depesz&#8217; Lubaczewski)<\/p>\n<hr\/>\n<p> <\/p>\n<p>This is all for now. A review of the last, March CommitFest for PostgreSQL 18 is coming up soon!<\/p>\n<\/div>\n<\/div>\n<\/div>\n<p><!----><!----><\/div>\n<p><!----><!----><br \/> \u0441\u0441\u044b\u043b\u043a\u0430 \u043d\u0430 \u043e\u0440\u0438\u0433\u0438\u043d\u0430\u043b \u0441\u0442\u0430\u0442\u044c\u0438 <a href=\"https:\/\/habr.com\/ru\/articles\/905934\/\"> https:\/\/habr.com\/ru\/articles\/905934\/<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<div><!--[--><!--]--><\/div>\n<div id=\"post-content-body\">\n<div>\n<div class=\"article-formatted-body article-formatted-body article-formatted-body_version-1\">\n<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">\n<p><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/webt\/fc\/ru\/ch\/fcruchd4hzgooayvankxaup0gpe.png\" sizes=\"(max-width: 780px) 100vw, 50vw\" srcset=\"https:\/\/habrastorage.org\/r\/w780\/webt\/fc\/ru\/ch\/fcruchd4hzgooayvankxaup0gpe.png 780w,&#10;       https:\/\/habrastorage.org\/r\/w1560\/webt\/fc\/ru\/ch\/fcruchd4hzgooayvankxaup0gpe.png 781w\" loading=\"lazy\" decode=\"async\"\/><\/p>\n<p> <\/p>\n<p>We continue to follow the news about PostgreSQL 18. The January CommitFest brings in some notable improvements to monitoring, as well as other new features.<\/p>\n<p> <\/p>\n<p>You can find previous reviews of PostgreSQL 18 CommitFests here: <a href=\"https:\/\/postgrespro.com\/blog\/pgsql\/5971431\">2024-07<\/a>, <a href=\"https:\/\/postgrespro.com\/blog\/pgsql\/5971632\">2024-09<\/a>, <a href=\"https:\/\/postgrespro.com\/blog\/pgsql\/5971769\">2024-11<\/a>.<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[],"tags":[],"class_list":["post-458274","post","type-post","status-publish","format-standard","hentry"],"_links":{"self":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/458274","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=458274"}],"version-history":[{"count":0,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/458274\/revisions"}],"wp:attachment":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=458274"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=458274"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=458274"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}