From 835e9b15b40bc0a423d542f11945c51ef4b8aba2 Mon Sep 17 00:00:00 2001 From: Shveta Malik Date: Wed, 14 Oct 2026 09:47:15 +0530 Subject: [PATCH] Fix and Test for Datestyle, IntervalStyle, extra_float_digits - With IntervalStyle = sql_standard, a mixed-sign interval such as '-1 day -11:22:33' is stored as '-1 11:22:33', which postgres-style parsing reads back as '-1 days +11:22:33' -- a wrong value. - With extra_float_digits < 0, a float8 key permanently loses precision and no longer round-trips. Add code changes for IntervalStyle and extra_float_digits. Add regression tests with non-default IntervalStyle, extra_float_digits and DateStyle on the subscriber, verifying the stored format and their round-trip back to the original key values. --- src/backend/replication/logical/conflict.c | 13 +++ src/test/subscription/t/035_conflicts.pl | 102 +++++++++++++++++++++ 2 files changed, 115 insertions(+) diff --git a/src/backend/replication/logical/conflict.c b/src/backend/replication/logical/conflict.c index 8df5fd6dbde..4683253587b 100644 --- a/src/backend/replication/logical/conflict.c +++ b/src/backend/replication/logical/conflict.c @@ -1071,10 +1071,23 @@ build_index_key_json(EState *estate, Relation localrel, build_index_datums_from_slot(estate, localrel, slot, indexDesc, values, isnull); + /* + * Store the key values in a consistent, standard format, independent of + * the apply worker's DateStyle, IntervalStyle and extra_float_digits + * settings, so that the stored key values always convert back + * unambiguously to the original ones. + */ save_nestlevel = NewGUCNestLevel(); (void) set_config_option("datestyle", "ISO", PGC_USERSET, PGC_S_SESSION, GUC_ACTION_SAVE, true, 0, false); + (void) set_config_option("intervalstyle", "postgres", + PGC_USERSET, PGC_S_SESSION, + GUC_ACTION_SAVE, true, 0, false); + (void) set_config_option("extra_float_digits", "3", + PGC_USERSET, PGC_S_SESSION, + GUC_ACTION_SAVE, true, 0, false); + /* * Build the JSON object here rather than handing the values to * row_to_json(), and render each of them with its type's output function. diff --git a/src/test/subscription/t/035_conflicts.pl b/src/test/subscription/t/035_conflicts.pl index bd1b20b664a..d3036384eb4 100644 --- a/src/test/subscription/t/035_conflicts.pl +++ b/src/test/subscription/t/035_conflicts.pl @@ -162,6 +162,108 @@ $node_subscriber->wait_for_log( pass('multiple_unique_conflicts detected on a leaf partition during insert'); +############################################################################### +# Verify that the JSON stored in the conflict log table uses a consistent, +# standard format, independent of the apply worker's DateStyle / IntervalStyle / +# extra_float_digits settings, so that the stored key values always convert +# back to the original ones. +############################################################################### + +# Clear the error state left by the leaf partition test. +$node_subscriber->safe_psql('postgres', "TRUNCATE conf_tab_2;"); +$node_publisher->wait_for_catchup($appname); + +# Non-default output-related GUCs for sessions of the test database; the apply +# worker picks them up when it (re)connects. +$node_subscriber->safe_psql('postgres', + "ALTER DATABASE postgres SET IntervalStyle = 'sql_standard'"); +$node_subscriber->safe_psql('postgres', + "ALTER DATABASE postgres SET extra_float_digits = -2"); +$node_subscriber->safe_psql('postgres', + "ALTER DATABASE postgres SET DateStyle = 'SQL, MDY'"); + +# Table with interval/float8/timestamptz replica identity key columns. +my $guc_ddl = qq( + CREATE TABLE conf_guc (a int, i interval NOT NULL, f float8 NOT NULL, + ts timestamptz NOT NULL); + CREATE UNIQUE INDEX conf_guc_ri ON conf_guc (i, f, ts); + ALTER TABLE conf_guc REPLICA IDENTITY USING INDEX conf_guc_ri; +); +$node_publisher->safe_psql('postgres', $guc_ddl); +$node_subscriber->safe_psql('postgres', $guc_ddl); + +$node_publisher->safe_psql('postgres', + "ALTER PUBLICATION pub_tab ADD TABLE conf_guc"); +$node_publisher->safe_psql('postgres', + "INSERT INTO conf_guc VALUES (1, '-1 day -11:22:33', 1.23456789012345, + '2026-03-04 10:11:12.123456+05:30')"); + +# Pick up the new table, and restart the apply worker so that it picks up the +# new database settings. +$node_subscriber->safe_psql('postgres', + "ALTER SUBSCRIPTION sub_tab REFRESH PUBLICATION"); +$node_subscriber->safe_psql('postgres', + "ALTER SUBSCRIPTION sub_tab DISABLE"); + +# Wait for the apply worker to stop +$node_subscriber->poll_query_until('postgres', + "SELECT count(*) = 0 FROM pg_stat_activity WHERE backend_type = 'logical replication apply worker'" +); + +$node_subscriber->safe_psql('postgres', + "ALTER SUBSCRIPTION sub_tab ENABLE"); +$node_subscriber->wait_for_subscription_sync($node_publisher, $appname); + +# Generate an update_missing conflict; its replica_identity JSON in the +# conflict log table contains the key values. +$node_subscriber->safe_psql('postgres', "DELETE FROM conf_guc"); +$node_publisher->safe_psql('postgres', + "UPDATE conf_guc SET a = 2 WHERE a = 1"); + +$node_subscriber->poll_query_until('postgres', + "SELECT count(*) > 0 FROM $clt WHERE relname = 'conf_guc' + AND conflict_type = 'update_missing'") + or die "timed out waiting for conf_guc conflict row"; + +# The stored interval and float formats must be the standard ones, so they +# can be converted back to the original values even when the apply worker +# ran with IntervalStyle = sql_standard and extra_float_digits = -2. +is( $node_subscriber->safe_psql('postgres', + "SELECT replica_identity->>'i', replica_identity->>'f' + FROM $clt WHERE relname = 'conf_guc'"), + '-1 days -11:22:33|1.23456789012345', + 'CLT JSON stores canonical interval and lossless float format'); + +# The timestamp format is ISO (DateStyle-independent) even with +# DateStyle = 'SQL, MDY'. +like( + $node_subscriber->safe_psql('postgres', + "SELECT replica_identity->>'ts' FROM $clt WHERE relname = 'conf_guc'"), + qr/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}(\.\d+)?[+-]\d{2}(:\d{2}){0,2}$/, + 'CLT JSON stores timestamp in ISO format'); + +# A reader with default settings must get the original key values back. +is( $node_subscriber->safe_psql('postgres', + "SET IntervalStyle = 'postgres'; SET extra_float_digits = 1; + SET DateStyle = 'SQL, DMY'; + SELECT (replica_identity->>'i')::interval + = interval '-1 day -11:22:33', + (replica_identity->>'f')::float8 + = 1.23456789012345::float8, + (replica_identity->>'ts')::timestamptz + = timestamptz '2026-03-04 10:11:12.123456+05:30' + FROM $clt WHERE relname = 'conf_guc'"), + 't|t|t', + 'CLT JSON values convert back to the original key values'); + +# Restore the defaults for any later tests. +$node_subscriber->safe_psql('postgres', + "ALTER DATABASE postgres RESET IntervalStyle"); +$node_subscriber->safe_psql('postgres', + "ALTER DATABASE postgres RESET extra_float_digits"); +$node_subscriber->safe_psql('postgres', + "ALTER DATABASE postgres RESET DateStyle"); + ############################################################################### # Setup a bidirectional logical replication between node_A & node_B ############################################################################### -- 2.34.1