Commit 21a4e4a4 authored by Alvaro Herrera's avatar Alvaro Herrera

Fix BRIN free space computations

A bug in the original free space computation made it possible to
return a page which wasn't actually able to fit the item.  Since the
insertion code isn't prepared to deal with PageAddItem failing, a PANIC
resulted ("failed to add BRIN tuple [to new page]").  Add a macro to
encapsulate the correct computation, and use it in
brin_getinsertbuffer's callers before calling that routine, to raise an
early error.

I became aware of the possiblity of a problem in this area while working
on ccc4c074.  There's no archived discussion about it, but it's
easy to reproduce a problem in the unpatched code with something like

CREATE TABLE t (a text);
CREATE INDEX ti ON t USING brin (a) WITH (pages_per_range=1);

for length in `seq 8000 8196`
do
	psql -f - <<EOF
TRUNCATE TABLE t;
INSERT INTO t VALUES ('z'), (repeat('a', $length));
EOF
done

Backpatch to 9.5, where BRIN was introduced.
parent 531d21b7
...@@ -23,6 +23,16 @@ ...@@ -23,6 +23,16 @@
#include "utils/rel.h" #include "utils/rel.h"
/*
* Maximum size of an entry in a BRIN_PAGETYPE_REGULAR page. We can tolerate
* a single item per page, unlike other index AMs.
*/
#define BrinMaxItemSize \
MAXALIGN_DOWN(BLCKSZ - \
(MAXALIGN(SizeOfPageHeaderData + \
sizeof(ItemIdData)) + \
MAXALIGN(sizeof(BrinSpecialSpace))))
static Buffer brin_getinsertbuffer(Relation irel, Buffer oldbuf, Size itemsz, static Buffer brin_getinsertbuffer(Relation irel, Buffer oldbuf, Size itemsz,
bool *extended); bool *extended);
static Size br_page_get_freespace(Page page); static Size br_page_get_freespace(Page page);
...@@ -58,6 +68,18 @@ brin_doupdate(Relation idxrel, BlockNumber pagesPerRange, ...@@ -58,6 +68,18 @@ brin_doupdate(Relation idxrel, BlockNumber pagesPerRange,
Assert(newsz == MAXALIGN(newsz)); Assert(newsz == MAXALIGN(newsz));
/* If the item is oversized, don't bother. */
if (newsz > BrinMaxItemSize)
{
ereport(ERROR,
(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
errmsg("index row size %lu exceeds maximum %lu for index \"%s\"",
(unsigned long) newsz,
(unsigned long) BrinMaxItemSize,
RelationGetRelationName(idxrel))));
return false; /* keep compiler quiet */
}
/* make sure the revmap is long enough to contain the entry we need */ /* make sure the revmap is long enough to contain the entry we need */
brinRevmapExtend(revmap, heapBlk); brinRevmapExtend(revmap, heapBlk);
...@@ -332,6 +354,18 @@ brin_doinsert(Relation idxrel, BlockNumber pagesPerRange, ...@@ -332,6 +354,18 @@ brin_doinsert(Relation idxrel, BlockNumber pagesPerRange,
Assert(itemsz == MAXALIGN(itemsz)); Assert(itemsz == MAXALIGN(itemsz));
/* If the item is oversized, don't even bother. */
if (itemsz > BrinMaxItemSize)
{
ereport(ERROR,
(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
errmsg("index row size %lu exceeds maximum %lu for index \"%s\"",
(unsigned long) itemsz,
(unsigned long) BrinMaxItemSize,
RelationGetRelationName(idxrel))));
return InvalidOffsetNumber; /* keep compiler quiet */
}
/* Make sure the revmap is long enough to contain the entry we need */ /* Make sure the revmap is long enough to contain the entry we need */
brinRevmapExtend(revmap, heapBlk); brinRevmapExtend(revmap, heapBlk);
...@@ -360,9 +394,9 @@ brin_doinsert(Relation idxrel, BlockNumber pagesPerRange, ...@@ -360,9 +394,9 @@ brin_doinsert(Relation idxrel, BlockNumber pagesPerRange,
*/ */
if (!BufferIsValid(*buffer)) if (!BufferIsValid(*buffer))
{ {
do
*buffer = brin_getinsertbuffer(idxrel, InvalidBuffer, itemsz, &extended); *buffer = brin_getinsertbuffer(idxrel, InvalidBuffer, itemsz, &extended);
Assert(BufferIsValid(*buffer)); while (!BufferIsValid(*buffer));
Assert(extended || br_page_get_freespace(BufferGetPage(*buffer)) >= itemsz);
} }
else else
extended = false; extended = false;
...@@ -610,8 +644,9 @@ brin_page_cleanup(Relation idxrel, Buffer buf) ...@@ -610,8 +644,9 @@ brin_page_cleanup(Relation idxrel, Buffer buf)
/* /*
* Return a pinned and exclusively locked buffer which can be used to insert an * Return a pinned and exclusively locked buffer which can be used to insert an
* index item of size itemsz. If oldbuf is a valid buffer, it is also locked * index item of size itemsz (caller must ensure not to request sizes
* (in an order determined to avoid deadlocks.) * impossible to fulfill). If oldbuf is a valid buffer, it is also locked (in
* an order determined to avoid deadlocks.)
* *
* If we find that the old page is no longer a regular index page (because * If we find that the old page is no longer a regular index page (because
* of a revmap extension), the old buffer is unlocked and we return * of a revmap extension), the old buffer is unlocked and we return
...@@ -636,20 +671,8 @@ brin_getinsertbuffer(Relation irel, Buffer oldbuf, Size itemsz, ...@@ -636,20 +671,8 @@ brin_getinsertbuffer(Relation irel, Buffer oldbuf, Size itemsz,
Page page; Page page;
int freespace; int freespace;
/* /* callers must have checked */
* If the item is oversized, don't bother. We have another, more precise Assert(itemsz <= BrinMaxItemSize);
* check below.
*/
if (itemsz > BLCKSZ - sizeof(BrinSpecialSpace))
{
ereport(ERROR,
(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
errmsg("index row size %lu exceeds maximum %lu for index \"%s\"",
(unsigned long) itemsz,
(unsigned long) BLCKSZ - sizeof(BrinSpecialSpace),
RelationGetRelationName(irel))));
return InvalidBuffer; /* keep compiler quiet */
}
*extended = false; *extended = false;
...@@ -759,7 +782,7 @@ brin_getinsertbuffer(Relation irel, Buffer oldbuf, Size itemsz, ...@@ -759,7 +782,7 @@ brin_getinsertbuffer(Relation irel, Buffer oldbuf, Size itemsz,
* page that has since been repurposed for the revmap.) * page that has since been repurposed for the revmap.)
*/ */
freespace = *extended ? freespace = *extended ?
BLCKSZ - sizeof(BrinSpecialSpace) : br_page_get_freespace(page); BrinMaxItemSize : br_page_get_freespace(page);
if (freespace >= itemsz) if (freespace >= itemsz)
{ {
RelationSetTargetBlock(irel, BufferGetBlockNumber(buf)); RelationSetTargetBlock(irel, BufferGetBlockNumber(buf));
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment