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 1we6Il-004Yop-10 for pgsql-hackers@arkaria.postgresql.org; Mon, 29 Jun 2026 07:21:07 +0000 Received: from localhost ([127.0.0.1] helo=malur.postgresql.org) by malur.postgresql.org with esmtp (Exim 4.96) (envelope-from ) id 1we6Ij-001aCO-2r for pgsql-hackers@arkaria.postgresql.org; Mon, 29 Jun 2026 07:21:05 +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 1we6Ij-001aCF-1w for pgsql-hackers@lists.postgresql.org; Mon, 29 Jun 2026 07:21:05 +0000 Received: from mail-pf1-x42a.google.com ([2607:f8b0:4864:20::42a]) by magus.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 (Exim 4.98.2) (envelope-from ) id 1we6Ih-00000000kaC-0xOP for pgsql-hackers@postgresql.org; Mon, 29 Jun 2026 07:21:05 +0000 Received: by mail-pf1-x42a.google.com with SMTP id d2e1a72fcca58-84592b55832so1385416b3a.3 for ; Mon, 29 Jun 2026 00:21:02 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1782717660; x=1783322460; darn=postgresql.org; h=to:cc:date:message-id:subject:mime-version :content-transfer-encoding:from:from:to:cc:subject:date:message-id :reply-to; bh=HO+1rjZH19WAAEIfIoaeyQ6FZUfVEkbeN8+ex/40oW8=; b=ITdmu8if0TXLqQxVSjN5gd8ba8e1n7LGdfMIzdKcKNVRRCsPZheSM0oOlQs5OHDmud duAsYmPUEgQuvTkSDlUp/eATaeNTha0mDQA3ORRPd6WQYUIQi+tQEa8E0Ex5VbWDx6zi 2+9TcrcsTXjzH7X62+HCyo3iQcXi20ZmocEtvAO63hbtbvhMlY8iOzkrpj4eRjTRAADx kG2JphLMDgVMLw4Qfgn8Qi7txc/JX4+AN79O0hVArp/tXHx4IqHGbPMXvmKxRqXc7vqR XHjSVkAeVDPXxEWr+NHGJasDu7WEQUlOIU7MczuVtI5KmVTYv4cyCrqQzxyhJBmH6g1/ T1tA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1782717660; x=1783322460; h=to:cc:date:message-id:subject:mime-version :content-transfer-encoding:from:x-gm-gg:x-gm-message-state:from:to :cc:subject:date:message-id:reply-to; bh=HO+1rjZH19WAAEIfIoaeyQ6FZUfVEkbeN8+ex/40oW8=; b=Sop6PmYElQvcCYW/WaSHThsw9iPM5h/1t5vFh4fDKTyQMiDnUC5C2d1JPPNUdpOS6M V6IyywOzW6cIwqZMTA3yc5uoJ3h7+sTG9RPUxRkdFQSVW6HVYQiByjUzExNXIyV2j+b4 i6Ml37xtQpq4Pq9ItEWoAhtdDkm1fRD0ViYvuZwiQF2sCZGp+zTHtfm/KUmvUZmlfKqM 7HjapYJkVG+Hqm29JXQansdCwP3bgXPr5k35MgMb0OxwrSgm4jGG2SyZy5fU915vGFhs ombfoq/cDmCa73E9CspdzT7MlsA6fGsSUZHh9Aov4PLpIG5U7puF0D9Y35JegzqbSO8q VFdw== X-Gm-Message-State: AOJu0YzFJh+ChiYJx/f09pUHRX6Yl1VHKq7+XdgDzj4GHnt/cbRjYnbU enIQK6s5d+5+DjpeIlKbhYNBZUb9+IGrDjBMfsYigwfFecuE2PMsiopl8n0m8w== X-Gm-Gg: AfdE7clUcHtXwP4qacQL57PHn4ASAzfCF9kwPwFD3mhrA4BxXbzHZjqhrzglyLMacQ9 rc0ZhVphnTry3dM32RoZOqSIQZ7qJu/n9bj94nXlQOakeuZMrF4t0t4voRxxCtgtSbPAuLeNSSB jaDJc7zCdEArKJfSJ2lmPo2rmqHAcgnirm0e42UCSKl7dgIqpV+XvJiPY+rIcolz4j7Jun1ESR6 PiEqXL7uxoqF9DGLNGXos2mD6N+d7qWNV8OThgHHheBivSoUGrg+VqVpeNjjyVNoKMN+mP/0Nr1 PGibYAZUn7QbemRNcqNkaApaZbQ7VELMY0BS0Nr2i+Hv+20i0ZrV6RMicp64LOY6ZoxvkRgFauZ jheOrsV6IDQmb6L1asnG1METXCzrcnp7BisCLcRYRX5/Xc/yUE7sjCD1dofEiLtWqhVOkgaM7N3 qhG+rC73bbzX9WJ6LVdEww+g== X-Received: by 2002:a05:6a00:9086:b0:845:d274:bf92 with SMTP id d2e1a72fcca58-845d274e47emr8401149b3a.59.1782717659838; Mon, 29 Jun 2026 00:20:59 -0700 (PDT) Received: from smtpclient.apple ([185.135.79.125]) by smtp.gmail.com with ESMTPSA id d2e1a72fcca58-845ebe987fcsm2613421b3a.39.2026.06.29.00.20.57 (version=TLS1_2 cipher=ECDHE-ECDSA-AES128-GCM-SHA256 bits=128/128); Mon, 29 Jun 2026 00:20:59 -0700 (PDT) From: Chao Li Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: quoted-printable Mime-Version: 1.0 (Mac OS X Mail 16.0 \(3864.600.51.1.1\)) Subject: Fix GROUP BY ALL handling of ORDER BY operator semantics Message-Id: <5243308F-8E5C-45AA-828C-FAD96C4F34DA@gmail.com> Date: Mon, 29 Jun 2026 15:20:24 +0800 Cc: Tom Lane To: PostgreSQL-development X-Mailer: Apple Mail (2.3864.600.51.1.1) List-Id: List-Help: List-Subscribe: List-Post: List-Owner: List-Archive: Archived-At: Precedence: bulk Hi, While testing =E2=80=9C[ef38a4d97] Add GROUP BY ALL=E2=80=9D, I traced = the code and found a suspicious difference. In theory, GROUP BY ALL = should behave the same as spelling out all inferred grouping = expressions, but I found that they go through different code paths. After entering transformGroupClause(), if this is GROUP BY ALL, it = immediately enters a separate branch, loops over the target list, calls = addTargetToGroupList() for every TLE, and then returns. For non-ALL, it calls transformGroupClauseExpr() for every group clause. = Inside transformGroupClauseExpr(), there is logic that the ALL path = misses: ``` /* * If the GROUP BY tlist entry also appears in ORDER BY, = copy operator * info from the (first) matching ORDER BY item. This = means that if * you write something like "GROUP BY foo ORDER BY foo = USING <<<", the * GROUP BY operation silently takes on the equality = semantics implied * by the ORDER BY. There are two reasons to do this: = it improves the * odds that we can implement both GROUP BY and ORDER BY = with a single * sort step, and it allows the user to choose the = equality semantics * used by GROUP BY, should she be working with a = datatype that has * more than one equality operator. * * If we're in a grouping set, though, we force our = requested ordering * to be NULLS LAST, because if we have any hope of = using a sorted agg * for the job, we're going to be tacking on generated = NULL values * after the corresponding groups. If the user demands = nulls first, * another sort step is going to be inevitable, but = that's the * planner's problem. */ foreach(sl, sortClause) { SortGroupClause *sc =3D (SortGroupClause *) = lfirst(sl); if (sc->tleSortGroupRef =3D=3D = tle->ressortgroupref) { SortGroupClause *grpc =3D = copyObject(sc); if (!toplevel) grpc->nulls_first =3D false; *flatresult =3D lappend(*flatresult, = grpc); found =3D true; break; } } ``` Based on this finding, I built a test using record_image_ops, where = row(1.0) and row(1.00) compare as distinct under record-image equality: ``` evantest=3D# CREATE TYPE t_rec AS (x numeric); CREATE TYPE evantest=3D# evantest=3D# SELECT row(1.0)::t_rec OPERATOR(pg_catalog.*=3D) = row(1.00)::t_rec; ?column? ---------- f (1 row) ``` Here is the repro: ``` evantest=3D# create table t (a t_rec); CREATE TABLE evantest=3D# set enable_hashagg =3D 0; SET evantest=3D# insert into t values(row(1.0)::t_rec), (row(1.00)::t_rec); INSERT 0 2 evantest=3D# select a, count(a) from t group by a order by a using = operator(pg_catalog.*<); a | count --------+------- (1.00) | 1 (1.0) | 1 (2 rows) evantest=3D# select a, count(a) from t group by all order by a using = operator(pg_catalog.*<); a | count -------+------- (1.0) | 2 (1 row) ``` As we can see, "GROUP BY a" distinguishes the two rows because it uses = the equality semantics implied by ORDER BY ... USING, but "GROUP BY ALL" = groups the two rows together because it uses the default grouping = semantics instead. The fix mostly refactors the existing logic so the GROUP BY ALL path = also handles the ORDER BY sort clause. See the attached patch for = details. Best regards, -- Chao Li (Evan) HighGo Software Co., Ltd. https://www.highgo.com/