# Copyright (c) 2026, PostgreSQL Global Development Group

# Reproducer: the conflict log table and the server log disagree on the key
# when DROP INDEX CONCURRENTLY demotes the replica identity index while the
# apply worker is in the middle of a change.
#
# Copy to src/test/subscription/t/ and run with meson test or prove.  The
# last test fails on v87: the table gets {"x":"7"} from the index the search
# used, the log gets the primary key from GetRelationIdentityOrPK().
use strict;
use warnings FATAL => 'all';
use PostgreSQL::Test::Cluster;
use PostgreSQL::Test::Utils;
use Test::More;

plan skip_all => 'Injection points not supported by this build'
  unless $ENV{enable_injection_points} eq 'yes';

my $pub = PostgreSQL::Test::Cluster->new('publisher');
$pub->init(allows_streaming => 'logical');
$pub->start;

my $sub = PostgreSQL::Test::Cluster->new('subscriber');
$sub->init;
$sub->append_conf('postgresql.conf', "track_commit_timestamp = on\n");
$sub->start;
$sub->safe_psql('postgres', 'CREATE EXTENSION injection_points');

# Replica identity is the unique index on x, the primary key is on z.
$pub->safe_psql(
	'postgres', q[
	CREATE TABLE t (x int NOT NULL, y int, z int NOT NULL);
	CREATE UNIQUE INDEX t_ri ON t (x);
	ALTER TABLE t REPLICA IDENTITY USING INDEX t_ri;
	INSERT INTO t SELECT i, i, i + 100 FROM generate_series(1, 10) i;
	CREATE PUBLICATION pub FOR TABLE t;
]);
$sub->safe_psql(
	'postgres', q[
	CREATE TABLE t (x int NOT NULL, y int, z int PRIMARY KEY);
	CREATE UNIQUE INDEX t_ri ON t (x);
	ALTER TABLE t REPLICA IDENTITY USING INDEX t_ri;
]);
my $connstr = $pub->connstr . ' dbname=postgres';
$sub->safe_psql('postgres',
	"CREATE SUBSCRIPTION sub CONNECTION '$connstr application_name=sub' "
	  . "PUBLICATION pub WITH (conflict_log_destination = 'all')");
$sub->wait_for_subscription_sync($pub, 'sub');
my $clt = $sub->safe_psql('postgres',
	"SELECT 'pg_conflict.pg_conflict_log_' || oid FROM pg_subscription WHERE subname = 'sub'"
);

# Make the next remote update an update_origin_differs conflict.
$sub->safe_psql('postgres', "UPDATE t SET y = -7 WHERE x = 7");

# Park the worker before it opens the indexes, then commit the first phase
# of the drop, which clears indisreplident.
$sub->safe_psql('postgres',
	"SELECT injection_points_attach('apply-update-before-open-indices', 'wait')"
);
my $log_offset = -s $sub->logfile;
$pub->safe_psql('postgres', "UPDATE t SET y = 77 WHERE x = 7");
$sub->wait_for_event('logical replication apply worker',
	'apply-update-before-open-indices');

my $drop = $sub->background_psql('postgres');
$drop->query_until(
	qr/starting_drop/, q[
	\echo starting_drop
	DROP INDEX CONCURRENTLY t_ri;
]);
$sub->poll_query_until('postgres',
	"SELECT count(*) = 0 FROM pg_index WHERE indrelid = 't'::regclass AND indisreplident"
) or die "timed out waiting for indisreplident to be cleared";

$sub->safe_psql(
	'postgres', q[
	SELECT injection_points_detach('apply-update-before-open-indices');
	SELECT injection_points_wakeup('apply-update-before-open-indices');
]);
$pub->wait_for_catchup('sub');
$sub->wait_for_log(qr/conflict=update_origin_differs/, $log_offset);
ok($drop->quit, 'DROP INDEX CONCURRENTLY completes');

is($sub->safe_psql('postgres', "SELECT y FROM t WHERE x = 7"),
	'77', 'change is applied through the demoted index');
is( $sub->safe_psql(
		'postgres',
		"SELECT replica_identity::text FROM $clt WHERE conflict_type = 'update_origin_differs'"
	),
	'{"x":"7"}',
	'conflict log table shows the key the search used');
like(
	slurp_file($sub->logfile, $log_offset),
	qr/replica identity \(x\)=\(7\)/,
	'server log shows the same key');

done_testing();
