Received: from malur.postgresql.org ([217.196.149.56]) by arkaria.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.96) (envelope-from ) id 1vqGCH-007Y3v-1W for pgsql-bugs@arkaria.postgresql.org; Wed, 11 Feb 2026 19:48:26 +0000 Received: from localhost ([127.0.0.1] helo=malur.postgresql.org) by malur.postgresql.org with esmtp (Exim 4.96) (envelope-from ) id 1vqGCG-005PQT-0i for pgsql-bugs@arkaria.postgresql.org; Wed, 11 Feb 2026 19:48:25 +0000 Received: from magus.postgresql.org ([2a02:c0:301:0:ffff::29]) by malur.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.96) (envelope-from ) id 1vqGCF-005PQK-2w for pgsql-bugs@lists.postgresql.org; Wed, 11 Feb 2026 19:48:24 +0000 Received: from mail-lf1-x129.google.com ([2a00:1450:4864:20::129]) by magus.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 (Exim 4.98.2) (envelope-from ) id 1vqGCE-00000000CcY-3ELu for pgsql-bugs@lists.postgresql.org; Wed, 11 Feb 2026 19:48:24 +0000 Received: by mail-lf1-x129.google.com with SMTP id 2adb3069b0e04-59dea72099eso5655854e87.0 for ; Wed, 11 Feb 2026 11:48:22 -0800 (PST) ARC-Seal: i=1; a=rsa-sha256; t=1770839296; cv=none; d=google.com; s=arc-20240605; b=hoGNwlFE7cA4AOuShYWUWkvDxYfyGCd2Y6Iwv91JTFYZ2dwOOdzwQyhDYd7IAqxpC6 3M9LP2+Gzjcs7mnCY+CoOq8s1MF4x1dn15/8lR0d1B3JX3s306/BXQM3mGHXQeDI2QJo /g6tQcc5z++YXQRMOWi/KmhqhWSEUEkwS7SLw1cDnz1IJr65dG7+Ny0boXrdjQmjar/P EYGmHM4FhRW96ZAyUOwpUWdITMjjqJhr1/Pih3wORnjmjY7zeoWbYPosjy6bGND6GSEx WD4O72obO4ZAlQzae9tUjZvN9oYDSUXYuPJ4DX+nmEM5jcoZHos7rLPnTuUMaj1ECL4I hNjg== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20240605; h=cc:to:subject:message-id:date:from:in-reply-to:references :mime-version:dkim-signature; bh=VZFh1JCBlkJLe+3wFBAqmeh2lss4pj9d1wU7F4HlRP4=; fh=UZ2RupGbXr1M5m4uTXiMQoSoiUlO7geb1q/gelp8Hy4=; b=HrbkIezpYJ//uwWYacIlL5cr+ea9GYg5XnSKVsRCzbqWXDxGskpN2muH8LCLN3XEvE 78jHWeEzf1VZPQuJXFQNjBCy+2D0Jg/NJZDsu4e5Ut5Ge9/wzKff7M2M3x0xFSH6rs4F 4KisfZ+A+CG41qBlQO3QgoeGoiAMSNYiGwFGKdk1RfW8iDU7YrWlzxp/AcWb96kNhCek HvRiPwaor45uKtLSYEFcoPZZeqkhjoDoR675lN26gcYrbQgWKJ+KZwbnXrugTw+TE9AA 83Rfot156Q0N4zsKkizib+pjIqDM22cSG4Tm7qO1y62IjgAUBPhom3pbArtaxEPsuQ4+ LZ1A==; darn=lists.postgresql.org ARC-Authentication-Results: i=1; mx.google.com; arc=none DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1770839296; x=1771444096; darn=lists.postgresql.org; h=cc:to:subject:message-id:date:from:in-reply-to:references :mime-version:from:to:cc:subject:date:message-id:reply-to; bh=VZFh1JCBlkJLe+3wFBAqmeh2lss4pj9d1wU7F4HlRP4=; b=P6C/LNq115kNAZChxzlqRUdh6q8wTYKw4n1UXFkgXtlNUULkNF7rVY2iYdGNfmOtm0 ugMyBOs9Vvyn8zuk+os+mJb6jQsifyacmFovE7jRpAs1JveYfZbirlg3OTro4zXTrwN2 +1cOcudyU64y3NZZFP1JIQgyceS5V14KORiOAEhb6ZIVB9zT2Vtq6ZG3cC3C1mikORVN r0obDqWVO/yVGEpAGkUPDGgJXt2Jtan+rkQbgciCitujlXLkiS64lIWuV2Mi+gICcaKw Gh0xZpM0m9wOms6kt9Fst60SvimASsLA/TFQYPufRvqRJe9EhH0z/eYVdkIbzXLjKtnv LK3Q== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1770839296; x=1771444096; h=cc:to:subject:message-id:date:from:in-reply-to:references :mime-version:x-gm-gg:x-gm-message-state:from:to:cc:subject:date :message-id:reply-to; bh=VZFh1JCBlkJLe+3wFBAqmeh2lss4pj9d1wU7F4HlRP4=; b=YluFtzlps2mWjTM08FGGJ0z6WHy1jo3O/nwdlrH8h2XjtocvCGAyAO8OC8QJznNO02 14FYzT2BdPMfJuHj+GiAIlW8j8bQQ4khDry+bQic1KvQq54ymFQR4UtcoaGcgL3y5vmz WjbxfcmoiVNoZ6tuHvHlna9lAtWbA6g/emDkUGwe+eq5rCNmBATw7G0P5hQBFKawaz6S ewSZ1Xh1AGkTa17oE1X7Sv3b7l6vcI6jU/Chi7ZXU+w4QvWprIi3oc557hwSDnAjQdu4 hiPgy8z98SP+EOaGlhf1TuyTFKV5NHl10lLtb0QX7HDINc+4q+IrsEHOIryO2a9Cwd1+ D60Q== X-Gm-Message-State: AOJu0Yx6WsoQoZqp6I7Fi2s8M/GrkI1FQZXKZei1ozwOR0b7Iki7gQ4o qxPyCXVKoglx+AxPPGnFTUDJX/HHnkddeR/0hF40fMiCcbyqlMiKCEsqlTChZ1gcnBksoisTvYy XSJ2yrlZl6/4tiVv5iDPVM2v64GgLLXlbOzxVMCw= X-Gm-Gg: AZuq6aI0vJw0SW6Tv1bdGC+alcS+tz4k97KvxZAcmWhtbA9BN+63l/QGZIPbiXQwwbO Rst/jk5eZXlvkuTtkxN16xVLfu7Y+XvYiAsVfHr5njOZ+h52SKzVgWueK2qsz1kj4DMJDBbjXfk UtRkYNLbhEcL8n5itmp3xev6hURGqSpBeDV2fXtJi66GpJHfFoSUumuiOcmv4PcxNlddgSGQlLF WbJDDJM5L9Hvmd0KLoCkqcHccquGICJe7ZSdwF3eXymBeAIe3yBclSIClgyOiUe/7vTchLu5ZMO 4L1lYriOQAxXQ2NQuarpnczQHcQitH5NtQJIYcSe9o4mzKyKZAHX4dEvo4CBI/d14wAMSTGvkuT TDh9r X-Received: by 2002:a05:6512:3c86:b0:59d:f473:aa8e with SMTP id 2adb3069b0e04-59e6414b6f6mr94296e87.27.1770839296206; Wed, 11 Feb 2026 11:48:16 -0800 (PST) MIME-Version: 1.0 References: <40d694df-39fd-4a4a-9459-9d6489165f60@gogi.tv> In-Reply-To: <40d694df-39fd-4a4a-9459-9d6489165f60@gogi.tv> From: Rafia Sabih Date: Wed, 11 Feb 2026 20:48:04 +0100 X-Gm-Features: AZwV_QgVDeSGe-M-LIPV3ZgRPM07tPPA7-UDXcft6OfPUVzd96FjDQCMk0CyDTM Message-ID: Subject: Re: Two issues with REFRESH MATERIALIZED VIEW CONCURRENTLY To: Giuliano Gagliardi Cc: pgsql-bugs@lists.postgresql.org Content-Type: multipart/mixed; boundary="000000000000348439064a91a703" List-Id: List-Help: List-Subscribe: List-Post: List-Owner: List-Archive: Archived-At: Precedence: bulk --000000000000348439064a91a703 Content-Type: multipart/alternative; boundary="000000000000348436064a91a701" --000000000000348436064a91a701 Content-Type: text/plain; charset="UTF-8" On Sun, 8 Feb 2026 at 22:49, Giuliano Gagliardi wrote: > I noticed the following two issues while looking at the code that handles > REFRESH MATERIALIZED VIEW CONCURRENTLY (refresh_by_match_merge() in > matview.c): > > 1. > > At the beginning of the function, there is some code that checks for > duplicate > rows, but it does not catch the following case: > > CREATE TABLE t(a text, b text); > INSERT INTO t VALUES('test', null); > CREATE MATERIALIZED VIEW m AS SELECT * FROM t; > CREATE UNIQUE INDEX ON m(a); > INSERT INTO t VALUES('test', null); -- t now contains two identical rows > > REFRESH MATERIALIZED VIEW CONCURRENTLY m; > -> no error, but m still contains only one row! > REFRESH MATERIALIZED VIEW m; > -> error (as expected) > Interesting issue and thanks for pointing it out. Going over the code in the function you mentioned(refresh_by_match_merge() in matview.c), I found out that it is explicitly checking for the columns where it is not NULL. appendStringInfo(&querybuf, "SELECT newdata.*::%s FROM %s newdata " "WHERE newdata.* IS NOT NULL AND EXISTS " "(SELECT 1 FROM %s newdata2 WHERE newdata2.* IS NOT NULL " "AND newdata2.* OPERATOR(pg_catalog.*=) newdata.* " "AND newdata2.ctid OPERATOR(pg_catalog.<>) " "newdata.ctid)", It is mentioned in the comments above as well that it checks for the duplicates in the rows without NULLs. However, if I changed the query as in the attached patch, it errors out as otherwise I would have expected. Honestly, I do not understand why it is checking for duplicates excluding null values. Behaviour wise this definitely seems like a bug, but I am not sure if the attached patch is the right way to fix it. -- Regards, Rafia Sabih CYBERTEC PostgreSQL International GmbH --000000000000348436064a91a701 Content-Type: text/html; charset="UTF-8" Content-Transfer-Encoding: quoted-printable


