Skip to content

Commit 5015d3c

Browse files
committed
Prevent infinite insertion loops in spgdoinsert().
Formerly we just relied on operator classes that assert longValuesOK to eventually shorten the leaf value enough to fit on an index page. That fails since the introduction of INCLUDE-column support (commit 09c1c6a), because the INCLUDE columns might alone take up more than a page, meaning no amount of leaf-datum compaction will get the job done. At least with spgtextproc.c, that leads to an infinite loop, since spgtextproc.c won't throw an error for not being able to shorten the leaf datum anymore. To fix without breaking cases that would otherwise work, add logic to spgdoinsert() to verify that the leaf tuple size is decreasing after each "choose" step. Some opclasses might not decrease the size on every single cycle, and in any case, alignment roundoff of the tuple size could obscure small gains. Therefore, allow up to 10 cycles without additional savings before throwing an error. (Perhaps this number will need adjustment, but it seems quite generous right now.) As long as we've developed this logic, let's back-patch it. The back branches don't have INCLUDE columns to worry about, but this seems like a good defense against possible bugs in operator classes. We already know that an infinite loop here is pretty unpleasant, so having a defense seems to outweigh the risk of breaking things. (Note that spgtextproc.c is actually the only known opclass with longValuesOK support, so that this is all moot for known non-core opclasses anyway.) Per report from Dilip Kumar. Discussion: https://postgr.es/m/CAFiTN-uxP_soPhVG840tRMQTBmtA_f_Y8N51G7DKYYqDh7XN-A@mail.gmail.com
1 parent 4c6cfcc commit 5015d3c

File tree

2 files changed

+62
-11
lines changed

2 files changed

+62
-11
lines changed

doc/src/sgml/spgist.sgml

+12
Original file line numberDiff line numberDiff line change
@@ -795,6 +795,18 @@ typedef struct spgLeafConsistentOut
795795
fails to do that, the <acronym>SP-GiST</acronym> core resorts to
796796
extraordinary measures described in <xref linkend="spgist-all-the-same">.
797797
</para>
798+
799+
<para>
800+
When <structfield>longValuesOK</structfield> is true, it is expected
801+
that successive levels of the <acronym>SP-GiST</acronym> tree will
802+
absorb more and more information into the prefixes and node labels of
803+
the inner tuples, making the required leaf datum smaller and smaller,
804+
so that eventually it will fit on a page.
805+
To prevent bugs in operator classes from causing infinite insertion
806+
loops, the <acronym>SP-GiST</acronym> core will raise an error if the
807+
leaf datum does not become any smaller within ten cycles
808+
of <function>choose</function> method calls.
809+
</para>
798810
</sect2>
799811

800812
<sect2 id="spgist-null-labels">

src/backend/access/spgist/spgdoinsert.c

