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 1vg472-007oBm-0A for pgsql-hackers@arkaria.postgresql.org; Wed, 14 Jan 2026 16:52:53 +0000 Received: from localhost ([127.0.0.1] helo=malur.postgresql.org) by malur.postgresql.org with esmtp (Exim 4.96) (envelope-from ) id 1vg470-00BgcO-3B for pgsql-hackers@arkaria.postgresql.org; Wed, 14 Jan 2026 16:52:51 +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 1vg470-00BgcG-1f for pgsql-hackers@lists.postgresql.org; Wed, 14 Jan 2026 16:52:50 +0000 Received: from mail-australiasoutheastazolkn19010010.outbound.protection.outlook.com ([52.103.73.10] helo=MEUPR01CU001.outbound.protection.outlook.com) by magus.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.96) (envelope-from ) id 1vg46x-000Sl8-2Z for pgsql-hackers@postgresql.org; Wed, 14 Jan 2026 16:52:50 +0000 ARC-Seal: i=1; a=rsa-sha256; s=arcselector10001; d=microsoft.com; cv=none; b=fYgriaIzLzgBGwc3+lATIO21gBais6qFYabqm7z5QNIW9mq8mehajWbhz8MY0hbWS+pia0dhmv9lI1B2cxMLdrqq+qdDD1WlVXSHsOC5mzOwmkTNvIXnSfgPJvmbZnUWMQVBfV+rnsazp4YlH3sfKHNtBNh3gJtBSUOHDr5GwwT+KDMIMyC1qXEKTilBOC/Qg8AvG7FHs1dO7cCa979I5j4z0bvzyTBGURyq/dU/rN8BiEaXsw6tOTaL/ePAjumO48UD78xpRmKvxqV3rZoBS7JisOk0u6CKctuPjEu+7n/hEhjXY/qwIfYwtFn88vf9clI9UNoMLqwu12i/UkNVeA== 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=nZbAXZjQunYKy/VIU483MJejwjNqQM9e0DdbUP/njcw=; b=X3MErQsOP5BJ9qdn9SeqjhxpXWCZbsYUYfoK8x+roIQTVoddbWuxijpw7P2zc82N2D813Xuge45S5Wa8gccnY3d/Py6PbYh+A9UwuhzfFdJDG1S51P0zlG7r2nk7dvljaxjT5QIHFXP/K3gUqfy9nfJ17mbaXzhMCoDbcOAkTXZ/as9bKHwRln6s8jSNn7DLLWZE16AXSw7hsBZ+EaCbKGRjwzZBu7+flWNtJh8WzKJBHlBMvdfyT8Top0ZwQgghos1O9qH1nh3xCCVYiGTMZGlbpdY1FAbH9O6wU400qextIzLacfsWakg5I7cFRUGdEq7yqXVPMJz+VHhxRvol5g== 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=nZbAXZjQunYKy/VIU483MJejwjNqQM9e0DdbUP/njcw=; b=Ys1AJ40LDjPmEE30OOFa4WgKHhJwuFYCoWnFcWgBU303RM+vV277E6UkfkkY1YJd4COWlbJWdkS/p1bogIIV/RWHAWlxNK3ZhSBwWaKr8O2l4l/tCzBzcv7PBLl7yyIH4TG7BokvWZ6qs0p+jJ/mFW53Zjavg4atJakf19EdrZ54jGkhG7cwtCdik+FDDYZFua909nc2wYiF/oMHIzGvMFzG4UeoONlnvcyADHpW+BN1sItEbyNwJc8RVo4233ZnlQfdd7hfYuq5lfvWDLJqXWdIZn4Img8QBIAcQJCe9zx7IwNyYeL/QElaPtxS9F1pcapY+MBJXDK6MqalO53mNg== Received: from MEAPR01MB3031.ausprd01.prod.outlook.com (2603:10c6:201:e::21) by MEWPR01MB8726.ausprd01.prod.outlook.com (2603:10c6:220:1f7::22) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.20.9520.5; Wed, 14 Jan 2026 16:52:40 +0000 Received: from MEAPR01MB3031.ausprd01.prod.outlook.com ([fe80::5552:2d27:fd1b:84d4]) by MEAPR01MB3031.ausprd01.prod.outlook.com ([fe80::5552:2d27:fd1b:84d4%4]) with mapi id 15.20.9520.005; Wed, 14 Jan 2026 16:52:40 +0000 From: Japin Li To: Roman Khapov Cc: Kirill Reshke , Andrey Borodin , Chao Li , Peter Eisentraut , pgsql-hackers Subject: Re: GIN pageinspect support for entry tree and posting tree In-Reply-To: <219F1AAC-EBE3-492B-B7B0-B6E130D40DDE@yandex-team.ru> (Roman Khapov's message of "Wed, 14 Jan 2026 21:12:04 +0500") References: <921aa27c-e983-4577-b1dc-3adda3ce79da@eisentraut.org> <83BBD2B9-6DEF-42DE-9385-0B2BDC9533D3@yandex-team.ru> <9B839464-8FE5-4569-B07E-24F4B54FB7BF@gmail.com> <0A0E3993-7617-48EA-8555-6C1907C147E5@yandex-team.ru> <9CFF5492-B542-4BE7-9093-E32EBC7DDB1C@yandex-team.ru> <219F1AAC-EBE3-492B-B7B0-B6E130D40DDE@yandex-team.ru> User-Agent: mu4e 1.12.12; emacs 29.3 Date: Thu, 15 Jan 2026 00:52:35 +0800 Message-ID: Content-Type: multipart/mixed; boundary="=-=-=" X-ClientProxiedBy: TYCP286CA0240.JPNP286.PROD.OUTLOOK.COM (2603:1096:400:3c7::16) To MEAPR01MB3031.ausprd01.prod.outlook.com (2603:10c6:201:e::21) X-Microsoft-Original-Message-ID: <875x94qgq4.fsf@hotmail.com> MIME-Version: 1.0 X-MS-Exchange-MessageSentRepresentingType: 1 X-MS-PublicTrafficType: Email X-MS-TrafficTypeDiagnostic: MEAPR01MB3031:EE_|MEWPR01MB8726:EE_ X-MS-Office365-Filtering-Correlation-Id: 133a5ed0-79fa-40fc-f212-08de538d53b5 X-Microsoft-Antispam: BCL:0;ARA:14566002|5072599009|12121999013|6092099016|461199028|15080799012|19110799012|8060799015|23021999003|1602099012|20055399003|52005399003|40105399003|440099028|3412199025|4302099013; X-Microsoft-Antispam-Message-Info: =?utf-8?B?NVJmS0s3OERFMEh3MTFOTmVVZnZLVXJJUmNMamxiUzZDLzVWcmxoVVpUS3JD?= =?utf-8?B?VnBPeXBEY1FJV2hsYWtRWCs2VG45aHBDQTlqeU85bUdPcW11TnFJNSs2bUxI?= =?utf-8?B?M1pGZVdodXFsRWhyeUJUSVM3NXh4MnltNHBSM3M3MzlGSFc4a2FFZmVMREFh?= =?utf-8?B?UHFqdjlTZ25nMFM0cFh3RXVLWWx4TnVFY25penRSdGxPZWpuTXU4YU9GV0Nz?= =?utf-8?B?Y1YrVHF5WkN4MVd4MkVORHJ3T2VSblB1dWhaWEkyV0JHZEgxcDkvU2lKek1Z?= =?utf-8?B?eGRsTm50UDUxaGRubXdib3pBQ1A5Y0pVYlRoV0tSb1ZrNG9YeGVEeUpjWEJw?= =?utf-8?B?Z1BiQTJsc0FZM2c4Y0ozYWFUOUlPRS8wcTFJMkpLUlU3ZlM4eExoMjRjUzUr?= =?utf-8?B?TGd5OFY3R255Nnk5eTZYbjBLMWlnaFJWVXpGdlFqbDNYbFlzZmF1aGlMcjQx?= =?utf-8?B?Z3Y2T3B4aEkxbUI3cGZNa2htTWJpQ1BaRTZ1QzBPWmY3WC85bUx2Nk9qdGlT?= =?utf-8?B?Ti9xUVE3d3Z0TnRaZWp2UXRvYU5rZDV6cHI1eUtjWk5VK0k1ZE01NzdXai9P?= =?utf-8?B?Um1KT3VrVkJtZlpCVU9IMzVsaGcxazltUmdLaE5FUExnazFQVThBTEJ5ZUxY?= =?utf-8?B?aDRScVhvQVY3clppdFVHZXljMW96YWE1OHdtRmtONDVUeEEwNkVJcS8rSHJj?= =?utf-8?B?RGxyUlZydEVFVlpiTUJJcFA0QklUSWVkOXN6eGxkUGFBeDQ3SFZ2ankxNThV?= =?utf-8?B?R1loSU1JUytyY3lzRE5FRjZyK0RxT3VsdkdkRUZiTGc4enB4WlpWSi9XVjhp?= =?utf-8?B?bDYrOHRVOExwVmhUSHZVaXIzcGhnVkM3bmtXc2tPcE1aSGF1WDRmd2VTc0la?= =?utf-8?B?cjlrSTE2SXA1Q0tTYS9LU0VzeTg2TVBHNVliZ3gycHh6VHZwUGZhUlNEblY2?= =?utf-8?B?Q1o1cWFrOVRJR3lLRUxLS0RwczViQmkvRWdsYzQwMzV5aTV0N01UREkrWjh6?= =?utf-8?B?akxPZHIvUjRUSm1iV1ZvbjdTWkRaS3EzSlgyMHhiTUxrdjdjUzdEVC9CYmVs?= =?utf-8?B?UGkyY0N6Sy9iTzhJMmlSWWpIU1h0RjYxekxjYndheU1WTlF1dVBrbzBiR1hS?= =?utf-8?B?TitrN1IzY1UwZmNOZ1VBanBpL0hCUXNSQURMRFp6Q0xLblEra1FpM2dseUV6?= =?utf-8?B?bjFFTnFqSHlGbjExYUt0T0lrcllSUGZCRThPNHU0eGdaSWZLT1V6VERvWERj?= =?utf-8?B?a1NGcFlUbENWVHArT0xpUHhOYkNxUmc0MjFVU0JMY21vaHhYVHFranVvVHdH?= =?utf-8?B?ZGZFTkliQ0ZkWU5NbENqcy8xVlRmcWkybVRlbVZsRGswZVBBWGNDWlhPQXNv?= =?utf-8?B?UjBjdmttQ29uTElwTmhjbEJSdUNEei9WV2hFTDJ6S2w0YTVDVFJ5NHhSdkxm?= =?utf-8?B?M0dRUWpXTllkaWF5YUkrVE9BMjJQSHhzUng5bjJ3MmcyMnAwTjdtQS9lMFg4?= =?utf-8?B?MmpzQ2NmclVIYmQ5eFhMTDJ4dUQyZkw3WVNlRzNPcmI0b1VFaGxwM1k4RGlB?= =?utf-8?B?QnlIS1I4N1o5clBVU3ZzZFlxMUt1TCtqZ05aTTBRYmNmS0hkTzNnTEZJc2h3?= =?utf-8?B?R0Q2bG1VL2kwNmEyckF6cmV4VjgvZEx2ZUNhOThlWjBHUGxuQXpKS0tKcC9S?= =?utf-8?B?TFJKdER4TmFHZUhmcGdBeUFuamdGblEzalVFbUxkL0ZaaERCbEdLWXhGODVP?= =?utf-8?B?K1haK1k4NWpmYldYdG9jZFdwMWgvWHU4eThVTndlWm5UbEw3anc2bTBCOEhh?= =?utf-8?B?aTQvN2k1U1gxaUlIa0lGYU95Lyt6U3ZiRkpWNm1xYjRoeTBjVHgzS3R5S3l6?= =?utf-8?Q?yE5lj9NFZbkZ0?= X-MS-Exchange-AntiSpam-MessageData-ChunkCount: 1 X-MS-Exchange-AntiSpam-MessageData-0: =?utf-8?B?eWgwbU9WejRreC9SYVJRVTlnSzFVVmJJTmp1UWRvU2xPTHpwOWt3cVNBOS9C?= =?utf-8?B?a0xYdUFULzNsVmRuZlRsZTJpNGphamJNN0dlV0dhZzhMV0llRm52dGNHai90?= =?utf-8?B?Skk4TmFjeVNYR2VTWS9hZ0FvbnRDZEFNNlhiTCt3Umx1aUUxMUdlS1JTeHdy?= =?utf-8?B?a00za1lRWkhjVC9vWnJiVlV1OTNHdVI4Z1NLc3RiMmRtQkpZcm5PdXFlc3VN?= =?utf-8?B?eUxVM2lmb3htSlQvaS9YOE42RWVuWVdIOUU5Rk82N2Z6RXRiOCsrVytQb1hj?= =?utf-8?B?NDk1ak55cXg5VVB6OVpEYUpTTEdSWWh3Vkh4WFkxWHNqei8wVnpkTk9lU1V4?= =?utf-8?B?MEYxOXdHWjlFMGNuVVNldVIxK1lOczE0RnM3V0FvL3BQOHBJQU1US3RMQ0ZW?= =?utf-8?B?QWlHM2g5VHhXN1lVa2s1TEdFRHpjWHhlRm5GRVBMVW5rR0grVDlvZVlWUHRM?= =?utf-8?B?MEN4K1dVVVBCYm1uVE5yZDdMSXd1Ry81Z0JLZW9QSWR0TFBMRjVRRjczTndB?= =?utf-8?B?UGJ1UWozN01zSnpNeVF0SHNCV2dGczl1OGcwemI4ZUk4R3plQmN0c21aY0lX?= =?utf-8?B?RUZYM1ZxbUlCeHlzNEpDaEtndFdNRkJTdUNhQTdJQTdKck1xWmZKN0dyNEth?= =?utf-8?B?bzVHVVhrODVBd2NyS0NucjlFTG5OSXQvbUJrTDNCMklPaDg1aXZFSTdxejRB?= =?utf-8?B?TGxzSzcyV25TU05oRUVtMk1yWEhwS01RT2x5eDlzQkF4V0NKK1dTUEszMStI?= =?utf-8?B?Ni9rNWhnR05FVU1TUFRCYmlpN2dLRExEd1FDUFhjc2Y3NXo0NWlKdE5tQVA2?= =?utf-8?B?a3hHMytLbXJ2dW5hSnVzeStWYzQvNzNZQzVLdk9FUzM3NmpqYWJYRHZKczJx?= =?utf-8?B?Qys3eTI3TmhrcWFQeU04dnpKc3ZrbGxtdU4yajdxbGVrbURka3JwTzB1ZTdO?= =?utf-8?B?a0cwVEFRem9zRU5qS3REbzZIRktvSUU1aFpobEJGQ0taV3QvVG8zOUswOFJ2?= =?utf-8?B?UzQzVEhBdFh3N1RyYUJUSnluUmdiUEx4cnl3aHdmOEIzRGFXWGhidC9ZNjBQ?= =?utf-8?B?cUJNWWE4Nkh6YzM2NlVNOEJSODZNQytxcC80VUJYVVBIWVVoTjVuUE12UEx1?= =?utf-8?B?clhNK3NYQ3djQzV4czkydVRBbWhZWUI5UUxiZmt6dGliTVJ3VFE4ZFlkV3BK?= =?utf-8?B?VWxoV0VyMFphcnltK0owKzRZVDRoalNuRkc1SVFqblpHNCtYakYzb0ZiOE03?= =?utf-8?B?ZGdHSVRkSEI2cm9GSk1rNUdMN2duZWhYV05yM0IyUzErOEpsbWhOZjZMM0hx?= =?utf-8?B?WnBjdUVaU2xaNmVnVi9rMy8xSGE2TENDTGtlVDlpVTRuYVFSczRycFliOExH?= =?utf-8?B?dndaeHlldlZYWjcrWGVFc2ZKVzF0bVVkK3k3aFg2WlVRS1AyTFI1a2xHRGIx?= =?utf-8?B?M3hEOUxkN2t0Q3hFaWlzOHdMTm12bUc0WEwrZmQ2Y3lzWlhUekZvRnVQVFJE?= =?utf-8?B?TFhwTjVRcTdIYkJUWTM2SVZoWEM5VUFhcEEvV3hPZTBpWWFLdEJLK0hOTDN3?= =?utf-8?B?M0NWTUJEa3h5dXBCMUQzcHQ4MmRpUnN3Z3Boa3FjT2NFT1JmUk8xTjgwUGxo?= =?utf-8?B?eE1Gb1I3N2xhc3VVN2RiSTlJTFlGdXI1VzVka0tnUWVJSEcxck9OOWRoTDVU?= =?utf-8?B?R0E2TVk1R0ttVmFmY0VCeG5BRWxGMnVBUDFKTldWeWVmNVl4TEp5dXAwSUFw?= =?utf-8?B?czdZVDc0WHpPSm9sL0RicTUvdjhPM2k4OWZzcytIbUo3bk5WeG14YW5RTEFR?= =?utf-8?B?TzBKU2YxRGU2MnFINDJ3MmhFOUgzdzlEOHJkM3NWTVlnZncwbEpJdCtMRzhv?= =?utf-8?Q?+bwiWOJtD2aH2?= X-OriginatorOrg: sct-15-20-9412-4-msonline-outlook-a559e.templateTenant X-MS-Exchange-CrossTenant-Network-Message-Id: 133a5ed0-79fa-40fc-f212-08de538d53b5 X-MS-Exchange-CrossTenant-AuthSource: MEAPR01MB3031.ausprd01.prod.outlook.com X-MS-Exchange-CrossTenant-AuthAs: Internal X-MS-Exchange-CrossTenant-OriginalArrivalTime: 14 Jan 2026 16:52:40.4431 (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: MEWPR01MB8726 List-Id: List-Help: List-Subscribe: List-Post: List-Owner: List-Archive: Archived-At: Precedence: bulk --=-=-= Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: quoted-printable On Wed, 14 Jan 2026 at 21:12, Roman Khapov wrote: >> Sorry, I=E2=80=99m not sure I understand. > > Oh, my mail app replaced 'z' with 'zero', sorry.. > >> INSERT INTO test1 VALUES (2, ARRAY[NULL, 222], ARRAY['d', NULL]); > > Yes, I meant that scenario, when z contains NULL. > I've added the test case in v16-0002. PFA. For v16-0003, I've kept it here as well for now. However, it may become unnecessary once [0] is committed. [0] https://www.postgresql.org/message-id/MEAPR01MB3031A889D4B3F610E9D2A3AF= B68FA@MEAPR01MB3031.ausprd01.prod.outlook.com --=20 Regards, Japin Li ChengDu WenWu Information Technology Co., Ltd. --=-=-= Content-Type: text/x-patch Content-Disposition: attachment; filename=v16-0001-Modernize-coding-in-GIN-pageinspect-functions.patch From af848136041346e7a82696ec647b4b5715906307 Mon Sep 17 00:00:00 2001 From: reshke Date: Wed, 7 Jan 2026 19:38:52 +0000 Subject: [PATCH v16 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 | 58 +++++++++++++++++----------------- 1 file changed, 29 insertions(+), 29 deletions(-) diff --git a/contrib/pageinspect/ginfuncs.c b/contrib/pageinspect/ginfuncs.c index b6574083b0a..33c0620a5c1 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) @@ -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); @@ -262,7 +262,7 @@ gin_leafpage_items(PG_FUNCTION_ARGS) /* build an array of decoded item pointers */ tids = ginPostingListDecode(cur, &ndecoded); - tids_datum = (Datum *) palloc(ndecoded * sizeof(Datum)); + tids_datum = palloc_array(Datum, ndecoded); for (i = 0; i < ndecoded; i++) tids_datum[i] = ItemPointerGetDatum(&tids[i]); values[2] = PointerGetDatum(construct_array_builtin(tids_datum, ndecoded, TIDOID)); -- 2.43.0 --=-=-= Content-Type: text/x-patch Content-Disposition: attachment; filename=v16-0002-GIN-pageinspect-support-for-entry-tree-and-posti.patch From fc40c729eb0167cfc42877fdf82a11b72364d2e8 Mon Sep 17 00:00:00 2001 From: reshke Date: Mon, 13 Oct 2025 20:14:26 +0000 Subject: [PATCH v16 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 33c0620a5c1..f0466bc10c3 100644 --- a/contrib/pageinspect/ginfuncs.c +++ b/contrib/pageinspect/ginfuncs.c @@ -11,18 +11,27 @@ #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" 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 +184,343 @@ 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); + + 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 leaf page"), + errdetail("Expected special size %d, got %d.", + (int) MAXALIGN(sizeof(GinPageOpaqueData)), + (int) PageGetSpecialSize(page)))); + + opaq = GinPageGetOpaque(page); + + /* + * Reject non-entry-tree GIN pages, which are metapage, fastlist pages, + * and posting tree pages. + */ + + if (opaq->flags & (GIN_META)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("gin_datapage_items is unsupported for metapage"))); + + if (opaq->flags & (GIN_LIST | GIN_LIST_FULLROW)) + ereport(ERROR, + errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("gin_datapage_items is unsupported for GIN fast update list")); + + 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("input page is a GIN data leaf tree page"), + errhint("This appears to be a GIN posting leaf tree page. Please use gin_leafpage_items.")); + + maxoff = GinPageGetOpaque(page)->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.43.0 --=-=-= Content-Type: text/x-patch Content-Disposition: attachment; filename=v16-0003-Move-IS_INDEX-macro-to-pageinspect.h.patch From eb900cf7a77bb7fd03cd881633d8f6fb31fff907 Mon Sep 17 00:00:00 2001 From: reshke Date: Sat, 10 Jan 2026 14:11:37 +0000 Subject: [PATCH v16 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 0585b7cee40..a49a9beee68 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 f0466bc10c3..d0954312187 100644 --- a/contrib/pageinspect/ginfuncs.c +++ b/contrib/pageinspect/ginfuncs.c @@ -30,8 +30,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) @@ -212,7 +211,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 7fc97d043ce..86e73b36b41 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 @@ -421,7 +420,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.43.0 --=-=-=--