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 1wPkym-0010LN-0F for pgsql-hackers@arkaria.postgresql.org; Wed, 20 May 2026 17:45:12 +0000 Received: from localhost ([127.0.0.1] helo=malur.postgresql.org) by malur.postgresql.org with esmtp (Exim 4.96) (envelope-from ) id 1wPkyj-008BC6-2E for pgsql-hackers@arkaria.postgresql.org; Wed, 20 May 2026 17:45:10 +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 1wPkyj-008BBy-0m for pgsql-hackers@lists.postgresql.org; Wed, 20 May 2026 17:45:10 +0000 Received: from mail-pl1-x630.google.com ([2607:f8b0:4864:20::630]) by makus.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 (Exim 4.98.2) (envelope-from ) id 1wPkyf-00000000WMJ-1VGb for pgsql-hackers@lists.postgresql.org; Wed, 20 May 2026 17:45:07 +0000 Received: by mail-pl1-x630.google.com with SMTP id d9443c01a7336-2bd266f6fc0so25953515ad.2 for ; Wed, 20 May 2026 10:45:05 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=enterprisedb.com; s=google; t=1779299105; x=1779903905; darn=lists.postgresql.org; h=to:date:message-id:subject:mime-version:content-transfer-encoding :from:from:to:cc:subject:date:message-id:reply-to; bh=OVBbV9lO/GGa9iYDJ9CM+EAZ8+NqtclUv0EFsKowY5U=; b=GUkwL7ny53HAigbxKkBJqofWkuBablhqa+ixiaYxI08YFuipCZd/J1Iuy6DHoZbVh9 ZzcYhRWzXHL1QTiQv5OBJ9Jhgut7Us87X6lq2tfPVLX6mY1zRtK4uW1+NMFfQlVW42RQ mbzHkQpGJQWen603lbxmgD+a/PNG2pShWR+NRKcmh4XUcm+4dy4wg9bok7YbhiAI723q ztcQ9d8sZ3wK9fCDEiJmF4fbGfxY8SuE5TY9QZXQMejT9U0vBqVEdof0YdH5Y0JA8VZp q980YSP87DC8ROJbfhoZEkJcnxgEa/qBK3zCJI8YMq5mxXav6aEglSKlRbwvUjTLeMQi cXQQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1779299105; x=1779903905; h=to:date:message-id:subject:mime-version:content-transfer-encoding :from:x-gm-gg:x-gm-message-state:from:to:cc:subject:date:message-id :reply-to; bh=OVBbV9lO/GGa9iYDJ9CM+EAZ8+NqtclUv0EFsKowY5U=; b=M07tGEC0xnKdcqrUiyoM6soaENygOLpEou6OLGeW+8EHDTDIVyEPrFbuJTQSL9tu6z ZCzIF0OOX9pgy7oFtgsI5PaNZl8Y6qemZ2xe1gFTf/SOCklG6HfcR6KRIRvklltvadO5 KL8518AKYbf3ypXDGZZ3szoWsG0Zp9OcR/U3X7EIoMJ7qZnC+8Zcw1Z1RjrRSDZpEGz4 zAB0gkI8zpCXFntgkvKlkI/s/arZuPL4Qh5TpQnYSSZKaHvURgHkflV1AwtmhkV/wrzK jrinyhoxD4sqsfeqYZMRFE4BiUvndax0rL4jVqGC+lCLP6fUjEPC3zqJyQj2UqcLMEFg W+bw== X-Gm-Message-State: AOJu0Yzy5OCpAya5wTTDy7W2Gv6d1U2s7JlcPlb75qS+YnXmp3jC74tk wobntbSSQbhAYTQ1lUtTgwfxYfrCcv6xkp66vg+3VAaVmv23pd10bNqpSdJ6sOsRi5L2ldigcxf l9k6OqNSq6+6q5P9avRIRvbWjigNLpCIDzA1tEMNZ5gmbLXiJcxLElcDJBQuoOaVbikb1lrENoA HAxODylm9H7nxDKC5JipMbMp5YZHAZ6h6URb3CO6P9MrbS+sOi4wO3M/MoYWChmBEuh0aH4nb9O A== X-Gm-Gg: Acq92OHkkktbi5gL3MVlWbmm2cVO5JnSuXeImqyonIsxQ3/D13aiJMhGUkbxk3thbqX peIJR0K4+aDMhYUMJ1uFYJ7+KI9jpv3eNyRKu8mW1MMY8x+hqPvt2RkueX58nULnXel77gHD2Aw KOP28x94rWNuR3iAuIBUwmJ/ECABWjE/sTnrWxu9n18B1qjiaA8VrnHUOQbo8UdzWDE+sGyo2FJ BBj1kl1LrBEAQs4stmdI98MieD4LRtAubpSZQHur3+eiEPefg60viFdXTbWxdRrMjpEaYBeWrIZ uXyNQcnu8cNR+cH0LwLCizzkseL6Rm8JZ9LqlZhwVYsLkLO7SOte4gfCnEcbVc4C0VbiP/Dnfgx 6oEdNYYMTBNJUj2THjojw6STDPeS96+cpJVE8qzVM5vMm/kZOYfRAT3feH8vnLxwgdSzW7P+VRA 0pIFJh9wS/QvvO6LMPW8sKl/EYCtxv6mpyN4NXiz/RjrmFrSPj4HucVpEpb2UJUwRP X-Received: by 2002:a17:902:7892:b0:2bd:63dc:b7ad with SMTP id d9443c01a7336-2bd7e86c6b1mr197059795ad.2.1779299104271; Wed, 20 May 2026 10:45:04 -0700 (PDT) Received: from smtpclient.apple ([163.116.141.145]) by smtp.gmail.com with ESMTPSA id d9443c01a7336-2bd5bd5f2cesm231127165ad.14.2026.05.20.10.45.03 for (version=TLS1_2 cipher=ECDHE-ECDSA-AES128-GCM-SHA256 bits=128/128); Wed, 20 May 2026 10:45:03 -0700 (PDT) From: Maxime Schoemans Content-Type: text/plain; charset=us-ascii Content-Transfer-Encoding: quoted-printable Mime-Version: 1.0 (Mac OS X Mail 16.0 \(3864.500.181\)) Subject: Multi-Entry Indexing for GiST & SP-GiST Message-Id: <8E23543E-F1E4-40AE-BDA0-8A5BEA7DADA1@enterprisedb.com> Date: Wed, 20 May 2026 10:44:52 -0700 To: pgsql-hackers@lists.postgresql.org X-Mailer: Apple Mail (2.3864.500.181) List-Id: List-Help: List-Subscribe: List-Post: List-Owner: List-Archive: Archived-At: Precedence: bulk Hi hackers, This patch set adds multi-entry support to the GiST and SP-GiST access methods, allowing a single heap tuple to produce multiple index entries = -- similar to how GIN decomposes values via extractValue, but within the R-tree (GiST) and quad-tree (SP-GiST) frameworks. I presented the concept and an extension-based prototype (MEST) at PGConf.dev 2024 [1]. This patch set integrates the feature directly into the GiST and SP-GiST access methods rather than maintaining a separate forked AM. Motivation ---------- The existing GiST multirange opclass compresses a multirange into its bounding union range, losing information about gaps. This makes = operators like @> and <@ imprecise at the index level, requiring many = false-positive rechecks. Multi-entry GiST instead stores each component range as a separate index entry, giving the R-tree much tighter bounds and significantly reducing rechecks for multiranges with gaps. SP-GiST currently has no multirange opclass at all. The multi-entry infrastructure lets us add one: multiranges are decomposed into = component ranges and indexed using the existing range quad-tree, providing SP-GiST multirange support for the first time. The approach generalizes beyond multiranges: any composite data type = that can be meaningfully decomposed into simpler sub-entries can benefit = (e.g., arrays of geometric types, route geometries). Design ------ A new optional support function, extractValue, is added to both GiST (proc 13) and SP-GiST (proc 8). When an opclass provides it, the insert and build paths call it to decompose each datum into multiple = sub-entries, each stored as a separate index tuple pointing to the same heap TID. The signature mirrors GIN's: Datum *extractValue(Datum value, int32 *nentries, bool **nullFlags) The opclass returns a palloc'd array of sub-entries (typed as the index key column's type), sets *nentries, and optionally fills *nullFlags. If it returns zero entries for a non-NULL input, the AM falls back to a single NULL index entry (matching only IS NULL); opclasses that want such values to remain visible to operator queries should return a sentinel instead -- e.g., multirange_me_ops stores an empty range for empty multiranges. On the scan side, a simplehash-based TID hash deduplicates results so each heap tuple is returned only once. For ordered (KNN) scans the same hash is also consulted before enqueuing leaf items as an early filter, but the dedup insert happens at dequeue time so the pairing heap can pick the copy with the smallest distance. Semantics for the opclass author: - Leaf consistent functions see one sub-entry at a time, not the full value, so most strategies must set recheck=3Dtrue. Strategies that are exact per-component (for multiranges: OVERLAPS and CONTAINS_ELEM) can skip it. - Internal-node keys are unions of sub-entries drawn from many heap tuples, so containment-style strategies (CONTAINS, EQ) must relax to OVERLAPS during descent: a matching value's components may live in multiple subtrees, and requiring the union key to fully contain the query would prune valid ones. Other design decisions: - extractValue is fully optional; existing opclasses are unaffected. - Multi-entry is restricted to single-key-column indexes (INCLUDE columns are fine); multi-column support can be added later. - Index-only scans are disabled on a multi-entry key column, since the stored sub-entries do not represent the original datum. - For SP-GiST, compress becomes optional when extractValue is present: extractValue produces the leaf-typed values directly, and leafType may differ from the input type (e.g., anymultirange -> anyrange). - Since leaf consistent functions see components, a separate opclass (multirange_me_ops) is provided alongside the existing multirange_ops. Patch overview -------------- 0001 - GiST AM infrastructure (gist.h, gist_private.h, gist.c, gistbuild.c, gistget.c, gistscan.c, gistutil.c, gistvalidate.c): new extractValue support function (proc 13), multi-entry insert/build = paths, TID deduplication during scans using simplehash. 0002 - Built-in GiST multirange_me_ops opclass (rangetypes_gist.c, = catalog files): decomposes multiranges into component ranges, with multi-entry consistent functions. Empty multiranges are stored as empty range sentinels to remain visible to operator queries. Regression tests = verify index correctness against sequential scan results for all operators = across index scan and bitmap scan, including empty range/multirange edge = cases, multi-column restriction, and NULL handling. 0003 - SP-GiST AM infrastructure (spgist.h, spgist_private.h, = spginsert.c, spgscan.c, spgvalidate.c, spgutils.c): new extractValue support = function (proc 8), multi-entry insert/build paths, TID deduplication during = scans using simplehash, compress made optional when extractValue is present. 0004 - Built-in SP-GiST multirange_me_ops opclass (rangetypes_spgist.c, catalog files): reuses the range quad-tree structure with new config, inner consistent, and leaf consistent functions that handle range, multirange, and element query types. Marked non-default, consistent with the GiST multirange_me_ops. Regression tests verify index correctness against sequential scan results for all operators across index scan and bitmap scan. Quick benchmark --------------- 100k multiranges with wide gaps: {[g, g+10), [g+100000, g+100010)}. Query: mr @> 100000 (matches 10 rows; value falls in the gap for most). CREATE TABLE bench_mr (mr int4multirange); INSERT INTO bench_mr SELECT int4multirange(int4range(g, g+10), int4range(g+100000, = g+100010)) FROM generate_series(1, 100000) g; Method Exec time Buffers Recheck Sequential scan 7.732 ms 834 - GiST multirange_ops (std) 9.504 ms 2311 99990 GiST multirange_me_ops 0.056 ms 6 0 SP-GiST multirange_me_ops 0.112 ms 27 0 This is a worst case for the standard opclass: the wide gap between components produces a bounding range [g, g+100010) that covers the query point for nearly every row, requiring 99990 heap rechecks and making it slower than a sequential scan. Multi-entry indexes store each component range separately, eliminating all false positives. The improvement grows with gap width; for multiranges with little or no gap the standard opclass performs comparably. [1] = https://www.pgevents.ca/events/pgconfdev2024/schedule/session/171-multi-en= try-generalized-search-trees/ -- Maxime Schoemans=