+50-11
Original file line numberDiff line numberDiff line change
@@ -668,7 +668,8 @@ checkAllTheSame(spgPickSplitIn *in, spgPickSplitOut *out, bool tooBig,
668668
* will eventually terminate if lack of balance is the issue. If the tuple
669669
* is too big, we assume that repeated picksplit operations will eventually
670670
* make it small enough by repeated prefix-stripping. A broken opclass could
671-
* make this an infinite loop, though.
671+
* make this an infinite loop, though, so spgdoinsert() checks that the
672+
* leaf datums get smaller each time.
672673
*/
673674
static bool
674675
doPickSplit(Relation index, SpGistState *state,
@@ -1869,6 +1870,8 @@ spgdoinsert(Relation index, SpGistState *state,
18691870
int level = 0;
18701871
Datum leafDatum;
18711872
int leafSize;
1873+
int bestLeafSize;
1874+
int numNoProgressCycles = 0;
18721875
SPPageDesc current,
18731876
parent;
18741877
FmgrInfo *procinfo = NULL;
@@ -1894,22 +1897,24 @@ spgdoinsert(Relation index, SpGistState *state,
18941897
* Compute space needed for a leaf tuple containing the given datum.
18951898
*
18961899
* If it isn't gonna fit, and the opclass can't reduce the datum size by
1897-
* suffixing, bail out now rather than getting into an endless loop.
1900+
* suffixing, bail out now rather than doing a lot of useless work.
18981901
*/
18991902
if (!isnull)
19001903
leafSize = SGLTHDRSZ + sizeof(ItemIdData) +
19011904
SpGistGetTypeSize(&state->attType, leafDatum);
19021905
else
19031906
leafSize = SGDTSIZE + sizeof(ItemIdData);
19041907

1905-
if (leafSize > SPGIST_PAGE_CAPACITY && !state->config.longValuesOK)
1908+
if (leafSize > SPGIST_PAGE_CAPACITY &&
1909+
(isnull || !state->config.longValuesOK))
19061910
ereport(ERROR,
19071911
(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
19081912
errmsg("index row size %zu exceeds maximum %zu for index \"%s\"",
19091913
leafSize - sizeof(ItemIdData),
19101914
SPGIST_PAGE_CAPACITY - sizeof(ItemIdData),
19111915
RelationGetRelationName(index)),
19121916
errhint("Values larger than a buffer page cannot be indexed.")));
1917+
bestLeafSize = leafSize;
19131918

19141919
/* Initialize "current" to the appropriate root page */
19151920
current.blkno = isnull ? SPGIST_NULL_BLKNO : SPGIST_ROOT_BLKNO;
@@ -2137,19 +2142,53 @@ spgdoinsert(Relation index, SpGistState *state,
21372142
SpGistGetTypeSize(&state->attType, leafDatum);
21382143
}
21392144

2145+
/*
2146+
* Check new tuple size; fail if it can't fit, unless the
2147+
* opclass says it can handle the situation by suffixing.
2148+
*
2149+
* A buggy opclass might not ever make the leaf datum
2150+
* small enough, causing an infinite loop. To detect such
2151+
* a loop, check to see if we are making progress by
2152+
* reducing the leafSize in each pass. This is a bit
2153+
* tricky though. Because of alignment considerations,
2154+
* the total tuple size might not decrease on every pass.
2155+
* Also, there are edge cases where the choose method
2156+
* might seem to not make progress for a cycle or two.
2157+
* Somewhat arbitrarily, we allow up to 10 no-progress
2158+
* iterations before failing. (This limit should be more
2159+
* than MAXALIGN, to accommodate opclasses that trim one
2160+
* byte from the leaf datum per pass.)
2161+
*/
2162+
if (leafSize > SPGIST_PAGE_CAPACITY)
2163+
{
2164+
bool ok = false;
2165+
2166+
if (state->config.longValuesOK && !isnull)
2167+
{
2168+
if (leafSize < bestLeafSize)
2169+
{
2170+
ok = true;
2171+
bestLeafSize = leafSize;
2172+
numNoProgressCycles = 0;
2173+
}
2174+
else if (++numNoProgressCycles < 10)
2175+
ok = true;
2176+
}
2177+
if (!ok)
2178+
ereport(ERROR,
2179+
(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
2180+
errmsg("index row size %zu exceeds maximum %zu for index \"%s\"",
2181+
leafSize - sizeof(ItemIdData),
2182+
SPGIST_PAGE_CAPACITY - sizeof(ItemIdData),
2183+
RelationGetRelationName(index)),
2184+
errhint("Values larger than a buffer page cannot be indexed.")));
2185+
}
2186+
21402187
/*
21412188
* Loop around and attempt to insert the new leafDatum at
21422189
* "current" (which might reference an existing child
21432190
* tuple, or might be invalid to force us to find a new
21442191
* page for the tuple).
2145-
*
2146-
* Note: if the opclass sets longValuesOK, we rely on the
2147-
* choose function to eventually shorten the leafDatum
2148-
* enough to fit on a page. We could add a test here to
2149-
* complain if the datum doesn't get visibly shorter each
2150-
* time, but that could get in the way of opclasses that
2151-
* "simplify" datums in a way that doesn't necessarily
2152-
* lead to physical shortening on every cycle.
21532192
*/
21542193
break;
21552194
case spgAddNode:

0 commit comments

Comments
 (0)