On Sun, 8 Feb 2= 026 at 22:49, Giuliano Gagliardi <gogi@g= ogi.tv> wrote:
I noticed the following two issues while looking at the code that han= dles
REFRESH MATERIALIZED VIEW CONCURRENTLY (refresh_by_match_merge() in matview= .c):

1.

At the beginning of the function, there is some code that checks for duplic= ate
rows, but it does not catch the following case:

CREATE TABLE t(a text, b text);
INSERT INTO t VALUES('test', null);
CREATE MATERIALIZED VIEW m AS SELECT * FROM t;
CREATE UNIQUE INDEX ON m(a);
INSERT INTO t VALUES('test', null); -- t now contains two identical= rows

REFRESH MATERIALIZED VIEW CONCURRENTLY m;
=C2=A0 =C2=A0 =C2=A0-> no error, but m still contains only one row!
REFRESH MATERIALIZED VIEW m;
=C2=A0 =C2=A0 =C2=A0-> error (as expected)
In= teresting issue and thanks for pointing it out.
Going over the co= de in the function you mentioned(refresh_by_match_merge() in matview.c), I = found out that it is explicitly checking for the columns where it is not NU= LL.
appendStringInfo(&querybuf,
"SELECT newdata= .*::%s FROM %s newdata "
"WHERE newdata.* IS NOT NULL AN= D EXISTS "
"(SELECT 1 FROM %s newdata2 WHERE newdata2.* = IS NOT NULL "
"AND newdata2.* OPERATOR(pg_catalog.*=3D) = newdata.* "
"AND newdata2.ctid OPERATOR(pg_catalog.<&= gt;) "
"newdata.ctid)",
It is mentioned i= n the comments above as well that it checks for the duplicates in the rows = without NULLs.
However, if I changed the query as in the attached= patch, it errors out as otherwise I would have expected.
Honestl= y, I do not understand why it is checking for duplicates excluding null val= ues.
Behaviour wise this definitely seems like a bug, but I am no= t sure if the attached patch is the right way to fix it.

--
Regards,
Rafia Sabih
CYBERTE= C PostgreSQL International GmbH
--000000000000348436064a91a701-- --000000000000348439064a91a703 Content-Type: application/octet-stream; name="0001-Check-for-duplicate-rows-with-NULLs.patch" Content-Disposition: attachment; filename="0001-Check-for-duplicate-rows-with-NULLs.patch" Content-Transfer-Encoding: base64 Content-ID: X-Attachment-Id: f_mlifxlwe0 RnJvbSAwY2E0YjEyMTRjODZmMWI5NTllNDMzOWNjOWI4ZTBhMWM2YWEwZmQzIE1vbiBTZXAgMTcg MDA6MDA6MDAgMjAwMQpGcm9tOiBSYWZpYSBTYWJpaCA8cmFmaWEuc2FiaWhAY3liZXJ0ZWMuYXQ+ CkRhdGU6IFdlZCwgMTEgRmViIDIwMjYgMTE6Mzg6MDcgLTA4MDAKU3ViamVjdDogW1BBVENIXSBD aGVjayBmb3IgZHVwbGljYXRlIHJvd3Mgd2l0aCBOVUxMcwoKSW4gY2FzZSBvZiBjb25jdXJyZW50 IHJlZnJlc2ggb2YgbWF0ZXJpYWxpemVkIHZpZXcsIGNoZWNrIGZvcgpjb2x1bW5zIHdpdGggTlVM TCB2YWx1ZXMuIElmIHRoZXJlIGFyZSBkdXBsaWNhdGUgZW50cmllcyB3aXRoIE5VTExTCnRoZW4g ZXJyb3Igb3V0LgotLS0KIHNyYy9iYWNrZW5kL2NvbW1hbmRzL21hdHZpZXcuYyB8IDYgKysrLS0t CiAxIGZpbGUgY2hhbmdlZCwgMyBpbnNlcnRpb25zKCspLCAzIGRlbGV0aW9ucygtKQoKZGlmZiAt LWdpdCBhL3NyYy9iYWNrZW5kL2NvbW1hbmRzL21hdHZpZXcuYyBiL3NyYy9iYWNrZW5kL2NvbW1h bmRzL21hdHZpZXcuYwppbmRleCA4MWE1NWEzM2VmMi4uNDhiYjI1ODJhOTIgMTAwNjQ0Ci0tLSBh L3NyYy9iYWNrZW5kL2NvbW1hbmRzL21hdHZpZXcuYworKysgYi9zcmMvYmFja2VuZC9jb21tYW5k cy9tYXR2aWV3LmMKQEAgLTY0OCw5ICs2NDgsOSBAQCByZWZyZXNoX2J5X21hdGNoX21lcmdlKE9p ZCBtYXR2aWV3T2lkLCBPaWQgdGVtcE9pZCwgT2lkIHJlbG93bmVyLAogCXJlc2V0U3RyaW5nSW5m bygmcXVlcnlidWYpOwogCWFwcGVuZFN0cmluZ0luZm8oJnF1ZXJ5YnVmLAogCQkJCQkgIlNFTEVD VCBuZXdkYXRhLio6OiVzIEZST00gJXMgbmV3ZGF0YSAiCi0JCQkJCSAiV0hFUkUgbmV3ZGF0YS4q IElTIE5PVCBOVUxMIEFORCBFWElTVFMgIgotCQkJCQkgIihTRUxFQ1QgMSBGUk9NICVzIG5ld2Rh dGEyIFdIRVJFIG5ld2RhdGEyLiogSVMgTk9UIE5VTEwgIgotCQkJCQkgIkFORCBuZXdkYXRhMi4q IE9QRVJBVE9SKHBnX2NhdGFsb2cuKj0pIG5ld2RhdGEuKiAiCisJCQkJCSAiV0hFUkUgRVhJU1RT ICIKKwkJCQkJICIoU0VMRUNUIDEgRlJPTSAlcyBuZXdkYXRhMiBXSEVSRSAiCisJCQkJCSAiIG5l d2RhdGEyLiogT1BFUkFUT1IocGdfY2F0YWxvZy4qPSkgbmV3ZGF0YS4qICIKIAkJCQkJICJBTkQg bmV3ZGF0YTIuY3RpZCBPUEVSQVRPUihwZ19jYXRhbG9nLjw+KSAiCiAJCQkJCSAibmV3ZGF0YS5j dGlkKSIsCiAJCQkJCSB0ZW1wbmFtZSwgdGVtcG5hbWUsIHRlbXBuYW1lKTsKLS0gCjIuMzkuNSAo QXBwbGUgR2l0LTE1NCkKCg== --000000000000348439064a91a703--