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 1x1aOt-005Ikg-2U for pgsql-hackers@arkaria.postgresql.org; Wed, 02 Sep 2026 02:08:32 +0000 Received: from localhost ([127.0.0.1] helo=malur.postgresql.org) by malur.postgresql.org with esmtp (Exim 4.96) (envelope-from ) id 1x1aOs-009eKT-1b for pgsql-hackers@arkaria.postgresql.org; Wed, 02 Sep 2026 02:08:30 +0000 Received: from makus.postgresql.org ([2001:4800:3e1:1::229]) by malur.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.96) (envelope-from ) id 1x1aOr-009eKK-2Q for pgsql-hackers@lists.postgresql.org; Wed, 02 Sep 2026 02:08:30 +0000 Received: from mail-australiasoutheastazolkn190100000.outbound.protection.outlook.com ([2a01:111:f403:d40e::] helo=MEUPR01CU001.outbound.protection.outlook.com) by makus.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.98.2) (envelope-from ) id 1x1aOn-00000003Yu1-1PHL for pgsql-hackers@postgresql.org; Wed, 02 Sep 2026 02:08:28 +0000 ARC-Seal: i=1; a=rsa-sha256; s=arcselector10001; d=microsoft.com; cv=none; b=MYZAtyyBFDWp+ah834o5aCto7rs7jcM2F2glgJLxBxwFP2HLiTItll/xKzp6uMJthsveG2Tu4M/WCiCmJCE2u6ASYfJ7Xkl4yLbgNz11XHuERi+l3ypc+yz9W5zjeJPpVAKnZsjXsxRXLVAwxGydu+1wZY2dKdDiKgxvxmwpnOpOwLShvVFVLAeOyBOn346b2WL1//qCxccsNOt42mKGRhgTLKZKAaKkOmI3j3Cnve0X3ol7nG+FT+p52uVIElW7M3vOPqcgU3UY2wrdbfunIUF3bdRZ3o0IEHt8iCYRquC4fir2xUB3WhnM8NN7ysRsmaDCvRNcVCC9h6iUwSMhbA== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=microsoft.com; s=arcselector10001; h=From:Date:Subject:Message-ID:Content-Type:MIME-Version:X-MS-Exchange-AntiSpam-MessageData-ChunkCount:X-MS-Exchange-AntiSpam-MessageData-0:X-MS-Exchange-AntiSpam-MessageData-1; bh=zoidDpgX5/SdTxbSWTOVSkZxCtVVsMgwSGnlyVr33gw=; b=pWejI3Sm17gHD3o719f3p9vgG5J+d1tsPFeHeS8A1Hf5AmUI+at+uArLypIKZBP4noxoBXmtQvMrBs1e4sthikfD56ulM2mnYDbxaTDUjZfbaVVWl8X/SrQTdnoMmHQWE/cPMNXOk1/cH9sUyjglzsUUqMeBY+BTMnkDrcd4oxXzMwwbqsfHekhrQq6zr+l1UKonjqhZx3K+40ldcACd9vyBP6Jxo/L1JY1Qa/fl449VVvyTNyCOBZTAenepCg1ja5umelqx4qRIf5KzTmBnzjCmugtk7bZ1dk4RaShQPHvovCZZEvrRKgpQhtfbIgWGs+A1nEGmXT7xeApco/O/6A== ARC-Authentication-Results: i=1; mx.microsoft.com 1; spf=none; dmarc=none; dkim=none; arc=none DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=hotmail.com; s=selector1; h=From:Date:Subject:Message-ID:Content-Type:MIME-Version:X-MS-Exchange-SenderADCheck; bh=zoidDpgX5/SdTxbSWTOVSkZxCtVVsMgwSGnlyVr33gw=; b=jRtXzMfWyAH8Ie+KNGKbYpGPzZt+aYYtDIx9s+iiCxuwT82RxoQrLHoar/o4NwSg4oqNHMOZfn8C66IJXLSWQIqhkZkdRXBf4ebHGXEEsSADsajSXwaKkpbdLxV7fOung1qS0dgXpNrquXcXeM1mdmZ46SKa9Zo8mcC27j74DtdGbqaRqujL10wwhHDFToY+rCvWFC7zJpM42YRwpFSTVUojgh1lxjJmd4DW8QqGLaTia2FlvH+nTuLwhsWxTvKFsDNy//WV27k6IjGB4j2E2z8/hIBg91ol8x/XLCqxMa1d4V3xD5mmy1MBaAo8V1d5lLpxbi0fVj/qdqNt6kCfQw== Received: from SY7PR01MB10921.ausprd01.prod.outlook.com (2603:10c6:10:334::16) by SY0PR01MB9275.ausprd01.prod.outlook.com (2603:10c6:10:224::15) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.21.382.10; Wed, 2 Sep 2026 02:08:15 +0000 Received: from SY7PR01MB10921.ausprd01.prod.outlook.com ([fe80::7908:e00:4ab1:d120]) by SY7PR01MB10921.ausprd01.prod.outlook.com ([fe80::7908:e00:4ab1:d120%6]) with mapi id 15.21.0382.007; Wed, 2 Sep 2026 02:08:15 +0000 From: Japin Li To: Andrey Borodin Cc: Kirill Reshke , Roman Khapov , Chao Li , "Peter Eisentraut" , pgsql-hackers Subject: Re: GIN pageinspect support for entry tree and posting tree In-Reply-To: (Japin Li's message of "Wed, 02 Sep 2026 00:10:23 +0800") References: <9B839464-8FE5-4569-B07E-24F4B54FB7BF@gmail.com> <0A0E3993-7617-48EA-8555-6C1907C147E5@yandex-team.ru> <9CFF5492-B542-4BE7-9093-E32EBC7DDB1C@yandex-team.ru> <779DFF42-61E0-4295-94C0-53B347F45A17@yandex-team.ru> User-Agent: mu4e 1.14.1; emacs 30.2 Date: Wed, 02 Sep 2026 10:08:08 +0800 Message-ID: Content-Type: multipart/mixed; boundary="=-=-=" X-ClientProxiedBy: TPYP295CA0031.TWNP295.PROD.OUTLOOK.COM (2603:1096:7d0:7::9) To SY7PR01MB10921.ausprd01.prod.outlook.com (2603:10c6:10:334::16) X-Microsoft-Original-Message-ID: <87pkywo16f.fsf@hotmail.com> MIME-Version: 1.0 X-MS-Exchange-MessageSentRepresentingType: 1 X-MS-PublicTrafficType: Email X-MS-TrafficTypeDiagnostic: SY7PR01MB10921:EE_|SY0PR01MB9275:EE_ X-MS-Office365-Filtering-Correlation-Id: d761b0d5-54b2-449d-0467-08df08970c63 X-MS-Exchange-SLBlob-MailProps: EgT5Wr3QDKyfJ5ctroE+AnoJKQNH7uyZrfBlqTLMaKZFUIo3uIOZ88wrh+Z0f0XeD8WxtYE2+ODvv3aVszIpDTwdTfJ4C/mw1uClpYLfdy28zxDLoqhNWk/s2pGkPOcanuKX9VlGLvv99CoWUx/oF4nWBIUmNhqOr+MGpBxPIcPtaNDHMo+qGvFxxSroNLsDjDyl5ijQAUjGk/ewjRMcmRzjwS45iZ2BvgVGNF/mMjmLXRkAGgwtmic54TDzccbSplQLrhPY3Vi9uhTdk+5CG63DUTo3yiqbaA70Bu9xY4OgNO7XjM2xrblKqJn+mp7tKUwDk4deRB6E8KY4JyzZcKzZFhACYcEXAZVDaNRMnH88+mJHioct3My0T3tKgDx+PqaghzPCGmccrnDNi8qKIHwPPNNxJ2A+tEFNsmIhgTSS06Q2MtDGvmmYI+ySujfbXyaL+3N6M8/D/S3QtCRMimD86ZV4kINR9DNzNN8EtV18iqQbekcertaKSHjFSQ0CBk9KQAzJ8UGBLg1pVl4k4SwKZSvldAWxuyByGzvOO7GDJvpFzySigocjVqThikTvbEgvBrn9Wvxu9jqXMs339keZ9vMyeLEChK9SIkjUiC/C7ZtjO6KsTS9X5TYDYYKW6kt4WP9wTmjwFC3tqemwN1wzDP8aHntnOVKC/RqSoviQ7eqvcJoqO4VhFxkY/TFouE3wfYBIH1oRvaKGQ2hod/bOxxx6kQLKWREWRVAD4/8= X-Microsoft-Antispam: BCL:0;ARA:14566002|24021099003|24121999003|22091999003|19110799012|8060799015|23021999003|12121999013|6092099016|15080799012|5072599009|4140399003|25010399006|52005399003|40105399003|26121999007|2607281247196008|1602099012|440099028|3412199025|20055399003|4302099013; X-Microsoft-Antispam-Message-Info: =?us-ascii?Q?Ox99COqX5KtNyTxGv2YkGsXuOfPSbCyxgI8ef9eWzMBHfjEjmewbo6eXpPGc?= =?us-ascii?Q?Aikj/Y71kzz2MUklLIWGxNINS5r1nSfrSfVZsHhFb1d+XLsjAaYjuOuyKELy?= =?us-ascii?Q?QOGl5x5xZcighRUDpkbASEFvVMR4seTZqzsbICTgOjSCl64ZhWUkNDnV5lyc?= =?us-ascii?Q?tzuEfICGbpkk1T520sr561bgRsi3/o8p+9K2Bmfl1f6xcYPISiWdXscGyPLX?= =?us-ascii?Q?2yuFlurLfPNccAcLQgCAIieTO+0LrQNGN7jw7T1FnfhvyliOQ5G13seWXpju?= =?us-ascii?Q?5pyFALgN7OaLNSM4llNbfjYfrbFxJSycgaOaqaIGlnIv5vZDBDgA9FvRk+cL?= =?us-ascii?Q?KN3LlZprlczjKZo9tk0SqRQaN61cDdiYmgqYBgzA4DXFoc4bCDgcTKVX8vI1?= =?us-ascii?Q?H5Iu9z5tdcAtdwZM2jV1upCQ/QBxprRWxLYitkMQpQWz+LQloLgRMypl6n56?= =?us-ascii?Q?P223v/02Rd5i3JuGCozH5COECaG6pKF6B5LdYlzxZMS1qM9tzrNu1OykScP4?= =?us-ascii?Q?TV475pQ1047yifHjgdSO9B8jPRM7Ty5IAC24XgLtv3vK9K4R6ABSh9VUV6iJ?= =?us-ascii?Q?sovOOyIdYaJAFwjKGOaSOr+KOZjXDcSoZWxiJ6iOolmkZXPk5QYnNEfqgb0h?= =?us-ascii?Q?sFn9XCaYsXMxIeEd/++WiEGI1bCe1pRoeN5EJn8vTm+yszAXQcMFZCQC9LnL?= =?us-ascii?Q?tgE5s9SH7GRKsCCg3x03PheDsaOM8etz8mKW83ob/2OmJgRwA2FIE75j0ZvG?= =?us-ascii?Q?kNkokIZiKZ3odr2q7ETDwMBUxQgTUZteJLXfI696G07p+98eDa5XlOae/kVC?= =?us-ascii?Q?IlGF2hH02xZm4hHEGfDgloXGu2aH0JyoFGX1h6HC/21OQK9UGxpvsWPttR6a?= =?us-ascii?Q?7eqOvz2d4hVzTxGXbamVhgDwXB7V1s4W7eV9kU8c1elsMRWPnFX6JhdHJI6E?= =?us-ascii?Q?t1Q2mbRcI7XJ4Ie9mgvWtAf8H4I5QbUkXdPY7XLM+onOoc4DVfdfsZOiyJ2z?= =?us-ascii?Q?vZCu6lmiAbUp3iqBalt6Ip6QEwYvxkfGQU/7HEA10tJcHHfmXg3PedeCrPAg?= =?us-ascii?Q?aE7GYPIVkAdbduoOsUGb5N3J6Ew6tKO5e4NN2G9RAGeGAHd2fH51hIjQCzUa?= =?us-ascii?Q?d+Ygk+mTs38+?= X-MS-Exchange-AntiSpam-MessageData-ChunkCount: 1 X-MS-Exchange-AntiSpam-MessageData-0: =?us-ascii?Q?VcIWyjxe5pfcCvUTyT88u8PwcnvchY53f9AKY4nLHzZOxNljuDG43h/Rb5qb?= =?us-ascii?Q?QkCK+jZsGMhMElmY5JbA8hO0YkL+RqKB11Xv+lmiVfeVf6hVbOIFvXDy7lP0?= =?us-ascii?Q?LbPFyvr6ViOxYlxBXQWiqESpgM19/K0Ss8+0WfYHmGG0ol0T7c4yslHYArPm?= =?us-ascii?Q?EImlADy3NarG9gOmiWlBRP7CfcOQTnLbXp0vO6K68Ucz4FfYk3woAEyarAak?= =?us-ascii?Q?dI1QL1j8my5crIX8WWTwJzp3gC3z7UoUYeaAIAO9wlSPlB7eNMFTBIjL6EYo?= =?us-ascii?Q?5V7jjvitxQ0hdTl9Us25eMnK8slw6r2L94sdLhF/JqbUiQoGxCDQ46La1tvq?= =?us-ascii?Q?DN3JDCnUzZ8eYNevtbN0qvd6x2hzQYh5kF4YOR+dYCbBm7hC9eg+CRJxo2k3?= =?us-ascii?Q?XDql4pJ0U/p51kmIf8QSrpN7fVhPcqFsLieG3ivRUhuqr3JeD34dShCZ3jIR?= =?us-ascii?Q?EA1u32WrMjOC7C0nO6/sR9OG+RTf8bAmbqvNqOxjoktNJltpvg0pM7AIHRfp?= =?us-ascii?Q?7wkmRxG+IRBPG7nybhlVzuT7imh50cblPSeB2SkfgJB/C/fAVFNx014zYVS/?= =?us-ascii?Q?X7PB15fCvjxyfB8lWNTTt6mrUNYghwqqO0NtaqDl6d7DKhgra1UyVAChYZBk?= =?us-ascii?Q?gxkxZuXDPrxDSj5RTpBFdojfS9vMQT85yRTPNqhy2w1LoFkr4oQYhIk3DEbz?= =?us-ascii?Q?8bJWhPqxMcobxKoB4eF5Ut21j+W+mCoULNIQYTNUdYdQH6OhyU0E37tPi9Mq?= =?us-ascii?Q?cvnlQHg/n7h8NsPGYdxkrXP/WaL8ih3jIlIf4YD7ZoHgq8S8jSh2tAM40YfN?= =?us-ascii?Q?YJYEdKerpO4WWIsovLqN361Tkdk+4gC0l4eINy07baMCjBQYCdMBxkGhgc1q?= =?us-ascii?Q?Wb218W24Ygp4HBlMM9U1samdL0ACphBupasVce6U45sd8JUuXICG/qtZThU4?= =?us-ascii?Q?XNWnX99xLhxTzuQ0z1XUdrkzuuKrIwmkit9ZFUaO5zqXpqB80DGC3MPi70w+?= =?us-ascii?Q?ijTd1h6vLULfYWYYvFYAXR9lr2FfiVPtDYMqNN305YtHrjtjHxtjVrm9nziP?= =?us-ascii?Q?g8sqc7upErh37nvab7244EqSbSdlOVtTr+DyO/ek5MbCfTCynzihlpzuv6Nh?= =?us-ascii?Q?QV20rcTm74vJyDYieRoN0ELTNFR9upBfe5sF807lmydWXUN45LbGke8DsjCy?= =?us-ascii?Q?aZ7t8uEAGBnARn7ZpTadEpMSF6YOHO9gVX8pzvv3YMzwuCv6UPTmRp1A628j?= =?us-ascii?Q?TSAJzt6rsdb4D02Gcg82GWeVrwOJtdXNq7SBDAYZfXjsnTGtyNCSlBm8wm/F?= =?us-ascii?Q?zpq3O+cyVojz2/RfDVVoXkJEqbxe+1gTotPsNimm04TKbnlTr9uZtZx63+41?= =?us-ascii?Q?FlXulcZP2/tPxIN8l8dGF2LNptlJ?= X-OriginatorOrg: sct-15-20-9412-4-msonline-outlook-feddd.templateTenant X-MS-Exchange-CrossTenant-Network-Message-Id: d761b0d5-54b2-449d-0467-08df08970c63 X-MS-Exchange-CrossTenant-AuthSource: SY7PR01MB10921.ausprd01.prod.outlook.com X-MS-Exchange-CrossTenant-AuthAs: Internal X-MS-Exchange-CrossTenant-OriginalArrivalTime: 02 Sep 2026 02:08:15.5133 (UTC) X-MS-Exchange-CrossTenant-FromEntityHeader: Hosted X-MS-Exchange-CrossTenant-Id: 84df9e7f-e9f6-40af-b435-aaaaaaaaaaaa X-MS-Exchange-CrossTenant-RMS-PersistedConsumerOrg: 00000000-0000-0000-0000-000000000000 X-MS-Exchange-Transport-CrossTenantHeadersStamped: SY0PR01MB9275 List-Id: List-Help: List-Subscribe: List-Post: List-Owner: List-Archive: Archived-At: Precedence: bulk --=-=-= Content-Type: text/plain On Wed, 02 Sep 2026 at 00:10, Japin Li wrote: > Hi, Andrey and Kirill > > On Sun, 30 Aug 2026 at 22:46, Andrey Borodin wrote: >>> On 3 Aug 2026, at 17:40, Kirill Reshke wrote: >>> >>> Hi all, >>> >>> I have rebased patches and did little polishing for error >>> messages/overall style. Posting to see how new CI reacts & also bump >>> thread. >>> >> Hi Kirill, >> >> The CF entry is still Ready for Committer, but Japin's August 13 review >> found that gin_entrypage_items() does not finalize its temporary tuple >> descriptor before index_getattr(). With assertions enabled, the new >> check added by c456e391138 can fail here. >> >> Could you fold the proposed TupleDescFinalize() call into a new patch >> version? I have moved the entry to Waiting on Author for now. >> > > The v20260308 patches need a rebase due to commit 51c43a5dbd86. I've rebased > and merged the fix from [1]. Please take a look. > > [1] https://www.postgresql.org/message-id/SY7PR01MB109210C8E9DEB6BF5FFEA7A81B6DB2%40SY7PR01MB10921.ausprd01.prod.outlook.com > Sorry, I forgot to update the patches. -- Regards, Japin Li ChengDu WenWu Information Technology Co., Ltd. --=-=-= Content-Type: text/x-patch Content-Disposition: attachment; filename=v20260901-0001-Modernize-coding-in-GIN-pageinspect-functi.patch From b44852db01077d498cf7595480dd7aa8ed45eb10 Mon Sep 17 00:00:00 2001 From: reshke Date: Wed, 7 Jan 2026 19:38:52 +0000 Subject: [PATCH v20260901 1/3] Modernize coding in GIN pageinspect functions. This patch switches palloc to our newly preferred palloc_array and modernizes ereport calls, switching from list-style to polymorphic ereport calls. This patch also fixes whitespace/tab issues, enforcing a single style. across existing ginfuncs.c code. Inspired by Peter Eisentraut's patch in the thread. Reviewed-by: Andrey Borodin Reviewed-by: Chao Li Reviewed-by: Japin Li Discussion: https://postgr.es/m/CALdSSPiN13n7feQcY0WCmq8jzxjwqhNrt1E=g=g6aZANyE_OoQ@mail.gmail.com --- contrib/pageinspect/ginfuncs.c | 60 +++++++++++++++++----------------- 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/contrib/pageinspect/ginfuncs.c b/contrib/pageinspect/ginfuncs.c index 058ad52b671..3fe1412fe26 100644 --- a/contrib/pageinspect/ginfuncs.c +++ b/contrib/pageinspect/ginfuncs.c @@ -38,8 +38,8 @@ gin_metapage_info(PG_FUNCTION_ARGS) if (!superuser()) ereport(ERROR, - (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), - errmsg("must be superuser to use raw page functions"))); + errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("must be superuser to use raw page functions")); page = get_page_from_raw(raw_page); @@ -48,20 +48,20 @@ gin_metapage_info(PG_FUNCTION_ARGS) if (PageGetSpecialSize(page) != MAXALIGN(sizeof(GinPageOpaqueData))) ereport(ERROR, - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("input page is not a valid GIN metapage"), - errdetail("Expected special size %d, got %d.", - (int) MAXALIGN(sizeof(GinPageOpaqueData)), - (int) PageGetSpecialSize(page)))); + errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("input page is not a valid GIN metapage"), + errdetail("Expected special size %d, got %d.", + (int) MAXALIGN(sizeof(GinPageOpaqueData)), + (int) PageGetSpecialSize(page))); opaq = GinPageGetOpaque(page); if (opaq->flags != GIN_META) ereport(ERROR, - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("input page is not a GIN metapage"), - errdetail("Flags %04X, expected %04X", - opaq->flags, GIN_META))); + errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("input page is not a GIN metapage"), + errdetail("Flags %04X, expected %04X", + opaq->flags, GIN_META)); /* Build a tuple descriptor for our result type */ if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE) @@ -108,8 +108,8 @@ gin_page_opaque_info(PG_FUNCTION_ARGS) if (!superuser()) ereport(ERROR, - (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), - errmsg("must be superuser to use raw page functions"))); + errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("must be superuser to use raw page functions")); page = get_page_from_raw(raw_page); @@ -118,11 +118,11 @@ gin_page_opaque_info(PG_FUNCTION_ARGS) if (PageGetSpecialSize(page) != MAXALIGN(sizeof(GinPageOpaqueData))) ereport(ERROR, - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("input page is not a valid GIN data leaf page"), - errdetail("Expected special size %d, got %d.", - (int) MAXALIGN(sizeof(GinPageOpaqueData)), - (int) PageGetSpecialSize(page)))); + errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("input page is not a valid GIN data leaf page"), + errdetail("Expected special size %d, got %d.", + (int) MAXALIGN(sizeof(GinPageOpaqueData)), + (int) PageGetSpecialSize(page))); opaq = GinPageGetOpaque(page); @@ -184,8 +184,8 @@ gin_leafpage_items(PG_FUNCTION_ARGS) if (!superuser()) ereport(ERROR, - (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), - errmsg("must be superuser to use raw page functions"))); + errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("must be superuser to use raw page functions")); if (SRF_IS_FIRSTCALL()) { @@ -207,20 +207,20 @@ gin_leafpage_items(PG_FUNCTION_ARGS) if (PageGetSpecialSize(page) != MAXALIGN(sizeof(GinPageOpaqueData))) ereport(ERROR, - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("input page is not a valid GIN data leaf page"), - errdetail("Expected special size %d, got %d.", - (int) MAXALIGN(sizeof(GinPageOpaqueData)), - (int) PageGetSpecialSize(page)))); + errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("input page is not a valid GIN data leaf page"), + errdetail("Expected special size %d, got %d.", + (int) MAXALIGN(sizeof(GinPageOpaqueData)), + (int) PageGetSpecialSize(page))); opaq = GinPageGetOpaque(page); if (opaq->flags != (GIN_DATA | GIN_LEAF | GIN_COMPRESSED)) ereport(ERROR, - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("input page is not a compressed GIN data leaf page"), - errdetail("Flags %04X, expected %04X", - opaq->flags, - (GIN_DATA | GIN_LEAF | GIN_COMPRESSED)))); + errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("input page is not a compressed GIN data leaf page"), + errdetail("Flags %04X, expected %04X", + opaq->flags, + (GIN_DATA | GIN_LEAF | GIN_COMPRESSED))); inter_call_data = palloc_object(gin_leafpage_items_state); -- 2.53.0 --=-=-= Content-Type: text/x-patch Content-Disposition: attachment; filename=v20260901-0002-GIN-pageinspect-support-for-entry-tree-and.patch From e543bf25ca1ad1e1705cab2e506e32c9519fa136 Mon Sep 17 00:00:00 2001 From: reshke Date: Mon, 13 Oct 2025 20:14:26 +0000 Subject: [PATCH v20260901 2/3] GIN pageinspect support for entry tree and posting tree internal pages This patch provides a new version for the pageinspect contrib module, including two new functions: * gin_entrypage_items. * gin_datapage_items. These two functions can be used to examine the GIN entry tree and posting tree pages. Namely, gin_entrypage_items can be used for both leaf and non-leaf entry tree pages. gin_datapage_items is provided in pairs with already-existing gin_leafpage_items to examine the non-leaf posting tree pages. We keep the different functions here mainly because of different GIN. pages layoff. Note that fast-list pages are out of scope of this patch. Co-authored-by: Peter Eisentraut Reviewed-by: Andrey Borodin Reviewed-by: Roman Khapov Reviewed-by: Chao Li Reviewed-by: Japin Li Discussion: https://postgr.es/m/CALdSSPiN13n7feQcY0WCmq8jzxjwqhNrt1E=g=g6aZANyE_OoQ@mail.gmail.com --- contrib/pageinspect/Makefile | 2 +- contrib/pageinspect/expected/gin.out | 117 +++++- contrib/pageinspect/expected/gin_1.out | 182 +++++++++ contrib/pageinspect/ginfuncs.c | 346 ++++++++++++++++++ contrib/pageinspect/meson.build | 1 + .../pageinspect/pageinspect--1.13--1.14.sql | 27 ++ contrib/pageinspect/pageinspect.control | 2 +- contrib/pageinspect/sql/gin.sql | 35 +- doc/src/sgml/pageinspect.sgml | 54 +++ 9 files changed, 759 insertions(+), 7 deletions(-) create mode 100644 contrib/pageinspect/expected/gin_1.out create mode 100644 contrib/pageinspect/pageinspect--1.13--1.14.sql diff --git a/contrib/pageinspect/Makefile b/contrib/pageinspect/Makefile index eae989569d0..09774fd340c 100644 --- a/contrib/pageinspect/Makefile +++ b/contrib/pageinspect/Makefile @@ -13,7 +13,7 @@ OBJS = \ rawpage.o EXTENSION = pageinspect -DATA = pageinspect--1.12--1.13.sql \ +DATA = pageinspect--1.13--1.14.sql pageinspect--1.12--1.13.sql \ pageinspect--1.11--1.12.sql pageinspect--1.10--1.11.sql \ pageinspect--1.9--1.10.sql pageinspect--1.8--1.9.sql \ pageinspect--1.7--1.8.sql pageinspect--1.6--1.7.sql \ diff --git a/contrib/pageinspect/expected/gin.out b/contrib/pageinspect/expected/gin.out index ff1da6a5a17..5a0874d1441 100644 --- a/contrib/pageinspect/expected/gin.out +++ b/contrib/pageinspect/expected/gin.out @@ -1,6 +1,9 @@ -CREATE TABLE test1 (x int, y int[]); -INSERT INTO test1 VALUES (1, ARRAY[11, 111]); +CREATE TABLE test1 (x int, y int[], z text[]); +INSERT INTO test1 VALUES (1, ARRAY[11, 111], ARRAY['a', 'b', 'c']); +INSERT INTO test1 VALUES (2, ARRAY[NULL, 222], ARRAY['d', NULL]); CREATE INDEX test1_y_idx ON test1 USING gin (y) WITH (fastupdate = off); +CREATE INDEX test2_y_z_idx ON test1 USING gin (y, z) WITH (fastupdate = off); +CREATE INDEX test3_y_z_idx ON test1 USING gin (y, z) WITH (fastupdate = on); \x SELECT * FROM gin_metapage_info(get_raw_page('test1_y_idx', 0)); -[ RECORD 1 ]----+----------- @@ -12,7 +15,7 @@ n_pending_tuples | 0 n_total_pages | 2 n_entry_pages | 1 n_data_pages | 0 -n_entries | 2 +n_entries | 4 version | 2 SELECT * FROM gin_metapage_info(get_raw_page('test1_y_idx', 1)); @@ -27,6 +30,75 @@ flags | {leaf} SELECT * FROM gin_leafpage_items(get_raw_page('test1_y_idx', 1)); ERROR: input page is not a compressed GIN data leaf page DETAIL: Flags 0002, expected 0083 +SELECT * FROM gin_entrypage_items(get_raw_page('test1_y_idx', 1), 'test1_y_idx'::regclass); +-[ RECORD 1 ]-------------- +itemoffset | 1 +downlink | (2147483664,1) +tids | {"(0,1)"} +keys | y=11 +-[ RECORD 2 ]-------------- +itemoffset | 2 +downlink | (2147483664,1) +tids | {"(0,1)"} +keys | y=111 +-[ RECORD 3 ]-------------- +itemoffset | 3 +downlink | (2147483664,1) +tids | {"(0,2)"} +keys | y=222 +-[ RECORD 4 ]-------------- +itemoffset | 4 +downlink | (2147483666,1) +tids | {"(0,2)"} +keys | y=NULL + +SELECT * FROM gin_entrypage_items(get_raw_page('test2_y_z_idx', 1), 'test2_y_z_idx'::regclass); +-[ RECORD 1 ]-------------- +itemoffset | 1 +downlink | (2147483664,1) +tids | {"(0,1)"} +keys | y=11 +-[ RECORD 2 ]-------------- +itemoffset | 2 +downlink | (2147483664,1) +tids | {"(0,1)"} +keys | y=111 +-[ RECORD 3 ]-------------- +itemoffset | 3 +downlink | (2147483664,1) +tids | {"(0,2)"} +keys | y=222 +-[ RECORD 4 ]-------------- +itemoffset | 4 +downlink | (2147483672,1) +tids | {"(0,2)"} +keys | y=NULL +-[ RECORD 5 ]-------------- +itemoffset | 5 +downlink | (2147483664,1) +tids | {"(0,1)"} +keys | z=a +-[ RECORD 6 ]-------------- +itemoffset | 6 +downlink | (2147483664,1) +tids | {"(0,1)"} +keys | z=b +-[ RECORD 7 ]-------------- +itemoffset | 7 +downlink | (2147483664,1) +tids | {"(0,1)"} +keys | z=c +-[ RECORD 8 ]-------------- +itemoffset | 8 +downlink | (2147483664,1) +tids | {"(0,2)"} +keys | z=d +-[ RECORD 9 ]-------------- +itemoffset | 9 +downlink | (2147483672,1) +tids | {"(0,2)"} +keys | z=NULL + INSERT INTO test1 SELECT x, ARRAY[1,10] FROM generate_series(2,10000) x; SELECT COUNT(*) > 0 FROM gin_leafpage_items(get_raw_page('test1_y_idx', @@ -35,6 +107,23 @@ FROM gin_leafpage_items(get_raw_page('test1_y_idx', -[ RECORD 1 ] ?column? | t +-- Now test posting tree non-leaf page. +-- This requires inserting many tuples on a single leaf page to trigger page split. +CREATE TABLE test_data_page(i INT[]); +CREATE INDEX test_data_page_i_idx ON test_data_page USING gin(i) WITH (fastupdate = off); +INSERT INTO test_data_page SELECT ARRAY[1] FROM generate_series(1, 10000); +-- For this index, block 0 is metapage, block 1 is entry tree, block 2 is +-- posting tree non-leaf page and block 3 & 4 are compressed data leaf pages. +SELECT * FROM gin_datapage_items(get_raw_page('test_data_page_i_idx', 2)); +-[ RECORD 1 ]------- +itemoffset | 1 +downlink | 4 +item_tid | (44,83) +-[ RECORD 2 ]------- +itemoffset | 2 +downlink | 3 +item_tid | (0,0) + -- Failure with various modes. -- Suppress the DETAIL message, to allow the tests to work across various -- page sizes and architectures. @@ -54,12 +143,34 @@ ERROR: input page is not a valid GIN data leaf page SELECT * FROM gin_leafpage_items(get_raw_page('test1', 0)); ERROR: input page is not a valid GIN data leaf page \set VERBOSITY default +-- Reject unsupported page types in gin_entrypage_items. +SELECT * FROM gin_entrypage_items(get_raw_page('test2_y_z_idx', 0), 'test2_y_z_idx'::regclass); +ERROR: gin_entrypage_items does not support metapages +-- Check the error message for the internal posting tree page. +SELECT * FROM gin_entrypage_items(get_raw_page('test_data_page_i_idx', 2), 'test_data_page_i_idx'::regclass); +ERROR: gin_entrypage_items does not support posting tree pages +HINT: This appears to be a GIN posting tree page. Please use gin_datapage_items. +-- insert new row to trigger new (fast-list) page allocation. +INSERT INTO test1 VALUES (1, ARRAY[11, 111], ARRAY['a', 'b', 'c']); +-- double check that the new page is fast-list. +SELECT * FROM gin_page_opaque_info(get_raw_page('test3_y_z_idx', 2)); +-[ RECORD 1 ]------------------ +rightlink | 3 +maxoff | 120 +flags | {list,list_fullrow} + +-- reject fast-list pages. +SELECT * FROM gin_entrypage_items(get_raw_page('test3_y_z_idx', 3), 'test3_y_z_idx'::regclass); +ERROR: gin_entrypage_items does not support fast list pages -- Tests with all-zero pages. SHOW block_size \gset SELECT gin_leafpage_items(decode(repeat('00', :block_size), 'hex')); -[ RECORD 1 ]------+- gin_leafpage_items | +SELECT gin_datapage_items(decode(repeat('00', :block_size), 'hex')); +(0 rows) + SELECT gin_metapage_info(decode(repeat('00', :block_size), 'hex')); -[ RECORD 1 ]-----+- gin_metapage_info | diff --git a/contrib/pageinspect/expected/gin_1.out b/contrib/pageinspect/expected/gin_1.out new file mode 100644 index 00000000000..029324710fd --- /dev/null +++ b/contrib/pageinspect/expected/gin_1.out @@ -0,0 +1,182 @@ +CREATE TABLE test1 (x int, y int[], z text[]); +INSERT INTO test1 VALUES (1, ARRAY[11, 111], ARRAY['a', 'b', 'c']); +INSERT INTO test1 VALUES (2, ARRAY[NULL, 222], ARRAY['d', NULL]); +CREATE INDEX test1_y_idx ON test1 USING gin (y) WITH (fastupdate = off); +CREATE INDEX test2_y_z_idx ON test1 USING gin (y, z) WITH (fastupdate = off); +CREATE INDEX test3_y_z_idx ON test1 USING gin (y, z) WITH (fastupdate = on); +\x +SELECT * FROM gin_metapage_info(get_raw_page('test1_y_idx', 0)); +-[ RECORD 1 ]----+----------- +pending_head | 4294967295 +pending_tail | 4294967295 +tail_free_size | 0 +n_pending_pages | 0 +n_pending_tuples | 0 +n_total_pages | 2 +n_entry_pages | 1 +n_data_pages | 0 +n_entries | 4 +version | 2 + +SELECT * FROM gin_metapage_info(get_raw_page('test1_y_idx', 1)); +ERROR: input page is not a GIN metapage +DETAIL: Flags 0002, expected 0008 +SELECT * FROM gin_page_opaque_info(get_raw_page('test1_y_idx', 1)); +-[ RECORD 1 ]--------- +rightlink | 4294967295 +maxoff | 0 +flags | {leaf} + +SELECT * FROM gin_leafpage_items(get_raw_page('test1_y_idx', 1)); +ERROR: input page is not a compressed GIN data leaf page +DETAIL: Flags 0002, expected 0083 +SELECT * FROM gin_entrypage_items(get_raw_page('test1_y_idx', 1), 'test1_y_idx'::regclass); +-[ RECORD 1 ]-------------- +itemoffset | 1 +downlink | (2147483660,1) +tids | {"(0,1)"} +keys | y=11 +-[ RECORD 2 ]-------------- +itemoffset | 2 +downlink | (2147483660,1) +tids | {"(0,1)"} +keys | y=111 +-[ RECORD 3 ]-------------- +itemoffset | 3 +downlink | (2147483660,1) +tids | {"(0,2)"} +keys | y=222 +-[ RECORD 4 ]-------------- +itemoffset | 4 +downlink | (2147483662,1) +tids | {"(0,2)"} +keys | y=NULL + +SELECT * FROM gin_entrypage_items(get_raw_page('test2_y_z_idx', 1), 'test2_y_z_idx'::regclass); +-[ RECORD 1 ]-------------- +itemoffset | 1 +downlink | (2147483664,1) +tids | {"(0,1)"} +keys | y=11 +-[ RECORD 2 ]-------------- +itemoffset | 2 +downlink | (2147483664,1) +tids | {"(0,1)"} +keys | y=111 +-[ RECORD 3 ]-------------- +itemoffset | 3 +downlink | (2147483664,1) +tids | {"(0,2)"} +keys | y=222 +-[ RECORD 4 ]-------------- +itemoffset | 4 +downlink | (2147483664,1) +tids | {"(0,2)"} +keys | y=NULL +-[ RECORD 5 ]-------------- +itemoffset | 5 +downlink | (2147483660,1) +tids | {"(0,1)"} +keys | z=a +-[ RECORD 6 ]-------------- +itemoffset | 6 +downlink | (2147483660,1) +tids | {"(0,1)"} +keys | z=b +-[ RECORD 7 ]-------------- +itemoffset | 7 +downlink | (2147483660,1) +tids | {"(0,1)"} +keys | z=c +-[ RECORD 8 ]-------------- +itemoffset | 8 +downlink | (2147483660,1) +tids | {"(0,2)"} +keys | z=d +-[ RECORD 9 ]-------------- +itemoffset | 9 +downlink | (2147483664,1) +tids | {"(0,2)"} +keys | z=NULL + +INSERT INTO test1 SELECT x, ARRAY[1,10] FROM generate_series(2,10000) x; +SELECT COUNT(*) > 0 +FROM gin_leafpage_items(get_raw_page('test1_y_idx', + (pg_relation_size('test1_y_idx') / + current_setting('block_size')::bigint)::int - 1)); +-[ RECORD 1 ] +?column? | t + +-- Now test posting tree non-leaf page. +-- This requires inserting many tuples on a single leaf page to trigger page split. +CREATE TABLE test_data_page(i INT[]); +CREATE INDEX test_data_page_i_idx ON test_data_page USING gin(i) WITH (fastupdate = off); +INSERT INTO test_data_page SELECT ARRAY[1] FROM generate_series(1, 10000); +-- For this index, block 0 is metapage, block 1 is entry tree, block 2 is +-- posting tree non-leaf page and block 3 & 4 are compressed data leaf pages. +SELECT * FROM gin_datapage_items(get_raw_page('test_data_page_i_idx', 2)); +-[ RECORD 1 ]-------- +itemoffset | 1 +downlink | 4 +item_tid | (41,125) +-[ RECORD 2 ]-------- +itemoffset | 2 +downlink | 3 +item_tid | (0,0) + +-- Failure with various modes. +-- Suppress the DETAIL message, to allow the tests to work across various +-- page sizes and architectures. +\set VERBOSITY terse +-- invalid page size +SELECT gin_leafpage_items('aaa'::bytea); +ERROR: invalid page size +SELECT gin_metapage_info('bbb'::bytea); +ERROR: invalid page size +SELECT gin_page_opaque_info('ccc'::bytea); +ERROR: invalid page size +-- invalid special area size +SELECT * FROM gin_metapage_info(get_raw_page('test1', 0)); +ERROR: input page is not a valid GIN metapage +SELECT * FROM gin_page_opaque_info(get_raw_page('test1', 0)); +ERROR: input page is not a valid GIN data leaf page +SELECT * FROM gin_leafpage_items(get_raw_page('test1', 0)); +ERROR: input page is not a valid GIN data leaf page +\set VERBOSITY default +-- Reject unsupported page types in gin_entrypage_items. +SELECT * FROM gin_entrypage_items(get_raw_page('test2_y_z_idx', 0), 'test2_y_z_idx'::regclass); +ERROR: gin_entrypage_items does not support metapages +-- Check the error message for the internal posting tree page. +SELECT * FROM gin_entrypage_items(get_raw_page('test_data_page_i_idx', 2), 'test_data_page_i_idx'::regclass); +ERROR: gin_entrypage_items does not support posting tree pages +HINT: This appears to be a GIN posting tree page. Please use gin_datapage_items. +-- insert new row to trigger new (fast-list) page allocation. +INSERT INTO test1 VALUES (1, ARRAY[11, 111], ARRAY['a', 'b', 'c']); +-- double check that the new page is fast-list. +SELECT * FROM gin_page_opaque_info(get_raw_page('test3_y_z_idx', 2)); +-[ RECORD 1 ]------------------ +rightlink | 3 +maxoff | 136 +flags | {list,list_fullrow} + +-- reject fast-list pages. +SELECT * FROM gin_entrypage_items(get_raw_page('test3_y_z_idx', 3), 'test3_y_z_idx'::regclass); +ERROR: gin_entrypage_items does not support fast list pages +-- Tests with all-zero pages. +SHOW block_size \gset +SELECT gin_leafpage_items(decode(repeat('00', :block_size), 'hex')); +-[ RECORD 1 ]------+- +gin_leafpage_items | + +SELECT gin_datapage_items(decode(repeat('00', :block_size), 'hex')); +(0 rows) + +SELECT gin_metapage_info(decode(repeat('00', :block_size), 'hex')); +-[ RECORD 1 ]-----+- +gin_metapage_info | + +SELECT gin_page_opaque_info(decode(repeat('00', :block_size), 'hex')); +-[ RECORD 1 ]--------+- +gin_page_opaque_info | + +DROP TABLE test1; diff --git a/contrib/pageinspect/ginfuncs.c b/contrib/pageinspect/ginfuncs.c index 3fe1412fe26..f51212a0280 100644 --- a/contrib/pageinspect/ginfuncs.c +++ b/contrib/pageinspect/ginfuncs.c @@ -11,18 +11,28 @@ #include "access/gin_private.h" #include "access/htup_details.h" +#include "access/relation.h" +#include "access/tupdesc.h" #include "catalog/pg_type.h" #include "funcapi.h" #include "miscadmin.h" #include "pageinspect.h" #include "utils/array.h" #include "utils/builtins.h" +#include "utils/lsyscache.h" +#include "utils/rel.h" +#include "utils/ruleutils.h" +#include "utils/tuplestore.h" PG_FUNCTION_INFO_V1(gin_metapage_info); PG_FUNCTION_INFO_V1(gin_page_opaque_info); +PG_FUNCTION_INFO_V1(gin_entrypage_items); PG_FUNCTION_INFO_V1(gin_leafpage_items); +PG_FUNCTION_INFO_V1(gin_datapage_items); +#define IS_INDEX(r) ((r)->rd_rel->relkind == RELKIND_INDEX) +#define IS_GIN(r) ((r)->rd_rel->relam == GIN_AM_OID) Datum gin_metapage_info(PG_FUNCTION_ARGS) @@ -175,6 +185,342 @@ typedef struct gin_leafpage_items_state GinPostingList *lastseg; } gin_leafpage_items_state; +/* + * gin_entrypage_items + * + * Allows inspection of contents of an entry tree page. + */ +Datum +gin_entrypage_items(PG_FUNCTION_ARGS) +{ + bytea *raw_page = PG_GETARG_BYTEA_P(0); + Oid indexRelid = PG_GETARG_OID(1); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + Relation indexRel; + OffsetNumber maxoff; + TupleDesc tupdesc; + Page page; + GinPageOpaque opaq; + StringInfoData buf; + + if (!superuser()) + ereport(ERROR, + errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("must be superuser to use raw page functions")); + + InitMaterializedSRF(fcinfo, 0); + + /* Open the index relation */ + indexRel = index_open(indexRelid, AccessShareLock); + + if (!IS_INDEX(indexRel) || !IS_GIN(indexRel)) + ereport(ERROR, + errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("\"%s\" is not a %s index", + RelationGetRelationName(indexRel), "GIN")); + + page = get_page_from_raw(raw_page); + + if (PageIsNew(page)) + { + index_close(indexRel, AccessShareLock); + PG_RETURN_NULL(); + } + + if (PageGetSpecialSize(page) != MAXALIGN(sizeof(GinPageOpaqueData))) + ereport(ERROR, + errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("input page is not a valid GIN entry tree page"), + errdetail("Expected special size %d, got %d.", + (int) MAXALIGN(sizeof(GinPageOpaqueData)), + (int) PageGetSpecialSize(page))); + + opaq = GinPageGetOpaque(page); + + /* we only support entry tree in this function, check that */ + if (opaq->flags & GIN_META) + ereport(ERROR, + errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("gin_entrypage_items does not support metapages")); + + if (opaq->flags & (GIN_LIST | GIN_LIST_FULLROW)) + ereport(ERROR, + errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("gin_entrypage_items does not support fast list pages")); + + if (opaq->flags & GIN_DATA) + ereport(ERROR, + errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("gin_entrypage_items does not support posting tree pages"), + errhint("This appears to be a GIN posting tree page. Please use gin_datapage_items.")); + + initStringInfo(&buf); + maxoff = PageGetMaxOffsetNumber(page); + + tupdesc = RelationGetDescr(indexRel); + + for (OffsetNumber offset = FirstOffsetNumber; + offset <= maxoff; + offset = OffsetNumberNext(offset)) + { + OffsetNumber indAtt; + Datum values[4]; + bool nulls[4] = {0}; + Datum attrVal; + bool isnull; + IndexTuple idxtuple; + ItemId iid = PageGetItemId(page, offset); + + if (!ItemIdIsValid(iid)) + ereport(ERROR, errcode(ERRCODE_INDEX_CORRUPTED), errmsg("invalid ItemId at offset %u", offset)); + + idxtuple = (IndexTuple) PageGetItem(page, iid); + + values[0] = UInt16GetDatum(offset); + + if (tupdesc->natts == 1) + { + indAtt = FirstOffsetNumber; + + /* Here we can safely reuse any tuple descriptor. */ + attrVal = index_getattr(idxtuple, FirstOffsetNumber, tupdesc, &isnull); + } + else + { + TupleDesc tmpTupdesc; + Datum res; + Form_pg_attribute attr; + + /* + * Multi-column GIN indexes store 2-attribute tuple on each page + * item. First attribute is which heap attribute is stored as the + * second value in pair. To display value with proper output + * function we need to recreate tuple descriptor on each offset. + * NB: It is safe to reuse the original index tuple. See also + * gintuple_get_attrnum. + */ + + res = index_getattr(idxtuple, FirstOffsetNumber, tupdesc, &isnull); + + /* + * we do not expect null for first attr in multi-column GIN + */ + if (isnull) + ereport(ERROR, + errcode(ERRCODE_INDEX_CORRUPTED), + errmsg("invalid gin entry page tuple at offset %u", offset)); + + indAtt = DatumGetUInt16(res); + + attr = TupleDescAttr(tupdesc, indAtt - 1); + + tmpTupdesc = CreateTemplateTupleDesc(2); + + TupleDescInitEntry(tmpTupdesc, (AttrNumber) 1, NULL, + INT2OID, -1, 0); + TupleDescInitEntry(tmpTupdesc, (AttrNumber) 2, NULL, + attr->atttypid, + attr->atttypmod, + attr->attndims); + TupleDescInitEntryCollation(tmpTupdesc, (AttrNumber) 2, + attr->attcollation); + + TupleDescFinalize(tmpTupdesc); + + attrVal = index_getattr(idxtuple, OffsetNumberNext(FirstOffsetNumber), + tmpTupdesc, + &isnull); + + FreeTupleDesc(tmpTupdesc); + } + + appendStringInfo(&buf, "%s=", quote_identifier(TupleDescAttr(tupdesc, indAtt - 1)->attname.data)); + + if (!isnull) + { + Oid foutoid; + bool typisvarlena; + Oid typoid; + char *value; + bool nq; + + /* + * The following value output and quoting logic is copied from + * record_out(). + */ + typoid = TupleDescAttr(tupdesc, indAtt - 1)->atttypid; + getTypeOutputInfo(typoid, &foutoid, &typisvarlena); + value = OidOutputFunctionCall(foutoid, attrVal); + + /* Check whether we need double quotes for this value */ + nq = (value[0] == '\0'); /* force quotes for empty string */ + for (const char *tmp = value; *tmp; tmp++) + { + char ch = *tmp; + + if (ch == '"' || ch == '\\' || + ch == '(' || ch == ')' || ch == ',' || + isspace((unsigned char) ch)) + { + nq = true; + break; + } + } + + /* And emit the string */ + if (nq) + appendStringInfoCharMacro(&buf, '"'); + for (const char *tmp = value; *tmp; tmp++) + { + char ch = *tmp; + + if (ch == '"' || ch == '\\') + appendStringInfoCharMacro(&buf, ch); + appendStringInfoCharMacro(&buf, ch); + } + if (nq) + appendStringInfoCharMacro(&buf, '"'); + } + else + { + appendStringInfo(&buf, "NULL"); + } + + values[3] = CStringGetTextDatum(buf.data); + resetStringInfo(&buf); + + if (GinIsPostingTree(idxtuple)) + { + values[1] = ItemPointerGetDatum(&idxtuple->t_tid); + nulls[2] = true; + } + else + { + int ndecoded; + Datum *tids_datum; + ItemPointer items_orig; + bool free_items_orig; + + values[1] = ItemPointerGetDatum(&idxtuple->t_tid); + /* Get list of item pointers from the tuple. */ + if (GinItupIsCompressed(idxtuple)) + { + items_orig = ginPostingListDecode((GinPostingList *) GinGetPosting(idxtuple), &ndecoded); + free_items_orig = true; + } + else + { + items_orig = (ItemPointer) GinGetPosting(idxtuple); + ndecoded = GinGetNPosting(idxtuple); + free_items_orig = false; + } + + tids_datum = palloc_array(Datum, ndecoded); + for (int i = 0; i < ndecoded; i++) + tids_datum[i] = ItemPointerGetDatum(&items_orig[i]); + values[2] = PointerGetDatum(construct_array_builtin(tids_datum, ndecoded, TIDOID)); + + pfree(tids_datum); + + if (free_items_orig) + pfree(items_orig); + } + + /* Build and return the result tuple. */ + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + } + + index_close(indexRel, AccessShareLock); + + return (Datum) 0; +} + +/* + * gin_datapage_items + * + * Allows inspection of contents of an posting tree non-leaf page. + */ +Datum +gin_datapage_items(PG_FUNCTION_ARGS) +{ + bytea *raw_page = PG_GETARG_BYTEA_P(0); + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + OffsetNumber maxoff; + Page page; + GinPageOpaque opaq; + + if (!superuser()) + ereport(ERROR, + errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("must be superuser to use raw page functions")); + + InitMaterializedSRF(fcinfo, 0); + page = get_page_from_raw(raw_page); + + if (PageIsNew(page)) + { + PG_RETURN_NULL(); + } + + if (PageGetSpecialSize(page) != MAXALIGN(sizeof(GinPageOpaqueData))) + ereport(ERROR, + errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("input page is not a valid GIN data page"), + errdetail("Expected special size %d, got %d.", + (int) MAXALIGN(sizeof(GinPageOpaqueData)), + (int) PageGetSpecialSize(page))); + + opaq = GinPageGetOpaque(page); + + /* + * Reject non-posting-tree-internal GIN pages, which are the metapage, fast + * list pages, entry tree pages and posting tree leaf pages. + */ + if (opaq->flags & GIN_META) + ereport(ERROR, + errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("gin_datapage_items does not support metapages")); + + if (opaq->flags & (GIN_LIST | GIN_LIST_FULLROW)) + ereport(ERROR, + errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("gin_datapage_items does not support fast list pages")); + + if (!(opaq->flags & GIN_DATA)) + ereport(ERROR, + errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("input page is not a GIN data tree page")); + + if (opaq->flags & GIN_LEAF) + ereport(ERROR, + errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("gin_datapage_items does not support posting tree leaf pages"), + errhint("This appears to be a GIN posting tree leaf page. Please use gin_leafpage_items.")); + + maxoff = opaq->maxoff; + + for (OffsetNumber offset = FirstOffsetNumber; + offset <= maxoff; + offset = OffsetNumberNext(offset)) + { + Datum values[3]; + bool nulls[3]; + PostingItem *item = GinDataPageGetPostingItem(page, offset); + + memset(nulls, 0, sizeof(nulls)); + + values[0] = UInt16GetDatum(offset); + + values[1] = UInt32GetDatum(BlockIdGetBlockNumber(&item->child_blkno)); + values[2] = ItemPointerGetDatum(&item->key); + + /* Build and return the result tuple. */ + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + } + + return (Datum) 0; +} + Datum gin_leafpage_items(PG_FUNCTION_ARGS) { diff --git a/contrib/pageinspect/meson.build b/contrib/pageinspect/meson.build index c43ea400a4d..2f333635838 100644 --- a/contrib/pageinspect/meson.build +++ b/contrib/pageinspect/meson.build @@ -38,6 +38,7 @@ install_data( 'pageinspect--1.10--1.11.sql', 'pageinspect--1.11--1.12.sql', 'pageinspect--1.12--1.13.sql', + 'pageinspect--1.13--1.14.sql', 'pageinspect.control', kwargs: contrib_data_args, ) diff --git a/contrib/pageinspect/pageinspect--1.13--1.14.sql b/contrib/pageinspect/pageinspect--1.13--1.14.sql new file mode 100644 index 00000000000..ef6fa87e0f4 --- /dev/null +++ b/contrib/pageinspect/pageinspect--1.13--1.14.sql @@ -0,0 +1,27 @@ +/* contrib/pageinspect/pageinspect--1.13--1.14.sql */ + +-- complain if script is sourced in psql, rather than via ALTER EXTENSION +\echo Use "ALTER EXTENSION pageinspect UPDATE TO '1.14'" to load this file. \quit + +-- +-- gin_entrypage_items() +-- +CREATE FUNCTION gin_entrypage_items(IN page bytea, IN reloid OID, + OUT itemoffset smallint, + OUT downlink tid, + OUT tids tid[], + OUT keys text) +RETURNS SETOF record +AS 'MODULE_PATHNAME', 'gin_entrypage_items' +LANGUAGE C STRICT PARALLEL SAFE; + +-- +-- gin_datapage_items() +-- +CREATE FUNCTION gin_datapage_items(IN page bytea, + OUT itemoffset smallint, + OUT downlink int, + OUT item_tid tid) +RETURNS SETOF record +AS 'MODULE_PATHNAME', 'gin_datapage_items' +LANGUAGE C STRICT PARALLEL SAFE; diff --git a/contrib/pageinspect/pageinspect.control b/contrib/pageinspect/pageinspect.control index cfc87feac03..aee3f598a9e 100644 --- a/contrib/pageinspect/pageinspect.control +++ b/contrib/pageinspect/pageinspect.control @@ -1,5 +1,5 @@ # pageinspect extension comment = 'inspect the contents of database pages at a low level' -default_version = '1.13' +default_version = '1.14' module_pathname = '$libdir/pageinspect' relocatable = true diff --git a/contrib/pageinspect/sql/gin.sql b/contrib/pageinspect/sql/gin.sql index b57466d7ebf..f19eef4b60f 100644 --- a/contrib/pageinspect/sql/gin.sql +++ b/contrib/pageinspect/sql/gin.sql @@ -1,6 +1,9 @@ -CREATE TABLE test1 (x int, y int[]); -INSERT INTO test1 VALUES (1, ARRAY[11, 111]); +CREATE TABLE test1 (x int, y int[], z text[]); +INSERT INTO test1 VALUES (1, ARRAY[11, 111], ARRAY['a', 'b', 'c']); +INSERT INTO test1 VALUES (2, ARRAY[NULL, 222], ARRAY['d', NULL]); CREATE INDEX test1_y_idx ON test1 USING gin (y) WITH (fastupdate = off); +CREATE INDEX test2_y_z_idx ON test1 USING gin (y, z) WITH (fastupdate = off); +CREATE INDEX test3_y_z_idx ON test1 USING gin (y, z) WITH (fastupdate = on); \x @@ -11,6 +14,10 @@ SELECT * FROM gin_page_opaque_info(get_raw_page('test1_y_idx', 1)); SELECT * FROM gin_leafpage_items(get_raw_page('test1_y_idx', 1)); +SELECT * FROM gin_entrypage_items(get_raw_page('test1_y_idx', 1), 'test1_y_idx'::regclass); + +SELECT * FROM gin_entrypage_items(get_raw_page('test2_y_z_idx', 1), 'test2_y_z_idx'::regclass); + INSERT INTO test1 SELECT x, ARRAY[1,10] FROM generate_series(2,10000) x; SELECT COUNT(*) > 0 @@ -18,6 +25,18 @@ FROM gin_leafpage_items(get_raw_page('test1_y_idx', (pg_relation_size('test1_y_idx') / current_setting('block_size')::bigint)::int - 1)); +-- Now test posting tree non-leaf page. +-- This requires inserting many tuples on a single leaf page to trigger page split. + +CREATE TABLE test_data_page(i INT[]); +CREATE INDEX test_data_page_i_idx ON test_data_page USING gin(i) WITH (fastupdate = off); + +INSERT INTO test_data_page SELECT ARRAY[1] FROM generate_series(1, 10000); + +-- For this index, block 0 is metapage, block 1 is entry tree, block 2 is +-- posting tree non-leaf page and block 3 & 4 are compressed data leaf pages. +SELECT * FROM gin_datapage_items(get_raw_page('test_data_page_i_idx', 2)); + -- Failure with various modes. -- Suppress the DETAIL message, to allow the tests to work across various -- page sizes and architectures. @@ -32,9 +51,21 @@ SELECT * FROM gin_page_opaque_info(get_raw_page('test1', 0)); SELECT * FROM gin_leafpage_items(get_raw_page('test1', 0)); \set VERBOSITY default +-- Reject unsupported page types in gin_entrypage_items. +SELECT * FROM gin_entrypage_items(get_raw_page('test2_y_z_idx', 0), 'test2_y_z_idx'::regclass); +-- Check the error message for the internal posting tree page. +SELECT * FROM gin_entrypage_items(get_raw_page('test_data_page_i_idx', 2), 'test_data_page_i_idx'::regclass); +-- insert new row to trigger new (fast-list) page allocation. +INSERT INTO test1 VALUES (1, ARRAY[11, 111], ARRAY['a', 'b', 'c']); +-- double check that the new page is fast-list. +SELECT * FROM gin_page_opaque_info(get_raw_page('test3_y_z_idx', 2)); +-- reject fast-list pages. +SELECT * FROM gin_entrypage_items(get_raw_page('test3_y_z_idx', 3), 'test3_y_z_idx'::regclass); + -- Tests with all-zero pages. SHOW block_size \gset SELECT gin_leafpage_items(decode(repeat('00', :block_size), 'hex')); +SELECT gin_datapage_items(decode(repeat('00', :block_size), 'hex')); SELECT gin_metapage_info(decode(repeat('00', :block_size), 'hex')); SELECT gin_page_opaque_info(decode(repeat('00', :block_size), 'hex')); diff --git a/doc/src/sgml/pageinspect.sgml b/doc/src/sgml/pageinspect.sgml index 3a113439e1d..4ed8826e92e 100644 --- a/doc/src/sgml/pageinspect.sgml +++ b/doc/src/sgml/pageinspect.sgml @@ -714,6 +714,60 @@ test=# SELECT first_tid, nbytes, tids[0:5] AS some_tids (170,30) | 376 | {"(170,30)","(170,31)","(170,32)","(170,33)","(170,34)"} (173,44) | 197 | {"(173,44)","(173,45)","(173,46)","(173,47)","(173,48)"} (7 rows) + + + + + + + + gin_entrypage_items(page bytea, reloid oid) returns setof record + + gin_entrypage_items + + + + + + gin_entrypage_items returns information about + the data stored in a entry tree GIN page. For example: + +test=# select * from gin_entrypage_items(get_raw_page('gin_test_idx', +1), 'gin_test_idx'::regclass); + itemoffset | downlink | tids | keys +------------+----------+------+------------------------------------ + 1 | (3,0) | {} | i=113 + 2 | (5,0) | {} | j=34173cb38f07f89ddbebc2ac9128303f + 3 | (2,0) | {} | j=a0a080f42e6f13b3a2df133f073095dd + 4 | (4,0) | {} | j=fc490ca45c00b1249bbe3554a4fdf6fb +(4 rows) + + + + + + + + gin_datapage_items(page bytea) returns setof record + + gin_datapage_items + + + + + + gin_datapage_items returns information about + the data stored in a posting tree GIN internal page. For example: + +test=# select * from gin_datapage_items(get_raw_page('gin_test_idx', +43)); + itemoffset | downlink | item_tid +------------+----------+---------- + 1 | 124 | (162,12) + 2 | 123 | (314,37) + 3 | 251 | (467,23) + 4 | 373 | (0,0) +(4 rows) -- 2.53.0 --=-=-= Content-Type: text/x-patch Content-Disposition: attachment; filename=v20260901-0003-Move-IS_INDEX-macro-to-pageinspect.h.patch From 49f164407c5b2311120c912680e496ffe82f404e Mon Sep 17 00:00:00 2001 From: reshke Date: Sat, 10 Jan 2026 14:11:37 +0000 Subject: [PATCH v20260901 3/3] Move IS_INDEX macro to pageinspect.h Suggested-by: Japin Li , Andrey Borodin Discussion: https://postgr.es/m/CALdSSPiN13n7feQcY0WCmq8jzxjwqhNrt1E=g=g6aZANyE_OoQ@mail.gmail.com --- contrib/pageinspect/btreefuncs.c | 7 +++---- contrib/pageinspect/ginfuncs.c | 5 ++--- contrib/pageinspect/hashfuncs.c | 5 ++--- contrib/pageinspect/pageinspect.h | 2 ++ 4 files changed, 9 insertions(+), 10 deletions(-) diff --git a/contrib/pageinspect/btreefuncs.c b/contrib/pageinspect/btreefuncs.c index 9917663593b..2e9e5b96bd0 100644 --- a/contrib/pageinspect/btreefuncs.c +++ b/contrib/pageinspect/btreefuncs.c @@ -49,8 +49,7 @@ PG_FUNCTION_INFO_V1(bt_page_stats_1_9); PG_FUNCTION_INFO_V1(bt_page_stats); PG_FUNCTION_INFO_V1(bt_multi_page_stats); -#define IS_INDEX(r) ((r)->rd_rel->relkind == RELKIND_INDEX) -#define IS_BTREE(r) ((r)->rd_rel->relam == BTREE_AM_OID) +#define IS_BTREE(r) (IS_INDEX(r) && (r)->rd_rel->relam == BTREE_AM_OID) /* ------------------------------------------------ * structure for single btree page statistics @@ -225,7 +224,7 @@ check_relation_block_range(Relation rel, int64 blkno) static void bt_index_block_validate(Relation rel, int64 blkno) { - if (!IS_INDEX(rel) || !IS_BTREE(rel)) + if (!IS_BTREE(rel)) ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("\"%s\" is not a %s index", @@ -858,7 +857,7 @@ bt_metap(PG_FUNCTION_ARGS) relrv = makeRangeVarFromNameList(textToQualifiedNameList(relname)); rel = relation_openrv(relrv, AccessShareLock); - if (!IS_INDEX(rel) || !IS_BTREE(rel)) + if (!IS_BTREE(rel)) ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("\"%s\" is not a %s index", diff --git a/contrib/pageinspect/ginfuncs.c b/contrib/pageinspect/ginfuncs.c index f51212a0280..b50807a251e 100644 --- a/contrib/pageinspect/ginfuncs.c +++ b/contrib/pageinspect/ginfuncs.c @@ -31,8 +31,7 @@ PG_FUNCTION_INFO_V1(gin_entrypage_items); PG_FUNCTION_INFO_V1(gin_leafpage_items); PG_FUNCTION_INFO_V1(gin_datapage_items); -#define IS_INDEX(r) ((r)->rd_rel->relkind == RELKIND_INDEX) -#define IS_GIN(r) ((r)->rd_rel->relam == GIN_AM_OID) +#define IS_GIN(r) (IS_INDEX(r) && (r)->rd_rel->relam == GIN_AM_OID) Datum gin_metapage_info(PG_FUNCTION_ARGS) @@ -213,7 +212,7 @@ gin_entrypage_items(PG_FUNCTION_ARGS) /* Open the index relation */ indexRel = index_open(indexRelid, AccessShareLock); - if (!IS_INDEX(indexRel) || !IS_GIN(indexRel)) + if (!IS_GIN(indexRel)) ereport(ERROR, errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("\"%s\" is not a %s index", diff --git a/contrib/pageinspect/hashfuncs.c b/contrib/pageinspect/hashfuncs.c index 30870329cc9..b9ec5bd34a3 100644 --- a/contrib/pageinspect/hashfuncs.c +++ b/contrib/pageinspect/hashfuncs.c @@ -28,8 +28,7 @@ PG_FUNCTION_INFO_V1(hash_page_items); PG_FUNCTION_INFO_V1(hash_bitmap_info); PG_FUNCTION_INFO_V1(hash_metapage_info); -#define IS_INDEX(r) ((r)->rd_rel->relkind == RELKIND_INDEX) -#define IS_HASH(r) ((r)->rd_rel->relam == HASH_AM_OID) +#define IS_HASH(r) (IS_INDEX(r) && (r)->rd_rel->relam == HASH_AM_OID) /* ------------------------------------------------ * structure for single hash page statistics @@ -420,7 +419,7 @@ hash_bitmap_info(PG_FUNCTION_ARGS) */ indexRel = relation_open(indexRelid, AccessShareLock); - if (!IS_INDEX(indexRel) || !IS_HASH(indexRel)) + if (!IS_HASH(indexRel)) ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("\"%s\" is not a %s index", diff --git a/contrib/pageinspect/pageinspect.h b/contrib/pageinspect/pageinspect.h index b241fdc97b2..7e5d28eeb4d 100644 --- a/contrib/pageinspect/pageinspect.h +++ b/contrib/pageinspect/pageinspect.h @@ -24,6 +24,8 @@ enum pageinspect_version PAGEINSPECT_V1_9, }; +#define IS_INDEX(r) ((r)->rd_rel->relkind == RELKIND_INDEX) + /* in rawpage.c */ extern Page get_page_from_raw(bytea *raw_page); -- 2.53.0 --=-=-=--